diff --git a/package.json b/package.json index ce6f93661..720f8d995 100644 --- a/package.json +++ b/package.json @@ -31,16 +31,16 @@ "@emotion/styled": "^11.10.4", "@ethersproject/address": "^5.7.0", "@ethersproject/providers": "^5.7.2", - "@galacticcouncil/apps": "3.4.11", + "@galacticcouncil/apps": "^3.5.4", "@galacticcouncil/math-lbp": "^0.2.1", "@galacticcouncil/math-liquidity-mining": "^0.2.0", "@galacticcouncil/math-omnipool": "^0.2.0", "@galacticcouncil/math-stableswap": "^0.2.2", "@galacticcouncil/math-staking": "^0.2.0", "@galacticcouncil/math-xyk": "^0.2.0", - "@galacticcouncil/sdk": "^2.1.0", - "@galacticcouncil/ui": "^3.1.5", - "@galacticcouncil/xcm-cfg": "^1.11.3", + "@galacticcouncil/sdk": "^2.2.3", + "@galacticcouncil/ui": "^3.1.14", + "@galacticcouncil/xcm-cfg": "^1.11.7", "@galacticcouncil/xcm-sdk": "^2.4.0", "@hookform/resolvers": "^3.3.4", "@lit-labs/react": "^1.1.0", @@ -69,15 +69,15 @@ "@testing-library/user-event": "^13.5.0", "@types/papaparse": "^5.3.14", "@types/uuid": "^8.3.4", - "@walletconnect/modal": "^2.6.2", - "@walletconnect/sign-client": "2.8.0", - "@walletconnect/types": "^2.8.0", - "@walletconnect/universal-provider": "2.8.0", + "@walletconnect/modal": "2.6.2", + "@walletconnect/sign-client": "2.12.2", + "@walletconnect/types": "2.12.2", + "@walletconnect/universal-provider": "2.12.2", "bignumber.js": "^9.1.0", "color": "^4.2.3", "comlink": "^4.3.1", "date-fns": "^2.29.1", - "ethers": "^5.7.0", + "ethers": "5.7.0", "ethers-decode-error": "^1.0.0", "framer-motion": "^10.16.4", "graphql": "^16.6.0", diff --git a/src/api/assetDetails.ts b/src/api/assetDetails.ts index bb3139857..e050a3ba8 100644 --- a/src/api/assetDetails.ts +++ b/src/api/assetDetails.ts @@ -113,7 +113,7 @@ export type TStableSwap = TAssetCommon & { export type TShareToken = TAssetCommon & { assetType: "ShareToken" assets: string[] - poolAddress: string | undefined + poolAddress: string } export type TAsset = TToken | TBond | TStableSwap | TShareToken @@ -161,12 +161,16 @@ export const getAssets = async (api: ApiPromise) => { rawAssetsData, rawAssetsLocations, hubAssetId, + poolAddresses, + xykPoolAssets, isReferralsEnabled, ] = await Promise.all([ api.rpc.system.properties(), api.query.assetRegistry.assets.entries(), api.query.assetRegistry.assetLocations.entries(), api.consts.omnipool.hubAssetId, + api.query.xyk.shareToken.entries(), + api.query.xyk.poolAssets.entries(), api.query.referrals, ]) @@ -193,7 +197,7 @@ export const getAssets = async (api: ApiPromise) => { //@ts-ignore const isExternal = assetType === "External" //@ts-ignore - const isSufficient = data.isSufficient.toPrimitive() + const isSufficient = data.isSufficient?.toPrimitive() ?? false let meta if (rawAssetsMeta) { @@ -369,7 +373,6 @@ export const getAssets = async (api: ApiPromise) => { stableswap.push(asset) } } else if (isShareToken) { - const poolAddresses = await api.query.xyk.shareToken.entries() const poolAddress = poolAddresses .find( (poolAddress) => poolAddress[1].toString() === assetCommon.id, @@ -377,16 +380,21 @@ export const getAssets = async (api: ApiPromise) => { .args[0].toString() if (poolAddress) { - const poolAssets = await api.query.xyk.poolAssets(poolAddress) - const assets = poolAssets - .unwrap() - .map((poolAsset) => poolAsset.toString()) + const poolAssets = xykPoolAssets.find( + (xykPool) => xykPool[0].args[0].toString() === poolAddress, + )?.[1] - shareTokensRaw.push({ - ...assetCommon, - assets, - poolAddress, - }) + if (poolAssets) { + const assets = poolAssets + .unwrap() + .map((poolAsset) => poolAsset.toString()) + + shareTokensRaw.push({ + ...assetCommon, + assets, + poolAddress, + }) + } } } else if (isExternal) { const location = rawAssetsLocations.find( @@ -432,6 +440,8 @@ export const getAssets = async (api: ApiPromise) => { const shareTokens = shareTokensRaw.reduce>( (acc, shareToken) => { + if (!shareToken.assets) return acc + const [assetAId, assetBId] = shareToken.assets const assetA = [...tokens, ...bonds, ...external].find( @@ -441,9 +451,9 @@ export const getAssets = async (api: ApiPromise) => { (token) => token.id === assetBId, ) as TToken - const isValdiTokens = assetA?.name && assetB?.name + const isValidTokens = assetA?.name && assetB?.name - if (isValdiTokens) { + if (isValidTokens) { const assetDecimal = Number(assetA.id) > Number(assetB.id) ? assetB : assetA @@ -494,6 +504,7 @@ export const getAssets = async (api: ApiPromise) => { hub, rawTradeAssets, }, + poolService, tradeRouter, featureFlags: { referrals: !!isReferralsEnabled, diff --git a/src/api/balances.ts b/src/api/balances.ts index 625cc44b8..a60d20ad6 100644 --- a/src/api/balances.ts +++ b/src/api/balances.ts @@ -159,3 +159,28 @@ export const getTokenLock = type: lock.id.toString(), })) } + +export const useShareTokenBalances = (shareTokenIds: string[]) => { + const { api, assets } = useRpcProvider() + + const shareTokens = assets + .getAssets(shareTokenIds) + .reduce<{ id: string; address: string }[]>((acc, asset) => { + if (assets.isShareToken(asset)) { + asset.assets.forEach((id) => + acc.push({ id, address: asset.poolAddress }), + ) + } + + return acc + }, []) + + return useQueries({ + queries: shareTokens.map(({ id, address }) => ({ + queryKey: QUERY_KEYS.tokenBalanceLive(id, address), + queryFn: + address != null ? getTokenBalance(api, address, id) : undefinedNoop, + enabled: !!id && !!address, + })), + }) +} diff --git a/src/api/deposits.ts b/src/api/deposits.ts index e1609d872..aa22937e8 100644 --- a/src/api/deposits.ts +++ b/src/api/deposits.ts @@ -1,63 +1,65 @@ import { ApiPromise } from "@polkadot/api" -import { u128, u32 } from "@polkadot/types" -import { AccountId32 } from "@polkadot/types/interfaces" +import { u128, u32, Option } from "@polkadot/types" import { useQueries, useQuery } from "@tanstack/react-query" -import { useAccount } from "sections/web3-connect/Web3Connect.utils" -import { Maybe, undefinedNoop, useQueryReduce } from "utils/helpers" import { QUERY_KEYS } from "utils/queryKeys" import { useRpcProvider } from "providers/rpcProvider" +import { PalletLiquidityMiningDepositData } from "@polkadot/types/lookup" +import { undefinedNoop } from "utils/helpers" +import { useAccount } from "sections/web3-connect/Web3Connect.utils" -const DEPOSIT_NFT_COLLECTION_ID = "2584" +export type TDeposit = { + id: string + data: PalletLiquidityMiningDepositData + isXyk: boolean +} -export const useAccountDepositIds = ( - accountId: Maybe, -) => { +export const useOmnipoolDeposits = (ids: string[]) => { const { api } = useRpcProvider() + return useQuery( - QUERY_KEYS.accountDepositIds(accountId), - accountId != null ? getAccountDepositIds(api, accountId) : undefinedNoop, - { enabled: !!accountId }, + QUERY_KEYS.omnipoolDeposits(ids), + getDeposits(api, "omnipool", ids), + { enabled: !!ids.length }, ) } -const getAccountDepositIds = - (api: ApiPromise, accountId: AccountId32 | string) => async () => { - const res = await api.query.uniques.account.entries( - accountId, - DEPOSIT_NFT_COLLECTION_ID, - ) - const nfts = res.map(([storageKey]) => { - const [owner, classId, instanceId] = storageKey.args - return { owner, classId, instanceId } - }) - - return nfts - } - -export const useAllDeposits = () => { +export const useXYKDeposits = (ids: string[]) => { const { api } = useRpcProvider() - return useQuery(QUERY_KEYS.allDeposits, getDeposits(api)) -} -export const usePoolDeposits = (poolId?: u32 | string) => { - const { api } = useRpcProvider() - return useQuery(QUERY_KEYS.poolDeposits(poolId), getDeposits(api), { - enabled: !!poolId, - select: (data) => - data.filter( - (item) => item.data.ammPoolId.toString() === poolId?.toString(), - ), + return useQuery(QUERY_KEYS.xykDeposits(ids), getDeposits(api, "xyk", ids), { + enabled: !!ids.length, }) } -export const useOmniPositionId = (positionId: u128 | string) => { - const { api } = useRpcProvider() +const getDeposits = + (api: ApiPromise, type: "omnipool" | "xyk", ids: string[]) => async () => { + if (!ids.length) return undefined - return useQuery( - QUERY_KEYS.omniPositionId(positionId), - getOmniPositionId(api, positionId), - ) -} + const keys = ids.map((id) => + api.query[`${type}WarehouseLM`].deposit.key(id), + ) + const values = (await api.rpc.state.queryStorageAt( + keys, + )) as Option[] + + const data = values + .filter((value) => !value.isNone) + .map( + (value) => + api.registry.createType( + type === "omnipool" ? "OmnipoolLMDeposit" : "XykLMDeposit", + value.unwrap(), + ) as PalletLiquidityMiningDepositData, + ) + + const data_ = ids.map((id, i) => ({ + id, + data: data[i], + isXyk: type === "xyk", + })) + + return data_ + } export const useOmniPositionIds = (positionIds: Array) => { const { api } = useRpcProvider() @@ -70,15 +72,6 @@ export const useOmniPositionIds = (positionIds: Array) => { })), }) } - -const getDeposits = (api: ApiPromise) => async () => { - const res = await api.query.omnipoolWarehouseLM.deposit.entries() - return res.map(([key, value]) => ({ - id: key.args[0], - data: value.unwrap(), - })) -} - const getOmniPositionId = (api: ApiPromise, depositionId: u128 | string) => async () => { const res = @@ -86,38 +79,76 @@ const getOmniPositionId = return { depositionId, value: res.value } } -export const useAccountDeposits = (poolId?: u32) => { +export const useAccountNFTPositions = (givenAddress?: string) => { const { account } = useAccount() - const accountDepositIds = useAccountDepositIds(account?.address) - const deposits = usePoolDeposits(poolId) - - return useQueryReduce( - [accountDepositIds, deposits] as const, - (accountDepositIds, deposits) => { - const ids = new Set( - accountDepositIds?.map((i) => i.instanceId.toString()), - ) - return deposits.filter((item) => ids.has(item.id.toString())) - }, + const { api } = useRpcProvider() + + const address = givenAddress ?? account?.address + + return useQuery( + QUERY_KEYS.accountOmnipoolPositions(address), + address != null + ? async () => { + const [omnipoolNftId, miningNftId, xykMiningNftId] = + await Promise.all([ + api.consts.omnipool.nftCollectionId, + api.consts.omnipoolLiquidityMining.nftCollectionId, + api.consts.xykLiquidityMining.nftCollectionId, + ]) + const [omnipoolNftsRaw, miningNftsRaw, xykMiningNftsRaw] = + await Promise.all([ + api.query.uniques.account.entries(address, omnipoolNftId), + api.query.uniques.account.entries(address, miningNftId), + api.query.uniques.account.entries(address, xykMiningNftId), + ]) + + const omnipoolNfts = omnipoolNftsRaw.map(([storageKey]) => { + const [owner, classId, instanceId] = storageKey.args + return { + owner: owner.toString(), + classId: classId.toString(), + instanceId: instanceId.toString(), + } + }) + + const miningNfts = miningNftsRaw.map(([storageKey]) => { + const [owner, classId, instanceId] = storageKey.args + return { + owner: owner.toString(), + classId: classId.toString(), + instanceId: instanceId.toString(), + } + }) + + const xykMiningNfts = xykMiningNftsRaw.map(([storageKey]) => { + const [owner, classId, instanceId] = storageKey.args + return { + owner: owner.toString(), + classId: classId.toString(), + instanceId: instanceId.toString(), + } + }) + + return { omnipoolNfts, miningNfts, xykMiningNfts } + } + : undefinedNoop, + { enabled: !!address }, ) } -export const useUserDeposits = () => { - const { account } = useAccount() - const accountDepositIds = useAccountDepositIds(account?.address) - const deposits = useAllDeposits() - - const query = useQueryReduce( - [accountDepositIds, deposits] as const, - (accountDepositIds, deposits) => { - return deposits.filter( - (deposit) => - accountDepositIds?.some( - (id) => id.instanceId.toString() === deposit.id.toString(), - ), - ) - }, - ) +export const useUserDeposits = (address?: string) => { + const nftPositions = useAccountNFTPositions(address) + + const { miningNfts = [], xykMiningNfts = [] } = nftPositions.data ?? {} + const omnipoolDeposits = + useOmnipoolDeposits( + miningNfts.map((miningNft) => miningNft.instanceId), + ).data?.filter((deposit) => deposit.data) ?? [] + + const xykDeposits = + useXYKDeposits( + xykMiningNfts.map((xykMiningNft) => xykMiningNft.instanceId), + ).data?.filter((deposit) => deposit.data) ?? [] - return query + return { omnipoolDeposits, xykDeposits } } diff --git a/src/api/externalAssetRegistry.ts b/src/api/externalAssetRegistry.ts index 0345b72d9..c67df5e5b 100644 --- a/src/api/externalAssetRegistry.ts +++ b/src/api/externalAssetRegistry.ts @@ -2,6 +2,20 @@ import { useQuery } from "@tanstack/react-query" import { QUERY_KEYS } from "utils/queryKeys" import { chainsMap } from "@galacticcouncil/xcm-cfg" import { SubstrateApis } from "@galacticcouncil/xcm-sdk" +import { + ASSET_HUB_ID, + TExternalAsset, +} from "sections/wallet/addToken/AddToken.utils" + +type TRegistryChain = { + assetCnt: string + id: string + paraID: number + relayChain: "polkadot" | "kusama" + data: (TExternalAsset & { currencyID: string })[] +} + +const HYDRA_PARACHAIN_ID = 2034 export const getAssetHubAssets = async () => { const parachain = chainsMap.get("assethub") @@ -76,3 +90,47 @@ export const useAssetHubAssetRegistry = () => { }, ) } + +export const usePolkadotRegistry = () => { + return useQuery(["polkadotRegistry"], async () => { + const res = await fetch( + "https://cdn.jsdelivr.net/gh/colorfulnotion/xcm-global-registry/metadata/xcmgar.json", + ) + const data = await res.json() + let polkadotAssets: TRegistryChain[] = [] + + try { + polkadotAssets = data?.assets?.polkadot ?? [] + } catch (error) {} + + return polkadotAssets + }) +} + +export const useParachainAmount = (id: string) => { + const chains = usePolkadotRegistry() + + const validChains = chains.data?.reduce((acc, chain) => { + // skip asst hub and hydra chains + if (chain.paraID === ASSET_HUB_ID || chain.paraID === HYDRA_PARACHAIN_ID) + return acc + + const assets = chain.data + + const isAsset = assets.some((asset) => { + try { + return asset.currencyID === id + } catch (error) { + return false + } + }) + + if (isAsset) { + acc.push(chain) + } + + return acc + }, []) + + return { chains: validChains ?? [], amount: validChains?.length ?? 0 } +} diff --git a/src/api/farms.ts b/src/api/farms.ts index d4bc9afd7..c4dc61c02 100644 --- a/src/api/farms.ts +++ b/src/api/farms.ts @@ -8,8 +8,8 @@ import { import { useQueries, useQuery } from "@tanstack/react-query" import BigNumber from "bignumber.js" import { secondsInYear } from "date-fns" -import { BLOCK_TIME, BN_0, PARACHAIN_BLOCK_TIME } from "utils/constants" -import { Maybe, isNotNil, undefinedNoop, useQueryReduce } from "utils/helpers" +import { BLOCK_TIME, BN_0, BN_1, PARACHAIN_BLOCK_TIME } from "utils/constants" +import { undefinedNoop, useQueryReduce } from "utils/helpers" import { QUERY_KEYS } from "utils/queryKeys" import { useBestNumber } from "./chain" import { fixed_from_rational } from "@galacticcouncil/math-liquidity-mining" @@ -18,184 +18,196 @@ import { useRpcProvider } from "providers/rpcProvider" import { useIndexerUrl } from "./provider" import request, { gql } from "graphql-request" import { AccountId32 } from "@polkadot/types/interfaces" +import { useMemo } from "react" +import { scale } from "utils/balance" const NEW_YIELD_FARMS_BLOCKS = (48 * 60 * 60) / PARACHAIN_BLOCK_TIME.toNumber() // 48 hours -export function useActiveYieldFarms(poolIds: Array>) { - const { api } = useRpcProvider() +type FarmIds = { + poolId: string + globalFarmId: string + yieldFarmId: string +} + +type FarmAprs = ReturnType + +export interface Farm { + globalFarm: PalletLiquidityMiningGlobalFarmData + yieldFarm: PalletLiquidityMiningYieldFarmData + poolId: string +} + +export function useActiveYieldFarms(poolIds: Array) { + const { api, assets } = useRpcProvider() return useQueries({ - queries: poolIds.map((poolId) => ({ - queryKey: QUERY_KEYS.activeYieldFarms(poolId), - queryFn: - poolId != null ? getActiveYieldFarms(api, poolId) : undefinedNoop, - enabled: poolId != null, - })), + queries: poolIds.map((poolId) => { + const meta = assets.getAsset(poolId) + const isXYK = assets.isShareToken(meta) + + return { + queryKey: isXYK + ? QUERY_KEYS.activeYieldFarmsXYK(poolId) + : QUERY_KEYS.activeYieldFarms(poolId), + queryFn: getActiveYieldFarms( + api, + poolId, + isXYK ? meta.poolAddress : undefined, + ), + enabled: poolId != null, + } + }), }) } +const getActiveYieldFarms = + (api: ApiPromise, poolId: string, poolAddress: string | undefined) => + async () => { + const res = poolAddress + ? await api.query.xykWarehouseLM.activeYieldFarm.entries(poolAddress) + : await api.query.omnipoolWarehouseLM.activeYieldFarm.entries(poolId) -export const getActiveYieldFarms = - (api: ApiPromise, poolId: u32 | string) => async () => { - const res = - await api.query.omnipoolWarehouseLM.activeYieldFarm.entries(poolId) return res.map(([storageKey, codec]) => { - const [poolId, globalFarmId] = storageKey.args + const [, globalFarmId] = storageKey.args const yieldFarmId = codec.unwrap() - return { poolId, globalFarmId, yieldFarmId } + return { + poolId, + globalFarmId: globalFarmId.toString(), + yieldFarmId: yieldFarmId.toString(), + } }) } -export function useYieldFarms( - ids: Maybe< - { - poolId: u32 | string - globalFarmId: u32 | string - yieldFarmId: u32 | string - }[] - >, -) { - const { api } = useRpcProvider() - return useQuery( - QUERY_KEYS.yieldFarms(ids), - ids != null ? getYieldFarms(api, ids) : undefinedNoop, - { enabled: ids != null }, - ) +export function useYieldFarms(ids: FarmIds[]) { + const { api, assets } = useRpcProvider() + + return useQueries({ + queries: ids.map(({ poolId, globalFarmId, yieldFarmId }) => { + const meta = assets.getAsset(poolId) + const isXYK = assets.isShareToken(meta) + + return { + queryKey: isXYK + ? QUERY_KEYS.yieldFarmXYK(yieldFarmId) + : QUERY_KEYS.yieldFarm(yieldFarmId), + queryFn: async () => { + const farm = await getYieldFarm( + api, + isXYK ? meta.poolAddress : poolId, + globalFarmId, + yieldFarmId, + isXYK, + )() + return { farm, poolId } + }, + enabled: poolId != null, + } + }), + }) } -const getYieldFarms = +const getYieldFarm = ( api: ApiPromise, - ids: { - poolId: u32 | string - globalFarmId: u32 | string - yieldFarmId: u32 | string - }[], + poolId: string, + globalFarmId: string, + yieldFarmId: string, + isXYK: boolean, ) => async () => { - const res = await Promise.all( - ids.map(({ poolId, globalFarmId, yieldFarmId }) => - api.query.omnipoolWarehouseLM.yieldFarm( + const yieldFarm = isXYK + ? await api.query.xykWarehouseLM.yieldFarm( poolId, globalFarmId, yieldFarmId, - ), - ), - ) - - return res.map((data) => data.unwrap()) - } - -export function useYieldFarm(data: { - poolId: Maybe - globalFarmId: Maybe - yieldFarmId: Maybe -}) { - const { api } = useRpcProvider() - return useQuery( - QUERY_KEYS.yieldFarm(data), - data.poolId != null && data.globalFarmId != null && data.yieldFarmId != null - ? getYieldFarm(api, data.poolId, data.globalFarmId, data.yieldFarmId) - : undefinedNoop, - { - enabled: - data.poolId != null && - data.globalFarmId != null && - data.yieldFarmId != null, - }, - ) -} + ) + : await api.query.omnipoolWarehouseLM.yieldFarm( + poolId, + globalFarmId, + yieldFarmId, + ) -const getYieldFarm = - ( - api: ApiPromise, - poolId: u32 | string, - globalFarmId: u32 | string, - yieldFarmId: u32 | string, - ) => - async () => { - const yieldFarm = await api.query.omnipoolWarehouseLM.yieldFarm( - poolId, - globalFarmId, - yieldFarmId, - ) return yieldFarm.unwrap() } -export function useGlobalFarm(id: Maybe) { - const { api } = useRpcProvider() - return useQuery( - QUERY_KEYS.globalFarm(id), - id != null ? getGlobalFarm(api, id) : undefinedNoop, - { enabled: id != null }, - ) -} +export function useGlobalFarms(ids: FarmIds[]) { + const { api, assets } = useRpcProvider() -const getGlobalFarm = (api: ApiPromise, id: u32 | string) => async () => { - const globalFarm = await api.query.omnipoolWarehouseLM.globalFarm(id) - return globalFarm.unwrap() + return useQueries({ + queries: ids.map(({ poolId, globalFarmId }) => { + const meta = assets.getAsset(poolId) + const isXYK = assets.isShareToken(meta) + + return { + queryKey: isXYK + ? QUERY_KEYS.globalFarmXYK(globalFarmId) + : QUERY_KEYS.globalFarm(globalFarmId), + queryFn: async () => { + const farm = await getGlobalFarm(api, globalFarmId, isXYK)() + return { farm, poolId } + }, + enabled: poolId != null, + } + }), + }) } -export function useGlobalFarms(ids: Maybe<{ globalFarmId: u32 }[]>) { - const { api } = useRpcProvider() - return useQuery( - QUERY_KEYS.globalFarms(ids), - ids != null ? getGlobalFarms(api, ids) : undefinedNoop, - { enabled: ids != null }, - ) -} +const getGlobalFarm = + (api: ApiPromise, id: string, isXYK: boolean) => async () => { + const globalFarm = isXYK + ? await api.query.xykWarehouseLM.globalFarm(id) + : await api.query.omnipoolWarehouseLM.globalFarm(id) -const getGlobalFarms = - (api: ApiPromise, ids: { globalFarmId: u32 }[]) => async () => { - const globalFarms = await api.query.omnipoolWarehouseLM.globalFarm.multi( - ids.map((i) => i.globalFarmId), - ) - return globalFarms.map((i) => i.unwrap()) + return globalFarm.unwrap() } -export interface Farm { - globalFarm: PalletLiquidityMiningGlobalFarmData - yieldFarm: PalletLiquidityMiningYieldFarmData - poolId: string -} +export const useFarms = (poolIds: Array) => { + const activeYieldFarmsQuery = useActiveYieldFarms(poolIds) -export type FarmAprs = ReturnType + const farmIds = activeYieldFarmsQuery + .reduce>>((acc, farm) => { + if (farm.data) acc.push(farm.data) + return acc + }, []) + .flat(2) -export type FarmQueryType = ReturnType + const globalFarms = useGlobalFarms(farmIds) + const yieldFarms = useYieldFarms(farmIds) -export const useFarms = (poolIds: Array) => { - const activeYieldFarms = useActiveYieldFarms(poolIds) + const queries = [globalFarms, yieldFarms] - const data = activeYieldFarms - .reduce( - (acc, farm) => { - if (farm.data) acc.push(farm.data) - return acc - }, - [] as Array>, - ) - .flat(2) + const isLoading = queries.some((querie) => + querie.some((q) => q.isInitialLoading), + ) - const globalFarms = useGlobalFarms(data) - const yieldFarms = useYieldFarms(data) + const data = useMemo(() => { + return farmIds + .map((farmId) => { + const globalFarm = globalFarms.find((globalFarm) => { + const data = globalFarm.data - return useQueryReduce( - [globalFarms, yieldFarms, ...activeYieldFarms] as const, - (globalFarms, yieldFarms, ...activeYieldFarms) => { - const farms = - activeYieldFarms.flat(2).map((af) => { - if (!af) return undefined - - const globalFarm = globalFarms?.find((gf) => - af.globalFarmId.eq(gf.id), + return ( + data?.farm.id.toString() === farmId.globalFarmId && + data.poolId === farmId.poolId ) - const yieldFarm = yieldFarms?.find((yf) => af.yieldFarmId.eq(yf.id)) - if (!globalFarm || !yieldFarm) return undefined - return { globalFarm, yieldFarm, poolId: af.poolId.toString() } - }) ?? [] + })?.data?.farm - return farms.filter((x): x is Farm => x != null) - }, - ) + const yieldFarm = yieldFarms.find((yieldFarm) => { + const data = yieldFarm.data + + return ( + data?.farm.id.toString() === farmId.yieldFarmId && + data.poolId === farmId.poolId + ) + })?.data?.farm + + if (!globalFarm || !yieldFarm) return undefined + + return { globalFarm, yieldFarm, poolId: farmId.poolId } + }) + .filter((x): x is Farm => x != null) + }, [farmIds, globalFarms, yieldFarms]) + + return { data, isLoading } } function getGlobalRewardPerPeriod( @@ -263,7 +275,7 @@ function getFarmApr( let apr if (totalSharesZ.isZero()) { - apr = yieldPerPeriod.times(multiplier).times(periodsPerYear) + apr = yieldPerPeriod.times(multiplier).times(periodsPerYear).shiftedBy(-18) } else { const globalRewardPerPeriod = getGlobalRewardPerPeriod( totalSharesZ, @@ -349,7 +361,12 @@ export const useFarmApr = (farm: { return useQueryReduce( [bestNumber, oraclePrice] as const, (bestNumber, oraclePrice) => { - return getFarmApr(bestNumber, farm, oraclePrice?.oraclePrice ?? BN_0) + return getFarmApr( + bestNumber, + farm, + oraclePrice?.oraclePrice ?? + farm.globalFarm.priceAdjustment.toBigNumber(), + ) }, ) } @@ -380,7 +397,8 @@ export const useFarmAprs = ( return getFarmApr( bestNumber, farm, - oraclePrice?.data?.oraclePrice ?? BN_0, + oraclePrice?.data?.oraclePrice ?? + farm.globalFarm.priceAdjustment.toBigNumber(), ) }) }) @@ -426,12 +444,24 @@ const getOraclePrice = string, string, ] + + if (rewardCurrency === incentivizedAsset) + return { + id: { rewardCurrency, incentivizedAsset }, + oraclePrice: scale(BN_1, "q"), + } const res = await api.query.emaOracle.oracles( "omnipool", orderedAssets, "TenMinutes", ) + if (res.isNone) + return { + id: { rewardCurrency, incentivizedAsset }, + oraclePrice: undefined, + } + const [data] = res.unwrap() const n = data.price.n.toString() const d = data.price.d.toString() @@ -446,6 +476,7 @@ const getOraclePrice = return { id: { rewardCurrency, incentivizedAsset }, oraclePrice: BN(oraclePrice), + price: { n, d }, } } @@ -464,12 +495,6 @@ export const getMinAndMaxAPR = (farms: FarmAprs) => { } } -export interface FarmIds { - poolId: u32 - globalFarmId: u32 - yieldFarmId: u32 -} - export const useFarmsPoolAssets = () => { const indexerUrl = useIndexerUrl() const { api } = useRpcProvider() @@ -533,35 +558,49 @@ export const useInactiveYieldFarms = (poolIds: (AccountId32 | string)[]) => { export const useInactiveFarms = (poolIds: Array) => { const activeYieldFarms = useInactiveYieldFarms(poolIds) - const data = activeYieldFarms.reduce< - { - poolId: string - globalFarmId: u32 - yieldFarmId: u32 - }[] - >((acc, farm) => (farm.data ? [...acc, ...farm.data] : acc), []) + const farmIds = activeYieldFarms.reduce( + (acc, farm) => (farm.data ? [...acc, ...farm.data] : acc), + [], + ) + + const globalFarms = useGlobalFarms(farmIds) + const yieldFarms = useYieldFarms(farmIds) + + const queries = [globalFarms, yieldFarms] - const globalFarms = useGlobalFarms( - data.map((id) => ({ globalFarmId: id.globalFarmId })), + const isLoading = queries.some((querie) => + querie.some((q) => q.isInitialLoading), ) - const yieldFarms = useYieldFarms(data) - return useQueryReduce( - [globalFarms, yieldFarms, ...activeYieldFarms] as const, - (globalFarms, yieldFarms, ...activeYieldFarms) => { - const farms = - activeYieldFarms.flat(2).map((af) => { - const globalFarm = globalFarms?.find((gf) => - af.globalFarmId.eq(gf.id), + const data = useMemo(() => { + return farmIds + .map((farmId) => { + const globalFarm = globalFarms.find((globalFarm) => { + const data = globalFarm.data + + return ( + data?.farm.id.toString() === farmId.globalFarmId && + data.poolId === farmId.poolId ) - const yieldFarm = yieldFarms?.find((yf) => af.yieldFarmId.eq(yf.id)) - if (!globalFarm || !yieldFarm) return undefined - return { globalFarm, yieldFarm } - }) ?? [] + })?.data?.farm - return farms.filter(isNotNil) - }, - ) + const yieldFarm = yieldFarms.find((yieldFarm) => { + const data = yieldFarm.data + + return ( + data?.farm.id.toString() === farmId.yieldFarmId && + data.poolId === farmId.poolId + ) + })?.data?.farm + + if (!globalFarm || !yieldFarm) return undefined + + return { globalFarm, yieldFarm, poolId: farmId.poolId } + }) + .filter((x): x is Farm => x != null) + }, [farmIds, globalFarms, yieldFarms]) + + return { data, isLoading } } const getInctiveYieldFarms = @@ -582,13 +621,7 @@ const getInctiveYieldFarms = ), ) - const stoppedFarms = globalFarms.reduce< - { - poolId: string - globalFarmId: u32 - yieldFarmId: u32 - }[] - >((acc, [globalFarm]) => { + const stoppedFarms = globalFarms.reduce((acc, [globalFarm]) => { if (globalFarm) { const yieldFarm = globalFarm[1].unwrap() @@ -597,8 +630,8 @@ const getInctiveYieldFarms = if (isStopped) acc.push({ poolId: globalFarm[0].args[0].toString(), - globalFarmId: globalFarm[0].args[1], - yieldFarmId: yieldFarm.id, + globalFarmId: globalFarm[0].args[1].toString(), + yieldFarmId: yieldFarm.id.toString(), }) } diff --git a/src/api/omnipool.ts b/src/api/omnipool.ts index 83fb889b8..c2443cac2 100644 --- a/src/api/omnipool.ts +++ b/src/api/omnipool.ts @@ -2,7 +2,6 @@ import { useQueries, useQuery } from "@tanstack/react-query" import { ApiPromise } from "@polkadot/api" import { QUERY_KEYS } from "utils/queryKeys" import { u128, u32 } from "@polkadot/types-codec" -import { ITuple } from "@polkadot/types-codec/types" import { undefinedNoop } from "utils/helpers" import { REFETCH_INTERVAL } from "utils/constants" import { useRpcProvider } from "providers/rpcProvider" @@ -92,19 +91,15 @@ export const getOmnipoolFee = (api: ApiPromise) => async () => { } } -export type OmnipoolPosition = { - id: string - assetId: u32 - amount: u128 - shares: u128 - price: ITuple<[u128, u128]> -} +export type OmnipoolPosition = Awaited< + ReturnType> +> export const getOmnipoolPosition = (api: ApiPromise, itemId: string) => async () => { const res = await api.query.omnipool.positions(itemId) const data = res.unwrap() - const position: OmnipoolPosition = { + const position = { id: itemId, assetId: data.assetId, amount: data.amount, diff --git a/src/api/payments.ts b/src/api/payments.ts index 89085f846..bfd4589d3 100644 --- a/src/api/payments.ts +++ b/src/api/payments.ts @@ -12,7 +12,6 @@ import { useAccount } from "sections/web3-connect/Web3Connect.utils" import { useAcountAssets } from "api/assetDetails" import { useMemo } from "react" import { uniqBy } from "utils/rx" -import { NATIVE_EVM_ASSET_ID, isEvmAccount } from "utils/evm" export const getAcceptedCurrency = (api: ApiPromise) => async () => { const dataRaw = @@ -103,24 +102,15 @@ export const useAccountCurrency = (address: Maybe) => { } export const useAccountFeePaymentAssets = () => { - const { assets } = useRpcProvider() const { account } = useAccount() const accountAssets = useAcountAssets(account?.address) const accountFeePaymentAsset = useAccountCurrency(account?.address) const feePaymentAssetId = accountFeePaymentAsset.data const allowedFeePaymentAssetsIds = useMemo(() => { - if (isEvmAccount(account?.address)) { - const evmNativeAssetId = assets.getAsset(NATIVE_EVM_ASSET_ID).id - return uniqBy( - identity, - [evmNativeAssetId, feePaymentAssetId].filter(isNotNil), - ) - } - const assetIds = accountAssets.map((accountAsset) => accountAsset.asset.id) return uniqBy(identity, [...assetIds, feePaymentAssetId].filter(isNotNil)) - }, [assets, account?.address, accountAssets, feePaymentAssetId]) + }, [accountAssets, feePaymentAssetId]) const acceptedFeePaymentAssets = useAcceptedCurrencies( allowedFeePaymentAssetsIds, diff --git a/src/api/provider.ts b/src/api/provider.ts index 37fa9242c..29735fa0e 100644 --- a/src/api/provider.ts +++ b/src/api/provider.ts @@ -102,6 +102,19 @@ export const useProviderData = (rpcUrl: string) => { const apiPool = SubstrateApis.getInstance() const api = await apiPool.api(url) + api.registry.register({ + XykLMDeposit: { + shares: "u128", + ammPoolId: "AccountId", + yieldFarmEntries: "Vec", + }, + OmnipoolLMDeposit: { + shares: "u128", + ammPoolId: "u32", + yieldFarmEntries: "Vec", + }, + }) + const { isStableCoin, stableCoinId: chainStableCoinId, @@ -139,6 +152,7 @@ export const useProviderData = (rpcUrl: string) => { assets: assets.assets, tradeRouter: assets.tradeRouter, featureFlags: assets.featureFlags, + poolService: assets.poolService, } }, { refetchOnWindowFocus: false }, diff --git a/src/api/volume.ts b/src/api/volume.ts index 54d0f74cf..85707c57c 100644 --- a/src/api/volume.ts +++ b/src/api/volume.ts @@ -11,12 +11,18 @@ import { u8aToHex } from "@polkadot/util" import { decodeAddress } from "@polkadot/util-crypto" export type TradeType = { - name: "Omnipool.SellExecuted" | "Omnipool.BuyExecuted" | "OTC.Placed" + name: + | "Omnipool.SellExecuted" + | "Omnipool.BuyExecuted" + | "XYK.SellExecuted" + | "XYK.BuyExecuted" + | "Router.Executed" id: string args: { who: string assetIn: number assetOut: number + amount?: string amountIn: string amountOut: string } @@ -28,6 +34,41 @@ export type TradeType = { } } +export type StableswapType = { + name: "Stableswap.LiquidityAdded" + id: string + args: { + who: string + poolId: number + assets: { amount: string; assetId: number }[] + amounts: { amount: string; assetId: number }[] + } + block: { + timestamp: string + } + extrinsic: { + hash: string + } +} + +export const isStableswapEvent = ( + event: TradeType | StableswapType, +): event is StableswapType => + ["Stableswap.LiquidityAdded", "Stableswap.LiquidityRemoved"].includes( + event.name, + ) + +export const isTradeEvent = ( + event: TradeType | StableswapType, +): event is TradeType => + [ + "Omnipool.SellExecuted", + "Omnipool.BuyExecuted", + "XYK.SellExecuted", + "XYK.BuyExecuted", + "Router.Executed", + ].includes(event.name) + export const getTradeVolume = (indexerUrl: string, assetId: string) => async () => { const assetIn = Number(assetId) @@ -148,26 +189,51 @@ export const getAllTrades = // describe the event arguments at all return { ...(await request<{ - events: Array + events: Array }>( indexerUrl, gql` query TradeVolume($assetId: Int, $after: DateTime!) { events( where: { - name_in: ["Omnipool.SellExecuted", "Omnipool.BuyExecuted"] - args_jsonContains: { assetIn: $assetId } - phase_eq: "ApplyExtrinsic" - block: { timestamp_gte: $after } - OR: { - name_in: ["Omnipool.SellExecuted", "Omnipool.BuyExecuted"] - args_jsonContains: { assetOut: $assetId } - phase_eq: "ApplyExtrinsic" - block: { timestamp_gte: $after } - } + OR: [ + { + name_in: [ + "Omnipool.SellExecuted" + "Omnipool.BuyExecuted" + "XYK.SellExecuted" + "XYK.BuyExecuted" + "Router.Executed" + ] + args_jsonContains: { assetIn: $assetId } + phase_eq: "ApplyExtrinsic" + block: { timestamp_gte: $after } + } + { + name_in: [ + "Omnipool.SellExecuted" + "Omnipool.BuyExecuted" + "XYK.SellExecuted" + "XYK.BuyExecuted" + "Router.Executed" + ] + args_jsonContains: { assetOut: $assetId } + phase_eq: "ApplyExtrinsic" + block: { timestamp_gte: $after } + } + { + name_in: [ + "Stableswap.LiquidityAdded" + "Stableswap.LiquidityRemoved" + ] + args_jsonContains: { poolId: $assetId } + phase_eq: "ApplyExtrinsic" + block: { timestamp_gte: $after } + } + ] } - orderBy: [block_height_DESC] - limit: 10 + orderBy: [block_height_DESC, pos_ASC] + limit: 100 ) { id name diff --git a/src/api/xyk.ts b/src/api/xyk.ts index 642c9f7c7..cc82fa4ec 100644 --- a/src/api/xyk.ts +++ b/src/api/xyk.ts @@ -66,14 +66,19 @@ export const useXYKConsts = () => { } const getXYKConsts = (api: ApiPromise) => async () => { - const [feeRaw, minTradingLimit] = await Promise.all([ + const [feeRaw, minTradingLimit, minPoolLiquidity] = await Promise.all([ api.consts.xyk.getExchangeFee, api.consts.xyk.minTradingLimit, + api.consts.xyk.minPoolLiquidity, ]) - //@ts-ignore - const fee = feeRaw?.map((el) => el.toString()) as string[] - return { fee: fee, minPoolLiquidity: minTradingLimit.toString() } + const fee = feeRaw.map((el) => el.toString()) + + return { + fee: fee, + minTradingLimit: minTradingLimit.toString(), + minPoolLiquidity: minPoolLiquidity.toString(), + } } export const useXYKTotalLiquidity = (address?: string) => { diff --git a/src/assets/icons/WarningIconRed.svg b/src/assets/icons/WarningIconRed.svg new file mode 100644 index 000000000..94cefb96b --- /dev/null +++ b/src/assets/icons/WarningIconRed.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/components/AppProviders/AppProviders.tsx b/src/components/AppProviders/AppProviders.tsx index b3feab5b7..547fdef41 100644 --- a/src/components/AppProviders/AppProviders.tsx +++ b/src/components/AppProviders/AppProviders.tsx @@ -6,6 +6,15 @@ import { FC, PropsWithChildren } from "react" import { SkeletonTheme } from "react-loading-skeleton" import { Transactions } from "sections/transaction/Transactions" import { theme } from "theme" +import * as React from "react" +import * as Apps from "@galacticcouncil/apps" +import { createComponent } from "@lit-labs/react" + +const AppsPersistenceProvider = createComponent({ + tagName: "gc-database-provider", + elementClass: Apps.DatabaseProvider, + react: React, +}) export const AppProviders: FC = ({ children }) => { return ( @@ -18,7 +27,7 @@ export const AppProviders: FC = ({ children }) => { highlightColor={`rgba(${theme.rgbColors.white}, 0.24)`} borderRadius={4} > - {children} + {children} diff --git a/src/components/AssetIcon/AssetIcon.tsx b/src/components/AssetIcon/AssetIcon.tsx index eb36f2f73..8bcddc703 100644 --- a/src/components/AssetIcon/AssetIcon.tsx +++ b/src/components/AssetIcon/AssetIcon.tsx @@ -2,6 +2,7 @@ import * as React from "react" import { createComponent } from "@lit-labs/react" import { AssetId, + AssetBadge, ChainLogo as ChainLogoUi, PlaceholderLogo, } from "@galacticcouncil/ui" @@ -12,10 +13,12 @@ import { useRpcProvider } from "providers/rpcProvider" import { useTranslation } from "react-i18next" const EXTERNAL_ASSETS_WHITELIST = [ - // DED - { id: "30", origin: 1000 }, // PINK { id: "23", origin: 1000 }, + // STINK + { id: "42069", origin: 1000 }, + // WUD + { id: "31337", origin: 1000 }, ] const chains = Array.from(chainsMap.values()) @@ -32,6 +35,12 @@ export const UigcAssetId = createComponent({ react: React, }) +export const UigcAssetBadge = createComponent({ + tagName: "uigc-asset-badge", + elementClass: AssetBadge, + react: React, +}) + export const UigcChainLogo = createComponent({ tagName: "uigc-logo-chain", elementClass: ChainLogoUi, @@ -79,12 +88,16 @@ export const AssetLogo = ({ id }: { id?: string }) => { item.origin === chain?.parachainId, ) - const displayWarning = assetDetails?.isExternal && !isWhitelisted + const badgeVariant: "warning" | "danger" | "" = assetDetails?.isExternal + ? isWhitelisted + ? "warning" + : "danger" + : "" return { chain: chain?.key, symbol: assetDetails?.symbol, - displayWarning, + badgeVariant, } }, [assets, id]) @@ -98,12 +111,15 @@ export const AssetLogo = ({ id }: { id?: string }) => { }} symbol={asset.symbol} chain={asset?.chain} - warning={ - asset?.displayWarning - ? t("wallet.addToken.tooltip.warning") - : undefined - } - /> + > + {asset.badgeVariant && ( + + )} + ) return ( diff --git a/src/components/AssetInput/AssetInput.styled.ts b/src/components/AssetInput/AssetInput.styled.ts index 28d431fbc..25dca3f54 100644 --- a/src/components/AssetInput/AssetInput.styled.ts +++ b/src/components/AssetInput/AssetInput.styled.ts @@ -50,6 +50,7 @@ export const SInput = styled.input` line-height: 24px; text-align: right; font-weight: 600; + font-family: "ChakraPetchSemiBold"; padding: 0; diff --git a/src/components/AssetSelect/AssetSelect.tsx b/src/components/AssetSelect/AssetSelect.tsx index fe58e6558..fb7df82b3 100644 --- a/src/components/AssetSelect/AssetSelect.tsx +++ b/src/components/AssetSelect/AssetSelect.tsx @@ -134,6 +134,7 @@ export const AssetSelect = (props: { void assetId: string + className?: string } -export const AssetSelectButton = ({ onClick, assetId }: Props) => { +export const AssetSelectButton = ({ onClick, assetId, className }: Props) => { const { t } = useTranslation() const { assets } = useRpcProvider() const asset = assets.getAsset(assetId) @@ -46,6 +47,7 @@ export const AssetSelectButton = ({ onClick, assetId }: Props) => { return ( { e.preventDefault() diff --git a/src/components/InfoTooltip/InfoTooltip.tsx b/src/components/InfoTooltip/InfoTooltip.tsx index 59564c10d..d50457724 100644 --- a/src/components/InfoTooltip/InfoTooltip.tsx +++ b/src/components/InfoTooltip/InfoTooltip.tsx @@ -39,6 +39,7 @@ export function InfoTooltip({ }} > { textOnClick && e.preventDefault() diff --git a/src/components/Layout/Header/menu/HeaderMenu.tsx b/src/components/Layout/Header/menu/HeaderMenu.tsx index 81420e01b..cc9aeb1ff 100644 --- a/src/components/Layout/Header/menu/HeaderMenu.tsx +++ b/src/components/Layout/Header/menu/HeaderMenu.tsx @@ -6,8 +6,8 @@ import { HeaderSubMenu } from "./HeaderSubMenu" import { forwardRef } from "react" import { useRpcProvider } from "providers/rpcProvider" import { useAccount } from "sections/web3-connect/Web3Connect.utils" -import { useAccountOmnipoolPositions } from "sections/pools/PoolsPage.utils" -import { useTokensBalances } from "api/balances" +import { useAccountNFTPositions } from "api/deposits" +import { useAccountBalances } from "api/accountBalances" export const HeaderMenu = forwardRef((_, ref) => { const { t } = useTranslation() @@ -75,26 +75,28 @@ const LiquidityMenuItem = ({ const { t } = useTranslation() const { account } = useAccount() const { assets } = useRpcProvider() - const accountPositions = useAccountOmnipoolPositions() + const accountPositions = useAccountNFTPositions() - const shareTokensId = assets.shareTokens.map((shareToken) => shareToken.id) - const stableswapsId = assets.stableswap.map((shareToken) => shareToken.id) + const balances = useAccountBalances(account?.address) - const userPositions = useTokensBalances( - [...shareTokensId, ...stableswapsId], - account?.address, - ) + const isPoolBalances = balances.data?.balances.some((balance) => { + if (balance.freeBalance.gt(0)) { + const meta = assets.getAsset(balance.id) + return meta.isStableSwap || meta.isShareToken + } + return false + }) - const isOmnipoolPositions = + const isPositions = accountPositions.data?.miningNfts.length || accountPositions.data?.omnipoolNfts.length || - userPositions.some((userPosition) => userPosition.data?.freeBalance.gt(0)) + isPoolBalances return ( {({ isActive }) => ( diff --git a/src/components/Modal/Modal.tsx b/src/components/Modal/Modal.tsx index 2df7a2700..7025d48a1 100644 --- a/src/components/Modal/Modal.tsx +++ b/src/components/Modal/Modal.tsx @@ -52,7 +52,6 @@ export const Modal = ({ return ( ) - }, [hasContentProps, children, className, onClose, onBack, contentProps]) + }, [hasContentProps, children, onClose, onBack, contentProps]) return ( @@ -69,6 +68,7 @@ export const Modal = ({ setIsAnimating(false)} + className={className} css={{ overflow: isAnimating ? "hidden" : undefined }} > ` +export const SLink = styled.div` display: flex; align-items: center; justify-content: center; - color: ${({ variant }) => { - if (variant === "progress") { - return theme.colors.white - } - - return `rgba(${theme.rgbColors.white}, 0.6)` - }}; + color: rgba(${theme.rgbColors.white}, 0.6); svg { width: 16px; diff --git a/src/components/Toast/ToastContent.tsx b/src/components/Toast/ToastContent.tsx index e5c70027c..36ec3d583 100644 --- a/src/components/Toast/ToastContent.tsx +++ b/src/components/Toast/ToastContent.tsx @@ -81,13 +81,13 @@ export function ToastContent(props: { - - {props.link && ( + {props.link && ( + - )} - + + )} {props.children} ) diff --git a/src/components/Toast/sidebar/ToastSidebar.tsx b/src/components/Toast/sidebar/ToastSidebar.tsx index d517276d9..0a9e9112b 100644 --- a/src/components/Toast/sidebar/ToastSidebar.tsx +++ b/src/components/Toast/sidebar/ToastSidebar.tsx @@ -81,6 +81,7 @@ export function ToastSidebar() { {pendingToasts.map((toast) => ( { +export const useRemount = ( + triggers: T[], +) => { const [version, setVersion] = useState(0) - const prevTriggerValue = usePrevious(trigger) + const prevTriggersValue = usePrevious(triggers) - const isChanged = !!prevTriggerValue !== trigger + const isChanged = + prevTriggersValue !== undefined + ? triggers.some((trigger, i) => prevTriggersValue[i] !== trigger) + : false useEffect(() => { if (isChanged) { diff --git a/src/i18n/locales/en/translations.json b/src/i18n/locales/en/translations.json index 86eb35b97..0425c4e84 100644 --- a/src/i18n/locales/en/translations.json +++ b/src/i18n/locales/en/translations.json @@ -1,4 +1,6 @@ { + "no": "No", + "yes": "Yes", "and": "and", "apr": "APR", "back": "Back", @@ -166,6 +168,7 @@ "liquidity.pool.details.price": "{{symbol}} Price", "liquidity.section.omnipoolAndStablepool": "Omnipool + Stablepool", "liquidity.section.xyk": "Isolated pools", + "liquidity.section.xyk.toggle": "Show low liquidity pools", "liquidity.asset.title": "Asset", "liquidity.assets.title": "Assets", "liquidity.asset.details.fee": "Fee", @@ -367,7 +370,8 @@ "otc.offers.table.header.offer": "Offer", "otc.offers.table.header.accepting": "Accepting", "otc.offers.table.header.assets": "Assets", - "otc.offers.table.header.orderPrice": "Order Price", + "otc.offers.table.header.orderPrice": "Offer Price", + "otc.offers.table.header.perToken": "per {{ symbol }}", "otc.offers.table.header.marketPrice": "Market Price", "otc.offers.table.header.status": "Order status", "otc.offers.table.actions.fill": "Fill order", @@ -512,10 +516,23 @@ "wallet.addToken.form.multilocation.label": "Multilocation", "wallet.addToken.form.button.register.hydra": "Register on Hydra", "wallet.addToken.form.button.register.forMe": "Add to my UI", + "wallet.addToken.form.info": { + "title":"Additional data", + "registered": "Asset registered on Hydra", + "registered.tooltip": "Asset {{value}} registered on HydraDx", + "masterAccount": "Master account key existing", + "masterAccount.tooltip": "The creator of this asset has not revoked their admin rights. This means the supply can be minted, burned or frozen at any point. Proceed at your own risk.", + "availability": "Available on other parachains", + "availability.tooltip_one": "This asset is also registered on {{count}} another parachain.", + "availability.tooltip_other": "This asset is also registered on {{count}} other parachains.", + "isolatedPool": "Isolated Pool created", + "volume": "Daily volume on HydraDX" + }, "wallet.addToken.toast.register.onLoading": "Registering {{name}} on HydraDx as external asset", "wallet.addToken.toast.register.onSuccess": "You registered {{name}} as external asset", "wallet.addToken.toast.add.onSuccess": "You added info about {{name}} to your UI", "wallet.addToken.tooltip.warning": "This asset is registered as an external token. Always conduct your own research before trading.", + "wallet.addToken.tooltip.danger": "The creator of this asset has not revoked their admin rights. This means the supply can be minted, burned or frozen at any point. Proceed at your own risk.", "wallet.assets.farmingPositions.empty.desc": "You don’t have any farming position yet, add liquidity and start earning with Hydra.", "wallet.transactions.table.header.title": "Transaction History", "wallet.transactions.table.header.download": "Download CSV", diff --git a/src/providers/rpcProvider.tsx b/src/providers/rpcProvider.tsx index 940df8c6b..d0a8a2113 100644 --- a/src/providers/rpcProvider.tsx +++ b/src/providers/rpcProvider.tsx @@ -1,4 +1,4 @@ -import { TradeRouter } from "@galacticcouncil/sdk" +import { TradeRouter, PoolService } from "@galacticcouncil/sdk" import { ApiPromise } from "@polkadot/api" import { TAsset, @@ -17,10 +17,11 @@ type IContextAssets = Awaited>["assets"] & { all: (TToken | TBond | TStableSwap | TShareToken)[] isStableSwap: (asset: TAsset) => asset is TStableSwap isBond: (asset: TAsset) => asset is TBond - isShareToken: (asset: TAsset) => asset is TShareToken + isShareToken: (asset: TAsset | undefined) => asset is TShareToken getAsset: (id: string) => TAsset getBond: (id: string) => TBond | undefined getAssets: (ids: string[]) => TAsset[] + getShareTokenByAddress: (address: string) => TShareToken | undefined tradeAssets: TAsset[] } @@ -28,6 +29,7 @@ type TProviderContext = { api: ApiPromise assets: IContextAssets tradeRouter: TradeRouter + poolService: PoolService isLoaded: boolean featureFlags: Awaited>["featureFlags"] } @@ -37,6 +39,7 @@ const ProviderContext = createContext({ assets: {} as TProviderContext["assets"], tradeRouter: {} as TradeRouter, featureFlags: {} as TProviderContext["featureFlags"], + poolService: {} as TProviderContext["poolService"], }) export const useRpcProvider = () => useContext(ProviderContext) @@ -86,8 +89,15 @@ export const RpcProvider = ({ children }: { children: ReactNode }) => { const isBond = (asset: TAsset): asset is TBond => asset.isBond - const isShareToken = (asset: TAsset): asset is TShareToken => - asset.isShareToken + const isShareToken = ( + asset: TAsset | undefined, + ): asset is TShareToken => { + if (!asset) return false + return asset.isShareToken + } + + const getShareTokenByAddress = (address: string) => + shareTokens.find((shareToken) => shareToken.poolAddress === address) const getAsset = (id: string) => allTokensObject[id] ?? fallbackAsset @@ -114,8 +124,10 @@ export const RpcProvider = ({ children }: { children: ReactNode }) => { getAsset, getBond, getAssets, + getShareTokenByAddress, tradeAssets, }, + poolService: providerData.data.poolService, api: providerData.data.api, tradeRouter: providerData.data.tradeRouter, featureFlags: providerData.data.featureFlags, @@ -128,6 +140,7 @@ export const RpcProvider = ({ children }: { children: ReactNode }) => { assets: {} as TProviderContext["assets"], tradeRouter: {} as TradeRouter, featureFlags: {} as TProviderContext["featureFlags"], + poolService: {} as TProviderContext["poolService"], } }, [preference._hasHydrated, providerData.data]) diff --git a/src/sections/assets/AssetsModal.tsx b/src/sections/assets/AssetsModal.tsx index 64a56dddb..36137b5a2 100644 --- a/src/sections/assets/AssetsModal.tsx +++ b/src/sections/assets/AssetsModal.tsx @@ -27,7 +27,6 @@ type Props = { allAssets?: boolean withBonds?: boolean withExternal?: boolean - withShareTokens?: boolean confirmRequired?: boolean defaultSelectedAsssetId?: string } @@ -40,7 +39,6 @@ export const AssetsModalContent = ({ hideInactiveAssets, allAssets, withBonds, - withShareTokens, confirmRequired, defaultSelectedAsssetId, withExternal, @@ -79,14 +77,14 @@ export const AssetsModalContent = ({ ...assets.tokens, ...assets.stableswap, ...(withExternal ? assets.external.filter((token) => token.name) : []), - ...(withShareTokens ? assets.shareTokens : []), ]) : accountAssets.filter( (accountAsset): accountAsset is { balance: TBalance; asset: TToken } => accountAsset.asset.isToken || accountAsset.asset.isStableSwap || - (withExternal ? accountAsset.asset.isExternal : false) || - (withShareTokens ? accountAsset.asset.isShareToken : false), + (withExternal + ? accountAsset.asset.isExternal && !!accountAsset.asset.name + : false), ) const bonds = allAssets diff --git a/src/sections/pools/PoolsPage.utils.ts b/src/sections/pools/PoolsPage.utils.ts index 6810831b3..d5880dbb0 100644 --- a/src/sections/pools/PoolsPage.utils.ts +++ b/src/sections/pools/PoolsPage.utils.ts @@ -1,4 +1,8 @@ -import { useTokenBalance, useTokensBalances } from "api/balances" +import { + useShareTokenBalances, + useTokenBalance, + useTokensBalances, +} from "api/balances" import { useHubAssetTradability, useOmnipoolAssets } from "api/omnipool" import { useMemo } from "react" import { useAccount } from "sections/web3-connect/Web3Connect.utils" @@ -16,10 +20,7 @@ import { encodeAddress, blake2AsHex } from "@polkadot/util-crypto" import { HYDRADX_SS58_PREFIX, XykMath } from "@galacticcouncil/sdk" import { useAccountBalances } from "api/accountBalances" import { useRpcProvider } from "providers/rpcProvider" -import { useQueries, useQuery } from "@tanstack/react-query" -import { QUERY_KEYS } from "utils/queryKeys" -import { isNotNil, undefinedNoop } from "utils/helpers" -import { ApiPromise } from "@polkadot/api" +import { isNotNil } from "utils/helpers" import { useOmnipoolPositionsData } from "sections/wallet/assets/hydraPositions/data/WalletAssetsHydraPositionsData.utils" import { useVolume } from "api/volume" import BN from "bignumber.js" @@ -32,7 +33,10 @@ import { import { useShareOfPools } from "api/pools" import { TShareToken } from "api/assetDetails" import { useXYKPoolTradeVolumes } from "./pool/details/PoolDetails.utils" -import { useAllUserDepositShare } from "./farms/position/FarmingPosition.utils" +import { + useAllOmnipoolDeposits, + useAllXYKDeposits, +} from "./farms/position/FarmingPosition.utils" import { useFee } from "api/stats" import { useTVL } from "api/stats" import { scaleHuman } from "utils/balance" @@ -42,6 +46,9 @@ import { is_remove_liquidity_allowed, is_sell_allowed, } from "@galacticcouncil/math-omnipool" +import { useUserDeposits } from "api/deposits" + +export const XYK_TVL_VISIBILITY = 5000 export const useAssetsTradability = () => { const { @@ -102,78 +109,15 @@ export type TPoolFullData = TPool & TPoolDetails export type TXYKPoolFullData = TXYKPool & NonNullable["data"]> +export type TXYKPool = NonNullable< + ReturnType["data"] +>[number] + export const derivePoolAccount = (assetId: string) => { const name = pool_account_name(Number(assetId)) return encodeAddress(blake2AsHex(name), HYDRADX_SS58_PREFIX) } -export const useAccountOmnipoolPositions = (givenAddress?: string) => { - const { account } = useAccount() - const { api } = useRpcProvider() - - const address = givenAddress ?? account?.address - - return useQuery( - QUERY_KEYS.accountOmnipoolPositions(address), - address != null - ? async () => { - const [omnipoolNftId, miningNftId] = await Promise.all([ - api.consts.omnipool.nftCollectionId, - api.consts.omnipoolLiquidityMining.nftCollectionId, - ]) - - const [omnipoolNftsRaw, miningNftsRaw] = await Promise.all([ - api.query.uniques.account.entries(address, omnipoolNftId), - api.query.uniques.account.entries(address, miningNftId), - ]) - - const omnipoolNfts = omnipoolNftsRaw.map(([storageKey]) => { - const [owner, classId, instanceId] = storageKey.args - return { - owner: owner.toString(), - classId: classId.toString(), - instanceId: instanceId.toString(), - } - }) - - const miningNfts = miningNftsRaw.map(([storageKey]) => { - const [owner, classId, instanceId] = storageKey.args - return { - owner: owner.toString(), - classId: classId.toString(), - instanceId: instanceId.toString(), - } - }) - - return { omnipoolNfts, miningNfts } - } - : undefinedNoop, - { enabled: !!address }, - ) -} - -export const useAccountMiningPositions = () => { - const { api } = useRpcProvider() - const { data } = useAccountOmnipoolPositions() - - return useQueries({ - queries: data?.miningNfts - ? data.miningNfts.map((nft) => ({ - queryKey: QUERY_KEYS.miningPosition(nft.instanceId), - queryFn: getMiningPosition(api, nft.instanceId), - enabled: !!nft.instanceId, - })) - : [], - }) -} - -const getMiningPosition = (api: ApiPromise, id: string) => async () => { - const dataRaw = await api.query.omnipoolWarehouseLM.deposit(id) - const data = dataRaw.unwrap() - - return { id, data } -} - const getTradeFee = (fee: string[]) => { if (fee?.length !== 2) return BN_NAN @@ -184,10 +128,6 @@ const getTradeFee = (fee: string[]) => { return tradeFee.times(100) } -export type TXYKPool = NonNullable< - ReturnType["data"] ->[number] - export const usePools = () => { const { assets } = useRpcProvider() const { stableCoinId } = useDisplayAssetStore() @@ -198,7 +138,7 @@ export const usePools = () => { const assetsTradability = useAssetsTradability() const omnipoolPositions = useOmnipoolPositionsData() - const miningPositions = useAllUserDepositShare() + const miningPositions = useAllOmnipoolDeposits() const assetsId = useMemo( () => omnipoolAssets.data?.map((a) => a.id.toString()) ?? [], @@ -343,7 +283,7 @@ export const usePoolDetails = (assetId: string) => { const meta = assets.getAsset(assetId) const isStablePool = assets.isStableSwap(meta) - const miningPositions = useAccountMiningPositions() + const { omnipoolDeposits } = useUserDeposits() const omnipoolPositions = useOmnipoolPositionsData() const poolAccountAddress = derivePoolAccount(assetId) @@ -361,12 +301,8 @@ export const usePoolDetails = (assetId: string) => { (position) => position.assetId === assetId, ) - const miningNftPositions = miningPositions - .filter( - (position) => position.data?.data.ammPoolId.toString() === assetId, - ) - .map((position) => position.data) - .filter(isNotNil) + const miningNftPositions = omnipoolDeposits + .filter((position) => position.data.ammPoolId.toString() === assetId) .sort((a, b) => { const firstFarmLastBlock = a.data.yieldFarmEntries.reduce( (acc, curr) => @@ -413,7 +349,7 @@ export const usePoolDetails = (assetId: string) => { assetId, assets, isStablePool, - miningPositions, + omnipoolDeposits, omnipoolPositions.data, stablePoolBalance.data?.balances, stablepool.data?.fee, @@ -429,7 +365,7 @@ export const useMyPools = () => { const pools = usePools() const omnipoolPositions = useOmnipoolPositionsData() - const miningPositions = useAllUserDepositShare() + const miningPositions = useAllOmnipoolDeposits() const stableswapsId = assets.stableswap.map((shareToken) => shareToken.id) @@ -489,6 +425,8 @@ export const useXYKPools = (withPositions?: boolean) => { : [], ) + const deposits = useAllXYKDeposits() + const queries = [ pools, shareTokens, @@ -538,6 +476,10 @@ export const useXYKPools = (withPositions?: boolean) => { (volume) => volume.poolAddress === pool.poolAddress, )?.volume ?? BN_NAN + const miningPositions = deposits.data.filter( + (deposit) => deposit.assetId === shareTokenMeta.id, + ) + return { id: shareTokenMeta.id, symbol: shareTokenMeta.symbol, @@ -552,11 +494,15 @@ export const useXYKPools = (withPositions?: boolean) => { shareTokenIssuance, volume, isVolumeLoading: volumes.isLoading, + miningPositions, } }) .filter(isNotNil) .filter((pool) => - withPositions ? pool.shareTokenIssuance?.myPoolShare?.gt(0) : true, + withPositions + ? pool.shareTokenIssuance?.myPoolShare?.gt(0) || + pool.miningPositions.length + : true, ) .sort((a, b) => b.tvlDisplay.minus(a.tvlDisplay).toNumber()) }, [ @@ -568,6 +514,7 @@ export const useXYKPools = (withPositions?: boolean) => { totalIssuances.data, withPositions, volumes, + deposits, ]) return { data, isInitialLoading } @@ -575,11 +522,39 @@ export const useXYKPools = (withPositions?: boolean) => { export const useXYKPoolDetails = (pool: TXYKPool) => { const volume = useXYKPoolTradeVolumes([pool.poolAddress]) + const { xykDeposits } = useUserDeposits() const isInitialLoading = volume.isLoading + const miningNftPositions = xykDeposits + .filter( + (position) => position.data.ammPoolId.toString() === pool.poolAddress, + ) + .sort((a, b) => { + const firstFarmLastBlock = a.data.yieldFarmEntries.reduce( + (acc, curr) => + acc.lt(curr.enteredAt.toBigNumber()) + ? curr.enteredAt.toBigNumber() + : acc, + BN_0, + ) + + const secondFarmLastBlock = b.data.yieldFarmEntries.reduce( + (acc, curr) => + acc.lt(curr.enteredAt.toBigNumber()) + ? curr.enteredAt.toBigNumber() + : acc, + BN_0, + ) + + return secondFarmLastBlock.minus(firstFarmLastBlock).toNumber() + }) + return { - data: { volumeDisplay: volume.data?.[0]?.volume ?? BN_0 }, + data: { + volumeDisplay: volume.data?.[0]?.volume ?? BN_0, + miningNftPositions, + }, isInitialLoading, } } @@ -617,3 +592,91 @@ export const useXYKSpotPrice = (shareTokenId: string) => { return { priceA, priceB, idA: metaA.id, idB: metaB.id } } + +export const useXYKDepositValues = (depositNfts: TMiningNftPosition[]) => { + const { assets } = useRpcProvider() + const depositNftsData = depositNfts.reduce< + { assetId: string; depositNft: TMiningNftPosition }[] + >((acc, depositNft) => { + const assetId = assets.getShareTokenByAddress( + depositNft.data.ammPoolId.toString(), + )?.id + + if (assetId) + acc.push({ + assetId, + depositNft, + }) + return acc + }, []) + + const uniqAssetIds = [ + ...new Set(depositNftsData.map((deposit) => deposit.assetId)), + ] + const totalIssuances = useShareOfPools(uniqAssetIds) + const balances = useShareTokenBalances(uniqAssetIds) + const shareTokeSpotPrices = useDisplayShareTokenPrice(uniqAssetIds) + + const isLoading = + totalIssuances.isInitialLoading || + balances.some((q) => q.isInitialLoading) || + shareTokeSpotPrices.isInitialLoading + + const data = useMemo(() => { + const defaultValue = { + assetA: undefined, + assetB: undefined, + amountUSD: undefined, + } + + return depositNftsData.map((deposit) => { + const shareTokenIssuance = totalIssuances.data?.find( + (totalIssuance) => totalIssuance.asset === deposit.assetId, + )?.totalShare + + if (!shareTokenIssuance) { + return { ...defaultValue, assetId: deposit.assetId } + } + + const shareTokenMeta = assets.getAsset(deposit.assetId) as TShareToken + const shares = deposit.depositNft.data.shares.toBigNumber() + const ratio = shares.div(shareTokenIssuance) + const amountUSD = scaleHuman(shareTokenIssuance, shareTokenMeta.decimals) + .multipliedBy(shareTokeSpotPrices.data?.[0]?.spotPrice ?? 1) + .times(ratio) + + const [assetA, assetB] = shareTokenMeta.assets.map((asset) => { + const meta = assets.getAsset(asset) + const balance = + balances.find( + (balance) => + balance.data?.assetId === asset && + balance.data?.accountId.toString() === shareTokenMeta.poolAddress, + )?.data?.balance ?? BN_0 + const amount = scaleHuman(balance.times(ratio), meta.decimals) + + return { + id: asset, + symbol: meta.symbol, + decimals: meta.decimals, + amount, + } + }) + return { + assetA, + assetB, + amountUSD, + assetId: deposit.assetId, + depositId: deposit.depositNft.id, + } + }) + }, [ + assets, + balances, + depositNftsData, + shareTokeSpotPrices.data, + totalIssuances.data, + ]) + + return { data, isLoading } +} diff --git a/src/sections/pools/farms/FarmingPositionWrapper.tsx b/src/sections/pools/farms/FarmingPositionWrapper.tsx index 8bcf520ff..c2b26924f 100644 --- a/src/sections/pools/farms/FarmingPositionWrapper.tsx +++ b/src/sections/pools/farms/FarmingPositionWrapper.tsx @@ -5,7 +5,7 @@ import { Icon } from "components/Icon/Icon" import FPIcon from "assets/icons/PoolsAndFarms.svg?react" import { ClaimRewardsCard } from "./components/claimableCard/ClaimRewardsCard" import { Spacer } from "components/Spacer/Spacer" -import { TPool, TPoolDetails } from "sections/pools/PoolsPage.utils" +import { TPool, TPoolDetails, TXYKPool } from "sections/pools/PoolsPage.utils" import { Button } from "components/Button/Button" import ExitIcon from "assets/icons/Exit.svg?react" import { useFarmExitAllMutation } from "utils/farms/exit" @@ -14,7 +14,7 @@ import { ToastMessage } from "state/store" import { useAccount } from "sections/web3-connect/Web3Connect.utils" interface Props { - pool: TPool + pool: TPool | TXYKPool positions: TPoolDetails["miningNftPositions"] } @@ -32,7 +32,7 @@ export const FarmingPositionWrapper = ({ pool, positions }: Props) => { return memo }, {} as ToastMessage) - const exit = useFarmExitAllMutation(positions, toast) + const exit = useFarmExitAllMutation(positions, pool.id, toast) if (!positions.length) return null diff --git a/src/sections/pools/farms/components/claimAllDropdown/ClaimAllContent.tsx b/src/sections/pools/farms/components/claimAllDropdown/ClaimAllContent.tsx index ce0bfb1c1..8fa3e8e71 100644 --- a/src/sections/pools/farms/components/claimAllDropdown/ClaimAllContent.tsx +++ b/src/sections/pools/farms/components/claimAllDropdown/ClaimAllContent.tsx @@ -2,7 +2,7 @@ import { forwardRef } from "react" import { ToastMessage } from "state/store" import { Trans, useTranslation } from "react-i18next" import { useRpcProvider } from "providers/rpcProvider" -import { useClaimableAmount, useClaimAllMutation } from "utils/farms/claiming" +import { useClaimableAmount, useClaimFarmMutation } from "utils/farms/claiming" import { TOAST_MESSAGES } from "state/toasts" import { DisplayValue } from "components/DisplayValue/DisplayValue" import { SClaimButton, SContent } from "./ClaimAllDrowpdown.styled" @@ -46,7 +46,7 @@ export const ClaimAllContent = forwardRef( return memo }, {} as ToastMessage) - const claimAll = useClaimAllMutation(undefined, undefined, toast) + const claimAll = useClaimFarmMutation(undefined, undefined, toast) return ( { const { t } = useTranslation() const [open, setOpen] = useState(false) const isDesktop = useMedia(theme.viewport.gte.sm) - const depositShares = useAllUserDepositShare() + const deposits = useAllFarmDeposits() - if (!Object.keys(depositShares.data).length) { + if (!Object.keys(deposits.omnipool).length && !deposits.xyk.length) { return null } diff --git a/src/sections/pools/farms/components/claimableCard/ClaimRewardsCard.tsx b/src/sections/pools/farms/components/claimableCard/ClaimRewardsCard.tsx index 906641038..87b955f51 100644 --- a/src/sections/pools/farms/components/claimableCard/ClaimRewardsCard.tsx +++ b/src/sections/pools/farms/components/claimableCard/ClaimRewardsCard.tsx @@ -6,18 +6,18 @@ import { Text } from "components/Typography/Text/Text" import { Fragment, useMemo } from "react" import { Trans, useTranslation } from "react-i18next" import { ToastMessage } from "state/store" -import { TMiningNftPosition } from "sections/pools/PoolsPage.utils" import { TOAST_MESSAGES } from "state/toasts" import { theme } from "theme" import { separateBalance } from "utils/balance" -import { useClaimAllMutation, useClaimableAmount } from "utils/farms/claiming" +import { useClaimFarmMutation, useClaimableAmount } from "utils/farms/claiming" import { useRpcProvider } from "providers/rpcProvider" import { useAccount } from "sections/web3-connect/Web3Connect.utils" import { Card } from "components/Card/Card" +import { TDeposit } from "api/deposits" export const ClaimRewardsCard = (props: { poolId: string - depositNft?: TMiningNftPosition + depositNft?: TDeposit onTxClose?: () => void }) => { const { t } = useTranslation() @@ -67,7 +67,7 @@ export const ClaimRewardsCard = (props: { return memo }, {} as ToastMessage) - const claimAll = useClaimAllMutation( + const claimAll = useClaimFarmMutation( props.poolId, props.depositNft, toast, diff --git a/src/sections/pools/farms/components/detailsCard/FarmDetailsCard.tsx b/src/sections/pools/farms/components/detailsCard/FarmDetailsCard.tsx index c67873371..c548e82de 100644 --- a/src/sections/pools/farms/components/detailsCard/FarmDetailsCard.tsx +++ b/src/sections/pools/farms/components/detailsCard/FarmDetailsCard.tsx @@ -3,7 +3,7 @@ import { Text } from "components/Typography/Text/Text" import { Trans, useTranslation } from "react-i18next" import { SContainer, SIcon, SRow } from "./FarmDetailsCard.styled" import { FillBar } from "components/FillBar/FillBar" -import { getFloatingPointAmount } from "utils/balance" +import { scaleHuman } from "utils/balance" import { GradientText } from "components/Typography/GradientText/GradientText" import { addSeconds } from "date-fns" import ChevronDown from "assets/icons/ChevronDown.svg?react" @@ -120,11 +120,11 @@ export const FarmDetailsCard = ({ t={t} i18nKey="farms.details.card.distribution" tOptions={{ - distributed: getFloatingPointAmount( + distributed: scaleHuman( apr.data.distributedRewards, - 12, + asset.decimals, ), - max: getFloatingPointAmount(apr.data.maxRewards, 12), + max: scaleHuman(apr.data.maxRewards, asset.decimals), }} > @@ -155,10 +155,7 @@ export const FarmDetailsCard = ({ css={{ justifySelf: "end" }} > {t("farms.details.card.lockedShares.value", { - value: getFloatingPointAmount( - depositNft.data.shares, - assetMeta.decimals, - ), + value: scaleHuman(depositNft.data.shares, assetMeta.decimals), })} diff --git a/src/sections/pools/farms/modals/join/JoinFarmsButton.tsx b/src/sections/pools/farms/modals/join/JoinFarmsButton.tsx new file mode 100644 index 000000000..b78765648 --- /dev/null +++ b/src/sections/pools/farms/modals/join/JoinFarmsButton.tsx @@ -0,0 +1,53 @@ +import { useFarms } from "api/farms" +import { Button } from "components/Button/Button" +import { Icon } from "components/Icon/Icon" +import { useState } from "react" +import { useTranslation } from "react-i18next" +import { useAccount } from "sections/web3-connect/Web3Connect.utils" +import FPIcon from "assets/icons/PoolsAndFarms.svg?react" +import { JoinFarmModal } from "sections/pools/farms/modals/join/JoinFarmsModal" +import { HydraPositionsTableData } from "sections/wallet/assets/hydraPositions/WalletAssetsHydraPositions.utils" +import { useFarmDepositMutation } from "utils/farms/deposit" + +export const JoinFarmsButton = (props: { + poolId: string + position?: HydraPositionsTableData + onSuccess: () => void +}) => { + const { t } = useTranslation() + const { account } = useAccount() + const [joinFarm, setJoinFarm] = useState(false) + const farms = useFarms([props.poolId]) + + const mutation = useFarmDepositMutation( + props.poolId, + props.position?.id ?? "", + farms.data, + () => setJoinFarm(false), + props.onSuccess, + ) + + return ( + <> + + + {joinFarm && farms.data && ( + setJoinFarm(false)} + initialShares={props.position?.shares} + mutation={mutation} + /> + )} + + ) +} diff --git a/src/sections/pools/farms/modals/join/JoinFarmsModal.styled.ts b/src/sections/pools/farms/modals/join/JoinFarmsModal.styled.ts index c7e2e79c8..ba2058018 100644 --- a/src/sections/pools/farms/modals/join/JoinFarmsModal.styled.ts +++ b/src/sections/pools/farms/modals/join/JoinFarmsModal.styled.ts @@ -2,6 +2,9 @@ import styled from "@emotion/styled" import { theme } from "theme" export const SJoinFarmContainer = styled.div` + display: flex; + flex-direction: column; + background: rgba(${theme.rgbColors.darkBlue900}, 0.4); margin: 16px calc(-1 * var(--modal-content-padding)) diff --git a/src/sections/pools/farms/modals/join/JoinFarmsModal.tsx b/src/sections/pools/farms/modals/join/JoinFarmsModal.tsx index eb4060abc..0c52d0ad6 100644 --- a/src/sections/pools/farms/modals/join/JoinFarmsModal.tsx +++ b/src/sections/pools/farms/modals/join/JoinFarmsModal.tsx @@ -10,36 +10,39 @@ import { useState } from "react" import { useTranslation } from "react-i18next" import { TMiningNftPosition } from "sections/pools/PoolsPage.utils" import { FarmDepositMutationType } from "utils/farms/deposit" -import { FarmRedepositMutationType } from "utils/farms/redeposit" import { FarmDetailsCard } from "sections/pools/farms/components/detailsCard/FarmDetailsCard" import { FarmDetailsModal } from "sections/pools/farms/modals/details/FarmDetailsModal" import { SJoinFarmContainer } from "./JoinFarmsModal.styled" import { useBestNumber } from "api/chain" import { useRpcProvider } from "providers/rpcProvider" import { Alert } from "components/Alert/Alert" +import { Controller, useForm } from "react-hook-form" +import { scaleHuman } from "utils/balance" +import { WalletTransferAssetSelect } from "sections/wallet/transfer/WalletTransferAssetSelect" +import { useZodSchema } from "./JoinFarmsModal.utils" +import { zodResolver } from "@hookform/resolvers/zod" import { Spacer } from "components/Spacer/Spacer" -import { BN_0 } from "utils/constants" +import { FormValues } from "utils/helpers" +import { FarmRedepositMutationType } from "utils/farms/redeposit" type JoinFarmModalProps = { - isOpen: boolean onClose: () => void poolId: string - shares?: BigNumber + initialShares?: BigNumber farms: Farm[] isRedeposit?: boolean - mutation?: FarmDepositMutationType | FarmRedepositMutationType depositNft?: TMiningNftPosition + mutation: FarmDepositMutationType | FarmRedepositMutationType } export const JoinFarmModal = ({ - isOpen, onClose, isRedeposit, poolId, - mutation, - shares, + initialShares, depositNft, farms, + mutation, }: JoinFarmModalProps) => { const { t } = useTranslation() const { assets } = useRpcProvider() @@ -50,25 +53,24 @@ export const JoinFarmModal = ({ const meta = assets.getAsset(poolId.toString()) const bestNumber = useBestNumber() + const zodSchema = useZodSchema(meta.id, farms, !!isRedeposit) + + const form = useForm<{ amount: string }>({ + mode: "onChange", + defaultValues: { + amount: initialShares + ? scaleHuman(initialShares, meta.decimals).toString() + : undefined, + }, + resolver: zodSchema ? zodResolver(zodSchema) : undefined, + }) + const selectedFarm = farms.find( (farm) => farm.globalFarm.id.eq(selectedFarmId?.globalFarmId) && farm.yieldFarm.id.eq(selectedFarmId?.yieldFarmId), ) - const { isValid, minDeposit } = farms.reduce<{ - isValid: boolean - minDeposit: BigNumber - }>( - (acc, farm) => { - const minDeposit = farm.globalFarm.minDeposit.toBigNumber() - const isValid = !!shares?.gte(minDeposit) - - return { isValid, minDeposit: !isValid ? minDeposit : acc.minDeposit } - }, - { isValid: false, minDeposit: BN_0 }, - ) - const { page, direction, back, next } = useModalPagination() const onBack = () => { @@ -82,8 +84,14 @@ export const JoinFarmModal = ({ selectedFarm?.globalFarm.blocksPerPeriod.toNumber() ?? 1, ) + const onSubmit = (values: FormValues) => { + mutation.mutate({ shares: values.amount }) + } + + const error = form.formState.errors.amount?.message + return ( - + - {mutation && shares && ( + +
-
-
- {t("farms.modal.footer.title")} -
- - {t("value.token", { - value: shares, - fixedPointScale: meta.decimals, - })} - -
- {!isValid && ( - <> - - {t("farms.modal.join.minDeposit", { - value: minDeposit.shiftedBy(-meta.decimals), +
+ {t("farms.modal.footer.title")} +
+ + {t("value.token", { + value: initialShares, + fixedPointScale: meta.decimals, })} -
+ + + ) : ( + <> + + ( + + )} + /> )} + + {error && initialShares && ( + + {error} + + )} +
- )} +
), }, diff --git a/src/sections/pools/farms/modals/join/JoinFarmsModal.utils.ts b/src/sections/pools/farms/modals/join/JoinFarmsModal.utils.ts new file mode 100644 index 000000000..8fbd4a8f5 --- /dev/null +++ b/src/sections/pools/farms/modals/join/JoinFarmsModal.utils.ts @@ -0,0 +1,53 @@ +import { z } from "zod" +import { maxBalance, required } from "utils/validators" +import { useTokenBalance } from "api/balances" +import { useAccount } from "sections/web3-connect/Web3Connect.utils" +import { useRpcProvider } from "providers/rpcProvider" +import { BN_0 } from "utils/constants" +import { Farm, useOraclePrice } from "api/farms" +import { useMemo } from "react" +import { scale, scaleHuman } from "utils/balance" +import { useTranslation } from "react-i18next" + +export const useZodSchema = ( + id: string, + farms: Farm[], + isRedeposit: boolean, +) => { + const { t } = useTranslation() + const { account } = useAccount() + const { assets } = useRpcProvider() + const { data: balance } = useTokenBalance(id, account?.address) + const oraclePrice = useOraclePrice(assets.hub.id, id) + + const meta = assets.getAsset(id) + + const minDeposit = useMemo(() => { + return farms.reduce((acc, farm) => { + const minDeposit = farm.globalFarm.minDeposit.toBigNumber() + + return minDeposit.gt(acc) ? minDeposit : acc + }, BN_0) + }, [farms]) + + if (!balance) return undefined + + const rule = required.refine( + (value) => { + const valueInHub = scale(value, meta.decimals) + .times(oraclePrice.data?.price?.n ?? 1) + .div(oraclePrice.data?.price?.d ?? 1) + + return valueInHub.gte(minDeposit) + }, + t("farms.modal.join.minDeposit", { + value: scaleHuman(minDeposit, meta.decimals), + }), + ) + + return z.object({ + amount: isRedeposit + ? rule + : rule.pipe(maxBalance(balance?.balance ?? BN_0, meta.decimals)), + }) +} diff --git a/src/sections/pools/farms/modals/joinedFarmDetails/JoinedFarmsDetails.tsx b/src/sections/pools/farms/modals/joinedFarmDetails/JoinedFarmsDetails.tsx index 525ba642b..b0d6e19ff 100644 --- a/src/sections/pools/farms/modals/joinedFarmDetails/JoinedFarmsDetails.tsx +++ b/src/sections/pools/farms/modals/joinedFarmDetails/JoinedFarmsDetails.tsx @@ -18,6 +18,7 @@ import { useFarmExitAllMutation } from "utils/farms/exit" import { useFarmRedepositMutation } from "utils/farms/redeposit" import { useRpcProvider } from "providers/rpcProvider" import { useAccount } from "sections/web3-connect/Web3Connect.utils" +import { scaleHuman } from "utils/balance" function isFarmJoined(depositNft: TMiningNftPosition, farm: Farm) { return depositNft.data.yieldFarmEntries.find( @@ -33,42 +34,25 @@ function JoinedFarmsDetailsRedeposit(props: { onSelect: (value: { globalFarm: u32; yieldFarm: u32 }) => void onTxClose: () => void }) { - const { t } = useTranslation() const { assets } = useRpcProvider() + const { t } = useTranslation() const { account } = useAccount() const farms = useFarms([props.poolId]) - const meta = assets.getAsset(props.poolId.toString()) + const meta = assets.getAsset(props.poolId) const availableFarms = farms.data?.filter( (farm) => !isFarmJoined(props.depositNft, farm), ) - const toast = TOAST_MESSAGES.reduce((memo, type) => { - const msType = type === "onError" ? "onLoading" : type - memo[type] = ( - - - - - ) - return memo - }, {} as ToastMessage) - const redeposit = useFarmRedepositMutation( availableFarms, - [props.depositNft], - toast, + props.depositNft, + props.poolId, props.onTxClose, ) if (!availableFarms?.length) return null + return ( <> @@ -92,7 +76,14 @@ function JoinedFarmsDetailsRedeposit(props: { fullWidth variant="primary" sx={{ mt: 16 }} - onClick={() => redeposit.mutate()} + onClick={() => + redeposit.mutate({ + shares: scaleHuman( + props.depositNft.data.shares.toString(), + meta.decimals, + ).toString(), + }) + } disabled={account?.isExternalWalletConnected} isLoading={redeposit.isLoading} > @@ -142,6 +133,7 @@ function JoinedFarmsDetailsPositions(props: { const exit = useFarmExitAllMutation( [props.depositNft], + props.poolId, toast, props.onTxClose, ) diff --git a/src/sections/pools/farms/position/FarmingPosition.tsx b/src/sections/pools/farms/position/FarmingPosition.tsx index 29d436673..1fa625a61 100644 --- a/src/sections/pools/farms/position/FarmingPosition.tsx +++ b/src/sections/pools/farms/position/FarmingPosition.tsx @@ -3,7 +3,10 @@ import { DollarAssetValue } from "components/DollarAssetValue/DollarAssetValue" import { Text } from "components/Typography/Text/Text" import { useState } from "react" import { Trans, useTranslation } from "react-i18next" -import { TMiningNftPosition } from "sections/pools/PoolsPage.utils" +import { + TMiningNftPosition, + useXYKDepositValues, +} from "sections/pools/PoolsPage.utils" import { WalletAssetsHydraPositionsData } from "sections/wallet/assets/hydraPositions/data/WalletAssetsHydraPositionsData" import { useEnteredDate } from "utils/block" import { BN_0 } from "utils/constants" @@ -82,7 +85,7 @@ const ExitFarmsButton = (props: { return memo }, {} as ToastMessage) - const exit = useFarmExitAllMutation([props.depositNft], toast) + const exit = useFarmExitAllMutation([props.depositNft], props.poolId, toast) return ( diff --git a/src/sections/pools/navigation/Navigation.tsx b/src/sections/pools/navigation/Navigation.tsx index 572965d9d..7366d81e2 100644 --- a/src/sections/pools/navigation/Navigation.tsx +++ b/src/sections/pools/navigation/Navigation.tsx @@ -5,10 +5,8 @@ import AllPools from "assets/icons/AllPools.svg?react" import OmniStablepools from "assets/icons/Omnipool&Stablepool.svg?react" import IsolatedPools from "assets/icons/IsolatedPools.svg?react" import { SSeparator } from "components/Separator/Separator.styled" -import { useAccountOmnipoolPositions } from "sections/pools/PoolsPage.utils" import { useRpcProvider } from "providers/rpcProvider" import { useTranslation } from "react-i18next" -import { useTokensBalances } from "api/balances" import { useAccount } from "sections/web3-connect/Web3Connect.utils" import { SubNavigation, @@ -17,6 +15,8 @@ import { import { BackSubHeader } from "components/Layout/Header/BackSubHeader/BackSubHeader" import { useLocation } from "@tanstack/react-location" import { t } from "i18next" +import { useAccountNFTPositions } from "api/deposits" +import { useAccountBalances } from "api/accountBalances" const routeMap = new Map([ [LINKS.allPools, t("liquidity.navigation.allPools")], @@ -55,22 +55,24 @@ const MyLiquidity = () => { const { t } = useTranslation() const { account } = useAccount() const { assets } = useRpcProvider() - const accountPositions = useAccountOmnipoolPositions() + const accountPositions = useAccountNFTPositions() - const shareTokensId = assets.shareTokens.map((shareToken) => shareToken.id) - const stableswapsId = assets.stableswap.map((shareToken) => shareToken.id) + const balances = useAccountBalances(account?.address) - const userPositions = useTokensBalances( - [...shareTokensId, ...stableswapsId], - account?.address, - ) + const isPoolBalances = balances.data?.balances.some((balance) => { + if (balance.freeBalance.gt(0)) { + const meta = assets.getAsset(balance.id) + return meta.isStableSwap || meta.isShareToken + } + return false + }) - const isOmnipoolPositions = + const isPositions = accountPositions.data?.miningNfts.length || accountPositions.data?.omnipoolNfts.length || - userPositions.some((userPosition) => userPosition.data?.freeBalance.gt(0)) + isPoolBalances - if (!isOmnipoolPositions) return null + if (!isPositions) return null return ( <> diff --git a/src/sections/pools/pool/Pool.tsx b/src/sections/pools/pool/Pool.tsx index 0095a17c8..03192e0d7 100644 --- a/src/sections/pools/pool/Pool.tsx +++ b/src/sections/pools/pool/Pool.tsx @@ -42,7 +42,8 @@ const XYKPool = ({ pool }: { pool: TXYKPool }) => { return ( - + + ) } diff --git a/src/sections/pools/pool/availableFarms/AvailableFarms.tsx b/src/sections/pools/pool/availableFarms/AvailableFarms.tsx index f14753ba2..2cd7ee371 100644 --- a/src/sections/pools/pool/availableFarms/AvailableFarms.tsx +++ b/src/sections/pools/pool/availableFarms/AvailableFarms.tsx @@ -1,5 +1,5 @@ import { useTranslation } from "react-i18next" -import { TPool } from "sections/pools/PoolsPage.utils" +import { TPool, TXYKPool } from "sections/pools/PoolsPage.utils" import { useFarms } from "api/farms" import { FarmDetailsCard } from "sections/pools/farms/components/detailsCard/FarmDetailsCard" import { useState } from "react" @@ -9,7 +9,7 @@ import { FarmDetailsModal } from "sections/pools/farms/modals/details/FarmDetail import { useBestNumber } from "api/chain" import { Text } from "components/Typography/Text/Text" -export const AvailableFarms = ({ pool }: { pool: TPool }) => { +export const AvailableFarms = ({ pool }: { pool: TPool | TXYKPool }) => { const { t } = useTranslation() const [selectedFarmId, setSelectedFarmId] = useState<{ yieldFarmId: u32 diff --git a/src/sections/pools/pool/capacity/PoolCapacity.utils.ts b/src/sections/pools/pool/capacity/PoolCapacity.utils.ts index 35ffa8af4..8bff41a24 100644 --- a/src/sections/pools/pool/capacity/PoolCapacity.utils.ts +++ b/src/sections/pools/pool/capacity/PoolCapacity.utils.ts @@ -5,10 +5,10 @@ import { OMNIPOOL_ACCOUNT_ADDRESS } from "utils/api" import { useMemo } from "react" import { BN_NAN } from "utils/constants" import BN from "bignumber.js" -import { calculate_cap_difference } from "@galacticcouncil/math-omnipool" import { getFloatingPointAmount } from "utils/balance" import { useRpcProvider } from "providers/rpcProvider" import { useDisplayAssetStore } from "utils/displayAsset" +import { OmniMath } from "@galacticcouncil/sdk" export const usePoolCapacity = (id: string) => { const { assets } = useRpcProvider() @@ -62,7 +62,7 @@ export const usePoolCapacity = (id: string) => { const assetCap = asset.data.cap.toString() const totalHubReserve = hubBalance.data.total.toString() - let capDifference = calculate_cap_difference( + const capDifference = OmniMath.calculateCapDifference( assetReserve, assetHubReserve, assetCap, diff --git a/src/sections/pools/pool/myPositions/MyPositions.tsx b/src/sections/pools/pool/myPositions/MyPositions.tsx index 48038145c..ba73af92f 100644 --- a/src/sections/pools/pool/myPositions/MyPositions.tsx +++ b/src/sections/pools/pool/myPositions/MyPositions.tsx @@ -5,9 +5,9 @@ import { Text } from "components/Typography/Text/Text" import { useRpcProvider } from "providers/rpcProvider" import { useMemo } from "react" import { useTranslation } from "react-i18next" -import { TPoolFullData, TXYKPool } from "sections/pools/PoolsPage.utils" +import { TPoolFullData, TXYKPoolFullData } from "sections/pools/PoolsPage.utils" import { FarmingPositionWrapper } from "sections/pools/farms/FarmingPositionWrapper" -import { useAllUserDepositShare } from "sections/pools/farms/position/FarmingPosition.utils" +import { useAllOmnipoolDeposits } from "sections/pools/farms/position/FarmingPosition.utils" import { LiquidityPositionWrapper } from "sections/pools/pool/positions/LiquidityPositionWrapper" import { XYKPosition } from "sections/pools/pool/xykPosition/XYKPosition" import { StablepoolPosition } from "sections/pools/stablepool/positions/StablepoolPosition" @@ -22,7 +22,7 @@ export const MyPositions = ({ pool }: { pool: TPoolFullData }) => { const meta = assets.getAsset(pool.id) const queryClient = useQueryClient() - const miningPositions = useAllUserDepositShare() + const miningPositions = useAllOmnipoolDeposits() const stablepoolBalance = useTokenBalance( pool.isStablePool ? pool.id : undefined, @@ -125,6 +125,36 @@ export const MyPositions = ({ pool }: { pool: TPoolFullData }) => { ) } -export const MyXYKPositions = ({ pool }: { pool: TXYKPool }) => { - return +export const MyXYKPositions = ({ pool }: { pool: TXYKPoolFullData }) => { + const { t } = useTranslation() + + const totalFarms = useMemo(() => { + return pool.miningPositions.reduce((memo, share) => { + return memo.plus(share.amountUSD ?? 0) + }, BN_0) + }, [pool.miningPositions]) + + return ( +
+ + {t("liquidity.pool.positions.title")} + + {!totalFarms.isZero() && ( + <> + +
+ + {t("liquidity.pool.positions.farming")} + + + {t("value.usd", { amount: totalFarms })} + +
+ + + )} + + +
+ ) } diff --git a/src/sections/pools/pool/positions/LiquidityPosition.tsx b/src/sections/pools/pool/positions/LiquidityPosition.tsx index d2390cde5..96f4c3de0 100644 --- a/src/sections/pools/pool/positions/LiquidityPosition.tsx +++ b/src/sections/pools/pool/positions/LiquidityPosition.tsx @@ -3,7 +3,7 @@ import { Icon } from "components/Icon/Icon" import { Separator } from "components/Separator/Separator" import { Text } from "components/Typography/Text/Text" import TrashIcon from "assets/icons/IconRemove.svg?react" -import { Trans, useTranslation } from "react-i18next" +import { useTranslation } from "react-i18next" import { SContainer } from "sections/pools/pool/positions/LiquidityPosition.styled" import { HydraPositionsTableData } from "sections/wallet/assets/hydraPositions/WalletAssetsHydraPositions.utils" import { WalletAssetsHydraPositionsData } from "sections/wallet/assets/hydraPositions/data/WalletAssetsHydraPositionsData" @@ -11,12 +11,6 @@ import { DollarAssetValue } from "components/DollarAssetValue/DollarAssetValue" import { useState } from "react" import { RemoveLiquidity } from "sections/pools/modals/RemoveLiquidity/RemoveLiquidity" import { Button } from "components/Button/Button" -import FPIcon from "assets/icons/PoolsAndFarms.svg?react" -import { JoinFarmModal } from "sections/pools/farms/modals/join/JoinFarmsModal" -import { useFarms } from "api/farms" -import { useFarmDepositMutation } from "utils/farms/deposit" -import { TOAST_MESSAGES } from "state/toasts" -import { ToastMessage } from "state/store" import { useAccount } from "sections/web3-connect/Web3Connect.utils" import { DisplayValue } from "components/DisplayValue/DisplayValue" import { LrnaPositionTooltip } from "sections/pools/components/LrnaPositionTooltip" @@ -25,6 +19,7 @@ import { MultipleIcons } from "components/MultipleIcons/MultipleIcons" import { TPoolFullData, TXYKPool } from "sections/pools/PoolsPage.utils" import { useMedia } from "react-use" import { theme } from "theme" +import { JoinFarmsButton } from "sections/pools/farms/modals/join/JoinFarmsButton" type Props = { position: HydraPositionsTableData @@ -33,71 +28,6 @@ type Props = { onSuccess: () => void } -function LiquidityPositionJoinFarmButton(props: { - poolId: string - position: HydraPositionsTableData - onSuccess: () => void -}) { - const { t } = useTranslation() - const { assets } = useRpcProvider() - const { account } = useAccount() - const [joinFarm, setJoinFarm] = useState(false) - const farms = useFarms([props.poolId]) - const meta = assets.getAsset(props.poolId.toString()) - - const toast = TOAST_MESSAGES.reduce((memo, type) => { - const msType = type === "onError" ? "onLoading" : type - memo[type] = ( - - - - - ) - return memo - }, {} as ToastMessage) - - const joinFarmMutation = useFarmDepositMutation( - props.poolId, - props.position.id, - toast, - () => setJoinFarm(false), - props.onSuccess, - ) - - return ( - <> - - - {joinFarm && farms.data && ( - setJoinFarm(false)} - mutation={joinFarmMutation} - /> - )} - - ) -} - export function LiquidityPositionRemoveLiquidity( props: | { @@ -178,7 +108,7 @@ export const LiquidityPosition = ({ }} > {!meta.isStableSwap && ( - { const { t } = useTranslation() const { account } = useAccount() const { assets } = useRpcProvider() const isDesktop = useMedia(theme.viewport.gte.sm) + const queryClient = useQueryClient() const shareTokenMeta = assets.getAsset(pool.id) as TShareToken @@ -77,11 +81,17 @@ export const XYKPosition = ({ pool }: { pool: TXYKPool }) => { if (!pool.shareTokenIssuance || pool.shareTokenIssuance.myPoolShare?.isZero()) return null + const onSuccess = () => { + queryClient.refetchQueries( + QUERY_KEYS.tokenBalance(shareTokenMeta.id, account?.address), + ) + queryClient.refetchQueries( + QUERY_KEYS.accountOmnipoolPositions(account?.address), + ) + } + return ( -
- - {t("liquidity.pool.positions.title")} - +
} /> @@ -107,11 +117,13 @@ export const XYKPosition = ({ pool }: { pool: TXYKPool }) => { })}
- - null} - /> +
+ + +
diff --git a/src/sections/pools/sections/AllPools.tsx b/src/sections/pools/sections/AllPools.tsx index 651e8ac35..1e83796ba 100644 --- a/src/sections/pools/sections/AllPools.tsx +++ b/src/sections/pools/sections/AllPools.tsx @@ -1,7 +1,11 @@ import { useRpcProvider } from "providers/rpcProvider" -import { useMemo } from "react" +import { useMemo, useState } from "react" import { useTranslation } from "react-i18next" -import { usePools, useXYKPools } from "sections/pools/PoolsPage.utils" +import { + usePools, + useXYKPools, + XYK_TVL_VISIBILITY, +} from "sections/pools/PoolsPage.utils" import { HeaderValues } from "sections/pools/header/PoolsHeader" import { HeaderTotalData } from "sections/pools/header/PoolsHeaderTotal" import { BN_0 } from "utils/constants" @@ -18,6 +22,7 @@ import { PoolSkeleton } from "sections/pools/pool/PoolSkeleton" import { EmptySearchState } from "components/EmptySearchState/EmptySearchState" import { TableLabel } from "sections/pools/components/TableLabel" import { CreateXYKPoolModalButton } from "sections/pools/modals/CreateXYKPool/CreateXYKPoolModalButton" +import { Switch } from "components/Switch/Switch" export const AllPools = () => { const { t } = useTranslation() @@ -86,6 +91,7 @@ const AllPoolsData = () => { const pools = usePools() const xylPools = useXYKPools() + const [showAllXyk, setShowAllXyk] = useState(false) const omnipoolTotal = useMemo( () => @@ -114,10 +120,23 @@ const AllPoolsData = () => { ? arraySearch(pools.data, search, ["symbol", "name"]) : pools.data) ?? [] - const filteredXYKPools = - (search && xylPools.data - ? arraySearch(xylPools.data, search, ["symbol", "name"]) - : xylPools.data) ?? [] + const filteredXYKPools = useMemo( + () => + (search && xylPools.data + ? arraySearch(xylPools.data, search, ["symbol", "name"]) + : xylPools.data) ?? [], + [search, xylPools.data], + ) + + const visibleXykPools = useMemo( + () => + showAllXyk + ? filteredXYKPools + : filteredXYKPools.filter((pool) => + pool.tvlDisplay.gte(XYK_TVL_VISIBILITY), + ), + [filteredXYKPools, showAllXyk], + ) if (id != null) { const pool = [...(pools.data ?? []), ...(xylPools.data ?? [])].find( @@ -198,7 +217,28 @@ const AllPoolsData = () => { align: ["flex-start", "flex-end"], }} > - +
+ + setShowAllXyk(value)} + size="small" + name="showAll" + label={t("liquidity.section.xyk.toggle")} + sx={{ pb: 20 }} + /> +
+ { {xylPools.isInitialLoading ? ( ) : ( - + )}
) : null} diff --git a/src/sections/pools/sections/IsolatedPools.tsx b/src/sections/pools/sections/IsolatedPools.tsx index 14b181293..145ca3685 100644 --- a/src/sections/pools/sections/IsolatedPools.tsx +++ b/src/sections/pools/sections/IsolatedPools.tsx @@ -1,9 +1,9 @@ import { useRpcProvider } from "providers/rpcProvider" -import { useXYKPools } from "sections/pools/PoolsPage.utils" +import { useXYKPools, XYK_TVL_VISIBILITY } from "sections/pools/PoolsPage.utils" import { HeaderValues } from "sections/pools/header/PoolsHeader" import { HeaderTotalData } from "sections/pools/header/PoolsHeaderTotal" import { useTranslation } from "react-i18next" -import { useMemo } from "react" +import { useMemo, useState } from "react" import { BN_0 } from "utils/constants" import { SearchFilter } from "sections/pools/filter/SearchFilter" import { useSearchFilter } from "sections/pools/filter/SearchFilter.utils" @@ -17,6 +17,7 @@ import { PoolSkeleton } from "sections/pools/pool/PoolSkeleton" import { EmptySearchState } from "components/EmptySearchState/EmptySearchState" import { Spacer } from "components/Spacer/Spacer" import { CreateXYKPoolModalButton } from "sections/pools/modals/CreateXYKPool/CreateXYKPoolModalButton" +import { Switch } from "components/Switch/Switch" export const IsolatedPools = () => { const { t } = useTranslation() @@ -58,6 +59,7 @@ export const IsolatedPools = () => { const IsolatedPoolsData = () => { const { t } = useTranslation() + const [showAllXyk, setShowAllXyk] = useState(false) const { search } = useSearchFilter() const { id } = useSearch<{ Search: { @@ -76,10 +78,23 @@ const IsolatedPoolsData = () => { return BN_0 }, [xykPools.data]) - const filteredPools = - (search && xykPools.data - ? arraySearch(xykPools.data, search, ["symbol", "name"]) - : xykPools.data) ?? [] + const filteredPools = useMemo( + () => + (search && xykPools.data + ? arraySearch(xykPools.data, search, ["symbol", "name"]) + : xykPools.data) ?? [], + [search, xykPools.data], + ) + + const visiblePools = useMemo( + () => + showAllXyk + ? filteredPools + : filteredPools.filter((pool) => + pool.tvlDisplay.gte(XYK_TVL_VISIBILITY), + ), + [filteredPools, showAllXyk], + ) if (id != null) { const pool = xykPools.data?.find((pool) => pool.id === id.toString()) @@ -116,7 +131,23 @@ const IsolatedPoolsData = () => { /> -
+
+ setShowAllXyk(value)} + size="small" + name="showAll" + label={t("liquidity.section.xyk.toggle")} + sx={{ pb: 20 }} + /> { {xykPools.isInitialLoading ? ( ) : filteredPools.length ? ( - + ) : ( )} diff --git a/src/sections/pools/table/PoolsTable.utils.tsx b/src/sections/pools/table/PoolsTable.utils.tsx index 1fb84b070..9caaef561 100644 --- a/src/sections/pools/table/PoolsTable.utils.tsx +++ b/src/sections/pools/table/PoolsTable.utils.tsx @@ -147,19 +147,19 @@ const AddLiqduidityButton = ({ account?.address, ) - const isPosition = - userStablePoolBalance.data?.freeBalance.gt(0) || - (isXykPool ? pool.shareTokenIssuance?.myPoolShare?.gt(0) : pool.isPositions) - - const positionsAmount = isPosition - ? !isXykPool - ? BN(pool.omnipoolPositions.length) - .plus(pool.miningPositions.length) - .plus(userStablePoolBalance.data?.freeBalance.gt(0) ? 1 : 0) - : pool.shareTokenIssuance?.myPoolShare?.gt(0) - ? BN_1 - : undefined - : undefined + let positionsAmount: BN = BN_0 + + if (isXykPool) { + positionsAmount = BN(pool.miningPositions.length).plus( + pool.shareTokenIssuance?.myPoolShare?.gt(0) ? 1 : 0, + ) + } else { + positionsAmount = BN(pool.omnipoolPositions.length) + .plus(pool.miningPositions.length) + .plus(userStablePoolBalance.data?.freeBalance.gt(0) ? 1 : 0) + } + + const isPositions = positionsAmount.gt(0) const onClick = () => onRowSelect(pool.id) @@ -185,10 +185,10 @@ const AddLiqduidityButton = ({ }} onClick={onClick} > - {isPosition ? } size={12} /> : null} - {isPosition ? t("manage") : t("details")} + {isPositions ? } size={12} /> : null} + {isPositions ? t("manage") : t("details")} - {positionsAmount?.gt(0) && ( + {isPositions && ( + if (isLoading || farms.isLoading) return if (farms.data?.length) return diff --git a/src/sections/stats/components/RecentTradesTable/data/RecentTradesTableData.utils.ts b/src/sections/stats/components/RecentTradesTable/data/RecentTradesTableData.utils.ts index 55a346505..340a28f08 100644 --- a/src/sections/stats/components/RecentTradesTable/data/RecentTradesTableData.utils.ts +++ b/src/sections/stats/components/RecentTradesTable/data/RecentTradesTableData.utils.ts @@ -1,5 +1,4 @@ import { useApiIds } from "api/consts" -import { useOmnipoolAssets } from "api/omnipool" import { useSpotPrices } from "api/spotPrice" import BN from "bignumber.js" import { useMemo } from "react" @@ -10,47 +9,132 @@ import { isHydraAddress } from "utils/formatting" import { decodeAddress, encodeAddress } from "@polkadot/util-crypto" import { HYDRA_ADDRESS_PREFIX } from "utils/api" import { useAccountsIdentity } from "api/stats" -import { useAllTrades } from "api/volume" +import { + TradeType, + isStableswapEvent, + isTradeEvent, + useAllTrades, +} from "api/volume" +import { groupBy } from "utils/rx" +import { isNotNil } from "utils/helpers" +import { BN_NAN } from "utils/constants" const withoutRefresh = true +const EVENTS_LIMIT = 10 + export const useRecentTradesTableData = (assetId?: string) => { const { assets } = useRpcProvider() - const omnipoolAssets = useOmnipoolAssets(withoutRefresh) const apiIds = useApiIds() const allTrades = useAllTrades(assetId ? Number(assetId) : undefined) const displayAsset = useDisplayAssetStore() - const omnipoolAssetsIds = omnipoolAssets.data?.map((a) => a.id) ?? [] const address = allTrades.data?.events.map((event) => event.args.who) ?? [] const identities = useAccountsIdentity(address) + const events = useMemo(() => { + if (!allTrades.data) return + const groupedEvents = groupBy( + allTrades.data.events, + ({ extrinsic }) => extrinsic.hash, + ) + + return Object.entries(groupedEvents) + .map(([, value]) => { + const routerEvent = value.find(({ name }) => name === "Router.Executed") + const tradeEvents = value.filter(isTradeEvent) + const stableswapEvents = value.filter(isStableswapEvent) + const [firstEvent] = tradeEvents + + if (!tradeEvents.length) return null + if (firstEvent?.name === "Router.Executed") { + const who = stableswapEvents?.[0]?.args?.who + if (!who) return null + return { + value, + ...firstEvent, + args: { + who: stableswapEvents[0].args.who, + assetIn: firstEvent.args.assetIn, + assetOut: firstEvent.args.assetOut, + amountIn: firstEvent.args.amountIn, + amountOut: firstEvent.args.amountOut, + }, + } + } + + let event: TradeType + if (!routerEvent) { + const lastEvent = tradeEvents[tradeEvents.length - 1] + const assetIn = firstEvent.args.assetIn + const assetOut = lastEvent.args.assetOut + + const stableswapIn = stableswapEvents.find( + ({ args }) => args.poolId === assetIn, + ) + const stableswapAssetIn = stableswapIn?.args?.assets?.[0]?.assetId + const stableswapAmountIn = stableswapIn?.args?.assets?.[0]?.amount + + const stableswapOut = stableswapEvents.find( + ({ args }) => args.poolId === assetOut, + ) + const stableswapAssetOut = stableswapOut?.args?.amounts?.[0]?.assetId + const stableswapAmountOut = stableswapIn?.args?.amounts?.[0]?.amount + + event = { + ...firstEvent, + args: { + who: firstEvent.args.who, + assetIn: stableswapAssetIn || assetIn, + assetOut: stableswapAssetOut || assetOut, + amountIn: + stableswapAmountIn || + firstEvent.args.amount || + firstEvent.args.amountIn, + amountOut: + stableswapAmountOut || + lastEvent.args.amount || + lastEvent.args.amountOut, + }, + } + } else { + event = { + ...firstEvent, + args: { + ...firstEvent.args, + ...routerEvent.args, + }, + } + } + + const assetInMeta = assets.getAsset(event.args.assetIn.toString()) + const assetOutMeta = assets.getAsset(event.args.assetOut.toString()) + + if (!assetInMeta?.name || !assetOutMeta?.name) return null + + return event + }) + .filter(isNotNil) + }, [allTrades.data, assets]) + + const assetIds = events + ? events?.map(({ args }) => args.assetIn.toString()) + : [] + const spotPrices = useSpotPrices( - omnipoolAssetsIds, + assetIds, displayAsset.stableCoinId, withoutRefresh, ) - const queries = [ - omnipoolAssets, - apiIds, - allTrades, - ...spotPrices, - ...identities, - ] + const queries = [apiIds, allTrades, ...spotPrices, ...identities] const isInitialLoading = queries.some((q) => q.isInitialLoading) const data = useMemo(() => { - if ( - !allTrades.data || - !omnipoolAssets.data || - !apiIds.data || - spotPrices.some((q) => !q.data) - ) - return [] + if (!events || !apiIds.data || spotPrices.some((q) => !q.data)) return [] - const trades = allTrades.data.events.reduce( + const trades = events.reduce( (memo, trade) => { const isSelectedAsset = assetId ? assetId === trade.args.assetIn.toString() || @@ -61,7 +145,9 @@ export const useRecentTradesTableData = (assetId?: string) => { isSelectedAsset && !memo.find((memoTrade) => memoTrade.id === trade.id) ) { - const isBuy = trade.name === "Omnipool.BuyExecuted" + const isBuy = + trade.name === "Omnipool.BuyExecuted" || + trade.name === "XYK.BuyExecuted" const assetIn = trade.args.assetIn.toString() const amountInRaw = new BN(trade.args.amountIn) @@ -84,7 +170,9 @@ export const useRecentTradesTableData = (assetId?: string) => { assetMetaOut.decimals, ) - const tradeValue = amountIn.multipliedBy(spotPriceIn?.spotPrice ?? 1) + const tradeValue = amountIn.multipliedBy( + spotPriceIn?.spotPrice ?? BN_NAN, + ) const hydraAddress = isHydraAddress(trade.args.who) ? trade.args.who @@ -134,16 +222,8 @@ export const useRecentTradesTableData = (assetId?: string) => { }>, ) - return trades - }, [ - allTrades.data, - omnipoolAssets.data, - apiIds.data, - spotPrices, - assetId, - assets, - identities, - ]) + return trades.slice(0, EVENTS_LIMIT) + }, [events, apiIds.data, spotPrices, assetId, assets, identities]) return { data, isLoading: isInitialLoading } } diff --git a/src/sections/stats/sections/omnipoolAsset/stats/AssetStats.tsx b/src/sections/stats/sections/omnipoolAsset/stats/AssetStats.tsx index 6cc03cbc8..1eb81f16f 100644 --- a/src/sections/stats/sections/omnipoolAsset/stats/AssetStats.tsx +++ b/src/sections/stats/sections/omnipoolAsset/stats/AssetStats.tsx @@ -62,7 +62,7 @@ const APYStatsCard = ({ value={ assetId === native.id ? "--" : t("value.percentage", { value: fee }) } - loading={loading || farms.isInitialLoading} + loading={loading || farms.isLoading} tooltip={t("stats.overview.table.assets.header.apy.desc")} /> ) diff --git a/src/sections/stats/sections/overview/components/OmnipoolAssetsTableWrapper/OmnipoolAssetsTableWrapper.utils.tsx b/src/sections/stats/sections/overview/components/OmnipoolAssetsTableWrapper/OmnipoolAssetsTableWrapper.utils.tsx index 22a97caff..76c32b1e6 100644 --- a/src/sections/stats/sections/overview/components/OmnipoolAssetsTableWrapper/OmnipoolAssetsTableWrapper.utils.tsx +++ b/src/sections/stats/sections/overview/components/OmnipoolAssetsTableWrapper/OmnipoolAssetsTableWrapper.utils.tsx @@ -69,7 +69,7 @@ const APY = ({ } = useRpcProvider() const farms = useFarms([assetId]) - if (isLoading || farms.isInitialLoading) return + if (isLoading || farms.isLoading) return if (farms.data?.length) return diff --git a/src/sections/trade/sections/otc/modals/FillOrder.tsx b/src/sections/trade/sections/otc/modals/FillOrder.tsx index d3bf89b88..a662f2a53 100644 --- a/src/sections/trade/sections/otc/modals/FillOrder.tsx +++ b/src/sections/trade/sections/otc/modals/FillOrder.tsx @@ -2,15 +2,15 @@ import { useTokenBalance } from "api/balances" import { Button } from "components/Button/Button" import { Modal } from "components/Modal/Modal" import { Text } from "components/Typography/Text/Text" -import { useState } from "react" +import { FormEvent, useState } from "react" import { Trans, useTranslation } from "react-i18next" -import { BN_10 } from "utils/constants" +import { BN_1, BN_10 } from "utils/constants" import { useStore } from "state/store" import { OfferingPair } from "sections/trade/sections/otc/orders/OtcOrdersData.utils" -import { OrderAssetPrice } from "./cmp/AssetPrice" import { OrderAssetGet, OrderAssetPay } from "./cmp/AssetSelect" import { useRpcProvider } from "providers/rpcProvider" import { useAccount } from "sections/web3-connect/Web3Connect.utils" +import { TokensConversion } from "sections/pools/modals/AddLiquidity/components/TokensConvertion/TokensConversion" type FillOrderProps = { orderId: string @@ -40,7 +40,8 @@ export const FillOrder = ({ const price = accepting.amount.div(offering.amount) - const handleSubmit = async () => { + const handleSubmit = async (e: FormEvent) => { + e.preventDefault() if (assetInMeta.decimals == null) throw new Error("Missing assetIn meta") if (assetInBalance.data?.balance == null) @@ -98,7 +99,7 @@ export const FillOrder = ({ } const isDisabled = - assetInBalance.data?.balance?.lte( + assetInBalance.data?.balance?.lt( accepting.amount.multipliedBy(BN_10.pow(assetInMeta.decimals)), ) ?? false @@ -128,10 +129,16 @@ export const FillOrder = ({ readonly={true} error={error} /> - ({ + mode: "onChange", defaultValues: { free: accepting.amount, }, + resolver: zodResolver(formSchema), }) - useEffect(() => { - form.trigger() - }, [form]) - - const { api, assets } = useRpcProvider() - const assetInMeta = assets.getAsset(accepting.asset) - const assetInBalance = useTokenBalance(accepting.asset, account?.address) - const assetOutMeta = assets.getAsset(offering.asset) - const { createTransaction } = useStore() const price = accepting.amount.div(offering.amount) @@ -63,14 +68,14 @@ export const PartialFillOrder = ({ const handlePayWithChange = () => { const { amountOut } = form.getValues() const amountIn = new BigNumber(amountOut).multipliedBy(price) - form.setValue("amountIn", amountIn.toFixed()) + form.setValue("amountIn", !amountIn.isNaN() ? amountIn.toFixed() : "") form.trigger() } const handleYouGetChange = () => { const { amountIn } = form.getValues() const amountOut = new BigNumber(amountIn).div(price) - form.setValue("amountOut", amountOut.toFixed()) + form.setValue("amountOut", !amountOut.isNaN() ? amountOut.toFixed() : "") form.trigger() } @@ -197,25 +202,6 @@ export const PartialFillOrder = ({ { - const balance = assetInBalance.data?.balance - const decimals = assetInMeta.decimals.toString() - if ( - balance && - decimals && - balance.gte( - new BigNumber(value).multipliedBy(BN_10.pow(decimals)), - ) - ) { - return true - } - return t("otc.order.fill.validation.notEnoughBalance") - }, - }, - }} render={({ field: { name, value, onChange }, fieldState: { error }, @@ -235,25 +221,20 @@ export const PartialFillOrder = ({ /> )} /> - { - if (offering.amount.gte(new BigNumber(value))) { - return true - } - return t("otc.order.fill.validation.orderTooBig") - }, - }, - }} render={({ field: { name, value, onChange }, fieldState: { error }, diff --git a/src/sections/trade/sections/otc/modals/PartialFillOrder.utils.tsx b/src/sections/trade/sections/otc/modals/PartialFillOrder.utils.tsx new file mode 100644 index 000000000..f0b9eb78e --- /dev/null +++ b/src/sections/trade/sections/otc/modals/PartialFillOrder.utils.tsx @@ -0,0 +1,27 @@ +import { required, maxBalance } from "utils/validators" +import * as z from "zod" +import BigNumber from "bignumber.js" +import { useTranslation } from "react-i18next" + +export const usePartialFillFormSchema = ({ + offeringAmount, + assetInBalance, + assetInDecimals, +}: { + offeringAmount: BigNumber + assetInBalance: BigNumber + assetInDecimals: number +}) => { + const { t } = useTranslation() + return z.object({ + amountIn: required.pipe(maxBalance(assetInBalance, assetInDecimals)), + amountOut: required.pipe( + z + .string() + .refine( + (value) => offeringAmount.gte(new BigNumber(value)), + t("otc.order.fill.validation.orderTooBig"), + ), + ), + }) +} diff --git a/src/sections/trade/sections/otc/modals/cmp/AssetPrice.tsx b/src/sections/trade/sections/otc/modals/cmp/AssetPrice.tsx deleted file mode 100644 index 2e04de8f8..000000000 --- a/src/sections/trade/sections/otc/modals/cmp/AssetPrice.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import * as React from "react" -import * as UI from "@galacticcouncil/ui" -import { createComponent } from "@lit-labs/react" -import { SContainer, SDivider, SPrice } from "./AssetPrice.styled" - -export const UigcAssetPrice = createComponent({ - tagName: "uigc-asset-price", - elementClass: UI.AssetPrice, - react: React, -}) - -export function OrderAssetPrice(props: { - inputAsset: string | undefined - outputAsset: string | undefined - price: string -}) { - return ( - - - {props.price && ( - - - - )} - - ) -} diff --git a/src/sections/trade/sections/otc/modals/cmp/AssetSelect.tsx b/src/sections/trade/sections/otc/modals/cmp/AssetSelect.tsx index 60b31a88b..2ccad4c40 100644 --- a/src/sections/trade/sections/otc/modals/cmp/AssetSelect.tsx +++ b/src/sections/trade/sections/otc/modals/cmp/AssetSelect.tsx @@ -1,43 +1,8 @@ import * as React from "react" -import * as UI from "@galacticcouncil/ui" -import { createComponent, EventName } from "@lit-labs/react" import { u32 } from "@polkadot/types" import BN from "bignumber.js" -import { useRpcProvider } from "providers/rpcProvider" - -export const UigcAsset = createComponent({ - tagName: "uigc-asset", - elementClass: UI.Asset, - react: React, -}) - -export const UigcAssetId = createComponent({ - tagName: "uigc-asset-id", - elementClass: UI.AssetId, - react: React, -}) - -export const UigcAssetTransfer = createComponent({ - tagName: "uigc-asset-transfer", - elementClass: UI.AssetTransfer, - react: React, - events: { - onAssetInputChange: "asset-input-change" as EventName, - onAssetSelectorClick: "asset-selector-click" as EventName, - }, -}) - -export const UigcAssetBalance = createComponent({ - tagName: "uigc-asset-balance", - elementClass: UI.AssetBalance, - react: React, -}) - -export const UigcButton = createComponent({ - tagName: "uigc-button", - elementClass: UI.Button, - react: React, -}) +import { WalletTransferAssetSelect } from "sections/wallet/transfer/WalletTransferAssetSelect" +import { Button } from "components/Button/Button" export function OrderAssetSelect(props: { name: string @@ -49,49 +14,16 @@ export function OrderAssetSelect(props: { onOpen: () => void error?: string }) { - const { assets } = useRpcProvider() - const asset = props.asset - ? assets.getAsset(props.asset.toString()) - : undefined - - const assetBalance = props.balance - const assetDecimals = asset?.decimals - - let blnc: string = "" - if (assetBalance && assetDecimals) { - blnc = assetBalance.shiftedBy(-1 * assetDecimals).toFixed() - } - return ( - { - if (!el) { - return - } - - if (props.error) { - el.setAttribute("error", props.error) - } else { - el.removeAttribute("error") - } - }} - onAssetInputChange={(e) => props.onChange(e.detail.value)} - onAssetSelectorClick={props.onOpen} - id={props.name} + - - - - props.onChange(blnc)} - /> - + asset={props.asset?.toString() ?? ""} + error={props.error} + onChange={(value) => (props.onChange ? props.onChange(value) : undefined)} + onAssetOpen={props.onOpen} + /> ) } @@ -105,46 +37,17 @@ export function OrderAssetPay(props: { error?: string readonly?: boolean }) { - const { assets } = useRpcProvider() - const asset = assets.getAsset(props.asset.toString()) - - const assetBalance = props.balance - const assetDecimals = asset.decimals - - let blnc: string = "" - if (assetBalance && assetDecimals) { - blnc = assetBalance.shiftedBy(-1 * assetDecimals).toFixed() - } - return ( - { - if (!el) { - return - } - props.readonly && el.setAttribute("readonly", "") - if (props.error) { - el.setAttribute("error", props.error) - } else { - el.removeAttribute("error") - } - }} - onAssetInputChange={(e) => - props.onChange && props.onChange(e.detail.value) - } - id={props.name} + - - - - - + asset={props.asset.toString()} + disabled={props.readonly} + error={props.error} + withoutMaxBtn + onChange={(value) => (props.onChange ? props.onChange(value) : undefined)} + /> ) } @@ -152,21 +55,22 @@ function getPercentageValue(value: BN, pct: number): BN { return value.div(100).multipliedBy(new BN(pct)) } -const OrderAssetPctBtn = ( - pct: number, - remaining: BN, - onClick: (value: string) => void, -) => ( - void +}> = ({ pct, remaining, onClick }) => ( + ) export function OrderAssetGet(props: { @@ -179,44 +83,51 @@ export function OrderAssetGet(props: { error?: string readonly?: boolean }) { - const { assets } = useRpcProvider() - const asset = assets.getAsset(props.asset.toString()) return ( - { - if (!el) { - return - } - props.readonly && el.setAttribute("readonly", "") - - if (props.error) { - el.setAttribute("error", props.error) - } else { - el.removeAttribute("error") - } - }} - onAssetInputChange={(e) => - props.onChange && props.onChange(e.detail.value) - } - id={props.name} - title={props.title} - asset={asset.symbol} - unit={asset.symbol} - amount={props.value} - selectable={false} - readonly={props.readonly || false} - > - - - +
+
+ + props.onChange ? props.onChange(value) : undefined + } + /> +
{props.onChange && ( -
- {OrderAssetPctBtn(25, props.remaining, props.onChange)} - {OrderAssetPctBtn(50, props.remaining, props.onChange)} - {OrderAssetPctBtn(75, props.remaining, props.onChange)} - {OrderAssetPctBtn(100, props.remaining, props.onChange)} +
+ + + +
)} - +
) } diff --git a/src/sections/trade/sections/otc/orders/OtcOrders.utils.tsx b/src/sections/trade/sections/otc/orders/OtcOrders.utils.tsx index 05f716979..754ff10b8 100644 --- a/src/sections/trade/sections/otc/orders/OtcOrders.utils.tsx +++ b/src/sections/trade/sections/otc/orders/OtcOrders.utils.tsx @@ -77,13 +77,20 @@ export const useOrdersTable = ( id: "offer", enableSorting: false, header: t("otc.offers.table.header.offer"), - cell: ({ row }) => , + cell: ({ row }) => ( + + ), }), accessor("accepting", { id: "accepting", enableSorting: false, header: t("otc.offers.table.header.accepting"), - cell: ({ row }) => , + cell: ({ row }) => ( + + ), }), accessor("orderPrice", { id: "orderPrice", diff --git a/src/sections/trade/sections/otc/orders/OtcOrdersData.tsx b/src/sections/trade/sections/otc/orders/OtcOrdersData.tsx index fc7a0b40e..3e543ef6a 100644 --- a/src/sections/trade/sections/otc/orders/OtcOrdersData.tsx +++ b/src/sections/trade/sections/otc/orders/OtcOrdersData.tsx @@ -6,9 +6,9 @@ import { Text } from "components/Typography/Text/Text" import { useTranslation } from "react-i18next" import { OfferingPair } from "./OtcOrdersData.utils" import { useRpcProvider } from "providers/rpcProvider" -import { abbreviateNumber } from "utils/helpers" import { useMedia } from "react-use" import { theme } from "theme" +import { DisplayValue } from "components/DisplayValue/DisplayValue" export const OrderPairColumn = (props: { offering: OfferingPair @@ -176,10 +176,14 @@ export const OrderPriceColumn = (props: { pair: OfferingPair; price: BN }) => { ) : ( <> - {t("value.token", { value: 1 })} {props.pair.symbol} + - ({abbreviateNumber(props.price)}) + ( + {t("otc.offers.table.header.perToken", { + symbol: props.pair.symbol, + })} + ) )} diff --git a/src/sections/trade/sections/otc/orders/OtcOrdersData.utils.ts b/src/sections/trade/sections/otc/orders/OtcOrdersData.utils.ts index 16ef54cb5..693ff06a1 100644 --- a/src/sections/trade/sections/otc/orders/OtcOrdersData.utils.ts +++ b/src/sections/trade/sections/otc/orders/OtcOrdersData.utils.ts @@ -2,7 +2,8 @@ import { useMemo } from "react" import { useOrdersData, useOrdersState, getOrderStateValue } from "api/otc" import BN from "bignumber.js" import { useAssetPrices } from "utils/displayAsset" -import { calculateDiffToAvg, calculateDiffToRef } from "@galacticcouncil/sdk" +import { calculateDiffToRef } from "@galacticcouncil/sdk" +import { isNotNil } from "utils/helpers" export const useOrdersTableData = () => { const treasuryAddr = import.meta.env.VITE_TRSRY_ADDR @@ -35,69 +36,80 @@ export const useOrdersTableData = () => { const data = useMemo(() => { if (!orders.data) return [] - return orders.data.map((order) => { - const orderState = ordersState.find( - (state) => state.data?.orderId === parseInt(order.id), - ) - const orderStateValue = getOrderStateValue(orderState?.data) - const amountInDp: number = order.assetIn?.decimals ?? 12 - const amountIn: BN = order.amountIn!.shiftedBy(-1 * amountInDp) - const amountInInitial: string | undefined = orderStateValue?.amountIn - const amountOutDp: number = order.assetOut?.decimals ?? 12 - const amountOut: BN = order.amountOut!.shiftedBy(-1 * amountOutDp) - const amountOutInitial: string | undefined = orderStateValue?.amountOut + return orders.data + .map((order) => { + const assetInValid = order.assetIn?.isExternal + ? !!order.assetIn?.name + : true + const assetOutValid = order.assetOut?.isExternal + ? !!order.assetOut?.name + : true - const spotPriceInUSD = assetPrices?.find( - (spotPrice) => spotPrice?.data?.tokenIn === order.assetIn?.id, - ) + if (!assetInValid || !assetOutValid) return null - const valueOfAssetIn = amountIn.multipliedBy( - spotPriceInUSD?.data?.spotPrice || 0, - ) - const orderPrice = valueOfAssetIn.div(amountOut || 0) + const orderState = ordersState.find( + (state) => state.data?.orderId === parseInt(order.id), + ) + const orderStateValue = getOrderStateValue(orderState?.data) + const amountInDp: number = order.assetIn?.decimals ?? 12 + const amountIn: BN = order.amountIn!.shiftedBy(-1 * amountInDp) + const amountInInitial: string | undefined = orderStateValue?.amountIn + const amountOutDp: number = order.assetOut?.decimals ?? 12 + const amountOut: BN = order.amountOut!.shiftedBy(-1 * amountOutDp) + const amountOutInitial: string | undefined = orderStateValue?.amountOut - const marketPriceInUSD = assetPrices?.find( - (spotPrice) => spotPrice?.data?.tokenIn === order.assetOut?.id, - ) - const marketPrice = marketPriceInUSD?.data?.spotPrice || null + const spotPriceInUSD = assetPrices?.find( + (spotPrice) => spotPrice?.data?.tokenIn === order.assetIn?.id, + ) - let marketPricePercentage = 0 - if (marketPrice) { - marketPricePercentage = calculateDiffToRef( - marketPrice, - orderPrice, - ).toNumber() - } + const valueOfAssetIn = amountIn.multipliedBy( + spotPriceInUSD?.data?.spotPrice || 0, + ) + const orderPrice = valueOfAssetIn.div(amountOut || 0) - return { - id: order.id, - owner: order.owner, - offer: { - initial: - amountOutInitial && - new BN(amountOutInitial).shiftedBy(-1 * amountOutDp), - amount: amountOut, - asset: order.assetOut?.id, - name: order.assetOut?.name, - symbol: order.assetOut?.symbol, - }, - accepting: { - initial: - amountInInitial && - new BN(amountInInitial).shiftedBy(-1 * amountInDp), - amount: amountIn, - asset: order.assetIn?.id, - name: order.assetIn?.name, - symbol: order.assetIn?.symbol, - }, - price: amountIn.div(amountOut), - orderPrice: orderPrice, - marketPrice: marketPrice, - marketPricePercentage: marketPricePercentage, - partiallyFillable: order.partiallyFillable, - pol: order.owner === treasuryAddr, - } as OrderTableData - }) + const marketPriceInUSD = assetPrices?.find( + (spotPrice) => spotPrice?.data?.tokenIn === order.assetOut?.id, + ) + const marketPrice = marketPriceInUSD?.data?.spotPrice || null + + let marketPricePercentage = 0 + if (marketPrice) { + marketPricePercentage = calculateDiffToRef( + marketPrice, + orderPrice, + ).toNumber() + } + + return { + id: order.id, + owner: order.owner, + offer: { + initial: + amountOutInitial && + new BN(amountOutInitial).shiftedBy(-1 * amountOutDp), + amount: amountOut, + asset: order.assetOut?.id, + name: order.assetOut?.name, + symbol: order.assetOut?.symbol, + }, + accepting: { + initial: + amountInInitial && + new BN(amountInInitial).shiftedBy(-1 * amountInDp), + amount: amountIn, + asset: order.assetIn?.id, + name: order.assetIn?.name, + symbol: order.assetIn?.symbol, + }, + price: amountIn.div(amountOut), + orderPrice: orderPrice, + marketPrice: marketPrice, + marketPricePercentage: marketPricePercentage, + partiallyFillable: order.partiallyFillable, + pol: order.owner === treasuryAddr, + } as OrderTableData + }) + .filter(isNotNil) }, [orders.data, ordersState, treasuryAddr, assetPrices]) return { diff --git a/src/sections/trade/sections/swap/SwapPage.tsx b/src/sections/trade/sections/swap/SwapPage.tsx index 3210eef81..bfef4b5c2 100644 --- a/src/sections/trade/sections/swap/SwapPage.tsx +++ b/src/sections/trade/sections/swap/SwapPage.tsx @@ -15,6 +15,9 @@ import { useDisplayAssetStore } from "utils/displayAsset" import { isEvmAccount } from "utils/evm" import { NATIVE_ASSET_ID } from "utils/api" import { useRemount } from "hooks/useRemount" +import { AddTokenModal } from "sections/wallet/addToken/modal/AddTokenModal" +import { useState } from "react" +import { useUserExternalTokenStore } from "sections/wallet/addToken/AddToken.utils" const defaultEvmTokenId: string = import.meta.env.VITE_EVM_NATIVE_ASSET_ID @@ -26,6 +29,7 @@ const SwapApp = createComponent({ onTxNew: "gc:tx:new" as EventName>, onDcaSchedule: "gc:tx:scheduleDca" as EventName>, onDcaTerminate: "gc:tx:terminateDca" as EventName>, + onNewAssetClick: "gc:external:new" as EventName>, }, }) @@ -54,9 +58,12 @@ export function SwapPage() { const { account } = useAccount() const { createTransaction } = useStore() const { stableCoinId } = useDisplayAssetStore() + const [addToken, setAddToken] = useState(false) + + const { tokens: externalTokensStored } = useUserExternalTokenStore.getState() const isEvm = isEvmAccount(account?.address) - const version = useRemount(isEvm) + const version = useRemount([isEvm, externalTokensStored.length]) const preference = useProviderRpcUrlStore() const rpcUrl = preference.rpcUrl ?? import.meta.env.VITE_PROVIDER_URL @@ -119,6 +126,7 @@ export function SwapPage() { if (r) { r.setAttribute("chart", "") r.setAttribute("twapOn", "") + r.setAttribute("newAssetBtn", "") } }} assetIn={assetIn} @@ -134,7 +142,14 @@ export function SwapPage() { onTxNew={(e) => handleSubmit(e)} onDcaSchedule={(e) => handleSubmit(e)} onDcaTerminate={(e) => handleSubmit(e)} + onNewAssetClick={() => setAddToken(true)} /> + {addToken && ( + setAddToken(false)} + /> + )} ) } diff --git a/src/sections/transaction/ReviewTransaction.tsx b/src/sections/transaction/ReviewTransaction.tsx index b1fc05ff7..07d1e0145 100644 --- a/src/sections/transaction/ReviewTransaction.tsx +++ b/src/sections/transaction/ReviewTransaction.tsx @@ -21,6 +21,7 @@ export const ReviewTransaction = (props: Transaction) => { const { sendTx, sendEvmTx, + sendPermitTx, isLoading, isSuccess, isError: isSendError, @@ -28,6 +29,7 @@ export const ReviewTransaction = (props: Transaction) => { data, txState, reset, + txLink, } = useSendTx({ id: props.id }) const isError = isSendError || !!signError @@ -84,7 +86,7 @@ export const ReviewTransaction = (props: Transaction) => { isSuccess={isSuccess} isError={isError} error={error} - link={data?.transactionLink} + link={txLink} onReview={onReview} onClose={onClose} toastMessage={props.toastMessage} @@ -125,6 +127,10 @@ export const ReviewTransaction = (props: Transaction) => { props.onSubmitted?.() sendTx(signed) }} + onPermitDispatched={(permit) => { + props.onSubmitted?.() + sendPermitTx(permit) + }} onSignError={setSignError} /> ) : isEvmXCall(props.xcall) && props.xcallMeta ? ( diff --git a/src/sections/transaction/ReviewTransaction.utils.tsx b/src/sections/transaction/ReviewTransaction.utils.tsx index 9f34fbf55..4fe2eb262 100644 --- a/src/sections/transaction/ReviewTransaction.utils.tsx +++ b/src/sections/transaction/ReviewTransaction.utils.tsx @@ -2,23 +2,24 @@ import { TransactionReceipt, TransactionResponse, } from "@ethersproject/providers" -import { evmChains } from "@galacticcouncil/xcm-sdk" import { Hash } from "@open-web3/orml-types/interfaces" +import { ApiPromise } from "@polkadot/api" import { SubmittableExtrinsic } from "@polkadot/api/types" import type { AnyJson } from "@polkadot/types-codec/types" import { ExtrinsicStatus } from "@polkadot/types/interfaces" import { ISubmittableResult } from "@polkadot/types/types" import { MutationObserverOptions, useMutation } from "@tanstack/react-query" -import { useTransactionLink } from "api/transaction" import { decodeError } from "ethers-decode-error" import { useRpcProvider } from "providers/rpcProvider" import { useRef, useState } from "react" import { useTranslation } from "react-i18next" import { useMountedState } from "react-use" import { useEvmAccount } from "sections/web3-connect/Web3Connect.utils" +import { PermitResult } from "sections/web3-connect/signer/EthereumSigner" import { useToast } from "state/toasts" -import { H160, getEvmTxLink, isEvmAccount } from "utils/evm" +import { H160, getEvmChainById, getEvmTxLink, isEvmAccount } from "utils/evm" import { defer } from "utils/helpers" +import { getSubscanLinkByType } from "utils/formatting" type TxMethod = AnyJson & { method: string @@ -114,6 +115,49 @@ function evmTxReceiptToSubmittableResult(txReceipt: TransactionReceipt) { return submittableResult } + +const createResultOnCompleteHandler = + ( + api: ApiPromise, + { + onSuccess, + onError, + onSettled, + }: { + onSuccess: (result: ISubmittableResult) => void + onError: (error: Error) => void + onSettled: () => void + }, + ) => + (result: ISubmittableResult) => { + if (result.isCompleted) { + if (result.dispatchError) { + let docs = "" + let method = "" + let errorMessage = result.dispatchError.toString() + + if (result.dispatchError.isModule) { + const decoded = api.registry.findMetaError( + result.dispatchError.asModule, + ) + docs = decoded.docs.join(" ") + method = decoded.method + errorMessage = `${decoded.section}.${decoded.method}: ${docs}` + } + + const error = new TransactionError(errorMessage) + error.docs = docs + error.method = method + + onError(error) + } else { + onSuccess(result) + } + + onSettled() + } + } + export const useSendEvmTransactionMutation = ( options: MutationObserverOptions< ISubmittableResult & { @@ -127,6 +171,8 @@ export const useSendEvmTransactionMutation = ( > = {}, ) => { const [txState, setTxState] = useState(null) + const [txHash, setTxHash] = useState("") + const { account } = useEvmAccount() const sendTx = useMutation(async ({ evmTx }) => { @@ -141,21 +187,11 @@ export const useSendEvmTransactionMutation = ( try { setTxState("Broadcast") + setTxHash(evmTx?.hash ?? "") const receipt = await evmTx.wait() setTxState("InBlock") - const chainEntries = Object.entries(evmChains).find( - ([_, chain]) => chain.id === account?.chainId, - ) - - const chain = chainEntries?.[0] - - const transactionLink = getEvmTxLink(receipt.transactionHash, chain) - - return resolve({ - transactionLink, - ...evmTxReceiptToSubmittableResult(receipt), - }) + return resolve(evmTxReceiptToSubmittableResult(receipt)) } catch (err) { const { error } = decodeError(err) reject(new TransactionError(error)) @@ -165,11 +201,94 @@ export const useSendEvmTransactionMutation = ( }) }, options) + const chain = account?.chainId ? getEvmChainById(account.chainId) : null + const txLink = txHash && chain ? getEvmTxLink(txHash, chain.key) : "" + + return { + ...sendTx, + txState, + txLink, + reset: () => { + setTxState(null) + setTxHash("") + sendTx.reset() + }, + } +} + +export const useSendDispatchPermit = ( + options: MutationObserverOptions< + ISubmittableResult, + unknown, + PermitResult + > = {}, +) => { + const { api } = useRpcProvider() + const [txState, setTxState] = useState(null) + const [txHash, setTxHash] = useState("") + const isMounted = useMountedState() + + const sendTx = useMutation(async (permit) => { + return await new Promise(async (resolve, reject) => { + try { + const unsubscribe = await api.tx.multiTransactionPayment + .dispatchPermit( + permit.message.from, + permit.message.to, + permit.message.value, + permit.message.data, + permit.message.gaslimit, + permit.message.deadline, + permit.signature.v, + permit.signature.r, + permit.signature.s, + ) + .send(async (result) => { + if (!result || !result.status) return + + const timeout = setTimeout(() => { + clearTimeout(timeout) + reject(new UnknownTransactionState()) + }, 60000) + + if (isMounted()) { + setTxHash(result.txHash.toHex()) + setTxState(result.status.type) + } else { + clearTimeout(timeout) + } + + const onComplete = createResultOnCompleteHandler(api, { + onError: (error) => { + clearTimeout(timeout) + reject(error) + }, + onSuccess: (result) => { + clearTimeout(timeout) + resolve(result) + }, + onSettled: unsubscribe, + }) + + return onComplete(result) + }) + } catch (err) { + reject(err?.toString() ?? "Unknown error") + } + }) + }, options) + + const txLink = txHash + ? `${getSubscanLinkByType("extrinsic")}/${txHash}` + : undefined + return { ...sendTx, txState, + txLink, reset: () => { setTxState(null) + setTxHash("") sendTx.reset() }, } @@ -184,8 +303,8 @@ export const useSendTransactionMutation = ( ) => { const { api } = useRpcProvider() const isMounted = useMountedState() - const link = useTransactionLink() const [txState, setTxState] = useState(null) + const [txHash, setTxHash] = useState("") const sendTx = useMutation(async (sign) => { return await new Promise(async (resolve, reject) => { @@ -199,47 +318,25 @@ export const useSendTransactionMutation = ( }, 60000) if (isMounted()) { + setTxHash(result.txHash.toHex()) setTxState(result.status.type) } else { clearTimeout(timeout) } - if (result.isCompleted) { - if (result.dispatchError) { - let docs = "" - let method = "" - let errorMessage = result.dispatchError.toString() - - if (result.dispatchError.isModule) { - const decoded = api.registry.findMetaError( - result.dispatchError.asModule, - ) - docs = decoded.docs.join(" ") - method = decoded.method - errorMessage = `${decoded.section}.${decoded.method}: ${docs}` - } - - const error = new TransactionError(errorMessage) - error.docs = docs - error.method = method - + const onComplete = createResultOnCompleteHandler(api, { + onError: (error) => { clearTimeout(timeout) reject(error) - } else { - const transactionLink = await link.mutateAsync({ - blockHash: result.status.asInBlock.toString(), - txIndex: result.txIndex?.toString(), - }) - + }, + onSuccess: (result) => { clearTimeout(timeout) - resolve({ - transactionLink, - ...result, - }) - } + resolve(result) + }, + onSettled: unsubscribe, + }) - unsubscribe() - } + return onComplete(result) }) } catch (err) { reject(err?.toString() ?? "Unknown error") @@ -247,11 +344,17 @@ export const useSendTransactionMutation = ( }) }, options) + const txLink = txHash + ? `${getSubscanLinkByType("extrinsic")}/${txHash}` + : undefined + return { ...sendTx, txState, + txLink, reset: () => { setTxState(null) + setTxHash("") sendTx.reset() }, } @@ -342,7 +445,9 @@ const useErrorToastUpdate = (id: string) => { } export const useSendTx = ({ id }: { id: string }) => { - const [txType, setTxType] = useState<"default" | "evm" | null>(null) + const [txType, setTxType] = useState<"default" | "evm" | "permit" | null>( + null, + ) const boundReferralToast = useBoundReferralToast() const updateErrorToast = useErrorToastUpdate(id) @@ -365,11 +470,19 @@ export const useSendTx = ({ id }: { id: string }) => { onError: updateErrorToast, }) - const activeMutation = txType === "default" ? sendTx : sendEvmTx + const sendPermitTx = useSendDispatchPermit({ + onMutate: () => { + setTxType("permit") + }, + }) + + const activeMutation = + txType === "default" ? sendTx : txType === "evm" ? sendEvmTx : sendPermitTx return { sendTx: sendTx.mutateAsync, sendEvmTx: sendEvmTx.mutateAsync, + sendPermitTx: sendPermitTx.mutateAsync, ...activeMutation, } } diff --git a/src/sections/transaction/ReviewTransactionForm.tsx b/src/sections/transaction/ReviewTransactionForm.tsx index 373d810d8..6a1925ed0 100644 --- a/src/sections/transaction/ReviewTransactionForm.tsx +++ b/src/sections/transaction/ReviewTransactionForm.tsx @@ -7,7 +7,6 @@ import { ModalScrollableContent } from "components/Modal/Modal" import { Text } from "components/Typography/Text/Text" import { useTranslation } from "react-i18next" import { useAccount, useWallet } from "sections/web3-connect/Web3Connect.utils" -import { MetaMaskSigner } from "sections/web3-connect/wallets/MetaMask/MetaMaskSigner" import { Transaction, useStore } from "state/store" import { theme } from "theme" import { ReviewTransactionData } from "./ReviewTransactionData" @@ -20,12 +19,12 @@ import { ReviewTransactionSummary } from "sections/transaction/ReviewTransaction import { HYDRADX_CHAIN_KEY } from "sections/xcm/XcmPage.utils" import { useReferralCodesStore } from "sections/referrals/store/useReferralCodesStore" import BN from "bignumber.js" -import { - NATIVE_EVM_ASSET_ID, - NATIVE_EVM_ASSET_SYMBOL, - isEvmAccount, -} from "utils/evm" +import { NATIVE_EVM_ASSET_ID, isEvmAccount } from "utils/evm" import { isSetCurrencyExtrinsic } from "sections/transaction/ReviewTransaction.utils" +import { + EthereumSigner, + PermitResult, +} from "sections/web3-connect/signer/EthereumSigner" type TxProps = Omit & { tx: SubmittableExtrinsic<"promise"> @@ -34,6 +33,7 @@ type TxProps = Omit & { type Props = TxProps & { title?: string onCancel: () => void + onPermitDispatched: (permit: PermitResult) => void onEvmSigned: (data: { evmTx: TransactionResponse tx: SubmittableExtrinsic<"promise"> @@ -94,8 +94,16 @@ export const ReviewTransactionForm: FC = (props) => { if (!wallet) throw new Error("Missing wallet") if (!wallet.signer) throw new Error("Missing signer") - if (wallet?.signer instanceof MetaMaskSigner) { - const evmTx = await wallet.signer.sendDispatch(tx.method.toHex()) + if (wallet?.signer instanceof EthereumSigner) { + const txData = tx.method.toHex() + const shouldUsePermit = feePaymentMeta?.id !== NATIVE_EVM_ASSET_ID + + if (shouldUsePermit) { + const permit = await wallet.signer.getPermit(txData) + return props.onPermitDispatched(permit) + } + + const evmTx = await wallet.signer.sendDispatch(txData) return props.onEvmSigned({ evmTx, tx }) } @@ -125,17 +133,11 @@ export const ReviewTransactionForm: FC = (props) => { : acceptedFeePaymentAssets.length > 1 const isEditPaymentBalance = !isEnoughPaymentBalance && hasMultipleFeeAssets - const isEvmFeePaymentAssetInvalid = isEvmAccount(account?.address) - ? feePaymentMeta?.id !== NATIVE_EVM_ASSET_ID - : false - if (isOpenEditFeePaymentAssetModal) return editFeePaymentAssetModal const onConfirmClick = () => shouldOpenPolkaJSUrl ? window.open(polkadotJSUrl, "_blank") - : isEvmFeePaymentAssetInvalid - ? openEditFeePaymentAssetModal() : isEnoughPaymentBalance ? signTx.mutate() : hasMultipleFeeAssets @@ -148,7 +150,7 @@ export const ReviewTransactionForm: FC = (props) => { btnText = t( "liquidity.reviewTransaction.modal.confirmButton.openPolkadotJS", ) - } else if (isEditPaymentBalance || isEvmFeePaymentAssetInvalid) { + } else if (isEditPaymentBalance) { btnText = t( "liquidity.reviewTransaction.modal.confirmButton.notEnoughBalance", ) @@ -181,9 +183,7 @@ export const ReviewTransactionForm: FC = (props) => { = (props) => { } onClick={onConfirmClick} /> - {!shouldOpenPolkaJSUrl && isEvmFeePaymentAssetInvalid && ( - - {t( - "liquidity.reviewTransaction.modal.confirmButton.invalidEvmPaymentAsset.msg", - { symbol: NATIVE_EVM_ASSET_SYMBOL }, - )} - - )} {!isEnoughPaymentBalance && !transactionValues.isLoading && ( {t( diff --git a/src/sections/transaction/ReviewTransactionForm.utils.tsx b/src/sections/transaction/ReviewTransactionForm.utils.tsx index d92c53d1c..374ff55bc 100644 --- a/src/sections/transaction/ReviewTransactionForm.utils.tsx +++ b/src/sections/transaction/ReviewTransactionForm.utils.tsx @@ -11,7 +11,11 @@ import { useAssetsModal } from "sections/assets/AssetsModal.utils" import { useAccount } from "sections/web3-connect/Web3Connect.utils" import { BN_1 } from "utils/constants" import { useRpcProvider } from "providers/rpcProvider" -import { NATIVE_EVM_ASSET_DECIMALS, isEvmAccount } from "utils/evm" +import { + NATIVE_EVM_ASSET_DECIMALS, + NATIVE_EVM_ASSET_ID, + isEvmAccount, +} from "utils/evm" import { BN_NAN } from "utils/constants" import { useUserReferrer } from "api/referrals" import { HYDRADX_CHAIN_KEY } from "sections/xcm/XcmPage.utils" @@ -39,7 +43,8 @@ export const useTransactionValues = ({ const { fee, currencyId: feePaymentId, feeExtra } = overrides ?? {} - const isEvm = isEvmAccount(account?.address) + const shouldUseEvmPermit = feePaymentId !== NATIVE_EVM_ASSET_ID + const isEvm = !shouldUseEvmPermit && isEvmAccount(account?.address) const evmPaymentFee = useEvmPaymentFee( tx.method.toHex(), isEvm ? account?.address : "", diff --git a/src/sections/transaction/ReviewTransactionXCallForm.tsx b/src/sections/transaction/ReviewTransactionXCallForm.tsx index 58621a74a..15daaa470 100644 --- a/src/sections/transaction/ReviewTransactionXCallForm.tsx +++ b/src/sections/transaction/ReviewTransactionXCallForm.tsx @@ -12,7 +12,7 @@ import { useEvmAccount, useWallet, } from "sections/web3-connect/Web3Connect.utils" -import { MetaMaskSigner } from "sections/web3-connect/wallets/MetaMask/MetaMaskSigner" +import { EthereumSigner } from "sections/web3-connect/signer/EthereumSigner" import { Transaction } from "state/store" import { theme } from "theme" @@ -45,7 +45,7 @@ export const ReviewTransactionXCallForm: FC = ({ if (!wallet.signer) throw new Error("Missing signer") if (!isEvmXCall(xcall)) throw new Error("Missing xcall") - if (wallet?.signer instanceof MetaMaskSigner) { + if (wallet?.signer instanceof EthereumSigner) { const { srcChain } = xcallMeta const evmTx = await wallet.signer.sendTransaction({ diff --git a/src/sections/wallet/addToken/AddToken.utils.tsx b/src/sections/wallet/addToken/AddToken.utils.tsx index 64eeaa619..276c3a75b 100644 --- a/src/sections/wallet/addToken/AddToken.utils.tsx +++ b/src/sections/wallet/addToken/AddToken.utils.tsx @@ -1,4 +1,5 @@ import { useMutation } from "@tanstack/react-query" +import { ExternalAssetCursor } from "@galacticcouncil/apps" import { useRpcProvider } from "providers/rpcProvider" import { ToastMessage, useStore } from "state/store" import { HydradxRuntimeXcmAssetLocation } from "@polkadot/types/lookup" @@ -122,7 +123,14 @@ export const useUserExternalTokenStore = create()( }, ], addToken: (token) => - set((store) => ({ tokens: [...store.tokens, token] })), + set((store) => { + const latest = { tokens: [...store.tokens, token] } + ExternalAssetCursor.reset({ + state: latest, + version: 0.2, + }) + return latest + }), isAdded: (id) => id ? get().tokens.some((token) => token.id === id) : false, }), diff --git a/src/sections/wallet/addToken/modal/AddTokenFormModal.tsx b/src/sections/wallet/addToken/modal/AddTokenFormModal.tsx index 443ec845f..7b34f4ee4 100644 --- a/src/sections/wallet/addToken/modal/AddTokenFormModal.tsx +++ b/src/sections/wallet/addToken/modal/AddTokenFormModal.tsx @@ -17,6 +17,7 @@ import { Spacer } from "components/Spacer/Spacer" import { useToast } from "state/toasts" import { useRefetchProviderData } from "api/provider" import { InputBox } from "components/Input/InputBox" +import { TokenInfo } from "./components/TokenInfo/TokenInfo" type Props = { asset: TExternalAsset @@ -153,7 +154,11 @@ export const AddTokenFormModal: FC = ({ asset, onClose }) => { )} /> - + + + + + {isChainStored ? (
-
- - {t("wallet.assets.hydraPositions.header.providedAmount")} - - - {t("value.tokenWithSymbol", { value: amount, symbol })} - - - - -
+ {!isXYK && ( + <> +
+ + {t("wallet.assets.hydraPositions.header.providedAmount")} + + + {t("value.tokenWithSymbol", { + value: position.providedAmount, + symbol, + })} + + + + +
- + + + )}
diff --git a/src/sections/wallet/assets/hydraPositions/WalletAssetsHydraPositions.utils.tsx b/src/sections/wallet/assets/hydraPositions/WalletAssetsHydraPositions.utils.tsx index c3b7c3d93..6626114a2 100644 --- a/src/sections/wallet/assets/hydraPositions/WalletAssetsHydraPositions.utils.tsx +++ b/src/sections/wallet/assets/hydraPositions/WalletAssetsHydraPositions.utils.tsx @@ -21,7 +21,6 @@ import { isXYKPosition, TXYKPosition, } from "./data/WalletAssetsHydraPositionsData.utils" -import { DisplayValue } from "components/DisplayValue/DisplayValue" export const useHydraPositionsTable = ( data: (HydraPositionsTableData | TXYKPosition)[], @@ -82,37 +81,16 @@ export const useHydraPositionsTable = ( textAlign: "center", }} > - {isXYKPosition(row.original) ? ( -
-
- - {row.original.balances - ?.map((balance) => - t("value.tokenWithSymbol", { - value: balance.balanceHuman, - symbol: balance.symbol, - }), - ) - .join(" | ")} - -
- - - -
- ) : ( - - )} + + {!isDesktop && ( { const { assets } = useRpcProvider() - const accountPositions = useAccountOmnipoolPositions(address) + const accountPositions = useAccountNFTPositions(address) const positions = useOmnipoolPositions( accountPositions.data?.omnipoolNfts.map((nft) => nft.instanceId) ?? [], ) @@ -184,7 +184,7 @@ export const useXykPositionsData = ({ search }: { search?: string } = {}) => { .multipliedBy(totalIssuance?.myPoolShare ?? 1) .div(100) - return { balanceHuman, symbol: balanceMeta.symbol } + return { amount: balanceHuman, symbol: balanceMeta.symbol } }) ?? [] if (meta.assets.includes(assets.native.id)) { @@ -196,9 +196,12 @@ export const useXykPositionsData = ({ search }: { search?: string } = {}) => { // order of the HDX in a share token pair if (meta.assets[0] === assets.native.id) { - balances.unshift({ balanceHuman, symbol: assets.native.symbol }) + balances.unshift({ + amount: balanceHuman, + symbol: assets.native.symbol, + }) } else { - balances.push({ balanceHuman, symbol: assets.native.symbol }) + balances.push({ amount: balanceHuman, symbol: assets.native.symbol }) } } diff --git a/src/sections/wallet/assets/hydraPositions/details/HydraPositionsDetailsMob.tsx b/src/sections/wallet/assets/hydraPositions/details/HydraPositionsDetailsMob.tsx index 28481aad1..83225a583 100644 --- a/src/sections/wallet/assets/hydraPositions/details/HydraPositionsDetailsMob.tsx +++ b/src/sections/wallet/assets/hydraPositions/details/HydraPositionsDetailsMob.tsx @@ -40,7 +40,7 @@ export const HydraPositionsDetailsMob = ({ row, onClose }: Props) => { {row.balances ?.map((balance) => t("value.tokenWithSymbol", { - value: balance.balanceHuman, + value: balance.amount, symbol: balance.symbol, }), ) diff --git a/src/sections/wallet/assets/hydraPositions/details/WalletAssetsHydraPositionsDetails.tsx b/src/sections/wallet/assets/hydraPositions/details/WalletAssetsHydraPositionsDetails.tsx index 9053d1dc9..19c593f0a 100644 --- a/src/sections/wallet/assets/hydraPositions/details/WalletAssetsHydraPositionsDetails.tsx +++ b/src/sections/wallet/assets/hydraPositions/details/WalletAssetsHydraPositionsDetails.tsx @@ -9,9 +9,12 @@ import { useRpcProvider } from "providers/rpcProvider" import { useSpotPrice } from "api/spotPrice" import { BN_0, BN_1 } from "utils/constants" +type PairAsset = { amount: BN; symbol: string } + type Props = { assetId: string - amount: BN + amount?: BN + amountPair?: PairAsset[] lrna?: BN amountDisplay?: BN } @@ -20,38 +23,38 @@ export const WalletAssetsHydraPositionsDetails = ({ amount, lrna, amountDisplay, + amountPair, assetId, }: Props) => { - const isDesktop = useMedia(theme.viewport.gte.sm) - const { t } = useTranslation() const { assets } = useRpcProvider() - - const meta = assetId ? assets.getAsset(assetId.toString()) : undefined - - const lrnaSpotPrice = useSpotPrice(assets.getAsset("1").id, assetId) - - const lrnaPositionPrice = - lrna?.multipliedBy(lrnaSpotPrice.data?.spotPrice ?? BN_1) ?? BN_0 + const meta = assets.getAsset(assetId.toString()) return (
-
+ {amountPair ? ( +
+ + {amountPair + .map((balance) => + t("value.tokenWithSymbol", { + value: balance.amount, + symbol: balance.symbol, + }), + ) + .join(" | ")} + +
+ ) : lrna && !lrna.isZero() ? ( + + ) : ( {t("value.tokenWithSymbol", { - value: lrnaPositionPrice.plus(amount ?? BN_0), + value: amount, symbol: meta?.symbol, })} - - {isDesktop && ( - - )} -
+ )} {amountDisplay && ( ) } + +const LrnaValue = ({ + assetId, + lrna, + amount, +}: { + assetId: string + lrna: BN + amount?: BN +}) => { + const isDesktop = useMedia(theme.viewport.gte.sm) + const { t } = useTranslation() + const { assets } = useRpcProvider() + + const meta = assetId ? assets.getAsset(assetId.toString()) : undefined + const lrnaSpotPrice = useSpotPrice(assets.getAsset("1").id, assetId) + + const lrnaPositionPrice = + lrna?.multipliedBy(lrnaSpotPrice.data?.spotPrice ?? BN_1) ?? BN_0 + return ( +
+ + {t("value.tokenWithSymbol", { + value: lrnaPositionPrice.plus(amount ?? BN_0), + symbol: meta?.symbol, + })} + + + {isDesktop && ( + + )} +
+ ) +} diff --git a/src/sections/wallet/assets/paymentAsset/WalletPaymentAsset.tsx b/src/sections/wallet/assets/paymentAsset/WalletPaymentAsset.tsx index 3ba8961f0..d4d0d856c 100644 --- a/src/sections/wallet/assets/paymentAsset/WalletPaymentAsset.tsx +++ b/src/sections/wallet/assets/paymentAsset/WalletPaymentAsset.tsx @@ -8,7 +8,6 @@ import { useRpcProvider } from "providers/rpcProvider" import { useTranslation } from "react-i18next" import { useEditFeePaymentAsset } from "sections/transaction/ReviewTransactionForm.utils" import { useAccount } from "sections/web3-connect/Web3Connect.utils" -import { isEvmAccount } from "utils/evm" export const WalletPaymentAsset = () => { const { t } = useTranslation() @@ -34,12 +33,6 @@ export const WalletPaymentAsset = () => { const isFeePaymentAssetEditable = acceptedFeePaymentAssetsIds.length > 1 - const isEvm = isEvmAccount(account?.address) - - if (isEvm) { - return null - } - return (
diff --git a/src/sections/wallet/assets/table/actions/WalletAssetsTableActions.tsx b/src/sections/wallet/assets/table/actions/WalletAssetsTableActions.tsx index 4e1a349d1..e627433f2 100644 --- a/src/sections/wallet/assets/table/actions/WalletAssetsTableActions.tsx +++ b/src/sections/wallet/assets/table/actions/WalletAssetsTableActions.tsx @@ -50,9 +50,6 @@ export const WalletAssetsTableActions = (props: Props) => { tradability: { inTradeRouter, canBuy }, } = props.asset - const enablePaymentFee = - couldBeSetAsPaymentFee && !isEvmAccount(account?.address) - const couldWatchMetaMaskAsset = isMetaMask(window?.ethereum) && isEvmAccount(account?.address) && @@ -106,7 +103,7 @@ export const WalletAssetsTableActions = (props: Props) => { onSelect: inTradeRouter ? () => navigate({ - to: "/trade/swap", + to: LINKS.swap, search: canBuy ? { assetOut: id } : { assetIn: id }, }) : undefined, @@ -129,7 +126,7 @@ export const WalletAssetsTableActions = (props: Props) => { ] const actionItems = [ - enablePaymentFee + couldBeSetAsPaymentFee ? { key: "setAsFeePayment", icon: , diff --git a/src/sections/wallet/assets/table/actions/WalletAssetsTableActionsMob.tsx b/src/sections/wallet/assets/table/actions/WalletAssetsTableActionsMob.tsx index 44c684c49..5f883202f 100644 --- a/src/sections/wallet/assets/table/actions/WalletAssetsTableActionsMob.tsx +++ b/src/sections/wallet/assets/table/actions/WalletAssetsTableActionsMob.tsx @@ -22,7 +22,6 @@ import { } from "sections/wallet/assets/table/data/WalletAssetsTableData.utils" import Skeleton from "react-loading-skeleton" import { AddTokenAction } from "./WalletAssetsTableActions" -import { isEvmAccount } from "utils/evm" type Props = { row?: AssetsTableData @@ -47,8 +46,6 @@ export const WalletAssetsTableActionsMob = ({ const isNativeAsset = row.id === NATIVE_ASSET_ID - const isEvm = isEvmAccount(account?.address) - return (
@@ -193,7 +190,7 @@ export const WalletAssetsTableActionsMob = ({ ) : (
- {!isEvm && ( - - )} +
)} diff --git a/src/sections/wallet/assets/table/data/WalletAssetsTableData.utils.ts b/src/sections/wallet/assets/table/data/WalletAssetsTableData.utils.ts index 3792a70d0..f2d930ec5 100644 --- a/src/sections/wallet/assets/table/data/WalletAssetsTableData.utils.ts +++ b/src/sections/wallet/assets/table/data/WalletAssetsTableData.utils.ts @@ -163,8 +163,8 @@ export const useAssetsData = ({ if (a.transferableDisplay.isNaN()) return 1 if (b.transferableDisplay.isNaN()) return -1 - if (a.isExternal) return 1 - if (b.isExternal) return -1 + if (a.isExternal && !a.name) return 1 + if (b.isExternal && !b.name) return -1 if (!b.transferableDisplay.eq(a.transferableDisplay)) return b.transferableDisplay.minus(a.transferableDisplay).toNumber() diff --git a/src/sections/wallet/transfer/WalletTransferAssetSelect.tsx b/src/sections/wallet/transfer/WalletTransferAssetSelect.tsx index 05a004a16..c7872c83c 100644 --- a/src/sections/wallet/transfer/WalletTransferAssetSelect.tsx +++ b/src/sections/wallet/transfer/WalletTransferAssetSelect.tsx @@ -20,6 +20,8 @@ export const WalletTransferAssetSelect = (props: { balance?: BN balanceMax?: BN withoutMaxBtn?: boolean + withoutMaxValue?: boolean + disabled?: boolean error?: string }) => { @@ -42,6 +44,8 @@ export const WalletTransferAssetSelect = (props: { error={props.error} balanceLabel={t("selectAsset.balance.label")} withoutMaxBtn={props.withoutMaxBtn} + withoutMaxValue={props.withoutMaxValue} + disabled={props.disabled} /> ) } diff --git a/src/sections/web3-connect/Web3Connect.utils.ts b/src/sections/web3-connect/Web3Connect.utils.ts index ea3cfeccc..cd9690028 100644 --- a/src/sections/web3-connect/Web3Connect.utils.ts +++ b/src/sections/web3-connect/Web3Connect.utils.ts @@ -9,7 +9,10 @@ import { useShallow } from "hooks/useShallow" import { useEffect, useRef } from "react" import { usePrevious } from "react-use" -import { WalletConnect } from "sections/web3-connect/wallets/WalletConnect" +import { + NamespaceType, + WalletConnect, +} from "sections/web3-connect/wallets/WalletConnect" import { POLKADOT_APP_NAME } from "utils/api" import { H160, getEvmAddress, isEvmAddress } from "utils/evm" import { safeConvertAddressSS58 } from "utils/formatting" @@ -21,12 +24,14 @@ import { } from "./store/useWeb3ConnectStore" import { WalletProviderType, getSupportedWallets } from "./wallets" import { ExternalWallet } from "./wallets/ExternalWallet" -import { MetaMask } from "./wallets/MetaMask/MetaMask" +import { MetaMask } from "./wallets/MetaMask" import { isMetaMask, requestNetworkSwitch } from "utils/metamask" import { genesisHashToChain } from "utils/helpers" import { WalletAccount } from "sections/web3-connect/types" import { EVM_PROVIDERS } from "sections/web3-connect/constants/providers" import { useAddressStore } from "components/AddressBook/AddressBook.utils" +import { EthereumSigner } from "sections/web3-connect/signer/EthereumSigner" +import { PolkadotSigner } from "sections/web3-connect/signer/PolkadotSigner" export type { WalletProvider } from "./wallets" export { WalletProviderType, getSupportedWallets } @@ -159,8 +164,12 @@ export const useWeb3ConnectEagerEnable = () => { // skip if already enabled if (isEnabled) return - // skip WalletConnect eager enable - if (wallet instanceof WalletConnect) return + // disconnect on missing WalletConnect session + if (wallet instanceof WalletConnect && !wallet._session) { + wallet.disconnect() + state.disconnect() + return + } await wallet?.enable(POLKADOT_APP_NAME) const accounts = await wallet?.getAccounts() @@ -190,8 +199,15 @@ export const useWeb3ConnectEagerEnable = () => { function cleanUp() { const metamask = prevWallet instanceof MetaMask ? prevWallet : null + const walletConnect = + prevWallet instanceof WalletConnect ? prevWallet : null const external = prevWallet instanceof ExternalWallet ? prevWallet : null + if (walletConnect) { + // disconnect from WalletConnect + walletConnect.disconnect() + } + if (metamask) { // unsub from metamask events on disconnect metamask.unsubscribe() @@ -208,13 +224,27 @@ export const useWeb3ConnectEagerEnable = () => { export const useEnableWallet = ( provider: WalletProviderType | null, - options?: MutationObserverOptions, + options?: MutationObserverOptions< + WalletAccount[] | undefined, + unknown, + NamespaceType | void, + unknown + >, ) => { const { wallet } = getWalletProviderByType(provider) const { add: addToAddressBook } = useAddressStore() const meta = useWeb3ConnectStore(useShallow((state) => state.meta)) - const { mutate: enable, ...mutation } = useMutation( - async () => { + const { mutate: enable, ...mutation } = useMutation< + WalletAccount[] | undefined, + unknown, + NamespaceType | void, + unknown + >( + async (namespace) => { + if (wallet instanceof WalletConnect && namespace) { + wallet.setNamespace(namespace) + } + await wallet?.enable(POLKADOT_APP_NAME) if (wallet instanceof MetaMask) { @@ -315,7 +345,11 @@ export function getWalletProviderByType(type?: WalletProviderType | null) { function getProviderQueryKey(type: WalletProviderType | null) { const { wallet } = getWalletProviderByType(type) - if (wallet instanceof MetaMask) { + if (wallet?.signer instanceof PolkadotSigner) { + return [type, wallet.signer?.session?.topic].join("-") + } + + if (wallet?.signer instanceof EthereumSigner) { return [type, wallet.signer?.address].join("-") } diff --git a/src/sections/web3-connect/accounts/Web3ConnectAccount.tsx b/src/sections/web3-connect/accounts/Web3ConnectAccount.tsx index c6b346a73..a7521ea62 100644 --- a/src/sections/web3-connect/accounts/Web3ConnectAccount.tsx +++ b/src/sections/web3-connect/accounts/Web3ConnectAccount.tsx @@ -10,6 +10,7 @@ import { genesisHashToChain } from "utils/helpers" import { DisplayValue } from "components/DisplayValue/DisplayValue" import BN from "bignumber.js" import { availableNetworks } from "@polkadot/networks" +import { isEvmAccount } from "utils/evm" type Props = Account & { isProxy?: boolean @@ -32,7 +33,9 @@ export const Web3ConnectAccount: FC = ({ const { hydraAddress } = getAddressVariants(address) - const chain = genesisHashToChain(genesisHash) + const chain = !isEvmAccount(address) + ? genesisHashToChain(genesisHash) + : undefined const addr = displayAddress || hydraAddress const isHydraAddr = addr && isHydraAddress(addr) diff --git a/src/sections/web3-connect/constants/providers.ts b/src/sections/web3-connect/constants/providers.ts index 2096dacae..30316fbb5 100644 --- a/src/sections/web3-connect/constants/providers.ts +++ b/src/sections/web3-connect/constants/providers.ts @@ -23,6 +23,16 @@ export const DESKTOP_PROVIDERS: WalletProviderType[] = [ export const EVM_PROVIDERS: WalletProviderType[] = [ WalletProviderType.MetaMask, WalletProviderType.TalismanEvm, + WalletProviderType.WalletConnect, +] + +export const SUBSTRATE_PROVIDERS: WalletProviderType[] = [ + WalletProviderType.Talisman, + WalletProviderType.SubwalletJS, + WalletProviderType.Enkrypt, + WalletProviderType.PolkadotJS, + WalletProviderType.NovaWallet, + WalletProviderType.WalletConnect, ] export const ALTERNATIVE_PROVIDERS: WalletProviderType[] = [ diff --git a/src/sections/web3-connect/modal/Web3ConnectWalletLoader.styled.ts b/src/sections/web3-connect/modal/Web3ConnectWalletLoader.styled.ts new file mode 100644 index 000000000..b3e792a8b --- /dev/null +++ b/src/sections/web3-connect/modal/Web3ConnectWalletLoader.styled.ts @@ -0,0 +1,20 @@ +import styled from "@emotion/styled" + +export const SContainer = styled.div` + display: flex; + flex-direction: column; + align-items: center; +` + +export const SContent = styled.div` + display: grid; + grid-template-columns: 1fr; + + align-items: center; + justify-items: center; + + > * { + grid-column: 1; + grid-row: 1; + } +` diff --git a/src/sections/web3-connect/modal/Web3ConnectWalletLoader.tsx b/src/sections/web3-connect/modal/Web3ConnectWalletLoader.tsx new file mode 100644 index 000000000..8b901e320 --- /dev/null +++ b/src/sections/web3-connect/modal/Web3ConnectWalletLoader.tsx @@ -0,0 +1,46 @@ +import { Text } from "components/Typography/Text/Text" +import { useTranslation } from "react-i18next" +import { + WalletProviderType, + getWalletProviderByType, +} from "sections/web3-connect/Web3Connect.utils" +import { FC } from "react" +import { Spinner } from "components/Spinner/Spinner" +import { SContainer, SContent } from "./Web3ConnectWalletLoader.styled" + +type Props = { provider: WalletProviderType } + +export const Web3ConnectWalletLoader: FC = ({ provider }) => { + const { t } = useTranslation() + const { wallet } = getWalletProviderByType(provider) + return ( + + + + {wallet?.logo.alt} + + + {t("walletConnect.pending.title")} + +
+ + {t("walletConnect.pending.description", { + name: wallet?.title, + })} + +
+
+ ) +} diff --git a/src/sections/web3-connect/providers/Web3ConnectProviderButton.tsx b/src/sections/web3-connect/providers/Web3ConnectProviderButton.tsx index 926638904..bfea257cf 100644 --- a/src/sections/web3-connect/providers/Web3ConnectProviderButton.tsx +++ b/src/sections/web3-connect/providers/Web3ConnectProviderButton.tsx @@ -5,6 +5,7 @@ import { FC } from "react" import { useTranslation } from "react-i18next" import { WalletProvider, + WalletProviderType, useEnableWallet, } from "sections/web3-connect/Web3Connect.utils" import { SProviderButton } from "./Web3ConnectProviders.styled" @@ -34,7 +35,12 @@ export const Web3ConnectProviderButton: FC = ({ type, wallet }) => { }) function onClick() { - installed ? enable() : openInstallUrl(installUrl) + if (type === WalletProviderType.WalletConnect) { + // defer WalletConnect enabling until the user clicks chooses a chain to connect to + setStatus(type, WalletProviderStatus.Pending) + } else { + installed ? enable() : openInstallUrl(installUrl) + } } return ( diff --git a/src/sections/web3-connect/providers/Web3ConnectProviderPending.tsx b/src/sections/web3-connect/providers/Web3ConnectProviderPending.tsx index 678405a12..075a68266 100644 --- a/src/sections/web3-connect/providers/Web3ConnectProviderPending.tsx +++ b/src/sections/web3-connect/providers/Web3ConnectProviderPending.tsx @@ -1,63 +1,23 @@ -import { css } from "@emotion/react" -import { Text } from "components/Typography/Text/Text" -import { useTranslation } from "react-i18next" -import { - WalletProviderType, - getWalletProviderByType, -} from "sections/web3-connect/Web3Connect.utils" import { FC } from "react" -import { Spinner } from "components/Spinner/Spinner" +import { WalletProviderType } from "sections/web3-connect/Web3Connect.utils" +import { Web3ConnectWalletLoader } from "sections/web3-connect/modal/Web3ConnectWalletLoader" +import { Web3ConnectWCSelector } from "sections/web3-connect/providers/Web3ConnectWCSelector" type Props = { provider: WalletProviderType } export const Web3ConnectProviderPending: FC = ({ provider }) => { - const { t } = useTranslation() - const { wallet } = getWalletProviderByType(provider) + const isWalletConnect = provider === WalletProviderType.WalletConnect return (
-
* { - grid-column: 1; - grid-row: 1; - } - `} - > - - {wallet?.logo.alt} -
- - {t("walletConnect.pending.title")} - -
- - {t("walletConnect.pending.description", { - name: wallet?.title, - })} - -
+ {isWalletConnect ? ( + + ) : ( + + )}
) } diff --git a/src/sections/web3-connect/providers/Web3ConnectProviders.tsx b/src/sections/web3-connect/providers/Web3ConnectProviders.tsx index 4b35da335..a11a8da7e 100644 --- a/src/sections/web3-connect/providers/Web3ConnectProviders.tsx +++ b/src/sections/web3-connect/providers/Web3ConnectProviders.tsx @@ -15,9 +15,11 @@ import { DESKTOP_PROVIDERS, EVM_PROVIDERS, MOBILE_PROVIDERS, + SUBSTRATE_PROVIDERS, } from "sections/web3-connect/constants/providers" +import { POLKADOT_CAIP_ID_MAP } from "sections/web3-connect/wallets/WalletConnect" -const useWalletProviders = (mode: WalletMode) => { +const useWalletProviders = (mode: WalletMode, chain?: string) => { const isDesktop = useMedia(theme.viewport.gte.sm) return useMemo(() => { @@ -34,13 +36,19 @@ const useWalletProviders = (mode: WalletMode) => { : MOBILE_PROVIDERS.includes(provider.type) const isEvmProvider = EVM_PROVIDERS.includes(provider.type) + const isSubstrateProvider = SUBSTRATE_PROVIDERS.includes(provider.type) const byMode = isDefaultMode || (isEvmMode && isEvmProvider) || - (isSubstrateMode && !isEvmProvider) + (isSubstrateMode && isSubstrateProvider) - return byScreen && byMode + const byWalletConnect = + isSubstrateMode && provider.type === "walletconnect" && chain + ? !!POLKADOT_CAIP_ID_MAP[chain] + : true + + return byScreen && byMode && byWalletConnect }) const alternativeProviders = wallets.filter((provider) => { @@ -52,15 +60,19 @@ const useWalletProviders = (mode: WalletMode) => { defaultProviders, alternativeProviders, } - }, [isDesktop, mode]) + }, [isDesktop, mode, chain]) } export const Web3ConnectProviders = () => { const { t } = useTranslation() const mode = useWeb3ConnectStore(useShallow((state) => state.mode)) + const meta = useWeb3ConnectStore(useShallow((state) => state.meta)) - const { defaultProviders, alternativeProviders } = useWalletProviders(mode) + const { defaultProviders, alternativeProviders } = useWalletProviders( + mode, + meta?.chain, + ) return ( <> diff --git a/src/sections/web3-connect/providers/Web3ConnectWCSelector.tsx b/src/sections/web3-connect/providers/Web3ConnectWCSelector.tsx new file mode 100644 index 000000000..5c62b0305 --- /dev/null +++ b/src/sections/web3-connect/providers/Web3ConnectWCSelector.tsx @@ -0,0 +1,93 @@ +import { Button } from "components/Button/Button" +import { useEffect } from "react" +import { + WalletProviderType, + getWalletProviderByType, + useEnableWallet, +} from "sections/web3-connect/Web3Connect.utils" +import { Web3ConnectWalletLoader } from "sections/web3-connect/modal/Web3ConnectWalletLoader" +import { + WalletProviderStatus, + useWeb3ConnectStore, +} from "sections/web3-connect/store/useWeb3ConnectStore" +import { WalletConnect } from "sections/web3-connect/wallets/WalletConnect" +import { isEvmAccount } from "utils/evm" + +const walletConnectType = WalletProviderType.WalletConnect + +const getWalletConnect = () => { + return getWalletProviderByType(walletConnectType).wallet as WalletConnect +} + +export const Web3ConnectWCSelector = () => { + const { setStatus, setError, provider, status, account, mode } = + useWeb3ConnectStore() + + const isDefaultMode = mode === "default" + const isEvmMode = mode === "evm" + const isSubstrateMode = mode === "substrate" + + const { enable, isLoading } = useEnableWallet(walletConnectType, { + onSuccess: () => + setStatus(walletConnectType, WalletProviderStatus.Connected), + onError: (error) => { + setStatus(walletConnectType, WalletProviderStatus.Error) + if (error instanceof Error && error.message) { + setError(error.message) + } + }, + }) + + const wallet = getWalletConnect() + + const isConnectedToWc = + !!account?.address && + provider === walletConnectType && + status === WalletProviderStatus.Connected + + const isSessionActive = !!wallet?._session + + const shouldTriggerAutoConnect = + (isConnectedToWc && !isSessionActive) || isEvmMode || isSubstrateMode + + useEffect(() => { + if (shouldTriggerAutoConnect) { + const isEvm = + (isDefaultMode && isEvmAccount(account?.address)) || isEvmMode + enable(isEvm ? "eip155" : "polkadot") + } + }, [ + account?.address, + enable, + isDefaultMode, + isEvmMode, + shouldTriggerAutoConnect, + ]) + + return ( +
+ {shouldTriggerAutoConnect || isLoading ? ( + + ) : ( +
+ + +
+ )} +
+ ) +} diff --git a/src/sections/web3-connect/signer/EthereumSigner.ts b/src/sections/web3-connect/signer/EthereumSigner.ts new file mode 100644 index 000000000..129301eaf --- /dev/null +++ b/src/sections/web3-connect/signer/EthereumSigner.ts @@ -0,0 +1,230 @@ +import { + JsonRpcSigner, + TransactionRequest, + Web3Provider, +} from "@ethersproject/providers" +import { evmChains } from "@galacticcouncil/xcm-sdk" +import UniversalProvider from "@walletconnect/universal-provider/dist/types/UniversalProvider" + +import BigNumber from "bignumber.js" +import { Contract, Signature } from "ethers" +import { splitSignature } from "ethers/lib/utils" +import { + CALL_PERMIT_ABI, + CALL_PERMIT_ADDRESS, + DISPATCH_ADDRESS, +} from "utils/evm" +import { + MetaMaskLikeProvider, + isEthereumProvider, + requestNetworkSwitch, +} from "utils/metamask" + +type PermitMessage = { + from: string + to: string + value: number + data: string + gaslimit: number + nonce: number + deadline: number +} + +export type PermitResult = { signature: Signature; message: PermitMessage } + +type EthereumProvider = MetaMaskLikeProvider | UniversalProvider + +export class EthereumSigner { + address: string + provider: EthereumProvider + signer: JsonRpcSigner + + constructor(address: string, provider: EthereumProvider) { + this.address = address + this.provider = provider + this.signer = this.getSigner(provider) + } + + getSigner(provider: EthereumProvider) { + return new Web3Provider(provider).getSigner() + } + + setAddress(address: string) { + this.address = address + } + + getGasValues(tx: TransactionRequest) { + return Promise.all([ + this.signer.provider.estimateGas(tx), + this.signer.provider.getGasPrice(), + ]) + } + + sendDispatch = async (data: string) => { + return this.sendTransaction({ + to: DISPATCH_ADDRESS, + data, + from: this.address, + }) + } + + getPermitNonce = async (): Promise => { + const callPermit = new Contract( + CALL_PERMIT_ADDRESS, + CALL_PERMIT_ABI, + this.signer.provider, + ) + + return callPermit.nonces(this.address) + } + + getPermit = async (data: string): Promise => { + if (this.provider && this.address) { + const nonce = await this.getPermitNonce() + const tx = { + from: this.address, + to: DISPATCH_ADDRESS, + data, + } + + const [gas] = await this.getGasValues(tx) + + const createPermitMessageData = () => { + const message: PermitMessage = { + ...tx, + value: 0, + gaslimit: gas.mul(11).div(10).toNumber(), + nonce: nonce.toNumber(), + deadline: Math.floor(Date.now() / 1000 + 3600), + } + + const typedData = JSON.stringify({ + types: { + EIP712Domain: [ + { + name: "name", + type: "string", + }, + { + name: "version", + type: "string", + }, + { + name: "chainId", + type: "uint256", + }, + { + name: "verifyingContract", + type: "address", + }, + ], + CallPermit: [ + { + name: "from", + type: "address", + }, + { + name: "to", + type: "address", + }, + { + name: "value", + type: "uint256", + }, + { + name: "data", + type: "bytes", + }, + { + name: "gaslimit", + type: "uint64", + }, + { + name: "nonce", + type: "uint256", + }, + { + name: "deadline", + type: "uint256", + }, + ], + }, + primaryType: "CallPermit", + domain: { + name: "Call Permit Precompile", + version: "1", + chainId: parseInt(import.meta.env.VITE_EVM_CHAIN_ID), + verifyingContract: CALL_PERMIT_ADDRESS, + }, + message: message, + }) + + return { + typedData, + message, + } + } + + const { message, typedData } = createPermitMessageData() + + const method = "eth_signTypedData_v4" + const params = [this.address, typedData] + + return new Promise((resolve, reject) => { + this.provider.sendAsync?.( + { + method, + params, + }, + (err, result) => { + if (err) { + return reject(err) + } + + return resolve({ + message, + signature: splitSignature(result.result), + }) + }, + ) + }) + } + + throw new Error("Error signing transaction. Provider not found") + } + + sendTransaction = async ( + transaction: TransactionRequest & { chain?: string }, + ) => { + const { chain, ...tx } = transaction + const from = chain && evmChains[chain] ? chain : "hydradx" + + if (isEthereumProvider(this.provider)) { + await requestNetworkSwitch(this.provider, { + chain: from, + onSwitch: () => { + // update signer after network switch + this.signer = this.getSigner(this.provider) + }, + }) + } + + if (from === "hydradx") { + const [gas, gasPrice] = await this.getGasValues(tx) + + const onePrc = gasPrice.div(100) + const gasPricePlus = gasPrice.add(onePrc) + + return await this.signer.sendTransaction({ + maxPriorityFeePerGas: gasPricePlus, + maxFeePerGas: gasPricePlus, + gasLimit: gas.mul(11).div(10), // add 10% + ...tx, + }) + } else { + return await this.signer.sendTransaction({ + ...tx, + }) + } + } +} diff --git a/src/sections/web3-connect/wallets/WalletConnect/WalletConnectSigner.ts b/src/sections/web3-connect/signer/PolkadotSigner.ts similarity index 80% rename from src/sections/web3-connect/wallets/WalletConnect/WalletConnectSigner.ts rename to src/sections/web3-connect/signer/PolkadotSigner.ts index daacb0cc4..7d6908812 100644 --- a/src/sections/web3-connect/wallets/WalletConnect/WalletConnectSigner.ts +++ b/src/sections/web3-connect/signer/PolkadotSigner.ts @@ -8,6 +8,8 @@ import type { import type { HexString } from "@polkadot/util/types" import SignClient from "@walletconnect/sign-client" import { SessionTypes, SignClientTypes } from "@walletconnect/types" +import { POLKADOT_CAIP_ID_MAP } from "sections/web3-connect/wallets/WalletConnect" +import { genesisHashToChain } from "utils/helpers" export type KeypairType = "ed25519" | "sr25519" export type WcAccount = `${string}:${string}:${string}` @@ -18,29 +20,26 @@ interface Signature { signature: HexString } -export class WalletConnectSigner implements Signer { +export class PolkadotSigner implements Signer { registry: TypeRegistry client: SignClient session: SessionTypes.Struct - chainId: PolkadotNamespaceChainId id = 0 - constructor( - client: SignClient, - session: SessionTypes.Struct, - chainId: PolkadotNamespaceChainId, - ) { + constructor(client: SignClient, session: SessionTypes.Struct) { this.client = client this.session = session this.registry = new TypeRegistry() - this.chainId = chainId } // this method is set this way to be bound to this class. signPayload = async (payload: SignerPayloadJSON): Promise => { + const chain = genesisHashToChain(payload.genesisHash as `0x${string}`) + const chainId = POLKADOT_CAIP_ID_MAP[chain?.network] + let request = { topic: this.session.topic, - chainId: this.chainId, + chainId, request: { id: 1, jsonrpc: "2.0", @@ -56,9 +55,10 @@ export class WalletConnectSigner implements Signer { // It might be used outside of the object context to sign messages. // ref: https://polkadot.js.org/docs/extension/cookbook#sign-a-message signRaw = async (raw: SignerPayloadRaw): Promise => { + const chainId = POLKADOT_CAIP_ID_MAP["hydradx"] let request = { topic: this.session.topic, - chainId: this.chainId, + chainId, request: { id: 1, jsonrpc: "2.0", diff --git a/src/sections/web3-connect/wallets/MetaMask/MetaMask.ts b/src/sections/web3-connect/wallets/MetaMask.ts similarity index 95% rename from src/sections/web3-connect/wallets/MetaMask/MetaMask.ts rename to src/sections/web3-connect/wallets/MetaMask.ts index 3fe846893..44ef6c627 100644 --- a/src/sections/web3-connect/wallets/MetaMask/MetaMask.ts +++ b/src/sections/web3-connect/wallets/MetaMask.ts @@ -1,6 +1,6 @@ import { SubscriptionFn, Wallet, WalletAccount } from "@talismn/connect-wallets" import MetaMaskLogo from "assets/icons/MetaMask.svg" -import { MetaMaskSigner } from "sections/web3-connect/wallets/MetaMask/MetaMaskSigner" +import { EthereumSigner } from "sections/web3-connect/signer/EthereumSigner" import { shortenAccountAddress } from "utils/formatting" import { noop } from "utils/helpers" import { @@ -26,7 +26,7 @@ export class MetaMask implements Wallet { } _extension: Required | undefined - _signer: MetaMaskSigner | undefined + _signer: EthereumSigner | undefined onAccountsChanged: SubscriptionFn | undefined onChainChanged: ChainSubscriptionFn | undefined @@ -80,7 +80,7 @@ export class MetaMask implements Wallet { Array.isArray(addresses) && addresses.length > 0 ? addresses[0] : "" this._extension = metamask - this._signer = address ? new MetaMaskSigner(address, metamask) : undefined + this._signer = address ? new EthereumSigner(address, metamask) : undefined this.subscribeAccounts(this.onAccountsChanged) this.subscribeChain(this.onChainChanged) diff --git a/src/sections/web3-connect/wallets/MetaMask/MetaMaskSigner.ts b/src/sections/web3-connect/wallets/MetaMask/MetaMaskSigner.ts deleted file mode 100644 index 545720b3f..000000000 --- a/src/sections/web3-connect/wallets/MetaMask/MetaMaskSigner.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { - JsonRpcSigner, - TransactionRequest, - Web3Provider, -} from "@ethersproject/providers" -import { evmChains } from "@galacticcouncil/xcm-sdk" -import { DISPATCH_ADDRESS } from "utils/evm" -import { MetaMaskLikeProvider, requestNetworkSwitch } from "utils/metamask" - -export class MetaMaskSigner { - address: string - provider: MetaMaskLikeProvider - signer: JsonRpcSigner - - constructor(address: string, provider: MetaMaskLikeProvider) { - this.address = address - this.provider = provider - this.signer = this.getSigner(provider) - } - - getSigner(provider: MetaMaskLikeProvider) { - return new Web3Provider(provider).getSigner() - } - - setAddress(address: string) { - this.address = address - } - - getGasValues(tx: TransactionRequest) { - return Promise.all([ - this.signer.provider.estimateGas(tx), - this.signer.provider.getGasPrice(), - ]) - } - - sendDispatch = async (data: string) => { - return this.sendTransaction({ - to: DISPATCH_ADDRESS, - data, - from: this.address, - }) - } - - sendTransaction = async ( - transaction: TransactionRequest & { chain?: string }, - ) => { - const { chain, ...tx } = transaction - const from = chain && evmChains[chain] ? chain : "hydradx" - await requestNetworkSwitch(this.provider, { - chain: from, - onSwitch: () => { - // update signer after network switch - this.signer = this.getSigner(this.provider) - }, - }) - - if (from === "hydradx") { - const [gas, gasPrice] = await this.getGasValues(tx) - - const onePrc = gasPrice.div(100) - const gasPricePlus = gasPrice.add(onePrc) - - return await this.signer.sendTransaction({ - maxPriorityFeePerGas: gasPricePlus, - maxFeePerGas: gasPricePlus, - gasLimit: gas.mul(11).div(10), // add 10% - ...tx, - }) - } else { - return await this.signer.sendTransaction({ - ...tx, - }) - } - } -} diff --git a/src/sections/web3-connect/wallets/MetaMask/index.ts b/src/sections/web3-connect/wallets/MetaMask/index.ts deleted file mode 100644 index ad0757cb4..000000000 --- a/src/sections/web3-connect/wallets/MetaMask/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { MetaMask } from "./MetaMask" -export { TalismanEvm } from "../TalismanEvm" diff --git a/src/sections/web3-connect/wallets/TalismanEvm.ts b/src/sections/web3-connect/wallets/TalismanEvm.ts index 826d02d03..38135b8be 100644 --- a/src/sections/web3-connect/wallets/TalismanEvm.ts +++ b/src/sections/web3-connect/wallets/TalismanEvm.ts @@ -1,5 +1,5 @@ import { isTalisman } from "utils/metamask" -import { MetaMask } from "./MetaMask/MetaMask" +import { MetaMask } from "./MetaMask" import TalismanLogo from "assets/icons/TalismanLogo.svg" diff --git a/src/sections/web3-connect/wallets/WalletConnect.ts b/src/sections/web3-connect/wallets/WalletConnect.ts new file mode 100644 index 000000000..60ec4371b --- /dev/null +++ b/src/sections/web3-connect/wallets/WalletConnect.ts @@ -0,0 +1,309 @@ +import { Wallet, WalletAccount } from "@talismn/connect-wallets" +import { WalletConnectModal } from "@walletconnect/modal" +import { SessionTypes } from "@walletconnect/types" +import { + IUniversalProvider, + NamespaceConfig, + UniversalProvider, +} from "@walletconnect/universal-provider" +import WalletConnectLogo from "assets/icons/WalletConnect.svg" +import { POLKADOT_APP_NAME } from "utils/api" +import { evmChains } from "@galacticcouncil/xcm-sdk" +import { EthereumSigner } from "sections/web3-connect/signer/EthereumSigner" +import { isEvmAddress } from "utils/evm" +import { shortenAccountAddress } from "utils/formatting" +import { + PolkadotNamespaceChainId, + PolkadotSigner, +} from "sections/web3-connect/signer/PolkadotSigner" +import { noop } from "utils/helpers" + +const WC_PROJECT_ID = import.meta.env.VITE_WC_PROJECT_ID as string +const DOMAIN_URL = import.meta.env.VITE_DOMAIN_URL as string + +export const POLKADOT_CAIP_ID_MAP: Record = { + hydradx: import.meta.env + .VITE_HDX_CAIP_ID as string as PolkadotNamespaceChainId, + polkadot: "polkadot:91b171bb158e2d3848fa23a9f1c25182", + acala: "polkadot:fc41b9bd8ef8fe53d58c7ea67c794c7e", + assethub: "polkadot:68d56f15f85d3136970ec16946040bc1", + astar: "polkadot:9eb76c5184c4ab8679d2d5d819fdf90b", + bifrost: "polkadot:262e1b2ad728475fd6fe88e62d34c200", + centrifuge: "polkadot:b3db41421702df9a7fcac62b53ffeac8", + crust: "polkadot:4319cc49ee79495b57a1fec4d2bd43f5", + interlay: "polkadot:bf88efe70e9e0e916416e8bed61f2b45", + nodle: "polkadot:97da7ede98d7bad4e36b4d734b605542", + phala: "polkadot:1bb969d85965e4bb5a651abbedf21a54", + subsocial: "polkadot:4a12be580bb959937a1c7a61d5cf2442", + unique: "polkadot:84322d9cddbf35088f1e54e9a85c967a", + zeitgeist: "polkadot:1bf2a2ecb4a868de66ea8610f2ce7c8c", +} + +const POLKADOT_CHAIN_IDS = Object.values(POLKADOT_CAIP_ID_MAP) + +const walletConnectParams = { + projectId: WC_PROJECT_ID, + relayUrl: "wss://relay.walletconnect.com", + metadata: { + name: POLKADOT_APP_NAME, + description: POLKADOT_APP_NAME, + url: DOMAIN_URL, + icons: ["https://walletconnect.com/walletconnect-logo.png"], + }, +} + +const evmChainsArr = Object.values(evmChains) + +const namespaces = { + eip155: { + chains: evmChainsArr.map(({ id }) => `eip155:${id}`), + methods: [ + "eth_sendTransaction", + "eth_signTransaction", + "eth_sign", + "personal_sign", + "eth_signTypedData", + "wallet_switchEthereumChain", + "wallet_addEthereumChain", + ], + events: ["chainChanged", "accountsChanged"], + rpcMap: evmChainsArr.reduce( + (prev, curr) => ({ + ...prev, + [curr.id]: curr.rpcUrls.default.http[0], + }), + {}, + ), + }, + polkadot: { + methods: ["polkadot_signTransaction", "polkadot_signMessage"], + chains: POLKADOT_CHAIN_IDS, + events: ["accountsChanged", "disconnect"], + }, +} + +export type NamespaceType = keyof typeof namespaces + +type ModalSubFn = (session?: SessionTypes.Struct) => void + +export class WalletConnect implements Wallet { + extensionName = "walletconnect" + title = "WalletConnect" + installUrl = "" + logo = { + src: WalletConnectLogo, + alt: "WalletConnect Logo", + } + + _modal: WalletConnectModal | undefined + _extension: IUniversalProvider | undefined + _signer: PolkadotSigner | EthereumSigner | undefined + _session: SessionTypes.Struct | undefined + _namespace: NamespaceConfig | undefined + + onSessionDelete: () => void = noop + + constructor({ + onModalOpen, + onModalClose, + onSesssionDelete, + }: { + onModalOpen?: ModalSubFn + onModalClose?: ModalSubFn + onSesssionDelete?: () => void + } = {}) { + this._modal = new WalletConnectModal({ + projectId: WC_PROJECT_ID, + }) + + this.subscribeToModalEvents(onModalOpen, onModalClose) + + if (onSesssionDelete) this.onSessionDelete = onSesssionDelete + } + + get extension() { + return this._extension + } + + get signer() { + return this._signer + } + + get modal() { + return this._modal + } + + get namespace() { + return this._namespace + } + + get installed() { + return true + } + + get rawExtension() { + return this._extension + } + + initializeProvider = async () => { + await this._extension?.cleanupPendingPairings() + await this._extension?.disconnect() + + const provider = await UniversalProvider.init(walletConnectParams).catch( + (err) => { + console.error(err) + }, + ) + + if (!provider) { + throw new Error( + "WalletConnectError: Connection failed. Please try again.", + ) + } + + this._extension = provider + provider.on("display_uri", this.handleDisplayUri) + provider.on("session_update", this.handleSessionUpdate) + provider.on("session_delete", this.handleSessionDelete) + + return provider + } + + transformError = (err: Error): Error => { + return err + } + + setNamespace = async (namespace: keyof typeof namespaces) => { + this._namespace = { + [namespace]: namespaces[namespace], + } + } + + getChains = () => { + if (!this.namespace) return [] + + return Object.values(this.namespace) + .map((namespace) => namespace.chains) + .flat() + } + + subscribeToModalEvents = (onOpen?: ModalSubFn, onClose?: ModalSubFn) => { + this.modal?.subscribeModal((state) => { + if (state.open) { + onOpen?.() + } else { + onClose?.(this._session) + + if (!this._session) { + this.disconnect() + } + } + }) + } + + handleDisplayUri = async (uri: string) => { + await this.modal?.openModal({ uri, chains: this.getChains() }) + } + + handleSessionUpdate = ({ session }: { session: SessionTypes.Struct }) => { + this._session = session + } + + handleSessionDelete = () => { + this.disconnect() + this.onSessionDelete() + } + + enable = async (dappName: string) => { + if (!dappName) { + throw new Error("MissingParamsError: Dapp name is required.") + } + + const provider = await this.initializeProvider() + + if (!provider) { + throw new Error( + "WalletConnectError: WalletConnect provider is not initialized.", + ) + } + + if (!this.namespace) { + throw new Error( + "WalletConnectError: Namespace is required to enable WalletConnect.", + ) + } + + try { + const session = await provider.connect({ + optionalNamespaces: this.namespace, + }) + + if (!session) { + throw new Error( + "WalletConnectError: Failed to create WalletConnect sessopm.", + ) + } + + this._session = session + + const accounts = await this.getAccounts() + + const namespace = Object.keys(this.namespace).pop() as NamespaceType + + if (namespace === "eip155" && provider instanceof UniversalProvider) { + const mainAddress = accounts[0]?.address + this._signer = mainAddress + ? new EthereumSigner(mainAddress, provider) + : undefined + } + + if (namespace === "polkadot" && provider.client) { + this._signer = new PolkadotSigner(provider.client, session) + } + } finally { + this.modal?.closeModal() + } + } + + getAccounts = async (): Promise => { + if (!this._session) { + throw new Error( + `The 'Wallet.enable(dappname)' function should be called first.`, + ) + } + + const wcAccounts = Object.values(this._session.namespaces) + .map((namespace) => namespace.accounts) + .flat() + + // return only first (active) account + return wcAccounts.slice(0, 1).map((wcAccount) => { + const address = wcAccount.split(":")[2] + return { + address, + source: this.extensionName, + name: isEvmAddress(address) + ? shortenAccountAddress(address) + : this.title, + wallet: this, + signer: this.signer, + } + }) + } + + subscribeAccounts = async () => {} + + disconnect = () => { + this._extension?.cleanupPendingPairings() + this._extension?.disconnect() + + this._signer = undefined + this._session = undefined + this._extension = undefined + + if ("indexedDB" in window) { + // reset previous saved settings of WC2 to avoid mismatch between EVM and Substrate + indexedDB.deleteDatabase("WALLET_CONNECT_V2_INDEXED_DB") + } + } +} diff --git a/src/sections/web3-connect/wallets/WalletConnect/WalletConnect.ts b/src/sections/web3-connect/wallets/WalletConnect/WalletConnect.ts deleted file mode 100644 index b680fa2cc..000000000 --- a/src/sections/web3-connect/wallets/WalletConnect/WalletConnect.ts +++ /dev/null @@ -1,145 +0,0 @@ -import { Wallet, WalletAccount } from "@talismn/connect-wallets" -import { WalletConnectModal } from "@walletconnect/modal" -import { SessionTypes } from "@walletconnect/types" -import { - IUniversalProvider, - UniversalProvider, -} from "@walletconnect/universal-provider" -import WalletConnectLogo from "assets/icons/WalletConnect.svg" -import { - PolkadotNamespaceChainId, - WalletConnectSigner, -} from "sections/web3-connect/wallets/WalletConnect/WalletConnectSigner" -import { POLKADOT_APP_NAME } from "utils/api" - -const WC_PROJECT_ID = import.meta.env.VITE_WC_PROJECT_ID as string -const DOMAIN_URL = import.meta.env.VITE_DOMAIN_URL as string -const HYDRADX_CHAIN_ID = import.meta.env - .VITE_HDX_CAIP_ID as string as PolkadotNamespaceChainId - -const walletConnectParams = { - projectId: WC_PROJECT_ID, - relayUrl: "wss://relay.walletconnect.com", - metadata: { - name: POLKADOT_APP_NAME, - description: POLKADOT_APP_NAME, - url: DOMAIN_URL, - icons: ["https://walletconnect.com/walletconnect-logo.png"], - }, -} - -const requiredNamespaces = { - polkadot: { - methods: ["polkadot_signTransaction", "polkadot_signMessage"], - chains: [HYDRADX_CHAIN_ID], - events: ["accountsChanged", "disconnect"], - }, -} - -const chains = Object.values(requiredNamespaces) - .map((namespace) => namespace.chains) - .flat() - -const modal = new WalletConnectModal({ - projectId: WC_PROJECT_ID, - chains, -}) - -const provider = await UniversalProvider.init(walletConnectParams) - -export class WalletConnect implements Wallet { - extensionName = "walletconnect" - title = "WalletConnect" - installUrl = "" - logo = { - src: WalletConnectLogo, - alt: "WalletConnect Logo", - } - - _extension: IUniversalProvider | undefined - _signer: WalletConnectSigner | undefined - _session: SessionTypes.Struct | undefined - - constructor({ - onModalOpen, - onModalClose, - }: { - onModalOpen?: () => void - onModalClose?: () => void - } = {}) { - modal.subscribeModal((state) => { - state.open ? onModalOpen?.() : onModalClose?.() - }) - } - - get extension() { - return this._extension - } - - get signer() { - return this._signer - } - - get installed() { - return true - } - - get rawExtension() { - return provider - } - - transformError = (err: Error): Error => { - return err - } - - enable = async (dappName: string) => { - if (!dappName) { - throw new Error("MissingParamsError: Dapp name is required.") - } - - try { - const { uri, approval } = await this.rawExtension.client.connect({ - requiredNamespaces, - }) - - if (uri) { - await modal.openModal({ uri, chains }) - } - - const session = await approval() - - const client = this.rawExtension.client - - this._extension = this.rawExtension - this._session = session - this._signer = new WalletConnectSigner(client, session, HYDRADX_CHAIN_ID) - } finally { - modal.closeModal() - } - } - - getAccounts = async (): Promise => { - if (!this._session) { - throw new Error( - `The 'Wallet.enable(dappname)' function should be called first.`, - ) - } - - const wcAccounts = Object.values(this._session.namespaces) - .map((namespace) => namespace.accounts) - .flat() - - return wcAccounts.map((wcAccount) => { - const address = wcAccount.split(":")[2] - return { - address, - source: this.extensionName, - name: this.title, - wallet: this, - signer: this.signer, - } - }) - } - - subscribeAccounts = async () => {} -} diff --git a/src/sections/web3-connect/wallets/WalletConnect/index.ts b/src/sections/web3-connect/wallets/WalletConnect/index.ts deleted file mode 100644 index 9a246b88a..000000000 --- a/src/sections/web3-connect/wallets/WalletConnect/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { WalletConnect } from "./WalletConnect" diff --git a/src/sections/web3-connect/wallets/index.ts b/src/sections/web3-connect/wallets/index.ts index 3e6036c38..9c584144d 100644 --- a/src/sections/web3-connect/wallets/index.ts +++ b/src/sections/web3-connect/wallets/index.ts @@ -1,9 +1,4 @@ -import { - SubscriptionFn, - Wallet, - WalletAccount, - getWallets, -} from "@talismn/connect-wallets" +import { SubscriptionFn, Wallet, getWallets } from "@talismn/connect-wallets" import { ExternalWallet } from "./ExternalWallet" import { MetaMask } from "./MetaMask" @@ -63,7 +58,19 @@ const metaMask: Wallet = new MetaMask({ onAccountsChanged: onMetaMaskLikeAccountChange(WalletProviderType.MetaMask), }) -const walletConnect: Wallet = new WalletConnect() +const walletConnect: Wallet = new WalletConnect({ + onModalClose: (session) => { + if (!session) { + const state = useWeb3ConnectStore.getState() + state.disconnect() + state.toggle() + } + }, + onSesssionDelete: () => { + const state = useWeb3ConnectStore.getState() + state.disconnect() + }, +}) const externalWallet: Wallet = new ExternalWallet() diff --git a/src/sections/xcm/XcmPage.tsx b/src/sections/xcm/XcmPage.tsx index 0dbb88163..f84f4e537 100644 --- a/src/sections/xcm/XcmPage.tsx +++ b/src/sections/xcm/XcmPage.tsx @@ -148,6 +148,7 @@ export function XcmPage() { onXcmNew={handleSubmit} onWalletChange={handleWalletChange} ss58Prefix={ss58Prefix} + blacklist="pendulum" /> diff --git a/src/utils/balance.ts b/src/utils/balance.ts index c7eb24b57..cb53de0a2 100644 --- a/src/utils/balance.ts +++ b/src/utils/balance.ts @@ -1,5 +1,5 @@ import { BN } from "@polkadot/util" -import { BN_10, QUINTILL, TRILL } from "./constants" +import { BN_10, BN_NAN, QUINTILL, TRILL } from "./constants" import BigNumber from "bignumber.js" import { BigNumberFormatOptionsSchema, @@ -51,9 +51,11 @@ export const getFixedPointAmount = ( * eg.: 1.23456789 => 123456789 */ export const scale = ( - amount: BigNumberLikeType, + amount: BigNumberLikeType | undefined, decimals: number | "t" | "q", ) => { + if (!amount) return BN_NAN + const _decimals = decimals === "t" ? TRILL : decimals === "q" ? QUINTILL : decimals @@ -68,9 +70,11 @@ export const scale = ( * eg.: 123456789 => 1.23456789 */ export const scaleHuman = ( - amount: BigNumberLikeType, + amount: BigNumberLikeType | undefined, decimals: number | "t" | "q", ) => { + if (!amount) return BN_NAN + const _decimals = decimals === "t" ? TRILL : decimals === "q" ? QUINTILL : decimals diff --git a/src/utils/evm.ts b/src/utils/evm.ts index 268f63839..9b31a3476 100644 --- a/src/utils/evm.ts +++ b/src/utils/evm.ts @@ -16,6 +16,8 @@ export const NATIVE_EVM_ASSET_ID = import.meta.env .VITE_EVM_NATIVE_ASSET_ID as string export const DISPATCH_ADDRESS = "0x0000000000000000000000000000000000000401" +export const CALL_PERMIT_ADDRESS = "0x000000000000000000000000000000000000080a" +export const CALL_PERMIT_ABI = `[{"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"uint64","name":"gaslimit","type":"uint64"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"dispatch","outputs":[{"internalType":"bytes","name":"output","type":"bytes"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"nonces","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]` export function isEvmAccount(address?: string) { if (!address) return false @@ -72,4 +74,17 @@ export function safeConvertAddressH160(value: string): string | null { } } +export function getEvmChainById(chainId: number) { + const entries = Object.entries(evmChains).find( + ([_, chain]) => chain.id === chainId, + ) + const [key, chain] = entries ?? [] + if (key) { + return { + key, + ...chain, + } + } +} + export { getEvmAddress, isEvmAddress } diff --git a/src/utils/farms/claiming.ts b/src/utils/farms/claiming.ts index b44ff6d58..552a97aa7 100644 --- a/src/utils/farms/claiming.ts +++ b/src/utils/farms/claiming.ts @@ -1,17 +1,13 @@ -/* eslint-disable @typescript-eslint/no-unused-expressions */ import { u32 } from "@polkadot/types" import { AccountId32 } from "@polkadot/types/interfaces" -import { u8aToHex } from "@polkadot/util" -import { decodeAddress } from "@polkadot/util-crypto" import { useMutation } from "@tanstack/react-query" import { useAccountAssetBalances } from "api/accountBalances" import { useBestNumber } from "api/chain" -import { useUserDeposits } from "api/deposits" +import { TDeposit, useUserDeposits } from "api/deposits" import { useFarms, useInactiveFarms, useOraclePrices } from "api/farms" import { useOmnipoolAssets } from "api/omnipool" import BigNumber from "bignumber.js" import { useMemo } from "react" -import { TMiningNftPosition } from "sections/pools/PoolsPage.utils" import { ToastMessage, useStore } from "state/store" import { getFloatingPointAmount } from "utils/balance" import { BN_0 } from "utils/constants" @@ -19,32 +15,38 @@ import { useDisplayPrices } from "utils/displayAsset" import { getAccountResolver } from "./claiming/accountResolver" import { OmnipoolLiquidityMiningClaimSim } from "./claiming/claimSimulator" import { MultiCurrencyContainer } from "./claiming/multiCurrency" -import { createMutableFarmEntries } from "./claiming/mutableFarms" +import { createMutableFarmEntry } from "./claiming/mutableFarms" import { useRpcProvider } from "providers/rpcProvider" -export const useClaimableAmount = ( - poolId?: string, - depositNft?: TMiningNftPosition, -) => { +export const useClaimableAmount = (poolId?: string, depositNft?: TDeposit) => { const bestNumberQuery = useBestNumber() + const { api, assets } = useRpcProvider() + const { omnipoolDeposits, xykDeposits } = useUserDeposits() - const allDeposits = useUserDeposits() + const meta = poolId ? assets.getAsset(poolId) : undefined + const isXYK = assets.isShareToken(meta) - const filteredDeposits = poolId - ? { - ...allDeposits, - data: - allDeposits.data?.filter( - (deposit) => deposit.data.ammPoolId.toString() === poolId, - ) ?? [], - } - : allDeposits + const filteredDeposits = useMemo( + () => + poolId + ? [...omnipoolDeposits, ...xykDeposits].filter((deposit) => { + return ( + deposit.data.ammPoolId.toString() === + (isXYK ? meta.poolAddress : poolId) + ) + }) ?? [] + : [...omnipoolDeposits, ...xykDeposits], + [isXYK, meta, omnipoolDeposits, poolId, xykDeposits], + ) const omnipoolAssets = useOmnipoolAssets() const poolIds = poolId ? [poolId] - : omnipoolAssets.data?.map((asset) => asset.id.toString()) ?? [] + : [ + ...(omnipoolAssets.data?.map((asset) => asset.id.toString()) ?? []), + ...assets.shareTokens.map((asset) => asset.id), + ] const farms = useFarms(poolIds) @@ -55,7 +57,6 @@ export const useClaimableAmount = ( [farms.data, inactiveFarms.data], ) - const { api, assets } = useRpcProvider() const accountResolver = getAccountResolver(api.registry) const assetIds = [ @@ -89,7 +90,6 @@ export const useClaimableAmount = ( const queries = [ bestNumberQuery, - filteredDeposits, farms, inactiveFarms, accountBalances, @@ -100,14 +100,13 @@ export const useClaimableAmount = ( const data = useMemo(() => { if ( !bestNumberQuery.data || - !filteredDeposits.data || + !filteredDeposits.length || !accountBalances.data || !spotPrices.data ) return undefined - const deposits = - depositNft != null ? [depositNft] : filteredDeposits.data ?? [] + const deposits = depositNft != null ? [depositNft] : filteredDeposits ?? [] const bestNumber = bestNumberQuery const multiCurrency = new MultiCurrencyContainer( @@ -120,15 +119,19 @@ export const useClaimableAmount = ( metas ?? [], ) - const { globalFarms, yieldFarms } = createMutableFarmEntries(allFarms ?? []) - return deposits ?.map((record) => record.data.yieldFarmEntries.map((farmEntry) => { + const poolId = record.isXyk + ? assets.getShareTokenByAddress(record.data.ammPoolId.toString()) + ?.id + : record.data.ammPoolId.toString() + const aprEntry = allFarms.find( (i) => i.globalFarm.id.eq(farmEntry.globalFarmId) && - i.yieldFarm.id.eq(farmEntry.yieldFarmId), + i.yieldFarm.id.eq(farmEntry.yieldFarmId) && + i.poolId === poolId, ) if (!aprEntry) return null @@ -143,12 +146,15 @@ export const useClaimableAmount = ( if (!oracle?.data) return null + const { globalFarm, yieldFarm } = createMutableFarmEntry(aprEntry) + const reward = simulator.claim_rewards( - globalFarms[aprEntry.globalFarm.id.toString()], - yieldFarms[aprEntry.yieldFarm.id.toString()], + globalFarm, + yieldFarm, farmEntry, bestNumber.data.relaychainBlockNumber.toBigNumber(), - oracle.data.oraclePrice, + oracle.data.oraclePrice ?? + aprEntry.globalFarm.priceAdjustment.toBigNumber(), ) const spotPrice = spotPrices.data?.find( @@ -207,7 +213,7 @@ export const useClaimableAmount = ( bestNumberQuery, depositNft, allFarms, - filteredDeposits.data, + filteredDeposits, metas, oraclePrices, spotPrices.data, @@ -216,30 +222,50 @@ export const useClaimableAmount = ( return { data, isLoading } } -export const useClaimAllMutation = ( +export const useClaimFarmMutation = ( poolId?: string, - depositNft?: TMiningNftPosition, + depositNft?: TDeposit, toast?: ToastMessage, onClose?: () => void, onBack?: () => void, ) => { - const { api } = useRpcProvider() + const { api, assets } = useRpcProvider() const { createTransaction } = useStore() + const meta = poolId ? assets.getAsset(poolId) : undefined + const isXYK = assets.isShareToken(meta) - const allUserDeposits = useUserDeposits() + const { omnipoolDeposits, xykDeposits } = useUserDeposits() - const filteredDeposits = poolId - ? allUserDeposits.data?.filter( - (deposit) => deposit.data.ammPoolId.toString() === poolId.toString(), - ) ?? [] - : allUserDeposits.data + let omnipoolDeposits_: TDeposit[] = [] + let xykDeposits_: TDeposit[] = [] - const deposits = depositNft ? [depositNft] : filteredDeposits + if (depositNft) { + if (isXYK) { + xykDeposits_ = [depositNft] + } else { + omnipoolDeposits_ = [depositNft] + } + } else { + if (poolId) { + if (isXYK) { + xykDeposits_ = xykDeposits.filter( + (deposit) => deposit.data.ammPoolId.toString() === meta.poolAddress, + ) + } else { + omnipoolDeposits_ = omnipoolDeposits.filter( + (deposit) => deposit.data.ammPoolId.toString() === poolId, + ) + } + } else { + xykDeposits_ = xykDeposits + omnipoolDeposits_ = omnipoolDeposits + } + } return useMutation(async () => { - const txs = - deposits - ?.map((deposit) => + const omnipoolTxs = + omnipoolDeposits_ + .map((deposit) => deposit.data.yieldFarmEntries.map((entry) => api.tx.omnipoolLiquidityMining.claimRewards( deposit.id, @@ -249,14 +275,27 @@ export const useClaimAllMutation = ( ) .flat(2) ?? [] - if (txs.length > 0) { + const xykTxs = + xykDeposits_ + .map((deposit) => + deposit.data.yieldFarmEntries.map((entry) => + api.tx.xykLiquidityMining.claimRewards( + deposit.id, + entry.yieldFarmId, + ), + ), + ) + .flat(2) ?? [] + + const allTxs = [...omnipoolTxs, ...xykTxs] + + if (allTxs.length > 0) { return await createTransaction( - { tx: txs.length > 1 ? api.tx.utility.forceBatch(txs) : txs[0] }, + { + tx: allTxs.length > 1 ? api.tx.utility.forceBatch(allTxs) : allTxs[0], + }, { toast, onBack, onClose }, ) } }) } - -// @ts-expect-error -window.decodeAddressToBytes = (bsx: string) => u8aToHex(decodeAddress(bsx)) diff --git a/src/utils/farms/claiming/mutableFarms.ts b/src/utils/farms/claiming/mutableFarms.ts index fda4e2d53..3badde62f 100644 --- a/src/utils/farms/claiming/mutableFarms.ts +++ b/src/utils/farms/claiming/mutableFarms.ts @@ -118,3 +118,73 @@ export function createMutableFarmEntries( return { yieldFarms, globalFarms } } + +export function createMutableFarmEntry({ + globalFarm, + yieldFarm, +}: { + globalFarm: PalletLiquidityMiningGlobalFarmData + yieldFarm: PalletLiquidityMiningYieldFarmData +}) { + const globalFarm_ = { + id: globalFarm.id, + incentivizedAsset: globalFarm.incentivizedAsset, + owner: globalFarm.owner, + rewardCurrency: globalFarm.rewardCurrency, + state: globalFarm.state, + // PeriodOf + updatedAt: globalFarm.updatedAt.toBigNumber(), + // Balance + totalSharesZ: globalFarm.totalSharesZ.toBigNumber(), + // FixedU128 + accumulatedRpz: globalFarm.accumulatedRpz.toBigNumber(), + // Balance + pendingRewards: globalFarm.pendingRewards.toBigNumber(), + // Balance + accumulatedPaidRewards: globalFarm.accumulatedPaidRewards.toBigNumber(), + // Perquintill + yieldPerPeriod: globalFarm.yieldPerPeriod + .toBigNumber() + .dividedBy(BN_QUINTILL), + // PeriodOf + plannedYieldingPeriods: globalFarm.plannedYieldingPeriods.toBigNumber(), + // BlockNumberFor + blocksPerPeriod: globalFarm.blocksPerPeriod.toBigNumber(), + // Balance + maxRewardPerPeriod: globalFarm.maxRewardPerPeriod.toBigNumber(), + // Balance + minDeposit: globalFarm.minDeposit.toBigNumber(), + // u32 + liveYieldFarmsCount: globalFarm.liveYieldFarmsCount.toBigNumber(), + // u32 + totalYieldFarmsCount: globalFarm.totalYieldFarmsCount.toBigNumber(), + // FixedU128 + priceAdjustment: globalFarm.priceAdjustment.toBigNumber(), + } + + const yieldFarm_ = { + id: yieldFarm.id, + // PeriodOf + updatedAt: yieldFarm.updatedAt.toBigNumber(), + // Balance + totalShares: yieldFarm.totalShares.toBigNumber(), + // Balance + totalValuedShares: yieldFarm.totalValuedShares.toBigNumber(), + // FixedU128 + accumulatedRpvs: yieldFarm.accumulatedRpvs.toBigNumber(), + // FixedU128 + accumulatedRpz: yieldFarm.accumulatedRpz.toBigNumber(), + // FarmMultiplier + multiplier: yieldFarm.multiplier.toBigNumber(), + // u64 + entriesCount: yieldFarm.entriesCount.toBigNumber(), + // Balance + leftToDistribute: yieldFarm.leftToDistribute.toBigNumber(), + // PeriodOf + totalStopped: yieldFarm.totalStopped.toBigNumber(), + loyaltyCurve: yieldFarm.loyaltyCurve, + state: yieldFarm.state, + } + + return { yieldFarm: yieldFarm_, globalFarm: globalFarm_ } +} diff --git a/src/utils/farms/deposit.ts b/src/utils/farms/deposit.ts deleted file mode 100644 index a0a74f4cc..000000000 --- a/src/utils/farms/deposit.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { useMutation } from "@tanstack/react-query" -import { useFarms } from "api/farms" -import { StepProps } from "components/Stepper/Stepper" -import { useTranslation } from "react-i18next" -import { ToastMessage, useStore } from "state/store" -import { useRpcProvider } from "providers/rpcProvider" - -export type FarmDepositMutationType = ReturnType - -export const useFarmDepositMutation = ( - poolId: string, - positionId: string, - toast: ToastMessage, - onClose: () => void, - onSuccess: () => void, -) => { - const { createTransaction } = useStore() - const { api } = useRpcProvider() - const farms = useFarms([poolId]) - const { t } = useTranslation() - - return useMutation( - async () => { - const [firstFarm, ...restFarm] = farms.data ?? [] - if (firstFarm == null) throw new Error("Missing farm") - - const firstStep: StepProps[] = [ - { - label: t("farms.modal.join.step", { number: 1 }), - state: "active", - }, - { - label: t("farms.modal.join.step", { number: 2 }), - state: "todo", - }, - ] - - const firstDeposit = await createTransaction( - { - tx: api.tx.omnipoolLiquidityMining.depositShares( - firstFarm.globalFarm.id, - firstFarm.yieldFarm.id, - positionId, - ), - }, - { - toast, - steps: restFarm.length ? firstStep : undefined, - onSubmitted: onClose, - onClose, - onBack: () => {}, - }, - ) - - for (const record of firstDeposit.events) { - if ( - api.events.omnipoolLiquidityMining.SharesDeposited.is(record.event) - ) { - const depositId = record.event.data.depositId - - const secondStep: StepProps[] = [ - { - label: t("farms.modal.join.step", { number: 1 }), - state: "done", - }, - { - label: t("farms.modal.join.step", { number: 2 }), - state: "active", - }, - ] - - const txs = restFarm.map((farm) => - api.tx.omnipoolLiquidityMining.redepositShares( - farm.globalFarm.id, - farm.yieldFarm.id, - depositId, - ), - ) - - if (txs.length > 0) { - await createTransaction( - { - tx: txs.length > 1 ? api.tx.utility.batch(txs) : txs[0], - }, - { toast, steps: secondStep }, - ) - } - } - } - }, - { onSuccess }, - ) -} diff --git a/src/utils/farms/deposit.tsx b/src/utils/farms/deposit.tsx new file mode 100644 index 000000000..cd819ec3d --- /dev/null +++ b/src/utils/farms/deposit.tsx @@ -0,0 +1,157 @@ +import { useMutation } from "@tanstack/react-query" +import { Farm } from "api/farms" +import { StepProps } from "components/Stepper/Stepper" +import { Trans, useTranslation } from "react-i18next" +import { ToastMessage, useStore } from "state/store" +import { useRpcProvider } from "providers/rpcProvider" +import { TOAST_MESSAGES } from "state/toasts" +import { scale } from "utils/balance" +import { useAccount } from "sections/web3-connect/Web3Connect.utils" +import { isEvmAccount } from "utils/evm" + +export type FarmDepositMutationType = ReturnType + +export const useFarmDepositMutation = ( + poolId: string, + positionId: string, + farms: Farm[], + onClose: () => void, + onSuccess: () => void, +) => { + const { createTransaction } = useStore() + const { api, assets } = useRpcProvider() + const { t } = useTranslation() + const { account } = useAccount() + const isEvm = isEvmAccount(account?.address) + + const meta = assets.getAsset(poolId) + const isXYK = assets.isShareToken(meta) + + return useMutation( + async ({ shares }: { shares: string }) => { + const [firstFarm, ...restFarm] = farms ?? [] + if (firstFarm == null) throw new Error("Missing farm") + + const toast = TOAST_MESSAGES.reduce((memo, type) => { + const msType = type === "onError" ? "onLoading" : type + memo[type] = ( + + + + + ) + return memo + }, {} as ToastMessage) + + const firstStep: StepProps[] = [ + { + label: t("farms.modal.join.step", { number: 1 }), + state: "active", + }, + { + label: t("farms.modal.join.step", { number: 2 }), + state: "todo", + }, + ] + + const firstDeposit = await createTransaction( + { + tx: isXYK + ? api.tx.xykLiquidityMining.depositShares( + firstFarm.globalFarm.id, + firstFarm.yieldFarm.id, + { assetIn: meta.assets[0], assetOut: meta.assets[1] }, + scale(shares, meta.decimals).toString(), + ) + : api.tx.omnipoolLiquidityMining.depositShares( + firstFarm.globalFarm.id, + firstFarm.yieldFarm.id, + positionId, + ), + }, + { + toast, + steps: restFarm.length ? firstStep : undefined, + onSubmitted: onClose, + onClose, + onBack: () => {}, + }, + ) + + const executeSecondMutation = async (depositId: string) => { + const secondStep: StepProps[] = [ + { + label: t("farms.modal.join.step", { number: 1 }), + state: "done", + }, + { + label: t("farms.modal.join.step", { number: 2 }), + state: "active", + }, + ] + + const txs = restFarm.map((farm) => + isXYK + ? api.tx.xykLiquidityMining.redepositShares( + farm.globalFarm.id, + farm.yieldFarm.id, + { assetIn: meta.assets[0], assetOut: meta.assets[1] }, + depositId, + ) + : api.tx.omnipoolLiquidityMining.redepositShares( + farm.globalFarm.id, + farm.yieldFarm.id, + depositId, + ), + ) + + if (txs.length > 0) { + await createTransaction( + { + tx: txs.length > 1 ? api.tx.utility.batch(txs) : txs[0], + }, + { toast, steps: secondStep }, + ) + } + } + + if (isEvm) { + const nftId = isXYK + ? await api.consts.xykLiquidityMining.nftCollectionId + : await api.consts.omnipoolLiquidityMining.nftCollectionId + + const positions = await api.query.uniques.account.entries( + account?.address, + nftId, + ) + + const depositId = positions + .map((position) => position[0].args[2].toNumber()) + .sort((a, b) => b - a)[0] + .toString() + + if (depositId) await executeSecondMutation(depositId) + } else { + for (const record of firstDeposit.events) { + if ( + api.events.omnipoolLiquidityMining.SharesDeposited.is( + record.event, + ) || + api.events.xykLiquidityMining.SharesDeposited.is(record.event) + ) { + const depositId = record.event.data.depositId.toString() + + await executeSecondMutation(depositId) + } + } + } + }, + { onSuccess }, + ) +} diff --git a/src/utils/farms/exit.ts b/src/utils/farms/exit.ts index 838190619..afddd251b 100644 --- a/src/utils/farms/exit.ts +++ b/src/utils/farms/exit.ts @@ -7,24 +7,34 @@ import { QUERY_KEYS } from "utils/queryKeys" export const useFarmExitAllMutation = ( depositNfts: TMiningNftPosition[], + poolId: string, toast: ToastMessage, onClose?: () => void, ) => { - const { api } = useRpcProvider() + const { api, assets } = useRpcProvider() const { createTransaction } = useStore() const { account } = useAccount() const queryClient = useQueryClient() + const meta = assets.getAsset(poolId) + const isXYK = assets.isShareToken(meta) + return useMutation( async () => { const txs = depositNfts ?.map((record) => { return record.data.yieldFarmEntries.map((entry) => { - return api.tx.omnipoolLiquidityMining.withdrawShares( - record.id, - entry.yieldFarmId, - ) + return isXYK + ? api.tx.xykLiquidityMining.withdrawShares( + record.id, + entry.yieldFarmId, + { assetIn: meta.assets[0], assetOut: meta.assets[1] }, + ) + : api.tx.omnipoolLiquidityMining.withdrawShares( + record.id, + entry.yieldFarmId, + ) }) }) .flat(2) ?? [] @@ -43,6 +53,9 @@ export const useFarmExitAllMutation = ( }, { onSuccess: () => { + queryClient.refetchQueries( + QUERY_KEYS.tokenBalance(meta.id, account?.address), + ) queryClient.refetchQueries( QUERY_KEYS.accountOmnipoolPositions(account?.address), ) diff --git a/src/utils/farms/redeposit.ts b/src/utils/farms/redeposit.ts deleted file mode 100644 index 63f63c5ff..000000000 --- a/src/utils/farms/redeposit.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { useMutation } from "@tanstack/react-query" -import { Farm } from "api/farms" -import { ToastMessage, useStore } from "state/store" -import { useRpcProvider } from "providers/rpcProvider" -import { TMiningNftPosition } from "sections/pools/PoolsPage.utils" - -export type FarmRedepositMutationType = ReturnType< - typeof useFarmRedepositMutation -> - -export const useFarmRedepositMutation = ( - availableYieldFarms: Farm[] | undefined, - depositNfts: TMiningNftPosition[], - toast: ToastMessage, - onClose?: () => void, -) => { - const { api } = useRpcProvider() - const { createTransaction } = useStore() - - return useMutation(async () => { - if (!availableYieldFarms?.length) - throw new Error("No available farms to redeposit into") - - const txs = depositNfts - .map((record) => { - return availableYieldFarms.map((farm) => { - return api.tx.omnipoolLiquidityMining.redepositShares( - farm.globalFarm.id, - farm.yieldFarm.id, - record.id, - ) - }) - }) - .flat(2) - - if (txs.length > 1) { - await createTransaction( - { tx: api.tx.utility.batchAll(txs) }, - { toast, onSubmitted: onClose, onBack: () => {}, onClose }, - ) - } else { - await createTransaction({ tx: txs[0] }, { toast, onSubmitted: onClose }) - } - }) -} diff --git a/src/utils/farms/redeposit.tsx b/src/utils/farms/redeposit.tsx new file mode 100644 index 000000000..5d4163101 --- /dev/null +++ b/src/utils/farms/redeposit.tsx @@ -0,0 +1,73 @@ +import { useMutation } from "@tanstack/react-query" +import { Farm } from "api/farms" +import { ToastMessage, useStore } from "state/store" +import { useRpcProvider } from "providers/rpcProvider" +import { TMiningNftPosition } from "sections/pools/PoolsPage.utils" +import { TOAST_MESSAGES } from "state/toasts" +import { Trans, useTranslation } from "react-i18next" + +export type FarmRedepositMutationType = ReturnType< + typeof useFarmRedepositMutation +> + +export const useFarmRedepositMutation = ( + availableYieldFarms: Farm[] | undefined, + depositNft: TMiningNftPosition, + poolId: string, + onClose?: () => void, +) => { + const { t } = useTranslation() + const { api, assets } = useRpcProvider() + const { createTransaction } = useStore() + + return useMutation(async ({ shares }: { shares: string }) => { + if (!availableYieldFarms?.length) + throw new Error("No available farms to redeposit into") + + const toast = TOAST_MESSAGES.reduce((memo, type) => { + const msType = type === "onError" ? "onLoading" : type + memo[type] = ( + + + + + ) + return memo + }, {} as ToastMessage) + + const meta = assets.getAsset(poolId) + const isXYK = assets.isShareToken(meta) + + const txs = availableYieldFarms + .map((farm) => { + return isXYK + ? api.tx.xykLiquidityMining.redepositShares( + farm.globalFarm.id, + farm.yieldFarm.id, + { assetIn: meta.assets[0], assetOut: meta.assets[1] }, + depositNft.id, + ) + : api.tx.omnipoolLiquidityMining.redepositShares( + farm.globalFarm.id, + farm.yieldFarm.id, + depositNft.id, + ) + }) + .flat(2) + + if (txs.length > 1) { + await createTransaction( + { tx: api.tx.utility.batchAll(txs) }, + { toast, onSubmitted: onClose, onBack: () => {}, onClose }, + ) + } else { + await createTransaction({ tx: txs[0] }, { toast, onSubmitted: onClose }) + } + }) +} diff --git a/src/utils/metamask.ts b/src/utils/metamask.ts index bfa0849c7..d7d112ad3 100644 --- a/src/utils/metamask.ts +++ b/src/utils/metamask.ts @@ -3,6 +3,7 @@ import { Maybe } from "utils/helpers" import type { ExternalProvider } from "@ethersproject/providers" import type EventEmitter from "events" import { evmChains } from "@galacticcouncil/xcm-sdk" +import UniversalProvider from "@walletconnect/universal-provider/dist/types/UniversalProvider" const METAMASK_LIKE_CHECKS = ["isTalisman"] as const type MetaMaskLikeChecksValues = (typeof METAMASK_LIKE_CHECKS)[number] @@ -63,6 +64,12 @@ export function isTalisman( return isMetaMaskLike(provider) && !!provider?.isTalisman } +export function isEthereumProvider( + provider: Maybe, +): provider is Required { + return typeof provider?.request === "function" +} + type RequestNetworkSwitchOptions = { onSwitch?: () => void chain?: keyof typeof evmChains @@ -71,7 +78,7 @@ export async function requestNetworkSwitch( provider: Maybe, options: RequestNetworkSwitchOptions = {}, ) { - if (!isMetaMask(provider)) return + if (!isEthereumProvider(provider)) return const params = getAddEvmChainParams(options.chain ?? "hydradx") @@ -83,8 +90,18 @@ export async function requestNetworkSwitch( }) .then(options?.onSwitch) } catch (error: any) { + let message: Record = {} + try { + message = + typeof error?.message === "string" ? JSON.parse(error.message) : {} + } catch (err) {} + + const errorCode = + message?.data?.originalError?.code || + error.data?.originalError?.code || + error?.code + // missing or unsupported network error - const errorCode = error.data?.originalError?.code || error?.code if (errorCode === 4902) { try { await provider @@ -94,6 +111,8 @@ export async function requestNetworkSwitch( }) .then(options?.onSwitch) } catch (err) {} + } else { + throw new Error(error) } } } diff --git a/src/utils/omnipool.ts b/src/utils/omnipool.ts index ea165d336..4df790b3a 100644 --- a/src/utils/omnipool.ts +++ b/src/utils/omnipool.ts @@ -3,7 +3,6 @@ import { calculate_liquidity_out, } from "@galacticcouncil/math-omnipool" import { u128 } from "@polkadot/types-codec" -import { PalletOmnipoolPosition } from "@polkadot/types/lookup" import { OmnipoolPosition } from "api/omnipool" import BN from "bignumber.js" import { BN_10, BN_NAN } from "utils/constants" @@ -18,7 +17,7 @@ export const calculatePositionLiquidity = ({ lrnaDecimals, assetDecimals, }: { - position: PalletOmnipoolPosition | OmnipoolPosition + position: Omit omnipoolBalance?: BN omnipoolHubReserve?: u128 omnipoolShares?: u128 diff --git a/src/utils/queryKeys.ts b/src/utils/queryKeys.ts index 194f33124..f24c02c4d 100644 --- a/src/utils/queryKeys.ts +++ b/src/utils/queryKeys.ts @@ -28,6 +28,7 @@ export const QUERY_KEYS = { id?.toString(), ], miningPosition: (id: string) => ["miningPosition", id], + miningPositionXYK: (id: string) => ["miningPositionXYK", id], accountBalances: (id: Maybe) => [ QUERY_KEY_PREFIX, "accountBalances", @@ -57,45 +58,34 @@ export const QUERY_KEYS = { address.toString(), ], deposit: (id: Maybe) => [QUERY_KEY_PREFIX, "deposit", id?.toString()], - allDeposits: [QUERY_KEY_PREFIX, "deposits"], - poolDeposits: (poolId: Maybe) => [ + allXYKDeposits: [QUERY_KEY_PREFIX, "allXYKDeposits"], + omnipoolDeposits: (ids: string[]) => [ QUERY_KEY_PREFIX, - "deposits", - poolId?.toString(), + "omnipoolDeposits", + ids.join("."), ], - accountDepositIds: (accountId: Maybe) => [ + xykDeposits: (ids: string[]) => [ QUERY_KEY_PREFIX, - "depositIds", - accountId?.toString(), + "xykDeposits", + ids.join("."), ], - globalFarms: (ids: Maybe<{ globalFarmId: u32 }[]>) => [ + poolDeposits: (poolId: Maybe) => [ QUERY_KEY_PREFIX, - "globalFarms", - ids?.map((i) => i.globalFarmId.toString()), + "deposits", + poolId?.toString(), ], - yieldFarms: ( - ids: Maybe< - { - poolId: u32 | string - globalFarmId: u32 | string - yieldFarmId: u32 | string - }[] - >, - ) => [QUERY_KEY_PREFIX, "yieldFarms", ids], activeYieldFarms: (poolId: Maybe) => [ "activeYieldFarms", poolId?.toString(), ], - globalFarm: (id: Maybe) => [ - QUERY_KEY_PREFIX, - "globalFarm", - id?.toString(), + activeYieldFarmsXYK: (poolId: Maybe) => [ + "activeYieldFarmsXYK", + poolId?.toString(), ], - yieldFarm: (ids: { - poolId: Maybe - globalFarmId: Maybe - yieldFarmId: Maybe - }) => [QUERY_KEY_PREFIX, "yieldFarm", ids], + globalFarm: (id: string) => [QUERY_KEY_PREFIX, "globalFarm", id], + globalFarmXYK: (id: string) => [QUERY_KEY_PREFIX, "globalFarmXYK", id], + yieldFarm: (id: string) => [QUERY_KEY_PREFIX, "yieldFarm", id], + yieldFarmXYK: (id: string) => [QUERY_KEY_PREFIX, "yieldFarmXYK", id], activeYieldFarm: (id: string) => [QUERY_KEY_PREFIX, "activeYieldFarm", id], totalLiquidity: (id: Maybe) => [ QUERY_KEY_PREFIX, diff --git a/yarn.lock b/yarn.lock index dc098b11d..3ae38578a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2020,9 +2020,9 @@ regenerator-runtime "^0.13.11" "@babel/runtime@^7.20.6": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.1.tgz#431f9a794d173b53720e69a6464abc6f0e2a5c57" - integrity sha512-+BIznRzyqBf+2wCTxcKE3wDjfGeCoVE61KSHGpkzqrLi8qxqFwBeUFyId2cxkTmm55fzDGnm0+yCxaxygrLUnQ== + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.4.tgz#de795accd698007a66ba44add6cc86542aff1edd" + integrity sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA== dependencies: regenerator-runtime "^0.14.0" @@ -2769,7 +2769,14 @@ resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.7.0.tgz#6ce9ae168e74fecf287be17062b590852c311892" integrity sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig== -"@ethersproject/networks@5.7.1", "@ethersproject/networks@^5.7.0": +"@ethersproject/networks@5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.7.0.tgz#df72a392f1a63a57f87210515695a31a245845ad" + integrity sha512-MG6oHSQHd4ebvJrleEQQ4HhVu8Ichr0RDYEfHzsVAVjHNM+w36x9wp9r+hf1JstMXtseXDtkiVoARAG6M959AA== + dependencies: + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/networks@^5.7.0": version "5.7.1" resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.7.1.tgz#118e1a981d757d45ccea6bb58d9fd3d9db14ead6" integrity sha512-n/MufjFYv3yFcUyfhnXotyDlNdFb7onmkSy8aQERi2PjNcnWQ66xXxa3XlS8nCcA8aJKJjIIMNJTC7tu80GwpQ== @@ -2791,7 +2798,33 @@ dependencies: "@ethersproject/logger" "^5.7.0" -"@ethersproject/providers@5.7.2", "@ethersproject/providers@^5.7.2": +"@ethersproject/providers@5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.7.0.tgz#a885cfc7650a64385e7b03ac86fe9c2d4a9c2c63" + integrity sha512-+TTrrINMzZ0aXtlwO/95uhAggKm4USLm1PbeCBR/3XZ7+Oey+3pMyddzZEyRhizHpy1HXV0FRWRMI1O3EGYibA== + dependencies: + "@ethersproject/abstract-provider" "^5.7.0" + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/base64" "^5.7.0" + "@ethersproject/basex" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/hash" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/networks" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/random" "^5.7.0" + "@ethersproject/rlp" "^5.7.0" + "@ethersproject/sha2" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + "@ethersproject/web" "^5.7.0" + bech32 "1.1.4" + ws "7.4.6" + +"@ethersproject/providers@^5.7.2": version "5.7.2" resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.7.2.tgz#f8b1a4f275d7ce58cf0a2eec222269a08beb18cb" integrity sha512-g34EWZ1WWAVgr4aptGlVBF8mhl3VWjv+8hoAnzStu8Ah22VHBsuGzP17eb6xDVRzw895G4W7vvx60lFFur/1Rg== @@ -2920,7 +2953,18 @@ "@ethersproject/transactions" "^5.7.0" "@ethersproject/wordlists" "^5.7.0" -"@ethersproject/web@5.7.1", "@ethersproject/web@^5.7.0": +"@ethersproject/web@5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/web/-/web-5.7.0.tgz#40850c05260edad8b54827923bbad23d96aac0bc" + integrity sha512-ApHcbbj+muRASVDSCl/tgxaH2LBkRMEYfLOLVa0COipx0+nlu0QKet7U2lEg0vdkh8XRSLf2nd1f1Uk9SrVSGA== + dependencies: + "@ethersproject/base64" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + +"@ethersproject/web@^5.7.0": version "5.7.1" resolved "https://registry.yarnpkg.com/@ethersproject/web/-/web-5.7.1.tgz#de1f285b373149bee5928f4eb7bcb87ee5fbb4ae" integrity sha512-Gueu8lSvyjBWL4cYsWsjh6MtMwM0+H4HvqFPZfB6dV8ctbP9zFAO73VG1cMWae0FLPCtz0peKPpZY8/ugJJX2w== @@ -2947,6 +2991,13 @@ resolved "https://registry.yarnpkg.com/@fal-works/esbuild-plugin-global-externals/-/esbuild-plugin-global-externals-2.1.2.tgz#c05ed35ad82df8e6ac616c68b92c2282bd083ba4" integrity sha512-cEee/Z+I12mZcFJshKcCqC8tuX5hG3s+d+9nZ3LabqKF1vKdF41B92pJVCBggjAGORAeOzyyDDKrZwIkLffeOQ== +"@floating-ui/core@^1.0.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.0.tgz#fa41b87812a16bf123122bf945946bae3fdf7fc1" + integrity sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g== + dependencies: + "@floating-ui/utils" "^0.2.1" + "@floating-ui/core@^1.4.1": version "1.4.1" resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.4.1.tgz#0d633f4b76052668afb932492ac452f7ebe97f17" @@ -2954,7 +3005,7 @@ dependencies: "@floating-ui/utils" "^0.1.1" -"@floating-ui/dom@^1.3.0", "@floating-ui/dom@^1.5.1": +"@floating-ui/dom@^1.3.0": version "1.5.1" resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.5.1.tgz#88b70defd002fe851f17b4a25efb2d3c04d7a8d7" integrity sha512-KwvVcPSXg6mQygvA1TjbN/gh///36kKtllIF8SUm0qpFj8+rvYrpvlYdL1JoA71SHpDqgSSdGOSoQ0Mp3uY5aw== @@ -2962,6 +3013,14 @@ "@floating-ui/core" "^1.4.1" "@floating-ui/utils" "^0.1.1" +"@floating-ui/dom@^1.5.1": + version "1.6.3" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.3.tgz#954e46c1dd3ad48e49db9ada7218b0985cee75ef" + integrity sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw== + dependencies: + "@floating-ui/core" "^1.0.0" + "@floating-ui/utils" "^0.2.0" + "@floating-ui/react-dom@^2.0.0": version "2.0.1" resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.0.1.tgz#7972a4fc488a8c746cded3cfe603b6057c308a91" @@ -2974,6 +3033,11 @@ resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.1.1.tgz#1a5b1959a528e374e8037c4396c3e825d6cf4a83" integrity sha512-m0G6wlnhm/AX0H12IOWtK8gASEMffnX08RtKkCgTdHb9JpHKGloI7icFfLg9ZmQeavcvR0PKmzxClyuFPSjKWw== +"@floating-ui/utils@^0.2.0", "@floating-ui/utils@^0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.1.tgz#16308cea045f0fc777b6ff20a9f25474dd8293d2" + integrity sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q== + "@fragnova/api-augment@0.1.0-spec-1.0.4-mainnet": version "0.1.0-spec-1.0.4-mainnet" resolved "https://registry.yarnpkg.com/@fragnova/api-augment/-/api-augment-0.1.0-spec-1.0.4-mainnet.tgz#4244b59a5e3b5809aa95e74d8c77a2ca86056292" @@ -2997,10 +3061,10 @@ resolved "https://registry.yarnpkg.com/@galacticcouncil/api-augment/-/api-augment-0.0.6.tgz#fc54c04c3bb953db3f739ea2c5820153088d564d" integrity sha512-5Qq+dzRoyuMS6uUXjl1asRrosIQ1n0vD0dzbxK8H8f3hmIzDdpiQVqlqWBLEc7D/PA7VM/7j1scICpdjtJJELw== -"@galacticcouncil/apps@3.4.11": - version "3.4.11" - resolved "https://registry.yarnpkg.com/@galacticcouncil/apps/-/apps-3.4.11.tgz#40e8874e5bffae9e7123cf338f7871bf6f1b7148" - integrity sha512-fftkcTMtIitB30OcN198HuaLw/iMzVTzZr9hPY+hujSGgoeqUksTc5pIZx+ootPtBmORQfswIFCRX+GgdH20hA== +"@galacticcouncil/apps@^3.5.4": + version "3.5.4" + resolved "https://registry.yarnpkg.com/@galacticcouncil/apps/-/apps-3.5.4.tgz#353c8dbe1a92e1389a025315fdea70b2b15cb780" + integrity sha512-oFw4mKpIuLBMB5PAvwk5Vi+y1xR0fN4Ok+PLzqK92G8eb8Tkp06EQMlp7rCunysZ2a995kfbGWzfjkUOIbUReg== dependencies: "@cfx-kit/wallet-avatar" "0.0.5" "@thi.ng/atom" "^5.1.3" @@ -3043,10 +3107,10 @@ resolved "https://registry.yarnpkg.com/@galacticcouncil/math-xyk/-/math-xyk-0.2.0.tgz#74feacdf94d4846f1a148835f25936b2ddc7bdfd" integrity sha512-mznedOeA8d9BQ8kUQ/4O/P6trqTC4FGwvTDGCHJ0O/bgA9iFb+suctbFtyFT3qfNnngR7FSF2Pls1O/SvtNn8g== -"@galacticcouncil/sdk@^2.1.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@galacticcouncil/sdk/-/sdk-2.1.0.tgz#44280daa444b8f0c1d45cda579449d19e20dd2ac" - integrity sha512-JIyym2q7UlEOZxoMtNTocW3Mxgg5vRqYdPGQVk4uYDTC5883635iGnW5COcbaxMwOteR/reKbswv49qyTsgKdw== +"@galacticcouncil/sdk@^2.2.3": + version "2.2.3" + resolved "https://registry.yarnpkg.com/@galacticcouncil/sdk/-/sdk-2.2.3.tgz#4610aadfc7839fd5b1f392bbec0942666f58838e" + integrity sha512-+7YlKh7R/O00vs5KyO//JA0MgWYFLnlzicTFlGC4BsazqKFImQ0px975PyWhjSwSeo9BtpVT5R86gFrrvoKY4A== dependencies: "@galacticcouncil/math-lbp" "^0.2.1" "@galacticcouncil/math-omnipool" "^0.2.0" @@ -3056,10 +3120,10 @@ bignumber.js "^9.1.0" lodash.clonedeep "^4.5.0" -"@galacticcouncil/ui@^3.1.5": - version "3.1.5" - resolved "https://registry.yarnpkg.com/@galacticcouncil/ui/-/ui-3.1.5.tgz#2450b424d51239e2cd89b32da4981e10b25478c0" - integrity sha512-qPgukXwa6fFa7oyzQWBUFTUrgOUlrvYIU+JgA+UB5pMpjAIIivrJpL4NDo8pMpUdtpm7QEizzWbBDsqMogdqEQ== +"@galacticcouncil/ui@^3.1.14": + version "3.1.14" + resolved "https://registry.yarnpkg.com/@galacticcouncil/ui/-/ui-3.1.14.tgz#09bf5cb8abb47d16fd7e4a75b7221e4c0e08c249" + integrity sha512-D580RMrfsLJO+68SewCSacDOUfVoM8BfyElw/bLqlcD46fUMhETghpLgIfuRIOEYyK7Y5s0qIBJOxaKdBKRwGw== dependencies: "@floating-ui/dom" "^1.5.1" "@lit/reactive-element" "^1.0.0" @@ -3067,10 +3131,10 @@ lit "^2.5.0" ts-debounce "^4.0.0" -"@galacticcouncil/xcm-cfg@^1.11.3": - version "1.11.3" - resolved "https://registry.yarnpkg.com/@galacticcouncil/xcm-cfg/-/xcm-cfg-1.11.3.tgz#899b651ec750f2effa9f58349add8ecc17b258b8" - integrity sha512-RFP2ZjZh6cMBjhXsvAiEn7oC42sKujcvWuEJIvoJx5j4P28f+n6Xbx/anCDUUmjc4KKdZ6Q+5tcZ79wQi90vzA== +"@galacticcouncil/xcm-cfg@^1.11.7": + version "1.11.7" + resolved "https://registry.yarnpkg.com/@galacticcouncil/xcm-cfg/-/xcm-cfg-1.11.7.tgz#46cb3d2064879a9f67696187571373232199935c" + integrity sha512-2s40nBxMCY6Z9Iw31e+rJk50bnTKC7zpd/TfrT8AKtw0SHfqhf1mpxGWyOwvafk6Suvw1sAgQd353Uw3KMi04g== "@galacticcouncil/xcm-sdk@^2.4.0": version "2.4.0" @@ -3272,31 +3336,6 @@ "@jridgewell/resolve-uri" "3.1.0" "@jridgewell/sourcemap-codec" "1.4.14" -"@json-rpc-tools/provider@^1.5.5": - version "1.7.6" - resolved "https://registry.yarnpkg.com/@json-rpc-tools/provider/-/provider-1.7.6.tgz#8a17c34c493fa892632e278fd9331104e8491ec6" - integrity sha512-z7D3xvJ33UfCGv77n40lbzOYjZKVM3k2+5cV7xS8G6SCvKTzMkhkUYuD/qzQUNT4cG/lv0e9mRToweEEVLVVmA== - dependencies: - "@json-rpc-tools/utils" "^1.7.6" - axios "^0.21.0" - safe-json-utils "^1.1.1" - ws "^7.4.0" - -"@json-rpc-tools/types@^1.7.6": - version "1.7.6" - resolved "https://registry.yarnpkg.com/@json-rpc-tools/types/-/types-1.7.6.tgz#5abd5fde01364a130c46093b501715bcce5bdc0e" - integrity sha512-nDSqmyRNEqEK9TZHtM15uNnDljczhCUdBmRhpNZ95bIPKEDQ+nTDmGMFd2lLin3upc5h2VVVd9tkTDdbXUhDIQ== - dependencies: - keyvaluestorage-interface "^1.0.0" - -"@json-rpc-tools/utils@^1.7.6": - version "1.7.6" - resolved "https://registry.yarnpkg.com/@json-rpc-tools/utils/-/utils-1.7.6.tgz#67f04987dbaa2e7adb6adff1575367b75a9a9ba1" - integrity sha512-HjA8x/U/Q78HRRe19yh8HVKoZ+Iaoo3YZjakJYxR+rw52NHo6jM+VE9b8+7ygkCFXl/EHID5wh/MkXaE/jGyYw== - dependencies: - "@json-rpc-tools/types" "^1.7.6" - "@pedrouid/environment" "^1.0.1" - "@juggle/resize-observer@^3.3.1": version "3.4.0" resolved "https://registry.yarnpkg.com/@juggle/resize-observer/-/resize-observer-3.4.0.tgz#08d6c5e20cf7e4cc02fd181c4b0c225cd31dbb60" @@ -3568,6 +3607,98 @@ dependencies: "@open-web3/orml-type-definitions" "^2.0.1" +"@parcel/watcher-android-arm64@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.4.1.tgz#c2c19a3c442313ff007d2d7a9c2c1dd3e1c9ca84" + integrity sha512-LOi/WTbbh3aTn2RYddrO8pnapixAziFl6SMxHM69r3tvdSm94JtCenaKgk1GRg5FJ5wpMCpHeW+7yqPlvZv7kg== + +"@parcel/watcher-darwin-arm64@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.4.1.tgz#c817c7a3b4f3a79c1535bfe54a1c2818d9ffdc34" + integrity sha512-ln41eihm5YXIY043vBrrHfn94SIBlqOWmoROhsMVTSXGh0QahKGy77tfEywQ7v3NywyxBBkGIfrWRHm0hsKtzA== + +"@parcel/watcher-darwin-x64@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.4.1.tgz#1a3f69d9323eae4f1c61a5f480a59c478d2cb020" + integrity sha512-yrw81BRLjjtHyDu7J61oPuSoeYWR3lDElcPGJyOvIXmor6DEo7/G2u1o7I38cwlcoBHQFULqF6nesIX3tsEXMg== + +"@parcel/watcher-freebsd-x64@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.4.1.tgz#0d67fef1609f90ba6a8a662bc76a55fc93706fc8" + integrity sha512-TJa3Pex/gX3CWIx/Co8k+ykNdDCLx+TuZj3f3h7eOjgpdKM+Mnix37RYsYU4LHhiYJz3DK5nFCCra81p6g050w== + +"@parcel/watcher-linux-arm-glibc@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.4.1.tgz#ce5b340da5829b8e546bd00f752ae5292e1c702d" + integrity sha512-4rVYDlsMEYfa537BRXxJ5UF4ddNwnr2/1O4MHM5PjI9cvV2qymvhwZSFgXqbS8YoTk5i/JR0L0JDs69BUn45YA== + +"@parcel/watcher-linux-arm64-glibc@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.4.1.tgz#6d7c00dde6d40608f9554e73998db11b2b1ff7c7" + integrity sha512-BJ7mH985OADVLpbrzCLgrJ3TOpiZggE9FMblfO65PlOCdG++xJpKUJ0Aol74ZUIYfb8WsRlUdgrZxKkz3zXWYA== + +"@parcel/watcher-linux-arm64-musl@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.4.1.tgz#bd39bc71015f08a4a31a47cd89c236b9d6a7f635" + integrity sha512-p4Xb7JGq3MLgAfYhslU2SjoV9G0kI0Xry0kuxeG/41UfpjHGOhv7UoUDAz/jb1u2elbhazy4rRBL8PegPJFBhA== + +"@parcel/watcher-linux-x64-glibc@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.4.1.tgz#0ce29966b082fb6cdd3de44f2f74057eef2c9e39" + integrity sha512-s9O3fByZ/2pyYDPoLM6zt92yu6P4E39a03zvO0qCHOTjxmt3GHRMLuRZEWhWLASTMSrrnVNWdVI/+pUElJBBBg== + +"@parcel/watcher-linux-x64-musl@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.4.1.tgz#d2ebbf60e407170bb647cd6e447f4f2bab19ad16" + integrity sha512-L2nZTYR1myLNST0O632g0Dx9LyMNHrn6TOt76sYxWLdff3cB22/GZX2UPtJnaqQPdCRoszoY5rcOj4oMTtp5fQ== + +"@parcel/watcher-wasm@^2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-wasm/-/watcher-wasm-2.4.1.tgz#c4353e4fdb96ee14389856f7f6f6d21b7dcef9e1" + integrity sha512-/ZR0RxqxU/xxDGzbzosMjh4W6NdYFMqq2nvo2b8SLi7rsl/4jkL8S5stIikorNkdR50oVDvqb/3JT05WM+CRRA== + dependencies: + is-glob "^4.0.3" + micromatch "^4.0.5" + napi-wasm "^1.1.0" + +"@parcel/watcher-win32-arm64@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.4.1.tgz#eb4deef37e80f0b5e2f215dd6d7a6d40a85f8adc" + integrity sha512-Uq2BPp5GWhrq/lcuItCHoqxjULU1QYEcyjSO5jqqOK8RNFDBQnenMMx4gAl3v8GiWa59E9+uDM7yZ6LxwUIfRg== + +"@parcel/watcher-win32-ia32@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.4.1.tgz#94fbd4b497be39fd5c8c71ba05436927842c9df7" + integrity sha512-maNRit5QQV2kgHFSYwftmPBxiuK5u4DXjbXx7q6eKjq5dsLXZ4FJiVvlcw35QXzk0KrUecJmuVFbj4uV9oYrcw== + +"@parcel/watcher-win32-x64@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.4.1.tgz#4bf920912f67cae5f2d264f58df81abfea68dadf" + integrity sha512-+DvS92F9ezicfswqrvIRM2njcYJbd5mb9CUgtrHCHmvn7pPPa+nMDRu1o1bYYz/l5IB2NVGNJWiH7h1E58IF2A== + +"@parcel/watcher@^2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-2.4.1.tgz#a50275151a1bb110879c6123589dba90c19f1bf8" + integrity sha512-HNjmfLQEVRZmHRET336f20H/8kOozUGwk7yajvsonjNxbj2wBTK1WsQuHkD5yYh9RxFGL2EyDHryOihOwUoKDA== + dependencies: + detect-libc "^1.0.3" + is-glob "^4.0.3" + micromatch "^4.0.5" + node-addon-api "^7.0.0" + optionalDependencies: + "@parcel/watcher-android-arm64" "2.4.1" + "@parcel/watcher-darwin-arm64" "2.4.1" + "@parcel/watcher-darwin-x64" "2.4.1" + "@parcel/watcher-freebsd-x64" "2.4.1" + "@parcel/watcher-linux-arm-glibc" "2.4.1" + "@parcel/watcher-linux-arm64-glibc" "2.4.1" + "@parcel/watcher-linux-arm64-musl" "2.4.1" + "@parcel/watcher-linux-x64-glibc" "2.4.1" + "@parcel/watcher-linux-x64-musl" "2.4.1" + "@parcel/watcher-win32-arm64" "2.4.1" + "@parcel/watcher-win32-ia32" "2.4.1" + "@parcel/watcher-win32-x64" "2.4.1" + "@peaqnetwork/type-definitions@0.0.4": version "0.0.4" resolved "https://registry.yarnpkg.com/@peaqnetwork/type-definitions/-/type-definitions-0.0.4.tgz#a893ff95bf824d13c902d3b5912b2954fc12e1e6" @@ -3575,11 +3706,6 @@ dependencies: "@open-web3/orml-type-definitions" "^0.9.4-38" -"@pedrouid/environment@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@pedrouid/environment/-/environment-1.0.1.tgz#858f0f8a057340e0b250398b75ead77d6f4342ec" - integrity sha512-HaW78NszGzRZd9SeoI3JD11JqY+lubnaOx7Pewj5pfjqWXOEATpeKIFb9Z4t2WBUK2iryiXX3lzWwmYWgUL0Ug== - "@phala/typedefs@0.2.33": version "0.2.33" resolved "https://registry.yarnpkg.com/@phala/typedefs/-/typedefs-0.2.33.tgz#6f18d73b5104db6a594d08be571954385b3e509b" @@ -4647,9 +4773,9 @@ integrity sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA== "@scure/base@~1.1.0", "@scure/base@~1.1.2": - version "1.1.5" - resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.5.tgz#1d85d17269fe97694b9c592552dd9e5e33552157" - integrity sha512-Brj9FiG2W1MRQSTB212YVPRrcbjkv48FoZi/u4l/zds/ieRrqsh7aUf6CLwkAq61oKXr/ZlTzlY66gLIj3TFTQ== + version "1.1.6" + resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.6.tgz#8ce5d304b436e4c84f896e0550c83e4d88cb917d" + integrity sha512-ok9AWwhcgYuGG3Zfhyqg+zwl+Wn5uE+dwC0NV/2qQkx4dABbb/bx96vWu8NSj+BNjjSjno+JRYRjle1jV08k3g== "@scure/bip32@1.3.2": version "1.3.2" @@ -6049,6 +6175,11 @@ resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.4.3.tgz#af975e367743fa91989cd666666aec31a8f50591" integrity sha512-kCUc5MEwaEMakkO5x7aoD+DLi02ehmEM2QCGWvNqAS1dV/fAvORWEjnjsEIvml59M7Y5kCkWN6fCCyPOe8OL6Q== +"@thi.ng/api@^8.11.0": + version "8.11.0" + resolved "https://registry.yarnpkg.com/@thi.ng/api/-/api-8.11.0.tgz#8f3549bfe8945d2bc35991aaae97c12cef82ee1f" + integrity sha512-Z9n2WzIXjByfMz1jefFm2/6+zkmZeoQzg/HTkHZZe2ZPRjIuF/KfQUJwLllGBCOGz1dSQg/VVIa15N0aiq1Yug== + "@thi.ng/api@^8.8.0": version "8.8.0" resolved "https://registry.yarnpkg.com/@thi.ng/api/-/api-8.8.0.tgz#b84c95b9324be03c0b5b433cba8235c664fd9f21" @@ -6067,15 +6198,15 @@ "@thi.ng/random" "^3.4.0" "@thi.ng/atom@^5.1.3": - version "5.2.1" - resolved "https://registry.yarnpkg.com/@thi.ng/atom/-/atom-5.2.1.tgz#a568f50a63f936e8239c90b371dae95faf3e71a3" - integrity sha512-/nGIZDvPXoVehdauQr9gdgT1US9l4BHIS4pnRzziiCGgxr4gBOcmp70EK816iD64l25nbLOHyWYMM/A5zRV8/Q== - dependencies: - "@thi.ng/api" "^8.8.0" - "@thi.ng/equiv" "^2.1.22" - "@thi.ng/errors" "^2.2.15" - "@thi.ng/paths" "^5.1.36" - tslib "^2.5.0" + version "5.2.44" + resolved "https://registry.yarnpkg.com/@thi.ng/atom/-/atom-5.2.44.tgz#c817555f74433f55e7ae3f0b57828e0aa70efec6" + integrity sha512-gVMzRA0Y+96O8jz7Wwbc4bMu/zHj7QlRn2eU8VAJh0JSrC9cNiuuJ0Zt60cEbNwCxbfvXdEh87Poh/QKHZSsIg== + dependencies: + "@thi.ng/api" "^8.11.0" + "@thi.ng/equiv" "^2.1.56" + "@thi.ng/errors" "^2.5.5" + "@thi.ng/paths" "^5.1.79" + tslib "^2.6.2" "@thi.ng/cache@^2.1.35": version "2.1.47" @@ -6093,6 +6224,13 @@ dependencies: tslib "^2.5.0" +"@thi.ng/checks@^3.6.2": + version "3.6.2" + resolved "https://registry.yarnpkg.com/@thi.ng/checks/-/checks-3.6.2.tgz#210b9efca632a05f86d0b7412f1301f213500234" + integrity sha512-Ig007X8h09mnOJq5AzcJTFbvoDvqjHtVP+zvJF8RE/PZWD1XdpsQbFkqlqvZ4mCLOUlglje6cjpZXMXiNEiKbQ== + dependencies: + tslib "^2.6.2" + "@thi.ng/compare@^2.1.30": version "2.1.30" resolved "https://registry.yarnpkg.com/@thi.ng/compare/-/compare-2.1.30.tgz#b8242a9efd959f6ae931cd1ff2eaf955b61dd9dd" @@ -6126,11 +6264,21 @@ resolved "https://registry.yarnpkg.com/@thi.ng/equiv/-/equiv-2.1.22.tgz#2d65de478b33233e731008aefab40c66228893d8" integrity sha512-Cx+9NGFO7nFGOyalqxpq9tW15NyuZ0xitEVEikUx1a9GOA1RBragjuAPs7D77l6HlVswroAyka1Q9D0jqm6BCQ== +"@thi.ng/equiv@^2.1.56": + version "2.1.56" + resolved "https://registry.yarnpkg.com/@thi.ng/equiv/-/equiv-2.1.56.tgz#bfe1e0e1426bb982400f80d165fb60c87fe988e1" + integrity sha512-ALgAu9Mkyuc7lI5mDFxNec0w2fK470pUpoBrcN+KpQibBfNKBJhKqdepaKBIZTrFRLeROHXTe83ZK9NKwfTHDw== + "@thi.ng/errors@^2.2.15": version "2.2.15" resolved "https://registry.yarnpkg.com/@thi.ng/errors/-/errors-2.2.15.tgz#626349585a1641aa9c6c46dbcad8da0f1829ced6" integrity sha512-zyaHmu4Sqpkl5t5tSQLfZPMzfV+pM29EbONXTRNW/8BnTE++iiHwUwFU0rHbeTMPmH0BMp5g4wNG8K+A5xewsw== +"@thi.ng/errors@^2.5.5": + version "2.5.5" + resolved "https://registry.yarnpkg.com/@thi.ng/errors/-/errors-2.5.5.tgz#adb6eba9b10cdce9b8778afff3f8ec900611b881" + integrity sha512-vxanfQH/dZq9JCTmnhdQuAfVCgChDJ3FdLum3CIcKQm5RHbp7HPU618lTVI4Odzz0lWeNHHTc3EY1nFuFAIIUA== + "@thi.ng/hex@^2.3.9": version "2.3.9" resolved "https://registry.yarnpkg.com/@thi.ng/hex/-/hex-2.3.9.tgz#1b56d34fe5b2d0a8cb465b5f7d8a4b17ed1d2d89" @@ -6143,14 +6291,14 @@ dependencies: "@thi.ng/api" "^8.8.0" -"@thi.ng/paths@^5.1.36": - version "5.1.36" - resolved "https://registry.yarnpkg.com/@thi.ng/paths/-/paths-5.1.36.tgz#a4f22b956e78d88aa20b53092c6cf157c7fc5d6f" - integrity sha512-IVH+UUp5yp6p3aCcl5JYivRvSiHLBZ40gEcw1hqNkXanD4xDQZuo+y7iZY7e1gF/bbQhDMmMV6+POEdfxim59Q== +"@thi.ng/paths@^5.1.79": + version "5.1.79" + resolved "https://registry.yarnpkg.com/@thi.ng/paths/-/paths-5.1.79.tgz#d5ed55edc3db4e2e29a49db48016f103b88447ff" + integrity sha512-WzWkR/j/sKbjLw0v1mnX6SuVivCmMNrAsifi31ZK93QgZU8pgGUI15mVV21mYtbfseGOqgWXovfmKjmsBdqbRA== dependencies: - "@thi.ng/api" "^8.8.0" - "@thi.ng/checks" "^3.3.12" - "@thi.ng/errors" "^2.2.15" + "@thi.ng/api" "^8.11.0" + "@thi.ng/checks" "^3.6.2" + "@thi.ng/errors" "^2.5.5" "@thi.ng/random@^3.4.0": version "3.4.0" @@ -6873,25 +7021,26 @@ loupe "^2.3.6" pretty-format "^29.5.0" -"@walletconnect/core@2.8.0": - version "2.8.0" - resolved "https://registry.yarnpkg.com/@walletconnect/core/-/core-2.8.0.tgz#f694e1562413c4eb700f6b3a83fa7964342100c0" - integrity sha512-pl7x4sq1nuU0ixA9wF2ecjDecUzIauKr7ZwC29rs9qTcmDpxgJbbOdZwaSl+dJlf1bHC87adVLf5KAkwwo9PzQ== +"@walletconnect/core@2.12.2": + version "2.12.2" + resolved "https://registry.yarnpkg.com/@walletconnect/core/-/core-2.12.2.tgz#12bd568b90daed876e58ebcc098c12843a3321e6" + integrity sha512-7Adv/b3pp9F42BkvReaaM4KS8NEvlkS7AMtwO3uF/o6aRMKtcfTJq9/jgWdKJh4RP8pPRTRFjCw6XQ/RZtT4aQ== dependencies: "@walletconnect/heartbeat" "1.2.1" "@walletconnect/jsonrpc-provider" "1.0.13" "@walletconnect/jsonrpc-types" "1.0.3" "@walletconnect/jsonrpc-utils" "1.0.8" - "@walletconnect/jsonrpc-ws-connection" "^1.0.11" - "@walletconnect/keyvaluestorage" "^1.0.2" - "@walletconnect/logger" "^2.0.1" + "@walletconnect/jsonrpc-ws-connection" "1.0.14" + "@walletconnect/keyvaluestorage" "^1.1.1" + "@walletconnect/logger" "^2.1.2" "@walletconnect/relay-api" "^1.0.9" "@walletconnect/relay-auth" "^1.0.4" "@walletconnect/safe-json" "^1.0.2" "@walletconnect/time" "^1.0.2" - "@walletconnect/types" "2.8.0" - "@walletconnect/utils" "2.8.0" + "@walletconnect/types" "2.12.2" + "@walletconnect/utils" "2.12.2" events "^3.3.0" + isomorphic-unfetch "3.1.0" lodash.isequal "4.5.0" uint8arrays "^3.1.0" @@ -6955,24 +7104,24 @@ "@walletconnect/jsonrpc-types" "^1.0.3" tslib "1.14.1" -"@walletconnect/jsonrpc-ws-connection@^1.0.11": - version "1.0.13" - resolved "https://registry.yarnpkg.com/@walletconnect/jsonrpc-ws-connection/-/jsonrpc-ws-connection-1.0.13.tgz#23b0cdd899801bfbb44a6556936ec2b93ef2adf4" - integrity sha512-mfOM7uFH4lGtQxG+XklYuFBj6dwVvseTt5/ahOkkmpcAEgz2umuzu7fTR+h5EmjQBdrmYyEBOWADbeaFNxdySg== +"@walletconnect/jsonrpc-ws-connection@1.0.14": + version "1.0.14" + resolved "https://registry.yarnpkg.com/@walletconnect/jsonrpc-ws-connection/-/jsonrpc-ws-connection-1.0.14.tgz#eec700e74766c7887de2bd76c91a0206628732aa" + integrity sha512-Jsl6fC55AYcbkNVkwNM6Jo+ufsuCQRqViOQ8ZBPH9pRREHH9welbBiszuTLqEJiQcO/6XfFDl6bzCJIkrEi8XA== dependencies: "@walletconnect/jsonrpc-utils" "^1.0.6" "@walletconnect/safe-json" "^1.0.2" events "^3.3.0" - tslib "1.14.1" ws "^7.5.1" -"@walletconnect/keyvaluestorage@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@walletconnect/keyvaluestorage/-/keyvaluestorage-1.0.2.tgz#92f5ca0f54c1a88a093778842ce0c874d86369c8" - integrity sha512-U/nNG+VLWoPFdwwKx0oliT4ziKQCEoQ27L5Hhw8YOFGA2Po9A9pULUYNWhDgHkrb0gYDNt//X7wABcEWWBd3FQ== +"@walletconnect/keyvaluestorage@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@walletconnect/keyvaluestorage/-/keyvaluestorage-1.1.1.tgz#dd2caddabfbaf80f6b8993a0704d8b83115a1842" + integrity sha512-V7ZQq2+mSxAq7MrRqDxanTzu2RcElfK1PfNYiaVnJgJ7Q7G7hTVwF8voIBx92qsRyGHZihrwNPHuZd1aKkd0rA== dependencies: - safe-json-utils "^1.1.1" - tslib "1.14.1" + "@walletconnect/safe-json" "^1.0.1" + idb-keyval "^6.2.1" + unstorage "^1.9.0" "@walletconnect/logger@^2.0.1": version "2.0.1" @@ -6982,6 +7131,14 @@ pino "7.11.0" tslib "1.14.1" +"@walletconnect/logger@^2.1.2": + version "2.1.2" + resolved "https://registry.yarnpkg.com/@walletconnect/logger/-/logger-2.1.2.tgz#813c9af61b96323a99f16c10089bfeb525e2a272" + integrity sha512-aAb28I3S6pYXZHQm5ESB+V6rDqIYfsnHaQyzFbwUUBFY4H0OXx/YtTl8lvhUNhMMfb9UxbwEBS253TlXUYJWSw== + dependencies: + "@walletconnect/safe-json" "^1.0.2" + pino "7.11.0" + "@walletconnect/modal-core@2.6.2": version "2.6.2" resolved "https://registry.yarnpkg.com/@walletconnect/modal-core/-/modal-core-2.6.2.tgz#d73e45d96668764e0c8668ea07a45bb8b81119e9" @@ -6999,7 +7156,7 @@ motion "10.16.2" qrcode "1.5.3" -"@walletconnect/modal@^2.6.2": +"@walletconnect/modal@2.6.2": version "2.6.2" resolved "https://registry.yarnpkg.com/@walletconnect/modal/-/modal-2.6.2.tgz#4b534a836f5039eeb3268b80be7217a94dd12651" integrity sha512-eFopgKi8AjKf/0U4SemvcYw9zlLpx9njVN8sf6DAkowC2Md0gPU/UNEbH1Wwj407pEKnEds98pKWib1NN1ACoA== @@ -7034,19 +7191,19 @@ dependencies: tslib "1.14.1" -"@walletconnect/sign-client@2.8.0": - version "2.8.0" - resolved "https://registry.yarnpkg.com/@walletconnect/sign-client/-/sign-client-2.8.0.tgz#735dc8bf120242584fb2ff22c6a3d672c1fae1a1" - integrity sha512-+l9qwvVeUGk0fBQsgx6yb6hdGYt8uQ3a9jR9GgsJvm8FjFh1oUzTKqFnG7XdhCBnzFnbSoLr41Xe8PbN8qoUSw== +"@walletconnect/sign-client@2.12.2": + version "2.12.2" + resolved "https://registry.yarnpkg.com/@walletconnect/sign-client/-/sign-client-2.12.2.tgz#10cddcba3740f726149c33ef1a9040a808d65e08" + integrity sha512-cM0ualXj6nVvLqS4BDNRk+ZWR+lubcsz/IHreH+3wYrQ2sV+C0fN6ctrd7MMGZss0C0qacWCx0pm62ZBuoKvqA== dependencies: - "@walletconnect/core" "2.8.0" + "@walletconnect/core" "2.12.2" "@walletconnect/events" "^1.0.1" "@walletconnect/heartbeat" "1.2.1" "@walletconnect/jsonrpc-utils" "1.0.8" - "@walletconnect/logger" "^2.0.1" + "@walletconnect/logger" "^2.1.2" "@walletconnect/time" "^1.0.2" - "@walletconnect/types" "2.8.0" - "@walletconnect/utils" "2.8.0" + "@walletconnect/types" "2.12.2" + "@walletconnect/utils" "2.12.2" events "^3.3.0" "@walletconnect/time@^1.0.2": @@ -7056,38 +7213,37 @@ dependencies: tslib "1.14.1" -"@walletconnect/types@2.8.0", "@walletconnect/types@^2.8.0": - version "2.8.0" - resolved "https://registry.yarnpkg.com/@walletconnect/types/-/types-2.8.0.tgz#f8a5f09ee2b31abed231966e7e1eebd22be058a2" - integrity sha512-FMeGK3lGXFDwcs5duoN74xL1aLrkgYqnavWE0DnFPt2i1QmSUITU9c8f88EDh8uPXANd2WIYOItm0DVCNxLGGA== +"@walletconnect/types@2.12.2": + version "2.12.2" + resolved "https://registry.yarnpkg.com/@walletconnect/types/-/types-2.12.2.tgz#8b64a2015a0a96972d28acb2ff317a9a994abfdb" + integrity sha512-9CmwTlPbrFTzayTL9q7xM7s3KTJkS6kYFtH2m1/fHFgALs6pIUjf1qAx1TF2E4tv7SEzLAIzU4NqgYUt2vWXTg== dependencies: "@walletconnect/events" "^1.0.1" "@walletconnect/heartbeat" "1.2.1" "@walletconnect/jsonrpc-types" "1.0.3" - "@walletconnect/keyvaluestorage" "^1.0.2" + "@walletconnect/keyvaluestorage" "^1.1.1" "@walletconnect/logger" "^2.0.1" events "^3.3.0" -"@walletconnect/universal-provider@2.8.0": - version "2.8.0" - resolved "https://registry.yarnpkg.com/@walletconnect/universal-provider/-/universal-provider-2.8.0.tgz#134f6873742f672c2424969335f9cc75d1532d17" - integrity sha512-BMsGiINI3rT7DRyDJM7miuWG6vDVE0PV6zMcCXIMDYYPay7zFvJxv2VHEx9an4MutrvQR76NTRyG//i1K84VOQ== +"@walletconnect/universal-provider@2.12.2": + version "2.12.2" + resolved "https://registry.yarnpkg.com/@walletconnect/universal-provider/-/universal-provider-2.12.2.tgz#0c855bbb5584fd11bdf2318344fe6f42fa3e91cb" + integrity sha512-0k5ZgSkABopQLVhkiwl2gRGG7dAP4SWiI915pIlyN5sRvWV+qX1ALhWAmRcdv0TXWlKHDcDgPJw/q2sCSAHuMQ== dependencies: "@walletconnect/jsonrpc-http-connection" "^1.0.7" "@walletconnect/jsonrpc-provider" "1.0.13" "@walletconnect/jsonrpc-types" "^1.0.2" "@walletconnect/jsonrpc-utils" "^1.0.7" - "@walletconnect/logger" "^2.0.1" - "@walletconnect/sign-client" "2.8.0" - "@walletconnect/types" "2.8.0" - "@walletconnect/utils" "2.8.0" - eip1193-provider "1.0.1" + "@walletconnect/logger" "^2.1.2" + "@walletconnect/sign-client" "2.12.2" + "@walletconnect/types" "2.12.2" + "@walletconnect/utils" "2.12.2" events "^3.3.0" -"@walletconnect/utils@2.8.0": - version "2.8.0" - resolved "https://registry.yarnpkg.com/@walletconnect/utils/-/utils-2.8.0.tgz#c219e78fd2c35062cf3e37f84961afde8da9b9a1" - integrity sha512-Q8OwMtUevIn1+64LXyTMLlhH58k3UOAjU5b3smYZ7CEEmwEGpOTfTDAWrB3v+ZDIhjyqP94+8fuvKIbcVLKLWA== +"@walletconnect/utils@2.12.2": + version "2.12.2" + resolved "https://registry.yarnpkg.com/@walletconnect/utils/-/utils-2.12.2.tgz#a2c349d4effef7c1c5e72e74a5483d8dfbb10918" + integrity sha512-zf50HeS3SfoLv1N9GPl2IXTZ9TsXfet4usVAsZmX9P6/Xzq7d/7QakjVQCHH/Wk1O9XkcsfeoZoUhRxoMJ5uJw== dependencies: "@stablelib/chacha20poly1305" "1.0.1" "@stablelib/hkdf" "1.0.1" @@ -7097,7 +7253,7 @@ "@walletconnect/relay-api" "^1.0.9" "@walletconnect/safe-json" "^1.0.2" "@walletconnect/time" "^1.0.2" - "@walletconnect/types" "2.8.0" + "@walletconnect/types" "2.12.2" "@walletconnect/window-getters" "^1.0.1" "@walletconnect/window-metadata" "^1.0.1" detect-browser "5.3.0" @@ -7195,6 +7351,11 @@ acorn@^7.4.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== +acorn@^8.11.3: + version "8.11.3" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" + integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== + acorn@^8.4.1, acorn@^8.8.2: version "8.8.2" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a" @@ -7282,7 +7443,7 @@ any-base@^1.1.0: resolved "https://registry.yarnpkg.com/any-base/-/any-base-1.1.0.tgz#ae101a62bc08a597b4c9ab5b7089d456630549fe" integrity sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg== -anymatch@^3.0.3, anymatch@~3.1.2: +anymatch@^3.0.3, anymatch@^3.1.3, anymatch@~3.1.2: version "3.1.3" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== @@ -7467,13 +7628,6 @@ axe-core@^4.6.2: resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.7.0.tgz#34ba5a48a8b564f67e103f0aa5768d76e15bbbbf" integrity sha512-M0JtH+hlOL5pLQwHOLNYZaXuhqmvS8oExsqB1SBYgA4Dk7u/xx+YdGHXaK5pyUfed5mYXdlYiphWq3G8cRi5JQ== -axios@^0.21.0: - version "0.21.4" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" - integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg== - dependencies: - follow-redirects "^1.14.0" - axobject-query@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-3.1.1.tgz#3b6e5c6d4e43ca7ba51c5babf99d22a9c68485e1" @@ -7922,6 +8076,21 @@ chokidar@^3.5.3: optionalDependencies: fsevents "~2.3.2" +chokidar@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" + integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + chownr@^1.1.1: version "1.1.4" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" @@ -7942,6 +8111,13 @@ ci-info@^3.2.0: resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.8.0.tgz#81408265a5380c929f0bc665d62256628ce9ef91" integrity sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw== +citty@^0.1.5, citty@^0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/citty/-/citty-0.1.6.tgz#0f7904da1ed4625e1a9ea7e0fa780981aab7c5e4" + integrity sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ== + dependencies: + consola "^3.2.3" + classnames@^2.2.5: version "2.3.2" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924" @@ -7973,6 +8149,15 @@ cli-table3@^0.6.1: optionalDependencies: "@colors/colors" "1.5.0" +clipboardy@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/clipboardy/-/clipboardy-4.0.0.tgz#e73ced93a76d19dd379ebf1f297565426dffdca1" + integrity sha512-5mOlNS0mhX0707P2I0aZ2V/cmHUEO/fL7VFLqszkhUsxt7RwnmrInf/eEQKlf5GzvYeHIjT+Ov1HRfNmymlG0w== + dependencies: + execa "^8.0.1" + is-wsl "^3.1.0" + is64bit "^2.0.0" + cliui@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" @@ -8122,6 +8307,11 @@ confusing-browser-globals@^1.0.11: resolved "https://registry.yarnpkg.com/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz#ae40e9b57cdd3915408a2805ebd3a5585608dc81" integrity sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA== +consola@^3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/consola/-/consola-3.2.3.tgz#0741857aa88cfa0d6fd53f1cff0375136e98502f" + integrity sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ== + content-disposition@0.5.4: version "0.5.4" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" @@ -8144,6 +8334,11 @@ convert-source-map@^2.0.0: resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== +cookie-es@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/cookie-es/-/cookie-es-1.1.0.tgz#68f8d9f48aeb5a534f3896f80e792760d3d20def" + integrity sha512-L2rLOcK0wzWSfSDA33YR+PUHDG10a8px7rUHKWbGLP4YfbsMed2KFUw5fczvDPbT98DDe3LEzviswl810apTEw== + cookie-signature@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" @@ -8240,6 +8435,11 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: shebang-command "^2.0.0" which "^2.0.1" +crossws@^0.2.0, crossws@^0.2.2: + version "0.2.4" + resolved "https://registry.yarnpkg.com/crossws/-/crossws-0.2.4.tgz#82a8b518bff1018ab1d21ced9e35ffbe1681ad03" + integrity sha512-DAxroI2uSOgUKLz00NX6A8U/8EE3SZHmIND+10jkVSaypvyt57J5JEOxAQOL6lQxyzi/wZbTIwssU1uy69h5Vg== + crypt@0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" @@ -8405,9 +8605,9 @@ date-fns@^2.29.1: integrity sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA== dayjs@^1.11.7: - version "1.11.7" - resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.7.tgz#4b296922642f70999544d1144a2c25730fce63e2" - integrity sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ== + version "1.11.10" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.10.tgz#68acea85317a6e164457d6d6947564029a6a16a0" + integrity sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ== debug@2.6.9, debug@^2.6.9: version "2.6.9" @@ -8528,6 +8728,11 @@ defu@^6.1.2: resolved "https://registry.yarnpkg.com/defu/-/defu-6.1.2.tgz#1217cba167410a1765ba93893c6dbac9ed9d9e5c" integrity sha512-+uO4+qr7msjNNWKYPHqN/3+Dx3NFkmIzayk2L1MyZQlvgZb/J1A0fo410dpKrN2SnqFjt8n4JL8fDJE0wIgjFQ== +defu@^6.1.3, defu@^6.1.4: + version "6.1.4" + resolved "https://registry.yarnpkg.com/defu/-/defu-6.1.4.tgz#4e0c9cf9ff68fe5f3d7f2765cc1a012dfdcb0479" + integrity sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg== + del@^6.0.0: version "6.1.1" resolved "https://registry.yarnpkg.com/del/-/del-6.1.1.tgz#3b70314f1ec0aa325c6b14eb36b95786671edb7a" @@ -8557,6 +8762,11 @@ dequal@^2.0.2: resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== +destr@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/destr/-/destr-2.0.3.tgz#7f9e97cb3d16dbdca7be52aca1644ce402cfe449" + integrity sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ== + destroy@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" @@ -8572,6 +8782,11 @@ detect-indent@^6.1.0: resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.1.0.tgz#592485ebbbf6b3b1ab2be175c8393d04ca0d57e6" integrity sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA== +detect-libc@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" + integrity sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg== + detect-node-es@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/detect-node-es/-/detect-node-es-1.1.0.tgz#163acdf643330caa0b4cd7c21e7ee7755d6fa493" @@ -8688,13 +8903,6 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== -eip1193-provider@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/eip1193-provider/-/eip1193-provider-1.0.1.tgz#420d29cf4f6c443e3f32e718fb16fafb250637c3" - integrity sha512-kSuqwQ26d7CzuS/t3yRXo2Su2cVH0QfvyKbr2H7Be7O5YDyIq4hQGCNTo5wRdP07bt+E2R/8nPCzey4ojBHf7g== - dependencies: - "@json-rpc-tools/provider" "^1.5.5" - ejs@^3.1.8: version "3.1.9" resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.9.tgz#03c9e8777fe12686a9effcef22303ca3d8eeb361" @@ -9275,10 +9483,10 @@ ethers-decode-error@^1.0.0: resolved "https://registry.yarnpkg.com/ethers-decode-error/-/ethers-decode-error-1.0.0.tgz#85f044a74173914a1d4b5f9bac6a132c9bfd51af" integrity sha512-wWB9g3XZZ/sQwNhzo+/IVtiyH3s1I5bPi0wH7Hp7XTPJEN0Tx2+LwXDNBgspxCHV5T8aok3D0LTnC062ZNnceA== -ethers@^5.7.0: - version "5.7.2" - resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.7.2.tgz#3a7deeabbb8c030d4126b24f84e525466145872e" - integrity sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg== +ethers@5.7.0: + version "5.7.0" + resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.7.0.tgz#0055da174b9e076b242b8282638bc94e04b39835" + integrity sha512-5Xhzp2ZQRi0Em+0OkOcRHxPzCfoBfgtOQA+RUylSkuHbhTEaQklnYi2hsWbRgs3ztJsXVXd9VKBcO1ScWL8YfA== dependencies: "@ethersproject/abi" "5.7.0" "@ethersproject/abstract-provider" "5.7.0" @@ -9295,10 +9503,10 @@ ethers@^5.7.0: "@ethersproject/json-wallets" "5.7.0" "@ethersproject/keccak256" "5.7.0" "@ethersproject/logger" "5.7.0" - "@ethersproject/networks" "5.7.1" + "@ethersproject/networks" "5.7.0" "@ethersproject/pbkdf2" "5.7.0" "@ethersproject/properties" "5.7.0" - "@ethersproject/providers" "5.7.2" + "@ethersproject/providers" "5.7.0" "@ethersproject/random" "5.7.0" "@ethersproject/rlp" "5.7.0" "@ethersproject/sha2" "5.7.0" @@ -9308,7 +9516,7 @@ ethers@^5.7.0: "@ethersproject/transactions" "5.7.0" "@ethersproject/units" "5.7.0" "@ethersproject/wallet" "5.7.0" - "@ethersproject/web" "5.7.1" + "@ethersproject/web" "5.7.0" "@ethersproject/wordlists" "5.7.0" eventemitter3@^4.0.1, eventemitter3@^4.0.7: @@ -9356,6 +9564,21 @@ execa@^7.1.1: signal-exit "^3.0.7" strip-final-newline "^3.0.0" +execa@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-8.0.1.tgz#51f6a5943b580f963c3ca9c6321796db8cc39b8c" + integrity sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^8.0.1" + human-signals "^5.0.0" + is-stream "^3.0.0" + merge-stream "^2.0.0" + npm-run-path "^5.1.0" + onetime "^6.0.0" + signal-exit "^4.1.0" + strip-final-newline "^3.0.0" + expect@^29.0.0: version "29.5.0" resolved "https://registry.yarnpkg.com/expect/-/expect-29.5.0.tgz#68c0509156cb2a0adb8865d413b137eeaae682f7" @@ -9686,11 +9909,6 @@ flux@^4.0.1: fbemitter "^3.0.0" fbjs "^3.0.1" -follow-redirects@^1.14.0: - version "1.15.3" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.3.tgz#fe2f3ef2690afce7e82ed0b44db08165b207123a" - integrity sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q== - for-each@^0.3.3: version "0.3.3" resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" @@ -9849,6 +10067,11 @@ get-package-type@^0.1.0: resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== +get-port-please@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/get-port-please/-/get-port-please-3.1.2.tgz#502795e56217128e4183025c89a48c71652f4e49" + integrity sha512-Gxc29eLs1fbn6LQ4jSU4vXjlwyZhF5HsGuMAa7gqBP4Rw4yxxltyDUuF5MBclFzDTXO+ACchGQoeela4DSfzdQ== + get-port@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/get-port/-/get-port-5.1.1.tgz#0469ed07563479de6efb986baf053dcd7d4e3193" @@ -9859,6 +10082,11 @@ get-stream@^6.0.0, get-stream@^6.0.1: resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== +get-stream@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-8.0.1.tgz#def9dfd71742cd7754a7761ed43749a27d02eca2" + integrity sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA== + get-symbol-description@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6" @@ -10019,6 +10247,22 @@ gunzip-maybe@^1.4.2: pumpify "^1.3.3" through2 "^2.0.3" +h3@^1.10.2, h3@^1.11.1: + version "1.11.1" + resolved "https://registry.yarnpkg.com/h3/-/h3-1.11.1.tgz#e9414ae6f2a076a345ea07256b320edb29bab9f7" + integrity sha512-AbaH6IDnZN6nmbnJOH72y3c5Wwh9P97soSVdGSBbcDACRdkC0FEWf25pzx4f/NuOCK6quHmW18yF2Wx+G4Zi1A== + dependencies: + cookie-es "^1.0.0" + crossws "^0.2.2" + defu "^6.1.4" + destr "^2.0.3" + iron-webcrypto "^1.0.0" + ohash "^1.1.3" + radix3 "^1.1.0" + ufo "^1.4.0" + uncrypto "^0.1.3" + unenv "^1.9.0" + handlebars@^4.7.7: version "4.7.7" resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1" @@ -10146,6 +10390,11 @@ http-errors@2.0.0: statuses "2.0.1" toidentifier "1.0.1" +http-shutdown@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/http-shutdown/-/http-shutdown-1.2.2.tgz#41bc78fc767637c4c95179bc492f312c0ae64c5f" + integrity sha512-S9wWkJ/VSY9/k4qcjG318bqJNruzE4HySUhFYknwmu6LBP97KLLfwNf+n4V1BHurvFNkSKLFnK/RsuUnRTf9Vw== + https-proxy-agent@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz#702b71fb5520a132a66de1f67541d9e62154d82b" @@ -10172,6 +10421,11 @@ human-signals@^4.3.0: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-4.3.1.tgz#ab7f811e851fca97ffbd2c1fe9a958964de321b2" integrity sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ== +human-signals@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-5.0.0.tgz#42665a284f9ae0dade3ba41ebc37eb4b852f3a28" + integrity sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ== + humanize-duration-ts@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/humanize-duration-ts/-/humanize-duration-ts-2.1.1.tgz#5382b2789f851005a67229eaf031931d71f37ee9" @@ -10208,6 +10462,11 @@ icss-utils@^5.0.0, icss-utils@^5.1.0: resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA== +idb-keyval@^6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/idb-keyval/-/idb-keyval-6.2.1.tgz#94516d625346d16f56f3b33855da11bfded2db33" + integrity sha512-8Sb3veuYCyrZL+VBt9LJfZjLUPWVvqn8tG28VqYNFCo43KHcKuq+b4EiXGeuaLAQWL2YmyDgMp2aSpH9JHsEQg== + ieee754@^1.1.13, ieee754@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" @@ -10293,6 +10552,11 @@ ipaddr.js@1.9.1: resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== +iron-webcrypto@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/iron-webcrypto/-/iron-webcrypto-1.1.0.tgz#f902f0cdbd77554b2195ecbb65558c311b01edfd" + integrity sha512-5vgYsCakNlaQub1orZK5QmNYhwYtcllTkZBp5sfIaCqY93Cf6l+v2rtE+E4TMbcfjxDMCdrO8wmp7+ZvhDECLA== + is-absolute-url@^3.0.0: version "3.0.3" resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-3.0.3.tgz#96c6a22b6a23929b11ea0afb1836c36ad4a5d698" @@ -10580,6 +10844,20 @@ is-wsl@^2.1.1, is-wsl@^2.2.0: dependencies: is-docker "^2.0.0" +is-wsl@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-3.1.0.tgz#e1c657e39c10090afcbedec61720f6b924c3cbd2" + integrity sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw== + dependencies: + is-inside-container "^1.0.0" + +is64bit@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is64bit/-/is64bit-2.0.0.tgz#198c627cbcb198bbec402251f88e5e1a51236c07" + integrity sha512-jv+8jaWCl0g2lSBkNSVXdzfBA0npK1HGC2KtWM9FumFRoGS94g3NbCCLVnCYHLjp4GrW2KZeeSTMo5ddtznmGw== + dependencies: + system-architecture "^0.1.0" + isarray@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" @@ -10600,6 +10878,14 @@ isobject@^3.0.1: resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== +isomorphic-unfetch@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/isomorphic-unfetch/-/isomorphic-unfetch-3.1.0.tgz#87341d5f4f7b63843d468438128cb087b7c3e98f" + integrity sha512-geDJjpoZ8N0kWexiwkX8F9NkTsXhetLPVbZFQ+JTW239QNOwvB0gniuR1Wc6f0AMTn7/mFGyXvHTifrCp/GH8Q== + dependencies: + node-fetch "^2.6.1" + unfetch "^4.2.0" + isows@1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/isows/-/isows-1.0.3.tgz#93c1cf0575daf56e7120bab5c8c448b0809d0d74" @@ -10801,6 +11087,11 @@ jiti@^1.18.2: resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.19.1.tgz#fa99e4b76a23053e0e7cde098efe1704a14c16f1" integrity sha512-oVhqoRDaBXf7sjkll95LHVS6Myyyb1zaunVwk4Z0+WPSW4gjS0pl01zYKHScTuyEhQsFxV5L4DR5r+YqSyqyyg== +jiti@^1.21.0: + version "1.21.0" + resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.0.tgz#7c97f8fe045724e136a397f7340475244156105d" + integrity sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q== + js-cookie@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8" @@ -10998,6 +11289,30 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== +listhen@^1.7.2: + version "1.7.2" + resolved "https://registry.yarnpkg.com/listhen/-/listhen-1.7.2.tgz#66b81740692269d5d8cafdc475020f2fc51afbae" + integrity sha512-7/HamOm5YD9Wb7CFgAZkKgVPA96WwhcTQoqtm2VTZGVbVVn3IWKRBTgrU7cchA3Q8k9iCsG8Osoi9GX4JsGM9g== + dependencies: + "@parcel/watcher" "^2.4.1" + "@parcel/watcher-wasm" "^2.4.1" + citty "^0.1.6" + clipboardy "^4.0.0" + consola "^3.2.3" + crossws "^0.2.0" + defu "^6.1.4" + get-port-please "^3.1.2" + h3 "^1.10.2" + http-shutdown "^1.2.2" + jiti "^1.21.0" + mlly "^1.6.1" + node-forge "^1.3.1" + pathe "^1.1.2" + std-env "^3.7.0" + ufo "^1.4.0" + untun "^0.1.3" + uqr "^0.1.2" + lit-element@^3.3.0: version "3.3.3" resolved "https://registry.yarnpkg.com/lit-element/-/lit-element-3.3.3.tgz#10bc19702b96ef5416cf7a70177255bfb17b3209" @@ -11138,6 +11453,11 @@ lower-case@^2.0.2: dependencies: tslib "^2.0.3" +lru-cache@^10.2.0: + version "10.2.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.2.0.tgz#0bd445ca57363465900f4d1f9bd8db343a4d95c3" + integrity sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q== + lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" @@ -11293,7 +11613,7 @@ methods@~1.1.2: resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== -micromatch@^4.0.2, micromatch@^4.0.4: +micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5: version "4.0.5" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== @@ -11323,6 +11643,11 @@ mime@^2.0.3: resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== +mime@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-3.0.0.tgz#b374550dca3a0c18443b0c950a6a58f1931cf7a7" + integrity sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A== + mimic-fn@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" @@ -11426,6 +11751,16 @@ mlly@^1.2.0, mlly@^1.4.0: pkg-types "^1.0.3" ufo "^1.1.2" +mlly@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/mlly/-/mlly-1.6.1.tgz#0983067dc3366d6314fc5e12712884e6978d028f" + integrity sha512-vLgaHvaeunuOXHSmEbZ9izxPx3USsk8KCQ8iC+aTlp5sKRSoZvwhHh5L9VbKSaVC6sJDqbyohIS76E2VmHIPAA== + dependencies: + acorn "^8.11.3" + pathe "^1.1.2" + pkg-types "^1.0.3" + ufo "^1.3.2" + mock-socket@^9.2.1: version "9.2.1" resolved "https://registry.yarnpkg.com/mock-socket/-/mock-socket-9.2.1.tgz#cc9c0810aa4d0afe02d721dcb2b7e657c00e2282" @@ -11505,6 +11840,11 @@ nanoid@^3.3.7: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== +napi-wasm@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/napi-wasm/-/napi-wasm-1.1.0.tgz#bbe617823765ae9c1bc12ff5942370eae7b2ba4e" + integrity sha512-lHwIAJbmLSjF9VDRm9GoVOy9AGp3aIvkjv+Kvz9h16QR3uSVYH78PNQUnT2U4X53mhlnV2M7wrhibQ3GHicDmg== + natural-compare-lite@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4" @@ -11548,6 +11888,11 @@ nock@^13.3.1: lodash "^4.17.21" propagate "^2.0.0" +node-addon-api@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-7.1.0.tgz#71f609369379c08e251c558527a107107b5e0fdb" + integrity sha512-mNcltoe1R8o7STTegSOHdnJNN7s5EUvhoS7ShnTHDyOSd+8H+UdWODq6qSv67PjC8Zc5JRT8+oLAMCr0SIXw7g== + node-dir@^0.1.10, node-dir@^0.1.17: version "0.1.17" resolved "https://registry.yarnpkg.com/node-dir/-/node-dir-0.1.17.tgz#5f5665d93351335caabef8f1c554516cf5f1e4e5" @@ -11565,6 +11910,11 @@ node-fetch-native@^1.0.2: resolved "https://registry.yarnpkg.com/node-fetch-native/-/node-fetch-native-1.2.0.tgz#13ec6df98f33168958dbfb6945f10aedf42e7ea8" integrity sha512-5IAMBTl9p6PaAjYCnMv5FmqIF6GcZnawAVnzaCG0rX2aYZJ4CxEkZNtVPuTRug7fL7wyM5BQYTlAzcyMPi6oTQ== +node-fetch-native@^1.6.1, node-fetch-native@^1.6.2, node-fetch-native@^1.6.3: + version "1.6.4" + resolved "https://registry.yarnpkg.com/node-fetch-native/-/node-fetch-native-1.6.4.tgz#679fc8fd8111266d47d7e72c379f1bed9acff06e" + integrity sha512-IhOigYzAKHd244OC0JIMIUrjzctirCmPkaIfhDeGcEETWof5zKYUW7e7MYvChGWh/4CJeXEgsRyGzuF334rOOQ== + node-fetch@2.6.7: version "2.6.7" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" @@ -11579,7 +11929,7 @@ node-fetch@^2.0.0: dependencies: whatwg-url "^5.0.0" -node-fetch@^2.6.12: +node-fetch@^2.6.1, node-fetch@^2.6.12: version "2.7.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== @@ -11604,6 +11954,11 @@ node-fetch@^3.3.2: fetch-blob "^3.1.4" formdata-polyfill "^4.0.10" +node-forge@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3" + integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA== + node-int64@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" @@ -11731,6 +12086,20 @@ object.values@^1.1.6: define-properties "^1.1.4" es-abstract "^1.20.4" +ofetch@^1.3.3: + version "1.3.4" + resolved "https://registry.yarnpkg.com/ofetch/-/ofetch-1.3.4.tgz#7ea65ced3c592ec2b9906975ae3fe1d26a56f635" + integrity sha512-KLIET85ik3vhEfS+3fDlc/BAZiAp+43QEC/yCo5zkNoY2YaKvNkOaFr/6wCFgFH1kuYQM5pMNi0Tg8koiIemtw== + dependencies: + destr "^2.0.3" + node-fetch-native "^1.6.3" + ufo "^1.5.3" + +ohash@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/ohash/-/ohash-1.1.3.tgz#f12c3c50bfe7271ce3fd1097d42568122ccdcf07" + integrity sha512-zuHHiGTYTA1sYJ/wZN+t5HKZaH23i4yI1HMwbuXm24Nid7Dv0KcuRlKoNKS9UNfAVSBlnGLcuQrnOKWOZoEGaw== + on-exit-leak-free@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/on-exit-leak-free/-/on-exit-leak-free-0.2.0.tgz#b39c9e3bf7690d890f4861558b0d7b90a442d209" @@ -12017,6 +12386,11 @@ pathe@^1.1.1: resolved "https://registry.yarnpkg.com/pathe/-/pathe-1.1.1.tgz#1dd31d382b974ba69809adc9a7a347e65d84829a" integrity sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q== +pathe@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/pathe/-/pathe-1.1.2.tgz#6c4cb47a945692e48a1ddd6e4094d170516437ec" + integrity sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ== + pathval@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d" @@ -12460,6 +12834,11 @@ quick-format-unescaped@^4.0.3: resolved "https://registry.yarnpkg.com/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz#93ef6dd8d3453cbc7970dd614fad4c5954d6b5a7" integrity sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg== +radix3@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/radix3/-/radix3-1.1.2.tgz#fd27d2af3896c6bf4bcdfab6427c69c2afc69ec0" + integrity sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA== + ramda@0.29.0: version "0.29.0" resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.29.0.tgz#fbbb67a740a754c8a4cbb41e2a6e0eb8507f55fb" @@ -13053,11 +13432,6 @@ safe-buffer@5.2.1, safe-buffer@~5.2.0: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== -safe-json-utils@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/safe-json-utils/-/safe-json-utils-1.1.1.tgz#0e883874467d95ab914c3f511096b89bfb3e63b1" - integrity sha512-SAJWGKDs50tAbiDXLf89PDwt9XYkWyANFWVzn4dTXl5QyI8t2o/bW5/OJl3lvc2WVU4MEpTo9Yz5NVFNsp+OJQ== - safe-regex-test@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.0.tgz#793b874d524eb3640d1873aad03596db2d4f2295" @@ -13268,6 +13642,11 @@ signal-exit@^4.0.1: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.0.2.tgz#ff55bb1d9ff2114c13b400688fa544ac63c36967" integrity sha512-MY2/qGx4enyjprQnFaZsHib3Yadh3IXyV2C321GY0pjGfVBu4un0uDJkwgdxqO+Rdx8JMT8IfJIRwbYVz3Ob3Q== +signal-exit@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + simple-swizzle@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" @@ -13450,6 +13829,11 @@ std-env@^3.3.3: resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.3.3.tgz#a54f06eb245fdcfef53d56f3c0251f1d5c3d01fe" integrity sha512-Rz6yejtVyWnVjC1RFvNmYL10kgjC49EOghxWn0RFqlCHGFpQx+Xe7yW3I4ceK1SGrWIGMjD5Kbue8W/udkbMJg== +std-env@^3.7.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.7.0.tgz#c9f7386ced6ecf13360b6c6c55b8aaa4ef7481d2" + integrity sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg== + stop-iteration-iterator@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz#6a60be0b4ee757d1ed5254858ec66b10c49285e4" @@ -13686,6 +14070,11 @@ synckit@^0.8.5: "@pkgr/utils" "^2.3.1" tslib "^2.5.0" +system-architecture@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/system-architecture/-/system-architecture-0.1.0.tgz#71012b3ac141427d97c67c56bc7921af6bff122d" + integrity sha512-ulAk51I9UVUyJgxlv9M6lFot2WP3e7t8Kz9+IS6D4rVba1tR9kON+Ey69f+1R4Q8cd45Lod6a4IcJIxnzGc/zA== + tar-fs@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" @@ -14024,6 +14413,11 @@ ufo@^1.1.2: resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.1.2.tgz#d0d9e0fa09dece0c31ffd57bd363f030a35cfe76" integrity sha512-TrY6DsjTQQgyS3E3dBaOXf0TpPD8u9FVrVYmKVegJuFw51n/YB9XPt+U6ydzFG5ZIN7+DIjPbNmXoBj9esYhgQ== +ufo@^1.3.2, ufo@^1.4.0, ufo@^1.5.3: + version "1.5.3" + resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.5.3.tgz#3325bd3c977b6c6cd3160bf4ff52989adc9d3344" + integrity sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw== + uglify-js@^3.1.4: version "3.17.4" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.17.4.tgz#61678cf5fa3f5b7eb789bb345df29afb8257c22c" @@ -14046,6 +14440,27 @@ unbox-primitive@^1.0.2: has-symbols "^1.0.3" which-boxed-primitive "^1.0.2" +uncrypto@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/uncrypto/-/uncrypto-0.1.3.tgz#e1288d609226f2d02d8d69ee861fa20d8348ef2b" + integrity sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q== + +unenv@^1.9.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/unenv/-/unenv-1.9.0.tgz#469502ae85be1bd3a6aa60f810972b1a904ca312" + integrity sha512-QKnFNznRxmbOF1hDgzpqrlIf6NC5sbZ2OJ+5Wl3OX8uM+LUJXbj4TXvLJCtwbPTmbMHCLIz6JLKNinNsMShK9g== + dependencies: + consola "^3.2.3" + defu "^6.1.3" + mime "^3.0.0" + node-fetch-native "^1.6.1" + pathe "^1.1.1" + +unfetch@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/unfetch/-/unfetch-4.2.0.tgz#7e21b0ef7d363d8d9af0fb929a5555f6ef97a3be" + integrity sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA== + unicode-canonical-property-names-ecmascript@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc" @@ -14118,11 +14533,36 @@ unplugin@^1.3.1: webpack-sources "^3.2.3" webpack-virtual-modules "^0.5.0" +unstorage@^1.9.0: + version "1.10.2" + resolved "https://registry.yarnpkg.com/unstorage/-/unstorage-1.10.2.tgz#fb7590ada8b30e83be9318f85100158b02a76dae" + integrity sha512-cULBcwDqrS8UhlIysUJs2Dk0Mmt8h7B0E6mtR+relW9nZvsf/u4SkAYyNliPiPW7XtFNb5u3IUMkxGxFTTRTgQ== + dependencies: + anymatch "^3.1.3" + chokidar "^3.6.0" + destr "^2.0.3" + h3 "^1.11.1" + listhen "^1.7.2" + lru-cache "^10.2.0" + mri "^1.2.0" + node-fetch-native "^1.6.2" + ofetch "^1.3.3" + ufo "^1.4.0" + untildify@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b" integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw== +untun@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/untun/-/untun-0.1.3.tgz#5d10dee37a3a5737ff03d158be877dae0a0e58a6" + integrity sha512-4luGP9LMYszMRZwsvyUd9MrxgEGZdZuZgpVQHEEX0lCYFESasVRvZd0EYpCkOIbJKHMuv0LskpXc/8Un+MJzEQ== + dependencies: + citty "^0.1.5" + consola "^3.2.3" + pathe "^1.1.1" + update-browserslist-db@^1.0.10, update-browserslist-db@^1.0.11: version "1.0.11" resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz#9a2a641ad2907ae7b3616506f4b977851db5b940" @@ -14131,6 +14571,11 @@ update-browserslist-db@^1.0.10, update-browserslist-db@^1.0.11: escalade "^3.1.1" picocolors "^1.0.0" +uqr@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/uqr/-/uqr-0.1.2.tgz#5c6cd5dcff9581f9bb35b982cb89e2c483a41d7d" + integrity sha512-MJu7ypHq6QasgF5YRTjqscSzQp/W11zoUk6kvmlH+fmWEs63Y0Eib13hYFwAzagRJcVY8WVnlV+eBDUGMJ5IbA== + uri-js@^4.2.2: version "4.4.1" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" @@ -14553,7 +14998,7 @@ ws@^6.1.0: dependencies: async-limiter "~1.0.0" -ws@^7.4.0, ws@^7.5.1: +ws@^7.5.1: version "7.5.9" resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==