diff --git a/apps/shell/src/app/app.tsx b/apps/shell/src/app/app.tsx index 8076182fb..284f69c9e 100644 --- a/apps/shell/src/app/app.tsx +++ b/apps/shell/src/app/app.tsx @@ -39,6 +39,16 @@ const WalletCheckPage = lazy(async () => { const { WalletCheckPage } = await import('@haqq/shell-airdrop'); return { default: WalletCheckPage }; }); +const CreateVestingAccountPage = lazy(async () => { + const { CreateVestingAccountPage } = await import( + '@haqq/shell/services-pages' + ); + return { default: CreateVestingAccountPage }; +}); +const VestingInfoPage = lazy(async () => { + const { VestingInfoPage } = await import('@haqq/shell/services-pages'); + return { default: VestingInfoPage }; +}); export function App() { return ( @@ -87,6 +97,9 @@ export function App() { } /> + } /> + } /> + } /> } /> diff --git a/libs/shell/services-pages/.babelrc b/libs/shell/services-pages/.babelrc new file mode 100644 index 000000000..1ea870ead --- /dev/null +++ b/libs/shell/services-pages/.babelrc @@ -0,0 +1,12 @@ +{ + "presets": [ + [ + "@nx/react/babel", + { + "runtime": "automatic", + "useBuiltIns": "usage" + } + ] + ], + "plugins": [] +} diff --git a/libs/shell/services-pages/.eslintrc.json b/libs/shell/services-pages/.eslintrc.json new file mode 100644 index 000000000..75b85077d --- /dev/null +++ b/libs/shell/services-pages/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["plugin:@nx/react", "../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/libs/shell/services-pages/README.md b/libs/shell/services-pages/README.md new file mode 100644 index 000000000..bbb860937 --- /dev/null +++ b/libs/shell/services-pages/README.md @@ -0,0 +1,7 @@ +# services-pages + +This library was generated with [Nx](https://nx.dev). + +## Running unit tests + +Run `nx test services-pages` to execute the unit tests via [Jest](https://jestjs.io). diff --git a/libs/shell/services-pages/jest.config.ts b/libs/shell/services-pages/jest.config.ts new file mode 100644 index 000000000..a21030752 --- /dev/null +++ b/libs/shell/services-pages/jest.config.ts @@ -0,0 +1,18 @@ +/* eslint-disable */ +export default { + displayName: 'services-pages', + preset: '../../../jest.preset.js', + transform: { + '^.+\\.[tj]sx?$': [ + '@swc/jest', + { + jsc: { + parser: { syntax: 'typescript', tsx: true }, + transform: { react: { runtime: 'automatic' } }, + }, + }, + ], + }, + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], + coverageDirectory: '../../../coverage/libs/shell/services-pages', +}; diff --git a/libs/shell/services-pages/project.json b/libs/shell/services-pages/project.json new file mode 100644 index 000000000..fd34e4bbd --- /dev/null +++ b/libs/shell/services-pages/project.json @@ -0,0 +1,30 @@ +{ + "name": "services-pages", + "$schema": "../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "libs/shell/services-pages/src", + "projectType": "library", + "tags": [], + "targets": { + "lint": { + "executor": "@nx/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["libs/shell/services-pages/**/*.{ts,tsx,js,jsx}"] + } + }, + "test": { + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "options": { + "jestConfig": "libs/shell/services-pages/jest.config.ts", + "passWithNoTests": true + }, + "configurations": { + "ci": { + "ci": true, + "codeCoverage": true + } + } + } + } +} diff --git a/libs/shell/services-pages/src/index.ts b/libs/shell/services-pages/src/index.ts new file mode 100644 index 000000000..48ad3ff24 --- /dev/null +++ b/libs/shell/services-pages/src/index.ts @@ -0,0 +1,2 @@ +export * from './lib/create-vesting-account-page/create-vesting-account-page'; +export * from './lib/vesting-info-page/vesting-info-page'; diff --git a/libs/shell/services-pages/src/lib/create-vesting-account-page/create-vesting-account-page.tsx b/libs/shell/services-pages/src/lib/create-vesting-account-page/create-vesting-account-page.tsx new file mode 100644 index 000000000..f8c233a02 --- /dev/null +++ b/libs/shell/services-pages/src/lib/create-vesting-account-page/create-vesting-account-page.tsx @@ -0,0 +1,499 @@ +import { ethToHaqq, haqqToEth, useAddress, useClipboard } from '@haqq/shared'; +import { Button, Container } from '@haqq/shell-ui-kit'; +import clsx from 'clsx'; +import { useCallback, useEffect, useState } from 'react'; +import { isAddress } from 'viem'; +import { useVestingActions } from '../use-vesting-actions/use-vesting-actions'; + +function formatDate(date: Date) { + return new Intl.DateTimeFormat('en-US', { + day: 'numeric', + month: 'short', + year: 'numeric', + hour: 'numeric', + minute: 'numeric', + timeZone: 'GMT', + }).format(date); +} + +type Period = { + length: number; + amount: { + denom: string; + amount: string; + }[]; +}; + +function createUnsignedTransaction( + fromAddress: string, + toAddress: string, + startTime: string, + lockupPeriods: Period[], + vestingPeriods: Period[], +) { + return { + body: { + messages: [ + { + '@type': '/haqq.vesting.v1.MsgConvertIntoVestingAccount', + from_address: fromAddress, + to_address: toAddress, + start_time: startTime, + lockup_periods: lockupPeriods, + vesting_periods: vestingPeriods, + merge: true, + stake: false, + validator_address: '', + }, + ], + memo: '', + timeout_height: '0', + extension_options: [], + non_critical_extension_options: [], + }, + auth_info: { + signer_infos: [], + fee: { + amount: [{ denom: 'aISLM', amount: '61559740000000000' }], + gas_limit: '3077987', + payer: '', + granter: '', + }, + tip: null, + }, + signatures: [], + }; +} + +export function CreateVestingAccountPage() { + return ( +
+
+ +
+ Create vesting account +
+
+
+ +
+
+ +
+
+
+ ); +} + +function CreateVestingAccountForm() { + const [fromAccount, setFromAccount] = useState(''); + const [isFromValid, setFromValid] = useState(false); + const [fromAddresses, setFromAddresses] = useState<{ + eth: string; + haqq: string; + }>({ + eth: '', + haqq: '', + }); + const [targetAccount, setTargetAccount] = useState(''); + const [isTargetValid, setTargetValid] = useState(false); + const [targetAddresses, setTargetAddresses] = useState<{ + eth: string; + haqq: string; + }>({ + eth: '', + haqq: '', + }); + const [amount, setAmount] = useState(10000); + const [lockup, setLockup] = useState(5); + const [unlock, setUnlock] = useState(60); + const [startTime, setStartTime] = useState(1695695564); + const { getPeriods } = useVestingActions(); + const [generatedTx, setGeneratedTx] = useState({}); + const { haqqAddress } = useAddress(); + const { copyText } = useClipboard(); + // const toast = useToast(); + // const { chain } = useNetwork(); + // const invalidateQueries = useQueryInvalidate(); + + // const handleCreateNewClick = useCallback(async () => { + // const grantPromise = createNew( + // targetAddresses.haqq, + // amount, + // startTime, + // lockup, + // unlock, + // ); + + // await toast.promise(grantPromise, { + // loading: Revoke in progress, + // success: (tx) => { + // console.log('Revoke successful', { tx }); + // const txHash = tx.txhash; + + // return ( + // + //
+ //
Revoke successful
+ //
+ // + // + // {getFormattedAddress(txHash)} + // + //
+ //
+ //
+ // ); + // }, + // error: (error) => { + // return {error.message}; + // }, + // }); + + // // invalidateQueries([ + // // [chain?.id, 'grants-granter'], + // // [chain?.id, 'grants-grantee'], + // // ]); + // }, [ + // amount, + // createNew, + // lockup, + // startTime, + // targetAddresses.haqq, + // toast, + // unlock, + // ]); + + useEffect(() => { + if (fromAccount.startsWith('0x')) { + console.log('validate as eth'); + try { + const isValidEthAddress = isAddress(fromAccount); + + if (isValidEthAddress) { + const haqq = ethToHaqq(fromAccount); + setFromValid(true); + setFromAddresses({ + eth: fromAccount, + haqq, + }); + } else { + setFromValid(false); + setFromAddresses({ + eth: fromAccount, + haqq: '', + }); + } + } catch { + setFromValid(false); + setFromAddresses({ + eth: fromAccount, + haqq: '', + }); + } + } else if (fromAccount.startsWith('haqq1')) { + console.log('validate as bech32'); + try { + const eth = haqqToEth(fromAccount); + setFromValid(true); + setFromAddresses({ + haqq: fromAccount, + eth: eth, + }); + } catch { + setFromValid(false); + setFromAddresses({ + haqq: fromAccount, + eth: '', + }); + } + } + }, [fromAccount]); + + useEffect(() => { + if (targetAccount.startsWith('0x')) { + console.log('validate as eth'); + try { + const isValidEthAddress = isAddress(targetAccount); + + if (isValidEthAddress) { + const haqq = ethToHaqq(targetAccount); + setTargetValid(true); + setTargetAddresses({ + eth: targetAccount, + haqq, + }); + } else { + setTargetValid(false); + setTargetAddresses({ + eth: targetAccount, + haqq: '', + }); + } + } catch { + setTargetValid(false); + setTargetAddresses({ + eth: targetAccount, + haqq: '', + }); + } + } else if (targetAccount.startsWith('haqq1')) { + console.log('validate as bech32'); + try { + const eth = haqqToEth(targetAccount); + setTargetValid(true); + setTargetAddresses({ + haqq: targetAccount, + eth: eth, + }); + } catch { + setTargetValid(false); + setTargetAddresses({ + haqq: targetAccount, + eth: '', + }); + } + } + }, [targetAccount]); + + useEffect(() => { + const { lockupPeriods, vestingPeriods } = getPeriods( + amount, + lockup, + unlock, + ); + + const date = new Date(); + date.setTime(startTime * 1000); + let startTimeIsoString = date.toISOString(); + startTimeIsoString = startTimeIsoString.replace('.000Z', 'Z'); + + const tx = createUnsignedTransaction( + fromAddresses.haqq, + targetAddresses.haqq, + startTimeIsoString, + lockupPeriods, + vestingPeriods, + ); + + setGeneratedTx(tx); + }, [ + amount, + getPeriods, + haqqAddress, + lockup, + startTime, + fromAddresses.haqq, + targetAddresses.haqq, + unlock, + ]); + + const handleSaveFileClick = useCallback(() => { + const jsonString = JSON.stringify(generatedTx, null, 2); + const blob = new Blob([jsonString], { type: 'application/json' }); + const blobUrl = URL.createObjectURL(blob); + const downloadLink = document.createElement('a'); + downloadLink.href = blobUrl; + downloadLink.download = `${targetAddresses.haqq}-convert-to-vesting-tx.json`; + downloadLink.click(); + URL.revokeObjectURL(blobUrl); + }, [generatedTx, targetAddresses.haqq]); + const [isCopied, setIsCopied] = useState(false); + + const handleCopyClick = useCallback(async () => { + const jsonString = JSON.stringify(generatedTx, null, 2); + await copyText(jsonString); + setIsCopied(true); + setTimeout(() => { + setIsCopied(false); + }, 2000); + }, [copyText, generatedTx]); + + return ( +
+ +
+
+ { + setFromAccount(value); + }} + /> + { + setTargetAccount(value); + }} + /> + { + const nextValue = !Number.isNaN(Number.parseFloat(value)) + ? Number.parseFloat(value) + : 0; + setAmount(nextValue); + }} + /> + { + const nextValue = !Number.isNaN(Number.parseInt(value)) + ? Number.parseInt(value) + : 0; + setLockup(nextValue); + }} + /> + { + const nextValue = !Number.isNaN(Number.parseInt(value)) + ? Number.parseInt(value) + : 0; + setUnlock(nextValue); + }} + /> +
+ { + const nextValue = !Number.isNaN(Number.parseInt(value)) + ? Number.parseInt(value) + : 0; + setStartTime(nextValue); + }} + /> +
+ {formatDate(new Date(startTime * 1000))} +
+
+ + {/*
+ +
*/} +
+
+
+
+
+                  {JSON.stringify(generatedTx, null, 2)}
+                
+
+ + {!isTargetValid && ( +
+ Wrong parameters +
+ )} +
+ +
+
+ +
+
+ +
+
+
+
+
+
+ ); +} + +function Input({ + value, + onChange, + placeholder, + label, + id, +}: { + id: string; + placeholder: string; + value: string; + label: string; + onChange: (value: string) => void; +}) { + return ( +
+
+ +
+
+ { + onChange(event.currentTarget.value); + }} + /> +
+
+ ); +} diff --git a/libs/shell/services-pages/src/lib/use-vesting-actions/use-vesting-actions.tsx b/libs/shell/services-pages/src/lib/use-vesting-actions/use-vesting-actions.tsx new file mode 100644 index 000000000..516799a9a --- /dev/null +++ b/libs/shell/services-pages/src/lib/use-vesting-actions/use-vesting-actions.tsx @@ -0,0 +1,315 @@ +import { useCallback, useMemo } from 'react'; +import { MessageMsgCreateClawbackVestingAccount } from '@evmos/transactions'; +import { + useAddress, + getChainParams, + useCosmosService, + mapToCosmosChain, +} from '@haqq/shared'; +import { useNetwork } from 'wagmi'; + +export function useVestingActions() { + const { chain } = useNetwork(); + // const { data: walletClient } = useWalletClient(); + const { getAccountBaseInfo, getPubkey } = useCosmosService(); + const { haqqAddress, ethAddress } = useAddress(); + + const haqqChain = useMemo(() => { + if (!chain || chain.unsupported) { + return undefined; + } + + const chainParams = getChainParams(chain.id); + return mapToCosmosChain(chainParams); + }, [chain]); + + const getSender = useCallback( + async (address: string, pubkey: string) => { + try { + const accInfo = await getAccountBaseInfo(address); + + if (!accInfo) { + throw new Error('no base account info'); + } + + return { + accountAddress: address, + sequence: parseInt(accInfo.sequence, 10), + accountNumber: parseInt(accInfo.account_number, 10), + pubkey, + }; + } catch (error) { + console.error((error as Error).message); + throw error; + } + }, + [getAccountBaseInfo], + ); + + // const signTransaction = useCallback( + // async (msg: TxGenerated, sender: Sender) => { + // if (haqqChain && ethAddress && walletClient) { + // const signature = await walletClient.request({ + // method: 'eth_signTypedData_v4', + // params: [ethAddress as `0x${string}`, JSON.stringify(msg.eipToSign)], + // }); + + // const extension = signatureToWeb3Extension( + // haqqChain, + // sender, + // signature, + // ); + // const rawTx = createTxRawEIP712( + // msg.legacyAmino.body, + // msg.legacyAmino.authInfo, + // extension, + // ); + + // return rawTx; + // } else { + // throw new Error('No haqqChain'); + // } + // }, + // [ethAddress, haqqChain, walletClient], + // ); + + const getPeriods = useCallback( + (amount: number, lockupInDays: number, vestingInDays: number) => { + const cliff = lockupInDays * 24 * 60 * 60; + const periodLength = 24 * 60 * 60; + const periods = vestingInDays; + const denom = 'aISLM'; + const lockupPeriods = []; + const aislmAmount = BigInt(amount * 10 ** 18); + let restAmount = aislmAmount; + let unlockAmount = aislmAmount / BigInt(periods); + let length: number = cliff; + + for (let i = 0; i < periods; i++) { + if (i === periods - 1) { + unlockAmount = restAmount; + } + + const unlockCoins = { + denom: denom, + amount: unlockAmount.toString(), + }; + + const period = { + length: length, + amount: [unlockCoins], + }; + + lockupPeriods.push(period); + restAmount -= unlockAmount; + + if (i === 0) { + length = periodLength; + } + } + + return { + lockupPeriods, + vestingPeriods: [] as typeof lockupPeriods, + }; + }, + [], + ); + + const getParams = useCallback( + async ( + fromAddress: string, + toAddress: string, + amount: number, + startTime: number, + lockupInDays: number, + vestingInDays: number, + ) => { + const { lockupPeriods, vestingPeriods } = getPeriods( + amount, + lockupInDays, + vestingInDays, + ); + + const createParams: MessageMsgCreateClawbackVestingAccount = { + fromAddress, + toAddress, + startTime, + lockupPeriods, + vestingPeriods, + merge: false, + }; + + return createParams; + }, + [getPeriods], + ); + + const handleCreateNew = useCallback( + async ( + toAddress: string, + amount: number, + startTime: number, + lockupInDays: number, + vestingInDays: number, + ) => { + console.log('handleCreateNew', { + toAddress, + startTime, + lockupInDays, + vestingInDays, + }); + const pubkey = await getPubkey(ethAddress as string); + const sender = await getSender(haqqAddress as string, pubkey); + // const memo = `Create vesting account with address ${toAddress}`; + + if (sender && haqqChain && ethAddress) { + // const createParams = await getParams( + // sender.accountAddress, + // toAddress, + // amount, + // startTime, + // lockupInDays, + // vestingInDays, + // ); + + // const msg = createTxCreateClawbackVestingAccount( + // haqqChain, + // sender, + // DEFAULT_FEE, + // memo, + // createParams, + // ); + // console.log({ msg }); + + // const rawTx = await signTransaction(msg, sender); + // const txResponse = await broadcastTransaction(rawTx); + + // return txResponse; + return { txhash: 'foo' }; + } else { + throw new Error('No sender'); + } + }, + [ethAddress, getPubkey, getSender, haqqAddress, haqqChain], + ); + + return useMemo(() => { + return { + createNew: handleCreateNew, + getParams, + getPeriods, + }; + }, [getParams, getPeriods, handleCreateNew]); +} + +// const MSG_CONVERT_TO_CLAWBACK_VESTING_ACCOUNT = {}; + +// function createTxCreateClawbackVestingAccount( +// chain: any, +// sender: any, +// fee: any, +// memo: any, +// params: any, +// ) { +// const feeObject = generateFee( +// fee.amount, +// fee.denom, +// fee.gas, +// sender.accountAddress, +// ); +// const types = generateTypes(MSG_CONVERT_TO_CLAWBACK_VESTING_ACCOUNT); +// const msg = createEipMsgConvertToClawbackVestingAccount( +// params.fromAddress, +// params.toAddress, +// params.startTime, +// params.lockupPeriods, +// params.vestingPeriods, +// params.merge, +// ); +// const messages = generateMessage( +// sender.accountNumber.toString(), +// sender.sequence.toString(), +// chain.cosmosChainId, +// memo, +// feeObject, +// msg, +// ); +// const eipToSign = createEIP712(types, chain.chainId, messages); +// const msgCosmos = createComsosMsgConvertToClawbackVestingAccount( +// params.fromAddress, +// params.toAddress, +// params.startTime, +// params.lockupPeriods, +// params.vestingPeriods, +// params.merge, +// ); +// const tx = createTransaction( +// msgCosmos, +// memo, +// fee.amount, +// fee.denom, +// Number.parseInt(fee.gas, 10), +// 'ethsecp256', +// sender.pubkey, +// sender.sequence, +// sender.accountNumber, +// chain.cosmosChainId, +// ); +// return { +// signDirect: tx.signDirect, +// legacyAmino: tx.legacyAmino, +// eipToSign, +// }; +// } + +// export function createComsosMsgConvertToClawbackVestingAccount( +// fromAddress: string, +// toAddress: string, +// startTime: number, +// lockupPeriods: Period[], +// vestingPeriods: Period[], +// merge: boolean, +// ) { +// // const msg = createProtoMsgConvertToClawbackVestingAccount( +// // fromAddress, +// // toAddress, +// // startTime, +// // lockupPeriods, +// // vestingPeriods, +// // merge, +// // ); + +// return { +// message: 'msg', +// path: 'haqq.vesting.v1.MsgConvertIntoVestingAccount', +// }; +// } + +// export function createEipMsgConvertToClawbackVestingAccount( +// from_address: string, +// to_address: string, +// start_time: number, +// lockup_periods: Period[], +// vesting_periods: Period[], +// merge: boolean, +// ) { +// // EIP712 requires the date to be a string in format YYYY-MM-DDTHH:MM:SSZ +// const date = new Date(); +// date.setTime(start_time * 1000); +// let startTime = date.toISOString(); +// startTime = startTime.replace('.000Z', 'Z'); + +// return { +// type: 'haqq/MsgConvertIntoVestingAccount', +// value: { +// from_address, +// to_address, +// start_time: startTime, +// lockup_periods, +// vesting_periods, +// merge, +// stake: false, +// }, +// }; +// } diff --git a/libs/shell/services-pages/src/lib/vesting-info-page/vesting-info-page.tsx b/libs/shell/services-pages/src/lib/vesting-info-page/vesting-info-page.tsx new file mode 100644 index 000000000..20bd6db91 --- /dev/null +++ b/libs/shell/services-pages/src/lib/vesting-info-page/vesting-info-page.tsx @@ -0,0 +1,7 @@ +export function VestingInfoPage() { + return ( +
+

VestingInfoPage

+
+ ); +} diff --git a/libs/shell/services-pages/tsconfig.json b/libs/shell/services-pages/tsconfig.json new file mode 100644 index 000000000..4daaf45cd --- /dev/null +++ b/libs/shell/services-pages/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "jsx": "react-jsx", + "allowJs": false, + "esModuleInterop": false, + "allowSyntheticDefaultImports": true, + "strict": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ], + "extends": "../../../tsconfig.base.json" +} diff --git a/libs/shell/services-pages/tsconfig.lib.json b/libs/shell/services-pages/tsconfig.lib.json new file mode 100644 index 000000000..21799b3e6 --- /dev/null +++ b/libs/shell/services-pages/tsconfig.lib.json @@ -0,0 +1,24 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../dist/out-tsc", + "types": [ + "node", + + "@nx/react/typings/cssmodule.d.ts", + "@nx/react/typings/image.d.ts" + ] + }, + "exclude": [ + "jest.config.ts", + "src/**/*.spec.ts", + "src/**/*.test.ts", + "src/**/*.spec.tsx", + "src/**/*.test.tsx", + "src/**/*.spec.js", + "src/**/*.test.js", + "src/**/*.spec.jsx", + "src/**/*.test.jsx" + ], + "include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx"] +} diff --git a/libs/shell/services-pages/tsconfig.spec.json b/libs/shell/services-pages/tsconfig.spec.json new file mode 100644 index 000000000..25b7af8f6 --- /dev/null +++ b/libs/shell/services-pages/tsconfig.spec.json @@ -0,0 +1,20 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "include": [ + "jest.config.ts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/**/*.d.ts" + ] +} diff --git a/tsconfig.base.json b/tsconfig.base.json index ca49a6e75..09e98ae90 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -116,6 +116,7 @@ "@haqq/shell-ui-kit": ["libs/shell/ui-kit/src/index.ts"], "@haqq/shell/authz-page": ["libs/shell/authz-page/src/index.ts"], "@haqq/shell/index-page": ["libs/shell/index-page/src/index.ts"], + "@haqq/shell/services-pages": ["libs/shell/services-pages/src/index.ts"], "@haqq/staking/ui-kit": ["libs/staking/ui-kit/src/index.ts"], "@haqq/staking/utils": ["libs/staking/utils/src/index.ts"], "@haqq/staking/validator-details-page": [