diff --git a/.eslintignore b/.eslintignore index 18115e8..3dd49f8 100644 --- a/.eslintignore +++ b/.eslintignore @@ -2,3 +2,4 @@ node_modules .yarn build dist +**/*.generated.* \ No newline at end of file diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 5752d50..f9731c8 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -49,8 +49,8 @@ jobs: echo "::set-output name=enable_demo_features::false" else echo "::set-output name=app_env::dev" - echo "::set-output name=testnet_squid_api_url::https://subsquid.squids.live/subsquid-network-testnet/v/v3/graphql" - echo "::set-output name=mainnet_squid_api_url::https://subsquid.squids.live/subsquid-network-mainnet/v/v3/graphql" + echo "::set-output name=testnet_squid_api_url::https://subsquid.squids.live/subsquid-network-testnet/v/v5/graphql" + echo "::set-output name=mainnet_squid_api_url::https://subsquid.squids.live/subsquid-network-mainnet/v/v5/graphql" echo "::set-output name=enable_demo_features::true" fi if [ "$NETWORK" = "mainnet" ]; then diff --git a/src/api/subsquid-network-squid/graphql.config.ts b/graphql.config.ts similarity index 81% rename from src/api/subsquid-network-squid/graphql.config.ts rename to graphql.config.ts index 93c2878..199c1ed 100644 --- a/src/api/subsquid-network-squid/graphql.config.ts +++ b/graphql.config.ts @@ -6,8 +6,9 @@ import { CodegenConfig } from '@graphql-codegen/cli'; export default { overwrite: true, schema: - process.env.SQUID_API_URL || 'https://subsquid.squids.live/subsquid-network-mainnet/graphql', - documents: ['src/api/subsquid-network-squid/*.graphql'], + process.env.SQUID_API_URL || + 'https://subsquid.squids.live/subsquid-network-testnet/v/v5/graphql', + documents: ['src/api/subsquid-network-squid/schema.graphql'], hooks: { afterOneFileWrite: ['prettier --write'], }, diff --git a/package.json b/package.json index 7130a99..8d1b51a 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "preview": "vite preview --outDir build --host 127.0.0.1 --port 3005", "upg": "yarn upgrade-interactive", "prepare": "husky install", - "codegen": "graphql-codegen --config src/api/subsquid-network-squid/graphql.config.ts" + "codegen": "graphql-codegen --config graphql.config.ts" }, "lint-staged": { "src/**/*.{js,jsx,ts,tsx}": "eslint --cache --fix" @@ -39,11 +39,11 @@ "graphql": "^16.9.0", "lodash-es": "^4.17.21", "material-ui-popup-state": "^5.1.2", - "notistack": "^3.0.1", "pretty-bytes": "^6.1.1", "qs": "^6.12.1", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-hot-toast": "^2.4.1", "react-router-dom": "^6.24.0", "react-scroll": "^1.9.0", "react-syntax-highlighter": "^15.5.0", @@ -52,8 +52,8 @@ "use-debounce": "^10.0.1", "use-element-position": "^1.0.13", "use-local-storage-state": "^19.3.1", - "viem": "^2.16.5", - "wagmi": "^2.10.8", + "viem": "^2.21.25", + "wagmi": "^2.12.17", "yup": "^1.4.0" }, "devDependencies": { @@ -76,6 +76,7 @@ "@typescript-eslint/eslint-plugin": "^7.14.1", "@typescript-eslint/parser": "^7.14.1", "@vitejs/plugin-react": "^4.3.1", + "@wagmi/cli": "^2.1.16", "dotenv": "^16.4.5", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", @@ -90,8 +91,8 @@ "husky": "^9.0.11", "lint-staged": "^15.2.7", "prettier": "^3.3.2", - "type-fest": "^4.20.1", - "typescript": "^5.5.2", + "type-fest": "^4.26.1", + "typescript": "^5.6.3", "vite": "^5.4.2", "vite-plugin-html": "^3.2.2", "vite-plugin-html-env": "^1.2.8", diff --git a/src/App.tsx b/src/App.tsx index 6d98fc8..e53430f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,14 +3,14 @@ import React from 'react'; import { CssBaseline, ThemeProvider } from '@mui/material'; import { RainbowKitProvider } from '@rainbow-me/rainbowkit'; import { QueryClientProvider } from '@tanstack/react-query'; -import { SnackbarProvider } from 'notistack'; import { BrowserRouter } from 'react-router-dom'; import { WagmiProvider } from 'wagmi'; import { queryClient } from '@api/client'; -import { Alert } from '@components/Alert'; +import { Toaster } from '@components/Toaster'; import { SquidHeightProvider } from '@hooks/useSquidNetworkHeightHooks'; -import { wagmiConfig } from '@network/config'; +import { rainbowConfig } from '@network/config'; +import { getChainId, getSubsquidNetwork } from '@network/useSubsquidNetwork'; import { AppRoutes } from './AppRoutes'; import { useCreateRainbowKitTheme, useCreateTheme, useThemeState } from './theme'; @@ -19,32 +19,31 @@ function App() { const [themeName] = useThemeState(); const theme = useCreateTheme(themeName); const rainbowkitTheme = useCreateRainbowKitTheme(themeName); + const network = getSubsquidNetwork(); return ( - - - + <> + + - - + + - + + {/* */} + - - - + + + ); } diff --git a/src/AppRoutes.tsx b/src/AppRoutes.tsx index 82a238d..6ee64f9 100644 --- a/src/AppRoutes.tsx +++ b/src/AppRoutes.tsx @@ -7,12 +7,9 @@ import { AssetsPage } from '@pages/AssetsPage/AssetsPage.tsx'; import { Vesting } from '@pages/AssetsPage/Vesting.tsx'; import { DashboardPage } from '@pages/DashboardPage/DashboardPage.tsx'; import { DelegationsPage } from '@pages/DelegationsPage/DelegationsPage.tsx'; -import { AddNewGateway } from '@pages/GatewaysPage/AddNewGateway.tsx'; import { Gateway } from '@pages/GatewaysPage/Gateway.tsx'; import { GatewaysPage } from '@pages/GatewaysPage/GatewaysPage.tsx'; -import { AddNewWorker } from '@pages/WorkersPage/AddNewWorker.tsx'; import { Worker } from '@pages/WorkersPage/Worker.tsx'; -import { WorkerEdit } from '@pages/WorkersPage/WorkerEdit.tsx'; import { WorkersPage } from '@pages/WorkersPage/WorkersPage.tsx'; import { hideLoader } from './index.tsx'; @@ -35,18 +32,17 @@ export const AppRoutes = () => { } index /> - } path="add" /> + {/* } path="add" /> */} } path=":peerId" /> - } path=":peerId/edit" /> } index /> - + } index /> - } path="add" /> - } path=":peerId" /> + } path=":peerId" /> + } /> } path="*" /> diff --git a/src/api/contracts/claim.ts b/src/api/contracts/claim.ts deleted file mode 100644 index fb04248..0000000 --- a/src/api/contracts/claim.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { useState } from 'react'; - -import { encodeFunctionData } from 'viem'; -import { waitForTransactionReceipt } from 'viem/actions'; -import { useWriteContract, useClient } from 'wagmi'; - -import { REWARD_TREASURY_CONTRACT_ABI } from '@api/contracts/reaward-treasury.abi'; -import { VESTING_CONTRACT_ABI } from '@api/contracts/vesting.abi'; -import { AccountType, SourceWallet } from '@api/subsquid-network-squid'; -import { useSquidNetworkHeight } from '@hooks/useSquidNetworkHeightHooks'; -import { useAccount } from '@network/useAccount.ts'; -import { useContracts } from '@network/useContracts.ts'; - -import { TxResult, errorMessage, WriteContractRes } from './utils'; - -export type ClaimRequest = { - wallet: SourceWallet; -}; - -function useClaimFromWallet() { - const contracts = useContracts(); - const { writeContractAsync } = useWriteContract({}); - - return async ({ wallet }: ClaimRequest): Promise => { - try { - return { - tx: await writeContractAsync({ - address: contracts.REWARD_TREASURY, - abi: REWARD_TREASURY_CONTRACT_ABI, - functionName: 'claimFor', - args: [contracts.REWARD_DISTRIBUTION, wallet.id as `0x${string}`], - }), - }; - } catch (e) { - return { error: errorMessage(e) }; - } - }; -} - -function useClaimFromVestingContract() { - const contracts = useContracts(); - const { address: account } = useAccount(); - const { writeContractAsync } = useWriteContract({}); - - return async ({ wallet }: ClaimRequest): Promise => { - try { - const data = encodeFunctionData({ - abi: REWARD_TREASURY_CONTRACT_ABI, - functionName: 'claimFor', - args: [contracts.REWARD_DISTRIBUTION, account as `0x${string}`], - }); - - return { - tx: await writeContractAsync({ - account, - address: wallet.id as `0x${string}`, - abi: VESTING_CONTRACT_ABI, - functionName: 'execute', - args: [contracts.REWARD_TREASURY, data], - }), - }; - } catch (e: unknown) { - return { error: errorMessage(e) }; - } - }; -} - -export function useClaim() { - const client = useClient(); - const { setWaitHeight } = useSquidNetworkHeight(); - const [isPending, setLoading] = useState(false); - const [error, setError] = useState(null); - - const claimFromWallet = useClaimFromWallet(); - const claimFromVestingContract = useClaimFromVestingContract(); - - const claim = async ({ wallet }: ClaimRequest): Promise => { - setLoading(true); - - const res = - wallet.type === AccountType.User - ? await claimFromWallet({ wallet }) - : await claimFromVestingContract({ wallet }); - - if (!res.tx) { - setLoading(false); - setError(res.error); - - return { success: false, failedReason: res.error }; - } - - const receipt = await waitForTransactionReceipt(client!, { hash: res.tx }); - setWaitHeight(receipt.blockNumber, []); - - setLoading(false); - - return { success: true }; - }; - - return { - claim, - isPending, - error, - }; -} diff --git a/src/api/contracts/gateway-registration/GatewayRegistration.abi.ts b/src/api/contracts/gateway-registration/GatewayRegistration.abi.ts deleted file mode 100644 index 123451e..0000000 --- a/src/api/contracts/gateway-registration/GatewayRegistration.abi.ts +++ /dev/null @@ -1,105 +0,0 @@ -export const GATEWAY_REGISTRATION_CONTRACT_ABI = [ - { - type: 'function', - name: 'register', - inputs: [ - { - name: 'peerId', - type: 'bytes', - internalType: 'bytes', - }, - { - name: 'metadata', - type: 'string', - internalType: 'string', - }, - ], - outputs: [], - stateMutability: 'nonpayable', - }, - { - type: 'function', - name: 'setMetadata', - inputs: [ - { - name: 'peerId', - type: 'bytes', - internalType: 'bytes', - }, - { - name: 'metadata', - type: 'string', - internalType: 'string', - }, - ], - outputs: [], - stateMutability: 'nonpayable', - }, - { - type: 'function', - name: 'stake', - inputs: [ - { - name: 'amount', - type: 'uint256', - internalType: 'uint256', - }, - { - name: 'durationBlocks', - type: 'uint128', - internalType: 'uint128', - }, - { - name: 'withAutoExtension', - type: 'bool', - internalType: 'bool', - }, - ], - outputs: [], - stateMutability: 'nonpayable', - }, - { - type: 'function', - name: 'unregister', - inputs: [ - { - name: 'peerId', - type: 'bytes', - internalType: 'bytes', - }, - ], - outputs: [], - stateMutability: 'nonpayable', - }, - { - type: 'function', - name: 'unstake', - inputs: [], - outputs: [], - stateMutability: 'nonpayable', - }, - { - type: 'function', - name: 'computationUnitsAmount', - inputs: [ - { - name: 'amount', - type: 'uint256', - internalType: 'uint256', - }, - { - name: 'durationBlocks', - type: 'uint256', - internalType: 'uint256', - }, - ], - outputs: [ - { - name: '', - type: 'uint256', - internalType: 'uint256', - }, - ], - stateMutability: 'view', - }, -] as const; diff --git a/src/api/contracts/gateway-registration/useComputationUnits.ts b/src/api/contracts/gateway-registration/useComputationUnits.ts deleted file mode 100644 index 8a50a88..0000000 --- a/src/api/contracts/gateway-registration/useComputationUnits.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { useReadContract } from 'wagmi'; - -import { useContracts } from '@network/useContracts'; - -import { GATEWAY_REGISTRATION_CONTRACT_ABI } from './GatewayRegistration.abi'; - -export function useComputationUnits({ - amount, - lockDuration, -}: { - amount: string; - lockDuration: number; -}) { - const contracts = useContracts(); - - const { data, isLoading, isPending } = useReadContract({ - address: contracts.GATEWAY_REGISTRATION, - abi: GATEWAY_REGISTRATION_CONTRACT_ABI, - functionName: 'computationUnitsAmount', - args: [BigInt(amount), BigInt(lockDuration)], - }); - - return { - data: data?.toString(), - isLoading, - isPending, - }; -} diff --git a/src/api/contracts/gateway-registration/useRegisterGateway.ts b/src/api/contracts/gateway-registration/useRegisterGateway.ts deleted file mode 100644 index 24a89ab..0000000 --- a/src/api/contracts/gateway-registration/useRegisterGateway.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { useState } from 'react'; - -import { peerIdToHex } from '@lib/network'; -import { logger } from '@logger'; -import { encodeFunctionData } from 'viem'; -import { usePublicClient, useWriteContract } from 'wagmi'; - -import { AccountType } from '@api/subsquid-network-squid'; -import { useSquidNetworkHeight } from '@hooks/useSquidNetworkHeightHooks'; -import { useAccount } from '@network/useAccount'; -import { useContracts } from '@network/useContracts.ts'; - -import { TxResult, errorMessage, WriteContractRes } from '../utils'; -import { VESTING_CONTRACT_ABI } from '../vesting.abi'; - -import { encodeGatewayMetadata, GetawayMetadata } from './GatewayMetadata'; -import { GATEWAY_REGISTRATION_CONTRACT_ABI } from './GatewayRegistration.abi'; - -export interface RegisterGatewayRequest extends GetawayMetadata { - peerId: string; - source: { - id: string; - type: AccountType; - }; -} - -function useRegisterGatewayFromWallet() { - const contracts = useContracts(); - const { writeContractAsync } = useWriteContract({}); - - return async ({ peerId, ...rest }: RegisterGatewayRequest): Promise => { - logger.debug(`registering gateway via worker contract...`); - - try { - return { - tx: await writeContractAsync({ - address: contracts.GATEWAY_REGISTRATION, - abi: GATEWAY_REGISTRATION_CONTRACT_ABI, - functionName: 'register', - args: [peerIdToHex(peerId), encodeGatewayMetadata(rest)], - }), - }; - } catch (e: unknown) { - return { - error: errorMessage(e), - }; - } - }; -} - -function useRegisterGatewayFromVestingContract() { - const contracts = useContracts(); - const { address: account } = useAccount(); - const { writeContractAsync } = useWriteContract({}); - - return async ({ peerId, source, ...rest }: RegisterGatewayRequest): Promise => { - try { - const data = encodeFunctionData({ - abi: GATEWAY_REGISTRATION_CONTRACT_ABI, - functionName: 'register', - args: [peerIdToHex(peerId), encodeGatewayMetadata(rest)], // encodeMetadata(rest) - }); - - return { - tx: await writeContractAsync({ - account, - address: source.id as `0x${string}`, - abi: VESTING_CONTRACT_ABI, - functionName: 'execute', - args: [contracts.GATEWAY_REGISTRATION, data], - }), - }; - } catch (e: unknown) { - return { error: errorMessage(e) }; - } - }; -} - -export function useRegisterGateway() { - const client = usePublicClient(); - const { address } = useAccount(); - const [error, setError] = useState(null); - const [isLoading, setLoading] = useState(false); - - const { setWaitHeight } = useSquidNetworkHeight(); - const registerGatewayFromWallet = useRegisterGatewayFromWallet(); - const registerGatewayFromVestingContract = useRegisterGatewayFromVestingContract(); - - const registerGateway = async (req: RegisterGatewayRequest): Promise => { - setLoading(true); - - const { tx, error } = - req.source.type === AccountType.User - ? await registerGatewayFromWallet(req) - : await registerGatewayFromVestingContract(req); - - if (!tx) { - logger.debug(`registering gateway failed ${error}`); - setLoading(false); - setError(error); - return { success: false, failedReason: error }; - } - - if (!client) { - return { success: false, failedReason: 'missing client' }; - } - - const receipt = await client.waitForTransactionReceipt({ hash: tx }); - setWaitHeight(receipt.blockNumber, ['myGateways', { address }]); - setLoading(false); - setError(null); - - return { success: true }; - }; - - return { registerGateway, isLoading, error }; -} diff --git a/src/api/contracts/gateway-registration/useStakeGateway.ts b/src/api/contracts/gateway-registration/useStakeGateway.ts deleted file mode 100644 index e80d8c5..0000000 --- a/src/api/contracts/gateway-registration/useStakeGateway.ts +++ /dev/null @@ -1,147 +0,0 @@ -import { useState } from 'react'; - -import { logger } from '@logger'; -import { encodeFunctionData } from 'viem'; -import { usePublicClient, useWriteContract } from 'wagmi'; - -import { AccountType, SourceWallet } from '@api/subsquid-network-squid'; -import { useSquidNetworkHeight } from '@hooks/useSquidNetworkHeightHooks'; -import { useAccount } from '@network/useAccount'; -import { useContracts } from '@network/useContracts.ts'; - -import { useApproveSqd } from '../sqd'; -import { errorMessage, TxResult, isApproveRequiredError, WriteContractRes } from '../utils'; -import { VESTING_CONTRACT_ABI } from '../vesting.abi'; - -import { GATEWAY_REGISTRATION_CONTRACT_ABI } from './GatewayRegistration.abi'; - -type StakeGatewayRequest = { - amount: string; - durationBlocks: number; - autoExtension: boolean; - wallet: SourceWallet; -}; - -function useStakeFromWallet() { - const contracts = useContracts(); - const { writeContractAsync } = useWriteContract({}); - - const [approveSqd] = useApproveSqd(); - - const tryCallContract = async ({ - autoExtension, - amount, - durationBlocks, - }: StakeGatewayRequest) => { - try { - return { - tx: await writeContractAsync({ - address: contracts.GATEWAY_REGISTRATION, - abi: GATEWAY_REGISTRATION_CONTRACT_ABI, - functionName: 'stake', - args: [BigInt(amount), BigInt(durationBlocks), autoExtension], - }), - }; - } catch (e) { - return { error: errorMessage(e) }; - } - }; - - return async (req: StakeGatewayRequest): Promise => { - logger.debug(`stake to gateway via worker contract...`); - - const res = await tryCallContract(req); - // Try to approve SQD - if (isApproveRequiredError(res.error)) { - const approveRes = await approveSqd({ - contractAddress: contracts.GATEWAY_REGISTRATION, - amount: req.amount, - }); - if (!approveRes.success) { - return { error: approveRes.failedReason }; - } - - logger.debug(`approved SQD successfully, now trying to register one more time...`); - - return tryCallContract(req); - } - - return res; - }; -} - -function useStakeFromVestingContract() { - const contracts = useContracts(); - const { address: account } = useAccount(); - const { writeContractAsync } = useWriteContract({}); - - return async ({ - amount, - wallet, - autoExtension, - durationBlocks, - }: StakeGatewayRequest): Promise => { - try { - const data = encodeFunctionData({ - abi: GATEWAY_REGISTRATION_CONTRACT_ABI, - functionName: 'stake', - args: [BigInt(amount), BigInt(durationBlocks), autoExtension], - }); - - return { - tx: await writeContractAsync({ - account, - address: wallet.id as `0x${string}`, - abi: VESTING_CONTRACT_ABI, - functionName: 'execute', - args: [contracts.GATEWAY_REGISTRATION, data, BigInt(amount)], - }), - }; - } catch (e: unknown) { - return { error: errorMessage(e) }; - } - }; -} - -export function useStakeGateway() { - const client = usePublicClient(); - const { setWaitHeight } = useSquidNetworkHeight(); - const [isLoading, setLoading] = useState(false); - const [error, setError] = useState(null); - - const stakeFromWallet = useStakeFromWallet(); - const stakeFromVestingContract = useStakeFromVestingContract(); - - const stakeToGateway = async (req: StakeGatewayRequest): Promise => { - setLoading(true); - - const res = - req.wallet.type === AccountType.User - ? await stakeFromWallet(req) - : await stakeFromVestingContract(req); - - if (!res.tx) { - setLoading(false); - setError(res.error); - - return { success: false, failedReason: res.error }; - } - - if (!client) { - return { success: false, failedReason: 'missing client' }; - } - - const receipt = await client.waitForTransactionReceipt({ hash: res.tx }); - setWaitHeight(receipt.blockNumber, []); - - setLoading(false); - - return { success: true }; - }; - - return { - stakeToGateway, - isLoading, - error, - }; -} diff --git a/src/api/contracts/gateway-registration/useUnregisterGateway.ts b/src/api/contracts/gateway-registration/useUnregisterGateway.ts deleted file mode 100644 index 4549036..0000000 --- a/src/api/contracts/gateway-registration/useUnregisterGateway.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { useState } from 'react'; - -import { peerIdToHex } from '@lib/network'; -import { logger } from '@logger'; -import { encodeFunctionData } from 'viem'; -import { useWriteContract, usePublicClient } from 'wagmi'; - -import { AccountType } from '@api/subsquid-network-squid'; -import { BlockchainGateway } from '@api/subsquid-network-squid/gateways-graphql'; -import { useSquidNetworkHeight } from '@hooks/useSquidNetworkHeightHooks'; -import { useAccount } from '@network/useAccount'; -import { useContracts } from '@network/useContracts.ts'; - -import { TxResult, errorMessage, WriteContractRes } from '../utils'; -import { VESTING_CONTRACT_ABI } from '../vesting.abi'; - -import { GATEWAY_REGISTRATION_CONTRACT_ABI } from './GatewayRegistration.abi'; - -export interface UnregisterGatewayRequest { - gateway: BlockchainGateway; -} - -function useUnregisterGatewayFromWallet() { - const contracts = useContracts(); - const { writeContractAsync } = useWriteContract(); - - return async ({ gateway }: UnregisterGatewayRequest): Promise => { - logger.debug(`unregistering gateway via worker contract...`); - - try { - return { - tx: await writeContractAsync({ - address: contracts.GATEWAY_REGISTRATION, - abi: GATEWAY_REGISTRATION_CONTRACT_ABI, - functionName: 'unregister', - args: [peerIdToHex(gateway.id)], - }), - }; - } catch (e: unknown) { - return { - error: errorMessage(e), - }; - } - }; -} - -function useUnregisterGatewayFromVestingContract() { - const contracts = useContracts(); - const { address: account } = useAccount(); - const { writeContractAsync } = useWriteContract(); - - return async ({ gateway }: UnregisterGatewayRequest): Promise => { - try { - const data = encodeFunctionData({ - abi: GATEWAY_REGISTRATION_CONTRACT_ABI, - functionName: 'unregister', - args: [peerIdToHex(gateway.id)], - }); - - return { - tx: await writeContractAsync({ - account, - address: gateway.owner.id as `0x${string}`, - abi: VESTING_CONTRACT_ABI, - functionName: 'execute', - args: [contracts.GATEWAY_REGISTRATION, data], - }), - }; - } catch (e: unknown) { - return { error: errorMessage(e) }; - } - }; -} - -export function useUnregisterGateway() { - const client = usePublicClient(); - const { address } = useAccount(); - const [error, setError] = useState(null); - const [isLoading, setLoading] = useState(false); - - const { setWaitHeight } = useSquidNetworkHeight(); - const unregisterGatewayFromWallet = useUnregisterGatewayFromWallet(); - const unregisterGatewayFromVestingContract = useUnregisterGatewayFromVestingContract(); - - const unregisterGateway = async (req: UnregisterGatewayRequest): Promise => { - setLoading(true); - - const { tx, error } = - req.gateway.owner.type === AccountType.User - ? await unregisterGatewayFromWallet(req) - : await unregisterGatewayFromVestingContract(req); - - if (!tx) { - logger.debug(`unregistering gateway failed ${error}`); - setLoading(false); - setError(error); - return { success: false, failedReason: error }; - } - - if (!client) { - return { success: false, failedReason: 'missing client' }; - } - - const receipt = await client.waitForTransactionReceipt({ hash: tx }); - setWaitHeight(receipt.blockNumber, ['myGateways', { address }]); - setLoading(false); - setError(null); - - return { success: true }; - }; - - return { unregisterGateway, isLoading, error }; -} diff --git a/src/api/contracts/gateway-registration/useUnstakeGateway.ts b/src/api/contracts/gateway-registration/useUnstakeGateway.ts deleted file mode 100644 index d08aba5..0000000 --- a/src/api/contracts/gateway-registration/useUnstakeGateway.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { useState } from 'react'; - -import { encodeFunctionData } from 'viem'; -import { useWriteContract, usePublicClient } from 'wagmi'; - -import { AccountType, GatewayStakeFragmentFragment } from '@api/subsquid-network-squid'; -import { useSquidNetworkHeight } from '@hooks/useSquidNetworkHeightHooks'; -import { useAccount } from '@network/useAccount'; -import { useContracts } from '@network/useContracts.ts'; - -import { TxResult, errorMessage, WriteContractRes } from '../utils'; -import { VESTING_CONTRACT_ABI } from '../vesting.abi'; - -import { GATEWAY_REGISTRATION_CONTRACT_ABI } from './GatewayRegistration.abi'; - -type UnstakeGatewayRequest = { - operator: GatewayStakeFragmentFragment; -}; - -function useUnstakeFromWallet() { - const contracts = useContracts(); - const { writeContractAsync } = useWriteContract({}); - - return async ({}: UnstakeGatewayRequest): Promise => { - try { - return { - tx: await writeContractAsync({ - address: contracts.GATEWAY_REGISTRATION, - abi: GATEWAY_REGISTRATION_CONTRACT_ABI, - functionName: 'unstake', - }), - }; - } catch (e) { - return { error: errorMessage(e) }; - } - }; -} - -function useUnstakeFromVestingContract() { - const contracts = useContracts(); - const { address: account } = useAccount(); - const { writeContractAsync } = useWriteContract({}); - - return async ({ operator }: UnstakeGatewayRequest): Promise => { - try { - const data = encodeFunctionData({ - abi: GATEWAY_REGISTRATION_CONTRACT_ABI, - functionName: 'unstake', - }); - - return { - tx: await writeContractAsync({ - account, - address: operator.account.id as `0x${string}`, - abi: VESTING_CONTRACT_ABI, - functionName: 'execute', - args: [contracts.GATEWAY_REGISTRATION, data], - }), - }; - } catch (e: unknown) { - return { error: errorMessage(e) }; - } - }; -} - -export function useUnstakeGateway() { - const client = usePublicClient(); - const { setWaitHeight } = useSquidNetworkHeight(); - const [isLoading, setLoading] = useState(false); - const [error, setError] = useState(null); - - const unstakeFromWallet = useUnstakeFromWallet(); - const unstakeFromVestingContract = useUnstakeFromVestingContract(); - - const unstakeFromGateway = async (req: UnstakeGatewayRequest): Promise => { - setLoading(true); - - const res = - req.operator.account.type === AccountType.User - ? await unstakeFromWallet(req) - : await unstakeFromVestingContract(req); - - if (!res.tx) { - setLoading(false); - setError(res.error); - - return { success: false, failedReason: res.error }; - } - - if (!client) { - return { success: false, failedReason: 'missing client' }; - } - - const receipt = await client.waitForTransactionReceipt({ hash: res.tx }); - setWaitHeight(receipt.blockNumber, []); - - setLoading(false); - - return { success: true }; - }; - - return { - unstakeFromGateway, - isLoading, - error, - }; -} diff --git a/src/api/contracts/index.ts b/src/api/contracts/index.ts new file mode 100644 index 0000000..d325827 --- /dev/null +++ b/src/api/contracts/index.ts @@ -0,0 +1 @@ +export * from './subsquid.generated'; diff --git a/src/api/contracts/reaward-treasury.abi.ts b/src/api/contracts/reaward-treasury.abi.ts deleted file mode 100644 index f51a666..0000000 --- a/src/api/contracts/reaward-treasury.abi.ts +++ /dev/null @@ -1,20 +0,0 @@ -export const REWARD_TREASURY_CONTRACT_ABI = [ - { - type: 'function', - name: 'claimFor', - inputs: [ - { - name: 'rewardDistribution', - type: 'address', - internalType: 'contract IRewardsDistribution', - }, - { - name: 'receiver', - type: 'address', - internalType: 'address', - }, - ], - outputs: [], - stateMutability: 'nonpayable', - }, -] as const; diff --git a/src/api/contracts/soft-cap.abi.ts b/src/api/contracts/soft-cap.abi.ts deleted file mode 100644 index 2f3a6f8..0000000 --- a/src/api/contracts/soft-cap.abi.ts +++ /dev/null @@ -1,26 +0,0 @@ -export const SOFT_CAP_ABI = [ - { - type: 'function', - name: 'cap', - inputs: [{ name: 'x', type: 'uint256', internalType: 'UD60x18' }], - outputs: [{ name: '', type: 'uint256', internalType: 'UD60x18' }], - stateMutability: 'pure', - }, - { - type: 'function', - name: 'capedStake', - inputs: [{ name: 'workerId', type: 'uint256', internalType: 'uint256' }], - outputs: [{ name: '', type: 'uint256', internalType: 'uint256' }], - stateMutability: 'view', - }, - { - type: 'function', - name: 'capedStakeAfterDelegation', - inputs: [ - { name: 'workerId', type: 'uint256', internalType: 'uint256' }, - { name: 'delegationAmount', type: 'int256', internalType: 'int256' }, - ], - outputs: [{ name: '', type: 'uint256', internalType: 'uint256' }], - stateMutability: 'view', - }, -] as const; diff --git a/src/api/contracts/sqd.ts b/src/api/contracts/sqd.ts index 7f87cef..bc30880 100644 --- a/src/api/contracts/sqd.ts +++ b/src/api/contracts/sqd.ts @@ -11,7 +11,7 @@ import { WriteContractRes, errorMessage } from './utils'; export function useApproveSqd() { const client = useClient(); const contracts = useContracts(); - const { writeContractAsync } = useWriteContract({}); + const { writeContractAsync } = useWriteContract(); async function approve({ contractAddress, diff --git a/src/api/contracts/staking.abi.ts b/src/api/contracts/staking.abi.ts deleted file mode 100644 index c0493ad..0000000 --- a/src/api/contracts/staking.abi.ts +++ /dev/null @@ -1,74 +0,0 @@ -export const STAKING_CONTRACT_ABI = [ - { - type: 'function', - name: 'deposit', - inputs: [ - { - name: 'worker', - type: 'uint256', - internalType: 'uint256', - }, - { - name: 'amount', - type: 'uint256', - internalType: 'uint256', - }, - ], - outputs: [], - stateMutability: 'nonpayable', - }, - { - type: 'function', - name: 'withdraw', - inputs: [ - { - type: 'uint256', - name: 'worker', - }, - { - type: 'uint256', - name: 'amount', - }, - ], - outputs: [], - stateMutability: 'nonpayable', - }, - { - type: 'function', - name: 'claimable', - inputs: [ - { - name: 'staker', - type: 'address', - internalType: 'address', - }, - ], - outputs: [ - { - name: '', - type: 'uint256', - internalType: 'uint256', - }, - ], - stateMutability: 'view', - }, - { - type: 'function', - name: 'delegated', - inputs: [ - { - name: 'worker', - type: 'uint256', - internalType: 'uint256', - }, - ], - outputs: [ - { - name: '', - type: 'uint256', - internalType: 'uint256', - }, - ], - stateMutability: 'view', - }, -] as const; diff --git a/src/api/contracts/staking.ts b/src/api/contracts/staking.ts index fa2067c..803b64e 100644 --- a/src/api/contracts/staking.ts +++ b/src/api/contracts/staking.ts @@ -14,14 +14,12 @@ import { } from 'wagmi'; import { useApproveSqd } from '@api/contracts/sqd'; -import { VESTING_CONTRACT_ABI } from '@api/contracts/vesting.abi'; import { AccountType, Worker, SourceWallet } from '@api/subsquid-network-squid'; -import { useSquidNetworkHeight } from '@hooks/useSquidNetworkHeightHooks'; +import { useSquidHeight } from '@hooks/useSquidNetworkHeightHooks'; import { useAccount } from '@network/useAccount'; import { useContracts } from '@network/useContracts.ts'; -import { SOFT_CAP_ABI } from './soft-cap.abi'; -import { STAKING_CONTRACT_ABI } from './staking.abi'; +import { softCapAbi, stakingAbi, vestingAbi } from './subsquid.generated'; import { errorMessage, TxResult, isApproveRequiredError, WriteContractRes } from './utils'; type WorkerDepositRequest = { @@ -41,7 +39,7 @@ function useDelegateFromWallet() { return { tx: await writeContractAsync({ address: contracts.STAKING, - abi: STAKING_CONTRACT_ABI, + abi: stakingAbi, functionName: 'deposit', args: [BigInt(worker.id), BigInt(amount)], }), @@ -82,7 +80,7 @@ function useDepositFromVestingContract() { return async ({ worker, amount, wallet }: WorkerDepositRequest): Promise => { try { const data = encodeFunctionData({ - abi: STAKING_CONTRACT_ABI, + abi: stakingAbi, functionName: 'deposit', args: [BigInt(worker.id), BigInt(amount)], }); @@ -91,7 +89,7 @@ function useDepositFromVestingContract() { tx: await writeContractAsync({ account, address: wallet.id as `0x${string}`, - abi: VESTING_CONTRACT_ABI, + abi: vestingAbi, functionName: 'execute', args: [contracts.STAKING, data, BigInt(amount)], }), @@ -104,7 +102,7 @@ function useDepositFromVestingContract() { export function useWorkerDelegate() { const client = useClient(); - const { setWaitHeight } = useSquidNetworkHeight(); + const { setWaitHeight } = useSquidHeight(); const [isPending, setLoading] = useState(false); const [error, setError] = useState(null); @@ -154,7 +152,7 @@ function useUndelegateFromWallet() { return { tx: await writeContractAsync({ address: contracts.STAKING, - abi: STAKING_CONTRACT_ABI, + abi: stakingAbi, functionName: 'withdraw', args: [BigInt(worker.id), BigInt(amount)], }), @@ -173,7 +171,7 @@ function useUndelegateFromVestingContract() { return async ({ worker, amount, wallet }: WorkerDepositRequest): Promise => { try { const data = encodeFunctionData({ - abi: STAKING_CONTRACT_ABI, + abi: stakingAbi, functionName: 'withdraw', args: [BigInt(worker.id), BigInt(amount)], }); @@ -182,7 +180,7 @@ function useUndelegateFromVestingContract() { tx: await writeContractAsync({ account, address: wallet.id as `0x${string}`, - abi: VESTING_CONTRACT_ABI, + abi: vestingAbi, functionName: 'execute', args: [contracts.STAKING, data, BigInt(amount)], }), @@ -195,7 +193,7 @@ function useUndelegateFromVestingContract() { export function useWorkerUndelegate() { const client = usePublicClient(); - const { setWaitHeight } = useSquidNetworkHeight(); + const { setWaitHeight } = useSquidHeight(); const [isPending, setLoading] = useState(false); const [error, setError] = useState(null); @@ -238,11 +236,11 @@ export function useWorkerUndelegate() { export function useCapedStake({ workerId }: { workerId?: string }) { const contracts = useContracts(); - const { currentHeight, isLoading: isHeightLoading } = useSquidNetworkHeight(); + const { currentHeight, isLoading: isHeightLoading } = useSquidHeight(); const { data, isLoading } = useReadContract({ address: contracts.SOFT_CAP, - abi: SOFT_CAP_ABI, + abi: softCapAbi, functionName: 'capedStake', args: [BigInt(workerId || -1)], blockNumber: BigInt(currentHeight), @@ -279,13 +277,13 @@ export function useCapedStakeAfterDelegation({ contracts: [ { address: contracts.SOFT_CAP, - abi: SOFT_CAP_ABI, + abi: softCapAbi, functionName: 'capedStakeAfterDelegation', args: [BigInt(workerId), BigInt(amount || 0n) * (undelegate ? -1n : 1n)], }, { address: contracts.STAKING, - abi: STAKING_CONTRACT_ABI, + abi: stakingAbi, functionName: 'delegated', args: [BigInt(workerId)], }, diff --git a/src/api/contracts/subsquid.generated.ts b/src/api/contracts/subsquid.generated.ts new file mode 100644 index 0000000..9ff2a35 --- /dev/null +++ b/src/api/contracts/subsquid.generated.ts @@ -0,0 +1,1479 @@ +import { + createUseReadContract, + createUseWriteContract, + createUseSimulateContract, + createUseWatchContractEvent, +} from 'wagmi/codegen' + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// ArbMulticall +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +export const arbMulticallAbi = [ + { + type: 'function', + inputs: [], + name: 'getCurrentBlockTimestamp', + outputs: [{ name: 'timestamp', internalType: 'uint256', type: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'getL1BlockNumber', + outputs: [ + { name: 'l1BlockNumber', internalType: 'uint256', type: 'uint256' }, + ], + stateMutability: 'view', + }, +] as const + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// GatewayRegistry +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +export const gatewayRegistryAbi = [ + { + type: 'function', + inputs: [ + { name: 'peerId', internalType: 'bytes', type: 'bytes' }, + { name: 'metadata', internalType: 'string', type: 'string' }, + ], + name: 'register', + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + inputs: [ + { name: 'peerId', internalType: 'bytes', type: 'bytes' }, + { name: 'metadata', internalType: 'string', type: 'string' }, + ], + name: 'setMetadata', + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + inputs: [ + { name: 'amount', internalType: 'uint256', type: 'uint256' }, + { name: 'durationBlocks', internalType: 'uint128', type: 'uint128' }, + { name: 'withAutoExtension', internalType: 'bool', type: 'bool' }, + ], + name: 'stake', + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + inputs: [{ name: 'peerId', internalType: 'bytes', type: 'bytes' }], + name: 'unregister', + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + inputs: [], + name: 'unstake', + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + inputs: [ + { name: 'amount', internalType: 'uint256', type: 'uint256' }, + { name: 'durationBlocks', internalType: 'uint256', type: 'uint256' }, + ], + name: 'computationUnitsAmount', + outputs: [{ name: '', internalType: 'uint256', type: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [{ name: 'amount', internalType: 'uint256', type: 'uint256' }], + name: 'addStake', + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + inputs: [], + name: 'disableAutoExtension', + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + inputs: [], + name: 'enableAutoExtension', + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + inputs: [{ name: 'operator', internalType: 'address', type: 'address' }], + name: 'canUnstake', + outputs: [{ name: '', internalType: 'bool', type: 'bool' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [ + { name: 'amount', internalType: 'uint256', type: 'uint256' }, + { name: 'durationBlocks', internalType: 'uint256', type: 'uint256' }, + ], + name: 'computationUnitsAmount', + outputs: [{ name: '', internalType: 'uint256', type: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [{ name: 'operator', internalType: 'address', type: 'address' }], + name: 'getStake', + outputs: [ + { + name: '', + internalType: 'struct IGatewayRegistry.Stake', + type: 'tuple', + components: [ + { name: 'amount', internalType: 'uint256', type: 'uint256' }, + { name: 'lockStart', internalType: 'uint128', type: 'uint128' }, + { name: 'lockEnd', internalType: 'uint128', type: 'uint128' }, + { name: 'duration', internalType: 'uint128', type: 'uint128' }, + { name: 'autoExtension', internalType: 'bool', type: 'bool' }, + { name: 'oldCUs', internalType: 'uint256', type: 'uint256' }, + ], + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'minStake', + outputs: [{ name: '', internalType: 'uint256', type: 'uint256' }], + stateMutability: 'view', + }, +] as const + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// NetworkController +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +export const networkControllerAbi = [ + { + type: 'function', + inputs: [], + name: 'bondAmount', + outputs: [{ name: '', internalType: 'uint256', type: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'epochLength', + outputs: [{ name: '', internalType: 'uint128', type: 'uint128' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'workerEpochLength', + outputs: [{ name: '', internalType: 'uint128', type: 'uint128' }], + stateMutability: 'view', + }, +] as const + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// RewardTreasury +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +export const rewardTreasuryAbi = [ + { + type: 'function', + inputs: [ + { + name: 'rewardDistribution', + internalType: 'contract IRewardsDistribution', + type: 'address', + }, + { name: 'receiver', internalType: 'address', type: 'address' }, + ], + name: 'claimFor', + outputs: [], + stateMutability: 'nonpayable', + }, +] as const + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Router +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +export const routerAbi = [ + { + type: 'function', + inputs: [], + name: 'networkController', + outputs: [ + { + name: '', + internalType: 'contract INetworkController', + type: 'address', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'rewardCalculation', + outputs: [ + { + name: '', + internalType: 'contract IRewardCalculation', + type: 'address', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'rewardTreasury', + outputs: [{ name: '', internalType: 'address', type: 'address' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'staking', + outputs: [{ name: '', internalType: 'contract IStaking', type: 'address' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'workerRegistration', + outputs: [ + { + name: '', + internalType: 'contract IWorkerRegistration', + type: 'address', + }, + ], + stateMutability: 'view', + }, +] as const + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// SQD +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +export const sqdAbi = [ + { + type: 'event', + inputs: [ + { name: 'owner', type: 'address', indexed: true }, + { name: 'spender', type: 'address', indexed: true }, + { name: 'value', type: 'uint256', indexed: false }, + ], + name: 'Approval', + }, + { + type: 'event', + inputs: [ + { name: 'from', type: 'address', indexed: true }, + { name: 'to', type: 'address', indexed: true }, + { name: 'value', type: 'uint256', indexed: false }, + ], + name: 'Transfer', + }, + { + type: 'function', + inputs: [ + { name: 'owner', type: 'address' }, + { name: 'spender', type: 'address' }, + ], + name: 'allowance', + outputs: [{ type: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [ + { name: 'spender', type: 'address' }, + { name: 'amount', type: 'uint256' }, + ], + name: 'approve', + outputs: [{ type: 'bool' }], + stateMutability: 'nonpayable', + }, + { + type: 'function', + inputs: [{ name: 'account', type: 'address' }], + name: 'balanceOf', + outputs: [{ type: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'decimals', + outputs: [{ type: 'uint8' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'name', + outputs: [{ type: 'string' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'symbol', + outputs: [{ type: 'string' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'totalSupply', + outputs: [{ type: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [ + { name: 'recipient', type: 'address' }, + { name: 'amount', type: 'uint256' }, + ], + name: 'transfer', + outputs: [{ type: 'bool' }], + stateMutability: 'nonpayable', + }, + { + type: 'function', + inputs: [ + { name: 'sender', type: 'address' }, + { name: 'recipient', type: 'address' }, + { name: 'amount', type: 'uint256' }, + ], + name: 'transferFrom', + outputs: [{ type: 'bool' }], + stateMutability: 'nonpayable', + }, +] as const + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// SoftCap +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +export const softCapAbi = [ + { + type: 'function', + inputs: [{ name: 'x', internalType: 'UD60x18', type: 'uint256' }], + name: 'cap', + outputs: [{ name: '', internalType: 'UD60x18', type: 'uint256' }], + stateMutability: 'pure', + }, + { + type: 'function', + inputs: [{ name: 'workerId', internalType: 'uint256', type: 'uint256' }], + name: 'capedStake', + outputs: [{ name: '', internalType: 'uint256', type: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [ + { name: 'workerId', internalType: 'uint256', type: 'uint256' }, + { name: 'delegationAmount', internalType: 'int256', type: 'int256' }, + ], + name: 'capedStakeAfterDelegation', + outputs: [{ name: '', internalType: 'uint256', type: 'uint256' }], + stateMutability: 'view', + }, +] as const + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Staking +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +export const stakingAbi = [ + { + type: 'function', + inputs: [ + { name: 'staker', internalType: 'address', type: 'address' }, + { name: 'worker', internalType: 'uint256', type: 'uint256' }, + ], + name: 'getDeposit', + outputs: [ + { name: 'depositAmount', internalType: 'uint256', type: 'uint256' }, + { name: 'withdrawAllowed', internalType: 'uint256', type: 'uint256' }, + ], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [ + { name: 'worker', internalType: 'uint256', type: 'uint256' }, + { name: 'amount', internalType: 'uint256', type: 'uint256' }, + ], + name: 'deposit', + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + inputs: [ + { name: 'worker', type: 'uint256' }, + { name: 'amount', type: 'uint256' }, + ], + name: 'withdraw', + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + inputs: [{ name: 'staker', internalType: 'address', type: 'address' }], + name: 'claimable', + outputs: [{ name: '', internalType: 'uint256', type: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [{ name: 'worker', internalType: 'uint256', type: 'uint256' }], + name: 'delegated', + outputs: [{ name: '', internalType: 'uint256', type: 'uint256' }], + stateMutability: 'view', + }, +] as const + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Vesting +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +export const vestingAbi = [ + { + type: 'function', + inputs: [], + name: 'depositedIntoProtocol', + outputs: [{ name: '', internalType: 'uint256', type: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'duration', + outputs: [{ name: '', internalType: 'uint256', type: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'end', + outputs: [{ name: '', internalType: 'uint256', type: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [ + { name: 'to', internalType: 'address', type: 'address' }, + { name: 'data', internalType: 'bytes', type: 'bytes' }, + ], + name: 'execute', + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + inputs: [ + { name: 'to', internalType: 'address', type: 'address' }, + { name: 'data', internalType: 'bytes', type: 'bytes' }, + { name: 'requiredApprove', internalType: 'uint256', type: 'uint256' }, + ], + name: 'execute', + outputs: [{ name: '', internalType: 'bytes', type: 'bytes' }], + stateMutability: 'nonpayable', + }, + { + type: 'function', + inputs: [], + name: 'expectedTotalAmount', + outputs: [{ name: '', internalType: 'uint256', type: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'immediateReleaseBIP', + outputs: [{ name: '', internalType: 'uint256', type: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [{ name: 'token', internalType: 'address', type: 'address' }], + name: 'releasable', + outputs: [{ name: '', internalType: 'uint256', type: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'releasable', + outputs: [{ name: '', internalType: 'uint256', type: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [{ name: 'token', internalType: 'address', type: 'address' }], + name: 'release', + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + inputs: [{ name: 'token', internalType: 'address', type: 'address' }], + name: 'released', + outputs: [{ name: '', internalType: 'uint256', type: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [], + name: 'start', + outputs: [{ name: '', internalType: 'uint256', type: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [ + { name: 'token', internalType: 'address', type: 'address' }, + { name: 'timestamp', internalType: 'uint64', type: 'uint64' }, + ], + name: 'vestedAmount', + outputs: [{ name: '', internalType: 'uint256', type: 'uint256' }], + stateMutability: 'view', + }, +] as const + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// WorkerRegistry +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +export const workerRegistryAbi = [ + { + type: 'function', + inputs: [], + name: 'bondAmount', + outputs: [{ name: '', internalType: 'uint256', type: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [ + { name: 'peerId', internalType: 'bytes', type: 'bytes' }, + { name: 'metadata', internalType: 'string', type: 'string' }, + ], + name: 'register', + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + inputs: [ + { name: 'peerId', internalType: 'bytes', type: 'bytes' }, + { name: 'metadata', internalType: 'string', type: 'string' }, + ], + name: 'updateMetadata', + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + inputs: [{ name: 'peerId', internalType: 'bytes', type: 'bytes' }], + name: 'deregister', + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + inputs: [{ name: 'peerId', internalType: 'bytes', type: 'bytes' }], + name: 'withdraw', + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + inputs: [], + name: 'lockPeriod', + outputs: [{ name: '', internalType: 'uint128', type: 'uint128' }], + stateMutability: 'view', + }, + { + type: 'function', + inputs: [{ name: 'workerId', internalType: 'uint256', type: 'uint256' }], + name: 'getWorker', + outputs: [ + { + name: '', + internalType: 'struct WorkerRegistration.Worker', + type: 'tuple', + components: [ + { name: 'creator', internalType: 'address', type: 'address' }, + { name: 'peerId', internalType: 'bytes', type: 'bytes' }, + { name: 'bond', internalType: 'uint256', type: 'uint256' }, + { name: 'registeredAt', internalType: 'uint128', type: 'uint128' }, + { name: 'deregisteredAt', internalType: 'uint128', type: 'uint128' }, + { name: 'metadata', internalType: 'string', type: 'string' }, + ], + }, + ], + stateMutability: 'view', + }, +] as const + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// React +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Wraps __{@link useReadContract}__ with `abi` set to __{@link arbMulticallAbi}__ + */ +export const useReadArbMulticall = /*#__PURE__*/ createUseReadContract({ + abi: arbMulticallAbi, +}) + +/** + * Wraps __{@link useReadContract}__ with `abi` set to __{@link arbMulticallAbi}__ and `functionName` set to `"getCurrentBlockTimestamp"` + */ +export const useReadArbMulticallGetCurrentBlockTimestamp = + /*#__PURE__*/ createUseReadContract({ + abi: arbMulticallAbi, + functionName: 'getCurrentBlockTimestamp', + }) + +/** + * Wraps __{@link useReadContract}__ with `abi` set to __{@link arbMulticallAbi}__ and `functionName` set to `"getL1BlockNumber"` + */ +export const useReadArbMulticallGetL1BlockNumber = + /*#__PURE__*/ createUseReadContract({ + abi: arbMulticallAbi, + functionName: 'getL1BlockNumber', + }) + +/** + * Wraps __{@link useReadContract}__ with `abi` set to __{@link gatewayRegistryAbi}__ + */ +export const useReadGatewayRegistry = /*#__PURE__*/ createUseReadContract({ + abi: gatewayRegistryAbi, +}) + +/** + * Wraps __{@link useReadContract}__ with `abi` set to __{@link gatewayRegistryAbi}__ and `functionName` set to `"computationUnitsAmount"` + */ +export const useReadGatewayRegistryComputationUnitsAmount = + /*#__PURE__*/ createUseReadContract({ + abi: gatewayRegistryAbi, + functionName: 'computationUnitsAmount', + }) + +/** + * Wraps __{@link useReadContract}__ with `abi` set to __{@link gatewayRegistryAbi}__ and `functionName` set to `"canUnstake"` + */ +export const useReadGatewayRegistryCanUnstake = + /*#__PURE__*/ createUseReadContract({ + abi: gatewayRegistryAbi, + functionName: 'canUnstake', + }) + +/** + * Wraps __{@link useReadContract}__ with `abi` set to __{@link gatewayRegistryAbi}__ and `functionName` set to `"getStake"` + */ +export const useReadGatewayRegistryGetStake = + /*#__PURE__*/ createUseReadContract({ + abi: gatewayRegistryAbi, + functionName: 'getStake', + }) + +/** + * Wraps __{@link useReadContract}__ with `abi` set to __{@link gatewayRegistryAbi}__ and `functionName` set to `"minStake"` + */ +export const useReadGatewayRegistryMinStake = + /*#__PURE__*/ createUseReadContract({ + abi: gatewayRegistryAbi, + functionName: 'minStake', + }) + +/** + * Wraps __{@link useWriteContract}__ with `abi` set to __{@link gatewayRegistryAbi}__ + */ +export const useWriteGatewayRegistry = /*#__PURE__*/ createUseWriteContract({ + abi: gatewayRegistryAbi, +}) + +/** + * Wraps __{@link useWriteContract}__ with `abi` set to __{@link gatewayRegistryAbi}__ and `functionName` set to `"register"` + */ +export const useWriteGatewayRegistryRegister = + /*#__PURE__*/ createUseWriteContract({ + abi: gatewayRegistryAbi, + functionName: 'register', + }) + +/** + * Wraps __{@link useWriteContract}__ with `abi` set to __{@link gatewayRegistryAbi}__ and `functionName` set to `"setMetadata"` + */ +export const useWriteGatewayRegistrySetMetadata = + /*#__PURE__*/ createUseWriteContract({ + abi: gatewayRegistryAbi, + functionName: 'setMetadata', + }) + +/** + * Wraps __{@link useWriteContract}__ with `abi` set to __{@link gatewayRegistryAbi}__ and `functionName` set to `"stake"` + */ +export const useWriteGatewayRegistryStake = + /*#__PURE__*/ createUseWriteContract({ + abi: gatewayRegistryAbi, + functionName: 'stake', + }) + +/** + * Wraps __{@link useWriteContract}__ with `abi` set to __{@link gatewayRegistryAbi}__ and `functionName` set to `"unregister"` + */ +export const useWriteGatewayRegistryUnregister = + /*#__PURE__*/ createUseWriteContract({ + abi: gatewayRegistryAbi, + functionName: 'unregister', + }) + +/** + * Wraps __{@link useWriteContract}__ with `abi` set to __{@link gatewayRegistryAbi}__ and `functionName` set to `"unstake"` + */ +export const useWriteGatewayRegistryUnstake = + /*#__PURE__*/ createUseWriteContract({ + abi: gatewayRegistryAbi, + functionName: 'unstake', + }) + +/** + * Wraps __{@link useWriteContract}__ with `abi` set to __{@link gatewayRegistryAbi}__ and `functionName` set to `"addStake"` + */ +export const useWriteGatewayRegistryAddStake = + /*#__PURE__*/ createUseWriteContract({ + abi: gatewayRegistryAbi, + functionName: 'addStake', + }) + +/** + * Wraps __{@link useWriteContract}__ with `abi` set to __{@link gatewayRegistryAbi}__ and `functionName` set to `"disableAutoExtension"` + */ +export const useWriteGatewayRegistryDisableAutoExtension = + /*#__PURE__*/ createUseWriteContract({ + abi: gatewayRegistryAbi, + functionName: 'disableAutoExtension', + }) + +/** + * Wraps __{@link useWriteContract}__ with `abi` set to __{@link gatewayRegistryAbi}__ and `functionName` set to `"enableAutoExtension"` + */ +export const useWriteGatewayRegistryEnableAutoExtension = + /*#__PURE__*/ createUseWriteContract({ + abi: gatewayRegistryAbi, + functionName: 'enableAutoExtension', + }) + +/** + * Wraps __{@link useSimulateContract}__ with `abi` set to __{@link gatewayRegistryAbi}__ + */ +export const useSimulateGatewayRegistry = + /*#__PURE__*/ createUseSimulateContract({ abi: gatewayRegistryAbi }) + +/** + * Wraps __{@link useSimulateContract}__ with `abi` set to __{@link gatewayRegistryAbi}__ and `functionName` set to `"register"` + */ +export const useSimulateGatewayRegistryRegister = + /*#__PURE__*/ createUseSimulateContract({ + abi: gatewayRegistryAbi, + functionName: 'register', + }) + +/** + * Wraps __{@link useSimulateContract}__ with `abi` set to __{@link gatewayRegistryAbi}__ and `functionName` set to `"setMetadata"` + */ +export const useSimulateGatewayRegistrySetMetadata = + /*#__PURE__*/ createUseSimulateContract({ + abi: gatewayRegistryAbi, + functionName: 'setMetadata', + }) + +/** + * Wraps __{@link useSimulateContract}__ with `abi` set to __{@link gatewayRegistryAbi}__ and `functionName` set to `"stake"` + */ +export const useSimulateGatewayRegistryStake = + /*#__PURE__*/ createUseSimulateContract({ + abi: gatewayRegistryAbi, + functionName: 'stake', + }) + +/** + * Wraps __{@link useSimulateContract}__ with `abi` set to __{@link gatewayRegistryAbi}__ and `functionName` set to `"unregister"` + */ +export const useSimulateGatewayRegistryUnregister = + /*#__PURE__*/ createUseSimulateContract({ + abi: gatewayRegistryAbi, + functionName: 'unregister', + }) + +/** + * Wraps __{@link useSimulateContract}__ with `abi` set to __{@link gatewayRegistryAbi}__ and `functionName` set to `"unstake"` + */ +export const useSimulateGatewayRegistryUnstake = + /*#__PURE__*/ createUseSimulateContract({ + abi: gatewayRegistryAbi, + functionName: 'unstake', + }) + +/** + * Wraps __{@link useSimulateContract}__ with `abi` set to __{@link gatewayRegistryAbi}__ and `functionName` set to `"addStake"` + */ +export const useSimulateGatewayRegistryAddStake = + /*#__PURE__*/ createUseSimulateContract({ + abi: gatewayRegistryAbi, + functionName: 'addStake', + }) + +/** + * Wraps __{@link useSimulateContract}__ with `abi` set to __{@link gatewayRegistryAbi}__ and `functionName` set to `"disableAutoExtension"` + */ +export const useSimulateGatewayRegistryDisableAutoExtension = + /*#__PURE__*/ createUseSimulateContract({ + abi: gatewayRegistryAbi, + functionName: 'disableAutoExtension', + }) + +/** + * Wraps __{@link useSimulateContract}__ with `abi` set to __{@link gatewayRegistryAbi}__ and `functionName` set to `"enableAutoExtension"` + */ +export const useSimulateGatewayRegistryEnableAutoExtension = + /*#__PURE__*/ createUseSimulateContract({ + abi: gatewayRegistryAbi, + functionName: 'enableAutoExtension', + }) + +/** + * Wraps __{@link useReadContract}__ with `abi` set to __{@link networkControllerAbi}__ + */ +export const useReadNetworkController = /*#__PURE__*/ createUseReadContract({ + abi: networkControllerAbi, +}) + +/** + * Wraps __{@link useReadContract}__ with `abi` set to __{@link networkControllerAbi}__ and `functionName` set to `"bondAmount"` + */ +export const useReadNetworkControllerBondAmount = + /*#__PURE__*/ createUseReadContract({ + abi: networkControllerAbi, + functionName: 'bondAmount', + }) + +/** + * Wraps __{@link useReadContract}__ with `abi` set to __{@link networkControllerAbi}__ and `functionName` set to `"epochLength"` + */ +export const useReadNetworkControllerEpochLength = + /*#__PURE__*/ createUseReadContract({ + abi: networkControllerAbi, + functionName: 'epochLength', + }) + +/** + * Wraps __{@link useReadContract}__ with `abi` set to __{@link networkControllerAbi}__ and `functionName` set to `"workerEpochLength"` + */ +export const useReadNetworkControllerWorkerEpochLength = + /*#__PURE__*/ createUseReadContract({ + abi: networkControllerAbi, + functionName: 'workerEpochLength', + }) + +/** + * Wraps __{@link useWriteContract}__ with `abi` set to __{@link rewardTreasuryAbi}__ + */ +export const useWriteRewardTreasury = /*#__PURE__*/ createUseWriteContract({ + abi: rewardTreasuryAbi, +}) + +/** + * Wraps __{@link useWriteContract}__ with `abi` set to __{@link rewardTreasuryAbi}__ and `functionName` set to `"claimFor"` + */ +export const useWriteRewardTreasuryClaimFor = + /*#__PURE__*/ createUseWriteContract({ + abi: rewardTreasuryAbi, + functionName: 'claimFor', + }) + +/** + * Wraps __{@link useSimulateContract}__ with `abi` set to __{@link rewardTreasuryAbi}__ + */ +export const useSimulateRewardTreasury = + /*#__PURE__*/ createUseSimulateContract({ abi: rewardTreasuryAbi }) + +/** + * Wraps __{@link useSimulateContract}__ with `abi` set to __{@link rewardTreasuryAbi}__ and `functionName` set to `"claimFor"` + */ +export const useSimulateRewardTreasuryClaimFor = + /*#__PURE__*/ createUseSimulateContract({ + abi: rewardTreasuryAbi, + functionName: 'claimFor', + }) + +/** + * Wraps __{@link useReadContract}__ with `abi` set to __{@link routerAbi}__ + */ +export const useReadRouter = /*#__PURE__*/ createUseReadContract({ + abi: routerAbi, +}) + +/** + * Wraps __{@link useReadContract}__ with `abi` set to __{@link routerAbi}__ and `functionName` set to `"networkController"` + */ +export const useReadRouterNetworkController = + /*#__PURE__*/ createUseReadContract({ + abi: routerAbi, + functionName: 'networkController', + }) + +/** + * Wraps __{@link useReadContract}__ with `abi` set to __{@link routerAbi}__ and `functionName` set to `"rewardCalculation"` + */ +export const useReadRouterRewardCalculation = + /*#__PURE__*/ createUseReadContract({ + abi: routerAbi, + functionName: 'rewardCalculation', + }) + +/** + * Wraps __{@link useReadContract}__ with `abi` set to __{@link routerAbi}__ and `functionName` set to `"rewardTreasury"` + */ +export const useReadRouterRewardTreasury = /*#__PURE__*/ createUseReadContract({ + abi: routerAbi, + functionName: 'rewardTreasury', +}) + +/** + * Wraps __{@link useReadContract}__ with `abi` set to __{@link routerAbi}__ and `functionName` set to `"staking"` + */ +export const useReadRouterStaking = /*#__PURE__*/ createUseReadContract({ + abi: routerAbi, + functionName: 'staking', +}) + +/** + * Wraps __{@link useReadContract}__ with `abi` set to __{@link routerAbi}__ and `functionName` set to `"workerRegistration"` + */ +export const useReadRouterWorkerRegistration = + /*#__PURE__*/ createUseReadContract({ + abi: routerAbi, + functionName: 'workerRegistration', + }) + +/** + * Wraps __{@link useReadContract}__ with `abi` set to __{@link sqdAbi}__ + */ +export const useReadSqd = /*#__PURE__*/ createUseReadContract({ abi: sqdAbi }) + +/** + * Wraps __{@link useReadContract}__ with `abi` set to __{@link sqdAbi}__ and `functionName` set to `"allowance"` + */ +export const useReadSqdAllowance = /*#__PURE__*/ createUseReadContract({ + abi: sqdAbi, + functionName: 'allowance', +}) + +/** + * Wraps __{@link useReadContract}__ with `abi` set to __{@link sqdAbi}__ and `functionName` set to `"balanceOf"` + */ +export const useReadSqdBalanceOf = /*#__PURE__*/ createUseReadContract({ + abi: sqdAbi, + functionName: 'balanceOf', +}) + +/** + * Wraps __{@link useReadContract}__ with `abi` set to __{@link sqdAbi}__ and `functionName` set to `"decimals"` + */ +export const useReadSqdDecimals = /*#__PURE__*/ createUseReadContract({ + abi: sqdAbi, + functionName: 'decimals', +}) + +/** + * Wraps __{@link useReadContract}__ with `abi` set to __{@link sqdAbi}__ and `functionName` set to `"name"` + */ +export const useReadSqdName = /*#__PURE__*/ createUseReadContract({ + abi: sqdAbi, + functionName: 'name', +}) + +/** + * Wraps __{@link useReadContract}__ with `abi` set to __{@link sqdAbi}__ and `functionName` set to `"symbol"` + */ +export const useReadSqdSymbol = /*#__PURE__*/ createUseReadContract({ + abi: sqdAbi, + functionName: 'symbol', +}) + +/** + * Wraps __{@link useReadContract}__ with `abi` set to __{@link sqdAbi}__ and `functionName` set to `"totalSupply"` + */ +export const useReadSqdTotalSupply = /*#__PURE__*/ createUseReadContract({ + abi: sqdAbi, + functionName: 'totalSupply', +}) + +/** + * Wraps __{@link useWriteContract}__ with `abi` set to __{@link sqdAbi}__ + */ +export const useWriteSqd = /*#__PURE__*/ createUseWriteContract({ abi: sqdAbi }) + +/** + * Wraps __{@link useWriteContract}__ with `abi` set to __{@link sqdAbi}__ and `functionName` set to `"approve"` + */ +export const useWriteSqdApprove = /*#__PURE__*/ createUseWriteContract({ + abi: sqdAbi, + functionName: 'approve', +}) + +/** + * Wraps __{@link useWriteContract}__ with `abi` set to __{@link sqdAbi}__ and `functionName` set to `"transfer"` + */ +export const useWriteSqdTransfer = /*#__PURE__*/ createUseWriteContract({ + abi: sqdAbi, + functionName: 'transfer', +}) + +/** + * Wraps __{@link useWriteContract}__ with `abi` set to __{@link sqdAbi}__ and `functionName` set to `"transferFrom"` + */ +export const useWriteSqdTransferFrom = /*#__PURE__*/ createUseWriteContract({ + abi: sqdAbi, + functionName: 'transferFrom', +}) + +/** + * Wraps __{@link useSimulateContract}__ with `abi` set to __{@link sqdAbi}__ + */ +export const useSimulateSqd = /*#__PURE__*/ createUseSimulateContract({ + abi: sqdAbi, +}) + +/** + * Wraps __{@link useSimulateContract}__ with `abi` set to __{@link sqdAbi}__ and `functionName` set to `"approve"` + */ +export const useSimulateSqdApprove = /*#__PURE__*/ createUseSimulateContract({ + abi: sqdAbi, + functionName: 'approve', +}) + +/** + * Wraps __{@link useSimulateContract}__ with `abi` set to __{@link sqdAbi}__ and `functionName` set to `"transfer"` + */ +export const useSimulateSqdTransfer = /*#__PURE__*/ createUseSimulateContract({ + abi: sqdAbi, + functionName: 'transfer', +}) + +/** + * Wraps __{@link useSimulateContract}__ with `abi` set to __{@link sqdAbi}__ and `functionName` set to `"transferFrom"` + */ +export const useSimulateSqdTransferFrom = + /*#__PURE__*/ createUseSimulateContract({ + abi: sqdAbi, + functionName: 'transferFrom', + }) + +/** + * Wraps __{@link useWatchContractEvent}__ with `abi` set to __{@link sqdAbi}__ + */ +export const useWatchSqdEvent = /*#__PURE__*/ createUseWatchContractEvent({ + abi: sqdAbi, +}) + +/** + * Wraps __{@link useWatchContractEvent}__ with `abi` set to __{@link sqdAbi}__ and `eventName` set to `"Approval"` + */ +export const useWatchSqdApprovalEvent = + /*#__PURE__*/ createUseWatchContractEvent({ + abi: sqdAbi, + eventName: 'Approval', + }) + +/** + * Wraps __{@link useWatchContractEvent}__ with `abi` set to __{@link sqdAbi}__ and `eventName` set to `"Transfer"` + */ +export const useWatchSqdTransferEvent = + /*#__PURE__*/ createUseWatchContractEvent({ + abi: sqdAbi, + eventName: 'Transfer', + }) + +/** + * Wraps __{@link useReadContract}__ with `abi` set to __{@link softCapAbi}__ + */ +export const useReadSoftCap = /*#__PURE__*/ createUseReadContract({ + abi: softCapAbi, +}) + +/** + * Wraps __{@link useReadContract}__ with `abi` set to __{@link softCapAbi}__ and `functionName` set to `"cap"` + */ +export const useReadSoftCapCap = /*#__PURE__*/ createUseReadContract({ + abi: softCapAbi, + functionName: 'cap', +}) + +/** + * Wraps __{@link useReadContract}__ with `abi` set to __{@link softCapAbi}__ and `functionName` set to `"capedStake"` + */ +export const useReadSoftCapCapedStake = /*#__PURE__*/ createUseReadContract({ + abi: softCapAbi, + functionName: 'capedStake', +}) + +/** + * Wraps __{@link useReadContract}__ with `abi` set to __{@link softCapAbi}__ and `functionName` set to `"capedStakeAfterDelegation"` + */ +export const useReadSoftCapCapedStakeAfterDelegation = + /*#__PURE__*/ createUseReadContract({ + abi: softCapAbi, + functionName: 'capedStakeAfterDelegation', + }) + +/** + * Wraps __{@link useReadContract}__ with `abi` set to __{@link stakingAbi}__ + */ +export const useReadStaking = /*#__PURE__*/ createUseReadContract({ + abi: stakingAbi, +}) + +/** + * Wraps __{@link useReadContract}__ with `abi` set to __{@link stakingAbi}__ and `functionName` set to `"getDeposit"` + */ +export const useReadStakingGetDeposit = /*#__PURE__*/ createUseReadContract({ + abi: stakingAbi, + functionName: 'getDeposit', +}) + +/** + * Wraps __{@link useReadContract}__ with `abi` set to __{@link stakingAbi}__ and `functionName` set to `"claimable"` + */ +export const useReadStakingClaimable = /*#__PURE__*/ createUseReadContract({ + abi: stakingAbi, + functionName: 'claimable', +}) + +/** + * Wraps __{@link useReadContract}__ with `abi` set to __{@link stakingAbi}__ and `functionName` set to `"delegated"` + */ +export const useReadStakingDelegated = /*#__PURE__*/ createUseReadContract({ + abi: stakingAbi, + functionName: 'delegated', +}) + +/** + * Wraps __{@link useWriteContract}__ with `abi` set to __{@link stakingAbi}__ + */ +export const useWriteStaking = /*#__PURE__*/ createUseWriteContract({ + abi: stakingAbi, +}) + +/** + * Wraps __{@link useWriteContract}__ with `abi` set to __{@link stakingAbi}__ and `functionName` set to `"deposit"` + */ +export const useWriteStakingDeposit = /*#__PURE__*/ createUseWriteContract({ + abi: stakingAbi, + functionName: 'deposit', +}) + +/** + * Wraps __{@link useWriteContract}__ with `abi` set to __{@link stakingAbi}__ and `functionName` set to `"withdraw"` + */ +export const useWriteStakingWithdraw = /*#__PURE__*/ createUseWriteContract({ + abi: stakingAbi, + functionName: 'withdraw', +}) + +/** + * Wraps __{@link useSimulateContract}__ with `abi` set to __{@link stakingAbi}__ + */ +export const useSimulateStaking = /*#__PURE__*/ createUseSimulateContract({ + abi: stakingAbi, +}) + +/** + * Wraps __{@link useSimulateContract}__ with `abi` set to __{@link stakingAbi}__ and `functionName` set to `"deposit"` + */ +export const useSimulateStakingDeposit = + /*#__PURE__*/ createUseSimulateContract({ + abi: stakingAbi, + functionName: 'deposit', + }) + +/** + * Wraps __{@link useSimulateContract}__ with `abi` set to __{@link stakingAbi}__ and `functionName` set to `"withdraw"` + */ +export const useSimulateStakingWithdraw = + /*#__PURE__*/ createUseSimulateContract({ + abi: stakingAbi, + functionName: 'withdraw', + }) + +/** + * Wraps __{@link useReadContract}__ with `abi` set to __{@link vestingAbi}__ + */ +export const useReadVesting = /*#__PURE__*/ createUseReadContract({ + abi: vestingAbi, +}) + +/** + * Wraps __{@link useReadContract}__ with `abi` set to __{@link vestingAbi}__ and `functionName` set to `"depositedIntoProtocol"` + */ +export const useReadVestingDepositedIntoProtocol = + /*#__PURE__*/ createUseReadContract({ + abi: vestingAbi, + functionName: 'depositedIntoProtocol', + }) + +/** + * Wraps __{@link useReadContract}__ with `abi` set to __{@link vestingAbi}__ and `functionName` set to `"duration"` + */ +export const useReadVestingDuration = /*#__PURE__*/ createUseReadContract({ + abi: vestingAbi, + functionName: 'duration', +}) + +/** + * Wraps __{@link useReadContract}__ with `abi` set to __{@link vestingAbi}__ and `functionName` set to `"end"` + */ +export const useReadVestingEnd = /*#__PURE__*/ createUseReadContract({ + abi: vestingAbi, + functionName: 'end', +}) + +/** + * Wraps __{@link useReadContract}__ with `abi` set to __{@link vestingAbi}__ and `functionName` set to `"expectedTotalAmount"` + */ +export const useReadVestingExpectedTotalAmount = + /*#__PURE__*/ createUseReadContract({ + abi: vestingAbi, + functionName: 'expectedTotalAmount', + }) + +/** + * Wraps __{@link useReadContract}__ with `abi` set to __{@link vestingAbi}__ and `functionName` set to `"immediateReleaseBIP"` + */ +export const useReadVestingImmediateReleaseBip = + /*#__PURE__*/ createUseReadContract({ + abi: vestingAbi, + functionName: 'immediateReleaseBIP', + }) + +/** + * Wraps __{@link useReadContract}__ with `abi` set to __{@link vestingAbi}__ and `functionName` set to `"releasable"` + */ +export const useReadVestingReleasable = /*#__PURE__*/ createUseReadContract({ + abi: vestingAbi, + functionName: 'releasable', +}) + +/** + * Wraps __{@link useReadContract}__ with `abi` set to __{@link vestingAbi}__ and `functionName` set to `"released"` + */ +export const useReadVestingReleased = /*#__PURE__*/ createUseReadContract({ + abi: vestingAbi, + functionName: 'released', +}) + +/** + * Wraps __{@link useReadContract}__ with `abi` set to __{@link vestingAbi}__ and `functionName` set to `"start"` + */ +export const useReadVestingStart = /*#__PURE__*/ createUseReadContract({ + abi: vestingAbi, + functionName: 'start', +}) + +/** + * Wraps __{@link useReadContract}__ with `abi` set to __{@link vestingAbi}__ and `functionName` set to `"vestedAmount"` + */ +export const useReadVestingVestedAmount = /*#__PURE__*/ createUseReadContract({ + abi: vestingAbi, + functionName: 'vestedAmount', +}) + +/** + * Wraps __{@link useWriteContract}__ with `abi` set to __{@link vestingAbi}__ + */ +export const useWriteVesting = /*#__PURE__*/ createUseWriteContract({ + abi: vestingAbi, +}) + +/** + * Wraps __{@link useWriteContract}__ with `abi` set to __{@link vestingAbi}__ and `functionName` set to `"execute"` + */ +export const useWriteVestingExecute = /*#__PURE__*/ createUseWriteContract({ + abi: vestingAbi, + functionName: 'execute', +}) + +/** + * Wraps __{@link useWriteContract}__ with `abi` set to __{@link vestingAbi}__ and `functionName` set to `"release"` + */ +export const useWriteVestingRelease = /*#__PURE__*/ createUseWriteContract({ + abi: vestingAbi, + functionName: 'release', +}) + +/** + * Wraps __{@link useSimulateContract}__ with `abi` set to __{@link vestingAbi}__ + */ +export const useSimulateVesting = /*#__PURE__*/ createUseSimulateContract({ + abi: vestingAbi, +}) + +/** + * Wraps __{@link useSimulateContract}__ with `abi` set to __{@link vestingAbi}__ and `functionName` set to `"execute"` + */ +export const useSimulateVestingExecute = + /*#__PURE__*/ createUseSimulateContract({ + abi: vestingAbi, + functionName: 'execute', + }) + +/** + * Wraps __{@link useSimulateContract}__ with `abi` set to __{@link vestingAbi}__ and `functionName` set to `"release"` + */ +export const useSimulateVestingRelease = + /*#__PURE__*/ createUseSimulateContract({ + abi: vestingAbi, + functionName: 'release', + }) + +/** + * Wraps __{@link useReadContract}__ with `abi` set to __{@link workerRegistryAbi}__ + */ +export const useReadWorkerRegistry = /*#__PURE__*/ createUseReadContract({ + abi: workerRegistryAbi, +}) + +/** + * Wraps __{@link useReadContract}__ with `abi` set to __{@link workerRegistryAbi}__ and `functionName` set to `"bondAmount"` + */ +export const useReadWorkerRegistryBondAmount = + /*#__PURE__*/ createUseReadContract({ + abi: workerRegistryAbi, + functionName: 'bondAmount', + }) + +/** + * Wraps __{@link useReadContract}__ with `abi` set to __{@link workerRegistryAbi}__ and `functionName` set to `"lockPeriod"` + */ +export const useReadWorkerRegistryLockPeriod = + /*#__PURE__*/ createUseReadContract({ + abi: workerRegistryAbi, + functionName: 'lockPeriod', + }) + +/** + * Wraps __{@link useReadContract}__ with `abi` set to __{@link workerRegistryAbi}__ and `functionName` set to `"getWorker"` + */ +export const useReadWorkerRegistryGetWorker = + /*#__PURE__*/ createUseReadContract({ + abi: workerRegistryAbi, + functionName: 'getWorker', + }) + +/** + * Wraps __{@link useWriteContract}__ with `abi` set to __{@link workerRegistryAbi}__ + */ +export const useWriteWorkerRegistry = /*#__PURE__*/ createUseWriteContract({ + abi: workerRegistryAbi, +}) + +/** + * Wraps __{@link useWriteContract}__ with `abi` set to __{@link workerRegistryAbi}__ and `functionName` set to `"register"` + */ +export const useWriteWorkerRegistryRegister = + /*#__PURE__*/ createUseWriteContract({ + abi: workerRegistryAbi, + functionName: 'register', + }) + +/** + * Wraps __{@link useWriteContract}__ with `abi` set to __{@link workerRegistryAbi}__ and `functionName` set to `"updateMetadata"` + */ +export const useWriteWorkerRegistryUpdateMetadata = + /*#__PURE__*/ createUseWriteContract({ + abi: workerRegistryAbi, + functionName: 'updateMetadata', + }) + +/** + * Wraps __{@link useWriteContract}__ with `abi` set to __{@link workerRegistryAbi}__ and `functionName` set to `"deregister"` + */ +export const useWriteWorkerRegistryDeregister = + /*#__PURE__*/ createUseWriteContract({ + abi: workerRegistryAbi, + functionName: 'deregister', + }) + +/** + * Wraps __{@link useWriteContract}__ with `abi` set to __{@link workerRegistryAbi}__ and `functionName` set to `"withdraw"` + */ +export const useWriteWorkerRegistryWithdraw = + /*#__PURE__*/ createUseWriteContract({ + abi: workerRegistryAbi, + functionName: 'withdraw', + }) + +/** + * Wraps __{@link useSimulateContract}__ with `abi` set to __{@link workerRegistryAbi}__ + */ +export const useSimulateWorkerRegistry = + /*#__PURE__*/ createUseSimulateContract({ abi: workerRegistryAbi }) + +/** + * Wraps __{@link useSimulateContract}__ with `abi` set to __{@link workerRegistryAbi}__ and `functionName` set to `"register"` + */ +export const useSimulateWorkerRegistryRegister = + /*#__PURE__*/ createUseSimulateContract({ + abi: workerRegistryAbi, + functionName: 'register', + }) + +/** + * Wraps __{@link useSimulateContract}__ with `abi` set to __{@link workerRegistryAbi}__ and `functionName` set to `"updateMetadata"` + */ +export const useSimulateWorkerRegistryUpdateMetadata = + /*#__PURE__*/ createUseSimulateContract({ + abi: workerRegistryAbi, + functionName: 'updateMetadata', + }) + +/** + * Wraps __{@link useSimulateContract}__ with `abi` set to __{@link workerRegistryAbi}__ and `functionName` set to `"deregister"` + */ +export const useSimulateWorkerRegistryDeregister = + /*#__PURE__*/ createUseSimulateContract({ + abi: workerRegistryAbi, + functionName: 'deregister', + }) + +/** + * Wraps __{@link useSimulateContract}__ with `abi` set to __{@link workerRegistryAbi}__ and `functionName` set to `"withdraw"` + */ +export const useSimulateWorkerRegistryWithdraw = + /*#__PURE__*/ createUseSimulateContract({ + abi: workerRegistryAbi, + functionName: 'withdraw', + }) diff --git a/src/api/contracts/useWriteTransaction.ts b/src/api/contracts/useWriteTransaction.ts new file mode 100644 index 0000000..c0d125c --- /dev/null +++ b/src/api/contracts/useWriteTransaction.ts @@ -0,0 +1,159 @@ +import { useState } from 'react'; + +import { MutateOptions } from '@tanstack/react-query'; +import { + readContract, + waitForTransactionReceipt, + WaitForTransactionReceiptReturnType, + writeContract, +} from '@wagmi/core'; +import { + Abi, + Address, + ContractFunctionArgs, + ContractFunctionName, + encodeFunctionData, + erc20Abi, + WriteContractErrorType, +} from 'viem'; +import { + Config, + ResolvedRegister, + useAccount, + useConfig, + useWriteContract, + UseWriteContractParameters, + UseWriteContractReturnType, +} from 'wagmi'; +import { WriteContractData, WriteContractVariables } from 'wagmi/query'; + +import { useContracts } from '@network/useContracts'; + +import { vestingAbi } from './subsquid.generated'; + +export type UseWriteTransactionParameters< + config extends Config, + context, +> = UseWriteContractParameters; + +export type UseWriteTransactionReturnType = Omit< + UseWriteContractReturnType, + 'writeContractAsync' | 'writeContract' +> & { + writeTransactionAsync: WriteTransactionMutateAsync; +}; + +export type WriteTransactionMutateAsync = < + const abi extends Abi | readonly unknown[], + functionName extends ContractFunctionName, + args extends ContractFunctionArgs, + chainId extends config['chains'][number]['id'], +>( + variables: WriteContractVariables & { + approve?: bigint; + vesting?: Address; + }, + options?: + | MutateOptions< + WriteContractData, + WriteContractErrorType, + WriteContractVariables< + abi, + functionName, + args, + config, + chainId, + // use `functionName` to make sure it's not union of all possible function names + functionName + >, + context + > + | undefined, +) => Promise>; + +export function useWriteSQDTransaction< + config extends Config = ResolvedRegister['config'], + context = unknown, +>( + parameters: UseWriteTransactionParameters = {}, +): UseWriteTransactionReturnType { + const account = useAccount(); + const config = useConfig(parameters); + const { writeContractAsync, ...result } = useWriteContract(parameters); + const [isPending, setPending] = useState(false); + const [error, setError] = useState(null); + const { SQD } = useContracts(); + + return { + ...(result as any), + isPending, + error, + isError: !!error, + writeTransactionAsync: async ( + ...args: Parameters> + ) => { + setPending(true); + try { + const address = + (typeof args[0].account === 'string' ? args[0].account : args[0].account?.address) || + account.address; + if (!address) return; + + let hash: `0x${string}`; + if (args[0].vesting) { + const { vesting, address, ...rest } = args[0]; + + const encodedFunctionData = encodeFunctionData({ + abi: args[0].abi, + functionName: args[0].functionName, + args: args[0].args, + }); + + hash = await writeContractAsync( + { + ...(args[0] as any), + address: vesting, + abi: vestingAbi, + functionName: 'execute', + args: args[0].approve + ? [address, encodedFunctionData, rest.approve] + : [address, encodedFunctionData], + }, + args[1] as any, + ); + } else { + if (args[0].approve) { + const amount = args[0].approve; + + const allowance = await readContract(config, { + abi: erc20Abi, + functionName: 'allowance', + address: SQD, + args: [address, args[0].address], + }); + + if (allowance < amount) { + const hash = await writeContract(config as any, { + ...(args[0] as any), + abi: erc20Abi, + functionName: 'approve', + address: SQD, + args: [args[0].address, amount], + }); + await waitForTransactionReceipt(config, { hash }); + } + } + + hash = await writeContractAsync(args[0], args[1] as any); + } + + return await waitForTransactionReceipt(config, { hash }); + } catch (e) { + setError(e as WriteContractErrorType); + throw e; + } finally { + setPending(false); + } + }, + }; +} diff --git a/src/api/contracts/vesting.abi.ts b/src/api/contracts/vesting.abi.ts deleted file mode 100644 index 7c2108e..0000000 --- a/src/api/contracts/vesting.abi.ts +++ /dev/null @@ -1,215 +0,0 @@ -export const VESTING_CONTRACT_ABI = [ - { - type: 'function', - name: 'depositedIntoProtocol', - inputs: [], - outputs: [ - { - name: '', - type: 'uint256', - internalType: 'uint256', - }, - ], - stateMutability: 'view', - }, - { - type: 'function', - name: 'duration', - inputs: [], - outputs: [ - { - name: '', - type: 'uint256', - internalType: 'uint256', - }, - ], - stateMutability: 'view', - }, - { - type: 'function', - name: 'end', - inputs: [], - outputs: [ - { - name: '', - type: 'uint256', - internalType: 'uint256', - }, - ], - stateMutability: 'view', - }, - { - type: 'function', - name: 'execute', - inputs: [ - { - name: 'to', - type: 'address', - internalType: 'address', - }, - { - name: 'data', - type: 'bytes', - internalType: 'bytes', - }, - ], - outputs: [], - stateMutability: 'nonpayable', - }, - { - type: 'function', - name: 'execute', - inputs: [ - { - name: 'to', - type: 'address', - internalType: 'address', - }, - { - name: 'data', - type: 'bytes', - internalType: 'bytes', - }, - { - name: 'requiredApprove', - type: 'uint256', - internalType: 'uint256', - }, - ], - outputs: [ - { - name: '', - type: 'bytes', - internalType: 'bytes', - }, - ], - stateMutability: 'nonpayable', - }, - { - type: 'function', - name: 'expectedTotalAmount', - inputs: [], - outputs: [ - { - name: '', - type: 'uint256', - internalType: 'uint256', - }, - ], - stateMutability: 'view', - }, - { - type: 'function', - name: 'immediateReleaseBIP', - inputs: [], - outputs: [ - { - name: '', - type: 'uint256', - internalType: 'uint256', - }, - ], - stateMutability: 'view', - }, - { - type: 'function', - name: 'releasable', - inputs: [ - { - name: 'token', - type: 'address', - internalType: 'address', - }, - ], - outputs: [ - { - name: '', - type: 'uint256', - internalType: 'uint256', - }, - ], - stateMutability: 'view', - }, - { - type: 'function', - name: 'releasable', - inputs: [], - outputs: [ - { - name: '', - type: 'uint256', - internalType: 'uint256', - }, - ], - stateMutability: 'view', - }, - { - type: 'function', - name: 'release', - inputs: [ - { - name: 'token', - type: 'address', - internalType: 'address', - }, - ], - outputs: [], - stateMutability: 'nonpayable', - }, - { - type: 'function', - name: 'released', - inputs: [ - { - name: 'token', - type: 'address', - internalType: 'address', - }, - ], - outputs: [ - { - name: '', - type: 'uint256', - internalType: 'uint256', - }, - ], - stateMutability: 'view', - }, - { - type: 'function', - name: 'start', - inputs: [], - outputs: [ - { - name: '', - type: 'uint256', - internalType: 'uint256', - }, - ], - stateMutability: 'view', - }, - { - type: 'function', - name: 'vestedAmount', - inputs: [ - { - name: 'token', - type: 'address', - internalType: 'address', - }, - { - name: 'timestamp', - type: 'uint64', - internalType: 'uint64', - }, - ], - outputs: [ - { - name: '', - type: 'uint256', - internalType: 'uint256', - }, - ], - stateMutability: 'view', - }, -] as const; diff --git a/src/api/contracts/vesting.ts b/src/api/contracts/vesting.ts deleted file mode 100644 index 6e55445..0000000 --- a/src/api/contracts/vesting.ts +++ /dev/null @@ -1,144 +0,0 @@ -import { useState } from 'react'; - -import { keepPreviousData } from '@tanstack/react-query'; -import { chunk } from 'lodash-es'; -import { erc20Abi, MulticallResponse } from 'viem'; -import { waitForTransactionReceipt } from 'viem/actions'; -import { useReadContracts, useWriteContract, useClient } from 'wagmi'; - -import { useSquidNetworkHeight } from '@hooks/useSquidNetworkHeightHooks'; -import { useContracts } from '@network/useContracts'; - -import { errorMessage, WriteContractRes } from './utils'; -import { VESTING_CONTRACT_ABI } from './vesting.abi'; - -export function useVestingContracts({ addresses }: { addresses?: `0x${string}`[] }) { - const contracts = useContracts(); - const { currentHeight, isLoading: isSquidHeightLoading } = useSquidNetworkHeight(); - - const { data, isLoading } = useReadContracts({ - contracts: addresses?.flatMap(address => { - const vestingContract = { abi: VESTING_CONTRACT_ABI, address } as const; - return [ - { - ...vestingContract, - functionName: 'start', - }, - { - ...vestingContract, - functionName: 'end', - }, - { - ...vestingContract, - functionName: 'depositedIntoProtocol', - }, - { - ...vestingContract, - functionName: 'releasable', - args: [contracts.SQD], - }, - { - ...vestingContract, - functionName: 'released', - args: [contracts.SQD], - }, - { - abi: erc20Abi, - address: contracts.SQD, - functionName: 'balanceOf', - args: [address], - }, - { - ...vestingContract, - functionName: 'immediateReleaseBIP', - }, - { - ...vestingContract, - functionName: 'expectedTotalAmount', - }, - ] as const; - }), - allowFailure: true, - // blockNumber: BigInt(currentHeight), - query: { - // enabled: !isSquidHeightLoading && !!addresses?.length, - placeholderData: keepPreviousData, - select: res => { - if (res?.some(r => r.status === 'success')) { - return chunk(res, 8).map(ch => ({ - start: Number(unwrapResult(ch[0])) * 1000, - end: Number(unwrapResult(ch[1])) * 1000, - deposited: unwrapResult(ch[2])?.toString(), - releasable: unwrapResult(ch[3])?.toString(), - released: unwrapResult(ch[4])?.toString(), - balance: unwrapResult(ch[5])?.toString(), - initialRelease: Number(unwrapResult(ch[6]) || 0) / 100, - expectedTotal: unwrapResult(ch[7])?.toString(), - })); - } else if (res?.length === 0) { - return []; - } - - return undefined; - }, - }, - }); - - return { - data, - isLoading: isLoading, - }; -} - -export function useVestingContract({ address }: { address?: `0x${string}` }) { - const { data, isLoading } = useVestingContracts({ addresses: address ? [address] : [] }); - - return { - data: data?.[0], - isLoading, - }; -} - -function unwrapResult(result?: MulticallResponse): T | undefined { - return result?.status === 'success' ? (result.result as T) : undefined; -} - -export function useVestingContractRelease() { - const client = useClient(); - const { setWaitHeight } = useSquidNetworkHeight(); - const [isLoading, setLoading] = useState(false); - const [error, setError] = useState(null); - const { SQD } = useContracts(); - - const { writeContractAsync } = useWriteContract({}); - - const release = async ({ address }: { address: `0x${string}` }): Promise => { - setLoading(true); - - try { - const hash = await writeContractAsync({ - abi: VESTING_CONTRACT_ABI, - functionName: 'release', - args: [SQD], - address, - }); - - const receipt = await waitForTransactionReceipt(client!, { hash }); - setWaitHeight(receipt.blockNumber, []); - - return { success: true }; - } catch (e) { - const failedReason = errorMessage(e); - setError(failedReason); - return { success: false, failedReason }; - } finally { - setLoading(false); - } - }; - - return { - release, - isLoading, - error, - }; -} diff --git a/src/api/contracts/worker-registration/WorkerMetadata.ts b/src/api/contracts/worker-registration/WorkerMetadata.ts index 5c86c37..86bdc06 100644 --- a/src/api/contracts/worker-registration/WorkerMetadata.ts +++ b/src/api/contracts/worker-registration/WorkerMetadata.ts @@ -2,9 +2,9 @@ import { pickBy } from 'lodash-es'; import isEmpty from 'lodash-es/isEmpty'; export interface WorkerMetadata { - name: string; - email: string; - description: string; + name?: string; + email?: string; + description?: string; website?: string; } diff --git a/src/api/contracts/worker-registration/WorkerRegistration.abi.ts b/src/api/contracts/worker-registration/WorkerRegistration.abi.ts deleted file mode 100644 index 8f2c7a8..0000000 --- a/src/api/contracts/worker-registration/WorkerRegistration.abi.ts +++ /dev/null @@ -1,78 +0,0 @@ -export const WORKER_REGISTRATION_CONTRACT_ABI = [ - { - type: 'function', - name: 'bondAmount', - inputs: [], - outputs: [ - { - name: '', - type: 'uint256', - internalType: 'uint256', - }, - ], - stateMutability: 'view', - }, - { - type: 'function', - name: 'register', - inputs: [ - { - name: 'peerId', - type: 'bytes', - internalType: 'bytes', - }, - { - name: 'metadata', - type: 'string', - internalType: 'string', - }, - ], - outputs: [], - stateMutability: 'nonpayable', - }, - { - type: 'function', - name: 'updateMetadata', - inputs: [ - { - name: 'peerId', - type: 'bytes', - internalType: 'bytes', - }, - { - name: 'metadata', - type: 'string', - internalType: 'string', - }, - ], - outputs: [], - stateMutability: 'nonpayable', - }, - { - inputs: [ - { - internalType: 'bytes', - name: 'peerId', - type: 'bytes', - }, - ], - name: 'deregister', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, - - { - type: 'function', - name: 'withdraw', - inputs: [ - { - name: 'peerId', - type: 'bytes', - internalType: 'bytes', - }, - ], - outputs: [], - stateMutability: 'nonpayable', - }, -] as const; diff --git a/src/api/contracts/worker-registration/useRegisterWorker.ts b/src/api/contracts/worker-registration/useRegisterWorker.ts deleted file mode 100644 index 457bae0..0000000 --- a/src/api/contracts/worker-registration/useRegisterWorker.ts +++ /dev/null @@ -1,151 +0,0 @@ -import { useState } from 'react'; - -import { peerIdToHex } from '@lib/network'; -import { logger } from '@logger'; -import { encodeFunctionData } from 'viem'; -import { waitForTransactionReceipt } from 'viem/actions'; -import { useWriteContract, usePublicClient, useClient } from 'wagmi'; - -import { useApproveSqd } from '@api/contracts/sqd'; -import { VESTING_CONTRACT_ABI } from '@api/contracts/vesting.abi'; -import { AccountType, SourceWallet } from '@api/subsquid-network-squid'; -import { useSquidNetworkHeight } from '@hooks/useSquidNetworkHeightHooks'; -import { useAccount } from '@network/useAccount'; -import { useContracts } from '@network/useContracts.ts'; - -import { TxResult, errorMessage, isApproveRequiredError, WriteContractRes } from '../utils'; - -import { encodeWorkerMetadata, WorkerMetadata } from './WorkerMetadata'; -import { WORKER_REGISTRATION_CONTRACT_ABI } from './WorkerRegistration.abi'; - -export interface AddWorkerRequest extends WorkerMetadata { - peerId: string; - source: SourceWallet; -} - -function useRegisterFromWallet() { - const contracts = useContracts(); - const publicClient = usePublicClient(); - const [approveSqd] = useApproveSqd(); - - const { writeContractAsync } = useWriteContract({}); - - const tryCallRegistrationContract = async ({ - peerId, - ...rest - }: AddWorkerRequest): Promise => { - try { - return { - tx: await writeContractAsync({ - address: contracts.WORKER_REGISTRATION, - abi: WORKER_REGISTRATION_CONTRACT_ABI, - functionName: 'register', - args: [peerIdToHex(peerId), encodeWorkerMetadata(rest)], - }), - }; - } catch (e: unknown) { - return { - error: errorMessage(e), - }; - } - }; - - return async (req: AddWorkerRequest): Promise => { - logger.debug(`registering worker via worker contract...`); - - const res = await tryCallRegistrationContract(req); - // Try to approve SQD - if (isApproveRequiredError(res.error)) { - const bond = await publicClient!.readContract({ - address: contracts.WORKER_REGISTRATION, - abi: WORKER_REGISTRATION_CONTRACT_ABI, - functionName: 'bondAmount', - }); - - const approveRes = await approveSqd({ - contractAddress: contracts.WORKER_REGISTRATION, - amount: bond.toString(), - }); - if (!approveRes.success) { - return { error: approveRes.failedReason }; - } - - logger.debug(`approved SQD successfully, now trying to register one more time...`); - - return tryCallRegistrationContract(req); - } - - return res; - }; -} - -function useRegisterWorkerFromVestingContract() { - const contracts = useContracts(); - const publicClient = usePublicClient(); - const { address: account } = useAccount(); - const { writeContractAsync } = useWriteContract({}); - - return async ({ peerId, source, ...rest }: AddWorkerRequest): Promise => { - try { - const bond = await publicClient!.readContract({ - address: contracts.WORKER_REGISTRATION, - abi: WORKER_REGISTRATION_CONTRACT_ABI, - functionName: 'bondAmount', - }); - - const data = encodeFunctionData({ - abi: WORKER_REGISTRATION_CONTRACT_ABI, - functionName: 'register', - args: [peerIdToHex(peerId), encodeWorkerMetadata(rest)], - }); - - return { - tx: await writeContractAsync({ - account, - address: source.id as `0x${string}`, - abi: VESTING_CONTRACT_ABI, - functionName: 'execute', - args: [contracts.WORKER_REGISTRATION, data, bond], - }), - }; - } catch (e: unknown) { - return { error: errorMessage(e) }; - } - }; -} - -export function useRegisterWorker() { - const client = useClient(); - const { address } = useAccount(); - const [error, setError] = useState(null); - const [isLoading, setLoading] = useState(false); - - const { setWaitHeight } = useSquidNetworkHeight(); - const registerWorkerContract = useRegisterFromWallet(); - const registerVestingContract = useRegisterWorkerFromVestingContract(); - - const registerWorker = async (req: AddWorkerRequest): Promise => { - setLoading(true); - - const { tx, error } = - req.source.type === AccountType.Vesting - ? await registerVestingContract(req) - : await registerWorkerContract(req); - - if (!tx) { - logger.debug(`registering worker failed ${error}`); - setLoading(false); - setError(error); - return { success: false, failedReason: error }; - } - - const receipt = await waitForTransactionReceipt(client!, { hash: tx }); - setWaitHeight(receipt.blockNumber, ['myWorkers', { address }]); - setLoading(false); - setError(null); - - return { success: true }; - }; - - return { registerWorker, isLoading, error }; -} diff --git a/src/api/contracts/worker-registration/useUnregisterWorker.ts b/src/api/contracts/worker-registration/useUnregisterWorker.ts deleted file mode 100644 index bea5a0a..0000000 --- a/src/api/contracts/worker-registration/useUnregisterWorker.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { useState } from 'react'; - -import { peerIdToHex } from '@lib/network'; -import { logger } from '@logger'; -import { encodeFunctionData } from 'viem'; -import { waitForTransactionReceipt } from 'viem/actions'; -import { useWriteContract, useClient } from 'wagmi'; - -import { VESTING_CONTRACT_ABI } from '@api/contracts/vesting.abi'; -import { AccountType } from '@api/subsquid-network-squid'; -import { useSquidNetworkHeight } from '@hooks/useSquidNetworkHeightHooks'; -import { useAccount } from '@network/useAccount'; -import { useContracts } from '@network/useContracts.ts'; - -import { TxResult, errorMessage, WriteContractRes } from '../utils'; - -import { WORKER_REGISTRATION_CONTRACT_ABI } from './WorkerRegistration.abi'; - -export interface UnregisterWorkerRequest { - peerId: string; - source: { - id: string; - type: AccountType; - }; -} - -function useUnregisterWorkerFromWallet() { - const contracts = useContracts(); - const { writeContractAsync } = useWriteContract({}); - - return async ({ peerId }: { peerId: string }): Promise => { - try { - return { - tx: await writeContractAsync({ - address: contracts.WORKER_REGISTRATION, - abi: WORKER_REGISTRATION_CONTRACT_ABI, - functionName: 'deregister', - args: [peerIdToHex(peerId)], - }), - }; - } catch (e) { - return { error: errorMessage(e) }; - } - }; -} - -function useUnregisterWorkerFromVestingContract() { - const contracts = useContracts(); - const { address: account } = useAccount(); - const { writeContractAsync } = useWriteContract({}); - - return async ({ peerId, source }: UnregisterWorkerRequest): Promise => { - try { - const data = encodeFunctionData({ - abi: WORKER_REGISTRATION_CONTRACT_ABI, - functionName: 'deregister', - args: [peerIdToHex(peerId)], - }); - - return { - tx: await writeContractAsync({ - account, - address: source.id as `0x${string}`, - abi: VESTING_CONTRACT_ABI, - functionName: 'execute', - args: [contracts.WORKER_REGISTRATION, data], - }), - }; - } catch (e: unknown) { - return { error: errorMessage(e) }; - } - }; -} - -export function useUnregisterWorker() { - const client = useClient(); - const { address } = useAccount(); - const [isLoading, setLoading] = useState(false); - const { setWaitHeight } = useSquidNetworkHeight(); - const [error, setError] = useState(null); - - const unregisterWorkerFromWallet = useUnregisterWorkerFromWallet(); - const unregisterWorkerFromVestingContract = useUnregisterWorkerFromVestingContract(); - - const unregisterWorker = async (req: UnregisterWorkerRequest): Promise => { - setLoading(true); - - const { tx, error } = - req.source.type === AccountType.User - ? await unregisterWorkerFromWallet(req) - : await unregisterWorkerFromVestingContract(req); - - if (!tx) { - logger.debug(`update worker failed ${error}`); - setLoading(false); - setError(error); - return { success: false, failedReason: error }; - } - - const receipt = await waitForTransactionReceipt(client!, { hash: tx }); - setWaitHeight(receipt.blockNumber, ['myWorkers', { address }]); - setLoading(false); - setError(null); - - return { success: true }; - }; - - return { - unregisterWorker, - isLoading, - error, - }; -} diff --git a/src/api/contracts/worker-registration/useUpdateWorker.ts b/src/api/contracts/worker-registration/useUpdateWorker.ts deleted file mode 100644 index 5f3581f..0000000 --- a/src/api/contracts/worker-registration/useUpdateWorker.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { useState } from 'react'; - -import { peerIdToHex } from '@lib/network'; -import { logger } from '@logger'; -import { encodeFunctionData } from 'viem'; -import { waitForTransactionReceipt } from 'viem/actions'; -import { useWriteContract, useClient } from 'wagmi'; - -import { AccountType } from '@api/subsquid-network-squid'; -import { useSquidNetworkHeight } from '@hooks/useSquidNetworkHeightHooks'; -import { useAccount } from '@network/useAccount'; -import { useContracts } from '@network/useContracts.ts'; - -import { TxResult, errorMessage, WriteContractRes } from '../utils'; -import { VESTING_CONTRACT_ABI } from '../vesting.abi'; - -import { encodeWorkerMetadata, WorkerMetadata } from './WorkerMetadata'; -import { WORKER_REGISTRATION_CONTRACT_ABI } from './WorkerRegistration.abi'; - -export interface UpdateWorkerRequest extends WorkerMetadata { - peerId: string; - source: { - id: string; - type: AccountType; - }; -} - -function useUpdateWorkerFromWallet() { - const contracts = useContracts(); - const { writeContractAsync } = useWriteContract({}); - - return async ({ peerId, ...rest }: UpdateWorkerRequest): Promise => { - try { - return { - tx: await writeContractAsync({ - address: contracts.WORKER_REGISTRATION, - abi: WORKER_REGISTRATION_CONTRACT_ABI, - functionName: 'updateMetadata', - args: [peerIdToHex(peerId), encodeWorkerMetadata(rest)], - }), - }; - } catch (e) { - return { error: errorMessage(e) }; - } - }; -} - -function useUpdateWorkerFromVestingContract() { - const contracts = useContracts(); - const { address: account } = useAccount(); - const { writeContractAsync } = useWriteContract({}); - - return async ({ peerId, source, ...rest }: UpdateWorkerRequest): Promise => { - try { - const data = encodeFunctionData({ - abi: WORKER_REGISTRATION_CONTRACT_ABI, - functionName: 'updateMetadata', - args: [peerIdToHex(peerId), encodeWorkerMetadata(rest)], - }); - - return { - tx: await writeContractAsync({ - account, - address: source.id as `0x${string}`, - abi: VESTING_CONTRACT_ABI, - functionName: 'execute', - args: [contracts.WORKER_REGISTRATION, data], - }), - }; - } catch (e: unknown) { - return { error: errorMessage(e) }; - } - }; -} - -export function useUpdateWorker() { - const client = useClient(); - const { address } = useAccount(); - const [error, setError] = useState(null); - const [isLoading, setLoading] = useState(false); - - const { setWaitHeight } = useSquidNetworkHeight(); - const updateWorkerFromWallet = useUpdateWorkerFromWallet(); - const updateWorkerFromVestingContract = useUpdateWorkerFromVestingContract(); - - const updateWorker = async (req: UpdateWorkerRequest): Promise => { - setLoading(true); - - const { tx, error } = - req.source.type === AccountType.User - ? await updateWorkerFromWallet(req) - : await updateWorkerFromVestingContract(req); - - if (!tx) { - logger.debug(`update worker failed ${error}`); - setLoading(false); - setError(error); - return { success: false, failedReason: error }; - } - - const receipt = await waitForTransactionReceipt(client!, { hash: tx }); - setWaitHeight(receipt.blockNumber, ['myWorkers', { address }]); - setLoading(false); - setError(null); - - return { success: true }; - }; - - return { updateWorker, isLoading, error }; -} diff --git a/src/api/contracts/worker-registration/useWithdrawWorker.ts b/src/api/contracts/worker-registration/useWithdrawWorker.ts deleted file mode 100644 index c72ac25..0000000 --- a/src/api/contracts/worker-registration/useWithdrawWorker.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { useState } from 'react'; - -import { peerIdToHex } from '@lib/network'; -import { logger } from '@logger'; -import { encodeFunctionData } from 'viem'; -import { waitForTransactionReceipt } from 'viem/actions'; -import { useWriteContract, useClient } from 'wagmi'; - -import { VESTING_CONTRACT_ABI } from '@api/contracts/vesting.abi'; -import { UnregisterWorkerRequest } from '@api/contracts/worker-registration/useUnregisterWorker'; -import { AccountType } from '@api/subsquid-network-squid'; -import { useSquidNetworkHeight } from '@hooks/useSquidNetworkHeightHooks'; -import { useAccount } from '@network/useAccount'; -import { useContracts } from '@network/useContracts.ts'; - -import { TxResult, errorMessage, WriteContractRes } from '../utils'; - -import { WORKER_REGISTRATION_CONTRACT_ABI } from './WorkerRegistration.abi'; - -function useWithdrawWorkerFromWallet() { - const contracts = useContracts(); - const { writeContractAsync } = useWriteContract({}); - - return async ({ peerId }: { peerId: string }): Promise => { - try { - return { - tx: await writeContractAsync({ - address: contracts.WORKER_REGISTRATION, - abi: WORKER_REGISTRATION_CONTRACT_ABI, - functionName: 'withdraw', - args: [peerIdToHex(peerId)], - }), - }; - } catch (e) { - return { error: errorMessage(e) }; - } - }; -} - -function useWithdrawWorkerFromVestingContract() { - const contracts = useContracts(); - const { address: account } = useAccount(); - const { writeContractAsync } = useWriteContract({}); - - return async ({ peerId, source }: UnregisterWorkerRequest): Promise => { - try { - const data = encodeFunctionData({ - abi: WORKER_REGISTRATION_CONTRACT_ABI, - functionName: 'withdraw', - args: [peerIdToHex(peerId)], - }); - - return { - tx: await writeContractAsync({ - account, - address: source.id as `0x${string}`, - abi: VESTING_CONTRACT_ABI, - functionName: 'execute', - args: [contracts.WORKER_REGISTRATION, data], - }), - }; - } catch (e: unknown) { - return { error: errorMessage(e) }; - } - }; -} - -export function useWithdrawWorker() { - const client = useClient(); - const { address } = useAccount(); - const [isLoading, setLoading] = useState(false); - const { setWaitHeight } = useSquidNetworkHeight(); - const [error, setError] = useState(null); - - const withdrawWorkerFromWallet = useWithdrawWorkerFromWallet(); - const withdrawWorkerFromVestingContract = useWithdrawWorkerFromVestingContract(); - - const withdrawWorker = async (req: UnregisterWorkerRequest): Promise => { - setLoading(true); - - const { tx, error } = - req.source.type === AccountType.User - ? await withdrawWorkerFromWallet(req) - : await withdrawWorkerFromVestingContract(req); - - if (!tx) { - logger.debug(`withdraw worker failed ${error}`); - setLoading(false); - setError(error); - return { success: false, failedReason: error }; - } - - const receipt = await waitForTransactionReceipt(client!, { hash: tx }); - setWaitHeight(receipt.blockNumber, ['myWorkers', { address }]); - setLoading(false); - setError(null); - - return { success: true }; - }; - - return { - withdrawWorker, - isLoading, - error, - }; -} diff --git a/src/api/subsquid-network-squid/accounts-graphql.ts b/src/api/subsquid-network-squid/accounts-graphql.ts index f316701..6d85ecb 100644 --- a/src/api/subsquid-network-squid/accounts-graphql.ts +++ b/src/api/subsquid-network-squid/accounts-graphql.ts @@ -1,14 +1,13 @@ import { useMemo } from 'react'; -import BigNumber from 'bignumber.js'; +import { UseQueryOptions } from '@tanstack/react-query'; import { useAccount } from '@network/useAccount'; -import { useSquidDataSource } from './datasource'; +import { useSquid } from './datasource'; import { AccountType, - useAccountQuery, - useMyAssetsQuery, + useSourcesQuery, useVestingByAddressQuery, VestingFragmentFragment, } from './graphql'; @@ -22,108 +21,40 @@ export type SourceWalletWithBalance = SourceWallet & { balance: string; }; -export function useMySources({ enabled }: { enabled?: boolean } = {}) { - const datasource = useSquidDataSource(); +export function useMySources({ + enabled, + select = data => data as TData, +}: { + enabled?: boolean; + select?: UseQueryOptions['select']; +} = {}) { + const squid = useSquid(); const { address } = useAccount(); - const requestEnabled = enabled && !!address; - const { data: data, isPending } = useAccountQuery( - datasource, - { - address: address || '', - }, - { - enabled: requestEnabled, - }, - ); - - const wallet = data?.accountById; - - const res = useMemo((): SourceWalletWithBalance[] => { - return !wallet - ? [ - { - type: AccountType.User, - id: address as string, - balance: '0', - }, - ] - : [wallet, ...wallet.owned].map(a => ({ - type: a.type, - id: a.id, - balance: a.balance, - })); - }, [address, wallet]); - - const vestingContracts = useMemo(() => { - return res.filter(a => a.type === AccountType.Vesting); - }, [res]); - return { - sources: res, - vestingContracts, - isPending, - }; -} - -export function useMyAssets() { - const datasource = useSquidDataSource(); - const { address } = useAccount(); - - const enabled = !!address; - const { data, isLoading } = useMyAssetsQuery( - datasource, - { - address: address || '', - }, + const { data: sourcesQuery, isLoading } = useSourcesQuery( + squid, + { address: address || '0x' }, { enabled }, ); - const assets = useMemo(() => { - const accounts = data?.accounts || []; - const delegations = data?.delegations || []; - const workers = data?.workers || []; - - let balance = BigNumber(0); - let locked = BigNumber(0); - let bonded = BigNumber(0); - let claimable = BigNumber(0); - let delegated = BigNumber(0); - const vestings: SourceWalletWithBalance[] = []; - - for (const a of accounts) { - balance = balance.plus(a.balance); - - for (const o of a.owned) { - locked = locked.plus(o.balance); - vestings.push({ - id: o.id, - type: AccountType.Vesting, - balance: BigNumber(o.balance).toFixed(0), - }); - } - } - for (const w of workers) { - bonded = bonded.plus(w.bond); - claimable = claimable.plus(w.claimableReward); - } - for (const d of delegations) { - claimable = claimable.plus(d.claimableReward); - delegated = delegated.plus(d.deposit); - } - - return { - balance: balance.toFixed(0), - locked: locked.toFixed(0), - bonded: bonded.toFixed(0), - claimable: claimable.toFixed(0), - delegated: delegated.toFixed(0), - vestings, - total: balance.plus(locked).plus(bonded).plus(claimable).plus(delegated).toFixed(0), - }; - }, [data]); + const data: SourceWalletWithBalance[] = useMemo( + () => + !sourcesQuery?.accounts?.length + ? [ + { + id: address || '0x', + type: AccountType.User, + balance: '0', + }, + ] + : sourcesQuery?.accounts, + [address, sourcesQuery?.accounts], + ); + + const tData: TData = useMemo(() => select(data), [data, select]); return { - assets, + data: tData, isLoading, }; } @@ -144,7 +75,7 @@ export class BlockchainApiVesting { } export function useVestingByAddress({ address }: { address?: string }) { - const datasource = useSquidDataSource(); + const datasource = useSquid(); const account = useAccount(); const { data, isPending } = useVestingByAddressQuery( diff --git a/src/api/subsquid-network-squid/datasource.ts b/src/api/subsquid-network-squid/datasource.ts index bb378bd..5832d6b 100644 --- a/src/api/subsquid-network-squid/datasource.ts +++ b/src/api/subsquid-network-squid/datasource.ts @@ -1,6 +1,6 @@ import { NetworkName, getSubsquidNetwork } from '@network/useSubsquidNetwork'; -export function useSquidDataSource() { +export function useSquid() { const network = getSubsquidNetwork(); return { diff --git a/src/api/subsquid-network-squid/fixes.ts b/src/api/subsquid-network-squid/fixes.ts new file mode 100644 index 0000000..3bfd987 --- /dev/null +++ b/src/api/subsquid-network-squid/fixes.ts @@ -0,0 +1,176 @@ +import { useMemo } from 'react'; + +import { getBlockTime } from '@lib/network'; +import { Simplify } from 'type-fest'; +import { useBlock, useReadContracts } from 'wagmi'; + +import { Worker, WorkerStatus } from '@api/subsquid-network-squid'; +import { useContracts } from '@network/useContracts'; + +import { + stakingAbi, + useReadRouterStaking, + useReadRouterWorkerRegistration, + useReadWorkerRegistryLockPeriod, + workerRegistryAbi, +} from '../contracts/subsquid.generated'; + +export function useFixWorkers>({ + workers, +}: { + workers?: T[]; +}) { + const { ROUTER, CHAIN_ID_L1 } = useContracts(); + + const { data: registryAddress, isLoading: isRegistryAddressLoading } = + useReadRouterWorkerRegistration({ + address: ROUTER, + query: { enabled: !!ROUTER }, + }); + + const { data: lockPeriod, isLoading: isLockPeriodLoading } = useReadWorkerRegistryLockPeriod({ + address: registryAddress || '0x', + }); + + const { data: lastL1Block, isLoading: isLastL1BlockLoading } = useBlock({ + chainId: CHAIN_ID_L1, + includeTransactions: false, + }); + + const { data: workersInfo, isLoading: isWorkersInfoLoading } = useReadContracts({ + contracts: workers?.map(worker => { + return { + abi: workerRegistryAbi, + address: registryAddress || '0x', + functionName: 'getWorker', + args: [BigInt(worker.id)], + } as const; + }), + allowFailure: false, + query: { + enabled: !!workers && !!registryAddress, + }, + }); + + const data = useMemo(() => { + if (!workersInfo || !lastL1Block) return workers; + + return workers?.map((worker, i) => { + const workerInfo = workersInfo?.[i]; + + const registerBlock = workerInfo.registeredAt; + const deregisterBlock = workerInfo.deregisteredAt; + const unlockBlock = deregisterBlock + (lockPeriod ?? 0n); + const timestamp = Number(lastL1Block.timestamp) * 1000; + + const { status, statusChangeAt } = + lastL1Block.number < registerBlock + ? { + status: WorkerStatus.Registering, + statusChangeAt: new Date( + timestamp + getBlockTime(registerBlock - lastL1Block.number), + ).toString(), + } + : lastL1Block.number < deregisterBlock + ? { + status: WorkerStatus.Deregistering, + statusChangeAt: new Date( + timestamp + getBlockTime(deregisterBlock - lastL1Block.number), + ).toString(), + } + : { status: worker.status }; + + const { locked, unlockedAt } = + lastL1Block.number < unlockBlock + ? { + locked: true, + unlockedAt: new Date( + timestamp + getBlockTime(unlockBlock - lastL1Block.number), + ).toISOString(), + } + : { locked: false }; + + return { + ...worker, + status, + statusChangeAt, + locked, + unlockedAt, + }; + }); + }, [workers, workersInfo, lastL1Block, lockPeriod]); + + return { + isLoading: + isRegistryAddressLoading || + isLockPeriodLoading || + isLastL1BlockLoading || + isWorkersInfoLoading, + data, + }; +} + +export function useFixDelegations< + T extends { id: string; delegations: { owner: { id: string } }[] }, +>({ workers }: { workers?: T[] }) { + const { ROUTER, CHAIN_ID_L1 } = useContracts(); + + const { data: stakingAddress, isLoading: isStakingAddressLoading } = useReadRouterStaking({ + address: ROUTER, + query: { enabled: !!ROUTER }, + }); + + const { data: lastL1Block, isLoading: isLastL1BlockLoading } = useBlock({ + chainId: CHAIN_ID_L1, + includeTransactions: false, + }); + + const { data: delegationsInfo, isLoading: isDelegationsInfoLoading } = useReadContracts({ + contracts: workers?.flatMap(worker => + worker.delegations.map( + delegation => + ({ + abi: stakingAbi, + address: stakingAddress || '0x', + functionName: 'getDeposit', + args: [delegation.owner.id as `0x${string}`, worker.id], + }) as const, + ), + ), + allowFailure: false, + query: { enabled: !!workers && !!stakingAddress }, + }); + + type R = Simplify< + Omit & { + delegations: Simplify[]; + } + >; + + const data = useMemo(() => { + if (!delegationsInfo || !lastL1Block || !workers) return workers; + + let index = 0; + return workers.map( + worker => + ({ + ...worker, + delegations: worker.delegations.map(delegation => { + const [, unlockBlock] = delegationsInfo[index++]; + const timestamp = Number(lastL1Block.timestamp) * 1000; + const locked = lastL1Block.number < unlockBlock; + const unlockedAt = locked + ? new Date(timestamp + getBlockTime(unlockBlock - lastL1Block.number)).toISOString() + : undefined; + + return { ...delegation, locked, unlockedAt }; + }), + }) as R, + ); + }, [delegationsInfo, lastL1Block, workers]); + + return { + data, + isLoading: isDelegationsInfoLoading || isLastL1BlockLoading || isStakingAddressLoading, + }; +} diff --git a/src/api/subsquid-network-squid/gateways-graphql.ts b/src/api/subsquid-network-squid/gateways-graphql.ts index c548f44..19b1950 100644 --- a/src/api/subsquid-network-squid/gateways-graphql.ts +++ b/src/api/subsquid-network-squid/gateways-graphql.ts @@ -1,32 +1,27 @@ -import { useSquidDataSource } from '@api/subsquid-network-squid/datasource'; +import { useSquid } from '@api/subsquid-network-squid/datasource'; import { useAccount } from '@network/useAccount'; -import { - GatewayFragmentFragment, - useGatewayByPeerIdQuery, - useMyGatewaysQuery, - useMyGatewayStakesQuery, -} from './graphql'; +import { useGatewayByPeerIdQuery, useMyGatewaysQuery, useMyGatewayStakesQuery } from './graphql'; // inherit API interface for internal class -export interface BlockchainGateway extends GatewayFragmentFragment { - owner: Exclude; -} +// export interface BlockchainGateway extends GatewayFragmentFragment { +// owner: Exclude; +// } -export class BlockchainGateway { - ownedByMe?: boolean; +// export class BlockchainGateway { +// ownedByMe?: boolean; - constructor({ gateway, address }: { gateway: GatewayFragmentFragment; address?: `0x${string}` }) { - Object.assign(this, { - ...gateway, - createdAt: new Date(), - ownedByMe: gateway?.owner?.id === address, - }); - } -} +// constructor({ gateway, address }: { gateway: GatewayFragmentFragment; address?: `0x${string}` }) { +// Object.assign(this, { +// ...gateway, +// createdAt: new Date(), +// ownedByMe: gateway?.owner?.id === address, +// }); +// } +// } export function useMyGateways() { - const datasource = useSquidDataSource(); + const datasource = useSquid(); const { address } = useAccount(); const enabled = !!address; @@ -37,13 +32,7 @@ export function useMyGateways() { }, { select: res => { - return res.gateways.map( - gateway => - new BlockchainGateway({ - gateway, - address, - }), - ); + return res.gateways; }, enabled, }, @@ -56,8 +45,7 @@ export function useMyGateways() { } export function useGatewayByPeerId(peerId?: string) { - const datasource = useSquidDataSource(); - const { address } = useAccount(); + const datasource = useSquid(); const enabled = !!peerId; const { data, isLoading } = useGatewayByPeerIdQuery( @@ -69,10 +57,7 @@ export function useGatewayByPeerId(peerId?: string) { select: res => { if (!res.gatewayById) return; - return new BlockchainGateway({ - gateway: res.gatewayById, - address, - }); + return res.gatewayById; }, enabled, }, @@ -84,8 +69,8 @@ export function useGatewayByPeerId(peerId?: string) { }; } -export function useMyGatewayStakes() { - const datasource = useSquidDataSource(); +export function useMyGatewayStake() { + const datasource = useSquid(); const { address } = useAccount(); const enabled = !!address; @@ -97,7 +82,7 @@ export function useMyGatewayStakes() { { select: res => { return { - operators: res.gatewayOperators.filter(o => o.pendingStake || o.stake), + stake: res.gatewayStakes.length ? res.gatewayStakes[0] : undefined, ...res.networkStats, }; }, diff --git a/src/api/subsquid-network-squid/graphql.tsx b/src/api/subsquid-network-squid/graphql.tsx index 3ab0771..56c25f5 100644 --- a/src/api/subsquid-network-squid/graphql.tsx +++ b/src/api/subsquid-network-squid/graphql.tsx @@ -53,7 +53,7 @@ export type Account = { claimableDelegationCount: Scalars['Int']['output']; claims: Array; delegations: Array; - gatewayOperator?: Maybe; + delegations2: Array; gatewayStakes: Array; gateways: Array; id: Scalars['String']['output']; @@ -64,6 +64,7 @@ export type Account = { transfersTo: Array; type: AccountType; workers: Array; + workers2: Array; }; export type AccountClaimsArgs = { @@ -80,6 +81,13 @@ export type AccountDelegationsArgs = { where?: InputMaybe; }; +export type AccountDelegations2Args = { + limit?: InputMaybe; + offset?: InputMaybe; + orderBy?: InputMaybe>; + where?: InputMaybe; +}; + export type AccountGatewayStakesArgs = { limit?: InputMaybe; offset?: InputMaybe; @@ -129,6 +137,13 @@ export type AccountWorkersArgs = { where?: InputMaybe; }; +export type AccountWorkers2Args = { + limit?: InputMaybe; + offset?: InputMaybe; + orderBy?: InputMaybe>; + where?: InputMaybe; +}; + export type AccountEdge = { __typename?: 'AccountEdge'; cursor: Scalars['String']['output']; @@ -148,18 +163,6 @@ export enum AccountOrderByInput { ClaimableDelegationCountDesc = 'claimableDelegationCount_DESC', ClaimableDelegationCountDescNullsFirst = 'claimableDelegationCount_DESC_NULLS_FIRST', ClaimableDelegationCountDescNullsLast = 'claimableDelegationCount_DESC_NULLS_LAST', - GatewayOperatorAutoExtensionAsc = 'gatewayOperator_autoExtension_ASC', - GatewayOperatorAutoExtensionAscNullsFirst = 'gatewayOperator_autoExtension_ASC_NULLS_FIRST', - GatewayOperatorAutoExtensionAscNullsLast = 'gatewayOperator_autoExtension_ASC_NULLS_LAST', - GatewayOperatorAutoExtensionDesc = 'gatewayOperator_autoExtension_DESC', - GatewayOperatorAutoExtensionDescNullsFirst = 'gatewayOperator_autoExtension_DESC_NULLS_FIRST', - GatewayOperatorAutoExtensionDescNullsLast = 'gatewayOperator_autoExtension_DESC_NULLS_LAST', - GatewayOperatorIdAsc = 'gatewayOperator_id_ASC', - GatewayOperatorIdAscNullsFirst = 'gatewayOperator_id_ASC_NULLS_FIRST', - GatewayOperatorIdAscNullsLast = 'gatewayOperator_id_ASC_NULLS_LAST', - GatewayOperatorIdDesc = 'gatewayOperator_id_DESC', - GatewayOperatorIdDescNullsFirst = 'gatewayOperator_id_DESC_NULLS_FIRST', - GatewayOperatorIdDescNullsLast = 'gatewayOperator_id_DESC_NULLS_LAST', IdAsc = 'id_ASC', IdAscNullsFirst = 'id_ASC_NULLS_FIRST', IdAscNullsLast = 'id_ASC_NULLS_LAST', @@ -342,11 +345,12 @@ export type AccountWhereInput = { claims_every?: InputMaybe; claims_none?: InputMaybe; claims_some?: InputMaybe; + delegations2_every?: InputMaybe; + delegations2_none?: InputMaybe; + delegations2_some?: InputMaybe; delegations_every?: InputMaybe; delegations_none?: InputMaybe; delegations_some?: InputMaybe; - gatewayOperator?: InputMaybe; - gatewayOperator_isNull?: InputMaybe; gatewayStakes_every?: InputMaybe; gatewayStakes_none?: InputMaybe; gatewayStakes_some?: InputMaybe; @@ -389,6 +393,9 @@ export type AccountWhereInput = { type_isNull?: InputMaybe; type_not_eq?: InputMaybe; type_not_in?: InputMaybe>; + workers2_every?: InputMaybe; + workers2_none?: InputMaybe; + workers2_some?: InputMaybe; workers_every?: InputMaybe; workers_none?: InputMaybe; workers_some?: InputMaybe; @@ -1808,8 +1815,9 @@ export type Gateway = { endpointUrl?: Maybe; id: Scalars['String']['output']; name?: Maybe; - operator?: Maybe; - owner?: Maybe; + owner: Account; + realOwner: Account; + stake: GatewayStake; status: GatewayStatus; statusHistory: Array; website?: Maybe; @@ -1828,193 +1836,6 @@ export type GatewayEdge = { node: Gateway; }; -export type GatewayOperator = { - __typename?: 'GatewayOperator'; - account: Account; - autoExtension: Scalars['Boolean']['output']; - gateways: Array; - id: Scalars['String']['output']; - pendingStake?: Maybe; - stake?: Maybe; -}; - -export type GatewayOperatorGatewaysArgs = { - limit?: InputMaybe; - offset?: InputMaybe; - orderBy?: InputMaybe>; - where?: InputMaybe; -}; - -export type GatewayOperatorEdge = { - __typename?: 'GatewayOperatorEdge'; - cursor: Scalars['String']['output']; - node: GatewayOperator; -}; - -export enum GatewayOperatorOrderByInput { - AccountBalanceAsc = 'account_balance_ASC', - AccountBalanceAscNullsFirst = 'account_balance_ASC_NULLS_FIRST', - AccountBalanceAscNullsLast = 'account_balance_ASC_NULLS_LAST', - AccountBalanceDesc = 'account_balance_DESC', - AccountBalanceDescNullsFirst = 'account_balance_DESC_NULLS_FIRST', - AccountBalanceDescNullsLast = 'account_balance_DESC_NULLS_LAST', - AccountClaimableDelegationCountAsc = 'account_claimableDelegationCount_ASC', - AccountClaimableDelegationCountAscNullsFirst = 'account_claimableDelegationCount_ASC_NULLS_FIRST', - AccountClaimableDelegationCountAscNullsLast = 'account_claimableDelegationCount_ASC_NULLS_LAST', - AccountClaimableDelegationCountDesc = 'account_claimableDelegationCount_DESC', - AccountClaimableDelegationCountDescNullsFirst = 'account_claimableDelegationCount_DESC_NULLS_FIRST', - AccountClaimableDelegationCountDescNullsLast = 'account_claimableDelegationCount_DESC_NULLS_LAST', - AccountIdAsc = 'account_id_ASC', - AccountIdAscNullsFirst = 'account_id_ASC_NULLS_FIRST', - AccountIdAscNullsLast = 'account_id_ASC_NULLS_LAST', - AccountIdDesc = 'account_id_DESC', - AccountIdDescNullsFirst = 'account_id_DESC_NULLS_FIRST', - AccountIdDescNullsLast = 'account_id_DESC_NULLS_LAST', - AccountTypeAsc = 'account_type_ASC', - AccountTypeAscNullsFirst = 'account_type_ASC_NULLS_FIRST', - AccountTypeAscNullsLast = 'account_type_ASC_NULLS_LAST', - AccountTypeDesc = 'account_type_DESC', - AccountTypeDescNullsFirst = 'account_type_DESC_NULLS_FIRST', - AccountTypeDescNullsLast = 'account_type_DESC_NULLS_LAST', - AutoExtensionAsc = 'autoExtension_ASC', - AutoExtensionAscNullsFirst = 'autoExtension_ASC_NULLS_FIRST', - AutoExtensionAscNullsLast = 'autoExtension_ASC_NULLS_LAST', - AutoExtensionDesc = 'autoExtension_DESC', - AutoExtensionDescNullsFirst = 'autoExtension_DESC_NULLS_FIRST', - AutoExtensionDescNullsLast = 'autoExtension_DESC_NULLS_LAST', - IdAsc = 'id_ASC', - IdAscNullsFirst = 'id_ASC_NULLS_FIRST', - IdAscNullsLast = 'id_ASC_NULLS_LAST', - IdDesc = 'id_DESC', - IdDescNullsFirst = 'id_DESC_NULLS_FIRST', - IdDescNullsLast = 'id_DESC_NULLS_LAST', - PendingStakeAmountAsc = 'pendingStake_amount_ASC', - PendingStakeAmountAscNullsFirst = 'pendingStake_amount_ASC_NULLS_FIRST', - PendingStakeAmountAscNullsLast = 'pendingStake_amount_ASC_NULLS_LAST', - PendingStakeAmountDesc = 'pendingStake_amount_DESC', - PendingStakeAmountDescNullsFirst = 'pendingStake_amount_DESC_NULLS_FIRST', - PendingStakeAmountDescNullsLast = 'pendingStake_amount_DESC_NULLS_LAST', - PendingStakeComputationUnitsAsc = 'pendingStake_computationUnits_ASC', - PendingStakeComputationUnitsAscNullsFirst = 'pendingStake_computationUnits_ASC_NULLS_FIRST', - PendingStakeComputationUnitsAscNullsLast = 'pendingStake_computationUnits_ASC_NULLS_LAST', - PendingStakeComputationUnitsDesc = 'pendingStake_computationUnits_DESC', - PendingStakeComputationUnitsDescNullsFirst = 'pendingStake_computationUnits_DESC_NULLS_FIRST', - PendingStakeComputationUnitsDescNullsLast = 'pendingStake_computationUnits_DESC_NULLS_LAST', - PendingStakeIdAsc = 'pendingStake_id_ASC', - PendingStakeIdAscNullsFirst = 'pendingStake_id_ASC_NULLS_FIRST', - PendingStakeIdAscNullsLast = 'pendingStake_id_ASC_NULLS_LAST', - PendingStakeIdDesc = 'pendingStake_id_DESC', - PendingStakeIdDescNullsFirst = 'pendingStake_id_DESC_NULLS_FIRST', - PendingStakeIdDescNullsLast = 'pendingStake_id_DESC_NULLS_LAST', - PendingStakeIndexAsc = 'pendingStake_index_ASC', - PendingStakeIndexAscNullsFirst = 'pendingStake_index_ASC_NULLS_FIRST', - PendingStakeIndexAscNullsLast = 'pendingStake_index_ASC_NULLS_LAST', - PendingStakeIndexDesc = 'pendingStake_index_DESC', - PendingStakeIndexDescNullsFirst = 'pendingStake_index_DESC_NULLS_FIRST', - PendingStakeIndexDescNullsLast = 'pendingStake_index_DESC_NULLS_LAST', - PendingStakeLockEndAsc = 'pendingStake_lockEnd_ASC', - PendingStakeLockEndAscNullsFirst = 'pendingStake_lockEnd_ASC_NULLS_FIRST', - PendingStakeLockEndAscNullsLast = 'pendingStake_lockEnd_ASC_NULLS_LAST', - PendingStakeLockEndDesc = 'pendingStake_lockEnd_DESC', - PendingStakeLockEndDescNullsFirst = 'pendingStake_lockEnd_DESC_NULLS_FIRST', - PendingStakeLockEndDescNullsLast = 'pendingStake_lockEnd_DESC_NULLS_LAST', - PendingStakeLockStartAsc = 'pendingStake_lockStart_ASC', - PendingStakeLockStartAscNullsFirst = 'pendingStake_lockStart_ASC_NULLS_FIRST', - PendingStakeLockStartAscNullsLast = 'pendingStake_lockStart_ASC_NULLS_LAST', - PendingStakeLockStartDesc = 'pendingStake_lockStart_DESC', - PendingStakeLockStartDescNullsFirst = 'pendingStake_lockStart_DESC_NULLS_FIRST', - PendingStakeLockStartDescNullsLast = 'pendingStake_lockStart_DESC_NULLS_LAST', - PendingStakeLockedAsc = 'pendingStake_locked_ASC', - PendingStakeLockedAscNullsFirst = 'pendingStake_locked_ASC_NULLS_FIRST', - PendingStakeLockedAscNullsLast = 'pendingStake_locked_ASC_NULLS_LAST', - PendingStakeLockedDesc = 'pendingStake_locked_DESC', - PendingStakeLockedDescNullsFirst = 'pendingStake_locked_DESC_NULLS_FIRST', - PendingStakeLockedDescNullsLast = 'pendingStake_locked_DESC_NULLS_LAST', - StakeAmountAsc = 'stake_amount_ASC', - StakeAmountAscNullsFirst = 'stake_amount_ASC_NULLS_FIRST', - StakeAmountAscNullsLast = 'stake_amount_ASC_NULLS_LAST', - StakeAmountDesc = 'stake_amount_DESC', - StakeAmountDescNullsFirst = 'stake_amount_DESC_NULLS_FIRST', - StakeAmountDescNullsLast = 'stake_amount_DESC_NULLS_LAST', - StakeComputationUnitsAsc = 'stake_computationUnits_ASC', - StakeComputationUnitsAscNullsFirst = 'stake_computationUnits_ASC_NULLS_FIRST', - StakeComputationUnitsAscNullsLast = 'stake_computationUnits_ASC_NULLS_LAST', - StakeComputationUnitsDesc = 'stake_computationUnits_DESC', - StakeComputationUnitsDescNullsFirst = 'stake_computationUnits_DESC_NULLS_FIRST', - StakeComputationUnitsDescNullsLast = 'stake_computationUnits_DESC_NULLS_LAST', - StakeIdAsc = 'stake_id_ASC', - StakeIdAscNullsFirst = 'stake_id_ASC_NULLS_FIRST', - StakeIdAscNullsLast = 'stake_id_ASC_NULLS_LAST', - StakeIdDesc = 'stake_id_DESC', - StakeIdDescNullsFirst = 'stake_id_DESC_NULLS_FIRST', - StakeIdDescNullsLast = 'stake_id_DESC_NULLS_LAST', - StakeIndexAsc = 'stake_index_ASC', - StakeIndexAscNullsFirst = 'stake_index_ASC_NULLS_FIRST', - StakeIndexAscNullsLast = 'stake_index_ASC_NULLS_LAST', - StakeIndexDesc = 'stake_index_DESC', - StakeIndexDescNullsFirst = 'stake_index_DESC_NULLS_FIRST', - StakeIndexDescNullsLast = 'stake_index_DESC_NULLS_LAST', - StakeLockEndAsc = 'stake_lockEnd_ASC', - StakeLockEndAscNullsFirst = 'stake_lockEnd_ASC_NULLS_FIRST', - StakeLockEndAscNullsLast = 'stake_lockEnd_ASC_NULLS_LAST', - StakeLockEndDesc = 'stake_lockEnd_DESC', - StakeLockEndDescNullsFirst = 'stake_lockEnd_DESC_NULLS_FIRST', - StakeLockEndDescNullsLast = 'stake_lockEnd_DESC_NULLS_LAST', - StakeLockStartAsc = 'stake_lockStart_ASC', - StakeLockStartAscNullsFirst = 'stake_lockStart_ASC_NULLS_FIRST', - StakeLockStartAscNullsLast = 'stake_lockStart_ASC_NULLS_LAST', - StakeLockStartDesc = 'stake_lockStart_DESC', - StakeLockStartDescNullsFirst = 'stake_lockStart_DESC_NULLS_FIRST', - StakeLockStartDescNullsLast = 'stake_lockStart_DESC_NULLS_LAST', - StakeLockedAsc = 'stake_locked_ASC', - StakeLockedAscNullsFirst = 'stake_locked_ASC_NULLS_FIRST', - StakeLockedAscNullsLast = 'stake_locked_ASC_NULLS_LAST', - StakeLockedDesc = 'stake_locked_DESC', - StakeLockedDescNullsFirst = 'stake_locked_DESC_NULLS_FIRST', - StakeLockedDescNullsLast = 'stake_locked_DESC_NULLS_LAST', -} - -export type GatewayOperatorWhereInput = { - AND?: InputMaybe>; - OR?: InputMaybe>; - account?: InputMaybe; - account_isNull?: InputMaybe; - autoExtension_eq?: InputMaybe; - autoExtension_isNull?: InputMaybe; - autoExtension_not_eq?: InputMaybe; - gateways_every?: InputMaybe; - gateways_none?: InputMaybe; - gateways_some?: InputMaybe; - id_contains?: InputMaybe; - id_containsInsensitive?: InputMaybe; - id_endsWith?: InputMaybe; - id_eq?: InputMaybe; - id_gt?: InputMaybe; - id_gte?: InputMaybe; - id_in?: InputMaybe>; - id_isNull?: InputMaybe; - id_lt?: InputMaybe; - id_lte?: InputMaybe; - id_not_contains?: InputMaybe; - id_not_containsInsensitive?: InputMaybe; - id_not_endsWith?: InputMaybe; - id_not_eq?: InputMaybe; - id_not_in?: InputMaybe>; - id_not_startsWith?: InputMaybe; - id_startsWith?: InputMaybe; - pendingStake?: InputMaybe; - pendingStake_isNull?: InputMaybe; - stake?: InputMaybe; - stake_isNull?: InputMaybe; -}; - -export type GatewayOperatorsConnection = { - __typename?: 'GatewayOperatorsConnection'; - edges: Array; - pageInfo: PageInfo; - totalCount: Scalars['Int']['output']; -}; - export enum GatewayOrderByInput { CreatedAtAsc = 'createdAt_ASC', CreatedAtAscNullsFirst = 'createdAt_ASC_NULLS_FIRST', @@ -2052,18 +1873,6 @@ export enum GatewayOrderByInput { NameDesc = 'name_DESC', NameDescNullsFirst = 'name_DESC_NULLS_FIRST', NameDescNullsLast = 'name_DESC_NULLS_LAST', - OperatorAutoExtensionAsc = 'operator_autoExtension_ASC', - OperatorAutoExtensionAscNullsFirst = 'operator_autoExtension_ASC_NULLS_FIRST', - OperatorAutoExtensionAscNullsLast = 'operator_autoExtension_ASC_NULLS_LAST', - OperatorAutoExtensionDesc = 'operator_autoExtension_DESC', - OperatorAutoExtensionDescNullsFirst = 'operator_autoExtension_DESC_NULLS_FIRST', - OperatorAutoExtensionDescNullsLast = 'operator_autoExtension_DESC_NULLS_LAST', - OperatorIdAsc = 'operator_id_ASC', - OperatorIdAscNullsFirst = 'operator_id_ASC_NULLS_FIRST', - OperatorIdAscNullsLast = 'operator_id_ASC_NULLS_LAST', - OperatorIdDesc = 'operator_id_DESC', - OperatorIdDescNullsFirst = 'operator_id_DESC_NULLS_FIRST', - OperatorIdDescNullsLast = 'operator_id_DESC_NULLS_LAST', OwnerBalanceAsc = 'owner_balance_ASC', OwnerBalanceAscNullsFirst = 'owner_balance_ASC_NULLS_FIRST', OwnerBalanceAscNullsLast = 'owner_balance_ASC_NULLS_LAST', @@ -2088,6 +1897,78 @@ export enum GatewayOrderByInput { OwnerTypeDesc = 'owner_type_DESC', OwnerTypeDescNullsFirst = 'owner_type_DESC_NULLS_FIRST', OwnerTypeDescNullsLast = 'owner_type_DESC_NULLS_LAST', + RealOwnerBalanceAsc = 'realOwner_balance_ASC', + RealOwnerBalanceAscNullsFirst = 'realOwner_balance_ASC_NULLS_FIRST', + RealOwnerBalanceAscNullsLast = 'realOwner_balance_ASC_NULLS_LAST', + RealOwnerBalanceDesc = 'realOwner_balance_DESC', + RealOwnerBalanceDescNullsFirst = 'realOwner_balance_DESC_NULLS_FIRST', + RealOwnerBalanceDescNullsLast = 'realOwner_balance_DESC_NULLS_LAST', + RealOwnerClaimableDelegationCountAsc = 'realOwner_claimableDelegationCount_ASC', + RealOwnerClaimableDelegationCountAscNullsFirst = 'realOwner_claimableDelegationCount_ASC_NULLS_FIRST', + RealOwnerClaimableDelegationCountAscNullsLast = 'realOwner_claimableDelegationCount_ASC_NULLS_LAST', + RealOwnerClaimableDelegationCountDesc = 'realOwner_claimableDelegationCount_DESC', + RealOwnerClaimableDelegationCountDescNullsFirst = 'realOwner_claimableDelegationCount_DESC_NULLS_FIRST', + RealOwnerClaimableDelegationCountDescNullsLast = 'realOwner_claimableDelegationCount_DESC_NULLS_LAST', + RealOwnerIdAsc = 'realOwner_id_ASC', + RealOwnerIdAscNullsFirst = 'realOwner_id_ASC_NULLS_FIRST', + RealOwnerIdAscNullsLast = 'realOwner_id_ASC_NULLS_LAST', + RealOwnerIdDesc = 'realOwner_id_DESC', + RealOwnerIdDescNullsFirst = 'realOwner_id_DESC_NULLS_FIRST', + RealOwnerIdDescNullsLast = 'realOwner_id_DESC_NULLS_LAST', + RealOwnerTypeAsc = 'realOwner_type_ASC', + RealOwnerTypeAscNullsFirst = 'realOwner_type_ASC_NULLS_FIRST', + RealOwnerTypeAscNullsLast = 'realOwner_type_ASC_NULLS_LAST', + RealOwnerTypeDesc = 'realOwner_type_DESC', + RealOwnerTypeDescNullsFirst = 'realOwner_type_DESC_NULLS_FIRST', + RealOwnerTypeDescNullsLast = 'realOwner_type_DESC_NULLS_LAST', + StakeAmountAsc = 'stake_amount_ASC', + StakeAmountAscNullsFirst = 'stake_amount_ASC_NULLS_FIRST', + StakeAmountAscNullsLast = 'stake_amount_ASC_NULLS_LAST', + StakeAmountDesc = 'stake_amount_DESC', + StakeAmountDescNullsFirst = 'stake_amount_DESC_NULLS_FIRST', + StakeAmountDescNullsLast = 'stake_amount_DESC_NULLS_LAST', + StakeAutoExtensionAsc = 'stake_autoExtension_ASC', + StakeAutoExtensionAscNullsFirst = 'stake_autoExtension_ASC_NULLS_FIRST', + StakeAutoExtensionAscNullsLast = 'stake_autoExtension_ASC_NULLS_LAST', + StakeAutoExtensionDesc = 'stake_autoExtension_DESC', + StakeAutoExtensionDescNullsFirst = 'stake_autoExtension_DESC_NULLS_FIRST', + StakeAutoExtensionDescNullsLast = 'stake_autoExtension_DESC_NULLS_LAST', + StakeComputationUnitsPendingAsc = 'stake_computationUnitsPending_ASC', + StakeComputationUnitsPendingAscNullsFirst = 'stake_computationUnitsPending_ASC_NULLS_FIRST', + StakeComputationUnitsPendingAscNullsLast = 'stake_computationUnitsPending_ASC_NULLS_LAST', + StakeComputationUnitsPendingDesc = 'stake_computationUnitsPending_DESC', + StakeComputationUnitsPendingDescNullsFirst = 'stake_computationUnitsPending_DESC_NULLS_FIRST', + StakeComputationUnitsPendingDescNullsLast = 'stake_computationUnitsPending_DESC_NULLS_LAST', + StakeComputationUnitsAsc = 'stake_computationUnits_ASC', + StakeComputationUnitsAscNullsFirst = 'stake_computationUnits_ASC_NULLS_FIRST', + StakeComputationUnitsAscNullsLast = 'stake_computationUnits_ASC_NULLS_LAST', + StakeComputationUnitsDesc = 'stake_computationUnits_DESC', + StakeComputationUnitsDescNullsFirst = 'stake_computationUnits_DESC_NULLS_FIRST', + StakeComputationUnitsDescNullsLast = 'stake_computationUnits_DESC_NULLS_LAST', + StakeIdAsc = 'stake_id_ASC', + StakeIdAscNullsFirst = 'stake_id_ASC_NULLS_FIRST', + StakeIdAscNullsLast = 'stake_id_ASC_NULLS_LAST', + StakeIdDesc = 'stake_id_DESC', + StakeIdDescNullsFirst = 'stake_id_DESC_NULLS_FIRST', + StakeIdDescNullsLast = 'stake_id_DESC_NULLS_LAST', + StakeLockEndAsc = 'stake_lockEnd_ASC', + StakeLockEndAscNullsFirst = 'stake_lockEnd_ASC_NULLS_FIRST', + StakeLockEndAscNullsLast = 'stake_lockEnd_ASC_NULLS_LAST', + StakeLockEndDesc = 'stake_lockEnd_DESC', + StakeLockEndDescNullsFirst = 'stake_lockEnd_DESC_NULLS_FIRST', + StakeLockEndDescNullsLast = 'stake_lockEnd_DESC_NULLS_LAST', + StakeLockStartAsc = 'stake_lockStart_ASC', + StakeLockStartAscNullsFirst = 'stake_lockStart_ASC_NULLS_FIRST', + StakeLockStartAscNullsLast = 'stake_lockStart_ASC_NULLS_LAST', + StakeLockStartDesc = 'stake_lockStart_DESC', + StakeLockStartDescNullsFirst = 'stake_lockStart_DESC_NULLS_FIRST', + StakeLockStartDescNullsLast = 'stake_lockStart_DESC_NULLS_LAST', + StakeLockedAsc = 'stake_locked_ASC', + StakeLockedAscNullsFirst = 'stake_locked_ASC_NULLS_FIRST', + StakeLockedAscNullsLast = 'stake_locked_ASC_NULLS_LAST', + StakeLockedDesc = 'stake_locked_DESC', + StakeLockedDescNullsFirst = 'stake_locked_DESC_NULLS_FIRST', + StakeLockedDescNullsLast = 'stake_locked_DESC_NULLS_LAST', StatusAsc = 'status_ASC', StatusAscNullsFirst = 'status_ASC_NULLS_FIRST', StatusAscNullsLast = 'status_ASC_NULLS_LAST', @@ -2105,14 +1986,23 @@ export enum GatewayOrderByInput { export type GatewayStake = { __typename?: 'GatewayStake'; amount: Scalars['BigInt']['output']; + autoExtension: Scalars['Boolean']['output']; computationUnits: Scalars['BigInt']['output']; + computationUnitsPending?: Maybe; + gateways: Array; id: Scalars['String']['output']; - index: Scalars['Int']['output']; - lockEnd: Scalars['Int']['output']; - lockStart: Scalars['Int']['output']; + lockEnd?: Maybe; + lockStart?: Maybe; locked: Scalars['Boolean']['output']; - operator: GatewayOperator; owner: Account; + realOwner: Account; +}; + +export type GatewayStakeGatewaysArgs = { + limit?: InputMaybe; + offset?: InputMaybe; + orderBy?: InputMaybe>; + where?: InputMaybe; }; export type GatewayStakeEdge = { @@ -2128,6 +2018,18 @@ export enum GatewayStakeOrderByInput { AmountDesc = 'amount_DESC', AmountDescNullsFirst = 'amount_DESC_NULLS_FIRST', AmountDescNullsLast = 'amount_DESC_NULLS_LAST', + AutoExtensionAsc = 'autoExtension_ASC', + AutoExtensionAscNullsFirst = 'autoExtension_ASC_NULLS_FIRST', + AutoExtensionAscNullsLast = 'autoExtension_ASC_NULLS_LAST', + AutoExtensionDesc = 'autoExtension_DESC', + AutoExtensionDescNullsFirst = 'autoExtension_DESC_NULLS_FIRST', + AutoExtensionDescNullsLast = 'autoExtension_DESC_NULLS_LAST', + ComputationUnitsPendingAsc = 'computationUnitsPending_ASC', + ComputationUnitsPendingAscNullsFirst = 'computationUnitsPending_ASC_NULLS_FIRST', + ComputationUnitsPendingAscNullsLast = 'computationUnitsPending_ASC_NULLS_LAST', + ComputationUnitsPendingDesc = 'computationUnitsPending_DESC', + ComputationUnitsPendingDescNullsFirst = 'computationUnitsPending_DESC_NULLS_FIRST', + ComputationUnitsPendingDescNullsLast = 'computationUnitsPending_DESC_NULLS_LAST', ComputationUnitsAsc = 'computationUnits_ASC', ComputationUnitsAscNullsFirst = 'computationUnits_ASC_NULLS_FIRST', ComputationUnitsAscNullsLast = 'computationUnits_ASC_NULLS_LAST', @@ -2140,12 +2042,6 @@ export enum GatewayStakeOrderByInput { IdDesc = 'id_DESC', IdDescNullsFirst = 'id_DESC_NULLS_FIRST', IdDescNullsLast = 'id_DESC_NULLS_LAST', - IndexAsc = 'index_ASC', - IndexAscNullsFirst = 'index_ASC_NULLS_FIRST', - IndexAscNullsLast = 'index_ASC_NULLS_LAST', - IndexDesc = 'index_DESC', - IndexDescNullsFirst = 'index_DESC_NULLS_FIRST', - IndexDescNullsLast = 'index_DESC_NULLS_LAST', LockEndAsc = 'lockEnd_ASC', LockEndAscNullsFirst = 'lockEnd_ASC_NULLS_FIRST', LockEndAscNullsLast = 'lockEnd_ASC_NULLS_LAST', @@ -2164,18 +2060,6 @@ export enum GatewayStakeOrderByInput { LockedDesc = 'locked_DESC', LockedDescNullsFirst = 'locked_DESC_NULLS_FIRST', LockedDescNullsLast = 'locked_DESC_NULLS_LAST', - OperatorAutoExtensionAsc = 'operator_autoExtension_ASC', - OperatorAutoExtensionAscNullsFirst = 'operator_autoExtension_ASC_NULLS_FIRST', - OperatorAutoExtensionAscNullsLast = 'operator_autoExtension_ASC_NULLS_LAST', - OperatorAutoExtensionDesc = 'operator_autoExtension_DESC', - OperatorAutoExtensionDescNullsFirst = 'operator_autoExtension_DESC_NULLS_FIRST', - OperatorAutoExtensionDescNullsLast = 'operator_autoExtension_DESC_NULLS_LAST', - OperatorIdAsc = 'operator_id_ASC', - OperatorIdAscNullsFirst = 'operator_id_ASC_NULLS_FIRST', - OperatorIdAscNullsLast = 'operator_id_ASC_NULLS_LAST', - OperatorIdDesc = 'operator_id_DESC', - OperatorIdDescNullsFirst = 'operator_id_DESC_NULLS_FIRST', - OperatorIdDescNullsLast = 'operator_id_DESC_NULLS_LAST', OwnerBalanceAsc = 'owner_balance_ASC', OwnerBalanceAscNullsFirst = 'owner_balance_ASC_NULLS_FIRST', OwnerBalanceAscNullsLast = 'owner_balance_ASC_NULLS_LAST', @@ -2200,6 +2084,30 @@ export enum GatewayStakeOrderByInput { OwnerTypeDesc = 'owner_type_DESC', OwnerTypeDescNullsFirst = 'owner_type_DESC_NULLS_FIRST', OwnerTypeDescNullsLast = 'owner_type_DESC_NULLS_LAST', + RealOwnerBalanceAsc = 'realOwner_balance_ASC', + RealOwnerBalanceAscNullsFirst = 'realOwner_balance_ASC_NULLS_FIRST', + RealOwnerBalanceAscNullsLast = 'realOwner_balance_ASC_NULLS_LAST', + RealOwnerBalanceDesc = 'realOwner_balance_DESC', + RealOwnerBalanceDescNullsFirst = 'realOwner_balance_DESC_NULLS_FIRST', + RealOwnerBalanceDescNullsLast = 'realOwner_balance_DESC_NULLS_LAST', + RealOwnerClaimableDelegationCountAsc = 'realOwner_claimableDelegationCount_ASC', + RealOwnerClaimableDelegationCountAscNullsFirst = 'realOwner_claimableDelegationCount_ASC_NULLS_FIRST', + RealOwnerClaimableDelegationCountAscNullsLast = 'realOwner_claimableDelegationCount_ASC_NULLS_LAST', + RealOwnerClaimableDelegationCountDesc = 'realOwner_claimableDelegationCount_DESC', + RealOwnerClaimableDelegationCountDescNullsFirst = 'realOwner_claimableDelegationCount_DESC_NULLS_FIRST', + RealOwnerClaimableDelegationCountDescNullsLast = 'realOwner_claimableDelegationCount_DESC_NULLS_LAST', + RealOwnerIdAsc = 'realOwner_id_ASC', + RealOwnerIdAscNullsFirst = 'realOwner_id_ASC_NULLS_FIRST', + RealOwnerIdAscNullsLast = 'realOwner_id_ASC_NULLS_LAST', + RealOwnerIdDesc = 'realOwner_id_DESC', + RealOwnerIdDescNullsFirst = 'realOwner_id_DESC_NULLS_FIRST', + RealOwnerIdDescNullsLast = 'realOwner_id_DESC_NULLS_LAST', + RealOwnerTypeAsc = 'realOwner_type_ASC', + RealOwnerTypeAscNullsFirst = 'realOwner_type_ASC_NULLS_FIRST', + RealOwnerTypeAscNullsLast = 'realOwner_type_ASC_NULLS_LAST', + RealOwnerTypeDesc = 'realOwner_type_DESC', + RealOwnerTypeDescNullsFirst = 'realOwner_type_DESC_NULLS_FIRST', + RealOwnerTypeDescNullsLast = 'realOwner_type_DESC_NULLS_LAST', } export type GatewayStakeWhereInput = { @@ -2214,6 +2122,18 @@ export type GatewayStakeWhereInput = { amount_lte?: InputMaybe; amount_not_eq?: InputMaybe; amount_not_in?: InputMaybe>; + autoExtension_eq?: InputMaybe; + autoExtension_isNull?: InputMaybe; + autoExtension_not_eq?: InputMaybe; + computationUnitsPending_eq?: InputMaybe; + computationUnitsPending_gt?: InputMaybe; + computationUnitsPending_gte?: InputMaybe; + computationUnitsPending_in?: InputMaybe>; + computationUnitsPending_isNull?: InputMaybe; + computationUnitsPending_lt?: InputMaybe; + computationUnitsPending_lte?: InputMaybe; + computationUnitsPending_not_eq?: InputMaybe; + computationUnitsPending_not_in?: InputMaybe>; computationUnits_eq?: InputMaybe; computationUnits_gt?: InputMaybe; computationUnits_gte?: InputMaybe; @@ -2223,6 +2143,9 @@ export type GatewayStakeWhereInput = { computationUnits_lte?: InputMaybe; computationUnits_not_eq?: InputMaybe; computationUnits_not_in?: InputMaybe>; + gateways_every?: InputMaybe; + gateways_none?: InputMaybe; + gateways_some?: InputMaybe; id_contains?: InputMaybe; id_containsInsensitive?: InputMaybe; id_endsWith?: InputMaybe; @@ -2240,15 +2163,6 @@ export type GatewayStakeWhereInput = { id_not_in?: InputMaybe>; id_not_startsWith?: InputMaybe; id_startsWith?: InputMaybe; - index_eq?: InputMaybe; - index_gt?: InputMaybe; - index_gte?: InputMaybe; - index_in?: InputMaybe>; - index_isNull?: InputMaybe; - index_lt?: InputMaybe; - index_lte?: InputMaybe; - index_not_eq?: InputMaybe; - index_not_in?: InputMaybe>; lockEnd_eq?: InputMaybe; lockEnd_gt?: InputMaybe; lockEnd_gte?: InputMaybe; @@ -2270,10 +2184,10 @@ export type GatewayStakeWhereInput = { locked_eq?: InputMaybe; locked_isNull?: InputMaybe; locked_not_eq?: InputMaybe; - operator?: InputMaybe; - operator_isNull?: InputMaybe; owner?: InputMaybe; owner_isNull?: InputMaybe; + realOwner?: InputMaybe; + realOwner_isNull?: InputMaybe; }; export type GatewayStakesConnection = { @@ -2530,10 +2444,12 @@ export type GatewayWhereInput = { name_not_in?: InputMaybe>; name_not_startsWith?: InputMaybe; name_startsWith?: InputMaybe; - operator?: InputMaybe; - operator_isNull?: InputMaybe; owner?: InputMaybe; owner_isNull?: InputMaybe; + realOwner?: InputMaybe; + realOwner_isNull?: InputMaybe; + stake?: InputMaybe; + stake_isNull?: InputMaybe; statusHistory_every?: InputMaybe; statusHistory_none?: InputMaybe; statusHistory_some?: InputMaybe; @@ -2601,61 +2517,34 @@ export type PageInfo = { export type Query = { __typename?: 'Query'; accountById?: Maybe; - /** @deprecated Use accountById */ - accountByUniqueInput?: Maybe; accountTransferById?: Maybe; - /** @deprecated Use accountTransferById */ - accountTransferByUniqueInput?: Maybe; accountTransfers: Array; accountTransfersConnection: AccountTransfersConnection; accounts: Array; accountsConnection: AccountsConnection; blockById?: Maybe; - /** @deprecated Use blockById */ - blockByUniqueInput?: Maybe; blocks: Array; blocksConnection: BlocksConnection; claimById?: Maybe; - /** @deprecated Use claimById */ - claimByUniqueInput?: Maybe; claims: Array; claimsConnection: ClaimsConnection; commitmentById?: Maybe; - /** @deprecated Use commitmentById */ - commitmentByUniqueInput?: Maybe; commitments: Array; commitmentsConnection: CommitmentsConnection; delegationById?: Maybe; - /** @deprecated Use delegationById */ - delegationByUniqueInput?: Maybe; delegationRewardById?: Maybe; - /** @deprecated Use delegationRewardById */ - delegationRewardByUniqueInput?: Maybe; delegationRewards: Array; delegationRewardsConnection: DelegationRewardsConnection; delegations: Array; delegationsConnection: DelegationsConnection; epochById?: Maybe; - /** @deprecated Use epochById */ - epochByUniqueInput?: Maybe; epoches: Array; epochesConnection: EpochesConnection; gatewayById?: Maybe; - /** @deprecated Use gatewayById */ - gatewayByUniqueInput?: Maybe; - gatewayOperatorById?: Maybe; - /** @deprecated Use gatewayOperatorById */ - gatewayOperatorByUniqueInput?: Maybe; - gatewayOperators: Array; - gatewayOperatorsConnection: GatewayOperatorsConnection; gatewayStakeById?: Maybe; - /** @deprecated Use gatewayStakeById */ - gatewayStakeByUniqueInput?: Maybe; gatewayStakes: Array; gatewayStakesConnection: GatewayStakesConnection; gatewayStatusChangeById?: Maybe; - /** @deprecated Use gatewayStatusChangeById */ - gatewayStatusChangeByUniqueInput?: Maybe; gatewayStatusChanges: Array; gatewayStatusChangesConnection: GatewayStatusChangesConnection; gateways: Array; @@ -2663,37 +2552,23 @@ export type Query = { networkStats: NetworkStats; settings: Array; settingsById?: Maybe; - /** @deprecated Use settingsById */ - settingsByUniqueInput?: Maybe; settingsConnection: SettingsConnection; squidStatus: SquidStatus; statistics: Array; statisticsById?: Maybe; - /** @deprecated Use statisticsById */ - statisticsByUniqueInput?: Maybe; statisticsConnection: StatisticsConnection; transferById?: Maybe; - /** @deprecated Use transferById */ - transferByUniqueInput?: Maybe; transfers: Array; transfersConnection: TransfersConnection; workerById?: Maybe; - /** @deprecated Use workerById */ - workerByUniqueInput?: Maybe; workerRewardById?: Maybe; - /** @deprecated Use workerRewardById */ - workerRewardByUniqueInput?: Maybe; workerRewards: Array; workerRewardsConnection: WorkerRewardsConnection; workerSnapshotById?: Maybe; - /** @deprecated Use workerSnapshotById */ - workerSnapshotByUniqueInput?: Maybe; workerSnapshots: Array; workerSnapshotsByDay: Array; workerSnapshotsConnection: WorkerSnapshotsConnection; workerStatusChangeById?: Maybe; - /** @deprecated Use workerStatusChangeById */ - workerStatusChangeByUniqueInput?: Maybe; workerStatusChanges: Array; workerStatusChangesConnection: WorkerStatusChangesConnection; workers: Array; @@ -2704,18 +2579,10 @@ export type QueryAccountByIdArgs = { id: Scalars['String']['input']; }; -export type QueryAccountByUniqueInputArgs = { - where: WhereIdInput; -}; - export type QueryAccountTransferByIdArgs = { id: Scalars['String']['input']; }; -export type QueryAccountTransferByUniqueInputArgs = { - where: WhereIdInput; -}; - export type QueryAccountTransfersArgs = { limit?: InputMaybe; offset?: InputMaybe; @@ -2748,10 +2615,6 @@ export type QueryBlockByIdArgs = { id: Scalars['String']['input']; }; -export type QueryBlockByUniqueInputArgs = { - where: WhereIdInput; -}; - export type QueryBlocksArgs = { limit?: InputMaybe; offset?: InputMaybe; @@ -2770,10 +2633,6 @@ export type QueryClaimByIdArgs = { id: Scalars['String']['input']; }; -export type QueryClaimByUniqueInputArgs = { - where: WhereIdInput; -}; - export type QueryClaimsArgs = { limit?: InputMaybe; offset?: InputMaybe; @@ -2792,10 +2651,6 @@ export type QueryCommitmentByIdArgs = { id: Scalars['String']['input']; }; -export type QueryCommitmentByUniqueInputArgs = { - where: WhereIdInput; -}; - export type QueryCommitmentsArgs = { limit?: InputMaybe; offset?: InputMaybe; @@ -2814,18 +2669,10 @@ export type QueryDelegationByIdArgs = { id: Scalars['String']['input']; }; -export type QueryDelegationByUniqueInputArgs = { - where: WhereIdInput; -}; - export type QueryDelegationRewardByIdArgs = { id: Scalars['String']['input']; }; -export type QueryDelegationRewardByUniqueInputArgs = { - where: WhereIdInput; -}; - export type QueryDelegationRewardsArgs = { limit?: InputMaybe; offset?: InputMaybe; @@ -2858,10 +2705,6 @@ export type QueryEpochByIdArgs = { id: Scalars['String']['input']; }; -export type QueryEpochByUniqueInputArgs = { - where: WhereIdInput; -}; - export type QueryEpochesArgs = { limit?: InputMaybe; offset?: InputMaybe; @@ -2880,40 +2723,10 @@ export type QueryGatewayByIdArgs = { id: Scalars['String']['input']; }; -export type QueryGatewayByUniqueInputArgs = { - where: WhereIdInput; -}; - -export type QueryGatewayOperatorByIdArgs = { - id: Scalars['String']['input']; -}; - -export type QueryGatewayOperatorByUniqueInputArgs = { - where: WhereIdInput; -}; - -export type QueryGatewayOperatorsArgs = { - limit?: InputMaybe; - offset?: InputMaybe; - orderBy?: InputMaybe>; - where?: InputMaybe; -}; - -export type QueryGatewayOperatorsConnectionArgs = { - after?: InputMaybe; - first?: InputMaybe; - orderBy: Array; - where?: InputMaybe; -}; - export type QueryGatewayStakeByIdArgs = { id: Scalars['String']['input']; }; -export type QueryGatewayStakeByUniqueInputArgs = { - where: WhereIdInput; -}; - export type QueryGatewayStakesArgs = { limit?: InputMaybe; offset?: InputMaybe; @@ -2932,10 +2745,6 @@ export type QueryGatewayStatusChangeByIdArgs = { id: Scalars['String']['input']; }; -export type QueryGatewayStatusChangeByUniqueInputArgs = { - where: WhereIdInput; -}; - export type QueryGatewayStatusChangesArgs = { limit?: InputMaybe; offset?: InputMaybe; @@ -2975,10 +2784,6 @@ export type QuerySettingsByIdArgs = { id: Scalars['String']['input']; }; -export type QuerySettingsByUniqueInputArgs = { - where: WhereIdInput; -}; - export type QuerySettingsConnectionArgs = { after?: InputMaybe; first?: InputMaybe; @@ -2997,10 +2802,6 @@ export type QueryStatisticsByIdArgs = { id: Scalars['String']['input']; }; -export type QueryStatisticsByUniqueInputArgs = { - where: WhereIdInput; -}; - export type QueryStatisticsConnectionArgs = { after?: InputMaybe; first?: InputMaybe; @@ -3012,10 +2813,6 @@ export type QueryTransferByIdArgs = { id: Scalars['String']['input']; }; -export type QueryTransferByUniqueInputArgs = { - where: WhereIdInput; -}; - export type QueryTransfersArgs = { limit?: InputMaybe; offset?: InputMaybe; @@ -3034,18 +2831,10 @@ export type QueryWorkerByIdArgs = { id: Scalars['String']['input']; }; -export type QueryWorkerByUniqueInputArgs = { - where: WhereIdInput; -}; - export type QueryWorkerRewardByIdArgs = { id: Scalars['String']['input']; }; -export type QueryWorkerRewardByUniqueInputArgs = { - where: WhereIdInput; -}; - export type QueryWorkerRewardsArgs = { limit?: InputMaybe; offset?: InputMaybe; @@ -3064,10 +2853,6 @@ export type QueryWorkerSnapshotByIdArgs = { id: Scalars['String']['input']; }; -export type QueryWorkerSnapshotByUniqueInputArgs = { - where: WhereIdInput; -}; - export type QueryWorkerSnapshotsArgs = { limit?: InputMaybe; offset?: InputMaybe; @@ -3092,10 +2877,6 @@ export type QueryWorkerStatusChangeByIdArgs = { id: Scalars['String']['input']; }; -export type QueryWorkerStatusChangeByUniqueInputArgs = { - where: WhereIdInput; -}; - export type QueryWorkerStatusChangesArgs = { limit?: InputMaybe; offset?: InputMaybe; @@ -3640,10 +3421,6 @@ export type TransfersConnection = { totalCount: Scalars['Int']['output']; }; -export type WhereIdInput = { - id: Scalars['String']['input']; -}; - export type Worker = { __typename?: 'Worker'; apr?: Maybe; @@ -5474,19 +5251,69 @@ export type SettingsQuery = { }; }; -export type AccountQueryVariables = Exact<{ +export type AccountFragmentFragment = { + __typename?: 'Account'; + id: string; + type: AccountType; + balance: string; +}; + +export type SourcesQueryVariables = Exact<{ address: Scalars['String']['input']; }>; -export type AccountQuery = { +export type SourcesQuery = { __typename?: 'Query'; - accountById?: { + accounts: Array<{ __typename?: 'Account'; id: string; type: AccountType; balance: string }>; +}; + +export type SourcesWithAssetsQueryVariables = Exact<{ + address: Scalars['String']['input']; +}>; + +export type SourcesWithAssetsQuery = { + __typename?: 'Query'; + accounts: Array<{ __typename?: 'Account'; id: string; type: AccountType; balance: string; - owned: Array<{ __typename?: 'Account'; id: string; type: AccountType; balance: string }>; - }; + workers2: Array<{ + __typename?: 'Worker'; + bond: string; + claimableReward: string; + id: string; + name?: string; + peerId: string; + }>; + delegations2: Array<{ + __typename?: 'Delegation'; + deposit: string; + claimableReward: string; + worker: { __typename?: 'Worker'; id: string; name?: string; peerId: string }; + }>; + }>; +}; + +export type SourcesWithDelegationsQueryVariables = Exact<{ + address: Scalars['String']['input']; + peerId?: InputMaybe; +}>; + +export type SourcesWithDelegationsQuery = { + __typename?: 'Query'; + accounts: Array<{ + __typename?: 'Account'; + id: string; + type: AccountType; + balance: string; + delegations2: Array<{ + __typename?: 'Delegation'; + deposit: string; + claimableReward: string; + worker: { __typename?: 'Worker'; id: string; name?: string; peerId: string }; + }>; + }>; }; export type WorkerBaseFragmentFragment = { @@ -5522,6 +5349,8 @@ export type WorkerFragmentFragment = { jailed?: boolean; dialOk?: boolean; jailReason?: string; + owner: { __typename?: 'Account'; id: string; type: AccountType }; + realOwner: { __typename?: 'Account'; id: string }; }; export type WorkerFullFragmentFragment = { @@ -5584,6 +5413,8 @@ export type AllWorkersQuery = { jailed?: boolean; dialOk?: boolean; jailReason?: string; + owner: { __typename?: 'Account'; id: string; type: AccountType }; + realOwner: { __typename?: 'Account'; id: string }; }>; }; @@ -5682,6 +5513,8 @@ export type MyWorkersQuery = { jailed?: boolean; dialOk?: boolean; jailReason?: string; + owner: { __typename?: 'Account'; id: string; type: AccountType }; + realOwner: { __typename?: 'Account'; id: string }; }>; }; @@ -5725,15 +5558,30 @@ export type WorkerOwnerQuery = { }; }; -export type MyWorkerDelegationsQueryVariables = Exact<{ +export type MyDelegationsQueryVariables = Exact<{ address: Scalars['String']['input']; - workerId: Scalars['String']['input']; + workerId?: InputMaybe; }>; -export type MyWorkerDelegationsQuery = { +export type MyDelegationsQuery = { __typename?: 'Query'; - workerById?: { + workers: Array<{ __typename?: 'Worker'; + version?: string; + createdAt: string; + uptime90Days?: number; + apr?: number; + stakerApr?: number; + totalDelegation: string; + capedDelegation: string; + id: string; + name?: string; + peerId: string; + status: WorkerStatus; + online?: boolean; + jailed?: boolean; + dialOk?: boolean; + jailReason?: string; delegations: Array<{ __typename?: 'Delegation'; claimableReward: string; @@ -5742,7 +5590,9 @@ export type MyWorkerDelegationsQuery = { locked?: boolean; owner: { __typename?: 'Account'; id: string; type: AccountType }; }>; - }; + owner: { __typename?: 'Account'; id: string; type: AccountType }; + realOwner: { __typename?: 'Account'; id: string }; + }>; }; export type MyAssetsQueryVariables = Exact<{ @@ -5767,40 +5617,6 @@ export type MyAssetsQuery = { delegations: Array<{ __typename?: 'Delegation'; claimableReward: string; deposit: string }>; }; -export type MyDelegationsQueryVariables = Exact<{ - address: Scalars['String']['input']; -}>; - -export type MyDelegationsQuery = { - __typename?: 'Query'; - delegations: Array<{ - __typename?: 'Delegation'; - claimableReward: string; - claimedReward: string; - deposit: string; - locked?: boolean; - owner: { __typename?: 'Account'; id: string; type: AccountType }; - worker: { - __typename?: 'Worker'; - totalDelegation: string; - capedDelegation: string; - version?: string; - createdAt: string; - uptime90Days?: number; - apr?: number; - stakerApr?: number; - id: string; - name?: string; - peerId: string; - status: WorkerStatus; - online?: boolean; - jailed?: boolean; - dialOk?: boolean; - jailReason?: string; - }; - }>; -}; - export type MyClaimsQueryVariables = Exact<{ address: Scalars['String']['input']; }>; @@ -5834,20 +5650,16 @@ export type GatewayFragmentFragment = { endpointUrl?: string; website?: string; createdAt: string; - owner?: { __typename?: 'Account'; id: string; type: AccountType }; - operator?: { - __typename?: 'GatewayOperator'; - stake?: { __typename?: 'GatewayStake'; locked: boolean }; - }; + owner: { __typename?: 'Account'; id: string; type: AccountType }; }; -export type MyGatewaysQueryVariables = Exact<{ - address: Scalars['String']['input']; +export type GatewayByPeerIdQueryVariables = Exact<{ + peerId: Scalars['String']['input']; }>; -export type MyGatewaysQuery = { +export type GatewayByPeerIdQuery = { __typename?: 'Query'; - gateways: Array<{ + gatewayById?: { __typename?: 'Gateway'; id: string; name?: string; @@ -5857,21 +5669,29 @@ export type MyGatewaysQuery = { endpointUrl?: string; website?: string; createdAt: string; - owner?: { __typename?: 'Account'; id: string; type: AccountType }; - operator?: { - __typename?: 'GatewayOperator'; - stake?: { __typename?: 'GatewayStake'; locked: boolean }; - }; - }>; + owner: { __typename?: 'Account'; id: string; type: AccountType }; + }; }; -export type GatewayByPeerIdQueryVariables = Exact<{ - peerId: Scalars['String']['input']; +export type GatewayStakeFragmentFragment = { + __typename?: 'GatewayStake'; + autoExtension: boolean; + amount: string; + computationUnits: string; + computationUnitsPending?: string; + locked: boolean; + lockStart?: number; + lockEnd?: number; + owner: { __typename?: 'Account'; id: string; type: AccountType }; +}; + +export type MyGatewaysQueryVariables = Exact<{ + address: Scalars['String']['input']; }>; -export type GatewayByPeerIdQuery = { +export type MyGatewaysQuery = { __typename?: 'Query'; - gatewayById?: { + gateways: Array<{ __typename?: 'Gateway'; id: string; name?: string; @@ -5881,32 +5701,8 @@ export type GatewayByPeerIdQuery = { endpointUrl?: string; website?: string; createdAt: string; - owner?: { __typename?: 'Account'; id: string; type: AccountType }; - operator?: { - __typename?: 'GatewayOperator'; - stake?: { __typename?: 'GatewayStake'; locked: boolean }; - }; - }; -}; - -export type GatewayStakeFragmentFragment = { - __typename?: 'GatewayOperator'; - autoExtension: boolean; - account: { __typename?: 'Account'; id: string; type: AccountType }; - stake?: { - __typename?: 'GatewayStake'; - amount: string; - locked: boolean; - lockStart: number; - lockEnd: number; - }; - pendingStake?: { - __typename?: 'GatewayStake'; - amount: string; - locked: boolean; - lockStart: number; - lockEnd: number; - }; + owner: { __typename?: 'Account'; id: string; type: AccountType }; + }>; }; export type MyGatewayStakesQueryVariables = Exact<{ @@ -5915,24 +5711,16 @@ export type MyGatewayStakesQueryVariables = Exact<{ export type MyGatewayStakesQuery = { __typename?: 'Query'; - gatewayOperators: Array<{ - __typename?: 'GatewayOperator'; + gatewayStakes: Array<{ + __typename?: 'GatewayStake'; autoExtension: boolean; - account: { __typename?: 'Account'; id: string; type: AccountType }; - stake?: { - __typename?: 'GatewayStake'; - amount: string; - locked: boolean; - lockStart: number; - lockEnd: number; - }; - pendingStake?: { - __typename?: 'GatewayStake'; - amount: string; - locked: boolean; - lockStart: number; - lockEnd: number; - }; + amount: string; + computationUnits: string; + computationUnitsPending?: string; + locked: boolean; + lockStart?: number; + lockEnd?: number; + owner: { __typename?: 'Account'; id: string; type: AccountType }; }>; networkStats: { __typename?: 'NetworkStats'; @@ -6004,6 +5792,13 @@ export type CurrentEpochQuery = { epoches: Array<{ __typename?: 'Epoch'; number: number; start: number; end: number }>; }; +export const AccountFragmentFragmentDoc = ` + fragment AccountFragment on Account { + id + type + balance +} + `; export const WorkerBaseFragmentFragmentDoc = ` fragment WorkerBaseFragment on Worker { id @@ -6031,6 +5826,13 @@ export const WorkerFragmentFragmentDoc = ` stakerApr totalDelegation capedDelegation + owner { + id + type + } + realOwner { + id + } } ${WorkerBaseFragmentFragmentDoc} ${WorkerStatusFragmentFragmentDoc}`; @@ -6058,13 +5860,6 @@ export const WorkerFullFragmentFragmentDoc = ` timestamp uptime } - owner { - id - type - } - realOwner { - id - } } ${WorkerFragmentFragmentDoc}`; export const GatewayFragmentFragmentDoc = ` @@ -6076,37 +5871,26 @@ export const GatewayFragmentFragmentDoc = ` email endpointUrl website + createdAt owner { id type } - operator { - stake { - locked - } - } - createdAt } `; export const GatewayStakeFragmentFragmentDoc = ` - fragment GatewayStakeFragment on GatewayOperator { - account { + fragment GatewayStakeFragment on GatewayStake { + owner { id type } autoExtension - stake { - amount - locked - lockStart - lockEnd - } - pendingStake { - amount - locked - lockStart - lockEnd - } + amount + computationUnits + computationUnitsPending + locked + lockStart + lockEnd } `; export const VestingFragmentFragmentDoc = ` @@ -6181,34 +5965,116 @@ export const useSettingsQuery = ( }); }; -export const AccountDocument = ` - query account($address: String!) { - accountById(id: $address) { - id - type - balance - owned { - id - type - balance +export const SourcesDocument = ` + query sources($address: String!) { + accounts( + where: {OR: [{id_eq: $address}, {owner: {id_eq: $address}}]} + orderBy: [type_ASC, id_ASC] + ) { + ...AccountFragment + } +} + ${AccountFragmentFragmentDoc}`; + +export const useSourcesQuery = ( + dataSource: { endpoint: string; fetchParams?: RequestInit }, + variables: SourcesQueryVariables, + options?: Omit, 'queryKey'> & { + queryKey?: UseQueryOptions['queryKey']; + }, +) => { + return useQuery({ + queryKey: ['sources', variables], + queryFn: fetcher( + dataSource.endpoint, + dataSource.fetchParams || {}, + SourcesDocument, + variables, + ), + ...options, + }); +}; + +export const SourcesWithAssetsDocument = ` + query sourcesWithAssets($address: String!) { + accounts( + where: {OR: [{id_eq: $address}, {owner: {id_eq: $address}}]} + orderBy: [type_ASC, id_ASC] + ) { + ...AccountFragment + workers2(where: {OR: [{bond_gt: 0}, {claimableReward_gt: 0}]}) { + ...WorkerBaseFragment + bond + claimableReward + } + delegations2(where: {OR: [{deposit_gt: 0}, {claimableReward_gt: 0}]}) { + deposit + claimableReward + worker { + ...WorkerBaseFragment + } } } } - `; + ${AccountFragmentFragmentDoc} +${WorkerBaseFragmentFragmentDoc}`; + +export const useSourcesWithAssetsQuery = ( + dataSource: { endpoint: string; fetchParams?: RequestInit }, + variables: SourcesWithAssetsQueryVariables, + options?: Omit, 'queryKey'> & { + queryKey?: UseQueryOptions['queryKey']; + }, +) => { + return useQuery({ + queryKey: ['sourcesWithAssets', variables], + queryFn: fetcher( + dataSource.endpoint, + dataSource.fetchParams || {}, + SourcesWithAssetsDocument, + variables, + ), + ...options, + }); +}; + +export const SourcesWithDelegationsDocument = ` + query sourcesWithDelegations($address: String!, $peerId: String) { + accounts( + where: {OR: [{id_eq: $address}, {owner: {id_eq: $address}}]} + orderBy: [type_ASC, id_ASC] + ) { + ...AccountFragment + delegations2( + where: {OR: [{deposit_gt: 0}], AND: [{worker: {peerId_eq: $peerId}}]} + ) { + deposit + claimableReward + worker { + ...WorkerBaseFragment + } + } + } +} + ${AccountFragmentFragmentDoc} +${WorkerBaseFragmentFragmentDoc}`; -export const useAccountQuery = ( +export const useSourcesWithDelegationsQuery = < + TData = SourcesWithDelegationsQuery, + TError = unknown, +>( dataSource: { endpoint: string; fetchParams?: RequestInit }, - variables: AccountQueryVariables, - options?: Omit, 'queryKey'> & { - queryKey?: UseQueryOptions['queryKey']; + variables: SourcesWithDelegationsQueryVariables, + options?: Omit, 'queryKey'> & { + queryKey?: UseQueryOptions['queryKey']; }, ) => { - return useQuery({ - queryKey: ['account', variables], - queryFn: fetcher( + return useQuery({ + queryKey: ['sourcesWithDelegations', variables], + queryFn: fetcher( dataSource.endpoint, dataSource.fetchParams || {}, - AccountDocument, + SourcesWithDelegationsDocument, variables, ), ...options, @@ -6436,9 +6302,13 @@ export const useWorkerOwnerQuery = ( }); }; -export const MyWorkerDelegationsDocument = ` - query myWorkerDelegations($address: String!, $workerId: String!) { - workerById(id: $workerId) { +export const MyDelegationsDocument = ` + query myDelegations($address: String!, $workerId: String) { + workers( + orderBy: id_ASC + where: {peerId_eq: $workerId, delegations_some: {realOwner: {id_eq: $address}, deposit_gt: 0}} + ) { + ...WorkerFragment delegations(where: {realOwner: {id_eq: $address}, deposit_gt: 0}) { claimableReward claimedReward @@ -6451,21 +6321,21 @@ export const MyWorkerDelegationsDocument = ` } } } - `; + ${WorkerFragmentFragmentDoc}`; -export const useMyWorkerDelegationsQuery = ( +export const useMyDelegationsQuery = ( dataSource: { endpoint: string; fetchParams?: RequestInit }, - variables: MyWorkerDelegationsQueryVariables, - options?: Omit, 'queryKey'> & { - queryKey?: UseQueryOptions['queryKey']; + variables: MyDelegationsQueryVariables, + options?: Omit, 'queryKey'> & { + queryKey?: UseQueryOptions['queryKey']; }, ) => { - return useQuery({ - queryKey: ['myWorkerDelegations', variables], - queryFn: fetcher( + return useQuery({ + queryKey: ['myDelegations', variables], + queryFn: fetcher( dataSource.endpoint, dataSource.fetchParams || {}, - MyWorkerDelegationsDocument, + MyDelegationsDocument, variables, ), ...options, @@ -6514,45 +6384,6 @@ export const useMyAssetsQuery = ( }); }; -export const MyDelegationsDocument = ` - query myDelegations($address: String!) { - delegations(where: {realOwner: {id_eq: $address}, deposit_gt: 0}) { - claimableReward - claimedReward - deposit - locked - owner { - id - type - } - worker { - ...WorkerFragment - totalDelegation - capedDelegation - } - } -} - ${WorkerFragmentFragmentDoc}`; - -export const useMyDelegationsQuery = ( - dataSource: { endpoint: string; fetchParams?: RequestInit }, - variables: MyDelegationsQueryVariables, - options?: Omit, 'queryKey'> & { - queryKey?: UseQueryOptions['queryKey']; - }, -) => { - return useQuery({ - queryKey: ['myDelegations', variables], - queryFn: fetcher( - dataSource.endpoint, - dataSource.fetchParams || {}, - MyDelegationsDocument, - variables, - ), - ...options, - }); -}; - export const MyClaimsDocument = ` query myClaims($address: String!) { delegations(where: {realOwner: {id_eq: $address}, claimableReward_gt: 0}) { @@ -6596,57 +6427,54 @@ export const useMyClaimsQuery = ( }); }; -export const MyGatewaysDocument = ` - query myGateways($address: String!) { - gateways( - orderBy: id_ASC - where: {owner: {id_eq: $address}, status_eq: REGISTERED} - ) { +export const GatewayByPeerIdDocument = ` + query gatewayByPeerId($peerId: String!) { + gatewayById(id: $peerId) { ...GatewayFragment } } ${GatewayFragmentFragmentDoc}`; -export const useMyGatewaysQuery = ( +export const useGatewayByPeerIdQuery = ( dataSource: { endpoint: string; fetchParams?: RequestInit }, - variables: MyGatewaysQueryVariables, - options?: Omit, 'queryKey'> & { - queryKey?: UseQueryOptions['queryKey']; + variables: GatewayByPeerIdQueryVariables, + options?: Omit, 'queryKey'> & { + queryKey?: UseQueryOptions['queryKey']; }, ) => { - return useQuery({ - queryKey: ['myGateways', variables], - queryFn: fetcher( + return useQuery({ + queryKey: ['gatewayByPeerId', variables], + queryFn: fetcher( dataSource.endpoint, dataSource.fetchParams || {}, - MyGatewaysDocument, + GatewayByPeerIdDocument, variables, ), ...options, }); }; -export const GatewayByPeerIdDocument = ` - query gatewayByPeerId($peerId: String!) { - gatewayById(id: $peerId) { +export const MyGatewaysDocument = ` + query myGateways($address: String!) { + gateways(where: {owner: {id_eq: $address}, status_not_eq: DEREGISTERED}) { ...GatewayFragment } } ${GatewayFragmentFragmentDoc}`; -export const useGatewayByPeerIdQuery = ( +export const useMyGatewaysQuery = ( dataSource: { endpoint: string; fetchParams?: RequestInit }, - variables: GatewayByPeerIdQueryVariables, - options?: Omit, 'queryKey'> & { - queryKey?: UseQueryOptions['queryKey']; + variables: MyGatewaysQueryVariables, + options?: Omit, 'queryKey'> & { + queryKey?: UseQueryOptions['queryKey']; }, ) => { - return useQuery({ - queryKey: ['gatewayByPeerId', variables], - queryFn: fetcher( + return useQuery({ + queryKey: ['myGateways', variables], + queryFn: fetcher( dataSource.endpoint, dataSource.fetchParams || {}, - GatewayByPeerIdDocument, + MyGatewaysDocument, variables, ), ...options, @@ -6655,9 +6483,7 @@ export const useGatewayByPeerIdQuery = { return { ...res.networkStats, - epoch: res.epoches[0], + epoch: res.epoches.length ? res.epoches[0] : undefined, }; }, refetchInterval: 6000, // a half of block time in l1 diff --git a/src/api/subsquid-network-squid/workers-graphql.ts b/src/api/subsquid-network-squid/workers-graphql.ts index 3e303df..683f395 100644 --- a/src/api/subsquid-network-squid/workers-graphql.ts +++ b/src/api/subsquid-network-squid/workers-graphql.ts @@ -2,20 +2,18 @@ import { useMemo } from 'react'; import { calculateDelegationCapacity } from '@lib/network'; import BigNumber from 'bignumber.js'; -import { groupBy, mapValues, values } from 'lodash-es'; import { compare as compareSemver } from 'semver'; import { PartialDeep, SimplifyDeep } from 'type-fest'; import { useAccount } from '@network/useAccount.ts'; -import { useSquidDataSource } from './datasource'; +import { useSquid } from './datasource'; +import { useFixDelegations, useFixWorkers } from './fixes'; import { - ClaimType, - MyDelegationsQuery, + AccountType, + Delegation, useAllWorkersQuery, - useMyClaimsQuery, useMyDelegationsQuery, - useMyWorkerDelegationsQuery, useMyWorkersCountQuery, useMyWorkersQuery, useWorkerByPeerIdQuery, @@ -198,7 +196,7 @@ export function useWorkers({ sortBy: WorkerSortBy; sortDir: SortDir; }) { - const dataSource = useSquidDataSource(); + const dataSource = useSquid(); const { isPending: isSettingsLoading } = useNetworkSettings(); const { data, isPending } = useAllWorkersQuery(dataSource, {}); @@ -244,12 +242,12 @@ export function useWorkers({ } export function useMyWorkers({ sortBy, sortDir }: { sortBy: WorkerSortBy; sortDir: SortDir }) { - const datasource = useSquidDataSource(); + const datasource = useSquid(); const { address } = useAccount(); const { isPending: isSettingsLoading } = useNetworkSettings(); const enabled = !!address; - const { data, isLoading } = useMyWorkersQuery( + const { data: workers, isLoading } = useMyWorkersQuery( datasource, { address: address || '', @@ -267,23 +265,25 @@ export function useMyWorkers({ sortBy, sortDir }: { sortBy: WorkerSortBy; sortDi }, ); - const workers = useMemo(() => { - return sortWorkers(data || [], sortBy, sortDir); - }, [data, sortBy, sortDir]); + const { data: fixedWorkers, isLoading: isFixedWorkersLoading } = useFixWorkers({ workers }); + + const data = useMemo(() => { + return sortWorkers(fixedWorkers || [], sortBy, sortDir); + }, [fixedWorkers, sortBy, sortDir]); return { - data: workers, - isLoading: enabled ? isSettingsLoading || isLoading : false, + data, + isLoading: isSettingsLoading || isLoading || isFixedWorkersLoading, }; } export function useWorkerByPeerId(peerId?: string) { - const datasource = useSquidDataSource(); + const datasource = useSquid(); const enabled = !!peerId; const { isPending: isSettingsLoading } = useNetworkSettings(); const { address } = useAccount(); - const { data, isPending } = useWorkerByPeerIdQuery( + const { data: worker, isLoading } = useWorkerByPeerIdQuery( datasource, { peerId: peerId || '', address }, { @@ -303,147 +303,165 @@ export function useWorkerByPeerId(peerId?: string) { }, ); - return { - data, - isPending: enabled ? isSettingsLoading || isPending : false, - }; -} - -export function useMyClaimsAvailable({ source }: { source?: string } = {}) { - const { address } = useAccount(); - const datasource = useSquidDataSource(); - - const { data, isLoading } = useMyClaimsQuery(datasource, { - address: address || '', + const { data: fixedWorker, isLoading: isFixedWorkerLoading } = useFixWorkers({ + workers: worker ? [worker] : undefined, }); - const { sources, claims, hasClaimsAvailable, currentSourceTotalClaimsAvailable } = useMemo(() => { - const allWorkers = [ - ...(data?.workers || []).map(w => ({ - ...w, - type: ClaimType.Worker, - })), - ...(data?.delegations || []).map(d => { - return { - ...d.worker, - type: ClaimType.Delegation, - owner: d.owner, - claimableReward: d.claimableReward, - }; - }), - ]; - - const filteredWorkers = source ? allWorkers.filter(w => w.owner.id === source) : allWorkers; - - return { - hasClaimsAvailable: allWorkers.some(w => BigInt(w.claimableReward) > 0), - currentSourceTotalClaimsAvailable: filteredWorkers.reduce( - (t, i) => t.plus(i.claimableReward), - BigNumber(0), - ), - sources: values( - mapValues(groupBy(allWorkers, 'owner.id'), g => { - const total = g.reduce((t, i) => t.plus(i.claimableReward), BigNumber(0)); - - return { - ...g[0].owner, - balance: total.toFixed(0), - }; - }), - ), - - claims: values( - mapValues(groupBy(filteredWorkers, 'id'), g => { - const total = g.reduce((t, i) => t.plus(i.claimableReward), BigNumber(0)); - - return { - ...g[0], - claimableReward: total.toFixed(0), - }; - }), - ), - }; - }, [data?.delegations, data?.workers, source]); - return { - isLoading, - hasClaimsAvailable, - currentSourceTotalClaimsAvailable, - sources, - claims, + data: fixedWorker?.[0], + isLoading: isSettingsLoading || isLoading || isFixedWorkerLoading, }; } +// export function useMyClaimsAvailable() { +// const { address } = useAccount(); +// const datasource = useSquidDataSource(); + +// const { data, isLoading } = useMyClaimsQuery(datasource, { +// address: address || '', +// }); + +// const { sources, claims } = useMemo(() => { +// const allWorkers = [ +// ...(data?.workers || []).map(w => ({ +// ...w, +// type: ClaimType.Worker, +// })), +// ...(data?.delegations || []).map(d => { +// return { +// ...d.worker, +// type: ClaimType.Delegation, +// owner: d.owner, +// claimableReward: d.claimableReward, +// }; +// }), +// ]; + +// const filteredWorkers = source ? allWorkers.filter(w => w.owner.id === source) : allWorkers; + +// return { +// sources: values( +// mapValues(groupBy(allWorkers, 'owner.id'), g => { +// const total = g.reduce((t, i) => t.plus(i.claimableReward), BigNumber(0)); + +// return { +// ...g[0].owner, +// balance: total.toFixed(0), +// }; +// }), +// ), + +// claims: values( +// mapValues(groupBy(filteredWorkers, 'id'), g => { +// const total = g.reduce((t, i) => t.plus(i.claimableReward), BigNumber(0)); + +// return { +// ...g[0], +// claimableReward: total.toFixed(0), +// }; +// }), +// ), +// }; +// }, [data?.delegations, data?.workers, source]); + +// return { +// isLoading, +// sources, +// claims, +// }; +// } + export function useMyDelegations({ sortBy, sortDir }: { sortBy: WorkerSortBy; sortDir: SortDir }) { const { address } = useAccount(); const { isPending: isSettingsLoading } = useNetworkSettings(); - const datasource = useSquidDataSource(); + const datasource = useSquid(); - const { data, isLoading } = useMyDelegationsQuery( + const { data: delegationsQuery, isLoading: isDelegationsQueryLoading } = useMyDelegationsQuery( datasource, - { - address: address || '', - }, - { - select: res => { - type W = SimplifyDeep< - MyDelegationsQuery['delegations'][number]['worker'] & - Pick< - WorkerExtended, - 'delegationCapacity' | 'myDelegation' | 'myTotalDelegationReward' - > & { - delegations: Omit[]; - } - >; - - const workers: Map = new Map(); - res?.delegations.map(d => { - let worker = workers.get(d.worker.id); - if (!worker) { - worker = { - ...d.worker, - delegations: [], - delegationCapacity: calculateDelegationCapacity({ - totalDelegation: d.worker.totalDelegation, - capedDelegation: d.worker.capedDelegation, - }), - myDelegation: '0', - myTotalDelegationReward: '0', - }; - workers.set(worker.id, worker); - } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const delegation = { - ...d, - worker: undefined, - }; - delete delegation['worker']; - - worker.myDelegation = BigNumber(worker.myDelegation).plus(delegation.deposit).toFixed(); - worker.myTotalDelegationReward = BigNumber(worker.myTotalDelegationReward) - .plus(delegation.claimableReward) - .plus(delegation.claimedReward) - .toFixed(); - worker.delegations.push(delegation); - }); - return [...workers.values()]; - }, - }, + { address: address || '0x' }, ); - const workers = useMemo(() => { - return sortWorkers(data || [], sortBy, sortDir); - }, [data, sortBy, sortDir]); + const { data: fixedWorkers, isLoading: isFixedWorkersLoading } = useFixWorkers({ + workers: delegationsQuery?.workers, + }); + + const { data: fixedDelegations, isLoading: isFixedDelegationsLoading } = useFixDelegations({ + workers: fixedWorkers, + }); + + const data = useMemo(() => { + type W = SimplifyDeep< + Pick< + Worker, + | 'id' + | 'peerId' + | 'name' + | 'capedDelegation' + | 'totalDelegation' + | 'online' + | 'jailed' + | 'status' + | 'stakerApr' + > & + Pick & { + delegations: (Pick & { + owner: { id: string; type: AccountType }; + unlockedAt?: string; + })[]; + } + >; + + const workers = fixedDelegations?.map(w => { + const worker: W = { + id: w.id, + name: w.name, + peerId: w.peerId, + status: w.status, + online: w.online, + jailed: w.jailed, + stakerApr: w.stakerApr, + totalDelegation: w.totalDelegation, + capedDelegation: w.capedDelegation, + delegationCapacity: calculateDelegationCapacity({ + totalDelegation: w.totalDelegation, + capedDelegation: w.capedDelegation, + }), + myDelegation: '0', + myTotalDelegationReward: '0', + delegations: [], + }; + + w.delegations.forEach(d => { + worker.myDelegation = BigNumber(worker.myDelegation).plus(d.deposit).toFixed(); + worker.myTotalDelegationReward = BigNumber(worker.myTotalDelegationReward) + .plus(d.claimableReward) + .plus(d.claimedReward) + .toFixed(); + + worker.delegations.push({ + ...d, + }); + }); + + return worker; + }); + + return sortWorkers(workers || [], sortBy, sortDir); + }, [fixedDelegations, sortBy, sortDir]); return { - isLoading: isSettingsLoading || isLoading, - workers, + isLoading: + isSettingsLoading || + isDelegationsQueryLoading || + isFixedWorkersLoading || + isFixedDelegationsLoading, + data, }; } export function useIsWorkerOperator() { const { address } = useAccount(); - const datasource = useSquidDataSource(); + const datasource = useSquid(); const { data, isLoading } = useMyWorkersCountQuery( datasource, { @@ -469,7 +487,7 @@ export function useWorkerDelegationInfo({ workerId?: string; enabled?: boolean; }) { - const datasource = useSquidDataSource(); + const datasource = useSquid(); const { data, isLoading } = useWorkerDelegationInfoQuery( datasource, { @@ -493,7 +511,7 @@ export function useWorkerDelegationInfo({ } export function useWorkerOwner({ workerId, enabled }: { workerId?: string; enabled?: boolean }) { - const datasource = useSquidDataSource(); + const datasource = useSquid(); const { data, isLoading } = useWorkerOwnerQuery( datasource, { @@ -514,30 +532,34 @@ export function useWorkerOwner({ workerId, enabled }: { workerId?: string; enabl } export function useMyWorkerDelegations({ - workerId, + peerId, enabled, }: { - workerId?: string; + peerId?: string; enabled?: boolean; }) { const { address } = useAccount(); - const datasource = useSquidDataSource(); - const { data, isLoading } = useMyWorkerDelegationsQuery( + const datasource = useSquid(); + const { data: delegations, isLoading: isDelegationsLoading } = useMyDelegationsQuery( datasource, { - workerId: workerId || '', + workerId: peerId || '', address: address || '', }, { select: res => { - return res.workerById; + return res.workers; }, enabled, }, ); + const { data: fixedDelegations, isLoading: isFixedDelegationsLoading } = useFixDelegations({ + workers: delegations, + }); + return { - isLoading: isLoading, - data, + isLoading: isDelegationsLoading || isFixedDelegationsLoading, + data: fixedDelegations?.[0]?.delegations, }; } diff --git a/src/components/Alert/Alert.tsx b/src/components/Alert/Alert.tsx deleted file mode 100644 index a438e25..0000000 --- a/src/components/Alert/Alert.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import React, { forwardRef } from 'react'; - -import { Alert as MaterialAlert, styled } from '@mui/material'; -import { InternalSnack, useSnackbar } from 'notistack'; - -export const AlertTitle = styled('div', { - name: 'AlertTitle', -})(({ theme }) => ({ - fontWeight: 500, - fontSize: '1.125rem', - marginBottom: theme.spacing(1), - maxWidth: 360, -})); - -export const AlertMessage = styled('div', { - name: 'AlertMessage', -})(({ theme }) => ({ - fontSize: '1rem', - fontWeight: 'normal', - maxWidth: 360, -})); - -export const Alert = forwardRef( - ( - { - id, - severity, - title, - message, - }: { - title?: string; - severity: 'warning'; - } & InternalSnack, - ref, - ) => { - const { closeSnackbar } = useSnackbar(); - - return ( - closeSnackbar(id)} - > - {title && {title}} - {message} - - ); - }, -); diff --git a/src/components/Alert/index.ts b/src/components/Alert/index.ts deleted file mode 100644 index 79e3b15..0000000 --- a/src/components/Alert/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './Alert'; diff --git a/src/components/Button/ConnectButton.tsx b/src/components/Button/ConnectButton.tsx index 388b0c2..6729f88 100644 --- a/src/components/Button/ConnectButton.tsx +++ b/src/components/Button/ConnectButton.tsx @@ -1,14 +1,22 @@ -import { MouseEventHandler } from 'react'; +import { LoginOutlined } from '@mui/icons-material'; +import { LoadingButton } from '@mui/lab'; +import { useConnectModal } from '@rainbow-me/rainbowkit'; -import { Button } from '@mui/material'; +function ConnectButton() { + const { openConnectModal, connectModalOpen } = useConnectModal(); -import { WalletIcon } from '@icons/WalletIcon'; - -function ConnectButton({ onClick }: { onClick?: MouseEventHandler }) { return ( - + ); } diff --git a/src/components/ConfirmDialog/ConfirmDialog.tsx b/src/components/ConfirmDialog/ConfirmDialog.tsx index 08c04c1..764f7d0 100644 --- a/src/components/ConfirmDialog/ConfirmDialog.tsx +++ b/src/components/ConfirmDialog/ConfirmDialog.tsx @@ -139,7 +139,7 @@ export function ConfirmDialog({ {!hideCancelButton ? ( - ) : null} diff --git a/src/components/ContractCallDialog/ContractCallDialog.tsx b/src/components/ContractCallDialog/ContractCallDialog.tsx index 1fd452c..ba91f34 100644 --- a/src/components/ContractCallDialog/ContractCallDialog.tsx +++ b/src/components/ContractCallDialog/ContractCallDialog.tsx @@ -1,7 +1,6 @@ import React, { PropsWithChildren } from 'react'; import { Box } from '@mui/material'; -import { useConnectModal } from '@rainbow-me/rainbowkit'; import { useAccount } from 'wagmi'; import ConnectButton from '@components/Button/ConnectButton'; @@ -14,7 +13,7 @@ export function ContractCallDialog({ maxWidth, minWidth = 600, confirmColor = 'info', - confirmButtonText = title.toUpperCase(), + confirmButtonText, cancelButtonText, hideCancelButton = true, disableBackdropClick = true, @@ -24,7 +23,6 @@ export function ContractCallDialog({ onApprove, }: PropsWithChildren) { const { isConnected } = useAccount(); - const { openConnectModal } = useConnectModal(); if (!isConnected) { return ( @@ -54,7 +52,7 @@ export function ContractCallDialog({ > Connect your wallet to proceed - + diff --git a/src/components/CopyToClipboard/CopyToClipboard.tsx b/src/components/CopyToClipboard/CopyToClipboard.tsx index 3c47b9d..afa3449 100644 --- a/src/components/CopyToClipboard/CopyToClipboard.tsx +++ b/src/components/CopyToClipboard/CopyToClipboard.tsx @@ -1,19 +1,19 @@ import React, { useRef, useState } from 'react'; -import { IconButton, Stack, styled } from '@mui/material'; -import { Box } from '@mui/system'; +import { ContentCopyOutlined } from '@mui/icons-material'; +import { Box, IconButton, Stack, styled } from '@mui/material'; import { alpha } from '@mui/system/colorManipulator'; -import classnames from 'classnames'; - -import { CopyIcon } from '@icons/CopyIcon'; +import classNames from 'classnames'; import { CopyToClipboardTooltip } from './CopyToClipboardTooltip'; export const Wrapper = styled(Stack)(({ theme }) => ({ '& .copyButton': { // marginTop: theme.spacing(-1), + // margin: 0, padding: 0, backgroundColor: 'transparent', + fontSize: 'inherit', }, '&.gutterBottom': { marginBottom: theme.spacing(1.5), @@ -79,7 +79,7 @@ export const CopyToClipboard = ({ return ( - - + + diff --git a/src/components/Form/FormikTextInput.tsx b/src/components/Form/FormikTextInput.tsx index 8dd5550..570d326 100644 --- a/src/components/Form/FormikTextInput.tsx +++ b/src/components/Form/FormikTextInput.tsx @@ -49,9 +49,10 @@ export function FormikTextInput({ onFocus, onBlur, showErrorOnlyOfTouched = false, + disabled, }: { id: string; - label?: string; + label?: React.ReactNode; formik?: any; multiline?: boolean; rows?: number; @@ -68,6 +69,7 @@ export function FormikTextInput({ autoComplete?: string; onFocus?: () => unknown; onBlur?: () => unknown; + disabled?: boolean; }) { const [visible, setVisible] = useState(false); @@ -114,6 +116,7 @@ export function FormikTextInput({ ) : undefined, } } + disabled={disabled} /> ); diff --git a/src/components/HelpTooltip/HelpTooltip.tsx b/src/components/HelpTooltip/HelpTooltip.tsx index 261d740..1876022 100644 --- a/src/components/HelpTooltip/HelpTooltip.tsx +++ b/src/components/HelpTooltip/HelpTooltip.tsx @@ -1,8 +1,7 @@ import React, { PropsWithChildren } from 'react'; -import { Box, Tooltip } from '@mui/material'; - -import { InfoIcon } from '@icons/InfoIcon'; +import { InfoOutlined } from '@mui/icons-material'; +import { Stack, Tooltip } from '@mui/material'; // export const Help = styled(Box)(({ theme, color }) => ({ // width: 15, @@ -21,14 +20,19 @@ import { InfoIcon } from '@icons/InfoIcon'; export const HelpTooltip = ({ title, + children, + placement = 'end', }: PropsWithChildren<{ title: React.ReactNode; + placement?: 'start' | 'end'; }>) => { return ( - - - - - + + {placement === 'end' && children} + + + + {placement === 'start' && children} + ); }; diff --git a/src/components/Logo/Logo.tsx b/src/components/Logo/Logo.tsx index 3af2121..0c5c323 100644 --- a/src/components/Logo/Logo.tsx +++ b/src/components/Logo/Logo.tsx @@ -2,9 +2,6 @@ import React from 'react'; import { Box, styled, useMediaQuery, useTheme } from '@mui/material'; -import { getSubsquidNetwork } from '@network/useSubsquidNetwork'; - -import { LogoCompact } from './LogoCompact'; import { LogoFull } from './LogoFull'; export const LogoWrapper = styled('div', { @@ -46,7 +43,8 @@ export function Logo({ color = '#fff' }: { color?: string }) { return ( - {compact ? : } + + {/* {compact ? : } */} {/* */} {/* {!narrow ? ( <> diff --git a/src/components/NoItems/index.tsx b/src/components/NoItems/index.tsx deleted file mode 100644 index 37ed42d..0000000 --- a/src/components/NoItems/index.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { Box, SxProps, Typography } from '@mui/material'; - -export function NoItems({ sx }: { sx?: SxProps }) { - return ( - - No items to show - - ); -} diff --git a/src/components/Placeholer/index.tsx b/src/components/Placeholer/index.tsx deleted file mode 100644 index ab8b3fc..0000000 --- a/src/components/Placeholer/index.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { PropsWithChildren } from 'react'; - -import { Box, SxProps } from '@mui/material'; - -function Placeholder({ children, sx }: PropsWithChildren<{ sx?: SxProps }>) { - return ( - - {children} - - ); -} - -export default Placeholder; diff --git a/src/components/SourceWallet/useMySourceOptions.tsx b/src/components/SourceWallet/useMySourceOptions.tsx deleted file mode 100644 index 6335388..0000000 --- a/src/components/SourceWallet/useMySourceOptions.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import React from 'react'; - -import { SourceWalletWithBalance, useMySources } from '@api/subsquid-network-squid'; - -import { SourceWalletOption } from './SourceWalletOption'; - -export function useMySourceOptions({ - enabled = true, - sourceDisabled, -}: { enabled?: boolean; sourceDisabled?: (w: SourceWalletWithBalance) => boolean } = {}) { - const { sources, isPending } = useMySources({ enabled }); - - const options = sources.map(s => { - return { - label: , - value: s.id, - disabled: sourceDisabled?.(s), - }; - }); - - return { - sources, - isPending, - options, - }; -} diff --git a/src/components/Table/DashboardTable.tsx b/src/components/Table/DashboardTable.tsx index 8b58d83..7cf8b1d 100644 --- a/src/components/Table/DashboardTable.tsx +++ b/src/components/Table/DashboardTable.tsx @@ -32,7 +32,7 @@ export const DashboardTableBase = styled(Table)(({ theme }) => ({ height: 200, }, - '& tbody .placeholder': { + '& tbody .no-items': { position: 'absolute', top: 0, left: 0, @@ -59,7 +59,7 @@ export function DashboardTable({ ) : null} - {loading ? : {children}} + {loading ? : children} ); diff --git a/src/components/Table/NoItems.tsx b/src/components/Table/NoItems.tsx new file mode 100644 index 0000000..e0a74c8 --- /dev/null +++ b/src/components/Table/NoItems.tsx @@ -0,0 +1,30 @@ +import React from 'react'; + +import { ErrorOutlineOutlined } from '@mui/icons-material'; +import { Box, Stack, SxProps, Typography } from '@mui/material'; + +export function NoItems({ + sx, + children, +}: React.PropsWithChildren<{ + sx?: SxProps; +}>) { + return ( + + + + + {children || No items to show} + + + + ); +} diff --git a/src/components/Table/TableList.tsx b/src/components/Table/TableList.tsx index 2724791..7e61789 100644 --- a/src/components/Table/TableList.tsx +++ b/src/components/Table/TableList.tsx @@ -2,9 +2,12 @@ import { styled, Table } from '@mui/material'; export const TableList = styled(Table, { name: 'TableList', -})(() => ({ - '& tbody tr:last-child td': { - border: 'none', +})(({ theme }) => ({ + // '& tbody tr:last-child td': { + // border: 'none', + // }, + '& td, & th': { + borderBottomColor: theme.palette.divider, }, '& tr td:first-child, & tr th:first-child': { paddingLeft: 0, diff --git a/src/components/Table/index.ts b/src/components/Table/index.ts new file mode 100644 index 0000000..683107c --- /dev/null +++ b/src/components/Table/index.ts @@ -0,0 +1,3 @@ +export { DashboardTable } from './DashboardTable'; +export { HeaderCell, SortableHeaderCell } from './BorderedTable'; +export { NoItems } from './NoItems'; diff --git a/src/components/Toaster/index.tsx b/src/components/Toaster/index.tsx new file mode 100644 index 0000000..793a5f6 --- /dev/null +++ b/src/components/Toaster/index.tsx @@ -0,0 +1,45 @@ +import { Report } from '@mui/icons-material'; +import { Alert, AlertColor, Box, CircularProgress, Typography } from '@mui/material'; +import toast, { Toaster as Toaster_ } from 'react-hot-toast'; + +export function Toaster() { + return ( + + {t => { + const content = typeof t.message === 'function' ? t.message(t) : t.message; + + const { color, icon } = + t.type === 'error' + ? { color: 'error', icon: } + : t.type === 'success' + ? { color: 'success' } + : t.type === 'loading' + ? { + color: 'info', + icon: ( + + + + ), + } + : {}; + + return ( + + ); + }} + + ); +} diff --git a/src/hooks/useSquidNetworkHeightHooks.tsx b/src/hooks/useSquidNetworkHeightHooks.tsx index 789b3ae..fdb88a6 100644 --- a/src/hooks/useSquidNetworkHeightHooks.tsx +++ b/src/hooks/useSquidNetworkHeightHooks.tsx @@ -10,9 +10,11 @@ import { import { logger } from '@logger'; import { useQueryClient } from '@tanstack/react-query'; import { max, partition } from 'lodash-es'; +// import { useSnackbar, } from 'notistack'; +import { toast } from 'react-hot-toast'; import { useBlockNumber } from 'wagmi'; -import { useSquidDataSource, useSquidNetworkHeightQuery } from '@api/subsquid-network-squid'; +import { useSquid, useSquidNetworkHeightQuery } from '@api/subsquid-network-squid'; import { localStorageStringSerializer, useLocalStorageState } from '@hooks/useLocalStorageState'; type HeightHook = { height: number; invalidateQueries: unknown[] }; @@ -31,7 +33,7 @@ const SquidHeightContext = createContext<{ setWaitHeight: () => {}, }); -export function useSquidNetworkHeight() { +export function useSquidHeight() { const { isLoading, currentHeight, waitHeight, setWaitHeight } = useContext(SquidHeightContext); return { @@ -45,8 +47,8 @@ export function useSquidNetworkHeight() { export function SquidHeightProvider({ children }: PropsWithChildren) { const queryClient = useQueryClient(); - const dataSource = useSquidDataSource(); - const [heightHooksRaw, setHeightHooksRaw] = useLocalStorageState('squid_height_hooks', { + const dataSource = useSquid(); + const [heightHooksRaw, setHeightHooksRaw] = useLocalStorageState('sqd_height_hooks', { defaultValue: '[]', serializer: localStorageStringSerializer, storageSync: false, @@ -58,6 +60,7 @@ export function SquidHeightProvider({ children }: PropsWithChildren) { refetchInterval: 2000, }, ); + // const { enqueueSnackbar } = useSnackbar(); const currentHeight = data?.squidStatus?.height || 0; @@ -89,6 +92,21 @@ export function SquidHeightProvider({ children }: PropsWithChildren) { }); }, [currentHeight, heightHooks, queryClient, setHeightHooksRaw]); + const maxWaitHeight = useMemo(() => { + return max(heightHooks.map(h => h.height)) || 0; + }, [heightHooks]); + + useEffect(() => { + if (maxWaitHeight > 0) { + toast.loading(`Syncing ${currentHeight} block of ${maxWaitHeight}`, { + id: 'squid-sync', + duration: Infinity, + }); + } else { + toast.remove('squid-sync'); + } + }, [maxWaitHeight, currentHeight]); + const setWaitHeight = useCallback( (height: bigint | string, invalidateQueries: unknown[] = []) => { heightHooks.push({ @@ -102,10 +120,6 @@ export function SquidHeightProvider({ children }: PropsWithChildren) { [heightHooks, setHeightHooksRaw], ); - const maxWaitHeight = useMemo(() => { - return max(heightHooks.map(h => h.height)) || 0; - }, [heightHooks]); - const { data: chainHeight } = useBlockNumber(); useEffect(() => { if (isLoading) return; diff --git a/src/i18n/dateFormat.ts b/src/i18n/dateFormat.ts index a915b20..10632f4 100644 --- a/src/i18n/dateFormat.ts +++ b/src/i18n/dateFormat.ts @@ -12,10 +12,29 @@ export function dateFormat( tpl = 'dd.MM.yyyy'; } - if (value.valueOf() === 0) return null; + if (value.valueOf() == 0) return null; const date = new Date(value); if (!isValid(date)) return null; return format(new Date(value), tpl); } + +export function relativeDateFormat( + from: Date | string | number | undefined, + to: Date | string | number | undefined, +) { + const fromMs = typeof from === 'number' ? from : new Date(from || 0).getTime(); + const toMs = typeof to === 'number' ? to : new Date(to || 0).getTime(); + const diff = Math.max(toMs - fromMs, 0) / 1000; + + const days = Math.floor(diff / 86400); + const hours = Math.floor((diff % 86400) / 3600); + const minutes = Math.floor((diff % 3600) / 60); + const seconds = Math.floor(diff % 60); + + if (days > 0) return `${days}d ${hours}h`; + if (hours > 0) return `${hours}h ${minutes}m`; + if (minutes > 0) return `${minutes}m ${seconds}s`; + return `${seconds}s`; +} diff --git a/src/icons/CopyIcon.tsx b/src/icons/CopyIcon.tsx deleted file mode 100644 index 299f64c..0000000 --- a/src/icons/CopyIcon.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react'; - -export function CopyIcon({ size = 20, strokeWidth }: { size?: number; strokeWidth?: number }) { - return ( - - - - ); -} diff --git a/src/icons/DashboardIcon.tsx b/src/icons/DashboardIcon.tsx deleted file mode 100644 index 0ff48a3..0000000 --- a/src/icons/DashboardIcon.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import React from 'react'; - -export function DashboardIcon({ variant }: { variant: 'filled' | 'outlined' }) { - return variant === 'filled' ? ( - - - - ) : ( - - - - ); -} diff --git a/src/icons/DoorIcon.tsx b/src/icons/DoorIcon.tsx deleted file mode 100644 index 8fe99cc..0000000 --- a/src/icons/DoorIcon.tsx +++ /dev/null @@ -1,17 +0,0 @@ -export function DoorIcon({ variant }: { variant: 'filled' | 'outlined' }) { - return variant === 'filled' ? ( - - - - ) : ( - - - - ); -} diff --git a/src/icons/EditIcon.tsx b/src/icons/EditIcon.tsx index f773b88..464c32d 100644 --- a/src/icons/EditIcon.tsx +++ b/src/icons/EditIcon.tsx @@ -1,4 +1,4 @@ -export function EditIcon({ size = 20, color = '#384955' }: { size?: number; color?: string }) { +export function EditIcon({ size = 16, color = '#384955' }: { size?: number; color?: string }) { return ( - - - ); -} diff --git a/src/icons/LogoutIcon.tsx b/src/icons/LogoutIcon.tsx deleted file mode 100644 index 4110335..0000000 --- a/src/icons/LogoutIcon.tsx +++ /dev/null @@ -1,10 +0,0 @@ -export function LogoutIcon() { - return ( - - - - ); -} diff --git a/src/icons/NetworkNodeIcon.tsx b/src/icons/NetworkNodeIcon.tsx index c6c8cc6..1b8f245 100644 --- a/src/icons/NetworkNodeIcon.tsx +++ b/src/icons/NetworkNodeIcon.tsx @@ -1,4 +1,4 @@ -export function NetworkNodeIcon({ variant }: { variant: 'filled' | 'outlined' }) { +export function NetworkNodeIcon({ variant = 'filled' }: { variant?: 'filled' | 'outlined' }) { return variant === 'filled' ? ( - - - ) : ( - - - - ); -} diff --git a/src/icons/WalletIcon.tsx b/src/icons/WalletIcon.tsx deleted file mode 100644 index 8b5a6d1..0000000 --- a/src/icons/WalletIcon.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react'; - -export function WalletIcon({ size = 24 }: { size?: number }) { - return ( - - - - - ); -} diff --git a/src/icons/WarningIcon.tsx b/src/icons/WarningIcon.tsx deleted file mode 100644 index ad38fba..0000000 --- a/src/icons/WarningIcon.tsx +++ /dev/null @@ -1,37 +0,0 @@ -export function WarningIcon({ - className, - color = 'warning', - size = 24, -}: { - className?: string; - size?: number; - color?: 'error' | 'warning'; -}) { - return color === 'warning' ? ( - - - - ) : ( - - - - ); -} diff --git a/src/layouts/NetworkLayout/LogoutMenuItem.tsx b/src/layouts/NetworkLayout/LogoutMenuItem.tsx index 94f5a65..6f43f1d 100644 --- a/src/layouts/NetworkLayout/LogoutMenuItem.tsx +++ b/src/layouts/NetworkLayout/LogoutMenuItem.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { LogoutIcon } from '@icons/LogoutIcon'; +import { LogoutOutlined } from '@mui/icons-material'; import { useAppReload } from '../../index.tsx'; @@ -9,5 +9,5 @@ import { BasicMenuItem } from './BasicMenuItem'; export function LogoutMenuItem() { const reload = useAppReload({ to: '/' }); - return reload()} />; + return reload()} />; } diff --git a/src/layouts/NetworkLayout/NetworkLayout.tsx b/src/layouts/NetworkLayout/NetworkLayout.tsx index 72c8a43..57f9c81 100644 --- a/src/layouts/NetworkLayout/NetworkLayout.tsx +++ b/src/layouts/NetworkLayout/NetworkLayout.tsx @@ -27,7 +27,6 @@ import { getChainId, getSubsquidNetwork } from '@network/useSubsquidNetwork'; import { ColorVariant } from '../../theme'; import { NetworkMenu } from './NetworkMenu'; -import { SyncSquidSnackbar } from './SyncSquidSnackbar'; import { UserMenu } from './UserMenu'; const APP_BAR_HEIGHT = 60; @@ -72,10 +71,6 @@ export const AppToolbarSidebar = styled('div', { stroke: '#fff', }, - [theme.breakpoints.down('xl')]: { - width: theme.spacing(7), - }, - [theme.breakpoints.down('md')]: { // width: 'auto', '&:after': { @@ -111,14 +106,10 @@ export const Content = styled('div', { alignItems: 'stretch', justifyContent: 'center', paddingTop: APP_BAR_HEIGHT + bannerHeight, - paddingLeft: SIDEBAR_WIDTH.M, + paddingLeft: 0, paddingBottom: theme.spacing(8), minWidth: 350, - '&.narrow': { - paddingLeft: 0, - }, - [theme.breakpoints.up('xl')]: { paddingLeft: SIDEBAR_WIDTH.L, }, @@ -157,6 +148,7 @@ const Sidebar = styled('div', { const bannerHeight = useBannerHeight(); return { + // borderRight: `solid ${theme.palette.divider} 1px`, display: 'flex', flexDirection: 'column', alignItems: 'stretch', @@ -170,13 +162,13 @@ const Sidebar = styled('div', { // paddingBottom: theme.spacing(3), zIndex: theme.zIndex.appBar + 1, // boxShadow: '-5px 4px 20px rgba(0, 0, 0, 0.25)', - width: SIDEBAR_WIDTH.M, + width: SIDEBAR_WIDTH.L, overflowY: 'auto', overflowX: 'hidden', - [theme.breakpoints.up('xl')]: { - width: SIDEBAR_WIDTH.L, - }, + // [theme.breakpoints.up('xl')]: { + // width: SIDEBAR_WIDTH.L, + // }, '&.guideActive': { zIndex: theme.zIndex.guide.highlight, @@ -263,9 +255,7 @@ export const NetworkLayout = ({ stretchContent?: boolean; }>) => { const theme = useTheme(); - const narrowLg = useMediaQuery(theme.breakpoints.down('lg')); - const narrowXs = useMediaQuery(theme.breakpoints.down('xs')); - const isMobile = useMediaQuery(theme.breakpoints.down('xxs')); + const narrow = useMediaQuery(theme.breakpoints.down('xl')); const [isMenuOpen, setIsMenuOpen] = useState(false); @@ -279,7 +269,7 @@ export const NetworkLayout = ({ if (chain?.id === getChainId(network)) return; disconnect(); - }, [isConnected, chain, disconnect, walletClient, network]); + }, [isConnected, chain?.id, walletClient, network, disconnect]); const centeredSx = { alignSelf: stretchContent ? 'stretch' : 'flex-start', @@ -287,7 +277,7 @@ export const NetworkLayout = ({ return (
- + {/* */} {/* */} @@ -308,7 +298,7 @@ export const NetworkLayout = ({ {/* */} {/*{narrowXs ? null : }*/} - {narrowXs ? : null} + {narrow ? : null} - + @@ -326,7 +316,7 @@ export const NetworkLayout = ({ setIsMenuOpen(false)} /> - + {children} diff --git a/src/layouts/NetworkLayout/NetworkMenu.tsx b/src/layouts/NetworkLayout/NetworkMenu.tsx index 3805c94..33b0440 100644 --- a/src/layouts/NetworkLayout/NetworkMenu.tsx +++ b/src/layouts/NetworkLayout/NetworkMenu.tsx @@ -1,79 +1,39 @@ import React, { ForwardedRef, forwardRef } from 'react'; -import { Box, Button, styled, Typography, useMediaQuery, useTheme } from '@mui/material'; +import { + ArrowOutwardOutlined, + BackHand, + BackHandOutlined, + Dashboard, + DashboardOutlined, + Lan, + LanOutlined, + Savings, + SavingsOutlined, + SensorDoor, + SensorDoorOutlined, + SmsOutlined, +} from '@mui/icons-material'; +import { Button, styled } from '@mui/material'; import { Link, useLocation } from 'react-router-dom'; import { useIsWorkerOperator } from '@api/subsquid-network-squid'; -import { ContactsIcon } from '@icons/ContactsIcon'; -import { DashboardIcon } from '@icons/DashboardIcon'; -import { HandIcon } from '@icons/HandIcon'; -import { NetworkNodeIcon } from '@icons/NetworkNodeIcon'; -import { OpenInNewIcon } from '@icons/OpenInNewIcon'; -import { SavingsIcon } from '@icons/SavingsIcon'; +import { demoFeaturesEnabled } from '@hooks/demoFeaturesEnabled'; import { useWorkersChatUrl } from '@network/useWorkersChat'; interface NetworkMenuProps { onItemClick: () => void; } -const MenuItem = styled(Button)(({ theme: { palette, spacing, breakpoints } }) => ({ - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - height: spacing(7), - minWidth: 0, +const MenuItem = styled(Button)(({ theme: { palette, spacing, breakpoints, typography } }) => ({ + ...typography.subtitle2, - padding: spacing(0), - [breakpoints.up('xl')]: { - justifyContent: 'flex-start', - padding: spacing(0, 2.5), - }, + height: spacing(7), + display: 'flex', + justifyContent: 'flex-start', + padding: spacing(0, 3), borderRadius: 0, - '& .leftIcon': { - display: 'flex', - alignItems: 'center', - marginRight: spacing(0), - - [breakpoints.up('xl')]: { - marginRight: spacing(1.5), - }, - }, - - '& .rightIcon': { - alignItems: 'center', - justifyContent: 'center', - marginLeft: spacing(1), - }, - // '& svg:not(.badge) path': { - // transition: 'fill 300ms ease-out', - // }, - // ['&:hover']: { - // backgroundColor: palette.background.paper, - // color: palette.info.contrastText, - // // '& svg:not(.badge) path': { - // // fill: 'red', - // // }, - // // '& svg.badge path': { - // // fill: 'inherit', - // // }, - // }, - // [`&.selected`]: { - // // backgroundColor: palette.info.main, - // // color: palette.info.contrastText, - // // '& svg:not(.badge) path': { - // // fill: palette.info.contrastText, - // // }, - // }, - - // [`&.${buttonClasses.disabled}`]: { - // opacity: 0.4, - // backgroundColor: 'transparent', - // color: palette.text.secondary, - // '& svg:not(.badge) path': { - // fill: palette.text.secondary, - // }, - // }, })); export const Item = forwardRef( @@ -94,8 +54,8 @@ export const Item = forwardRef( path: string; target?: string; disabled?: boolean; - LeftIcon: any; - RightIcon?: any; + LeftIcon: React.ReactNode | ((active: boolean) => React.ReactNode); + RightIcon?: React.ReactNode | ((active: boolean) => React.ReactNode); label?: string; onClick?: () => void; }, @@ -104,11 +64,19 @@ export const Item = forwardRef( const location = useLocation(); const active = forceActive || (!forceInactive && location.pathname.startsWith(path)); - const theme = useTheme(); - const compact = useMediaQuery(theme.breakpoints.down('xl')); + // const theme = useTheme(); + // const compact = useMediaQuery(theme.breakpoints.down('xl')); + // const mobile = useMediaQuery(theme.breakpoints.down('xs')); + + // LeftIcon.props.on = active; + + const startIcon = typeof LeftIcon === 'function' ? LeftIcon(active) : LeftIcon; + const endIcon = typeof RightIcon === 'function' ? RightIcon(active) : RightIcon; - const button = ( + return ( {startIcon}} + endIcon={{endIcon}} ref={ref} onClick={onClick} className={active ? 'selected' : undefined} @@ -119,29 +87,19 @@ export const Item = forwardRef( target={target} rel={target ? 'noreferrer' : undefined} > - - - - {!compact ? ( - <> - {label} - {RightIcon ? ( - - - - ) : null} - + {/* {!compact || mobile ? ( + <> + ) : null} + + */} + {label} ); - - if (disabled) return button; - - return button; - // - // { button } - // }, + // + // { button } + // ); export const NetworkMenu = ({ onItemClick }: NetworkMenuProps) => { @@ -150,11 +108,38 @@ export const NetworkMenu = ({ onItemClick }: NetworkMenuProps) => { return ( <> - - - - - {/* */} + (active ? : )} + label="Dashboard" + onClick={onItemClick} + path="/dashboard" + /> + (active ? : )} + label="Assets" + onClick={onItemClick} + path="/assets" + /> + (active ? : )} + label="Workers" + onClick={onItemClick} + path="/workers" + /> + (active ? : )} + label="Delegations" + onClick={onItemClick} + path="/delegations" + /> + {demoFeaturesEnabled() && ( + (active ? : )} + label="Portals" + onClick={onItemClick} + path="/portals" + /> + )}
@@ -170,16 +155,16 @@ export const NetworkMenu = ({ onItemClick }: NetworkMenuProps) => { label="Operators Chat" path={workersChatUrl || '/null'} target="_blank" - LeftIcon={ContactsIcon} - RightIcon={OpenInNewIcon} + LeftIcon={} + RightIcon={} /> ) : null} } + RightIcon={} /> ); diff --git a/src/layouts/NetworkLayout/SyncSquidSnackbar.tsx b/src/layouts/NetworkLayout/SyncSquidSnackbar.tsx deleted file mode 100644 index 480889b..0000000 --- a/src/layouts/NetworkLayout/SyncSquidSnackbar.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import React from 'react'; - -import { Box, Card, CircularProgress, Stack, Typography, useTheme } from '@mui/material'; - -import { useSquidNetworkHeight } from '@hooks/useSquidNetworkHeightHooks'; - -export const SyncSquidSnackbar = () => { - const theme = useTheme(); - - const { isWaiting, waitHeight, currentHeight } = useSquidNetworkHeight(); - - if (!isWaiting) return null; - - return ( - - - - - - Synced {currentHeight} block of {waitHeight} - - - - - ); -}; diff --git a/src/layouts/NetworkLayout/UserMenu.tsx b/src/layouts/NetworkLayout/UserMenu.tsx index c06b33b..bb0084e 100644 --- a/src/layouts/NetworkLayout/UserMenu.tsx +++ b/src/layouts/NetworkLayout/UserMenu.tsx @@ -1,9 +1,8 @@ import React, { useCallback, useMemo, useRef, useState } from 'react'; import { addressFormatter } from '@lib/formatters/formatters'; -import { ExpandMore } from '@mui/icons-material'; -import { Box, Button, Menu, Stack, styled, Typography } from '@mui/material'; -import { useConnectModal } from '@rainbow-me/rainbowkit'; +import { AccountBalanceWalletOutlined, ExpandMore } from '@mui/icons-material'; +import { Box, Button, Menu, styled, Typography } from '@mui/material'; import { useAccount } from 'wagmi'; import ConnectButton from '@components/Button/ConnectButton'; @@ -24,7 +23,6 @@ export const Dropdown = styled(Button)(({ theme }) => ({ export function UserMenu() { const { address, isConnected } = useAccount(); - const { openConnectModal } = useConnectModal(); const ref = useRef(null); const [open, setOpen] = useState(false); @@ -40,32 +38,32 @@ export function UserMenu() { }, [address]); if (!address || !isConnected) { - return ; + return ; } return ( <> - } + endIcon={ + + } > {maskedAddress} - - + 0 && n < 0.1 ** decimals) { -// return `<0.01 ${SQD_TOKEN}`; -// } - -// return `${numberWithSpacesFormatter(value.toFixed(decimals))} ${SQD_TOKEN}`; -// } - export function peerIdToHex(peerId: string) { return toHex(bs58.decode(peerId)); } + +export function unwrapMulticallResult(result?: MulticallResponse): T | undefined { + return result?.status === 'success' ? (result.result as T) : undefined; +} + +export function getBlockTime(blocksCount: number | bigint) { + return Number(blocksCount) * 12_000; +} diff --git a/src/network/ConnectedWalletRequired.tsx b/src/network/ConnectedWalletRequired.tsx index 48cc013..a20cb79 100644 --- a/src/network/ConnectedWalletRequired.tsx +++ b/src/network/ConnectedWalletRequired.tsx @@ -1,14 +1,12 @@ import React, { PropsWithChildren } from 'react'; import { Box } from '@mui/material'; -import { useConnectModal } from '@rainbow-me/rainbowkit'; import { useAccount } from 'wagmi'; import ConnectButton from '@components/Button/ConnectButton'; export function ConnectedWalletRequired({ children }: PropsWithChildren) { const { isConnected } = useAccount(); - const { openConnectModal } = useConnectModal(); if (!isConnected) { return ( @@ -22,7 +20,7 @@ export function ConnectedWalletRequired({ children }: PropsWithChildren) { > Connect your wallet to proceed - + ); diff --git a/src/network/config.ts b/src/network/config.ts index 2516b32..7105325 100644 --- a/src/network/config.ts +++ b/src/network/config.ts @@ -1,6 +1,6 @@ import { getDefaultConfig } from '@rainbow-me/rainbowkit'; import { upperFirst } from 'lodash-es'; -import { arbitrumSepolia, arbitrum } from 'wagmi/chains'; +import { arbitrumSepolia, arbitrum, sepolia, mainnet } from 'wagmi/chains'; import { getSubsquidNetwork, NetworkName } from './useSubsquidNetwork'; @@ -18,31 +18,9 @@ import { getSubsquidNetwork, NetworkName } from './useSubsquidNetwork'; const network = getSubsquidNetwork(); -export const wagmiConfig = getDefaultConfig({ +export const rainbowConfig = getDefaultConfig({ appName: `Subsquid Network ${upperFirst(network)}`, projectId: process.env.WALLET_CONNECT_PROJECT_ID || '', - chains: - network === NetworkName.Mainnet - ? [ - { - ...arbitrum, - // rpcUrls: { - // default: { - // http: ['https://arbitrum-one.public.blastapi.io'], - // webSocket: ['wss://arbitrum-one.public.blastapi.io'], - // }, - // }, - }, - ] - : [ - { - ...arbitrumSepolia, - // rpcUrls: { - // default: { - // http: ['https://arbitrum-sepolia.public.blastapi.io'], - // webSocket: ['wss://arbitrum-sepolia.public.blastapi.io'], - // }, - // }, - }, - ], + chains: network === NetworkName.Mainnet ? [arbitrum, mainnet] : [arbitrumSepolia, sepolia], + syncConnectedChain: true, }); diff --git a/src/network/useContracts.ts b/src/network/useContracts.ts index cf24249..63c2cea 100644 --- a/src/network/useContracts.ts +++ b/src/network/useContracts.ts @@ -1,7 +1,10 @@ +import { mainnet, sepolia } from 'viem/chains'; + import { NetworkName, getSubsquidNetwork } from './useSubsquidNetwork.ts'; export function useContracts(): { SQD: `0x${string}`; + ROUTER: `0x${string}`; WORKER_REGISTRATION: `0x${string}`; STAKING: `0x${string}`; REWARD_TREASURY: `0x${string}`; @@ -9,6 +12,8 @@ export function useContracts(): { GATEWAY_REGISTRATION: `0x${string}`; SOFT_CAP: `0x${string}`; SQD_TOKEN: string; + CHAIN_ID_L1: number; + MULTICALL: `0x${string}`; } { const network = getSubsquidNetwork(); @@ -23,6 +28,9 @@ export function useContracts(): { GATEWAY_REGISTRATION: `0xAB46F688AbA4FcD1920F21E9BD16B229316D8b0a`, SOFT_CAP: `0x52f31c9c019f840A9C0e74F66ACc95455B254BeA`, SQD_TOKEN: 'tSQD', + ROUTER: '0xD2093610c5d27c201CD47bCF1Df4071610114b64', + CHAIN_ID_L1: sepolia.id, + MULTICALL: '0x7eCfBaa8742fDf5756DAC92fbc8b90a19b8815bF', }; } case NetworkName.Mainnet: { @@ -35,6 +43,9 @@ export function useContracts(): { GATEWAY_REGISTRATION: `0x8a90a1ce5fa8cf71de9e6f76b7d3c0b72feb8c4b`, SOFT_CAP: `0x0eb27b1cbba04698dd7ce0f2364584d33a616545`, SQD_TOKEN: 'SQD', + ROUTER: '0x67F56D27dab93eEb07f6372274aCa277F49dA941', + CHAIN_ID_L1: mainnet.id, + MULTICALL: '0x7eCfBaa8742fDf5756DAC92fbc8b90a19b8815bF', }; } } diff --git a/src/pages/AssetsPage/Assets.tsx b/src/pages/AssetsPage/Assets.tsx index 8899e7f..3581324 100644 --- a/src/pages/AssetsPage/Assets.tsx +++ b/src/pages/AssetsPage/Assets.tsx @@ -7,7 +7,6 @@ import { Box, Divider, Stack, - styled, SxProps, Theme, Typography, @@ -18,9 +17,16 @@ import Grid from '@mui/material/Unstable_Grid2'; import BigNumber from 'bignumber.js'; import { Cell, Pie, PieChart } from 'recharts'; -import { useMyAssets } from '@api/subsquid-network-squid'; +import { + AccountType, + ClaimType, + useSourcesWithAssetsQuery, + useSquid, + Worker, +} from '@api/subsquid-network-squid'; import SquaredChip from '@components/Chip/SquaredChip'; import { HelpTooltip } from '@components/HelpTooltip'; +import { useAccount } from '@network/useAccount'; import { useContracts } from '@network/useContracts'; import { ColumnLabel, ColumnValue, SummarySection } from '@pages/DashboardPage/Summary'; @@ -34,26 +40,20 @@ type TokenBalance = { tip?: string; }; -const TokenBalanceList = styled(Box, { - name: 'TokenBalanceList', -})(({ theme }) => ({ - display: 'grid', - gap: theme.spacing(1.5), -})); - function TokenBalance({ sx, balance }: { sx?: SxProps; balance?: TokenBalance }) { const { SQD_TOKEN } = useContracts(); return ( - - - {balance?.name} - - + + + + {balance?.name} + + - {tokenFormatter(balance?.value || 0, SQD_TOKEN, 3)} + {tokenFormatter(fromSqd(balance?.value), SQD_TOKEN, 3)} ); } @@ -106,7 +106,6 @@ function TotalBalance({ balances, total }: { balances: TokenBalance[]; total: Bi - {/* */} @@ -121,54 +120,130 @@ export function MyAssets() { const theme = useTheme(); const narrowXs = useMediaQuery(theme.breakpoints.down('xs')); const narrowSm = useMediaQuery(theme.breakpoints.down('sm')); - const { isLoading, assets } = useMyAssets(); - - const data = useMemo( - (): TokenBalance[] => [ - { - name: 'Transferable', - value: fromSqd(assets.balance), - color: theme.palette.success.main, - background: theme.palette.success.background, - tip: 'Liquid tokens, can be freely transferred to external addresses', - }, - { - name: 'Locked', - value: fromSqd(assets.locked), - color: theme.palette.warning.main, - background: theme.palette.warning.background, - tip: 'Tokens locked in the vesting contracts owned by the wallet. Can be used for bonding (running a worker) and/or delegation', - }, - { - name: 'Claimable', - value: fromSqd(assets.claimable), - color: theme.palette.info.main, - background: theme.palette.info.background, - tip: 'Earned but not yet claimed token rewards, aggregated across all workers and delegations', - }, - { - name: 'Bonded', - value: fromSqd(assets.bonded), - color: theme.palette.primary.contrastText, - background: theme.palette.primary.main, - tip: 'Tokens bonded in the worker registry contract. 100000 SQD has to be bonded per a worker node', - }, - { - name: 'Delegated', - value: fromSqd(assets.delegated), - color: theme.palette.secondary.contrastText, - background: theme.palette.secondary.main, - tip: 'Tokens delegated to workers', - }, - ], - [ - assets.balance, - assets.bonded, - assets.claimable, - assets.delegated, - assets.locked, - theme.palette, - ], + + const account = useAccount(); + const squid = useSquid(); + + const { data: sourcesQuery, isLoading: isSourcesLoading } = useSourcesWithAssetsQuery(squid, { + address: account.address || '0x', + }); + + const isLoading = isSourcesLoading; + + const balances = useMemo((): TokenBalance[] => { + const transferable: TokenBalance = { + name: 'Transferable', + value: BigNumber(0), + color: theme.palette.success.main, + background: theme.palette.success.background, + tip: 'Liquid tokens, can be freely transferred to external addresses', + }; + const vesting: TokenBalance = { + name: 'Vesting', + value: BigNumber(0), + color: theme.palette.warning.main, + background: theme.palette.warning.background, + tip: 'Tokens locked in the vesting contracts owned by the wallet. Can be used for bonding (running a worker) and/or delegation', + }; + const claimable: TokenBalance = { + name: 'Claimable', + value: BigNumber(0), + color: theme.palette.info.main, + background: theme.palette.info.background, + tip: 'Earned but not yet claimed token rewards, aggregated across all workers and delegations', + }; + const bonded: TokenBalance = { + name: 'Bonded', + value: BigNumber(0), + color: theme.palette.primary.contrastText, + background: theme.palette.primary.main, + tip: 'Tokens bonded in the worker registry contract', + }; + const delegated: TokenBalance = { + name: 'Delegated', + value: BigNumber(0), + color: theme.palette.secondary.contrastText, + background: theme.palette.secondary.main, + tip: 'Tokens delegated to workers', + }; + + sourcesQuery?.accounts.forEach(s => { + if (s.type === AccountType.User) { + transferable.value = transferable.value.plus(s.balance); + } else if (s.type === AccountType.Vesting) { + vesting.value = vesting.value.plus(s.balance); + } + + s.delegations2.forEach(d => { + delegated.value = delegated.value.plus(d.deposit); + claimable.value = claimable.value.plus(d.claimableReward); + }); + + s.workers2.forEach(w => { + bonded.value = bonded.value.plus(w.bond); + claimable.value = claimable.value.plus(w.claimableReward); + }); + }); + + return [transferable, vesting, claimable, bonded, delegated]; + }, [sourcesQuery?.accounts, theme.palette]); + + const totalBalance = useMemo(() => { + return balances.reduce((a, b) => a.plus(b.value), BigNumber(0)); + }, [balances]); + + const claimableSources = useMemo(() => { + if (!sourcesQuery?.accounts) return; + + return sourcesQuery.accounts.map(s => { + const claims: (Pick & { + type: ClaimType; + claimableReward: string; + })[] = []; + + s.delegations2.forEach(d => { + if (d.claimableReward === '0') return; + + claims.push({ + id: d.worker.id, + peerId: d.worker.peerId, + name: d.worker.name, + claimableReward: d.claimableReward, + type: ClaimType.Delegation, + }); + }); + + s.workers2.forEach(w => { + if (w.claimableReward === '0') return; + + claims.push({ + id: w.id, + peerId: w.peerId, + name: w.name, + claimableReward: w.claimableReward, + type: ClaimType.Worker, + }); + }); + + const totalClaimableBalance = claims.reduce( + (t, i) => t.plus(i.claimableReward), + BigNumber(0), + ); + + return { + id: s.id, + type: s.type, + balance: totalClaimableBalance.toString(), + claims: claims.sort( + (a, b) => BigNumber(a.claimableReward).comparedTo(b.claimableReward) * -1, + ), + }; + }); + }, [sourcesQuery?.accounts]); + + const hasAvailableClaims = useMemo( + () => !!claimableSources?.some(s => s.balance !== '0'), + [claimableSources], ); return ( @@ -177,7 +252,9 @@ export function MyAssets() { loading={isLoading} sx={{ width: 1 }} title={} - action={} + action={ + + } > @@ -190,20 +267,20 @@ export function MyAssets() { justifyContent="stretch" > } spacing={1} flex={1}> - - - + + + } spacing={1} flex={1}> - - + + {narrowSm ? null : ( - + )} diff --git a/src/pages/AssetsPage/ClaimButton.tsx b/src/pages/AssetsPage/ClaimButton.tsx index ba32ff7..7df6487 100644 --- a/src/pages/AssetsPage/ClaimButton.tsx +++ b/src/pages/AssetsPage/ClaimButton.tsx @@ -1,19 +1,31 @@ -import { useEffect, useMemo, useState } from 'react'; +import { useMemo, useState } from 'react'; import { tokenFormatter } from '@lib/formatters/formatters'; import { fromSqd } from '@lib/network/utils'; +import { TollOutlined } from '@mui/icons-material'; import { LoadingButton } from '@mui/lab'; import { Box, TableBody, TableCell, TableRow } from '@mui/material'; import { useFormik } from 'formik'; +import toast from 'react-hot-toast'; +import { useClient } from 'wagmi'; import * as yup from 'yup'; -import { useClaim } from '@api/contracts/claim'; -import { ClaimType, useMyClaimsAvailable, useMySources } from '@api/subsquid-network-squid'; -import { BlockchainContractError } from '@components/BlockchainContractError'; +import { rewardTreasuryAbi, useReadRouterRewardTreasury } from '@api/contracts'; +import { useWriteSQDTransaction } from '@api/contracts/useWriteTransaction'; +import { errorMessage } from '@api/contracts/utils'; +import { + AccountType, + ClaimType, + SourceWalletWithBalance, + Worker, +} from '@api/subsquid-network-squid'; import { ContractCallDialog } from '@components/ContractCallDialog'; import { Form, FormikSelect, FormRow } from '@components/Form'; +import { Loader } from '@components/Loader'; import { SourceWalletOption } from '@components/SourceWallet'; import { TableList } from '@components/Table/TableList.tsx'; +import { useSquidHeight } from '@hooks/useSquidNetworkHeightHooks'; +import { useAccount } from '@network/useAccount'; import { useContracts } from '@network/useContracts'; import { WorkerName } from '@pages/WorkersPage/WorkerName'; @@ -21,105 +33,141 @@ export const claimSchema = yup.object({ source: yup.string().label('Source').trim().required('Source is required'), }); -export function ClaimButton() { - const { SQD_TOKEN } = useContracts(); - const { claim, error, isPending } = useClaim(); +export type SourceWalletWithClaims = SourceWalletWithBalance & { + claims: (Pick & { + type: ClaimType; + claimableReward: string; + })[]; +}; + +export function ClaimButton({ + sources, + disabled, +}: { + sources?: SourceWalletWithClaims[]; + disabled?: boolean; +}) { + const [open, setOpen] = useState(false); + + return ( + <> + } + onClick={() => setOpen(true)} + color="info" + variant="contained" + loading={open} + disabled={disabled} + > + CLAIM + + setOpen(false)} /> + + ); +} + +export function ClaimDialog({ + sources, + open, + onClose, +}: { + sources?: SourceWalletWithClaims[]; + open: boolean; + onClose: () => void; +}) { + const client = useClient(); + const account = useAccount(); + + const { setWaitHeight } = useSquidHeight(); + + const contracts = useContracts(); + const contractWriter = useWriteSQDTransaction({}); + + const rewardTreasuryAddress = useReadRouterRewardTreasury({ + address: contracts.ROUTER, + }); + + const isLoading = rewardTreasuryAddress.isLoading; + + const initialValues = useMemo(() => { + const option = sources?.find(c => c.balance !== '0') || sources?.[0]; + + return { + source: option?.id || '0x', + }; + }, [sources]); + const formik = useFormik({ - initialValues: { - source: '', - amount: 0, - max: 0, - }, + initialValues, validationSchema: claimSchema, validateOnChange: true, validateOnBlur: true, validateOnMount: true, + enableReinitialize: true, onSubmit: async values => { - const wallet = claimableSources.find(w => w?.id === values.source); - if (!wallet) return; - - const { failedReason } = await claim({ - wallet, - }); - - if (!failedReason) { - handleClose(); + if (!client) return; + if (!account.address) return; + if (!rewardTreasuryAddress.data) return; + if (!sources) return; + + try { + const { source } = claimSchema.cast(values); + + const wallet = sources.find(w => w?.id === source); + if (!wallet) return; + + const receipt = await contractWriter.writeTransactionAsync({ + address: rewardTreasuryAddress.data, + abi: rewardTreasuryAbi, + functionName: 'claimFor', + args: [contracts.REWARD_DISTRIBUTION, account.address], + vesting: wallet.type === AccountType.Vesting ? (wallet.id as `0x${string}`) : undefined, + }); + setWaitHeight(receipt.blockNumber, []); + + onClose(); + } catch (e: unknown) { + toast.error(errorMessage(e)); } }, }); - const [open, setOpen] = useState(false); - const handleOpen = () => setOpen(true); - const handleClose = () => setOpen(false); - - const { sources } = useMySources(); - const { - claims, - hasClaimsAvailable, - sources: claimableSources, - isLoading: isClaimsLoading, - currentSourceTotalClaimsAvailable, - } = useMyClaimsAvailable({ - source: formik.values.source, - }); - - const options = useMemo( - () => - sources.map(s => { - const claimableSource = claimableSources.find(cs => cs.id === s.id); - return { - label: , - value: s.id, - disabled: fromSqd(claimableSource?.balance).eq(0), - }; - }), - [claimableSources, sources], - ); - - useEffect(() => { - if (isClaimsLoading) return; - else if (formik.values.source) return; - - const source = claimableSources?.[0]; - if (!source) return; - - formik.setValues({ - ...formik.values, - source: source.id, - }); - }, [formik, isClaimsLoading, claimableSources]); + const hasAvailableClaims = useMemo(() => !!sources?.some(s => s.balance !== '0'), [sources]); return ( - <> - - CLAIM - - { - if (!confirmed) return handleClose(); - - formik.handleSubmit(); - }} - loading={isPending} - confirmColor="info" - disableConfirmButton={currentSourceTotalClaimsAvailable.lte(0)} - > + { + if (!confirmed) return onClose(); + + formik.handleSubmit(); + }} + loading={contractWriter.isPending} + confirmColor="info" + disableConfirmButton={!formik.isValid || !hasAvailableClaims} + > + {isLoading || !sources ? ( + + ) : (
({ + label: , + value: s.id, + disabled: s.balance === '0', + }))} formik={formik} + onChange={e => { + const source = sources.find(s => s.id === e.target.value); + if (!source) return; + + formik.setFieldValue('source', source.id); + }} /> @@ -132,28 +180,28 @@ export function ClaimButton() { > - {claims.map(w => { - return ( - - - - - - {w.type === ClaimType.Worker ? 'Worker reward' : 'Delegation reward'} - - - {tokenFormatter(fromSqd(w.claimableReward), SQD_TOKEN)} - - - ); - })} + {sources + .find(s => s.id === formik.values.source) + ?.claims.map(w => { + return ( + + + + + + {w.type === ClaimType.Worker ? 'Worker reward' : 'Delegation reward'} + + + {tokenFormatter(fromSqd(w.claimableReward), contracts.SQD_TOKEN)} + + + ); + })} - - -
- + )} +
); } diff --git a/src/pages/AssetsPage/ReleaseButton.tsx b/src/pages/AssetsPage/ReleaseButton.tsx index 2391ca5..03460b5 100644 --- a/src/pages/AssetsPage/ReleaseButton.tsx +++ b/src/pages/AssetsPage/ReleaseButton.tsx @@ -1,12 +1,15 @@ import React from 'react'; -import { fromSqd } from '@lib/network'; import { LoadingButton } from '@mui/lab'; +import { toast } from 'react-hot-toast'; import * as yup from 'yup'; -import { useVestingContract, useVestingContractRelease } from '@api/contracts/vesting'; +import { vestingAbi } from '@api/contracts'; +import { useWriteSQDTransaction } from '@api/contracts/useWriteTransaction'; +import { errorMessage } from '@api/contracts/utils'; import { SourceWallet } from '@api/subsquid-network-squid'; -import { BlockchainContractError } from '@components/BlockchainContractError'; +import { useSquidHeight } from '@hooks/useSquidNetworkHeightHooks'; +import { useContracts } from '@network/useContracts'; export const claimSchema = yup.object({ source: yup.string().label('Source').trim().required('Source is required'), @@ -19,29 +22,36 @@ export function ReleaseButton({ vesting: SourceWallet; disabled?: boolean; }) { - const { release, error, isLoading } = useVestingContractRelease(); - const { data, isLoading: isVestingLoading } = useVestingContract({ - address: vesting.id as `0x${string}`, - }); - const isDisabled = isVestingLoading || fromSqd(data?.releasable).lte(0); + const { setWaitHeight } = useSquidHeight(); + const { SQD } = useContracts(); + + const { writeTransactionAsync, isPending } = useWriteSQDTransaction({}); + + const onClick = async () => { + try { + const receipt = await writeTransactionAsync({ + abi: vestingAbi, + functionName: 'release', + args: [SQD], + address: vesting.id as `0x${string}`, + }); + setWaitHeight(receipt.blockNumber, []); + } catch (e: unknown) { + toast.error(errorMessage(e)); + } + }; return ( <> { - e.stopPropagation(); - await release({ - address: vesting.id as `0x${string}`, - }); - }} + loading={isPending} + onClick={onClick} variant="outlined" color="secondary" - disabled={disabled || isDisabled} + disabled={disabled} > RELEASE - ); } diff --git a/src/pages/AssetsPage/Vesting.tsx b/src/pages/AssetsPage/Vesting.tsx index 4e36114..7a956b1 100644 --- a/src/pages/AssetsPage/Vesting.tsx +++ b/src/pages/AssetsPage/Vesting.tsx @@ -1,11 +1,13 @@ import { dateFormat } from '@i18n'; import { addressFormatter, percentFormatter, tokenFormatter } from '@lib/formatters/formatters'; -import { fromSqd } from '@lib/network/utils'; -import { Divider, Stack, styled, useMediaQuery, useTheme } from '@mui/material'; +import { fromSqd, unwrapMulticallResult } from '@lib/network/utils'; +import { Divider, Stack, styled } from '@mui/material'; import { Box } from '@mui/system'; +import { keepPreviousData } from '@tanstack/react-query'; import { useParams, useSearchParams } from 'react-router-dom'; +import { useReadContracts } from 'wagmi'; -import { useVestingContract } from '@api/contracts/vesting'; +import { sqdAbi, vestingAbi } from '@api/contracts'; import { useVestingByAddress } from '@api/subsquid-network-squid'; import { Card } from '@components/Card'; import SquaredChip from '@components/Chip/SquaredChip'; @@ -45,13 +47,71 @@ export const Title = styled(SquaredChip)(({ theme }) => ({ })); export function Vesting({ backPath }: { backPath: string }) { - const theme = useTheme(); - const isMobile = useMediaQuery(theme.breakpoints.down('xs')); + const { SQD_TOKEN, SQD } = useContracts(); const { address } = useParams<{ address: `0x${string}` }>(); - const { data: vestingInfo, isLoading: isVestingInfoLoading } = useVestingContract({ address }); + + const vestingContract = { abi: vestingAbi, address } as const; + const { data: vestingInfo, isLoading: isVestingInfoLoading } = useReadContracts({ + contracts: [ + { + ...vestingContract, + functionName: 'start', + }, + { + ...vestingContract, + functionName: 'end', + }, + { + ...vestingContract, + functionName: 'depositedIntoProtocol', + }, + { + ...vestingContract, + functionName: 'releasable', + args: [SQD], + }, + { + ...vestingContract, + functionName: 'released', + args: [SQD], + }, + { + abi: sqdAbi, + address: SQD, + functionName: 'balanceOf', + args: [address || '0x'], + }, + { + ...vestingContract, + functionName: 'immediateReleaseBIP', + }, + { + ...vestingContract, + functionName: 'expectedTotalAmount', + }, + ] as const, + query: { + placeholderData: keepPreviousData, + select: res => { + if (res?.some(r => r.status === 'success')) { + return { + start: Number(unwrapMulticallResult(res[0])) * 1000, + end: Number(unwrapMulticallResult(res[1])) * 1000, + deposited: unwrapMulticallResult(res[2]), + releasable: unwrapMulticallResult(res[3]), + released: unwrapMulticallResult(res[4]), + balance: unwrapMulticallResult(res[5]), + initialRelease: Number(unwrapMulticallResult(res[6]) || 0) / 100, + expectedTotal: unwrapMulticallResult(res[7]), + }; + } + + return undefined; + }, + }, + }); const { data: vesting, isPending: isVestingLoading } = useVestingByAddress({ address }); - const { SQD_TOKEN } = useContracts(); const [searchParams] = useSearchParams(); @@ -94,9 +154,7 @@ export function Vesting({ backPath }: { backPath: string }) { Deposited - {vestingInfo?.deposited - ? tokenFormatter(fromSqd(vestingInfo?.deposited), SQD_TOKEN, 8) - : '-'} + {tokenFormatter(fromSqd(vestingInfo?.deposited), SQD_TOKEN, 8)} diff --git a/src/pages/AssetsPage/Vestings.tsx b/src/pages/AssetsPage/Vestings.tsx index 1afa54c..20520f1 100644 --- a/src/pages/AssetsPage/Vestings.tsx +++ b/src/pages/AssetsPage/Vestings.tsx @@ -1,24 +1,76 @@ import { tokenFormatter } from '@lib/formatters/formatters'; -import { fromSqd } from '@lib/network/utils'; +import { fromSqd, unwrapMulticallResult } from '@lib/network/utils'; import { Box, TableBody, TableCell, TableHead, TableRow } from '@mui/material'; +import { keepPreviousData } from '@tanstack/react-query'; +import chunk from 'lodash-es/chunk'; +import { erc20Abi } from 'viem'; +import { useReadContracts } from 'wagmi'; -import { useVestingContracts } from '@api/contracts/vesting'; -import { useMyAssets } from '@api/subsquid-network-squid'; +import { vestingAbi } from '@api/contracts'; +import { AccountType, useSourcesQuery, useSquid } from '@api/subsquid-network-squid'; import SquaredChip from '@components/Chip/SquaredChip'; -import { NoItems } from '@components/NoItems'; -import Placeholder from '@components/Placeholer'; -import { DashboardTable } from '@components/Table/DashboardTable'; +import { DashboardTable, NoItems } from '@components/Table'; +import { useAccount } from '@network/useAccount'; import { useContracts } from '@network/useContracts'; import { ReleaseButton } from './ReleaseButton'; import { SourceWalletName } from './VestingName'; export function MyVestings() { - const { assets, isLoading } = useMyAssets(); - const { data, isLoading: isVestingsLoading } = useVestingContracts({ - addresses: assets.vestings.map(v => v.id as `0x${string}`), + const account = useAccount(); + const squid = useSquid(); + + const { data: sourcesQuery, isLoading } = useSourcesQuery(squid, { + address: account.address as `0x${string}`, + }); + const { SQD_TOKEN, SQD } = useContracts(); + + const vestingsQuery = { + accounts: sourcesQuery?.accounts.filter(s => s.type === AccountType.Vesting), + }; + + const { data: vestings, isLoading: isVestingsLoading } = useReadContracts({ + contracts: vestingsQuery.accounts?.flatMap(s => { + if (s.type !== AccountType.Vesting) return []; + + const vestingContract = { abi: vestingAbi, address: s.id as `0x${string}` } as const; + return [ + { + ...vestingContract, + functionName: 'depositedIntoProtocol', + }, + { + ...vestingContract, + functionName: 'releasable', + args: [SQD], + }, + { + abi: erc20Abi, + address: SQD, + functionName: 'balanceOf', + args: [s.id as `0x${string}`], + }, + ] as const; + }), + allowFailure: true, + query: { + enabled: !!vestingsQuery.accounts?.length, + placeholderData: keepPreviousData, + select: res => { + if (res?.some(r => r.status === 'success')) { + return chunk(res, 3).map(ch => ({ + deposited: unwrapMulticallResult(ch[0]), + releasable: unwrapMulticallResult(ch[1]), + balance: unwrapMulticallResult(ch[2]), + })); + } else if (res?.length === 0) { + return []; + } + + return undefined; + }, + }, }); - const { SQD_TOKEN } = useContracts(); return ( - {assets.vestings.length ? ( + {vestingsQuery.accounts?.length ? ( <> - {assets.vestings.map((vesting, i) => { - const d = data?.[i]; + {vestingsQuery.accounts.map((vesting, i) => { + const d = vestings?.[i]; return ( @@ -49,7 +101,7 @@ export function MyVestings() { {tokenFormatter(fromSqd(d?.releasable), SQD_TOKEN)} - + @@ -57,9 +109,9 @@ export function MyVestings() { })} ) : ( - - - + + No vesting was found + )} diff --git a/src/pages/DashboardPage/Summary.tsx b/src/pages/DashboardPage/Summary.tsx index 7dbd075..d546a98 100644 --- a/src/pages/DashboardPage/Summary.tsx +++ b/src/pages/DashboardPage/Summary.tsx @@ -1,5 +1,6 @@ import React, { PropsWithChildren, useEffect, useMemo, useState } from 'react'; +import { relativeDateFormat } from '@i18n'; import { bytesFormatter, numberWithCommasFormatter, @@ -20,6 +21,7 @@ import { } from '@mui/material'; import Grid from '@mui/material/Unstable_Grid2'; import { AreaChart, Area, ResponsiveContainer, Tooltip, TooltipProps } from 'recharts'; +import { useDebounce } from 'use-debounce'; import { useNetworkSummary } from '@api/subsquid-network-squid'; import SquaredChip from '@components/Chip/SquaredChip'; @@ -109,32 +111,13 @@ function CurrentEpoch() { const { data, isLoading } = useNetworkSummary(); const [epochEnd, setEpochEnd] = useState(Date.now()); - const [curTime, setCurTime] = useState(Date.now()); - - const epochEndsIn = useMemo(() => { - const secondsLeft = Math.ceil(Math.max(epochEnd - curTime, 0) / 1000); - const { hours, minutes, seconds } = { - hours: Math.floor((secondsLeft % (60 * 60 * 24)) / (60 * 60)), - minutes: Math.floor((secondsLeft % (60 * 60)) / 60), - seconds: Math.floor(secondsLeft % 60), - }; - - let res = ''; - if (hours) { - res += `${hours}h `; - } - if (minutes) { - res += `${minutes}m `; - } - if (!hours) { - res += `${seconds}s`; - } - - return res; - }, [curTime, epochEnd]); + + const [curTime] = useDebounce(Date.now(), 1000); + + const epochEndsIn = useMemo(() => relativeDateFormat(curTime, epochEnd), [curTime, epochEnd]); useEffect(() => { - if (!data) return; + if (!data || !data.epoch) return; const newEpochEnd = (data.epoch.end - data.lastBlockL1 + 1) * data.blockTimeL1 + @@ -143,14 +126,6 @@ function CurrentEpoch() { setEpochEnd(newEpochEnd); }, [data, epochEnd]); - useEffect(() => { - const interval = setInterval(() => { - setCurTime(Date.now()); - }, 1000); - - return () => clearInterval(interval); - }, []); - return ( } > - - {data?.epoch?.number || 0} - + {data?.epoch?.number || 0} ); } @@ -304,10 +277,9 @@ function WorkersApr({ length }: { length?: number }) { sx={{ height: 1, overflow: 'visible' }} title={} action={ - - {`Last ${aprs.length} days`} - - + + {`Last ${aprs.length} days`} + } > diff --git a/src/pages/DashboardPage/Workers.tsx b/src/pages/DashboardPage/Workers.tsx index 1e0dc6f..64c8cf4 100644 --- a/src/pages/DashboardPage/Workers.tsx +++ b/src/pages/DashboardPage/Workers.tsx @@ -14,17 +14,14 @@ import { } from '@mui/material'; import { Box } from '@mui/system'; -import { SortDir, useWorkers, WorkerSortBy } from '@api/subsquid-network-squid'; -import { NoItems } from '@components/NoItems'; -import Placeholder from '@components/Placeholer'; +import { SortDir, useMySources, useWorkers, WorkerSortBy } from '@api/subsquid-network-squid'; import { Search } from '@components/Search/Search'; -import { SortableHeaderCell } from '@components/Table/BorderedTable'; -import { DashboardTable } from '@components/Table/DashboardTable'; +import { DashboardTable, SortableHeaderCell, NoItems } from '@components/Table'; import { Location, useLocationState } from '@hooks/useLocationState'; import { DelegationCapacity } from '@pages/WorkersPage/DelegationCapacity'; import { WorkerDelegate } from '@pages/WorkersPage/WorkerDelegate'; import { WorkerName } from '@pages/WorkersPage/WorkerName'; -import { WorkerStatus } from '@pages/WorkersPage/WorkerStatus'; +import { WorkerStatusChip } from '@pages/WorkersPage/WorkerStatus'; import { WorkerVersion } from '@pages/WorkersPage/WorkerVersion'; function TableNavigation({ @@ -48,6 +45,7 @@ function TableNavigation({ justifyContent="flex-end" > { setPage?.(page - 1); }} @@ -59,6 +57,7 @@ function TableNavigation({ {page} / {totalPages} { setPage?.(page + 1); }} @@ -93,7 +92,15 @@ export function Workers() { sortBy: new Location.Enum(WorkerSortBy.StakerAPR), sortDir: new Location.Enum(SortDir.Desc), }); - const { workers, totalPages, page, isLoading } = useWorkers({ + + const { data: sources, isLoading: isSourcesLoading } = useMySources(); + + const { + workers, + totalPages, + page, + isLoading: isWorkersLoading, + } = useWorkers({ search: query.search, page: query.page, perPage: 15, @@ -101,6 +108,8 @@ export function Workers() { sortDir: query.sortDir as SortDir, }); + const isLoading = isSourcesLoading || isWorkersLoading; + return ( - Registered + Created @@ -171,7 +180,7 @@ export function Workers() { /> - + @@ -188,20 +197,18 @@ export function Workers() { {dateFormat(worker.createdAt)} - + ); }) ) : ( - - - + )} - {isLoading ? null : ( + {isWorkersLoading ? null : ( )} diff --git a/src/pages/DelegationsPage/DelegationsPage.tsx b/src/pages/DelegationsPage/DelegationsPage.tsx index 29bb374..3eb83cb 100644 --- a/src/pages/DelegationsPage/DelegationsPage.tsx +++ b/src/pages/DelegationsPage/DelegationsPage.tsx @@ -3,12 +3,9 @@ import { fromSqd } from '@lib/network'; import { Box, Stack, TableBody, TableCell, TableHead, TableRow } from '@mui/material'; import { Outlet } from 'react-router-dom'; -import { SortDir, useMyDelegations, WorkerSortBy } from '@api/subsquid-network-squid'; +import { SortDir, useMyDelegations, useMySources, WorkerSortBy } from '@api/subsquid-network-squid'; import SquaredChip from '@components/Chip/SquaredChip'; -import { NoItems } from '@components/NoItems'; -import Placeholder from '@components/Placeholer'; -import { SortableHeaderCell } from '@components/Table/BorderedTable'; -import { DashboardTable } from '@components/Table/DashboardTable'; +import { DashboardTable, NoItems, SortableHeaderCell } from '@components/Table'; import { Location, useLocationState } from '@hooks/useLocationState'; import { CenteredPageWrapper } from '@layouts/NetworkLayout'; import { ConnectedWalletRequired } from '@network/ConnectedWalletRequired'; @@ -16,7 +13,7 @@ import { useContracts } from '@network/useContracts'; import { DelegationCapacity } from '@pages/WorkersPage/DelegationCapacity'; import { WorkerDelegate } from '@pages/WorkersPage/WorkerDelegate'; import { WorkerName } from '@pages/WorkersPage/WorkerName'; -import { WorkerStatus } from '@pages/WorkersPage/WorkerStatus'; +import { WorkerStatusChip } from '@pages/WorkersPage/WorkerStatus'; import { WorkerUndelegate } from '@pages/WorkersPage/WorkerUndelegate'; export function MyDelegations() { @@ -24,30 +21,16 @@ export function MyDelegations() { sortBy: new Location.Enum(WorkerSortBy.MyDelegationReward), sortDir: new Location.Enum(SortDir.Desc), }); - const { workers: delegations, isLoading } = useMyDelegations({ + + const { data: delegations, isLoading: isDelegationsLoading } = useMyDelegations({ sortBy: query.sortBy as WorkerSortBy, sortDir: query.sortDir as SortDir, }); + const { data: sources, isLoading: isSourcesLoading } = useMySources({}); + const { SQD_TOKEN } = useContracts(); - // const groupedDelegations = useMemo(() => { - // return mapValues( - // keyBy(delegations, w => w.id), - // w => { - // return w.delegations.reduce( - // (s, d) => { - // s.deposit = s.deposit.plus(d.deposit); - // s.reward = s.reward.plus(d.claimedReward).plus(d.claimableReward); - // return s; - // }, - // { - // deposit: new BigNumber(0), - // reward: new BigNumber(0), - // }, - // ); - // }, - // ); - // }, [delegations]); + const isLoading = isDelegationsLoading || isSourcesLoading; return ( @@ -98,7 +81,7 @@ export function MyDelegations() { - + {worker.stakerApr != null ? percentFormatter(worker.stakerApr) : '-'} @@ -111,18 +94,26 @@ export function MyDelegations() { {tokenFormatter(fromSqd(worker.myTotalDelegationReward), SQD_TOKEN)} - - - + + + ({ + id: d.owner.id, + type: d.owner.type, + balance: d.deposit, + locked: d.locked || false, + unlockedAt: d.unlockedAt || '', + }))} + disabled={!worker.delegations.some(d => !d.locked)} + /> ); }) ) : ( - - - + )} diff --git a/src/pages/GatewaysPage/AddNewGateway.tsx b/src/pages/GatewaysPage/AddNewGateway.tsx index 85289c7..2f68e00 100644 --- a/src/pages/GatewaysPage/AddNewGateway.tsx +++ b/src/pages/GatewaysPage/AddNewGateway.tsx @@ -1,30 +1,82 @@ -import React, { useEffect } from 'react'; +import React, { useMemo, useState } from 'react'; +import { peerIdToHex } from '@lib/network'; +import { Add } from '@mui/icons-material'; import { LoadingButton } from '@mui/lab'; -import { Box } from '@mui/material'; +import { Alert, SxProps } from '@mui/material'; import { useFormik } from 'formik'; -import { useNavigate } from 'react-router-dom'; - -import { useRegisterGateway } from '@api/contracts/gateway-registration/useRegisterGateway'; -import { useMySources } from '@api/subsquid-network-squid'; -import { BlockchainContractError } from '@components/BlockchainContractError'; -import { Card } from '@components/Card'; +import toast from 'react-hot-toast'; +import { useClient } from 'wagmi'; + +import { gatewayRegistryAbi } from '@api/contracts'; +import { encodeGatewayMetadata } from '@api/contracts/gateway-registration/GatewayMetadata'; +import { useWriteSQDTransaction } from '@api/contracts/useWriteTransaction'; +import { errorMessage } from '@api/contracts/utils'; +import { AccountType, SourceWalletWithBalance } from '@api/subsquid-network-squid'; +import { ContractCallDialog } from '@components/ContractCallDialog'; import { Form, FormikSwitch, FormikTextInput, FormRow } from '@components/Form'; import { FormikSelect } from '@components/Form/FormikSelect'; +import { HelpTooltip } from '@components/HelpTooltip'; import { Loader } from '@components/Loader'; import { SourceWalletOption } from '@components/SourceWallet'; -import { CenteredPageWrapper, NetworkPageTitle } from '@layouts/NetworkLayout'; -import { ConnectedWalletRequired } from '@network/ConnectedWalletRequired'; +import { useSquidHeight } from '@hooks/useSquidNetworkHeightHooks'; +import { useContracts } from '@network/useContracts'; import { addGatewaySchema } from './gateway-schema'; -function AddGatewayForm() { - const navigate = useNavigate(); - const { registerGateway, isLoading: isRegistering, error } = useRegisterGateway(); - const { sources, isPending: isDataLoading } = useMySources(); +export function AddGatewayButton({ + sx, + disabled, + sources, +}: { + sx?: SxProps; + disabled?: boolean; + sources?: SourceWalletWithBalance[]; +}) { + const [open, setOpen] = useState(false); - const formik = useFormik({ - initialValues: { + return ( + <> + } + variant="contained" + onClick={() => setOpen(true)} + > + ADD PORTAL + + setOpen(false)} sources={sources} /> + + ); +} + +export function AddGatewayDialog({ + open, + onClose, + sources, +}: { + open: boolean; + onClose: () => void; + sources?: SourceWalletWithBalance[]; +}) { + const client = useClient(); + + const contracts = useContracts(); + + const { writeTransactionAsync, isPending } = useWriteSQDTransaction(); + const { setWaitHeight } = useSquidHeight(); + + const isSourceDisabled = (source: SourceWalletWithBalance) => source.type === AccountType.Vesting; + const hasAvailableSource = useMemo(() => !!sources?.some(s => !isSourceDisabled(s)), [sources]); + + const initialValues = useMemo(() => { + const source = sources?.find(s => !isSourceDisabled(s)) || sources?.[0]; + + return { + source: source?.id || '0x', name: '', description: '', website: '', @@ -32,140 +84,134 @@ function AddGatewayForm() { email: '', peerId: '', endpointUrl: '', - source: '', - }, + }; + }, [sources]); + + const formik = useFormik({ + initialValues, validationSchema: addGatewaySchema, validateOnChange: true, validateOnBlur: true, validateOnMount: true, + enableReinitialize: true, onSubmit: async values => { - const source = sources.find(s => s.id === values.source); + if (!client) return; + + const source = sources?.find(s => s.id === values.source); if (!source) return; - const castedValues = addGatewaySchema.cast(values); - if (!castedValues.public) { - delete castedValues.email; + try { + const castedValues = addGatewaySchema.cast(values); + if (!castedValues.public) { + delete castedValues.email; + } + + const receipt = await writeTransactionAsync({ + address: contracts.GATEWAY_REGISTRATION, + abi: gatewayRegistryAbi, + functionName: 'register', + args: [peerIdToHex(castedValues.peerId), encodeGatewayMetadata(castedValues)], + }); + setWaitHeight(receipt.blockNumber, []); + + onClose(); + } catch (e: unknown) { + toast.custom({errorMessage(e)}); } - - const { success } = await registerGateway({ - ...castedValues, - source, - }); - if (!success) return; - - navigate('/gateways'); }, }); - useEffect(() => { - if (isDataLoading) return; - else if (formik.values.source) return; - - const contract = sources[0]; - if (!contract) return; - - formik.setValues({ - ...formik.values, - source: contract.id, - }); - }, [formik, isDataLoading, sources]); - return ( - <> - {isDataLoading ? ( + { + if (!confirmed) return onClose(); + + formik.handleSubmit(); + }} + loading={isPending} + disableConfirmButton={!hasAvailableSource} + > + {!sources ? ( ) : (
- - - { - return { - label: , - value: s.id, - }; - })} - formik={formik} - /> - - - - - - - - - - - - - {formik.values.public ? ( - <> - - - - - - - - - - - - - - ) : null} - - - - - - Register - - + + { + return { + label: , + value: s.id, + disabled: isSourceDisabled(s), + }; + })} + formik={formik} + /> + + + + + + + Peer ID + + } + formik={formik} + /> + + + + + + + {formik.values.public ? ( + <> + + + + + + + + + + + + + + ) : null}
)} - - ); -} - -export function AddNewGateway() { - return ( - - - - - - +
); } diff --git a/src/pages/GatewaysPage/AutoExtension.tsx b/src/pages/GatewaysPage/AutoExtension.tsx new file mode 100644 index 0000000..34d0a11 --- /dev/null +++ b/src/pages/GatewaysPage/AutoExtension.tsx @@ -0,0 +1,54 @@ +import { Box, FormControlLabel, FormGroup, Switch, Typography } from '@mui/material'; +import toast from 'react-hot-toast'; +import { usePublicClient } from 'wagmi'; + +import { gatewayRegistryAbi } from '@api/contracts'; +import { useWriteSQDTransaction } from '@api/contracts/useWriteTransaction'; +import { errorMessage } from '@api/contracts/utils'; +import { useSquidHeight } from '@hooks/useSquidNetworkHeightHooks'; +import { useContracts } from '@network/useContracts'; + +export function AutoExtension({ value, disabled }: { value?: boolean; disabled?: boolean }) { + const client = usePublicClient(); + const { setWaitHeight } = useSquidHeight(); + const contracts = useContracts(); + const { writeTransactionAsync } = useWriteSQDTransaction({}); + + const handleChange = async () => { + if (!client) return; + + try { + const receipt = value + ? await writeTransactionAsync({ + address: contracts.GATEWAY_REGISTRATION, + abi: gatewayRegistryAbi, + functionName: 'disableAutoExtension', + args: [], + }) + : await writeTransactionAsync({ + address: contracts.GATEWAY_REGISTRATION, + abi: gatewayRegistryAbi, + functionName: 'enableAutoExtension', + args: [], + }); + + setWaitHeight(receipt.blockNumber, []); + } catch (e) { + toast.error(errorMessage(e)); + } + }; + + return ( + + + } + label={Auto Extension} + labelPlacement="start" + /> + + + ); +} diff --git a/src/pages/GatewaysPage/Gateway.tsx b/src/pages/GatewaysPage/Gateway.tsx index 64cd288..44857ff 100644 --- a/src/pages/GatewaysPage/Gateway.tsx +++ b/src/pages/GatewaysPage/Gateway.tsx @@ -1,5 +1,3 @@ -import React from 'react'; - import { dateFormat } from '@i18n'; import { urlFormatter } from '@lib/formatters/formatters'; import { Divider, Stack, styled } from '@mui/material'; @@ -13,9 +11,10 @@ import { CopyToClipboard } from '@components/CopyToClipboard'; import { Loader } from '@components/Loader'; import { NotFound } from '@components/NotFound'; import { CenteredPageWrapper } from '@layouts/NetworkLayout'; +import { Title } from '@pages/WorkersPage/Worker'; import { GatewayCard } from './GatewayCard'; -import { GatewayUnregister } from './GatewayUnregister'; +import { GatewayUnregisterButton } from './GatewayUnregister'; export const DescLabel = styled(Box, { name: 'DescLabel', @@ -51,53 +50,56 @@ export const Gateway = ({ backPath }: { backPath: string }) => { - - - }> - - - - Registered - {dateFormat(gateway.createdAt, 'dateTime')} - - - Endpoint URL - - {gateway.endpointUrl ? ( - - ) : ( - '-' - )} - - - - Website - - {gateway.website ? ( - - {urlFormatter(gateway.website)} - - ) : ( - '-' - )} - - - - Contact - - {gateway.email ? : '-'} - - - - Description - {gateway.description || '-'} + + }> + + }> + + + <Stack spacing={2} direction="column"> + <Stack direction="row"> + <DescLabel>Registered</DescLabel> + <DescValue>{dateFormat(gateway.createdAt, 'dateTime')}</DescValue> + </Stack> + <Stack direction="row"> + <DescLabel>Endpoint URL</DescLabel> + <DescValue> + {gateway.endpointUrl ? ( + <CopyToClipboard text={urlFormatter(gateway.endpointUrl)} /> + ) : ( + '-' + )} + </DescValue> + </Stack> + <Stack direction="row"> + <DescLabel>Website</DescLabel> + <DescValue> + {gateway.website ? ( + <a href={urlFormatter(gateway.website)} target="_blank" rel="noreferrer"> + {urlFormatter(gateway.website)} + </a> + ) : ( + '-' + )} + </DescValue> + </Stack> + <Stack direction="row"> + <DescLabel>Contact</DescLabel> + <DescValue> + {gateway.email ? <CopyToClipboard text={gateway.email} /> : '-'} + </DescValue> + </Stack> + <Stack direction="row"> + <DescLabel>Description</DescLabel> + <DescValue>{gateway.description || '-'}</DescValue> + </Stack> </Stack> - </Stack> - </Box> + </Box> + </Stack> </Stack> </Card> - <Box mt={2.5} display="flex" justifyContent="flex-end"> - <GatewayUnregister gateway={gateway} /> + <Box mt={3} display="flex" justifyContent="flex-end"> + <GatewayUnregisterButton gateway={gateway} /> </Box> </CenteredPageWrapper> ); diff --git a/src/pages/GatewaysPage/GatewayCard.tsx b/src/pages/GatewaysPage/GatewayCard.tsx index b7f44a9..2934889 100644 --- a/src/pages/GatewaysPage/GatewayCard.tsx +++ b/src/pages/GatewaysPage/GatewayCard.tsx @@ -1,17 +1,14 @@ import React from 'react'; -import { IconButton, Stack, styled, useMediaQuery, useTheme } from '@mui/material'; +import { IconButton, Stack, styled, Typography, useTheme } from '@mui/material'; import { Box } from '@mui/system'; import { Link } from 'react-router-dom'; -import { BlockchainGateway } from '@api/subsquid-network-squid/gateways-graphql'; +import { GatewayFragmentFragment } from '@api/subsquid-network-squid'; import { Avatar } from '@components/Avatar'; import { CopyToClipboard } from '@components/CopyToClipboard'; -import { shortPeerId } from '@components/PeerId'; import { EditIcon } from '@icons/EditIcon'; -import { GatewayStatus } from './GatewayStatus'; - export const PeerIdRow = styled(Box, { name: 'PeerIdRow', })(({ theme }) => ({ @@ -29,43 +26,63 @@ export const GatewayDescription = styled(Box, { lineHeight: 1.8, })); -function GatewayTitle({ gateway }: { gateway: BlockchainGateway }) { +function GatewayTitle({ + gateway, + canEdit, +}: { + gateway: GatewayFragmentFragment; + canEdit: boolean; +}) { + const theme = useTheme(); + return ( - <Box sx={{ display: 'flex', alignItems: 'center' }}> - <Box sx={{ fontSize: '1.5rem', lineHeight: 1.4, overflowWrap: 'anywhere' }}> - {gateway.name || gateway.id} - </Box> - {gateway.ownedByMe ? ( - <IconButton component={Link} to={`/gateways/${gateway.id}/edit`}> - <EditIcon size={18} color="#1D1D1F" /> - </IconButton> - ) : null} - </Box> + <Stack spacing={0.5}> + <Stack direction="row" alignItems="center" spacing={0.5}> + <Typography variant="h4" sx={{ overflowWrap: 'anywhere' }}> + {gateway.name || gateway.id} + </Typography> + {canEdit ? ( + <IconButton component={Link} to={`/portals/${gateway.id}/edit`} sx={{ padding: 0.5 }}> + <EditIcon /> + </IconButton> + ) : null} + </Stack> + <CopyToClipboard + text={gateway.id} + content={ + <Typography + variant="body2" + sx={{ overflowWrap: 'anywhere', color: theme.palette.text.secondary }} + > + {gateway.id} + </Typography> + } + /> + </Stack> ); } -export const GatewayCard = ({ gateway }: { gateway: BlockchainGateway }) => { - const theme = useTheme(); - const isMobile = useMediaQuery(theme.breakpoints.down('xs')); - +export const GatewayCard = ({ + gateway, + canEdit, +}: { + gateway: GatewayFragmentFragment; + canEdit: boolean; +}) => { return ( - <Stack spacing={3} direction="row"> - <Avatar - variant="circular" - name={gateway.name || gateway.id} - colorDiscriminator={gateway.id} - size={100} - /> - <Box sx={{ flex: 1 }}> - <GatewayTitle gateway={gateway} /> - <PeerIdRow> - <CopyToClipboard - text={gateway.id} - content={isMobile ? shortPeerId(gateway.id) : gateway.id} - /> - </PeerIdRow> - <GatewayStatus gateway={gateway} /> - </Box> + <Stack direction="row" justifyContent="space-between"> + <Stack spacing={2} direction="row" alignItems="center"> + <Avatar + variant="circular" + name={gateway.name || gateway.id} + colorDiscriminator={gateway.id} + size={56} + /> + {/* <Stack justifyContent="stretch" flex={1} spacing={0.125}> */} + <GatewayTitle gateway={gateway} canEdit={canEdit} /> + + {/* </Stack> */} + </Stack> </Stack> ); }; diff --git a/src/pages/GatewaysPage/GatewayName.tsx b/src/pages/GatewaysPage/GatewayName.tsx index efd3e69..1796992 100644 --- a/src/pages/GatewaysPage/GatewayName.tsx +++ b/src/pages/GatewaysPage/GatewayName.tsx @@ -1,10 +1,10 @@ import React from 'react'; -import { Stack, styled } from '@mui/material'; +import { Stack, styled, Typography } from '@mui/material'; import { Box } from '@mui/system'; import { Link } from 'react-router-dom'; -import { BlockchainGateway } from '@api/subsquid-network-squid/gateways-graphql'; +import { Gateway } from '@api/subsquid-network-squid'; import { Avatar } from '@components/Avatar'; import { CopyToClipboard } from '@components/CopyToClipboard'; import { PeerIdShort, shortPeerId } from '@components/PeerId'; @@ -16,24 +16,35 @@ const Name = styled(Box, { fontWeight: 500, })); -export const GatewayName = ({ gateway, to }: { gateway: BlockchainGateway; to?: string }) => { +export const GatewayName = ({ + gateway, + to, +}: { + gateway: Pick<Gateway, 'name' | 'id'>; + to?: string; +}) => { return ( - <Stack spacing={2} direction="row"> - <Avatar colorDiscriminator={gateway.id} name={gateway.name || gateway.id} /> - <Box> - {gateway.name ? <Name>{gateway.name}</Name> : null} - <Stack direction="row" spacing={1}> - <Box> - <CopyToClipboard - text={gateway.id} - content={ - <PeerIdShort> - <Link to={to || ''}>{shortPeerId(gateway.id)}</Link> - </PeerIdShort> - } - /> - </Box> - </Stack> + <Stack spacing={1.5} direction="row" alignItems="center"> + <Avatar + // online={!!gateway.online} + name={gateway.name || gateway.id} + colorDiscriminator={gateway.id} + /> + <Box overflow="clip"> + {gateway.name ? ( + <Name>{gateway.name.length > 30 ? gateway.name.slice(0, 27) + '...' : gateway.name}</Name> + ) : null} + <Typography variant="caption"> + <CopyToClipboard + text={gateway.id} + content={ + <PeerIdShort> + <Link to={to || '#'}>{shortPeerId(gateway.id)}</Link> + </PeerIdShort> + } + /> + </Typography> + {/* <WorkerDelegationCapacity gateway={gateway} /> */} </Box> </Stack> ); diff --git a/src/pages/GatewaysPage/GatewayStake.tsx b/src/pages/GatewaysPage/GatewayStake.tsx index b34b52e..29392c4 100644 --- a/src/pages/GatewaysPage/GatewayStake.tsx +++ b/src/pages/GatewaysPage/GatewayStake.tsx @@ -1,21 +1,40 @@ -import React, { useEffect, useMemo, useState } from 'react'; +import { useMemo, useState } from 'react'; import { dateFormat } from '@i18n'; -import { fromSqd, toSqd } from '@lib/network/utils'; -import { Box, Button, Chip, Stack } from '@mui/material'; +import { numberWithCommasFormatter, tokenFormatter } from '@lib/formatters/formatters'; +import { fromSqd, getBlockTime, toSqd, unwrapMulticallResult } from '@lib/network/utils'; +import { LockOutlined as LockIcon } from '@mui/icons-material'; +import { LoadingButton } from '@mui/lab'; +import { Box, Chip, InputAdornment, Stack, SxProps } from '@mui/material'; import * as yup from '@schema'; +import BigNumber from 'bignumber.js'; import { useFormik } from 'formik'; +import toast from 'react-hot-toast'; import { useDebounce } from 'use-debounce'; +import { useBlock, useReadContracts } from 'wagmi'; -import { useComputationUnits } from '@api/contracts/gateway-registration/useComputationUnits'; -import { useStakeGateway } from '@api/contracts/gateway-registration/useStakeGateway'; -import { useMyGatewayStakes } from '@api/subsquid-network-squid/gateways-graphql'; -import { BlockchainContractError } from '@components/BlockchainContractError'; +import { + gatewayRegistryAbi, + useReadNetworkControllerWorkerEpochLength, + useReadRouterNetworkController, +} from '@api/contracts'; +import { useWriteSQDTransaction } from '@api/contracts/useWriteTransaction'; +import { errorMessage } from '@api/contracts/utils'; +import { AccountType, SourceWalletWithBalance } from '@api/subsquid-network-squid'; import { ContractCallDialog } from '@components/ContractCallDialog'; -import { Form, FormikSelect, FormikSwitch, FormikTextInput, FormRow } from '@components/Form'; +import { Form, FormDivider, FormikSelect, FormikTextInput, FormRow } from '@components/Form'; import { HelpTooltip } from '@components/HelpTooltip'; import { Loader } from '@components/Loader'; -import { useMySourceOptions } from '@components/SourceWallet/useMySourceOptions'; +import { SourceWalletOption } from '@components/SourceWallet'; +import { useSquidHeight } from '@hooks/useSquidNetworkHeightHooks'; +import { useContracts } from '@network/useContracts'; + +export type SourceWalletWithStake = SourceWalletWithBalance & { + stake: { + amount: bigint; + duration: bigint; + }; +}; const MIN_BLOCKS_LOCK = 1000; @@ -37,181 +56,297 @@ export const stakeSchema = yup.object({ .required('Lock min blocks is required'), }); -export function GatewayStake({ disabled }: { disabled?: boolean }) { - const { data } = useMyGatewayStakes(); - const { stakeToGateway, error, isLoading } = useStakeGateway(); - +export function GatewayStakeButton({ + sx, + disabled, + sources, +}: { + sx?: SxProps; + disabled?: boolean; + sources?: SourceWalletWithStake[]; +}) { const [open, setOpen] = useState(false); - const handleOpen = () => setOpen(true); - const handleClose = () => setOpen(false); - - const { - sources, - options, - isPending: isSourceLoading, - } = useMySourceOptions({ - sourceDisabled: s => s.balance === '0' || !!data?.operators.some(o => o.account.id === s.id), + + return ( + <> + <LoadingButton + loading={open} + startIcon={<LockIcon />} + onClick={() => setOpen(true)} + variant="contained" + color="info" + disabled={disabled} + sx={sx} + > + LOCK + </LoadingButton> + <GatewayStakeDialog open={open} onClose={() => setOpen(false)} sources={sources} /> + </> + ); +} + +export function GatewayStakeDialog({ + open, + onClose, + sources, +}: { + open: boolean; + onClose: () => void; + sources?: SourceWalletWithStake[]; +}) { + const { setWaitHeight } = useSquidHeight(); + + const { GATEWAY_REGISTRATION, ROUTER, CHAIN_ID_L1 } = useContracts(); + + const networkController = useReadRouterNetworkController({ + address: ROUTER, + }); + const workerEpochLength = useReadNetworkControllerWorkerEpochLength({ + address: networkController.data, }); - const formik = useFormik({ - initialValues: { - source: '', + // const myGatewaysStake = useMyGatewayStake(); + const gatewayRegistryContract = useWriteSQDTransaction(); + + const { data: lastL1Block, isLoading: isLastL1BlockLoading } = useBlock({ + chainId: CHAIN_ID_L1, + }); + + const isLoading = isLastL1BlockLoading; + + const isSourceDisabled = (source: SourceWalletWithBalance) => + source.balance === '0' || source.type === AccountType.Vesting; + const hasAvailableSource = useMemo(() => !!sources?.some(s => !isSourceDisabled(s)), [sources]); + + const initialValues = useMemo(() => { + const source = sources?.find(s => !isSourceDisabled(s)) || sources?.[0]; + + return { + source: source?.id || '0x', amount: '0', - max: '0', - autoExtension: false, - durationBlocks: MIN_BLOCKS_LOCK.toString(), - }, + max: fromSqd(source?.balance)?.toString() || '0', + durationBlocks: (source?.stake.duration || MIN_BLOCKS_LOCK).toString(), + }; + }, [sources]); + + const formik = useFormik({ + initialValues, validationSchema: stakeSchema, validateOnChange: true, validateOnBlur: true, validateOnMount: true, + enableReinitialize: true, onSubmit: async values => { - const wallet = sources.find(w => w?.id === values.source); - if (!wallet) return; - - const { failedReason } = await stakeToGateway({ - amount: toSqd(values.amount), - durationBlocks: Number(values.durationBlocks), - autoExtension: values.autoExtension, - wallet, - }); - - if (!failedReason) { - handleClose(); + try { + const { amount, durationBlocks, source: sourceId } = stakeSchema.cast(values); + + const source = sources?.find(s => s.id === sourceId); + if (!source) return; + + const sqdAmount = BigInt(toSqd(amount)); + + const functionData = { + abi: gatewayRegistryAbi, + address: GATEWAY_REGISTRATION, + approve: sqdAmount, + }; + + const receipt = await gatewayRegistryContract.writeTransactionAsync( + source.stake.amount > 0n + ? { + ...functionData, + functionName: 'addStake', + args: [sqdAmount], + } + : { + ...functionData, + functionName: 'stake', + args: [sqdAmount, BigInt(durationBlocks), false], + }, + ); + setWaitHeight(receipt.blockNumber, []); + + onClose(); + } catch (e: unknown) { + toast.error(errorMessage(e)); } }, }); - const source = useMemo(() => { - if (isSourceLoading) return; + const selectedSource = useMemo( + () => sources?.find(s => s.id === formik.values.source), + [sources, formik.values.source], + ); + const [debouncedValues] = useDebounce(stakeSchema.cast(formik.values), 500); - return ( - (formik.values.source - ? sources.find(c => c.id === formik.values.source) - : sources.find(c => fromSqd(c.balance).gte(0))) || sources?.[0] - ); - }, [formik.values.source, isSourceLoading, sources]); - - useEffect(() => { - if (!source) return; - - formik.setValues({ - ...formik.values, - source: source.id, - max: fromSqd(source.balance).toFixed(), - }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [source]); - - const [lockDuration] = useDebounce(formik.values.durationBlocks, 500); - const [amount] = useDebounce(formik.values.amount, 500); - const unlockAt = useMemo(() => { - if (!data) return Date.now(); - return ( - (Number(lockDuration) + 1) * data.blockTimeL1 + new Date(data.lastBlockTimestampL1).getTime() - ); - }, [data, lockDuration]); - const { data: computationUnits, isPending: isComputationUnitsLoading } = useComputationUnits({ - amount: toSqd(amount), - lockDuration: Number(lockDuration), + const newContractValues = useReadContracts({ + contracts: [ + { + address: GATEWAY_REGISTRATION, + abi: gatewayRegistryAbi, + functionName: 'computationUnitsAmount', + args: [ + (selectedSource?.stake.amount || 0n) + BigInt(toSqd(debouncedValues.amount)), + BigInt(debouncedValues.durationBlocks), + ], + }, + ], + query: { + select: res => { + if (!res) return; + + return { + computationUnitsAmount: unwrapMulticallResult(res[0]) || 0n, + }; + }, + }, }); + const preview = useMemo(() => { + if (!newContractValues.data || !lastL1Block || !selectedSource) return; + + const workerEpochLengthValue = workerEpochLength.data || 0n; + + const epochCount = Math.ceil(debouncedValues.durationBlocks / Number(workerEpochLengthValue)); + + const cuPerEpoch = Number( + epochCount <= 1 + ? newContractValues.data.computationUnitsAmount + : (newContractValues.data.computationUnitsAmount * workerEpochLengthValue) / + BigInt(debouncedValues.durationBlocks), + ); + + const unlockAt = + Number(lastL1Block.timestamp) * 1000 + getBlockTime(debouncedValues.durationBlocks); + + const totalAmount = new BigNumber(selectedSource.stake.amount.toString()) + .plus(toSqd(debouncedValues.amount)) + .toString(); + + return { + epochCount, + cuPerEpoch, + unlockAt, + totalAmount, + }; + }, [ + debouncedValues, + lastL1Block, + newContractValues.data, + selectedSource, + workerEpochLength.data, + ]); + return ( - <> - <Button - onClick={handleOpen} - variant="contained" - disabled={disabled || data?.operators.length === sources.length} - > - Add lock - </Button> - <ContractCallDialog - title="Lock" - open={open} - onResult={confirmed => { - if (!confirmed) return handleClose(); - - formik.handleSubmit(); - }} - loading={isLoading} - confirmButtonText="Lock" - > - {isSourceLoading ? ( - <Loader /> - ) : ( - <Form onSubmit={formik.handleSubmit}> - <FormRow> - <FormikSelect - id="source" - showErrorOnlyOfTouched - options={options} - formik={formik} - onChange={e => { - const wallet = sources.find(w => w?.id === e.target.value); - if (!wallet) return; - - formik.setFieldValue('source', wallet.id); - - const balance = fromSqd(wallet.balance).toNumber(); - formik.setFieldValue('max', balance); - }} - /> - </FormRow> - <FormRow> - <FormikTextInput - id="amount" - label="Amount" - formik={formik} - showErrorOnlyOfTouched - autoComplete="off" - InputProps={{ - endAdornment: ( + <ContractCallDialog + title="Lock" + open={open} + onResult={confirmed => { + if (!confirmed) return onClose(); + + formik.handleSubmit(); + }} + disableConfirmButton={!formik.isValid || isLoading || !hasAvailableSource} + loading={gatewayRegistryContract.isPending} + > + {isLoading ? ( + <Loader /> + ) : ( + <Form onSubmit={formik.handleSubmit}> + <FormRow> + <FormikSelect + id="source" + showErrorOnlyOfTouched + options={ + sources?.map(source => { + return { + label: <SourceWalletOption source={source} />, + value: source.id, + disabled: source.balance === '0' || source.type !== AccountType.User, + max: fromSqd(source.balance).toString(), + }; + }) || [] + } + formik={formik} + onChange={e => { + const wallet = sources?.find(w => w?.id === e.target.value); + if (!wallet) return; + + formik.setFieldValue('source', wallet.id); + formik.setFieldValue('max', fromSqd(wallet.balance).toString()); + }} + /> + </FormRow> + <FormRow> + <FormikTextInput + id="amount" + label="Amount" + formik={formik} + showErrorOnlyOfTouched + autoComplete="off" + InputProps={{ + endAdornment: ( + <InputAdornment position="end"> <Chip clickable disabled={formik.values.max === formik.values.amount} onClick={() => { formik.setValues({ ...formik.values, - amount: formik.values.max, + amount: formik.values.max || '0', }); }} label="Max" /> - ), - }} - /> - </FormRow> - <FormRow> - <FormikTextInput - id="durationBlocks" - label="Lock blocks duration" - formik={formik} - showErrorOnlyOfTouched - autoComplete="off" - /> - </FormRow> - <FormRow> - <FormikSwitch id="autoExtension" label="Auto extension" formik={formik} /> - </FormRow> - <Stack spacing={2}> - <Stack direction="row" justifyContent="space-between" alignContent="center"> - <Box>Unlock at</Box> - <Stack direction="row"> - ~{dateFormat(unlockAt, 'dateTime')} - <HelpTooltip title="Automatically relocked if auto extension is enabled" /> - </Stack> - </Stack> - <Stack direction="row" justifyContent="space-between" alignContent="center"> - <Box>Expected CU</Box> - {isComputationUnitsLoading ? '-' : computationUnits} - </Stack> + </InputAdornment> + ), + }} + /> + </FormRow> + <FormRow> + <FormikTextInput + id="durationBlocks" + label={ + // TODO: add tooltip text + <HelpTooltip title="Lorem ipsum dolor"> + <span>Duration</span> + </HelpTooltip> + } + formik={formik} + showErrorOnlyOfTouched + autoComplete="off" + disabled={!!selectedSource?.stake.amount} + /> + </FormRow> + {/* <FormRow> + <FormikSwitch id="autoExtension" label="Auto extension" formik={formik} /> + </FormRow> */} + <FormDivider /> + <Stack spacing={2}> + <Stack direction="row" justifyContent="space-between" alignContent="center"> + <Box>Total amount</Box> + {tokenFormatter(fromSqd(preview?.totalAmount), 'SQD', 6)} </Stack> - - <BlockchainContractError error={error} /> - </Form> - )} - </ContractCallDialog> - </> + <Stack direction="row" justifyContent="space-between" alignContent="center"> + <Box>Epoch count</Box> + {numberWithCommasFormatter(preview?.epochCount)} + </Stack> + <Stack direction="row" justifyContent="space-between" alignContent="center"> + <HelpTooltip title="Lorem ipsum dolor"> + <span>Available CUs</span> + </HelpTooltip> + {numberWithCommasFormatter(preview?.cuPerEpoch)} + </Stack> + <Stack direction="row" justifyContent="space-between" alignContent="center"> + <HelpTooltip title="Automatically relocked if auto extension is enabled"> + <span>Unlocked at</span> + </HelpTooltip> + <span>~{dateFormat(preview?.unlockAt, 'dateTime')}</span> + </Stack> + </Stack> + </Form> + )} + </ContractCallDialog> ); } diff --git a/src/pages/GatewaysPage/GatewayStatus.tsx b/src/pages/GatewaysPage/GatewayStatus.tsx index 976d106..1240797 100644 --- a/src/pages/GatewaysPage/GatewayStatus.tsx +++ b/src/pages/GatewaysPage/GatewayStatus.tsx @@ -1,6 +1,6 @@ import { chipClasses, Chip as MaterialChip, styled } from '@mui/material'; -import { BlockchainGateway } from '@api/subsquid-network-squid/gateways-graphql'; +import { GatewayFragmentFragment } from '@api/subsquid-network-squid'; export const Chip = styled(MaterialChip)(({ theme: { spacing } }) => ({ fontSize: '0.75rem', @@ -23,14 +23,14 @@ export const Chip = styled(MaterialChip)(({ theme: { spacing } }) => ({ }, })); -export function GatewayStatus({ gateway }: { gateway: BlockchainGateway }) { +export function GatewayStatus({ gateway }: { gateway: GatewayFragmentFragment }) { return ( - <> - {gateway.operator?.stake?.locked ? ( - <Chip color="success" label="Active" /> - ) : ( - <Chip color="default" label="Idle" /> - )} - </> + <Chip color="success" label="Active" /> + // <> + // {gateway?.stake?.locked ? ( + // ) : ( + // <Chip color="default" label="Idle" /> + // )} + // </> ); } diff --git a/src/pages/GatewaysPage/GatewayUnregister.tsx b/src/pages/GatewaysPage/GatewayUnregister.tsx index f46e93c..62159d6 100644 --- a/src/pages/GatewaysPage/GatewayUnregister.tsx +++ b/src/pages/GatewaysPage/GatewayUnregister.tsx @@ -1,43 +1,92 @@ -import React from 'react'; +import { useState } from 'react'; +import { peerIdToHex } from '@lib/network'; import { LoadingButton } from '@mui/lab'; -import { Box } from '@mui/material'; -import { useNavigate } from 'react-router-dom'; +import { SxProps } from '@mui/material'; +import toast from 'react-hot-toast'; +import { useClient } from 'wagmi'; -import { useUnregisterGateway } from '@api/contracts/gateway-registration/useUnregisterGateway'; -import { BlockchainGateway } from '@api/subsquid-network-squid/gateways-graphql'; -import { BlockchainContractError } from '@components/BlockchainContractError'; +import { gatewayRegistryAbi } from '@api/contracts'; +import { useWriteSQDTransaction } from '@api/contracts/useWriteTransaction'; +import { errorMessage } from '@api/contracts/utils'; +import { Gateway } from '@api/subsquid-network-squid'; +import { ContractCallDialog } from '@components/ContractCallDialog'; +import { useSquidHeight } from '@hooks/useSquidNetworkHeightHooks'; +import { useContracts } from '@network/useContracts'; -export function GatewayUnregister({ gateway }: { gateway: BlockchainGateway }) { - const navigate = useNavigate(); - const { - unregisterGateway, - error: unregisterError, - isLoading: isUnregistering, - } = useUnregisterGateway(); - - if (!gateway.ownedByMe) return null; +export function GatewayUnregisterButton({ + gateway, + sx, +}: { + gateway: Pick<Gateway, 'id'>; + sx?: SxProps; +}) { + const [open, setOpen] = useState(false); return ( - <Box> + <> <LoadingButton - loading={isUnregistering} - onClick={async e => { - e.stopPropagation(); - - const { failedReason } = await unregisterGateway({ gateway }); - - if (!failedReason) { - navigate('/gateways'); - } - }} - variant="contained" + // startIcon={<Remove />} + sx={sx} + loading={open} + onClick={() => setOpen(true)} + variant="outlined" color="error" > - Unregister + DELETE </LoadingButton> + <GatewayUnregisterDialog open={open} onClose={() => setOpen(false)} gateway={gateway} /> + </> + ); +} + +export function GatewayUnregisterDialog({ + open, + onClose, + gateway, +}: { + open: boolean; + onClose: () => void; + gateway: Pick<Gateway, 'id'>; +}) { + const client = useClient(); + const { setWaitHeight } = useSquidHeight(); + + const contracts = useContracts(); + const gatewayRegistryContract = useWriteSQDTransaction(); + + const handleSubmit = async () => { + if (!client) return; + + try { + const receipt = await gatewayRegistryContract.writeTransactionAsync({ + address: contracts.GATEWAY_REGISTRATION, + abi: gatewayRegistryAbi, + functionName: 'unregister', + args: [peerIdToHex(gateway.id)], + }); + setWaitHeight(receipt.blockNumber, []); + + onClose(); + } catch (e: unknown) { + toast.error(errorMessage(e)); + } + }; + + return ( + <ContractCallDialog + title="Unregister gateway?" + open={open} + onResult={confirmed => { + if (!confirmed) return onClose(); - <BlockchainContractError error={unregisterError} /> - </Box> + handleSubmit(); + }} + loading={gatewayRegistryContract.isPending} + hideCancelButton={false} + > + Are you sure you want to unregister this gateway? This will disable the gateway, but you can + re-register it later. + </ContractCallDialog> ); } diff --git a/src/pages/GatewaysPage/GatewayUnstake.tsx b/src/pages/GatewaysPage/GatewayUnstake.tsx index b3aaf87..6453ac2 100644 --- a/src/pages/GatewaysPage/GatewayUnstake.tsx +++ b/src/pages/GatewaysPage/GatewayUnstake.tsx @@ -1,20 +1,18 @@ -import React, { useMemo, useState } from 'react'; +import React, { useState } from 'react'; -import { Button } from '@mui/material'; -import { useFormik } from 'formik'; +import { LockOpen as LockOpenIcon } from '@mui/icons-material'; +import { LoadingButton } from '@mui/lab'; +import { SxProps } from '@mui/material'; +import toast from 'react-hot-toast'; +import { useClient } from 'wagmi'; import * as yup from 'yup'; -import { useUnstakeGateway } from '@api/contracts/gateway-registration/useUnstakeGateway'; -import { - AccountType, - GatewayStakeFragmentFragment, - useMySources, -} from '@api/subsquid-network-squid'; -import { BlockchainContractError } from '@components/BlockchainContractError'; +import { gatewayRegistryAbi } from '@api/contracts'; +import { useWriteSQDTransaction } from '@api/contracts/useWriteTransaction'; +import { errorMessage } from '@api/contracts/utils'; import { ContractCallDialog } from '@components/ContractCallDialog'; -import { Form, FormikSelect, FormRow } from '@components/Form'; -import { Loader } from '@components/Loader'; -import { SourceWalletOption } from '@components/SourceWallet'; +import { useSquidHeight } from '@hooks/useSquidNetworkHeightHooks'; +import { useContracts } from '@network/useContracts'; export const stakeSchema = yup.object({ source: yup.string().label('Source').trim().required('Source is required'), @@ -26,120 +24,66 @@ export const stakeSchema = yup.object({ // .max(yup.ref('max'), ({ max }) => `Amount should be less than ${formatSqd(max)} `), }); -export function GatewayUnstake({ operator }: { operator?: GatewayStakeFragmentFragment }) { - const { unstakeFromGateway, error, isLoading } = useUnstakeGateway(); - +export function GatewayUnstakeButton({ sx, disabled }: { sx?: SxProps; disabled?: boolean }) { const [open, setOpen] = useState(false); - const handleOpen = () => setOpen(true); - const handleClose = () => setOpen(false); - const { sources, isPending: isSourceLoading } = useMySources({ - enabled: open, - }); + return ( + <> + <LoadingButton + startIcon={<LockOpenIcon />} + disabled={disabled} + loading={open} + variant="contained" + color="error" + onClick={() => setOpen(true)} + sx={sx} + > + WITHDRAW + </LoadingButton> + <GatewayUnstakeDialog open={open} onClose={() => setOpen(false)} /> + </> + ); +} - const options = useMemo(() => { - return [ - { - label: ( - <SourceWalletOption - source={{ - id: operator?.account.id || '', - type: operator?.account.type || AccountType.User, - balance: operator?.stake?.amount || '0', - }} - /> - ), - value: operator?.account.id || '', - }, - ]; - }, [operator]); +export function GatewayUnstakeDialog({ onClose, open }: { onClose: () => void; open: boolean }) { + const client = useClient(); + const { setWaitHeight } = useSquidHeight(); - const formik = useFormik({ - initialValues: { - source: operator?.account.id, - }, - validationSchema: stakeSchema, - validateOnChange: true, - validateOnBlur: true, - validateOnMount: true, + const contracts = useContracts(); + const gatewayRegistryContract = useWriteSQDTransaction(); - onSubmit: async values => { - const wallet = sources.find(w => w?.id === values.source); - if (!wallet || !operator) return; + const handleSubmit = async () => { + if (!client) return; - const { failedReason } = await unstakeFromGateway({ operator }); + try { + const receipt = await gatewayRegistryContract.writeTransactionAsync({ + address: contracts.GATEWAY_REGISTRATION, + abi: gatewayRegistryAbi, + functionName: 'unstake', + args: [], + }); + setWaitHeight(receipt.blockNumber, []); - if (!failedReason) { - handleClose(); - } - }, - }); + onClose(); + } catch (e: unknown) { + toast.error(errorMessage(e)); + } + }; return ( - <> - <Button - disabled={ - (!operator?.stake && !operator?.pendingStake) || - operator.stake?.locked || - operator.pendingStake?.locked - } - variant="contained" - color="error" - onClick={handleOpen} - > - Unlock - </Button> - <ContractCallDialog - title="Unlock" - open={open} - onResult={confirmed => { - if (!confirmed) return handleClose(); + <ContractCallDialog + title="Withdraw tokens?" + open={open} + onResult={confirmed => { + if (!confirmed) return onClose(); - formik.handleSubmit(); - }} - loading={isLoading} - confirmColor="error" - > - {isSourceLoading ? ( - <Loader /> - ) : ( - <Form onSubmit={formik.handleSubmit}> - <FormRow> - <FormikSelect - id="source" - showErrorOnlyOfTouched - options={options} - disabled - formik={formik} - /> - </FormRow> - {/*<FormRow>*/} - {/* <FormikTextInput*/} - {/* id="amount"*/} - {/* label="Amount"*/} - {/* formik={formik}*/} - {/* showErrorOnlyOfTouched*/} - {/* InputProps={{*/} - {/* endAdornment: (*/} - {/* <Chip*/} - {/* clickable*/} - {/* disabled={totalStaked === formik.values.amount}*/} - {/* onClick={() => {*/} - {/* formik.setValues({*/} - {/* ...formik.values,*/} - {/* amount: fromSqd(gateway.totalStaked).toNumber(),*/} - {/* });*/} - {/* }}*/} - {/* label="Max"*/} - {/* />*/} - {/* ),*/} - {/* }}*/} - {/* />*/} - {/*</FormRow>*/} - <BlockchainContractError error={error} /> - </Form> - )} - </ContractCallDialog> - </> + handleSubmit(); + }} + loading={gatewayRegistryContract.isPending} + hideCancelButton={false} + > + Are you sure you want to withdraw your tokens? This will return all previously locked tokens + to your wallet. + </ContractCallDialog> ); } diff --git a/src/pages/GatewaysPage/GatewaysPage.tsx b/src/pages/GatewaysPage/GatewaysPage.tsx index bfd8ae1..5eacecf 100644 --- a/src/pages/GatewaysPage/GatewaysPage.tsx +++ b/src/pages/GatewaysPage/GatewaysPage.tsx @@ -1,173 +1,396 @@ -import React, { useCallback } from 'react'; +import React, { useMemo, useState } from 'react'; import { dateFormat } from '@i18n'; -import { tokenFormatter } from '@lib/formatters/formatters'; -import { fromSqd } from '@lib/network'; -import { Box, Button, Stack, TableBody, TableCell, TableHead, TableRow } from '@mui/material'; -import { Link, Outlet } from 'react-router-dom'; - -import { GatewayStake as GatewayStakeGraphql } from '@api/subsquid-network-squid'; -import { useMyGateways, useMyGatewayStakes } from '@api/subsquid-network-squid/gateways-graphql'; -import { Card } from '@components/Card'; -import { Loader } from '@components/Loader'; -import { BorderedTable } from '@components/Table/BorderedTable'; -import { CenteredPageWrapper, NetworkPageTitle } from '@layouts/NetworkLayout'; +import { numberWithCommasFormatter, tokenFormatter } from '@lib/formatters/formatters'; +import { fromSqd, getBlockTime } from '@lib/network'; +import { ExpandMore, Info } from '@mui/icons-material'; +import { + Alert, + Avatar, + Box, + Button, + Collapse, + Divider, + IconButton, + List, + ListItem, + ListItemIcon, + ListItemText, + Stack, + TableBody, + TableCell, + TableHead, + TableRow, + Tooltip, + Typography, + useMediaQuery, + useTheme, +} from '@mui/material'; +import { Outlet } from 'react-router-dom'; +import { useBlock } from 'wagmi'; + +import { + useReadGatewayRegistryComputationUnitsAmount, + useReadGatewayRegistryGetStake, + useReadNetworkControllerWorkerEpochLength, + useReadRouterNetworkController, +} from '@api/contracts'; +import { + AccountType, + useMyGatewaysQuery, + useMySources, + useSquid, +} from '@api/subsquid-network-squid'; +import SquaredChip from '@components/Chip/SquaredChip'; +import { HelpTooltip } from '@components/HelpTooltip'; +import { DashboardTable, NoItems } from '@components/Table'; +import { CenteredPageWrapper } from '@layouts/NetworkLayout'; import { ConnectedWalletRequired } from '@network/ConnectedWalletRequired'; +import { useAccount } from '@network/useAccount'; import { useContracts } from '@network/useContracts'; -import { SourceWalletName } from '@pages/AssetsPage/VestingName'; -import { GatewayName } from '@pages/GatewaysPage/GatewayName'; +import { ColumnLabel, ColumnValue, SummarySection } from '@pages/DashboardPage/Summary'; -import { GatewayStake } from './GatewayStake'; -import { GatewayStatus } from './GatewayStatus'; -import { GatewayUnregister } from './GatewayUnregister'; -import { GatewayUnstake } from './GatewayUnstake'; +import { AddGatewayButton } from './AddNewGateway'; +import { AutoExtension } from './AutoExtension'; +import { GatewayName } from './GatewayName'; +import { GatewayStakeButton } from './GatewayStake'; +import { GatewayUnregisterButton } from './GatewayUnregister'; +import { GatewayUnstakeButton } from './GatewayUnstake'; export function MyStakes() { - const { data, isLoading } = useMyGatewayStakes(); - const { SQD_TOKEN } = useContracts(); - - const getUnlockAt = useCallback( - (o: { - pendingStake?: Pick<GatewayStakeGraphql, 'lockEnd'>; - stake?: Pick<GatewayStakeGraphql, 'lockEnd'>; - autoExtension: boolean; - }) => { - if (!data) return; - - const stake = o.pendingStake || o.stake; - if (!stake) return; - - return ( - (Number(stake.lockEnd) - data.lastBlockL1 + 1) * data.blockTimeL1 + - new Date(data.lastBlockTimestampL1).getTime() - ); + const theme = useTheme(); + const narrowXs = useMediaQuery(theme.breakpoints.down('xs')); + + const { address } = useAccount(); + + const { GATEWAY_REGISTRATION, ROUTER, SQD_TOKEN, CHAIN_ID_L1: l1ChainId } = useContracts(); + + const { data: stake, isLoading: isStakeLoading } = useReadGatewayRegistryGetStake({ + address: GATEWAY_REGISTRATION, + args: [address || '0x'], + }); + const { data: cuAmount, isLoading: isCuAmountLoading } = + useReadGatewayRegistryComputationUnitsAmount({ + address: GATEWAY_REGISTRATION, + args: [stake?.amount || 0n, stake?.duration || 0n], + }); + const { data: sources, isLoading: isSourcesLoading } = useMySources({ + select: res => { + return res?.map(s => ({ + ...s, + stake: + s.type === AccountType.User + ? { + amount: stake?.amount || 0n, + duration: stake?.duration || 0n, + } + : { + amount: 0n, + duration: 0n, + }, + })); }, - [data], - ); + }); + + const { data: lastL1Block, isLoading: isL1BlockLoading } = useBlock({ + chainId: l1ChainId, + }); + const { data: unlockedAtL1Block, isLoading: isUnlockedAtBlockLoading } = useBlock({ + chainId: l1ChainId, + blockNumber: stake?.lockEnd, + includeTransactions: false, + query: { + enabled: stake && stake?.lockEnd <= (lastL1Block?.number || 0n), + }, + }); + + const { data: workerEpochLength, isLoading: isWorkerEpochLengthLoading } = + useReadNetworkControllerWorkerEpochLength({ + address: useReadRouterNetworkController({ address: ROUTER }).data, + }); + + const isLoading = + isStakeLoading || + isCuAmountLoading || + isL1BlockLoading || + isWorkerEpochLengthLoading || + isUnlockedAtBlockLoading || + isSourcesLoading; + + const isPending = !!stake?.amount && stake.lockStart > (lastL1Block?.number || 0n); + const isActive = + !!stake?.amount && + stake.lockStart <= (lastL1Block?.number || 0n) && + stake.lockEnd >= (lastL1Block?.number || 0n); + const isExpired = !!stake?.amount && stake.lockEnd < (lastL1Block?.number || 0n); + + const unlockDate = useMemo(() => { + if (!stake || !lastL1Block) return; + + if (stake.lockEnd < lastL1Block.number) + return Number(unlockedAtL1Block?.timestamp || 0n) * 1000; + + return ( + Number(lastL1Block.timestamp) * 1000 + getBlockTime(stake.lockEnd - lastL1Block.number + 1n) + ); + }, [lastL1Block, stake, unlockedAtL1Block?.timestamp]); + + const cuPerEpoch = useMemo(() => { + if (!stake?.lockEnd || !workerEpochLength || !lastL1Block || isExpired) return 0; + + const computationUnits = stake.lockStart > lastL1Block.number ? stake.oldCUs : cuAmount || 0n; + if (stake.duration < workerEpochLength) return computationUnits; + + return (computationUnits * workerEpochLength) / stake.duration; + }, [cuAmount, isExpired, lastL1Block, stake, workerEpochLength]); return ( - <Box> - <NetworkPageTitle title="My Locks" endAdornment={<GatewayStake />} /> - {isLoading ? ( - <Loader /> - ) : data?.operators.length ? ( - <Card noPadding> - <BorderedTable> - <TableHead> - <TableRow> - <TableCell>Operator</TableCell> - <TableCell>Pending</TableCell> - <TableCell>Active</TableCell> - <TableCell>Expired</TableCell> - <TableCell>Unlock at</TableCell> - </TableRow> - </TableHead> - <TableBody> - {data?.operators.map((o, i) => { - return ( - <TableRow key={i}> - <TableCell> - <SourceWalletName source={o.account} /> - </TableCell> - <TableCell> - {o.pendingStake - ? tokenFormatter(fromSqd(o.pendingStake?.amount), SQD_TOKEN) - : '-'} - </TableCell> - <TableCell> - {o.stake && o.stake.locked - ? tokenFormatter(fromSqd(o.stake?.amount), SQD_TOKEN) - : '-'} - </TableCell> - <TableCell> - {o.stake && !o.stake.locked - ? tokenFormatter(fromSqd(o.stake?.amount), SQD_TOKEN) - : '-'} - </TableCell> - <TableCell> - {o.autoExtension ? '-' : dateFormat(getUnlockAt(o), 'dateTime')} - </TableCell> - <TableCell> - <Box display="flex" justifyContent="flex-end"> - <GatewayUnstake operator={o} /> - </Box> - </TableCell> - </TableRow> - ); - })} - </TableBody> - </BorderedTable> - </Card> - ) : ( - <Card sx={{ textAlign: 'center' }}>No items to show</Card> - )} - </Box> + <> + <Box minHeight={256} mb={2} display="flex"> + <SummarySection + loading={isLoading} + sx={{ width: 1 }} + title={<SquaredChip label="Lock Info" color="primary" />} + action={ + <Stack direction="row" spacing={1}> + <GatewayStakeButton sources={sources} disabled={isLoading || isPending} /> + <GatewayUnstakeButton disabled={isLoading || !isExpired} /> + </Stack> + } + > + <Stack direction="column" flex={1}> + <Box alignSelf="end"> + <AutoExtension value={stake?.autoExtension} disabled={isLoading || !stake?.amount} /> + </Box> + <Stack + divider={<Divider flexItem />} + spacing={1} + direction={narrowXs ? 'column' : 'row'} + alignItems={narrowXs ? 'stretch' : 'flex-end'} + height={1} + justifyContent="stretch" + width={0.75} + > + <Stack divider={<Divider flexItem />} spacing={1} flex={1}> + <Box> + <ColumnLabel> + <Stack direction="row" spacing={1}> + <span>Amount</span> + <Tooltip + title="Lorem ipsum dolor sit amet, consectetur adipiscing elit" + placement="top" + > + <Box display="flex"> + {stake && + lastL1Block && + (isPending ? ( + <SquaredChip label="Pending" color="warning" /> + ) : isActive ? ( + <SquaredChip label="Active" color="info" /> + ) : isExpired ? ( + <SquaredChip label="Expired" color="error" /> + ) : null)} + </Box> + </Tooltip> + </Stack> + </ColumnLabel> + <ColumnValue>{tokenFormatter(fromSqd(stake?.amount), SQD_TOKEN, 3)}</ColumnValue> + </Box> + <Box> + <ColumnLabel> + <HelpTooltip title="In the current epoch"> + <span>Available CUs</span> + </HelpTooltip> + </ColumnLabel> + <ColumnValue>{numberWithCommasFormatter(cuPerEpoch || 0)}</ColumnValue> + </Box> + </Stack> + <Stack divider={<Divider flexItem />} spacing={1} flex={1}> + <Box> + <ColumnLabel>Unlocked At</ColumnLabel> + <ColumnValue> + {!stake?.autoExtension + ? unlockDate && stake?.lockEnd + ? dateFormat(unlockDate, narrowXs ? 'date' : 'dateTime') + : '-' + : 'Auto-extension enabled'} + </ColumnValue> + </Box> + </Stack> + </Stack> + </Stack> + </SummarySection> + </Box> + </> ); } export function MyGateways() { - const { data, isLoading } = useMyGateways(); + const account = useAccount(); + const squid = useSquid(); + + const { data: sources, isLoading: isSourcesLoading } = useMySources(); + + const { data: gatewaysQuery, isLoading: isGatewaysQueryLoading } = useMyGatewaysQuery(squid, { + address: account?.address || '0x', + }); + + const isLoading = isSourcesLoading || isGatewaysQueryLoading; return ( - <Box> - <NetworkPageTitle - title="My Gateways" - endAdornment={ - <Stack direction="row" spacing={2}> - <Button variant="contained" component={Link} to="/gateways/add"> - Add gateway + <DashboardTable + loading={isLoading} + title={ + <> + <SquaredChip label="My Portals" color="primary" /> + + <Stack direction="row" spacing={1}> + <Button color="secondary" variant="outlined"> + LEARN MORE </Button> + <AddGatewayButton disabled={isLoading} sources={sources} /> </Stack> - } - /> - {isLoading ? ( - <Loader /> - ) : data.length ? ( - <Card noPadding> - <BorderedTable> - <TableHead> - <TableRow> - <TableCell>Gateway</TableCell> - <TableCell>Status</TableCell> - <TableCell>Created</TableCell> - <TableCell></TableCell> - </TableRow> - </TableHead> - <TableBody> - {data.map(gateway => { - return ( - <TableRow key={gateway.id}> - <TableCell> - <GatewayName gateway={gateway} to={`/gateways/${gateway.id}`} /> - </TableCell> - <TableCell> - <GatewayStatus gateway={gateway} /> - </TableCell> - <TableCell>{dateFormat(gateway.createdAt, 'date')}</TableCell> - <TableCell> - <Box display="flex" justifyContent="flex-end"> - <GatewayUnregister gateway={gateway} /> - </Box> - </TableCell> - </TableRow> - ); - })} - </TableBody> - </BorderedTable> - </Card> - ) : ( - <Card sx={{ textAlign: 'center' }}>No items to show</Card> - )} - </Box> + </> + } + > + <TableHead> + <TableRow> + <TableCell>Portal</TableCell> + <TableCell>Registered</TableCell> + <TableCell></TableCell> + </TableRow> + </TableHead> + <TableBody> + {gatewaysQuery?.gateways.length ? ( + <> + {gatewaysQuery?.gateways.map(gateway => { + return ( + <TableRow key={gateway.id}> + <TableCell> + <GatewayName gateway={gateway} to={`/portals/${gateway.id}`} /> + </TableCell> + <TableCell>{dateFormat(gateway.createdAt)}</TableCell> + <TableCell> + <Box display="flex" justifyContent="flex-end"> + <GatewayUnregisterButton gateway={gateway} /> + </Box> + </TableCell> + </TableRow> + ); + })} + </> + ) : ( + <NoItems> + <Typography>No portal registered yet</Typography> + {/* {!isProMode && ( + <AddGateway + sx={{ mt: 2 }} + disabled={!isProMode && BigInt(stake?.stake?.amount || 0) <= 0n} + /> + )} */} + </NoItems> + )} + </TableBody> + </DashboardTable> ); } +const GettingStartedAlert = () => { + const theme = useTheme(); + const [open, setOpen] = useState(false); + + const steps = [ + { + primary: 'Get SQD tokens', + secondary: ( + <> + Make sure you have enough SQD tokens. <a href="#">How much do I need?</a> + </> + ), + }, + { + primary: 'Lock you tokens', + secondary: ( + <> + Lock your tokens to obtain Compute Units (CUs). <a href="#">How do I lock my tokens?</a> + </> + ), + }, + { + primary: 'Generate a Peer ID', + secondary: ( + <> + Create a Peer ID to identify your portal. <a href="#">How do I generate a Peer ID?</a> + </> + ), + }, + { + primary: 'Add your portal', + secondary: <>Register your portal on a chain.</>, + }, + ]; + + return ( + <Alert + sx={{ mb: 2 }} + color="info" + icon={<Info />} + action={ + <IconButton color="inherit" sx={{ padding: 0.5 }} onClick={() => setOpen(!open)}> + <ExpandMore + sx={{ + transition: 'transform 300ms ease-out', + transform: open ? 'rotate(180deg)' : 'rotate(0deg)', + }} + /> + </IconButton> + } + > + <Typography>Getting started with your portal</Typography> + + <Collapse in={open} unmountOnExit timeout={300}> + <Box pt={1.5}> + <List disablePadding> + {steps.map(({ primary, secondary }, i) => ( + <ListItem + key={i} + sx={{ listStyle: 'list-item' }} + disablePadding + alignItems="flex-start" + > + <ListItemIcon> + <Avatar + sx={{ + width: 32, + height: 32, + fontSize: '16px', + backgroundColor: theme.palette.info.main, + }} + > + {i + 1} + </Avatar> + </ListItemIcon> + <ListItemText primary={primary} secondary={secondary} /> + </ListItem> + ))} + </List> + <Typography variant="body1" mt={1}> + That's it! You're ready to run your Portal. If you need more guidance read our{' '} + <a href="#">portal section</a> in our docs or reach out to our{' '} + <a href="#">support team</a> for help. + </Typography> + </Box> + </Collapse> + </Alert> + ); +}; + export function GatewaysPage() { return ( <CenteredPageWrapper className="wide"> <ConnectedWalletRequired> + <GettingStartedAlert /> <MyStakes /> - <Box sx={{ height: 64 }} /> <MyGateways /> </ConnectedWalletRequired> <Outlet /> diff --git a/src/pages/GatewaysPage/gateway-schema.ts b/src/pages/GatewaysPage/gateway-schema.ts index a6c3f38..cc384e3 100644 --- a/src/pages/GatewaysPage/gateway-schema.ts +++ b/src/pages/GatewaysPage/gateway-schema.ts @@ -1,7 +1,7 @@ import * as yup from 'yup'; export const editGatewaySchema = yup.object({ - name: yup.string().label('Name').max(255).trim().required('Gateway name is required'), + name: yup.string().label('Name').max(255).trim().required('Portal name is required'), public: yup.boolean(), endpointUrl: yup .string() @@ -27,8 +27,7 @@ export const addGatewaySchema = editGatewaySchema.shape({ peerId: yup .string() .matches(/^[a-z1-9]+$/i, 'Peer ID must contains only base 58 symbols') - .max(52) - .min(52) + .length(52) .label('Peer ID') .trim() .required('Peer ID is required'), diff --git a/src/pages/WorkersPage/AddNewWorker.tsx b/src/pages/WorkersPage/AddNewWorker.tsx index 5c6368e..c288216 100644 --- a/src/pages/WorkersPage/AddNewWorker.tsx +++ b/src/pages/WorkersPage/AddNewWorker.tsx @@ -1,155 +1,209 @@ -import React, { useEffect, useMemo, useState } from 'react'; +import { useCallback, useMemo, useState } from 'react'; -import { fromSqd } from '@lib/network/utils'; +import { fromSqd, peerIdToHex } from '@lib/network/utils'; +import { Add } from '@mui/icons-material'; import { LoadingButton } from '@mui/lab'; -import { Box } from '@mui/material'; -import BigNumber from 'bignumber.js'; +import { SxProps } from '@mui/material'; import { useFormik } from 'formik'; -import { useNavigate } from 'react-router-dom'; +import toast from 'react-hot-toast'; import useLocalStorageState from 'use-local-storage-state'; -import { useRegisterWorker } from '@api/contracts/worker-registration/useRegisterWorker'; -import { useMySources } from '@api/subsquid-network-squid'; -import { useNetworkSettings } from '@api/subsquid-network-squid/settings-graphql'; -import { BlockchainContractError } from '@components/BlockchainContractError'; -import { Card } from '@components/Card'; +import { + useReadRouterWorkerRegistration, + useReadWorkerRegistryBondAmount, + workerRegistryAbi, +} from '@api/contracts'; +import { useWriteSQDTransaction } from '@api/contracts/useWriteTransaction'; +import { errorMessage } from '@api/contracts/utils'; +import { encodeWorkerMetadata } from '@api/contracts/worker-registration/WorkerMetadata'; +import { AccountType, SourceWalletWithBalance } from '@api/subsquid-network-squid'; import { ConfirmDialog } from '@components/ConfirmDialog'; +import { ContractCallDialog } from '@components/ContractCallDialog'; import { Form, FormikCheckBoxInput, FormikTextInput, FormRow } from '@components/Form'; import { FormikSelect } from '@components/Form/FormikSelect'; import { Loader } from '@components/Loader'; import { SourceWalletOption } from '@components/SourceWallet'; -import { CenteredPageWrapper, NetworkPageTitle } from '@layouts/NetworkLayout'; -import { ConnectedWalletRequired } from '@network/ConnectedWalletRequired'; +import { useSquidHeight } from '@hooks/useSquidNetworkHeightHooks'; +import { useContracts } from '@network/useContracts'; import { useWorkersChatUrl } from '@network/useWorkersChat'; import { addWorkerSchema } from './worker-schema'; -function JoinChatDialog({ open, onResult }: { open: boolean; onResult: () => void }) { - const [, setSkipWorkerJoinChat] = useLocalStorageState<boolean>('skip_join_workers_chat'); - const chatUrl = useWorkersChatUrl(); - - const formik = useFormik({ - initialValues: { - doNotShow: false, - }, +export function AddWorkerButton({ + sx, + disabled, + sources, +}: { + sx?: SxProps; + disabled?: boolean; + sources?: SourceWalletWithBalance[]; +}) { + const [open, setOpen] = useState(false); - onSubmit: async values => { - setSkipWorkerJoinChat(values.doNotShow); - onResult(); - }, - }); + const [isJoinChatOpen, setJoinChatOpen] = useState(false); + const [skipWorkerJoinChat] = useLocalStorageState<boolean>('sqd_skip_join_workers_chat'); return ( - <ConfirmDialog - open={open} - title="Registered" - confirmButtonText="Join chat" - confirmColor="success" - hideCancelButton - onApprove={() => window.open(chatUrl)} - onResult={formik.submitForm} - > - <Form onSubmit={formik.handleSubmit}> - Join the <b>Subsquid Network</b> node operators chat for support and updates on worker - images. - <FormRow> - <FormikCheckBoxInput id="doNotShow" label="Don't show this again" formik={formik} /> - </FormRow> - </Form> - </ConfirmDialog> + <> + <LoadingButton + disabled={disabled} + sx={sx} + loading={open} + color="info" + startIcon={<Add />} + variant="contained" + onClick={() => setOpen(true)} + > + ADD WORKER + </LoadingButton> + <AddNewWorkerDialog + open={open} + onResult={confirmed => { + setOpen(false); + + if (confirmed && !skipWorkerJoinChat) { + setJoinChatOpen(true); + } + }} + sources={sources} + /> + <JoinChatDialog + open={isJoinChatOpen} + onResult={() => { + setJoinChatOpen(false); + }} + /> + </> ); } -function AddWorkerForm() { - const navigate = useNavigate(); - const { bondAmount, isPending: isSettingsLoading } = useNetworkSettings(); - const { sources, isPending: isContractsLoading } = useMySources(); - const { registerWorker, isLoading, error } = useRegisterWorker(); - const [isJoinChatOpen, setJoinChatOpen] = useState(false); - const [skipWorkerJoinChat] = useLocalStorageState<boolean>('skip_join_workers_chat'); +export function AddNewWorkerDialog({ + open, + onResult, + sources, +}: { + open: boolean; + onResult: (confirmed: boolean) => void; + sources?: SourceWalletWithBalance[]; +}) { + const { setWaitHeight } = useSquidHeight(); - const formik = useFormik({ - initialValues: { + const contracts = useContracts(); + const contractWriter = useWriteSQDTransaction(); + + const { data: workerRegistryAddress, isLoading: isWorkerRegistryAddressLoading } = + useReadRouterWorkerRegistration({ + address: contracts.ROUTER, + }); + + const { data: bondAmount, isPending: isBondLoading } = useReadWorkerRegistryBondAmount({ + address: workerRegistryAddress, + }); + + const isLoading = isWorkerRegistryAddressLoading || isBondLoading; + + const isSourceDisabled = useCallback( + (source: SourceWalletWithBalance) => BigInt(source.balance) < (bondAmount || 0n), + [bondAmount], + ); + const hasAvailableSource = useMemo( + () => !!sources?.some(s => !isSourceDisabled(s)), + [isSourceDisabled, sources], + ); + + const initialValues = useMemo(() => { + const source = sources?.find(s => !isSourceDisabled(s)) || sources?.[0]; + + return { name: '', description: '', website: '', email: '', peerId: '', - source: '', - }, + source: source?.id || '', + bond: fromSqd(bondAmount).toString(), + }; + }, [bondAmount, isSourceDisabled, sources]); + + const formik = useFormik({ + initialValues, validationSchema: addWorkerSchema, validateOnChange: true, validateOnBlur: true, validateOnMount: true, + enableReinitialize: true, onSubmit: async values => { - const source = sources.find(s => s.id === values.source); - if (!source) return; - - const { success } = await registerWorker({ - ...values, - source, - }); - if (!success) return; - - if (skipWorkerJoinChat) { - navigate('/workers'); - } else { - setJoinChatOpen(true); - } - }, - }); + if (!workerRegistryAddress || !bondAmount) return; - const source = useMemo(() => { - if (isContractsLoading) return; - if (isSettingsLoading) return; + try { + const { peerId, source: sourceId, ...metadata } = addWorkerSchema.cast(values); - return ( - (formik.values.source - ? sources.find(c => c.id === formik.values.source) - : sources.find(c => fromSqd(c.balance).gte(fromSqd(bondAmount)))) || sources?.[0] - ); - }, [bondAmount, formik.values.source, isContractsLoading, isSettingsLoading, sources]); + const source = sources?.find(s => s.id === sourceId); + if (!source) return; - useEffect(() => { - if (!source) return; + const peerIdHex = peerIdToHex(peerId); - formik.setValues({ - ...formik.values, - source: source.id, - }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [source]); + const receipt = await contractWriter.writeTransactionAsync({ + address: workerRegistryAddress, + abi: workerRegistryAbi, + functionName: 'register', + args: [peerIdHex, encodeWorkerMetadata(metadata)], + vesting: source.type === AccountType.Vesting ? (source.id as `0x${string}`) : undefined, + approve: bondAmount, + }); + setWaitHeight(receipt.blockNumber, []); + + formik.resetForm(); + + onResult(true); + } catch (error) { + toast.error(errorMessage(error)); + } + }, + }); return ( <> - {isContractsLoading ? ( - <Loader /> - ) : ( - <> - <Form onSubmit={formik.handleSubmit}> - <Card outlined> + <ContractCallDialog + title="Worker registration" + open={open} + onResult={confirmed => { + if (!confirmed) return onResult(confirmed); + + formik.handleSubmit(); + }} + disableConfirmButton={isLoading || !hasAvailableSource} + loading={contractWriter.isPending} + > + {isLoading ? ( + <Loader /> + ) : ( + <> + <Form onSubmit={formik.handleSubmit}> <FormRow> <FormikSelect id="source" - disabled={!sources.length} showErrorOnlyOfTouched - options={sources.map(s => { - return { - label: <SourceWalletOption source={s} />, - value: s.id, - disabled: BigNumber(s.balance).lt(bondAmount), - }; - })} + options={ + sources?.map(s => { + return { + label: <SourceWalletOption source={s} />, + value: s.id, + disabled: isSourceDisabled(s), + }; + }) || [] + } formik={formik} /> </FormRow> <FormRow> <FormikTextInput showErrorOnlyOfTouched - id="name" - label="Worker name" + id="bond" + label="Bond amount" formik={formik} + disabled /> </FormRow> <FormRow> @@ -160,6 +214,14 @@ function AddWorkerForm() { formik={formik} /> </FormRow> + <FormRow> + <FormikTextInput + showErrorOnlyOfTouched + id="name" + label="Worker name" + formik={formik} + /> + </FormRow> <FormRow> <FormikTextInput showErrorOnlyOfTouched @@ -178,39 +240,55 @@ function AddWorkerForm() { formik={formik} /> </FormRow> - <BlockchainContractError error={error} /> - </Card> - <Box mt={3} justifyContent="flex-end" display="flex"> - <LoadingButton - disabled={isLoading || fromSqd(source?.balance || 0).lt(fromSqd(bondAmount))} - variant="contained" - type="submit" - color="info" - > - REGISTER - </LoadingButton> - </Box> - </Form> - <JoinChatDialog - open={isJoinChatOpen} - onResult={() => { - navigate('/workers'); - setJoinChatOpen(false); - }} - /> - </> - )} + </Form> + </> + )} + </ContractCallDialog> </> ); } -export function AddNewWorker() { +function JoinChatDialog({ + open, + onResult, +}: { + open: boolean; + onResult: (confirmed: boolean) => void; +}) { + const [, setSkipWorkerJoinChat] = useLocalStorageState<boolean>('sqd_skip_join_workers_chat'); + const chatUrl = useWorkersChatUrl(); + + const formik = useFormik({ + initialValues: { + doNotShow: false, + }, + + onSubmit: async values => { + setSkipWorkerJoinChat(values.doNotShow); + onResult(true); + }, + }); + return ( - <CenteredPageWrapper> - <ConnectedWalletRequired> - <NetworkPageTitle backPath="/workers"></NetworkPageTitle> - <AddWorkerForm /> - </ConnectedWalletRequired> - </CenteredPageWrapper> + <ConfirmDialog + open={open} + title="Worker registered!" + confirmButtonText="JOIN CHAT" + confirmColor="success" + hideCancelButton + onApprove={() => window.open(chatUrl)} + onResult={(confirmed: boolean) => { + if (!confirmed) onResult(confirmed); + + formik.handleSubmit(); + }} + > + <Form onSubmit={formik.handleSubmit}> + Join the <b>SQD Network</b> node operators chat for support and updates on worker images. + <FormRow> + <FormikCheckBoxInput id="doNotShow" label="Don't show this again" formik={formik} /> + </FormRow> + </Form> + </ConfirmDialog> ); } diff --git a/src/pages/WorkersPage/Worker.tsx b/src/pages/WorkersPage/Worker.tsx index 7d11c67..2c86131 100644 --- a/src/pages/WorkersPage/Worker.tsx +++ b/src/pages/WorkersPage/Worker.tsx @@ -10,7 +10,13 @@ import { fromSqd } from '@lib/network'; import { Box, Divider, Stack, styled } from '@mui/material'; import { useParams, useSearchParams } from 'react-router-dom'; -import { useWorkerByPeerId, WorkerStatus as ApiWorkerStatus } from '@api/subsquid-network-squid'; +import { + useWorkerByPeerId, + WorkerStatus as ApiWorkerStatus, + WorkerStatus, + useMySources, + useMyWorkerDelegations, +} from '@api/subsquid-network-squid'; import { Card } from '@components/Card'; import SquaredChip from '@components/Chip/SquaredChip'; import { Loader } from '@components/Loader'; @@ -18,13 +24,14 @@ import { NotFound } from '@components/NotFound'; import { CenteredPageWrapper, NetworkPageTitle } from '@layouts/NetworkLayout'; import { useAccount } from '@network/useAccount'; import { useContracts } from '@network/useContracts'; -import { WorkerUnregister } from '@pages/WorkersPage/WorkerUnregister'; +import { WorkerUnregisterButton } from '@pages/WorkersPage/WorkerUnregister'; import { DelegationCapacity } from './DelegationCapacity'; import { WorkerCard } from './WorkerCard'; import { WorkerDelegate } from './WorkerDelegate'; import { WorkerUndelegate } from './WorkerUndelegate'; import { WorkerVersion } from './WorkerVersion'; +import { WorkerWithdrawButton } from './WorkerWithdraw'; // const sx = { // background: '#000', @@ -80,20 +87,41 @@ export const Title = styled(SquaredChip)(({ theme }) => ({ export const Worker = ({ backPath }: { backPath: string }) => { const { peerId } = useParams<{ peerId: string }>(); - const { data: worker, isPending } = useWorkerByPeerId(peerId); + const { data: worker, isLoading: isPending } = useWorkerByPeerId(peerId); const { address } = useAccount(); const { SQD_TOKEN } = useContracts(); const [searchParams] = useSearchParams(); + const { data: sources, isLoading: isSourcesLoading } = useMySources(); + const { data: delegations, isLoading: isDelegationsLoading } = useMyWorkerDelegations({ peerId }); + + const isLoading = isSourcesLoading || isDelegationsLoading; + return ( <CenteredPageWrapper className="wide"> <NetworkPageTitle backPath={searchParams.get('backPath') || backPath} endAdornment={ <Stack direction="row" spacing={2}> - <WorkerDelegate worker={worker} variant="outlined" /> - <WorkerUndelegate worker={worker} /> + <WorkerDelegate + worker={worker} + variant="outlined" + sources={sources} + disabled={isLoading} + /> + <WorkerUndelegate + worker={worker} + sources={delegations?.map(d => ({ + id: d.owner.id, + type: d.owner.type, + balance: d.deposit, + locked: d.locked || false, + // FIXME: some issue with types + unlockedAt: (d as any).unlockedAt, + }))} + disabled={isLoading || !delegations?.some(d => !d.locked)} + /> </Stack> } /> @@ -108,6 +136,7 @@ export const Worker = ({ backPath }: { backPath: string }) => { <Stack spacing={3} divider={<Divider orientation="horizontal" flexItem />}> <WorkerCard worker={worker} + owner={worker.owner} canEdit={ worker.realOwner.id === address && [ApiWorkerStatus.Active, ApiWorkerStatus.Registering].includes(worker.status) @@ -117,7 +146,7 @@ export const Worker = ({ backPath }: { backPath: string }) => { <Title label="Info" /> <Stack spacing={2} direction="column"> <Stack direction="row"> - <WorkerDescLabel>Registered</WorkerDescLabel> + <WorkerDescLabel>Created</WorkerDescLabel> <WorkerDescValue>{dateFormat(worker.createdAt, 'dateTime')}</WorkerDescValue> </Stack> <Stack direction="row"> @@ -240,8 +269,26 @@ export const Worker = ({ backPath }: { backPath: string }) => { </Card> {worker.realOwner.id === address && worker.status !== ApiWorkerStatus.Withdrawn ? ( - <Box mt={3}> - <WorkerUnregister worker={worker} /> + <Box mt={3} display="flex" justifyContent="flex-end"> + {worker.status === WorkerStatus.Deregistered || + worker.status === WorkerStatus.Deregistering ? ( + <WorkerWithdrawButton + worker={worker} + source={{ + ...worker.owner, + // FIXME: some types issue + locked: (worker as any).locked, + unlockedAt: (worker as any).unlockedAt, + }} + disabled={worker.status !== WorkerStatus.Deregistered} + /> + ) : ( + <WorkerUnregisterButton + worker={worker} + source={worker.owner} + disabled={worker.status !== WorkerStatus.Active} + /> + )} </Box> ) : null} </> diff --git a/src/pages/WorkersPage/WorkerCard.tsx b/src/pages/WorkersPage/WorkerCard.tsx index 73e039b..de16cf1 100644 --- a/src/pages/WorkersPage/WorkerCard.tsx +++ b/src/pages/WorkersPage/WorkerCard.tsx @@ -1,13 +1,12 @@ -import { IconButton, Stack, Typography, useMediaQuery, useTheme } from '@mui/material'; +import { Stack, Typography, useTheme } from '@mui/material'; import { Box } from '@mui/system'; -import { Link } from 'react-router-dom'; -import { Worker, WorkerStatusFragmentFragment } from '@api/subsquid-network-squid'; +import { Account, Worker, WorkerStatusFragmentFragment } from '@api/subsquid-network-squid'; import { Avatar } from '@components/Avatar'; import { CopyToClipboard } from '@components/CopyToClipboard'; -import { EditIcon } from '@icons/EditIcon'; -import { WorkerStatus } from './WorkerStatus'; +import { WorkerEdit } from './WorkerEdit'; +import { WorkerStatusChip } from './WorkerStatus'; // export const PeerIdRow = styled(Box, { // name: 'PeerIdRow', @@ -19,50 +18,42 @@ import { WorkerStatus } from './WorkerStatus'; function WorkerTitle({ worker, + owner, canEdit, }: { worker: Pick<Worker, 'id' | 'status' | 'peerId' | 'name'>; + owner: Pick<Account, 'id' | 'type'>; canEdit: boolean; }) { const theme = useTheme(); return ( <Stack spacing={0.5}> - <Stack direction="row" alignItems="center" spacing={1}> + <Stack direction="row" alignItems="center" spacing={0.5}> <Typography variant="h4" sx={{ overflowWrap: 'anywhere' }}> {worker.name || worker.peerId} </Typography> - {canEdit ? ( - <IconButton component={Link} to={`/workers/${worker.peerId}/edit`} sx={{ padding: 0 }}> - <EditIcon /> - </IconButton> - ) : null} + {canEdit ? <WorkerEdit worker={worker} owner={owner} disabled={!canEdit} /> : null} </Stack> - <CopyToClipboard - text={worker.peerId} - content={ - <Typography - variant="body2" - sx={{ overflowWrap: 'anywhere', color: theme.palette.text.secondary }} - > - {worker.peerId} - </Typography> - } - /> + <Typography + variant="body2" + sx={{ overflowWrap: 'anywhere', color: theme.palette.text.secondary }} + > + <CopyToClipboard text={worker.peerId} content={<span>{worker.peerId}</span>} /> + </Typography> </Stack> ); } export const WorkerCard = ({ worker, + owner, canEdit, }: { worker: Pick<Worker, 'id' | 'status' | 'peerId' | 'name'> & WorkerStatusFragmentFragment; + owner: Pick<Account, 'id' | 'type'>; canEdit: boolean; }) => { - const theme = useTheme(); - const isMobile = useMediaQuery(theme.breakpoints.down('xs')); - return ( <> <Stack direction="row" justifyContent="space-between"> @@ -74,12 +65,12 @@ export const WorkerCard = ({ size={56} /> {/* <Stack justifyContent="stretch" flex={1} spacing={0.125}> */} - <WorkerTitle worker={worker} canEdit={canEdit} /> + <WorkerTitle worker={worker} owner={owner} canEdit={canEdit} /> {/* </Stack> */} </Stack> <Box> - <WorkerStatus worker={worker} /> + <WorkerStatusChip worker={worker} /> </Box> </Stack> </> diff --git a/src/pages/WorkersPage/WorkerDelegate.tsx b/src/pages/WorkersPage/WorkerDelegate.tsx index bfb7f20..b80cca8 100644 --- a/src/pages/WorkersPage/WorkerDelegate.tsx +++ b/src/pages/WorkersPage/WorkerDelegate.tsx @@ -1,27 +1,38 @@ -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useMemo, useState } from 'react'; import { percentFormatter } from '@lib/formatters/formatters'; import { fromSqd, toSqd } from '@lib/network/utils'; import { LoadingButton } from '@mui/lab'; -import { Box, Chip, Stack } from '@mui/material'; +import { Chip, Stack } from '@mui/material'; import * as yup from '@schema'; import BigNumber from 'bignumber.js'; import { useFormik } from 'formik'; +import toast from 'react-hot-toast'; import { useDebounce } from 'use-debounce'; -import { useCapedStakeAfterDelegation, useWorkerDelegate } from '@api/contracts/staking'; -import { useWorkerDelegationInfo, Worker, WorkerStatus } from '@api/subsquid-network-squid'; -import { BlockchainContractError } from '@components/BlockchainContractError'; +import { stakingAbi, useReadRouterStaking } from '@api/contracts'; +import { useCapedStakeAfterDelegation } from '@api/contracts/staking'; +import { useWriteSQDTransaction } from '@api/contracts/useWriteTransaction'; +import { errorMessage } from '@api/contracts/utils'; +import { + AccountType, + SourceWalletWithBalance, + useWorkerDelegationInfo, + Worker, + WorkerStatus, +} from '@api/subsquid-network-squid'; import { ContractCallDialog } from '@components/ContractCallDialog'; import { Form, FormDivider, FormikSelect, FormikTextInput, FormRow } from '@components/Form'; import { HelpTooltip } from '@components/HelpTooltip'; -import { useMySourceOptions } from '@components/SourceWallet/useMySourceOptions'; +import { SourceWalletOption } from '@components/SourceWallet'; +import { useSquidHeight } from '@hooks/useSquidNetworkHeightHooks'; +import { useContracts } from '@network/useContracts'; export const EXPECTED_APR_TIP = ( - <Box> + <span> An estimated delegation APR. The realized APR may differ significantly and depends on the worker uptime and the total amount of SQD delegated to the worker, which may change over time. - </Box> + </span> ); export const delegateSchema = yup.object({ @@ -37,80 +48,108 @@ export const delegateSchema = yup.object({ }); export function WorkerDelegate({ + sources, worker, disabled, variant = 'outlined', }: { + sources?: SourceWalletWithBalance[]; worker?: Pick<Worker, 'id' | 'status'>; variant?: 'outlined' | 'contained'; disabled?: boolean; }) { - const { delegateToWorker, error, isPending } = useWorkerDelegate(); - const [open, setOpen] = useState(false); - const handleOpen = (event: React.UIEvent) => { - event.stopPropagation(); - setOpen(true); - }; - const handleClose = () => setOpen(false); - - const { - sources, - options, - isPending: isSourceLoading, - } = useMySourceOptions({ - enabled: open, - sourceDisabled: s => BigNumber(s.balance).lte(0), + + return ( + <> + <LoadingButton + loading={open} + disabled={disabled || !worker || worker.status !== WorkerStatus.Active} + onClick={() => setOpen(true)} + variant={variant} + color={variant === 'contained' ? 'info' : 'secondary'} + > + DELEGATE + </LoadingButton> + <WorkerDelegateDialog + open={open} + onClose={() => setOpen(false)} + sources={sources} + worker={worker} + /> + </> + ); +} + +export function WorkerDelegateDialog({ + open, + sources, + worker, + onClose, +}: { + open: boolean; + sources?: SourceWalletWithBalance[]; + worker?: Pick<Worker, 'id' | 'status'>; + onClose: () => void; +}) { + const contracts = useContracts(); + const { writeTransactionAsync, isPending } = useWriteSQDTransaction(); + + const { setWaitHeight } = useSquidHeight(); + + const stakingAddress = useReadRouterStaking({ + address: contracts.ROUTER, }); + const isSourceDisabled = (source: SourceWalletWithBalance) => source.balance === '0'; + + const initialValues = useMemo(() => { + const source = sources?.find(c => !isSourceDisabled(c)) || sources?.[0]; + + return { + source: source?.id || '', + amount: '0', + max: fromSqd(source?.balance).toString(), + }; + }, [sources]); + const formik = useFormik({ - initialValues: { - source: '', - amount: '', - max: '0', - }, + initialValues, validationSchema: delegateSchema, validateOnChange: true, validateOnBlur: true, validateOnMount: true, + enableReinitialize: true, onSubmit: async values => { - const wallet = sources.find(w => w?.id === values.source); - if (!wallet || !worker) return; - - const { failedReason } = await delegateToWorker({ - worker, - amount: toSqd(values.amount), - wallet, - }); - - if (!failedReason) { - handleClose(); + if (!stakingAddress.data) return; + if (!worker) return; + + try { + const { amount, source: sourceId } = delegateSchema.cast(values); + + const source = sources?.find(w => w?.id === sourceId); + if (!source) return; + + const sqdAmount = BigInt(toSqd(amount)); + + const receipt = await writeTransactionAsync({ + abi: stakingAbi, + address: stakingAddress.data, + functionName: 'deposit', + args: [BigInt(worker.id), sqdAmount], + vesting: source.type === AccountType.Vesting ? (source.id as `0x${string}`) : undefined, + approve: sqdAmount, + }); + setWaitHeight(receipt.blockNumber, []); + + onClose(); + } catch (e) { + toast.error(errorMessage(e)); } }, }); - const source = useMemo(() => { - if (isSourceLoading) return; - - return ( - (formik.values.source - ? sources.find(c => c.id === formik.values.source) - : sources.find(c => fromSqd(c.balance).gte(0))) || sources?.[0] - ); - }, [formik.values.source, isSourceLoading, sources]); - - useEffect(() => { - if (!source) return; - - formik.setValues({ - ...formik.values, - source: source.id, - max: fromSqd(source.balance).toFixed(), - }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [source]); - const [delegation] = useDebounce(formik.values.amount, 500); const { isPending: isExpectedAprPending, stakerApr } = useExpectedAprAfterDelegation({ workerId: worker?.id, @@ -119,80 +158,73 @@ export function WorkerDelegate({ }); return ( - <> - <LoadingButton - disabled={disabled || !worker || worker.status !== WorkerStatus.Active} - onClick={handleOpen} - variant={variant} - color={variant === 'contained' ? 'info' : 'secondary'} - > - DELEGATE - </LoadingButton> - <ContractCallDialog - title="Delegate" - confirmButtonText="DELEGATE" - open={open} - onResult={confirmed => { - if (!confirmed) return handleClose(); - - formik.handleSubmit(); - }} - loading={isPending} - > - <Form onSubmit={formik.handleSubmit}> - <FormRow> - <FormikSelect - id="source" - disabled={!options.length} - showErrorOnlyOfTouched - options={options} - formik={formik} - onChange={e => { - const wallet = sources.find(w => w?.id === e.target.value); - if (!wallet) return; - - formik.setFieldValue('source', wallet.id); - formik.setFieldValue('max', fromSqd(wallet.balance).toFixed()); - }} - /> - </FormRow> - <FormRow> - <FormikTextInput - id="amount" - label="Amount" - formik={formik} - showErrorOnlyOfTouched - autoComplete="off" - InputProps={{ - endAdornment: ( - <Chip - clickable - disabled={formik.values.max === formik.values.amount} - onClick={() => { - formik.setValues({ - ...formik.values, - amount: formik.values.max, - }); - }} - label="Max" - /> - ), - }} - /> - </FormRow> - <FormDivider /> - <Stack direction="row" justifyContent="space-between" alignContent="center"> - <Box>Expected APR</Box> - <Stack direction="row" alignItems="center" spacing={0.5}> - <Box>{isExpectedAprPending ? '-' : percentFormatter(stakerApr)}</Box> - <HelpTooltip title={EXPECTED_APR_TIP} /> - </Stack> - </Stack> - - <BlockchainContractError error={error} /> - </Form> - </ContractCallDialog> - </> + <ContractCallDialog + title="Delegate worker" + open={open} + onResult={confirmed => { + if (!confirmed) return onClose(); + + formik.handleSubmit(); + }} + loading={isPending} + > + <Form onSubmit={formik.handleSubmit}> + <FormRow> + <FormikSelect + id="source" + showErrorOnlyOfTouched + options={ + sources?.map(s => { + return { + label: <SourceWalletOption source={s} />, + value: s.id, + disabled: isSourceDisabled(s), + }; + }) || [] + } + formik={formik} + onChange={e => { + const source = sources?.find(w => w?.id === e.target.value); + if (!source) return; + + formik.setFieldValue('source', source.id); + formik.setFieldValue('max', fromSqd(source.balance).toString()); + }} + /> + </FormRow> + <FormRow> + <FormikTextInput + id="amount" + label="Amount" + formik={formik} + showErrorOnlyOfTouched + autoComplete="off" + InputProps={{ + endAdornment: ( + <Chip + clickable + disabled={formik.values.max === formik.values.amount} + onClick={() => { + formik.setValues({ + ...formik.values, + amount: formik.values.max, + }); + }} + label="Max" + /> + ), + }} + /> + </FormRow> + <FormDivider /> + <Stack direction="row" justifyContent="space-between" alignContent="center"> + <HelpTooltip title={EXPECTED_APR_TIP}> + <span>Expected APR</span> + </HelpTooltip> + <span>{isExpectedAprPending ? '-' : percentFormatter(stakerApr)}</span> + </Stack> + </Form> + </ContractCallDialog> ); } diff --git a/src/pages/WorkersPage/WorkerEdit.tsx b/src/pages/WorkersPage/WorkerEdit.tsx index 2e24fe4..443eb3c 100644 --- a/src/pages/WorkersPage/WorkerEdit.tsx +++ b/src/pages/WorkersPage/WorkerEdit.tsx @@ -1,28 +1,77 @@ -import { LoadingButton } from '@mui/lab'; -import { Box } from '@mui/material'; +import { useState } from 'react'; + +import { peerIdToHex } from '@lib/network'; +import { EditOutlined } from '@mui/icons-material'; +import { IconButton, SxProps } from '@mui/material'; import { useFormik } from 'formik'; -import { useParams } from 'react-router-dom'; +import toast from 'react-hot-toast'; +import { useClient } from 'wagmi'; -import { useUpdateWorker } from '@api/contracts/worker-registration/useUpdateWorker'; -import { Worker, Account, useWorkerByPeerId } from '@api/subsquid-network-squid'; -import { BlockchainContractError } from '@components/BlockchainContractError'; -import { Card } from '@components/Card'; +import { useReadRouterWorkerRegistration, workerRegistryAbi } from '@api/contracts'; +import { useWriteSQDTransaction } from '@api/contracts/useWriteTransaction'; +import { errorMessage } from '@api/contracts/utils'; +import { encodeWorkerMetadata } from '@api/contracts/worker-registration/WorkerMetadata'; +import { Account, AccountType, Worker } from '@api/subsquid-network-squid'; +import { ContractCallDialog } from '@components/ContractCallDialog'; import { Form, FormikTextInput, FormRow } from '@components/Form'; -import { Loader } from '@components/Loader'; -import { NotFound } from '@components/NotFound'; -import { NetworkPageTitle, CenteredPageWrapper } from '@layouts/NetworkLayout'; -import { ConnectedWalletRequired } from '@network/ConnectedWalletRequired'; +import { useSquidHeight } from '@hooks/useSquidNetworkHeightHooks'; import { useAccount } from '@network/useAccount'; +import { useContracts } from '@network/useContracts'; import { editWorkerSchema } from '@pages/WorkersPage/worker-schema'; -function WorkerForm({ +export function WorkerEdit({ + sx, + disabled, + worker, + owner, +}: { + sx?: SxProps; + disabled?: boolean; + worker: Pick<Worker, 'peerId' | 'name' | 'website' | 'description' | 'email'>; + owner: Pick<Account, 'id' | 'type'>; +}) { + const [open, setOpen] = useState(false); + + return ( + <> + <IconButton + color="inherit" + sx={{ padding: 0, ...sx }} + onClick={() => setOpen(true)} + disabled={disabled} + > + <EditOutlined fontSize="inherit" /> + </IconButton> + <WorkerEditDialog worker={worker} owner={owner} open={open} onResult={() => setOpen(false)} /> + </> + ); +} + +function WorkerEditDialog({ worker, + owner, + open, + onResult, }: { - worker: Pick<Worker, 'name' | 'description' | 'website' | 'email' | 'peerId'> & { - owner: Pick<Account, 'id' | 'type'>; - }; + worker: Pick<Worker, 'name' | 'description' | 'website' | 'email' | 'peerId'>; + owner: Pick<Account, 'id' | 'type'>; + open: boolean; + onResult: (confirmed: boolean) => void; }) { - const { updateWorker, isLoading: isUpdating, error } = useUpdateWorker(); + const client = useClient(); + const account = useAccount(); + + const { setWaitHeight } = useSquidHeight(); + + const contracts = useContracts(); + const contractWriter = useWriteSQDTransaction(); + + const { data: registrationAddress, isLoading: isRegistrationAddressLoading } = + useReadRouterWorkerRegistration({ + address: contracts.ROUTER, + }); + + const isLoading = isRegistrationAddressLoading; const formik = useFormik({ initialValues: { @@ -30,25 +79,59 @@ function WorkerForm({ description: worker.description || '', website: worker.website || '', email: worker.email || '', + peerId: worker.peerId || '', }, validationSchema: editWorkerSchema, validateOnChange: true, validateOnBlur: true, validateOnMount: true, + enableReinitialize: true, onSubmit: async values => { - const { success } = await updateWorker({ - ...values, - peerId: worker.peerId, - source: worker.owner, - }); - if (!success) return; + if (!client || !account.address || !registrationAddress) return; + + try { + const metadata = editWorkerSchema.cast(values); + + const peerIdHex = peerIdToHex(worker.peerId); + + const receipt = await contractWriter.writeTransactionAsync({ + address: registrationAddress, + abi: workerRegistryAbi, + functionName: 'updateMetadata', + args: [peerIdHex, encodeWorkerMetadata(metadata)], + vesting: owner.type === AccountType.Vesting ? (owner.id as `0x${string}`) : undefined, + }); + setWaitHeight(receipt.blockNumber, []); + + onResult(true); + } catch (error) { + toast.error(errorMessage(error)); + } }, }); return ( - <Form onSubmit={formik.handleSubmit}> - <Card outlined> + <ContractCallDialog + title="Worker edit" + open={open} + onResult={confirmed => { + if (!confirmed) return onResult(confirmed); + + formik.handleSubmit(); + }} + loading={isLoading} + > + <Form onSubmit={formik.handleSubmit}> + <FormRow> + <FormikTextInput + showErrorOnlyOfTouched + id="peerId" + label="Peer ID" + formik={formik} + disabled + /> + </FormRow> <FormRow> <FormikTextInput showErrorOnlyOfTouched id="name" label="Worker name" formik={formik} /> </FormRow> @@ -65,35 +148,33 @@ function WorkerForm({ <FormRow> <FormikTextInput showErrorOnlyOfTouched id="website" label="Website" formik={formik} /> </FormRow> - - <BlockchainContractError error={error} /> - </Card> - <Box mt={3} justifyContent="flex-end" display="flex"> + {/* <Box mt={3} justifyContent="flex-end" display="flex"> <LoadingButton disabled={isUpdating} variant="contained" type="submit" color="info"> APPLY </LoadingButton> - </Box> - </Form> + </Box> */} + </Form> + </ContractCallDialog> ); } -export function WorkerEdit() { - const { peerId } = useParams<{ peerId: string }>(); - const { data: worker, isPending } = useWorkerByPeerId(peerId); - const { address } = useAccount(); +// export function WorkerEdit() { +// const { peerId } = useParams<{ peerId: string }>(); +// const { data: worker, isPending } = useWorkerByPeerId(peerId); +// const { address } = useAccount(); - return ( - <CenteredPageWrapper> - <ConnectedWalletRequired> - <NetworkPageTitle backPath={`/workers/${peerId}`} /> - {isPending ? ( - <Loader /> - ) : !worker || worker.realOwner.id !== address ? ( - <NotFound id={peerId} item="worker" /> - ) : ( - <WorkerForm worker={worker} /> - )} - </ConnectedWalletRequired> - </CenteredPageWrapper> - ); -} +// return ( +// <CenteredPageWrapper> +// <ConnectedWalletRequired> +// <NetworkPageTitle backPath={`/workers/${peerId}`} /> +// {isPending ? ( +// <Loader /> +// ) : !worker || worker.realOwner.id !== address ? ( +// <NotFound id={peerId} item="worker" /> +// ) : ( +// <WorkerForm worker={worker} /> +// )} +// </ConnectedWalletRequired> +// </CenteredPageWrapper> +// ); +// } diff --git a/src/pages/WorkersPage/WorkerName.tsx b/src/pages/WorkersPage/WorkerName.tsx index c163bbe..5d124c0 100644 --- a/src/pages/WorkersPage/WorkerName.tsx +++ b/src/pages/WorkersPage/WorkerName.tsx @@ -27,11 +27,7 @@ export const WorkerName = ({ }) => { return ( <Stack spacing={1.5} direction="row" alignItems="center"> - <Avatar - // online={!!worker.online} - name={worker.name || worker.peerId} - colorDiscriminator={worker.peerId} - /> + <Avatar name={worker.name || worker.peerId} colorDiscriminator={worker.peerId} /> <Box overflow="clip"> {worker.name ? ( <Name>{worker.name.length > 30 ? worker.name.slice(0, 27) + '...' : worker.name}</Name> diff --git a/src/pages/WorkersPage/WorkerStatus.tsx b/src/pages/WorkersPage/WorkerStatus.tsx index 60244bc..86efec9 100644 --- a/src/pages/WorkersPage/WorkerStatus.tsx +++ b/src/pages/WorkersPage/WorkerStatus.tsx @@ -1,10 +1,12 @@ import { useMemo } from 'react'; +import { dateFormat, relativeDateFormat } from '@i18n'; import { CircleRounded } from '@mui/icons-material'; import { Box, Chip as MaterialChip, Tooltip, chipClasses, styled } from '@mui/material'; import capitalize from 'lodash-es/capitalize'; +import { useDebounce } from 'use-debounce'; -import { WorkerStatus as Status, WorkerStatusFragmentFragment } from '@api/subsquid-network-squid'; +import { WorkerStatus as Status, Worker } from '@api/subsquid-network-squid'; export const Chip = styled(MaterialChip)(({ theme }) => ({ // [`&.${chipClasses.colorSuccess}`]: { @@ -21,10 +23,14 @@ export const Chip = styled(MaterialChip)(({ theme }) => ({ }, })); -export function WorkerStatus({ worker }: { worker: WorkerStatusFragmentFragment }) { +export function WorkerStatusChip({ + worker, +}: { + worker: Pick<Worker, 'status' | 'jailReason' | 'jailed' | 'online'> & { statusChangeAt?: string }; +}) { const { label, color, tip } = useMemo((): { label: string; - color: 'error' | 'success' | 'primary'; + color: 'error' | 'warning' | 'success' | 'primary'; tip?: string; } => { if (!worker.status) return { label: 'Unknown', color: 'primary' }; @@ -32,9 +38,9 @@ export function WorkerStatus({ worker }: { worker: WorkerStatusFragmentFragment switch (worker.status) { case Status.Active: if (worker.jailed) { - return { label: 'Jailed', color: 'error', tip: worker.jailReason || 'Unknown' }; + return { label: 'Jailed', color: 'warning', tip: worker.jailReason || 'Unknown' }; } else if (!worker.online) { - return { label: 'Offline', color: 'primary' }; + return { label: 'Offline', color: 'error' }; } return { label: 'Online', color: 'success' }; @@ -49,17 +55,33 @@ export function WorkerStatus({ worker }: { worker: WorkerStatusFragmentFragment return { label: capitalize(worker.status), color: 'primary' }; }, [worker.jailReason, worker.jailed, worker.online, worker.status]); + const [curTimestamp] = useDebounce(Date.now(), 1000); + const timeLeft = useMemo( + () => + worker.statusChangeAt ? relativeDateFormat(curTimestamp, worker.statusChangeAt) : undefined, + [curTimestamp, worker.statusChangeAt], + ); + const chip = ( - <Chip - color={color} - label={label} - variant="outlined" - icon={ - <Box display="flex" justifyContent="center"> - <CircleRounded sx={{ fontSize: 7 }} /> - </Box> + <Tooltip + title={ + worker.statusChangeAt + ? `Applies in ${timeLeft} (${dateFormat(worker.statusChangeAt, 'dateTime')})` + : '' } - /> + placement="top" + > + <Chip + color={color} + label={label} + variant="outlined" + icon={ + <Box display="flex" justifyContent="center"> + <CircleRounded sx={{ fontSize: 7 }} /> + </Box> + } + /> + </Tooltip> ); return tip ? ( diff --git a/src/pages/WorkersPage/WorkerUndelegate.tsx b/src/pages/WorkersPage/WorkerUndelegate.tsx index 38bdb10..08b33fc 100644 --- a/src/pages/WorkersPage/WorkerUndelegate.tsx +++ b/src/pages/WorkersPage/WorkerUndelegate.tsx @@ -1,24 +1,34 @@ -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useMemo, useState } from 'react'; -import { percentFormatter } from '@lib/formatters/formatters'; +import { dateFormat, relativeDateFormat } from '@i18n'; +import { percentFormatter, tokenFormatter } from '@lib/formatters/formatters'; import { fromSqd, toSqd } from '@lib/network'; +import { Lock } from '@mui/icons-material'; import { LoadingButton } from '@mui/lab'; -import { Box, Chip, Stack } from '@mui/material'; +import { Box, Chip, Stack, Tooltip } from '@mui/material'; import * as yup from '@schema'; -import BigNumber from 'bignumber.js'; import { useFormik } from 'formik'; +import toast from 'react-hot-toast'; import { useDebounce } from 'use-debounce'; -import { useWorkerUndelegate } from '@api/contracts/staking'; -import { Account, Delegation, Worker } from '@api/subsquid-network-squid'; -import { BlockchainContractError } from '@components/BlockchainContractError'; +import { stakingAbi, useReadRouterStaking } from '@api/contracts'; +import { useWriteSQDTransaction } from '@api/contracts/useWriteTransaction'; +import { errorMessage } from '@api/contracts/utils'; +import { AccountType, SourceWalletWithBalance, Worker } from '@api/subsquid-network-squid'; import { ContractCallDialog } from '@components/ContractCallDialog'; import { Form, FormDivider, FormikSelect, FormikTextInput, FormRow } from '@components/Form'; import { HelpTooltip } from '@components/HelpTooltip'; import { SourceWalletOption } from '@components/SourceWallet'; +import { useSquidHeight } from '@hooks/useSquidNetworkHeightHooks'; +import { useContracts } from '@network/useContracts'; import { EXPECTED_APR_TIP, useExpectedAprAfterDelegation } from './WorkerDelegate'; +export type SourceWalletWithDelegation = SourceWalletWithBalance & { + locked: boolean; + unlockedAt?: string; +}; + export const undelegateSchema = yup.object({ source: yup.string().label('Source').trim().required().typeError('${path} is invalid'), amount: yup @@ -34,69 +44,143 @@ export const undelegateSchema = yup.object({ export function WorkerUndelegate({ worker, disabled, + sources, }: { - worker?: Pick<Worker, 'id'> & { - delegations: (Pick<Delegation, 'deposit' | 'locked'> & { - owner: Pick<Account, 'id' | 'type'>; - })[]; - }; + sources?: SourceWalletWithDelegation[]; + worker?: Pick<Worker, 'id'>; disabled?: boolean; }) { - const { undelegateFromWorker, error, isPending } = useWorkerUndelegate(); - const [open, setOpen] = useState(false); - const handleOpen = (e: React.UIEvent) => { - e.stopPropagation(); - setOpen(true); - }; - const handleClose = () => setOpen(false); - - const options = useMemo( - () => - (worker?.delegations || []) - .filter(s => !BigNumber(s.deposit).isZero()) - .map(s => { - return { - label: ( - <SourceWalletOption - source={{ - id: s.owner.id, - balance: s.deposit, - type: s.owner.type, - }} - /> - ), - value: s.owner.id, - }; - }), - [worker?.delegations], + + const isSourceDisabled = (source: SourceWalletWithDelegation) => + source.balance === '0' || source.locked; + + const isLocked = useMemo(() => !!sources?.length && !sources?.some(d => !d.locked), [sources]); + + const [curTimestamp] = useDebounce(Date.now(), 1000); + const { unlockedAt, timeLeft } = useMemo(() => { + const min = sources?.reduce( + (r, d) => { + if (!d.unlockedAt) return r; + + const ms = new Date(d.unlockedAt).getTime(); + return !r || ms < r ? ms : r; + }, + undefined as number | undefined, + ); + + return { + unlockedAt: min ? new Date(min).toISOString() : undefined, + timeLeft: min ? relativeDateFormat(curTimestamp, min) : undefined, + }; + }, [curTimestamp, sources]); + + return ( + <> + <Box sx={{ position: 'relative', display: 'inline-flex', alignItems: 'center' }}> + {isLocked && ( + <Tooltip + title={unlockedAt && `Unlocks in ${timeLeft} (${dateFormat(unlockedAt, 'dateTime')})`} + placement="top" + > + <Lock + fontSize="small" + sx={{ + color: '#3e4a5c', + position: 'absolute', + top: '0px', + right: '0px', + transform: 'translate(0%, -25%)', + zIndex: 1, + }} + /> + </Tooltip> + )} + <LoadingButton + loading={open} + onClick={() => setOpen(true)} + variant="outlined" + color="error" + disabled={disabled || isLocked} + > + UNDELEGATE + </LoadingButton> + </Box> + <WorkerUndelegateDialog + open={open} + onClose={() => setOpen(false)} + worker={worker} + sources={sources} + isSourceDisabled={isSourceDisabled} + /> + </> ); +} + +function WorkerUndelegateDialog({ + open, + onClose, + worker, + sources, + isSourceDisabled, +}: { + open: boolean; + onClose: () => void; + worker?: Pick<Worker, 'id'>; + sources?: SourceWalletWithDelegation[]; + isSourceDisabled: (source: SourceWalletWithDelegation) => boolean; +}) { + const { setWaitHeight } = useSquidHeight(); + + const contracts = useContracts(); + const { writeTransactionAsync, isPending } = useWriteSQDTransaction(); + + const stakingAddress = useReadRouterStaking({ + address: contracts.ROUTER, + }); + + const initialValues = useMemo(() => { + const source = sources?.find(c => !isSourceDisabled(c)) || sources?.[0]; + + return { + source: source?.id || '', + amount: '0', + max: fromSqd(source?.balance).toString(), + }; + }, [sources, isSourceDisabled]); const formik = useFormik({ - initialValues: { - source: '', - amount: '', - max: '0', - }, + initialValues, validationSchema: undelegateSchema, validateOnChange: true, validateOnBlur: true, validateOnMount: true, + enableReinitialize: true, onSubmit: async values => { - const wallet = worker?.delegations.find(w => w?.owner.id === values.source); - if (!wallet || !worker) return; - - const { failedReason } = await undelegateFromWorker({ - worker, - amount: toSqd(values.amount), - wallet: { - id: wallet.owner.id, - type: wallet.owner.type, - }, - }); - if (!failedReason) { - handleClose(); + if (!stakingAddress.data) return; + if (!worker) return; + + try { + const { amount, source: sourceId } = undelegateSchema.cast(values); + + const source = sources?.find(w => w?.id === sourceId); + if (!source) return; + + const sqdAmount = BigInt(toSqd(amount)); + + const receipt = await writeTransactionAsync({ + abi: stakingAbi, + address: stakingAddress.data, + functionName: 'withdraw', + args: [BigInt(worker.id), sqdAmount], + vesting: source.type === AccountType.Vesting ? (source.id as `0x${string}`) : undefined, + }); + setWaitHeight(receipt.blockNumber, []); + + onClose(); + } catch (e) { + toast.error(errorMessage(e)); } }, }); @@ -108,104 +192,74 @@ export function WorkerUndelegate({ enabled: open && !!worker, }); - const source = useMemo(() => { - if (!worker) return; - - return ( - (formik.values.source - ? worker?.delegations.find(c => c.owner.id === formik.values.source) - : worker?.delegations.find(c => fromSqd(c.deposit).gte(0))) || worker?.delegations?.[0] - ); - }, [formik.values.source, worker]); - - useEffect(() => { - if (!source) return; - - formik.setValues({ - ...formik.values, - source: source.owner.id, - max: fromSqd(source.deposit).toFixed(), - }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [source]); + return ( + <ContractCallDialog + title="Undelegate worker" + open={open} + loading={isPending} + onResult={confirmed => { + if (!confirmed) return onClose(); - const canUndelegate = useMemo(() => { - return !!worker?.delegations.some(d => !d.locked && BigNumber(d.deposit).gt(0)); - }, [worker?.delegations]); + formik.handleSubmit(); + }} + confirmColor="error" + > + <Form onSubmit={formik.handleSubmit}> + <FormRow> + <FormikSelect + showErrorOnlyOfTouched + options={ + sources?.map(s => { + return { + label: <SourceWalletOption source={s} />, + value: s.id, + disabled: isSourceDisabled(s), + }; + }) || [] + } + id="source" + formik={formik} + onChange={e => { + const wallet = sources?.find(s => s.id === e.target.value); + if (!wallet) return; - return ( - <> - <LoadingButton - disabled={disabled || !canUndelegate} - color="error" - onClick={handleOpen} - variant="outlined" - > - UNDELEGATE - </LoadingButton> - <ContractCallDialog - title="Undelegate" - open={open} - loading={isPending} - onResult={confirmed => { - if (!confirmed) return handleClose(); - - formik.handleSubmit(); - }} - confirmColor="error" - > - <Form onSubmit={formik.handleSubmit}> - <FormRow> - <FormikSelect - disabled={!options.length} - showErrorOnlyOfTouched - options={options} - id="source" - formik={formik} - onChange={e => { - const wallet = worker?.delegations.find(s => s.owner.id === e.target.value); - if (!wallet) return; - - formik.setFieldValue('source', wallet.owner.id); - formik.setFieldValue('max', fromSqd(wallet.deposit).toFixed()); - }} - /> - </FormRow> - <FormRow> - <FormikTextInput - showErrorOnlyOfTouched - id="amount" - label="Amount" - formik={formik} - autoComplete="off" - InputProps={{ - endAdornment: ( - <Chip - clickable - disabled={formik.values.max === formik.values.amount} - onClick={() => { - formik.setValues({ - ...formik.values, - amount: formik.values.max, - }); - }} - label="Max" - /> - ), - }} - /> - </FormRow> - <FormDivider /> - <Stack direction="row" justifyContent="space-between" alignContent="center"> + formik.setFieldValue('source', wallet.id); + formik.setFieldValue('max', fromSqd(wallet.balance).toFixed()); + }} + /> + </FormRow> + <FormRow> + <FormikTextInput + showErrorOnlyOfTouched + id="amount" + label="Amount" + formik={formik} + autoComplete="off" + InputProps={{ + endAdornment: ( + <Chip + clickable + disabled={formik.values.max === formik.values.amount} + onClick={() => { + formik.setValues({ + ...formik.values, + amount: formik.values.max, + }); + }} + label="Max" + /> + ), + }} + /> + </FormRow> + <FormDivider /> + <Stack direction="row" justifyContent="space-between" alignContent="center"> + <HelpTooltip title={EXPECTED_APR_TIP}> <Box>Expected APR</Box> - <Stack direction="row" alignItems="center" spacing={0.5}> - <Box>{isExpectedAprPending ? '-' : percentFormatter(stakerApr)}</Box> - <HelpTooltip title={EXPECTED_APR_TIP} /> - </Stack> - </Stack> - <BlockchainContractError error={error} /> - </Form> - </ContractCallDialog> - </> + </HelpTooltip> + <span>{isExpectedAprPending ? '-' : percentFormatter(stakerApr)}</span> + </Stack> + </Form> + </ContractCallDialog> ); } diff --git a/src/pages/WorkersPage/WorkerUnregister.tsx b/src/pages/WorkersPage/WorkerUnregister.tsx index 6a322d6..7bc0ad0 100644 --- a/src/pages/WorkersPage/WorkerUnregister.tsx +++ b/src/pages/WorkersPage/WorkerUnregister.tsx @@ -1,66 +1,114 @@ -import React from 'react'; +import React, { useState } from 'react'; +import { peerIdToHex } from '@lib/network'; import { LoadingButton } from '@mui/lab'; -import { Box } from '@mui/material'; +import { SxProps } from '@mui/material'; +import toast from 'react-hot-toast'; +import { useClient } from 'wagmi'; -import { useUnregisterWorker } from '@api/contracts/worker-registration/useUnregisterWorker'; -import { useWithdrawWorker } from '@api/contracts/worker-registration/useWithdrawWorker'; -import { AccountType, useWorkerOwner, Worker, WorkerStatus } from '@api/subsquid-network-squid'; -import { BlockchainContractError } from '@components/BlockchainContractError'; +import { useReadRouterWorkerRegistration, workerRegistryAbi } from '@api/contracts'; +import { useWriteSQDTransaction } from '@api/contracts/useWriteTransaction'; +import { errorMessage } from '@api/contracts/utils'; +import { AccountType, SourceWallet, Worker } from '@api/subsquid-network-squid'; +import { ContractCallDialog } from '@components/ContractCallDialog'; +import { useSquidHeight } from '@hooks/useSquidNetworkHeightHooks'; +import { useAccount } from '@network/useAccount'; +import { useContracts } from '@network/useContracts'; -export function WorkerUnregister({ +export function WorkerUnregisterButton({ worker, + source, disabled, + sx, }: { + sx?: SxProps; worker: Pick<Worker, 'id' | 'status' | 'peerId'>; + source: SourceWallet; disabled?: boolean; }) { - const { - unregisterWorker, - error: unregisterError, - isLoading: isDeregistering, - } = useUnregisterWorker(); - const { withdrawWorker, error: withdrawError, isLoading: isWithdrawing } = useWithdrawWorker(); - const { data } = useWorkerOwner({ workerId: worker.id, enabled: !disabled }); + const [open, setOpen] = useState(false); return ( - <Box> - <Box sx={{ textAlign: 'right' }}> - {worker.status === WorkerStatus.Deregistered || worker.status === WorkerStatus.Withdrawn ? ( - <LoadingButton - loading={isWithdrawing} - onClick={async e => { - e.stopPropagation(); - await withdrawWorker({ - peerId: worker.peerId, - source: data?.owner || { id: '', type: AccountType.User }, - }); - }} - disabled={disabled || worker.status === WorkerStatus.Withdrawn} - variant="outlined" - color="error" - > - WITHDRAW - </LoadingButton> - ) : ( - <LoadingButton - loading={isDeregistering} - disabled={disabled || worker.status !== WorkerStatus.Active} - onClick={async e => { - e.stopPropagation(); - await unregisterWorker({ - peerId: worker.peerId, - source: data?.owner || { id: '', type: AccountType.User }, - }); - }} - variant="outlined" - color="error" - > - UNREGISTER - </LoadingButton> - )} - </Box> - <BlockchainContractError error={unregisterError || withdrawError} /> - </Box> + <> + <LoadingButton + // startIcon={<Remove />} + sx={sx} + loading={open} + onClick={() => setOpen(true)} + variant="outlined" + color="error" + disabled={disabled} + > + DELETE + </LoadingButton> + <WorkerUnregisterDialog + open={open} + onClose={() => setOpen(false)} + worker={worker} + source={source} + /> + </> + ); +} + +export function WorkerUnregisterDialog({ + open, + onClose, + worker, + source, +}: { + open: boolean; + onClose: () => void; + worker: Pick<Worker, 'id' | 'status' | 'peerId'>; + source: SourceWallet; +}) { + const client = useClient(); + const account = useAccount(); + + const { setWaitHeight } = useSquidHeight(); + + const contracts = useContracts(); + const contractWriter = useWriteSQDTransaction(); + + const { data: registrationAddress } = useReadRouterWorkerRegistration({ + address: contracts.ROUTER, + }); + + const handleSubmit = async () => { + if (!client || !account.address || !registrationAddress) return; + + try { + const peerIdHex = peerIdToHex(worker.peerId); + + const receipt = await contractWriter.writeTransactionAsync({ + address: registrationAddress, + abi: workerRegistryAbi, + functionName: 'deregister', + args: [peerIdHex], + vesting: source.type === AccountType.Vesting ? (source.id as `0x${string}`) : undefined, + }); + setWaitHeight(receipt.blockNumber, []); + + onClose(); + } catch (error) { + toast.error(errorMessage(error)); + } + }; + + return ( + <ContractCallDialog + title="Unregister worker?" + open={open} + onResult={confirmed => { + if (!confirmed) return onClose(); + handleSubmit(); + }} + loading={contractWriter.isPending} + hideCancelButton={false} + > + Are you sure you want to unregister this worker? This will disable the worker, but you can + re-register it later. You will be able to withdraw your tokens after the end of the lock + period. + </ContractCallDialog> ); } diff --git a/src/pages/WorkersPage/WorkerVersion.tsx b/src/pages/WorkersPage/WorkerVersion.tsx index b343ee0..7f732e1 100644 --- a/src/pages/WorkersPage/WorkerVersion.tsx +++ b/src/pages/WorkersPage/WorkerVersion.tsx @@ -1,8 +1,8 @@ -import { Box, Stack, styled } from '@mui/material'; +import { Report, Warning } from '@mui/icons-material'; +import { Box, Stack, styled, Tooltip } from '@mui/material'; import { satisfies } from 'semver'; import { Worker, useNetworkSettings } from '@api/subsquid-network-squid'; -import { WarningIcon } from '@icons/WarningIcon'; export const WorkerVersionName = styled(Box, { name: 'WorkerVersionName', @@ -22,9 +22,13 @@ export const WorkerVersion = ({ worker }: { worker: Pick<Worker, 'version'> }) = <Box display="flex"> {!satisfies(worker.version, recommendedWorkerVersion, { includePrerelease: true }) ? ( !satisfies(worker.version, minimalWorkerVersion, { includePrerelease: true }) ? ( - <WarningIcon color="error" /> + <Tooltip title="Unsupported" placement="top"> + <Report color="error" /> + </Tooltip> ) : ( - <WarningIcon color="warning" /> + <Tooltip title="Outdated" placement="top"> + <Warning color="warning" /> + </Tooltip> ) ) : null} </Box> diff --git a/src/pages/WorkersPage/WorkerWithdraw.tsx b/src/pages/WorkersPage/WorkerWithdraw.tsx new file mode 100644 index 0000000..145052b --- /dev/null +++ b/src/pages/WorkersPage/WorkerWithdraw.tsx @@ -0,0 +1,140 @@ +import React, { useState } from 'react'; + +import { dateFormat, relativeDateFormat } from '@i18n'; +import { peerIdToHex } from '@lib/network'; +import { Lock } from '@mui/icons-material'; +import { LoadingButton } from '@mui/lab'; +import { Box, SxProps, Tooltip } from '@mui/material'; +import toast from 'react-hot-toast'; +import { useClient } from 'wagmi'; + +import { useReadRouterWorkerRegistration, workerRegistryAbi } from '@api/contracts'; +import { useWriteSQDTransaction } from '@api/contracts/useWriteTransaction'; +import { errorMessage } from '@api/contracts/utils'; +import { AccountType, SourceWallet, Worker } from '@api/subsquid-network-squid'; +import { ContractCallDialog } from '@components/ContractCallDialog'; +import { useSquidHeight } from '@hooks/useSquidNetworkHeightHooks'; +import { useAccount } from '@network/useAccount'; +import { useContracts } from '@network/useContracts'; + +export function WorkerWithdrawButton({ + worker, + source, + disabled, + sx, +}: { + sx?: SxProps; + worker: Pick<Worker, 'id' | 'status' | 'peerId'>; + source: SourceWallet & { + locked: boolean; + unlockedAt?: string; + }; + disabled?: boolean; +}) { + const [open, setOpen] = useState(false); + + return ( + <> + <Box sx={{ position: 'relative', display: 'inline-flex', alignItems: 'center' }}> + {source.locked && ( + <Tooltip + title={`Unlocks in ${relativeDateFormat(Date.now(), source.unlockedAt)} (${dateFormat( + source.unlockedAt, + 'dateTime', + )})`} + placement="top" + > + <Lock + fontSize="small" + // color="secondary" + sx={{ + color: '#3e4a5c', + position: 'absolute', + top: '0px', + right: '0px', + transform: 'translate(0%, -25%)', + zIndex: 1, + }} + /> + </Tooltip> + )} + <LoadingButton + loading={open} + onClick={() => setOpen(true)} + variant="outlined" + color="error" + disabled={disabled || source.locked} + > + WITHDRAW + </LoadingButton> + </Box> + <WorkerWithdrawDialog + open={open} + onClose={() => setOpen(false)} + worker={worker} + source={source} + /> + </> + ); +} + +export function WorkerWithdrawDialog({ + open, + onClose, + worker, + source, +}: { + open: boolean; + onClose: () => void; + worker: Pick<Worker, 'id' | 'status' | 'peerId'>; + source: SourceWallet; +}) { + const client = useClient(); + const account = useAccount(); + const { setWaitHeight } = useSquidHeight(); + + const contracts = useContracts(); + const contractWriter = useWriteSQDTransaction(); + + const { data: registrationAddress } = useReadRouterWorkerRegistration({ + address: contracts.ROUTER, + }); + + const handleSubmit = async () => { + if (!client) return; + if (!account.address || !registrationAddress) return; + + try { + const peerIdHex = peerIdToHex(worker.peerId); + + const receipt = await contractWriter.writeTransactionAsync({ + address: registrationAddress, + abi: workerRegistryAbi, + functionName: 'withdraw', + args: [peerIdHex], + vesting: source.type === AccountType.Vesting ? (source.id as `0x${string}`) : undefined, + }); + setWaitHeight(receipt.blockNumber, []); + + onClose(); + } catch (e: unknown) { + toast.error(errorMessage(e)); + } + }; + + return ( + <ContractCallDialog + title="Unregister worker?" + open={open} + onResult={confirmed => { + if (!confirmed) return onClose(); + + handleSubmit(); + }} + loading={contractWriter.isPending} + hideCancelButton={false} + > + Are you sure you want to withdraw your tokens from this worker? + </ContractCallDialog> + ); +} diff --git a/src/pages/WorkersPage/WorkersPage.tsx b/src/pages/WorkersPage/WorkersPage.tsx index b5f223f..3960415 100644 --- a/src/pages/WorkersPage/WorkersPage.tsx +++ b/src/pages/WorkersPage/WorkersPage.tsx @@ -1,37 +1,45 @@ import { percentFormatter, tokenFormatter } from '@lib/formatters/formatters.ts'; import { fromSqd } from '@lib/network'; -import { Add } from '@mui/icons-material'; -import { Box, Button, TableBody, TableCell, TableHead, TableRow } from '@mui/material'; +import { Box, Button, Stack, TableBody, TableCell, TableHead, TableRow } from '@mui/material'; import { Link, Outlet } from 'react-router-dom'; -import { SortDir, useMyWorkers, WorkerSortBy } from '@api/subsquid-network-squid'; +import { + SortDir, + useMySources, + useMyWorkers, + WorkerSortBy, + WorkerStatus, +} from '@api/subsquid-network-squid'; import SquaredChip from '@components/Chip/SquaredChip'; -import { NoItems } from '@components/NoItems'; -import Placeholder from '@components/Placeholer'; -import { SortableHeaderCell } from '@components/Table/BorderedTable'; -import { DashboardTable } from '@components/Table/DashboardTable'; +import { DashboardTable, SortableHeaderCell, NoItems } from '@components/Table'; import { Location, useLocationState } from '@hooks/useLocationState'; import { CenteredPageWrapper } from '@layouts/NetworkLayout'; import { ConnectedWalletRequired } from '@network/ConnectedWalletRequired'; -import { useAccount } from '@network/useAccount'; import { useContracts } from '@network/useContracts'; import { WorkerName } from '@pages/WorkersPage/WorkerName'; -import { WorkerStatus } from '@pages/WorkersPage/WorkerStatus'; +import { WorkerStatusChip } from '@pages/WorkersPage/WorkerStatus'; -import { WorkerUnregister } from './WorkerUnregister'; +import { AddWorkerButton } from './AddNewWorker'; +import { WorkerUnregisterButton } from './WorkerUnregister'; import { WorkerVersion } from './WorkerVersion'; +import { WorkerWithdrawButton } from './WorkerWithdraw'; export function MyWorkers() { const [query, setQuery] = useLocationState({ sortBy: new Location.Enum<WorkerSortBy>(WorkerSortBy.WorkerReward), sortDir: new Location.Enum<SortDir>(SortDir.Desc), }); - const { data, isLoading } = useMyWorkers({ + + const { SQD_TOKEN } = useContracts(); + + const { data: sources, isLoading: isSourcesLoading } = useMySources(); + + const { data: workers, isLoading: isWorkersLoading } = useMyWorkers({ sortBy: query.sortBy as WorkerSortBy, sortDir: query.sortDir as SortDir, }); - const { isConnected } = useAccount(); - const { SQD_TOKEN } = useContracts(); + + const isLoading = isSourcesLoading || isWorkersLoading; return ( <Box> @@ -40,16 +48,18 @@ export function MyWorkers() { title={ <> <SquaredChip label="My Workers" color="primary" /> - <Button - color="info" - startIcon={<Add />} - variant="contained" - disabled={!isConnected} - component={Link} - to="/workers/add" - > - ADD WORKER - </Button> + <Stack direction="row" spacing={1}> + <Button + color="secondary" + variant="outlined" + component={Link} + target="_blank" + to="https://docs.sqd.dev/subsquid-network/participate/worker/" + > + LEARN MORE + </Button> + <AddWorkerButton sources={sources} disabled={isLoading} /> + </Stack> </> } > @@ -88,15 +98,15 @@ export function MyWorkers() { </TableRow> </TableHead> <TableBody> - {data.length ? ( - data.map(worker => { + {workers?.length ? ( + workers.map(worker => { return ( <TableRow key={worker.peerId}> <TableCell> <WorkerName worker={worker} to={`/workers/${worker.peerId}`} /> </TableCell> <TableCell> - <WorkerStatus worker={worker} /> + <WorkerStatusChip worker={worker} /> </TableCell> <TableCell> <WorkerVersion worker={worker} /> @@ -112,16 +122,34 @@ export function MyWorkers() { </TableCell> <TableCell> <Box display="flex" justifyContent="flex-end"> - <WorkerUnregister worker={worker} /> + {worker.status === WorkerStatus.Deregistered || + worker.status === WorkerStatus.Deregistering ? ( + <WorkerWithdrawButton + worker={worker} + source={{ + ...worker.owner, + // FIXME: some types issue + locked: (worker as any).locked, + unlockedAt: (worker as any).unlockedAt, + }} + disabled={worker.status !== WorkerStatus.Deregistered} + /> + ) : ( + <WorkerUnregisterButton + worker={worker} + source={worker.owner} + disabled={worker.status !== WorkerStatus.Active} + /> + )} </Box> </TableCell> </TableRow> ); }) ) : ( - <Placeholder> - <NoItems /> - </Placeholder> + <NoItems> + <span>No worker registered yet</span> + </NoItems> )} </TableBody> </> diff --git a/src/schema/decimal.ts b/src/schema/decimal.ts index 3e1c8ba..ab3c5bc 100644 --- a/src/schema/decimal.ts +++ b/src/schema/decimal.ts @@ -57,7 +57,8 @@ class DecimalSchema< this.withMutation(() => { this.transform(value => { - return BigNumber(value); + const res = BigNumber(value || 0); + return res.isNaN() ? BigNumber(0) : res; }); }); } diff --git a/src/theme/theme.tsx b/src/theme/theme.tsx index 42f0509..f702fdb 100644 --- a/src/theme/theme.tsx +++ b/src/theme/theme.tsx @@ -144,14 +144,14 @@ declare module '@mui/material/styles/createPalette' { } } -declare module 'notistack' { - interface VariantOverrides { - subsquid: { - title: string; - severity: 'warning' | 'success' | 'error' | 'info'; - }; - } -} +// declare module 'notistack' { +// interface VariantOverrides { +// subsquid: { +// title: string; +// severity: 'warning' | 'success' | 'error' | 'info'; +// }; +// } +// } const fontFamily = `'Matter', 'Inter', sans-serif`; @@ -179,14 +179,14 @@ export const useCreateTheme = (mode: PaletteType) => { typography: { fontFamily, h1: { - fontSize: 64, + fontSize: 60, lineHeight: 1, fontWeight: 500, color: colors.text.primary, letterSpacing: '-0.01rem', }, h2: { - fontSize: 40, + fontSize: 36, lineHeight: 1, fontWeight: 500, color: colors.text?.primary, @@ -306,7 +306,7 @@ export const useCreateTheme = (mode: PaletteType) => { styleOverrides: { root: { textTransform: 'none', - transition: 'all 300ms ease-out', + // transition: 'all 300ms ease-out', borderRadius: 360, boxShadow: 'none', textShadow: 'none', @@ -387,6 +387,13 @@ export const useCreateTheme = (mode: PaletteType) => { }, }, }, + // MuiInput: { + // styleOverrides: { + // root: { + // backgroundColor: colors.background.paper, + // }, + // }, + // }, MuiFilledInput: { styleOverrides: { root: { @@ -411,15 +418,15 @@ export const useCreateTheme = (mode: PaletteType) => { '&:after': { display: 'none', }, - // '&:active': { - // borderStyle: 'solid', - // borderWidth: '1px', - // borderColor: colors.divider, - // }, + + '&.Mui-focused': { + borderColor: colors.secondary.main, + }, + + borderColor: colors.divider, borderRadius: 4, borderStyle: 'solid', borderWidth: '1px', - borderColor: colors.divider, backgroundColor: colors.background.paper, }, sizeSmall: { @@ -437,6 +444,9 @@ export const useCreateTheme = (mode: PaletteType) => { // backgroundClip: 'text', // fontFamily, }, + + borderRadius: 4, + backgroundColor: colors.background.paper, }, inputHiddenLabel: { paddingTop: `${spacing}px !important`, diff --git a/tsconfig.json b/tsconfig.json index 1f33217..cc08f86 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -20,25 +20,25 @@ "jsx": "react-jsx", /* Paths */ - "baseUrl": "./", + // "baseUrl": "./", "paths": { - "@components/*": ["src/components/*"], - "@layouts/*": ["src/layouts/*"], - "@contexts/*": ["src/contexts/*"], - "@loaders/*": ["src/loaders/*"], - "@hooks/*": ["src/hooks/*"], - "@utils/*": ["src/utils/*"], - "@pages/*": ["src/pages/*"], - "@api/*": ["src/api/*"], - "@lib/*": ["src/lib/*"], - "@icons/*": ["src/icons/*"], - "@images/*": ["src/images/*"], - "@i18n": ["src/i18n/index"], - "@logger": ["src/logger/index"], - "@apps": ["src/apps/apps"], - "@apps/*": ["src/apps/*"], - "@network/*": ["src/network/*"], - "@schema": ["src/schema"], + "@components/*": ["./src/components/*"], + "@layouts/*": ["./src/layouts/*"], + "@contexts/*": ["./src/contexts/*"], + "@loaders/*": ["./src/loaders/*"], + "@hooks/*": ["./src/hooks/*"], + "@utils/*": ["./src/utils/*"], + "@pages/*": ["./src/pages/*"], + "@api/*": ["./src/api/*"], + "@lib/*": ["./src/lib/*"], + "@icons/*": ["./src/icons/*"], + "@images/*": ["./src/images/*"], + "@i18n": ["./src/i18n/index"], + "@logger": ["./src/logger/index"], + "@apps": ["./src/apps/apps"], + "@apps/*": ["./src/apps/*"], + "@network/*": ["./src/network/*"], + "@schema": ["./src/schema"], } }, "include": [ diff --git a/tsconfig.node.json b/tsconfig.node.json index 97ede7e..2b2f61d 100644 --- a/tsconfig.node.json +++ b/tsconfig.node.json @@ -7,5 +7,6 @@ "allowSyntheticDefaultImports": true, "strict": true }, - "include": ["vite.config.ts"] + "include": ["vite.config.ts"], + "exclude": ["dist", "node_modules"], } diff --git a/vite.config.ts b/vite.config.ts index a454c8f..5529713 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -40,7 +40,13 @@ export default defineConfig({ }, optimizeDeps: { - include: ['@mui/material', '@emotion/react', '@emotion/styled'], + include: [ + '@mui/material', + '@emotion/react', + '@emotion/styled', + '@mui/material/styles', + '@mui/material/Unstable_Grid2', + ], }, plugins: [ tsconfigPaths(), diff --git a/wagmi.config.ts b/wagmi.config.ts new file mode 100644 index 0000000..cd985f2 --- /dev/null +++ b/wagmi.config.ts @@ -0,0 +1,857 @@ +import { defineConfig } from '@wagmi/cli'; +import { react } from '@wagmi/cli/plugins'; +import { erc20Abi } from 'viem'; + +export default defineConfig({ + out: 'src/api/contracts/subsquid.generated.ts', + contracts: [ + { + name: 'SQD', + abi: erc20Abi, + }, + { + name: 'Router', + abi: [ + { + type: 'function', + name: 'networkController', + inputs: [], + outputs: [ + { + name: '', + type: 'address', + internalType: 'contract INetworkController', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'rewardCalculation', + inputs: [], + outputs: [ + { + name: '', + type: 'address', + internalType: 'contract IRewardCalculation', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'rewardTreasury', + inputs: [], + outputs: [ + { + name: '', + type: 'address', + internalType: 'address', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'staking', + inputs: [], + outputs: [ + { + name: '', + type: 'address', + internalType: 'contract IStaking', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'workerRegistration', + inputs: [], + outputs: [ + { + name: '', + type: 'address', + internalType: 'contract IWorkerRegistration', + }, + ], + stateMutability: 'view', + }, + ], + }, + { + name: 'WorkerRegistry', + abi: [ + { + type: 'function', + name: 'bondAmount', + inputs: [], + outputs: [ + { + name: '', + type: 'uint256', + internalType: 'uint256', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'register', + inputs: [ + { + name: 'peerId', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'metadata', + type: 'string', + internalType: 'string', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'updateMetadata', + inputs: [ + { + name: 'peerId', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'metadata', + type: 'string', + internalType: 'string', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + inputs: [ + { + internalType: 'bytes', + name: 'peerId', + type: 'bytes', + }, + ], + name: 'deregister', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + type: 'function', + name: 'withdraw', + inputs: [ + { + name: 'peerId', + type: 'bytes', + internalType: 'bytes', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'lockPeriod', + inputs: [], + outputs: [{ name: '', type: 'uint128', internalType: 'uint128' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getWorker', + inputs: [{ name: 'workerId', type: 'uint256', internalType: 'uint256' }], + outputs: [ + { + name: '', + type: 'tuple', + internalType: 'struct WorkerRegistration.Worker', + components: [ + { name: 'creator', type: 'address', internalType: 'address' }, + { name: 'peerId', type: 'bytes', internalType: 'bytes' }, + { name: 'bond', type: 'uint256', internalType: 'uint256' }, + { name: 'registeredAt', type: 'uint128', internalType: 'uint128' }, + { name: 'deregisteredAt', type: 'uint128', internalType: 'uint128' }, + { name: 'metadata', type: 'string', internalType: 'string' }, + ], + }, + ], + stateMutability: 'view', + }, + ], + }, + { + name: 'Staking', + abi: [ + { + type: 'function', + name: 'getDeposit', + inputs: [ + { name: 'staker', type: 'address', internalType: 'address' }, + { name: 'worker', type: 'uint256', internalType: 'uint256' }, + ], + outputs: [ + { name: 'depositAmount', type: 'uint256', internalType: 'uint256' }, + { name: 'withdrawAllowed', type: 'uint256', internalType: 'uint256' }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'deposit', + inputs: [ + { + name: 'worker', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'amount', + type: 'uint256', + internalType: 'uint256', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'withdraw', + inputs: [ + { + type: 'uint256', + name: 'worker', + }, + { + type: 'uint256', + name: 'amount', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'claimable', + inputs: [ + { + name: 'staker', + type: 'address', + internalType: 'address', + }, + ], + outputs: [ + { + name: '', + type: 'uint256', + internalType: 'uint256', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'delegated', + inputs: [ + { + name: 'worker', + type: 'uint256', + internalType: 'uint256', + }, + ], + outputs: [ + { + name: '', + type: 'uint256', + internalType: 'uint256', + }, + ], + stateMutability: 'view', + }, + ], + }, + { + name: 'Vesting', + abi: [ + { + type: 'function', + name: 'depositedIntoProtocol', + inputs: [], + outputs: [ + { + name: '', + type: 'uint256', + internalType: 'uint256', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'duration', + inputs: [], + outputs: [ + { + name: '', + type: 'uint256', + internalType: 'uint256', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'end', + inputs: [], + outputs: [ + { + name: '', + type: 'uint256', + internalType: 'uint256', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'execute', + inputs: [ + { + name: 'to', + type: 'address', + internalType: 'address', + }, + { + name: 'data', + type: 'bytes', + internalType: 'bytes', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'execute', + inputs: [ + { + name: 'to', + type: 'address', + internalType: 'address', + }, + { + name: 'data', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'requiredApprove', + type: 'uint256', + internalType: 'uint256', + }, + ], + outputs: [ + { + name: '', + type: 'bytes', + internalType: 'bytes', + }, + ], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'expectedTotalAmount', + inputs: [], + outputs: [ + { + name: '', + type: 'uint256', + internalType: 'uint256', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'immediateReleaseBIP', + inputs: [], + outputs: [ + { + name: '', + type: 'uint256', + internalType: 'uint256', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'releasable', + inputs: [ + { + name: 'token', + type: 'address', + internalType: 'address', + }, + ], + outputs: [ + { + name: '', + type: 'uint256', + internalType: 'uint256', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'releasable', + inputs: [], + outputs: [ + { + name: '', + type: 'uint256', + internalType: 'uint256', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'release', + inputs: [ + { + name: 'token', + type: 'address', + internalType: 'address', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'released', + inputs: [ + { + name: 'token', + type: 'address', + internalType: 'address', + }, + ], + outputs: [ + { + name: '', + type: 'uint256', + internalType: 'uint256', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'start', + inputs: [], + outputs: [ + { + name: '', + type: 'uint256', + internalType: 'uint256', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'vestedAmount', + inputs: [ + { + name: 'token', + type: 'address', + internalType: 'address', + }, + { + name: 'timestamp', + type: 'uint64', + internalType: 'uint64', + }, + ], + outputs: [ + { + name: '', + type: 'uint256', + internalType: 'uint256', + }, + ], + stateMutability: 'view', + }, + ], + }, + { + name: 'GatewayRegistry', + abi: [ + { + type: 'function', + name: 'register', + inputs: [ + { + name: 'peerId', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'metadata', + type: 'string', + internalType: 'string', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'setMetadata', + inputs: [ + { + name: 'peerId', + type: 'bytes', + internalType: 'bytes', + }, + { + name: 'metadata', + type: 'string', + internalType: 'string', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'stake', + inputs: [ + { + name: 'amount', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'durationBlocks', + type: 'uint128', + internalType: 'uint128', + }, + { + name: 'withAutoExtension', + type: 'bool', + internalType: 'bool', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'unregister', + inputs: [ + { + name: 'peerId', + type: 'bytes', + internalType: 'bytes', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'unstake', + inputs: [], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'computationUnitsAmount', + inputs: [ + { + name: 'amount', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'durationBlocks', + type: 'uint256', + internalType: 'uint256', + }, + ], + outputs: [ + { + name: '', + type: 'uint256', + internalType: 'uint256', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'addStake', + inputs: [ + { + name: 'amount', + type: 'uint256', + internalType: 'uint256', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'disableAutoExtension', + inputs: [], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'enableAutoExtension', + inputs: [], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'canUnstake', + inputs: [ + { + name: 'operator', + type: 'address', + internalType: 'address', + }, + ], + outputs: [ + { + name: '', + type: 'bool', + internalType: 'bool', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'computationUnitsAmount', + inputs: [ + { + name: 'amount', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'durationBlocks', + type: 'uint256', + internalType: 'uint256', + }, + ], + outputs: [ + { + name: '', + type: 'uint256', + internalType: 'uint256', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'getStake', + inputs: [ + { + name: 'operator', + type: 'address', + internalType: 'address', + }, + ], + outputs: [ + { + name: '', + type: 'tuple', + internalType: 'struct IGatewayRegistry.Stake', + components: [ + { + name: 'amount', + type: 'uint256', + internalType: 'uint256', + }, + { + name: 'lockStart', + type: 'uint128', + internalType: 'uint128', + }, + { + name: 'lockEnd', + type: 'uint128', + internalType: 'uint128', + }, + { + name: 'duration', + type: 'uint128', + internalType: 'uint128', + }, + { + name: 'autoExtension', + type: 'bool', + internalType: 'bool', + }, + { + name: 'oldCUs', + type: 'uint256', + internalType: 'uint256', + }, + ], + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'minStake', + inputs: [], + outputs: [ + { + name: '', + type: 'uint256', + internalType: 'uint256', + }, + ], + stateMutability: 'view', + }, + ], + }, + { + name: 'RewardTreasury', + abi: [ + { + type: 'function', + name: 'claimFor', + inputs: [ + { + name: 'rewardDistribution', + type: 'address', + internalType: 'contract IRewardsDistribution', + }, + { + name: 'receiver', + type: 'address', + internalType: 'address', + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + ], + }, + { + name: 'SoftCap', + abi: [ + { + type: 'function', + name: 'cap', + inputs: [{ name: 'x', type: 'uint256', internalType: 'UD60x18' }], + outputs: [{ name: '', type: 'uint256', internalType: 'UD60x18' }], + stateMutability: 'pure', + }, + { + type: 'function', + name: 'capedStake', + inputs: [{ name: 'workerId', type: 'uint256', internalType: 'uint256' }], + outputs: [{ name: '', type: 'uint256', internalType: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'capedStakeAfterDelegation', + inputs: [ + { name: 'workerId', type: 'uint256', internalType: 'uint256' }, + { name: 'delegationAmount', type: 'int256', internalType: 'int256' }, + ], + outputs: [{ name: '', type: 'uint256', internalType: 'uint256' }], + stateMutability: 'view', + }, + ], + }, + { + name: 'NetworkController', + abi: [ + { + type: 'function', + name: 'bondAmount', + inputs: [], + outputs: [ + { + name: '', + type: 'uint256', + internalType: 'uint256', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'epochLength', + inputs: [], + outputs: [ + { + name: '', + type: 'uint128', + internalType: 'uint128', + }, + ], + stateMutability: 'view', + }, + { + type: 'function', + name: 'workerEpochLength', + inputs: [], + outputs: [ + { + name: '', + type: 'uint128', + internalType: 'uint128', + }, + ], + stateMutability: 'view', + }, + ], + }, + { + name: 'ArbMulticall', + abi: [ + { + inputs: [], + name: 'getCurrentBlockTimestamp', + outputs: [{ internalType: 'uint256', name: 'timestamp', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getL1BlockNumber', + outputs: [{ internalType: 'uint256', name: 'l1BlockNumber', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + ], + }, + ], + plugins: [react({})], +}); diff --git a/yarn.lock b/yarn.lock index 15abb3d..3f592d5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12,10 +12,10 @@ __metadata: languageName: node linkType: hard -"@adraffy/ens-normalize@npm:1.10.0": - version: 1.10.0 - resolution: "@adraffy/ens-normalize@npm:1.10.0" - checksum: 10c0/78ae700847a2516d5a0ae12c4e23d09392a40c67e73b137eb7189f51afb1601c8d18784aeda2ed288a278997824dc924d1f398852c21d41ee2c4c564f2fb4d26 +"@adraffy/ens-normalize@npm:1.11.0": + version: 1.11.0 + resolution: "@adraffy/ens-normalize@npm:1.11.0" + checksum: 10c0/5111d0f1a273468cb5661ed3cf46ee58de8f32f84e2ebc2365652e66c1ead82649df94c736804e2b9cfa831d30ef24e1cc3575d970dbda583416d3a98d8870a6 languageName: node linkType: hard @@ -886,6 +886,15 @@ __metadata: languageName: node linkType: hard +"@babel/runtime@npm:^7.23.2": + version: 7.24.7 + resolution: "@babel/runtime@npm:7.24.7" + dependencies: + regenerator-runtime: "npm:^0.14.0" + checksum: 10c0/b6fa3ec61a53402f3c1d75f4d808f48b35e0dfae0ec8e2bb5c6fc79fb95935da75766e0ca534d0f1c84871f6ae0d2ebdd950727cfadb745a2cdbef13faef5513 + languageName: node + linkType: hard + "@babel/template@npm:^7.18.10, @babel/template@npm:^7.20.7, @babel/template@npm:^7.22.15, @babel/template@npm:^7.24.0": version: 7.24.0 resolution: "@babel/template@npm:7.24.0" @@ -1139,6 +1148,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/aix-ppc64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/aix-ppc64@npm:0.19.12" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + "@esbuild/aix-ppc64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/aix-ppc64@npm:0.21.5" @@ -1146,6 +1162,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-arm64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/android-arm64@npm:0.19.12" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/android-arm64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/android-arm64@npm:0.21.5" @@ -1153,6 +1176,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-arm@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/android-arm@npm:0.19.12" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + "@esbuild/android-arm@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/android-arm@npm:0.21.5" @@ -1160,6 +1190,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-x64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/android-x64@npm:0.19.12" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + "@esbuild/android-x64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/android-x64@npm:0.21.5" @@ -1167,6 +1204,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/darwin-arm64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/darwin-arm64@npm:0.19.12" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/darwin-arm64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/darwin-arm64@npm:0.21.5" @@ -1174,6 +1218,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/darwin-x64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/darwin-x64@npm:0.19.12" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + "@esbuild/darwin-x64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/darwin-x64@npm:0.21.5" @@ -1181,6 +1232,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/freebsd-arm64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/freebsd-arm64@npm:0.19.12" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/freebsd-arm64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/freebsd-arm64@npm:0.21.5" @@ -1188,6 +1246,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/freebsd-x64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/freebsd-x64@npm:0.19.12" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/freebsd-x64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/freebsd-x64@npm:0.21.5" @@ -1195,6 +1260,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-arm64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/linux-arm64@npm:0.19.12" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/linux-arm64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/linux-arm64@npm:0.21.5" @@ -1202,6 +1274,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-arm@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/linux-arm@npm:0.19.12" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + "@esbuild/linux-arm@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/linux-arm@npm:0.21.5" @@ -1209,6 +1288,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-ia32@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/linux-ia32@npm:0.19.12" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + "@esbuild/linux-ia32@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/linux-ia32@npm:0.21.5" @@ -1216,6 +1302,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-loong64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/linux-loong64@npm:0.19.12" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + "@esbuild/linux-loong64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/linux-loong64@npm:0.21.5" @@ -1223,6 +1316,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-mips64el@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/linux-mips64el@npm:0.19.12" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + "@esbuild/linux-mips64el@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/linux-mips64el@npm:0.21.5" @@ -1230,6 +1330,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-ppc64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/linux-ppc64@npm:0.19.12" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + "@esbuild/linux-ppc64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/linux-ppc64@npm:0.21.5" @@ -1237,6 +1344,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-riscv64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/linux-riscv64@npm:0.19.12" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + "@esbuild/linux-riscv64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/linux-riscv64@npm:0.21.5" @@ -1244,6 +1358,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-s390x@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/linux-s390x@npm:0.19.12" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + "@esbuild/linux-s390x@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/linux-s390x@npm:0.21.5" @@ -1251,6 +1372,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-x64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/linux-x64@npm:0.19.12" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + "@esbuild/linux-x64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/linux-x64@npm:0.21.5" @@ -1258,6 +1386,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/netbsd-x64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/netbsd-x64@npm:0.19.12" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/netbsd-x64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/netbsd-x64@npm:0.21.5" @@ -1265,6 +1400,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/openbsd-x64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/openbsd-x64@npm:0.19.12" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/openbsd-x64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/openbsd-x64@npm:0.21.5" @@ -1272,6 +1414,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/sunos-x64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/sunos-x64@npm:0.19.12" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + "@esbuild/sunos-x64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/sunos-x64@npm:0.21.5" @@ -1279,6 +1428,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-arm64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/win32-arm64@npm:0.19.12" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/win32-arm64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/win32-arm64@npm:0.21.5" @@ -1286,6 +1442,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-ia32@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/win32-ia32@npm:0.19.12" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + "@esbuild/win32-ia32@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/win32-ia32@npm:0.21.5" @@ -1293,6 +1456,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-x64@npm:0.19.12": + version: 0.19.12 + resolution: "@esbuild/win32-x64@npm:0.19.12" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@esbuild/win32-x64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/win32-x64@npm:0.21.5" @@ -2369,7 +2539,7 @@ __metadata: languageName: node linkType: hard -"@metamask/json-rpc-engine@npm:^7.0.0, @metamask/json-rpc-engine@npm:^7.3.2": +"@metamask/json-rpc-engine@npm:^7.0.0": version: 7.3.3 resolution: "@metamask/json-rpc-engine@npm:7.3.3" dependencies: @@ -2380,15 +2550,26 @@ __metadata: languageName: node linkType: hard -"@metamask/json-rpc-middleware-stream@npm:^6.0.2": - version: 6.0.2 - resolution: "@metamask/json-rpc-middleware-stream@npm:6.0.2" +"@metamask/json-rpc-engine@npm:^8.0.1, @metamask/json-rpc-engine@npm:^8.0.2": + version: 8.0.2 + resolution: "@metamask/json-rpc-engine@npm:8.0.2" dependencies: - "@metamask/json-rpc-engine": "npm:^7.3.2" + "@metamask/rpc-errors": "npm:^6.2.1" + "@metamask/safe-event-emitter": "npm:^3.0.0" + "@metamask/utils": "npm:^8.3.0" + checksum: 10c0/57a584e713be98837b56b1985fc14020b74939af200c304e9dcde0a59b622f0d4b1fd07a9032dd3652b72ce330e47db8b9aa13402a443ad8c09667a4204c4c17 + languageName: node + linkType: hard + +"@metamask/json-rpc-middleware-stream@npm:^7.0.1": + version: 7.0.2 + resolution: "@metamask/json-rpc-middleware-stream@npm:7.0.2" + dependencies: + "@metamask/json-rpc-engine": "npm:^8.0.2" "@metamask/safe-event-emitter": "npm:^3.0.0" "@metamask/utils": "npm:^8.3.0" readable-stream: "npm:^3.6.2" - checksum: 10c0/a91b8d834253a1700d96cf0f08d2362e2db58365f751cb3e60b3c5e9422a1f443a8a515d5a653ced59535726717d0f827c1aaf2a33dd33efb96a05f653bb0915 + checksum: 10c0/5819e5cd1460046d309218110a76727d5b5b7b0fb379efd2e938e145905a359c2b6d4278d390760227ad5823e3f4bcaa001cbb5abeeeb014b08badbb1fa29f1f languageName: node linkType: hard @@ -2411,15 +2592,15 @@ __metadata: languageName: node linkType: hard -"@metamask/providers@npm:^15.0.0": - version: 15.0.0 - resolution: "@metamask/providers@npm:15.0.0" +"@metamask/providers@npm:16.1.0": + version: 16.1.0 + resolution: "@metamask/providers@npm:16.1.0" dependencies: - "@metamask/json-rpc-engine": "npm:^7.3.2" - "@metamask/json-rpc-middleware-stream": "npm:^6.0.2" + "@metamask/json-rpc-engine": "npm:^8.0.1" + "@metamask/json-rpc-middleware-stream": "npm:^7.0.1" "@metamask/object-multiplex": "npm:^2.0.0" "@metamask/rpc-errors": "npm:^6.2.1" - "@metamask/safe-event-emitter": "npm:^3.0.0" + "@metamask/safe-event-emitter": "npm:^3.1.1" "@metamask/utils": "npm:^8.3.0" detect-browser: "npm:^5.2.0" extension-port-stream: "npm:^3.0.0" @@ -2427,7 +2608,7 @@ __metadata: is-stream: "npm:^2.0.0" readable-stream: "npm:^3.6.2" webextension-polyfill: "npm:^0.10.0" - checksum: 10c0/c079cb8440f7cbd8ba863070a8c5c1ada4ad99e31694ec7b0c537b1cb11e66f9d4271e737633ce89f98248208ba076bfc90ddab94ce0299178fdab9a8489fb09 + checksum: 10c0/ef0fe2cad0db6e2fd1c0b73894419e4dc153e1742e8b16e233164eaec941ef3d4859728e4a2e733e818b56093abd889fc96c7a75dccf9878cbdab45fd3b36e2c languageName: node linkType: hard @@ -2448,21 +2629,21 @@ __metadata: languageName: node linkType: hard -"@metamask/safe-event-emitter@npm:^3.0.0": +"@metamask/safe-event-emitter@npm:^3.0.0, @metamask/safe-event-emitter@npm:^3.1.1": version: 3.1.1 resolution: "@metamask/safe-event-emitter@npm:3.1.1" checksum: 10c0/4dd51651fa69adf65952449b20410acac7edad06f176dc6f0a5d449207527a2e85d5a21a864566e3d8446fb259f8840bd69fdb65932007a882f771f473a2b682 languageName: node linkType: hard -"@metamask/sdk-communication-layer@npm:0.26.2": - version: 0.26.2 - resolution: "@metamask/sdk-communication-layer@npm:0.26.2" +"@metamask/sdk-communication-layer@npm:0.28.2": + version: 0.28.2 + resolution: "@metamask/sdk-communication-layer@npm:0.28.2" dependencies: bufferutil: "npm:^4.0.8" date-fns: "npm:^2.29.3" debug: "npm:^4.3.4" - utf-8-validate: "npm:^6.0.3" + utf-8-validate: "npm:^5.0.2" uuid: "npm:^8.3.2" peerDependencies: cross-fetch: ^4.0.0 @@ -2470,20 +2651,19 @@ __metadata: eventemitter2: ^6.4.7 readable-stream: ^3.6.2 socket.io-client: ^4.5.1 - checksum: 10c0/32ac9d3febb2cb250d7a6cb4714077335f651fceb0562a85e9723475622b5caa5d9f0d39f8983349258f62c34e46eec14f366c0d56baf3b4cfbaa4d1c157c387 + checksum: 10c0/7d51316eb313bd4464e8e5d787c4d88228e40673414883a693f5772908cb5c17903db0d3101bc04ee9db218728525a0ad3a8545c6e7d933b48f3ae6ce8a474bc languageName: node linkType: hard -"@metamask/sdk-install-modal-web@npm:0.26.0": - version: 0.26.0 - resolution: "@metamask/sdk-install-modal-web@npm:0.26.0" +"@metamask/sdk-install-modal-web@npm:0.28.1": + version: 0.28.1 + resolution: "@metamask/sdk-install-modal-web@npm:0.28.1" dependencies: qr-code-styling: "npm:^1.6.0-rc.1" peerDependencies: - i18next: 22.5.1 + i18next: 23.11.5 react: ^18.2.0 react-dom: ^18.2.0 - react-i18next: ^13.2.2 react-native: "*" peerDependenciesMeta: react: @@ -2492,26 +2672,27 @@ __metadata: optional: true react-native: optional: true - checksum: 10c0/04529b31485c4cf0b09e5751dc16c16616d4c9f5dedb16b9e2682e22b087f3189659cfb7c8e376bd3791d73b270347e077f88518f4e38b1109b4fe78542f95d8 + checksum: 10c0/e7bc9789d6499ff1f2ec2587b0604c4df445bc35e0914165289348fe9325ccff60ef094b5ebe39310af9c68ee8d6d71ed0a6a217e2d3947a2aa92a4c7063e4a1 languageName: node linkType: hard -"@metamask/sdk@npm:0.26.3": - version: 0.26.3 - resolution: "@metamask/sdk@npm:0.26.3" +"@metamask/sdk@npm:0.28.4": + version: 0.28.4 + resolution: "@metamask/sdk@npm:0.28.4" dependencies: "@metamask/onboarding": "npm:^1.0.1" - "@metamask/providers": "npm:^15.0.0" - "@metamask/sdk-communication-layer": "npm:0.26.2" - "@metamask/sdk-install-modal-web": "npm:0.26.0" + "@metamask/providers": "npm:16.1.0" + "@metamask/sdk-communication-layer": "npm:0.28.2" + "@metamask/sdk-install-modal-web": "npm:0.28.1" "@types/dom-screen-wake-lock": "npm:^1.0.0" + "@types/uuid": "npm:^10.0.0" bowser: "npm:^2.9.0" cross-fetch: "npm:^4.0.0" debug: "npm:^4.3.4" eciesjs: "npm:^0.3.15" eth-rpc-errors: "npm:^4.0.3" eventemitter2: "npm:^6.4.7" - i18next: "npm:22.5.1" + i18next: "npm:23.11.5" i18next-browser-languagedetector: "npm:7.1.0" obj-multiplex: "npm:^1.0.0" pump: "npm:^3.0.0" @@ -2530,7 +2711,7 @@ __metadata: optional: true react-dom: optional: true - checksum: 10c0/ed9a7cb456a7a9a3c5f71f80ea18838475b551f4d4034d7922bd7b29a116d65c770c37be1961ad191d9373845b13b4e3bcb16207322c2da9216746271e98397b + checksum: 10c0/734045ba59e01baf9d5e87cfe85eeb44433e497ac845810f43361ce821260d6a2d89e34a2fe17a877a0f659b137767475fadf3ed23bc4562ad03220817c6df4c languageName: node linkType: hard @@ -2870,15 +3051,6 @@ __metadata: languageName: node linkType: hard -"@noble/curves@npm:1.2.0, @noble/curves@npm:~1.2.0": - version: 1.2.0 - resolution: "@noble/curves@npm:1.2.0" - dependencies: - "@noble/hashes": "npm:1.3.2" - checksum: 10c0/0bac7d1bbfb3c2286910b02598addd33243cb97c3f36f987ecc927a4be8d7d88e0fcb12b0f0ef8a044e7307d1844dd5c49bb724bfa0a79c8ec50ba60768c97f6 - languageName: node - linkType: hard - "@noble/curves@npm:1.3.0, @noble/curves@npm:~1.3.0": version: 1.3.0 resolution: "@noble/curves@npm:1.3.0" @@ -2888,30 +3060,30 @@ __metadata: languageName: node linkType: hard -"@noble/curves@npm:1.4.0, @noble/curves@npm:~1.4.0": - version: 1.4.0 - resolution: "@noble/curves@npm:1.4.0" +"@noble/curves@npm:1.6.0, @noble/curves@npm:^1.4.0, @noble/curves@npm:~1.6.0": + version: 1.6.0 + resolution: "@noble/curves@npm:1.6.0" dependencies: - "@noble/hashes": "npm:1.4.0" - checksum: 10c0/31fbc370df91bcc5a920ca3f2ce69c8cf26dc94775a36124ed8a5a3faf0453badafd2ee4337061ffea1b43c623a90ee8b286a5a81604aaf9563bdad7ff795d18 - languageName: node - linkType: hard - -"@noble/hashes@npm:1.3.2": - version: 1.3.2 - resolution: "@noble/hashes@npm:1.3.2" - checksum: 10c0/2482cce3bce6a596626f94ca296e21378e7a5d4c09597cbc46e65ffacc3d64c8df73111f2265444e36a3168208628258bbbaccba2ef24f65f58b2417638a20e7 + "@noble/hashes": "npm:1.5.0" + checksum: 10c0/f3262aa4d39148e627cd82b5ac1c93f88c5bb46dd2566b5e8e52ffac3a0fc381ad30c2111656fd2bd3b0d37d43d540543e0d93a5ff96a6cb184bc3bfe10d1cd9 languageName: node linkType: hard -"@noble/hashes@npm:1.3.3, @noble/hashes@npm:~1.3.0, @noble/hashes@npm:~1.3.2": +"@noble/hashes@npm:1.3.3, @noble/hashes@npm:~1.3.2": version: 1.3.3 resolution: "@noble/hashes@npm:1.3.3" checksum: 10c0/23c020b33da4172c988e44100e33cd9f8f6250b68b43c467d3551f82070ebd9716e0d9d2347427aa3774c85934a35fa9ee6f026fca2117e3fa12db7bedae7668 languageName: node linkType: hard -"@noble/hashes@npm:1.4.0, @noble/hashes@npm:^1.3.1, @noble/hashes@npm:~1.4.0": +"@noble/hashes@npm:1.5.0, @noble/hashes@npm:^1.4.0, @noble/hashes@npm:~1.5.0": + version: 1.5.0 + resolution: "@noble/hashes@npm:1.5.0" + checksum: 10c0/1b46539695fbfe4477c0822d90c881a04d4fa2921c08c552375b444a48cac9930cb1ee68de0a3c7859e676554d0f3771999716606dc4d8f826e414c11692cdd9 + languageName: node + linkType: hard + +"@noble/hashes@npm:^1.3.1": version: 1.4.0 resolution: "@noble/hashes@npm:1.4.0" checksum: 10c0/8c3f005ee72e7b8f9cff756dfae1241485187254e3f743873e22073d63906863df5d4f13d441b7530ea614b7a093f0d889309f28b59850f33b66cb26a779a4a5 @@ -3323,23 +3495,23 @@ __metadata: languageName: node linkType: hard -"@safe-global/safe-apps-provider@npm:0.18.1": - version: 0.18.1 - resolution: "@safe-global/safe-apps-provider@npm:0.18.1" +"@safe-global/safe-apps-provider@npm:0.18.3": + version: 0.18.3 + resolution: "@safe-global/safe-apps-provider@npm:0.18.3" dependencies: - "@safe-global/safe-apps-sdk": "npm:^8.1.0" + "@safe-global/safe-apps-sdk": "npm:^9.1.0" events: "npm:^3.3.0" - checksum: 10c0/9e6375132930cedd0935baa83cd026eb7c76776c7285edb3ff8c463ccf48d1e30cea03e93ce7199d3d3efa3cd035495e5f85fc361e203a2c03a4459d1989e726 + checksum: 10c0/7209d761919969c0859e8b9df90fd46d06c3f99424ecd5fd2e0b8080355a880504ee5c46cebcbaa94739f8be272f3f7102a9f40cf18e6c1a9e1d7dd29d77ee5e languageName: node linkType: hard -"@safe-global/safe-apps-sdk@npm:8.1.0, @safe-global/safe-apps-sdk@npm:^8.1.0": - version: 8.1.0 - resolution: "@safe-global/safe-apps-sdk@npm:8.1.0" +"@safe-global/safe-apps-sdk@npm:9.1.0, @safe-global/safe-apps-sdk@npm:^9.1.0": + version: 9.1.0 + resolution: "@safe-global/safe-apps-sdk@npm:9.1.0" dependencies: "@safe-global/safe-gateway-typescript-sdk": "npm:^3.5.3" - viem: "npm:^1.0.0" - checksum: 10c0/b6ad0610ed39a1106ecaa91e43e411dd361c8d4d9712cb3fbf15342950b86fe387ce331bd91ae35c90ff036cded188272ea45ca4e3534c2b08e7e3d3c741fdc0 + viem: "npm:^2.1.1" + checksum: 10c0/13af12122a6b1388e7960a76c3c421ea5ed97197646cd1f720b9fc9364fad0cc8f21cda23773130cd6bf57935a36f9e93f5222569cc80382709430b5cad26fda languageName: node linkType: hard @@ -3357,28 +3529,10 @@ __metadata: languageName: node linkType: hard -"@scure/base@npm:~1.1.0, @scure/base@npm:~1.1.2": - version: 1.1.5 - resolution: "@scure/base@npm:1.1.5" - checksum: 10c0/6eb07be0202fac74a57c79d0d00a45f6f7e57447010c1e3d90a4275d197829727b7abc54b248fc6f9bef9ae374f7be5ee9154dde5b5b73da773560bf17aa8504 - languageName: node - linkType: hard - -"@scure/base@npm:~1.1.6": - version: 1.1.7 - resolution: "@scure/base@npm:1.1.7" - checksum: 10c0/2d06aaf39e6de4b9640eb40d2e5419176ebfe911597856dcbf3bc6209277ddb83f4b4b02cb1fd1208f819654268ec083da68111d3530bbde07bae913e2fc2e5d - languageName: node - linkType: hard - -"@scure/bip32@npm:1.3.2": - version: 1.3.2 - resolution: "@scure/bip32@npm:1.3.2" - dependencies: - "@noble/curves": "npm:~1.2.0" - "@noble/hashes": "npm:~1.3.2" - "@scure/base": "npm:~1.1.2" - checksum: 10c0/2e9c1ce67f72b6c3329483f5fd39fb43ba6dcf732ed7ac63b80fa96341d2bc4cad1ea4c75bfeb91e801968c00df48b577b015fd4591f581e93f0d91178e630ca +"@scure/base@npm:~1.1.7, @scure/base@npm:~1.1.8": + version: 1.1.9 + resolution: "@scure/base@npm:1.1.9" + checksum: 10c0/77a06b9a2db8144d22d9bf198338893d77367c51b58c72b99df990c0a11f7cadd066d4102abb15e3ca6798d1529e3765f55c4355742465e49aed7a0c01fe76e8 languageName: node linkType: hard @@ -3393,24 +3547,14 @@ __metadata: languageName: node linkType: hard -"@scure/bip32@npm:1.4.0": - version: 1.4.0 - resolution: "@scure/bip32@npm:1.4.0" - dependencies: - "@noble/curves": "npm:~1.4.0" - "@noble/hashes": "npm:~1.4.0" - "@scure/base": "npm:~1.1.6" - checksum: 10c0/6849690d49a3bf1d0ffde9452eb16ab83478c1bc0da7b914f873e2930cd5acf972ee81320e3df1963eb247cf57e76d2d975b5f97093d37c0e3f7326581bf41bd - languageName: node - linkType: hard - -"@scure/bip39@npm:1.2.1": - version: 1.2.1 - resolution: "@scure/bip39@npm:1.2.1" +"@scure/bip32@npm:1.5.0": + version: 1.5.0 + resolution: "@scure/bip32@npm:1.5.0" dependencies: - "@noble/hashes": "npm:~1.3.0" - "@scure/base": "npm:~1.1.0" - checksum: 10c0/fe951f69dd5a7cdcefbe865bce1b160d6b59ba19bd01d09f0718e54fce37a7d8be158b32f5455f0e9c426a7fbbede3e019bf0baa99bacc88ef26a76a07e115d4 + "@noble/curves": "npm:~1.6.0" + "@noble/hashes": "npm:~1.5.0" + "@scure/base": "npm:~1.1.7" + checksum: 10c0/3319beda59e7f129d770cbe49709a2d1742f2deb6989b12e37aa1a47cd128a8c943bdd9286c6a5513ef4539307c4bca8f89f9aa91f294cac4598cbf95fa0c01d languageName: node linkType: hard @@ -3424,13 +3568,13 @@ __metadata: languageName: node linkType: hard -"@scure/bip39@npm:1.3.0": - version: 1.3.0 - resolution: "@scure/bip39@npm:1.3.0" +"@scure/bip39@npm:1.4.0": + version: 1.4.0 + resolution: "@scure/bip39@npm:1.4.0" dependencies: - "@noble/hashes": "npm:~1.4.0" - "@scure/base": "npm:~1.1.6" - checksum: 10c0/1ae1545a7384a4d9e33e12d9e9f8824f29b0279eb175b0f0657c0a782c217920054f9a1d28eb316a417dfc6c4e0b700d6fbdc6da160670107426d52fcbe017a8 + "@noble/hashes": "npm:~1.5.0" + "@scure/base": "npm:~1.1.8" + checksum: 10c0/dcdceeac348ed9c0f545c1a7ef8854ef62d6eb4e7b7aaafa4e2ef27f7e1c5744b0cd26292afd04e1ee59ae035b19abdd65174a444b8db8c238ccc662f6b90eac languageName: node linkType: hard @@ -3764,6 +3908,7 @@ __metadata: "@typescript-eslint/eslint-plugin": "npm:^7.14.1" "@typescript-eslint/parser": "npm:^7.14.1" "@vitejs/plugin-react": "npm:^4.3.1" + "@wagmi/cli": "npm:^2.1.16" axios: "npm:^1.7.2" base58-universal: "npm:^2.0.0" bignumber.js: "npm:^9.1.2" @@ -3789,28 +3934,28 @@ __metadata: lint-staged: "npm:^15.2.7" lodash-es: "npm:^4.17.21" material-ui-popup-state: "npm:^5.1.2" - notistack: "npm:^3.0.1" prettier: "npm:^3.3.2" pretty-bytes: "npm:^6.1.1" qs: "npm:^6.12.1" react: "npm:^18.3.1" react-dom: "npm:^18.3.1" + react-hot-toast: "npm:^2.4.1" react-router-dom: "npm:^6.24.0" react-scroll: "npm:^1.9.0" react-syntax-highlighter: "npm:^15.5.0" recharts: "npm:^2.12.7" semver: "npm:^7.6.3" - type-fest: "npm:^4.20.1" - typescript: "npm:^5.5.2" + type-fest: "npm:^4.26.1" + typescript: "npm:^5.6.3" use-debounce: "npm:^10.0.1" use-element-position: "npm:^1.0.13" use-local-storage-state: "npm:^19.3.1" - viem: "npm:^2.16.5" + viem: "npm:^2.21.25" vite: "npm:^5.4.2" vite-plugin-html: "npm:^3.2.2" vite-plugin-html-env: "npm:^1.2.8" vite-tsconfig-paths: "npm:^4.3.2" - wagmi: "npm:^2.10.8" + wagmi: "npm:^2.12.17" yup: "npm:^1.4.0" languageName: unknown linkType: soft @@ -4219,6 +4364,13 @@ __metadata: languageName: node linkType: hard +"@types/uuid@npm:^10.0.0": + version: 10.0.0 + resolution: "@types/uuid@npm:10.0.0" + checksum: 10c0/9a1404bf287164481cb9b97f6bb638f78f955be57c40c6513b7655160beb29df6f84c915aaf4089a1559c216557dc4d2f79b48d978742d3ae10b937420ddac60 + languageName: node + linkType: hard + "@types/ws@npm:^8.0.0": version: 8.5.10 resolution: "@types/ws@npm:8.5.10" @@ -4491,34 +4643,69 @@ __metadata: languageName: node linkType: hard -"@wagmi/connectors@npm:5.0.20": - version: 5.0.20 - resolution: "@wagmi/connectors@npm:5.0.20" +"@wagmi/cli@npm:^2.1.16": + version: 2.1.16 + resolution: "@wagmi/cli@npm:2.1.16" + dependencies: + abitype: "npm:^1.0.4" + bundle-require: "npm:^4.0.2" + cac: "npm:^6.7.14" + change-case: "npm:^5.4.4" + chokidar: "npm:^3.5.3" + dedent: "npm:^0.7.0" + dotenv: "npm:^16.3.1" + dotenv-expand: "npm:^10.0.0" + esbuild: "npm:^0.19.0" + execa: "npm:^8.0.1" + fdir: "npm:^6.1.1" + find-up: "npm:^6.3.0" + fs-extra: "npm:^11.2.0" + ora: "npm:^6.3.1" + pathe: "npm:^1.1.2" + picocolors: "npm:^1.0.0" + picomatch: "npm:^3.0.0" + prettier: "npm:^3.0.3" + viem: "npm:2.x" + zod: "npm:^3.22.2" + peerDependencies: + typescript: ">=5.0.4" + peerDependenciesMeta: + typescript: + optional: true + bin: + wagmi: dist/esm/cli.js + checksum: 10c0/289fc520d8403c5d0fd51e7dfab934365770d32f9a9ce58a3e25a4cbfe09948c5109d674fe8bb2c8d36fc5737c2a590f3f5d5f907ac5715316111e839107f6d4 + languageName: node + linkType: hard + +"@wagmi/connectors@npm:5.1.15": + version: 5.1.15 + resolution: "@wagmi/connectors@npm:5.1.15" dependencies: "@coinbase/wallet-sdk": "npm:4.0.4" - "@metamask/sdk": "npm:0.26.3" - "@safe-global/safe-apps-provider": "npm:0.18.1" - "@safe-global/safe-apps-sdk": "npm:8.1.0" - "@walletconnect/ethereum-provider": "npm:2.13.0" - "@walletconnect/modal": "npm:2.6.2" + "@metamask/sdk": "npm:0.28.4" + "@safe-global/safe-apps-provider": "npm:0.18.3" + "@safe-global/safe-apps-sdk": "npm:9.1.0" + "@walletconnect/ethereum-provider": "npm:2.17.0" + "@walletconnect/modal": "npm:2.7.0" cbw-sdk: "npm:@coinbase/wallet-sdk@3.9.3" peerDependencies: - "@wagmi/core": 2.11.5 + "@wagmi/core": 2.13.8 typescript: ">=5.0.4" viem: 2.x peerDependenciesMeta: typescript: optional: true - checksum: 10c0/09e3a1ee7ab4de1e77d7b9392c9e70f43996fef1665085c5246d4b74e0ad60df5cf2af4e5f6b5faee284f8a4c7e5aad3a8ef23c5f8cc605255e112754fa3c3ad + checksum: 10c0/99b282618772aaf5321875e89b4dd8e03c0a2de47a6c4faa52704b3fcd6bfcba2548504b79424cd96cc939b41df20c521241f402d6b3021f6858618aad057b0f languageName: node linkType: hard -"@wagmi/core@npm:2.11.5": - version: 2.11.5 - resolution: "@wagmi/core@npm:2.11.5" +"@wagmi/core@npm:2.13.8": + version: 2.13.8 + resolution: "@wagmi/core@npm:2.13.8" dependencies: eventemitter3: "npm:5.0.1" - mipd: "npm:0.0.5" + mipd: "npm:0.0.7" zustand: "npm:4.4.1" peerDependencies: "@tanstack/query-core": ">=5.0.0" @@ -4529,13 +4716,13 @@ __metadata: optional: true typescript: optional: true - checksum: 10c0/687c804547ff37830a824cfc9fc5ca1c41a159f2137d45c730486a607f557d2637ef2cff404b88e65309e0be1fe13b3fdee6d3171fe9c172567d46c54753fdfd + checksum: 10c0/20adc34fd2e400ae977745a6c9ca11f1fc35c022ee806b52997c3de510734835dec002fe4c0f8f916cdffc199eea7f301c3ee0e7d131e38969afbd357a32e6fa languageName: node linkType: hard -"@walletconnect/core@npm:2.13.0": - version: 2.13.0 - resolution: "@walletconnect/core@npm:2.13.0" +"@walletconnect/core@npm:2.17.0": + version: 2.17.0 + resolution: "@walletconnect/core@npm:2.17.0" dependencies: "@walletconnect/heartbeat": "npm:1.2.2" "@walletconnect/jsonrpc-provider": "npm:1.0.14" @@ -4544,17 +4731,16 @@ __metadata: "@walletconnect/jsonrpc-ws-connection": "npm:1.0.14" "@walletconnect/keyvaluestorage": "npm:1.1.1" "@walletconnect/logger": "npm:2.1.2" - "@walletconnect/relay-api": "npm:1.0.10" + "@walletconnect/relay-api": "npm:1.0.11" "@walletconnect/relay-auth": "npm:1.0.4" "@walletconnect/safe-json": "npm:1.0.2" "@walletconnect/time": "npm:1.0.2" - "@walletconnect/types": "npm:2.13.0" - "@walletconnect/utils": "npm:2.13.0" + "@walletconnect/types": "npm:2.17.0" + "@walletconnect/utils": "npm:2.17.0" events: "npm:3.3.0" - isomorphic-unfetch: "npm:3.1.0" lodash.isequal: "npm:4.5.0" uint8arrays: "npm:3.1.0" - checksum: 10c0/e1356eb8ac94f8f6743814337607244557280d43a6e2ec14591beb21dca0e73cc79b16f0a2ace60ef447149778c5383a1fd4eac67788372d249c8c5f6d8c7dc2 + checksum: 10c0/34ae5b9b68c08c1dd3ebb2a6ebff8697307e76fbfe4d6b51d5d090da5cd1613e1c66fa5ac3a87c914333458d7b5bf075bb664292f6b2c7d438c72f706d87416d languageName: node linkType: hard @@ -4567,21 +4753,21 @@ __metadata: languageName: node linkType: hard -"@walletconnect/ethereum-provider@npm:2.13.0": - version: 2.13.0 - resolution: "@walletconnect/ethereum-provider@npm:2.13.0" +"@walletconnect/ethereum-provider@npm:2.17.0": + version: 2.17.0 + resolution: "@walletconnect/ethereum-provider@npm:2.17.0" dependencies: "@walletconnect/jsonrpc-http-connection": "npm:1.0.8" "@walletconnect/jsonrpc-provider": "npm:1.0.14" "@walletconnect/jsonrpc-types": "npm:1.0.4" "@walletconnect/jsonrpc-utils": "npm:1.0.8" - "@walletconnect/modal": "npm:2.6.2" - "@walletconnect/sign-client": "npm:2.13.0" - "@walletconnect/types": "npm:2.13.0" - "@walletconnect/universal-provider": "npm:2.13.0" - "@walletconnect/utils": "npm:2.13.0" + "@walletconnect/modal": "npm:2.7.0" + "@walletconnect/sign-client": "npm:2.17.0" + "@walletconnect/types": "npm:2.17.0" + "@walletconnect/universal-provider": "npm:2.17.0" + "@walletconnect/utils": "npm:2.17.0" events: "npm:3.3.0" - checksum: 10c0/4bc3c76b7a9e81ac505fcff99244bfa9f14419ee2de322e491dacd94669923adf5e9e1a2298ae84b33e3d5985a0bfab6b7715237e6f2ce23ec02c67dedb58898 + checksum: 10c0/b046a9c296e95b22841f0b2efd28a4ce1a38529a9ba412d3c8ffc482879d79c3d2a24b8c0ec712baecf781938b4321ab5c1ecad5573d078add7c47b0cfd08a25 languageName: node linkType: hard @@ -4698,43 +4884,43 @@ __metadata: languageName: node linkType: hard -"@walletconnect/modal-core@npm:2.6.2": - version: 2.6.2 - resolution: "@walletconnect/modal-core@npm:2.6.2" +"@walletconnect/modal-core@npm:2.7.0": + version: 2.7.0 + resolution: "@walletconnect/modal-core@npm:2.7.0" dependencies: valtio: "npm:1.11.2" - checksum: 10c0/5e3fb21a1fc923ec0d2a3e33cc360e3d56278a211609d5fd4cc4d6e3b4f1acb40b9783fcc771b259b78c7e731af3862def096aa1da2e210e7859729808304c94 + checksum: 10c0/84b11735c005e37e661aa0f08b2e8c8098db3b2cacd957c4a73f4d3de11b2d5e04dd97ab970f8d22fc3e8269fea3297b9487e177343bbab8dd69b3b917fb7f60 languageName: node linkType: hard -"@walletconnect/modal-ui@npm:2.6.2": - version: 2.6.2 - resolution: "@walletconnect/modal-ui@npm:2.6.2" +"@walletconnect/modal-ui@npm:2.7.0": + version: 2.7.0 + resolution: "@walletconnect/modal-ui@npm:2.7.0" dependencies: - "@walletconnect/modal-core": "npm:2.6.2" + "@walletconnect/modal-core": "npm:2.7.0" lit: "npm:2.8.0" motion: "npm:10.16.2" qrcode: "npm:1.5.3" - checksum: 10c0/5d8f0a2703b9757dfa48ad3e48a40e64608f6a28db31ec93a2f10e942dcc5ee986c03ffdab94018e905836d339131fc928bc14614a94943011868cdddc36a32a + checksum: 10c0/b717f1fc9854b7d14a4364720fce2d44167f547533340704644ed2fdf9d861b3798ffd19a3b51062a366a8bc39f84b9a8bb3dd04e9e33da742192359be00b051 languageName: node linkType: hard -"@walletconnect/modal@npm:2.6.2": - version: 2.6.2 - resolution: "@walletconnect/modal@npm:2.6.2" +"@walletconnect/modal@npm:2.7.0": + version: 2.7.0 + resolution: "@walletconnect/modal@npm:2.7.0" dependencies: - "@walletconnect/modal-core": "npm:2.6.2" - "@walletconnect/modal-ui": "npm:2.6.2" - checksum: 10c0/1cc309f63d061e49fdf7b10d28093d7ef1a47f4624f717f8fd3bf6097ac3b00cea4acc45c50e8bd386d4bcfdf10f4dcba960f7129c557b9dc42ef7d05b970807 + "@walletconnect/modal-core": "npm:2.7.0" + "@walletconnect/modal-ui": "npm:2.7.0" + checksum: 10c0/2f3074eebbca41a46e29680dc2565bc762133508774f05db0075a82b0b66ecc8defca40a94ad63669676090a7e3ef671804592b10e91636ab1cdeac014a1eb11 languageName: node linkType: hard -"@walletconnect/relay-api@npm:1.0.10": - version: 1.0.10 - resolution: "@walletconnect/relay-api@npm:1.0.10" +"@walletconnect/relay-api@npm:1.0.11": + version: 1.0.11 + resolution: "@walletconnect/relay-api@npm:1.0.11" dependencies: "@walletconnect/jsonrpc-types": "npm:^1.0.2" - checksum: 10c0/2709bbe45f60579cd2e1c74b0fd03c36ea409cd8a9117e00a7485428d0c9ba7eb02e525c21e5286db2b6ce563b1d29053b0249c2ed95f8adcf02b11e54f61fcd + checksum: 10c0/2595d7e68d3a93e7735e0b6204811762898b0ce1466e811d78be5bcec7ac1cde5381637615a99104099165bf63695da5ef9381d6ded29924a57a71b10712a91d languageName: node linkType: hard @@ -4761,20 +4947,20 @@ __metadata: languageName: node linkType: hard -"@walletconnect/sign-client@npm:2.13.0": - version: 2.13.0 - resolution: "@walletconnect/sign-client@npm:2.13.0" +"@walletconnect/sign-client@npm:2.17.0": + version: 2.17.0 + resolution: "@walletconnect/sign-client@npm:2.17.0" dependencies: - "@walletconnect/core": "npm:2.13.0" + "@walletconnect/core": "npm:2.17.0" "@walletconnect/events": "npm:1.0.1" "@walletconnect/heartbeat": "npm:1.2.2" "@walletconnect/jsonrpc-utils": "npm:1.0.8" "@walletconnect/logger": "npm:2.1.2" "@walletconnect/time": "npm:1.0.2" - "@walletconnect/types": "npm:2.13.0" - "@walletconnect/utils": "npm:2.13.0" + "@walletconnect/types": "npm:2.17.0" + "@walletconnect/utils": "npm:2.17.0" events: "npm:3.3.0" - checksum: 10c0/58c702997f719cab9b183d23c53efee561a3a407de24e464e339e350124a71eeccb1bd651f0893ad0f39343ce42a7ff3666bbd28cb8dfc6a0e8d12c94eacc288 + checksum: 10c0/48f7d13b3db49584a40dc2653f49fabadd100a324e2213476b8d9e4d6fe0808a08ae14103d2e5b609abff3115197003d8570d606275dbd0f6774d0d49da10c61 languageName: node linkType: hard @@ -4787,9 +4973,9 @@ __metadata: languageName: node linkType: hard -"@walletconnect/types@npm:2.13.0": - version: 2.13.0 - resolution: "@walletconnect/types@npm:2.13.0" +"@walletconnect/types@npm:2.17.0": + version: 2.17.0 + resolution: "@walletconnect/types@npm:2.17.0" dependencies: "@walletconnect/events": "npm:1.0.1" "@walletconnect/heartbeat": "npm:1.2.2" @@ -4797,46 +4983,48 @@ __metadata: "@walletconnect/keyvaluestorage": "npm:1.1.1" "@walletconnect/logger": "npm:2.1.2" events: "npm:3.3.0" - checksum: 10c0/9962284daf92d6b27a009b90b908518b3f028f10f2168ddbc37ad2cb2b20cb0e65d170aa4343e2ea445c519cf79e78264480e2b2c4ab9f974f2c15962db5b012 + checksum: 10c0/bdc0c062da1edb4410882d9cfca1bb30eb0afd7caea90d5e7a66eaf15e28380e9ef97635cd5e5a017947f4c814c1f780622b4d8946b11a335d415ae066ec7ade languageName: node linkType: hard -"@walletconnect/universal-provider@npm:2.13.0": - version: 2.13.0 - resolution: "@walletconnect/universal-provider@npm:2.13.0" +"@walletconnect/universal-provider@npm:2.17.0": + version: 2.17.0 + resolution: "@walletconnect/universal-provider@npm:2.17.0" dependencies: "@walletconnect/jsonrpc-http-connection": "npm:1.0.8" "@walletconnect/jsonrpc-provider": "npm:1.0.14" "@walletconnect/jsonrpc-types": "npm:1.0.4" "@walletconnect/jsonrpc-utils": "npm:1.0.8" "@walletconnect/logger": "npm:2.1.2" - "@walletconnect/sign-client": "npm:2.13.0" - "@walletconnect/types": "npm:2.13.0" - "@walletconnect/utils": "npm:2.13.0" + "@walletconnect/sign-client": "npm:2.17.0" + "@walletconnect/types": "npm:2.17.0" + "@walletconnect/utils": "npm:2.17.0" events: "npm:3.3.0" - checksum: 10c0/79d14cdce74054859f26f69a17215c59367d961d0f36e7868601ed98030bd0636b3806dd68b76cc66ec4a70d5f6ec107fbe18bb6a1022a5161ea6d71810a0ed9 + checksum: 10c0/7c1afc79054db5add4e937d7adaadb4fc26aecffb5d749d388418fa5d4eb153807ab4de301b642cd80669b4e5c6bcae917f18cf5ce8696d87da8b3705b60d1ec languageName: node linkType: hard -"@walletconnect/utils@npm:2.13.0": - version: 2.13.0 - resolution: "@walletconnect/utils@npm:2.13.0" +"@walletconnect/utils@npm:2.17.0": + version: 2.17.0 + resolution: "@walletconnect/utils@npm:2.17.0" dependencies: "@stablelib/chacha20poly1305": "npm:1.0.1" "@stablelib/hkdf": "npm:1.0.1" "@stablelib/random": "npm:1.0.2" "@stablelib/sha256": "npm:1.0.1" "@stablelib/x25519": "npm:1.0.3" - "@walletconnect/relay-api": "npm:1.0.10" + "@walletconnect/relay-api": "npm:1.0.11" + "@walletconnect/relay-auth": "npm:1.0.4" "@walletconnect/safe-json": "npm:1.0.2" "@walletconnect/time": "npm:1.0.2" - "@walletconnect/types": "npm:2.13.0" + "@walletconnect/types": "npm:2.17.0" "@walletconnect/window-getters": "npm:1.0.1" "@walletconnect/window-metadata": "npm:1.0.1" detect-browser: "npm:5.3.0" + elliptic: "npm:^6.5.7" query-string: "npm:7.1.3" uint8arrays: "npm:3.1.0" - checksum: 10c0/2dbdb9ed790492411eb5c4e6b06aa511f6c0204c4ff283ecb5a4d339bb1bf3da033ef3a0c0af66b94df0553676f408222c2feca8c601b0554be2bbfbef43d6ec + checksum: 10c0/d1da74b2cd7af35f16d735fe408cfc820c611b2709bd00899e4e91b0b0a6dcd8f344f97df34d0ef8cabc121619a40b62118ffa2aa233ddba9863d1ba23480a0c languageName: node linkType: hard @@ -4929,24 +5117,9 @@ __metadata: languageName: node linkType: hard -"abitype@npm:0.9.8": - version: 0.9.8 - resolution: "abitype@npm:0.9.8" - peerDependencies: - typescript: ">=5.0.4" - zod: ^3 >=3.19.1 - peerDependenciesMeta: - typescript: - optional: true - zod: - optional: true - checksum: 10c0/ec559461d901d456820faf307e21b2c129583d44f4c68257ed9d0d44eae461114a7049046e715e069bc6fa70c410f644e06bdd2c798ac30d0ada794cd2a6c51e - languageName: node - linkType: hard - -"abitype@npm:1.0.5": - version: 1.0.5 - resolution: "abitype@npm:1.0.5" +"abitype@npm:1.0.6, abitype@npm:^1.0.4": + version: 1.0.6 + resolution: "abitype@npm:1.0.6" peerDependencies: typescript: ">=5.0.4" zod: ^3 >=3.22.0 @@ -4955,7 +5128,7 @@ __metadata: optional: true zod: optional: true - checksum: 10c0/dc954877fba19e2b7a70f1025807d69fa5aabec8bd58ce94e68d1a5ec1697fff3fe5214b4392508db7191762150f19a2396cf66ffb1d3ba8c1f37a89fd25e598 + checksum: 10c0/30ca97010bbf34b9aaed401858eeb6bc30419f7ff11eb34adcb243522dd56c9d8a9d3d406aa5d4f60a7c263902f5136043005698e3f073ea882a4922d43a2929 languageName: node linkType: hard @@ -5443,6 +5616,17 @@ __metadata: languageName: node linkType: hard +"bl@npm:^5.0.0": + version: 5.1.0 + resolution: "bl@npm:5.1.0" + dependencies: + buffer: "npm:^6.0.3" + inherits: "npm:^2.0.4" + readable-stream: "npm:^3.4.0" + checksum: 10c0/528a9c3d7d6b87af98c46f10a887654d027c28c503c7f7de87440e643f0056d7a2319a967762b8ec18150c64799d2825a277147a752a0570a7407c0b705b0d01 + languageName: node + linkType: hard + "bn.js@npm:^4.11.9": version: 4.12.0 resolution: "bn.js@npm:4.12.0" @@ -5584,6 +5768,17 @@ __metadata: languageName: node linkType: hard +"bundle-require@npm:^4.0.2": + version: 4.2.1 + resolution: "bundle-require@npm:4.2.1" + dependencies: + load-tsconfig: "npm:^0.2.3" + peerDependencies: + esbuild: ">=0.17" + checksum: 10c0/f458ce39f8dd23f900f1877f475f36aa502ecf888cc97cfa2b8d1e9178d091a0d4c09f07afff001aae8b805ba6a94ca71bbbd9efe08b0e03c870bd61e8c00cb3 + languageName: node + linkType: hard + "busboy@npm:^1.6.0": version: 1.6.0 resolution: "busboy@npm:1.6.0" @@ -5593,6 +5788,13 @@ __metadata: languageName: node linkType: hard +"cac@npm:^6.7.14": + version: 6.7.14 + resolution: "cac@npm:6.7.14" + checksum: 10c0/4ee06aaa7bab8981f0d54e5f5f9d4adcd64058e9697563ce336d8a3878ed018ee18ebe5359b2430eceae87e0758e62ea2019c3f52ae6e211b1bd2e133856cd10 + languageName: node + linkType: hard + "cacache@npm:^18.0.0": version: 18.0.2 resolution: "cacache@npm:18.0.2" @@ -5706,7 +5908,7 @@ __metadata: languageName: node linkType: hard -"chalk@npm:~5.3.0": +"chalk@npm:^5.0.0, chalk@npm:~5.3.0": version: 5.3.0 resolution: "chalk@npm:5.3.0" checksum: 10c0/8297d436b2c0f95801103ff2ef67268d362021b8210daf8ddbe349695333eb3610a71122172ff3b0272f1ef2cf7cc2c41fdaa4715f52e49ffe04c56340feed09 @@ -5769,6 +5971,13 @@ __metadata: languageName: node linkType: hard +"change-case@npm:^5.4.4": + version: 5.4.4 + resolution: "change-case@npm:5.4.4" + checksum: 10c0/2a9c2b9c9ad6ab2491105aaf506db1a9acaf543a18967798dcce20926c6a173aa63266cb6189f3086e3c14bf7ae1f8ea4f96ecc466fcd582310efa00372f3734 + languageName: node + linkType: hard + "character-entities-legacy@npm:^1.0.0": version: 1.1.4 resolution: "character-entities-legacy@npm:1.1.4" @@ -5797,7 +6006,7 @@ __metadata: languageName: node linkType: hard -"chokidar@npm:^3.6.0": +"chokidar@npm:^3.5.3, chokidar@npm:^3.6.0": version: 3.6.0 resolution: "chokidar@npm:3.6.0" dependencies: @@ -5880,7 +6089,7 @@ __metadata: languageName: node linkType: hard -"cli-spinners@npm:^2.5.0": +"cli-spinners@npm:^2.5.0, cli-spinners@npm:^2.6.1": version: 2.9.2 resolution: "cli-spinners@npm:2.9.2" checksum: 10c0/907a1c227ddf0d7a101e7ab8b300affc742ead4b4ebe920a5bf1bc6d45dce2958fcd195eb28fa25275062fe6fa9b109b93b63bc8033396ed3bcb50297008b3a3 @@ -5961,7 +6170,7 @@ __metadata: languageName: node linkType: hard -"clsx@npm:^1.1.0, clsx@npm:^1.2.1": +"clsx@npm:^1.2.1": version: 1.2.1 resolution: "clsx@npm:1.2.1" checksum: 10c0/34dead8bee24f5e96f6e7937d711978380647e936a22e76380290e35486afd8634966ce300fc4b74a32f3762c7d4c0303f442c3e259f4ce02374eb0c82834f27 @@ -6467,6 +6676,13 @@ __metadata: languageName: node linkType: hard +"dedent@npm:^0.7.0": + version: 0.7.0 + resolution: "dedent@npm:0.7.0" + checksum: 10c0/7c3aa00ddfe3e5fcd477958e156156a5137e3bb6ff1493ca05edff4decf29a90a057974cc77e75951f8eb801c1816cb45aea1f52d628cdd000b82b36ab839d1b + languageName: node + linkType: hard + "deep-is@npm:^0.1.3": version: 0.1.4 resolution: "deep-is@npm:0.1.4" @@ -6690,6 +6906,13 @@ __metadata: languageName: node linkType: hard +"dotenv-expand@npm:^10.0.0": + version: 10.0.0 + resolution: "dotenv-expand@npm:10.0.0" + checksum: 10c0/298f5018e29cfdcb0b5f463ba8e8627749103fbcf6cf81c561119115754ed582deee37b49dfc7253028aaba875ab7aea5fa90e5dac88e511d009ab0e6677924e + languageName: node + linkType: hard + "dotenv-expand@npm:^8.0.2": version: 8.0.3 resolution: "dotenv-expand@npm:8.0.3" @@ -6697,7 +6920,7 @@ __metadata: languageName: node linkType: hard -"dotenv@npm:^16.0.0, dotenv@npm:^16.4.5": +"dotenv@npm:^16.0.0, dotenv@npm:^16.3.1, dotenv@npm:^16.4.5": version: 16.4.5 resolution: "dotenv@npm:16.4.5" checksum: 10c0/48d92870076832af0418b13acd6e5a5a3e83bb00df690d9812e94b24aff62b88ade955ac99a05501305b8dc8f1b0ee7638b18493deb6fe93d680e5220936292f @@ -6774,6 +6997,21 @@ __metadata: languageName: node linkType: hard +"elliptic@npm:^6.5.7": + version: 6.5.7 + resolution: "elliptic@npm:6.5.7" + dependencies: + bn.js: "npm:^4.11.9" + brorand: "npm:^1.1.0" + hash.js: "npm:^1.0.0" + hmac-drbg: "npm:^1.0.1" + inherits: "npm:^2.0.4" + minimalistic-assert: "npm:^1.0.1" + minimalistic-crypto-utils: "npm:^1.0.1" + checksum: 10c0/799959b6c54ea3564e8961f35abdf8c77e37617f3051614b05ab1fb6a04ddb65bd1caa75ed1bae375b15dda312a0f79fed26ebe76ecf05c5a7af244152a601b8 + languageName: node + linkType: hard + "emoji-regex@npm:^10.3.0": version: 10.3.0 resolution: "emoji-regex@npm:10.3.0" @@ -7056,6 +7294,86 @@ __metadata: languageName: node linkType: hard +"esbuild@npm:^0.19.0": + version: 0.19.12 + resolution: "esbuild@npm:0.19.12" + dependencies: + "@esbuild/aix-ppc64": "npm:0.19.12" + "@esbuild/android-arm": "npm:0.19.12" + "@esbuild/android-arm64": "npm:0.19.12" + "@esbuild/android-x64": "npm:0.19.12" + "@esbuild/darwin-arm64": "npm:0.19.12" + "@esbuild/darwin-x64": "npm:0.19.12" + "@esbuild/freebsd-arm64": "npm:0.19.12" + "@esbuild/freebsd-x64": "npm:0.19.12" + "@esbuild/linux-arm": "npm:0.19.12" + "@esbuild/linux-arm64": "npm:0.19.12" + "@esbuild/linux-ia32": "npm:0.19.12" + "@esbuild/linux-loong64": "npm:0.19.12" + "@esbuild/linux-mips64el": "npm:0.19.12" + "@esbuild/linux-ppc64": "npm:0.19.12" + "@esbuild/linux-riscv64": "npm:0.19.12" + "@esbuild/linux-s390x": "npm:0.19.12" + "@esbuild/linux-x64": "npm:0.19.12" + "@esbuild/netbsd-x64": "npm:0.19.12" + "@esbuild/openbsd-x64": "npm:0.19.12" + "@esbuild/sunos-x64": "npm:0.19.12" + "@esbuild/win32-arm64": "npm:0.19.12" + "@esbuild/win32-ia32": "npm:0.19.12" + "@esbuild/win32-x64": "npm:0.19.12" + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 10c0/0f2d21ffe24ebead64843f87c3aebe2e703a5ed9feb086a0728b24907fac2eb9923e4a79857d3df9059c915739bd7a870dd667972eae325c67f478b592b8582d + languageName: node + linkType: hard + "esbuild@npm:^0.21.3": version: 0.21.5 resolution: "esbuild@npm:0.21.5" @@ -7795,6 +8113,18 @@ __metadata: languageName: node linkType: hard +"fdir@npm:^6.1.1": + version: 6.4.0 + resolution: "fdir@npm:6.4.0" + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + checksum: 10c0/9a03efa1335d78ea386b701799b08ad9e7e8da85d88567dc162cd28dd8e9486e8c269b3e95bfeb21dd6a5b14ebf69d230eb6e18f49d33fbda3cd97432f648c48 + languageName: node + linkType: hard + "figures@npm:^3.0.0": version: 3.2.0 resolution: "figures@npm:3.2.0" @@ -7881,6 +8211,16 @@ __metadata: languageName: node linkType: hard +"find-up@npm:^6.3.0": + version: 6.3.0 + resolution: "find-up@npm:6.3.0" + dependencies: + locate-path: "npm:^7.1.0" + path-exists: "npm:^5.0.0" + checksum: 10c0/07e0314362d316b2b13f7f11ea4692d5191e718ca3f7264110127520f3347996349bf9e16805abae3e196805814bc66ef4bff2b8904dc4a6476085fc9b0eba07 + languageName: node + linkType: hard + "flat-cache@npm:^3.0.4": version: 3.2.0 resolution: "flat-cache@npm:3.2.0" @@ -7975,6 +8315,17 @@ __metadata: languageName: node linkType: hard +"fs-extra@npm:^11.2.0": + version: 11.2.0 + resolution: "fs-extra@npm:11.2.0" + dependencies: + graceful-fs: "npm:^4.2.0" + jsonfile: "npm:^6.0.1" + universalify: "npm:^2.0.0" + checksum: 10c0/d77a9a9efe60532d2e790e938c81a02c1b24904ef7a3efb3990b835514465ba720e99a6ea56fd5e2db53b4695319b644d76d5a0e9988a2beef80aa7b1da63398 + languageName: node + linkType: hard + "fs-minipass@npm:^2.0.0": version: 2.1.0 resolution: "fs-minipass@npm:2.1.0" @@ -8211,12 +8562,12 @@ __metadata: languageName: node linkType: hard -"goober@npm:^2.0.33": - version: 2.1.14 - resolution: "goober@npm:2.1.14" +"goober@npm:^2.1.10": + version: 2.1.16 + resolution: "goober@npm:2.1.16" peerDependencies: csstype: ^3.0.10 - checksum: 10c0/184eda787a9a14cffbaa8284e98dc127095e538b4acab2a84b81babca84253bb883e16208822e02584f27c7a69f3ec47341e5060dfa40a0e07c32ac1f79b2714 + checksum: 10c0/f4c8256bf9c27873d47c1443f348779ac7f322516cb80a5dc647a6ebe790ce6bb9d3f487a0fb8be0b583fb96b9b2f6b7463f7fea3cd680306f95fa6fc9db1f6a languageName: node linkType: hard @@ -8546,12 +8897,12 @@ __metadata: languageName: node linkType: hard -"i18next@npm:22.5.1": - version: 22.5.1 - resolution: "i18next@npm:22.5.1" +"i18next@npm:23.11.5": + version: 23.11.5 + resolution: "i18next@npm:23.11.5" dependencies: - "@babel/runtime": "npm:^7.20.6" - checksum: 10c0/a284f8d805ebad77114a830e60d5c59485a7f4d45179761f877249b63035572cff4103e5b4702669dff1a0e03b4e8b6df377bc871935f8215e43fd97e8e9e910 + "@babel/runtime": "npm:^7.23.2" + checksum: 10c0/b0bec64250a3e529d4c51e2fc511406a85c5dde3d005d3aabe919551ca31dfc0a8f5490bf6e44649822e895a1fa91a58092d112367669cd11b2eb89e6ba90d1a languageName: node linkType: hard @@ -8955,6 +9306,13 @@ __metadata: languageName: node linkType: hard +"is-interactive@npm:^2.0.0": + version: 2.0.0 + resolution: "is-interactive@npm:2.0.0" + checksum: 10c0/801c8f6064f85199dc6bf99b5dd98db3282e930c3bc197b32f2c5b89313bb578a07d1b8a01365c4348c2927229234f3681eb861b9c2c92bee72ff397390fa600 + languageName: node + linkType: hard + "is-lambda@npm:^1.0.1": version: 1.0.1 resolution: "is-lambda@npm:1.0.1" @@ -9100,6 +9458,13 @@ __metadata: languageName: node linkType: hard +"is-unicode-supported@npm:^1.1.0": + version: 1.3.0 + resolution: "is-unicode-supported@npm:1.3.0" + checksum: 10c0/b8674ea95d869f6faabddc6a484767207058b91aea0250803cbf1221345cb0c56f466d4ecea375dc77f6633d248d33c47bd296fb8f4cdba0b4edba8917e83d8a + languageName: node + linkType: hard + "is-upper-case@npm:^2.0.2": version: 2.0.2 resolution: "is-upper-case@npm:2.0.2" @@ -9197,16 +9562,6 @@ __metadata: languageName: node linkType: hard -"isomorphic-unfetch@npm:3.1.0": - version: 3.1.0 - resolution: "isomorphic-unfetch@npm:3.1.0" - dependencies: - node-fetch: "npm:^2.6.1" - unfetch: "npm:^4.2.0" - checksum: 10c0/d3b61fca06304db692b7f76bdfd3a00f410e42cfa7403c3b250546bf71589d18cf2f355922f57198e4cc4a9872d3647b20397a5c3edf1a347c90d57c83cf2a89 - languageName: node - linkType: hard - "isomorphic-ws@npm:^5.0.0": version: 5.0.0 resolution: "isomorphic-ws@npm:5.0.0" @@ -9216,21 +9571,12 @@ __metadata: languageName: node linkType: hard -"isows@npm:1.0.3": - version: 1.0.3 - resolution: "isows@npm:1.0.3" - peerDependencies: - ws: "*" - checksum: 10c0/adec15db704bb66615dd8ef33f889d41ae2a70866b21fa629855da98cc82a628ae072ee221fe9779a9a19866cad2a3e72593f2d161a0ce0e168b4484c7df9cd2 - languageName: node - linkType: hard - -"isows@npm:1.0.4": - version: 1.0.4 - resolution: "isows@npm:1.0.4" +"isows@npm:1.0.6": + version: 1.0.6 + resolution: "isows@npm:1.0.6" peerDependencies: ws: "*" - checksum: 10c0/46f43b07edcf148acba735ddfc6ed985e1e124446043ea32b71023e67671e46619c8818eda8c34a9ac91cb37c475af12a3aeeee676a88a0aceb5d67a3082313f + checksum: 10c0/f89338f63ce2f497d6cd0f86e42c634209328ebb43b3bdfdc85d8f1589ee75f02b7e6d9e1ba274101d0f6f513b1b8cbe6985e6542b4aaa1f0c5fd50d9c1be95c languageName: node linkType: hard @@ -9688,6 +10034,13 @@ __metadata: languageName: node linkType: hard +"load-tsconfig@npm:^0.2.3": + version: 0.2.5 + resolution: "load-tsconfig@npm:0.2.5" + checksum: 10c0/bf2823dd26389d3497b6567f07435c5a7a58d9df82e879b0b3892f87d8db26900f84c85bc329ef41c0540c0d6a448d1c23ddc64a80f3ff6838b940f3915a3fcb + languageName: node + linkType: hard + "localforage@npm:^1.8.1": version: 1.10.0 resolution: "localforage@npm:1.10.0" @@ -9715,6 +10068,15 @@ __metadata: languageName: node linkType: hard +"locate-path@npm:^7.1.0": + version: 7.2.0 + resolution: "locate-path@npm:7.2.0" + dependencies: + p-locate: "npm:^6.0.0" + checksum: 10c0/139e8a7fe11cfbd7f20db03923cacfa5db9e14fa14887ea121345597472b4a63c1a42a8a5187defeeff6acf98fd568da7382aa39682d38f0af27433953a97751 + languageName: node + linkType: hard + "lodash-es@npm:^4.17.21": version: 4.17.21 resolution: "lodash-es@npm:4.17.21" @@ -9767,6 +10129,16 @@ __metadata: languageName: node linkType: hard +"log-symbols@npm:^5.1.0": + version: 5.1.0 + resolution: "log-symbols@npm:5.1.0" + dependencies: + chalk: "npm:^5.0.0" + is-unicode-supported: "npm:^1.1.0" + checksum: 10c0/c14f8567c6618a7f96209c4c4b9fb3b794187116904712f7b526e465a5c9535728aec983735a5bef919247d0e54b9b72b6680a7fb9fc72d76b945dac4865e669 + languageName: node + linkType: hard + "log-update@npm:^4.0.0": version: 4.0.0 resolution: "log-update@npm:4.0.0" @@ -10156,17 +10528,15 @@ __metadata: languageName: node linkType: hard -"mipd@npm:0.0.5": - version: 0.0.5 - resolution: "mipd@npm:0.0.5" - dependencies: - viem: "npm:^1.1.4" +"mipd@npm:0.0.7": + version: 0.0.7 + resolution: "mipd@npm:0.0.7" peerDependencies: typescript: ">=5.0.4" peerDependenciesMeta: typescript: optional: true - checksum: 10c0/6b0a82cdc9eec5c12132b46422799cf536b1062b307a6aa0ce57ef240c56bd2dd231a5eda367c8a8962cbff73dd1af6131b8d769e3b47a06f0fc9d148b56f3dd + checksum: 10c0/c536e4fcdc15793b4538f72da389f8901a7eccb2e1eb55d8878f234a45f1c271064650e76fa2967b94743e19cc32ceab3c7b1e0dc614e28a45b0bbd6c987795d languageName: node linkType: hard @@ -10424,19 +10794,6 @@ __metadata: languageName: node linkType: hard -"notistack@npm:^3.0.1": - version: 3.0.1 - resolution: "notistack@npm:3.0.1" - dependencies: - clsx: "npm:^1.1.0" - goober: "npm:^2.0.33" - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - checksum: 10c0/dd5bd492dbaf8d07a1f45a53ae195c5d481bc7136d73a756eb076534d315216a3cd2f4628263be55ad21d8bfed6ec546e5063584ffcc2798fb2aac56e5ccf0cf - languageName: node - linkType: hard - "npm-run-path@npm:^5.1.0": version: 5.3.0 resolution: "npm-run-path@npm:5.3.0" @@ -10656,6 +11013,23 @@ __metadata: languageName: node linkType: hard +"ora@npm:^6.3.1": + version: 6.3.1 + resolution: "ora@npm:6.3.1" + dependencies: + chalk: "npm:^5.0.0" + cli-cursor: "npm:^4.0.0" + cli-spinners: "npm:^2.6.1" + is-interactive: "npm:^2.0.0" + is-unicode-supported: "npm:^1.1.0" + log-symbols: "npm:^5.1.0" + stdin-discarder: "npm:^0.1.0" + strip-ansi: "npm:^7.0.1" + wcwidth: "npm:^1.0.1" + checksum: 10c0/f8753e234c9967c86cfb73e7396e1a51ed8771c4921d539af8e8962b32c7928cefef7b3c4ce730a504be72b1437f91cc0523f468927b9fe322498c4edcc50203 + languageName: node + linkType: hard + "os-tmpdir@npm:~1.0.2": version: 1.0.2 resolution: "os-tmpdir@npm:1.0.2" @@ -10688,6 +11062,15 @@ __metadata: languageName: node linkType: hard +"p-limit@npm:^4.0.0": + version: 4.0.0 + resolution: "p-limit@npm:4.0.0" + dependencies: + yocto-queue: "npm:^1.0.0" + checksum: 10c0/a56af34a77f8df2ff61ddfb29431044557fcbcb7642d5a3233143ebba805fc7306ac1d448de724352861cb99de934bc9ab74f0d16fe6a5460bdbdf938de875ad + languageName: node + linkType: hard + "p-locate@npm:^4.1.0": version: 4.1.0 resolution: "p-locate@npm:4.1.0" @@ -10706,6 +11089,15 @@ __metadata: languageName: node linkType: hard +"p-locate@npm:^6.0.0": + version: 6.0.0 + resolution: "p-locate@npm:6.0.0" + dependencies: + p-limit: "npm:^4.0.0" + checksum: 10c0/d72fa2f41adce59c198270aa4d3c832536c87a1806e0f69dffb7c1a7ca998fb053915ca833d90f166a8c082d3859eabfed95f01698a3214c20df6bb8de046312 + languageName: node + linkType: hard + "p-map@npm:^4.0.0": version: 4.0.0 resolution: "p-map@npm:4.0.0" @@ -10805,6 +11197,13 @@ __metadata: languageName: node linkType: hard +"path-exists@npm:^5.0.0": + version: 5.0.0 + resolution: "path-exists@npm:5.0.0" + checksum: 10c0/b170f3060b31604cde93eefdb7392b89d832dfbc1bed717c9718cbe0f230c1669b7e75f87e19901da2250b84d092989a0f9e44d2ef41deb09aa3ad28e691a40a + languageName: node + linkType: hard + "path-is-absolute@npm:^1.0.0": version: 1.0.1 resolution: "path-is-absolute@npm:1.0.1" @@ -10901,6 +11300,13 @@ __metadata: languageName: node linkType: hard +"picomatch@npm:^3.0.0": + version: 3.0.1 + resolution: "picomatch@npm:3.0.1" + checksum: 10c0/70ec738569f1864658378b7abdab8939d15dae0718c1df994eae3346fd33daf6a3c1ff4e0c1a0cd1e2c0319130985b63a2cff34d192f2f2acbb78aca76111736 + languageName: node + linkType: hard + "pidtree@npm:~0.6.0": version: 0.6.0 resolution: "pidtree@npm:0.6.0" @@ -11028,6 +11434,15 @@ __metadata: languageName: node linkType: hard +"prettier@npm:^3.0.3": + version: 3.3.3 + resolution: "prettier@npm:3.3.3" + bin: + prettier: bin/prettier.cjs + checksum: 10c0/b85828b08e7505716324e4245549b9205c0cacb25342a030ba8885aba2039a115dbcf75a0b7ca3b37bc9d101ee61fab8113fc69ca3359f2a226f1ecc07ad2e26 + languageName: node + linkType: hard + "prettier@npm:^3.3.2": version: 3.3.2 resolution: "prettier@npm:3.3.2" @@ -11308,6 +11723,18 @@ __metadata: languageName: node linkType: hard +"react-hot-toast@npm:^2.4.1": + version: 2.4.1 + resolution: "react-hot-toast@npm:2.4.1" + dependencies: + goober: "npm:^2.1.10" + peerDependencies: + react: ">=16" + react-dom: ">=16" + checksum: 10c0/591ecec3c6adc1cdb70f00165a57baa3d7f75d0d30fa767213c36496bdcc6be2b2e6a3edbf7c04f7d726a1b17dcfb5e7feb2136b04b17c9ccb769894b970f365 + languageName: node + linkType: hard + "react-is@npm:^16.10.2, react-is@npm:^16.13.1, react-is@npm:^16.7.0": version: 16.13.1 resolution: "react-is@npm:16.13.1" @@ -12385,6 +12812,15 @@ __metadata: languageName: node linkType: hard +"stdin-discarder@npm:^0.1.0": + version: 0.1.0 + resolution: "stdin-discarder@npm:0.1.0" + dependencies: + bl: "npm:^5.0.0" + checksum: 10c0/3bbf7f8107e49c05b4a46bd739afdd34605cf1f06a038c8b2a33d034bf146344fc0ebc5149df1e6422510dd219971a220f25b1102413ef5128fe267683fbef9d + languageName: node + linkType: hard + "stream-shift@npm:^1.0.2": version: 1.0.3 resolution: "stream-shift@npm:1.0.3" @@ -12871,10 +13307,10 @@ __metadata: languageName: node linkType: hard -"type-fest@npm:^4.20.1": - version: 4.20.1 - resolution: "type-fest@npm:4.20.1" - checksum: 10c0/c31da16fe170a74c5b7e102e70e764ba8ec6ade128e782a56f842aefa07307df5a6353e6b5601d3b30ff2d856d8b955f89813df639e4758fedcf8e426b2d854e +"type-fest@npm:^4.26.1": + version: 4.26.1 + resolution: "type-fest@npm:4.26.1" + checksum: 10c0/d2719ff8d380befe8a3c61068f37f28d6fa2849fd140c5d2f0f143099e371da6856aad7c97e56b83329d45bfe504afe9fd936a7cff600cc0d46aa9ffb008d6c6 languageName: node linkType: hard @@ -12944,23 +13380,23 @@ __metadata: languageName: node linkType: hard -"typescript@npm:^5.5.2": - version: 5.5.2 - resolution: "typescript@npm:5.5.2" +"typescript@npm:^5.6.3": + version: 5.6.3 + resolution: "typescript@npm:5.6.3" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10c0/8ca39b27b5f9bd7f32db795045933ab5247897660627251e8254180b792a395bf061ea7231947d5d7ffa5cb4cc771970fd4ef543275f9b559f08c9325cccfce3 + checksum: 10c0/44f61d3fb15c35359bc60399cb8127c30bae554cd555b8e2b46d68fa79d680354b83320ad419ff1b81a0bdf324197b29affe6cc28988cd6a74d4ac60c94f9799 languageName: node linkType: hard -"typescript@patch:typescript@npm%3A^5.5.2#optional!builtin<compat/typescript>": - version: 5.5.2 - resolution: "typescript@patch:typescript@npm%3A5.5.2#optional!builtin<compat/typescript>::version=5.5.2&hash=5adc0c" +"typescript@patch:typescript@npm%3A^5.6.3#optional!builtin<compat/typescript>": + version: 5.6.3 + resolution: "typescript@patch:typescript@npm%3A5.6.3#optional!builtin<compat/typescript>::version=5.6.3&hash=5adc0c" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10c0/6721ac8933a70c252d7b640b345792e103d881811ff660355617c1836526dbb71c2044e2e77a8823fb3570b469f33276875a4cab6d3c4de4ae7d7ee1c3074ae4 + checksum: 10c0/ac8307bb06bbfd08ae7137da740769b7d8c3ee5943188743bb622c621f8ad61d244767480f90fbd840277fbf152d8932aa20c33f867dea1bb5e79b187ca1a92f languageName: node linkType: hard @@ -13042,13 +13478,6 @@ __metadata: languageName: node linkType: hard -"unfetch@npm:^4.2.0": - version: 4.2.0 - resolution: "unfetch@npm:4.2.0" - checksum: 10c0/a5c0a896a6f09f278b868075aea65652ad185db30e827cb7df45826fe5ab850124bf9c44c4dafca4bf0c55a0844b17031e8243467fcc38dd7a7d435007151f1b - languageName: node - linkType: hard - "unique-filename@npm:^3.0.0": version: 3.0.0 resolution: "unique-filename@npm:3.0.0" @@ -13308,13 +13737,13 @@ __metadata: languageName: node linkType: hard -"utf-8-validate@npm:^6.0.3": - version: 6.0.4 - resolution: "utf-8-validate@npm:6.0.4" +"utf-8-validate@npm:^5.0.2": + version: 5.0.10 + resolution: "utf-8-validate@npm:5.0.10" dependencies: node-gyp: "npm:latest" node-gyp-build: "npm:^4.3.0" - checksum: 10c0/f7042d94aec6ca02461b64e725bdc7262266610dbb787331e5bbd49374ef6f75fe9900600df3fc63d97906c23614a965c8989b4bf95d70bf35dc617da99215e7 + checksum: 10c0/23cd6adc29e6901aa37ff97ce4b81be9238d0023c5e217515b34792f3c3edb01470c3bd6b264096dd73d0b01a1690b57468de3a24167dd83004ff71c51cc025f languageName: node linkType: hard @@ -13403,45 +13832,25 @@ __metadata: languageName: node linkType: hard -"viem@npm:^1.0.0, viem@npm:^1.1.4": - version: 1.21.4 - resolution: "viem@npm:1.21.4" - dependencies: - "@adraffy/ens-normalize": "npm:1.10.0" - "@noble/curves": "npm:1.2.0" - "@noble/hashes": "npm:1.3.2" - "@scure/bip32": "npm:1.3.2" - "@scure/bip39": "npm:1.2.1" - abitype: "npm:0.9.8" - isows: "npm:1.0.3" - ws: "npm:8.13.0" - peerDependencies: - typescript: ">=5.0.4" - peerDependenciesMeta: - typescript: - optional: true - checksum: 10c0/8b29c790181e44c4c95b9ffed1a8c1b6c2396eb949b95697cc390ca8c49d88ef9e2cd56bd4800b90a9bbc93681ae8d63045fc6fa06e00d84f532bef77967e751 - languageName: node - linkType: hard - -"viem@npm:^2.16.5": - version: 2.16.5 - resolution: "viem@npm:2.16.5" +"viem@npm:2.x, viem@npm:^2.1.1, viem@npm:^2.21.25": + version: 2.21.25 + resolution: "viem@npm:2.21.25" dependencies: - "@adraffy/ens-normalize": "npm:1.10.0" - "@noble/curves": "npm:1.4.0" - "@noble/hashes": "npm:1.4.0" - "@scure/bip32": "npm:1.4.0" - "@scure/bip39": "npm:1.3.0" - abitype: "npm:1.0.5" - isows: "npm:1.0.4" - ws: "npm:8.17.1" + "@adraffy/ens-normalize": "npm:1.11.0" + "@noble/curves": "npm:1.6.0" + "@noble/hashes": "npm:1.5.0" + "@scure/bip32": "npm:1.5.0" + "@scure/bip39": "npm:1.4.0" + abitype: "npm:1.0.6" + isows: "npm:1.0.6" + webauthn-p256: "npm:0.0.10" + ws: "npm:8.18.0" peerDependencies: typescript: ">=5.0.4" peerDependenciesMeta: typescript: optional: true - checksum: 10c0/24fd0cf8dd91a831ed5d2a8c2a7ec55dbe213b174973942448a30b5984531d1d066ac371d864cb11a34bbf16cdcab63e48f2861a8c7ea6c8093c503af81c7a1e + checksum: 10c0/8c9c9e6ed5c16484c7d27b3cfc4ec3ebf90f32a7ddaac2a2cbec417e65ec77470009619dcc3a24bf7284f3ee6d04b274d0da933154758b75edafc95f45223fcb languageName: node linkType: hard @@ -13535,12 +13944,12 @@ __metadata: languageName: node linkType: hard -"wagmi@npm:^2.10.8": - version: 2.10.8 - resolution: "wagmi@npm:2.10.8" +"wagmi@npm:^2.12.17": + version: 2.12.17 + resolution: "wagmi@npm:2.12.17" dependencies: - "@wagmi/connectors": "npm:5.0.20" - "@wagmi/core": "npm:2.11.5" + "@wagmi/connectors": "npm:5.1.15" + "@wagmi/core": "npm:2.13.8" use-sync-external-store: "npm:1.2.0" peerDependencies: "@tanstack/react-query": ">=5.0.0" @@ -13550,7 +13959,7 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 10c0/8231268155606c22e91ffde2d066e127fcd858d3e4e3386e38db1f817d87452fc88c01b18380f383ab9e597a508f6d6177cc4b2b67ed9512a27c8727ecc3dd89 + checksum: 10c0/a64b5fd364c051297ad9efec292be46c7e00f0718f944bc7974f4715b617f762f1c167f6cfb86881c0a51474320aaeef10b230b7722771a7285252fd1bf6e53b languageName: node linkType: hard @@ -13570,6 +13979,16 @@ __metadata: languageName: node linkType: hard +"webauthn-p256@npm:0.0.10": + version: 0.0.10 + resolution: "webauthn-p256@npm:0.0.10" + dependencies: + "@noble/curves": "npm:^1.4.0" + "@noble/hashes": "npm:^1.4.0" + checksum: 10c0/27d836d81a1fec24a31d2d9b652f8ff6876b51940d1003bbd14dc5cfa57c58d84223b5a4eece229516522fd997bc0bc7be618ac42b129fb5fa42fa530060b16d + languageName: node + linkType: hard + "webcrypto-core@npm:^1.7.8": version: 1.7.8 resolution: "webcrypto-core@npm:1.7.8" @@ -13752,24 +14171,9 @@ __metadata: languageName: node linkType: hard -"ws@npm:8.13.0": - version: 8.13.0 - resolution: "ws@npm:8.13.0" - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: ">=5.0.2" - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - checksum: 10c0/579817dbbab3ee46669129c220cfd81ba6cdb9ab5c3e9a105702dd045743c4ab72e33bb384573827c0c481213417cc880e41bc097e0fc541a0b79fa3eb38207d - languageName: node - linkType: hard - -"ws@npm:8.17.1": - version: 8.17.1 - resolution: "ws@npm:8.17.1" +"ws@npm:8.18.0": + version: 8.18.0 + resolution: "ws@npm:8.18.0" peerDependencies: bufferutil: ^4.0.1 utf-8-validate: ">=5.0.2" @@ -13778,7 +14182,7 @@ __metadata: optional: true utf-8-validate: optional: true - checksum: 10c0/f4a49064afae4500be772abdc2211c8518f39e1c959640457dcee15d4488628620625c783902a52af2dd02f68558da2868fd06e6fd0e67ebcd09e6881b1b5bfe + checksum: 10c0/25eb33aff17edcb90721ed6b0eb250976328533ad3cd1a28a274bd263682e7296a6591ff1436d6cbc50fa67463158b062f9d1122013b361cec99a05f84680e06 languageName: node linkType: hard @@ -13959,6 +14363,13 @@ __metadata: languageName: node linkType: hard +"yocto-queue@npm:^1.0.0": + version: 1.1.1 + resolution: "yocto-queue@npm:1.1.1" + checksum: 10c0/cb287fe5e6acfa82690acb43c283de34e945c571a78a939774f6eaba7c285bacdf6c90fbc16ce530060863984c906d2b4c6ceb069c94d1e0a06d5f2b458e2a92 + languageName: node + linkType: hard + "yup@npm:^1.4.0": version: 1.4.0 resolution: "yup@npm:1.4.0" @@ -13971,6 +14382,13 @@ __metadata: languageName: node linkType: hard +"zod@npm:^3.22.2": + version: 3.23.8 + resolution: "zod@npm:3.23.8" + checksum: 10c0/8f14c87d6b1b53c944c25ce7a28616896319d95bc46a9660fe441adc0ed0a81253b02b5abdaeffedbeb23bdd25a0bf1c29d2c12dd919aef6447652dd295e3e69 + languageName: node + linkType: hard + "zustand@npm:4.4.1": version: 4.4.1 resolution: "zustand@npm:4.4.1"