From 01d9d2ccd8c7a893b43e28432999aaece8d10900 Mon Sep 17 00:00:00 2001 From: Davide Segullo Date: Mon, 5 Aug 2024 15:38:43 +0200 Subject: [PATCH 1/5] fix: :bug: fix hidden alloyed composition (#3670) --- packages/web/pages/assets/[denom].tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/web/pages/assets/[denom].tsx b/packages/web/pages/assets/[denom].tsx index 9b397ce9ff..74b6bc16bb 100644 --- a/packages/web/pages/assets/[denom].tsx +++ b/packages/web/pages/assets/[denom].tsx @@ -179,7 +179,7 @@ const AssetInfoView: FunctionComponent = observer( {SwapTool_} - {asset.areTransfersDisabled && asset.contract ? ( + {!asset.areTransfersDisabled && asset.contract ? ( = observer(
{SwapTool_}
- {asset.areTransfersDisabled && asset.contract ? ( + {!asset.areTransfersDisabled && asset.contract ? ( Date: Mon, 5 Aug 2024 11:38:58 -0400 Subject: [PATCH 2/5] (Deposit/Withdraw) Fix Skip timeout height chain ID (#3672) * fix chain ID * fix test --- .../skip/__tests__/skip-bridge-provider.spec.ts | 1 + packages/bridge/src/skip/index.ts | 14 ++++++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/bridge/src/skip/__tests__/skip-bridge-provider.spec.ts b/packages/bridge/src/skip/__tests__/skip-bridge-provider.spec.ts index ed8d673339..56c03585b4 100644 --- a/packages/bridge/src/skip/__tests__/skip-bridge-provider.spec.ts +++ b/packages/bridge/src/skip/__tests__/skip-bridge-provider.spec.ts @@ -339,6 +339,7 @@ describe("SkipBridgeProvider", () => { const txRequest = (await provider.createTransaction( "1", + "osmosis-1", "0xabc", messages )) as EvmBridgeTransactionRequest; diff --git a/packages/bridge/src/skip/index.ts b/packages/bridge/src/skip/index.ts index 40df3b1738..68c48901a1 100644 --- a/packages/bridge/src/skip/index.ts +++ b/packages/bridge/src/skip/index.ts @@ -212,6 +212,7 @@ export class SkipBridgeProvider implements BridgeProvider { const transactionRequest = await this.createTransaction( fromChain.chainId.toString(), + toChain.chainId.toString(), fromAddress as Address, msgs ); @@ -439,26 +440,31 @@ export class SkipBridgeProvider implements BridgeProvider { } async createTransaction( - chainID: string, + fromChainId: string, + toChainId: string, address: Address, messages: SkipMsg[] ) { for (const message of messages) { if ("evm_tx" in message) { return await this.createEvmTransaction( - chainID, + fromChainId, address, message.evm_tx ); } if ("multi_chain_msg" in message) { - return await this.createCosmosTransaction(message.multi_chain_msg); + return await this.createCosmosTransaction( + toChainId, + message.multi_chain_msg + ); } } } async createCosmosTransaction( + toChainId: string, message: SkipMultiChainMsg ): Promise { const messageData = JSON.parse(message.msg); @@ -494,7 +500,7 @@ export class SkipBridgeProvider implements BridgeProvider { // is an ibc transfer const timeoutHeight = await this.ctx.getTimeoutHeight({ - chainId: messageData.chain_id, + chainId: toChainId, }); const { typeUrl, value } = cosmosMsgOpts.ibcTransfer.messageComposer({ From c373c768b4c6e5f36835be8ea1f016410f8af2d1 Mon Sep 17 00:00:00 2001 From: Jon Ator Date: Mon, 5 Aug 2024 11:48:23 -0400 Subject: [PATCH 3/5] log asset selected from query params (#3675) --- packages/web/components/bridge/immersive-bridge.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/web/components/bridge/immersive-bridge.tsx b/packages/web/components/bridge/immersive-bridge.tsx index 27fc1577eb..39b38816e3 100644 --- a/packages/web/components/bridge/immersive-bridge.tsx +++ b/packages/web/components/bridge/immersive-bridge.tsx @@ -66,8 +66,14 @@ export const ImmersiveBridgeFlow = ({ if (!isNil(selectedAssetDenom) && !isVisible) { setIsVisible(true); setStep(ImmersiveBridgeScreen.Amount); + logEvent([ + EventName.DepositWithdraw.assetSelected, + { + tokenName: selectedAssetDenom, + }, + ]); } - }, [direction, selectedAssetDenom, isVisible, setDirection]); + }, [direction, selectedAssetDenom, isVisible, setDirection, logEvent]); const [fiatRampParams, setFiatRampParams] = useState<{ fiatRampKey: FiatRampKey; From a904c950e94ace61cbc4a3f4f0ad43100ae9d743 Mon Sep 17 00:00:00 2001 From: Matt Upham <30577966+mattupham@users.noreply.github.com> Date: Mon, 5 Aug 2024 08:52:01 -0700 Subject: [PATCH 4/5] Portfolio allocation (#3660) * Add getAllocation procedure * add base ui * Render all to frontend * Add assets * add other * Add get assets * Add available * Add temp tabs * add types for allocation * Add tabs * Update classnames * Lint issue * Add ion * Clean up styles * Add open / close * Add address for allocation * Update colors * Fix error * Update types * type 2 * fix lint * Move alloction * Clean up tabs * Update URL * Clean up addresses * Clean up comments * Add translations * Add translations * add pooled * Update useMemo * Fix ion color * Remove cachified * Remove color * Update to fiatValue * Migrate all to RatePretty and PricePretty * Convert to Dec and PricePretty * Refactor with RatePretty and PricePretty * Update * Remove log * Add spec * Update tests * Update tests * Moved to sidecar folder * Update to local router * Clean up * Add neglible percent * Extract color class * Update sort * Update allocation limit * Top coins results * Add allocation limit * Update allocation --- packages/server/jest.config.js | 1 + packages/server/src/queries/complex/index.ts | 1 + .../portfolio/__tests__/allocation.spec.ts | 187 ++++++++++++++++++ .../queries/complex/portfolio/allocation.ts | 157 +++++++++++++++ .../src/queries/complex/portfolio/index.ts | 1 + .../server/src/queries/data-services/index.ts | 1 + .../server/src/queries/sidecar/allocation.ts | 45 +++++ packages/trpc/src/index.ts | 1 + packages/trpc/src/portfolio.ts | 20 ++ .../web/components/complex/portfolio-page.tsx | 18 ++ .../complex/portfolio/allocation-tabs.tsx | 66 +++++++ .../complex/portfolio/allocation.tsx | 135 +++++++++++++ .../web/components/complex/portfolio/types.ts | 1 + .../transactions/transaction-utils.tsx | 3 +- packages/web/localizations/de.json | 11 +- packages/web/localizations/en.json | 11 +- packages/web/localizations/es.json | 11 +- packages/web/localizations/fa.json | 11 +- packages/web/localizations/fr.json | 11 +- packages/web/localizations/gu.json | 11 +- packages/web/localizations/hi.json | 11 +- packages/web/localizations/ja.json | 11 +- packages/web/localizations/ko.json | 11 +- packages/web/localizations/pl.json | 11 +- packages/web/localizations/pt-br.json | 11 +- packages/web/localizations/ro.json | 11 +- packages/web/localizations/ru.json | 11 +- packages/web/localizations/tr.json | 11 +- packages/web/localizations/zh-cn.json | 11 +- packages/web/localizations/zh-hk.json | 11 +- packages/web/localizations/zh-tw.json | 11 +- packages/web/server/api/local-router.ts | 2 + 32 files changed, 807 insertions(+), 19 deletions(-) create mode 100644 packages/server/src/queries/complex/portfolio/__tests__/allocation.spec.ts create mode 100644 packages/server/src/queries/complex/portfolio/allocation.ts create mode 100644 packages/server/src/queries/complex/portfolio/index.ts create mode 100644 packages/server/src/queries/sidecar/allocation.ts create mode 100644 packages/trpc/src/portfolio.ts create mode 100644 packages/web/components/complex/portfolio/allocation-tabs.tsx create mode 100644 packages/web/components/complex/portfolio/allocation.tsx create mode 100644 packages/web/components/complex/portfolio/types.ts diff --git a/packages/server/jest.config.js b/packages/server/jest.config.js index ef54106983..d1bb8b8f71 100644 --- a/packages/server/jest.config.js +++ b/packages/server/jest.config.js @@ -23,4 +23,5 @@ module.exports = { "jest-watch-typeahead/filename", "jest-watch-typeahead/testname", ], + maxWorkers: 1, }; diff --git a/packages/server/src/queries/complex/index.ts b/packages/server/src/queries/complex/index.ts index 3aabb6274c..72dccfe885 100644 --- a/packages/server/src/queries/complex/index.ts +++ b/packages/server/src/queries/complex/index.ts @@ -6,6 +6,7 @@ export * from "./earn"; export * from "./get-timeout-height"; export * from "./osmosis"; export * from "./pools"; +export * from "./portfolio"; export * from "./staking"; export * from "./swap-routers"; export * from "./transactions"; diff --git a/packages/server/src/queries/complex/portfolio/__tests__/allocation.spec.ts b/packages/server/src/queries/complex/portfolio/__tests__/allocation.spec.ts new file mode 100644 index 0000000000..4f061b41a0 --- /dev/null +++ b/packages/server/src/queries/complex/portfolio/__tests__/allocation.spec.ts @@ -0,0 +1,187 @@ +import { AssetLists as assetLists } from "../../../../queries/__tests__/mock-asset-lists"; +import { AllocationResponse } from "../../../sidecar/allocation"; +import { calculatePercentAndFiatValues, getAll } from "../allocation"; + +const MOCK_DATA: AllocationResponse = { + categories: { + "in-locks": { + capitalization: "5.000000000000000000", + is_best_effort: false, + }, + pooled: { + capitalization: "5.000000000000000000", + is_best_effort: false, + }, + staked: { + capitalization: "5.000000000000000000", + is_best_effort: false, + }, + "total-assets": { + capitalization: "60.000000000000000000", + account_coins_result: [ + { + coin: { + denom: "factory/osmo1pfyxruwvtwk00y8z06dh2lqjdj82ldvy74wzm3/WOSMO", + amount: "789", + }, + cap_value: "10.000000000000000000", + }, + { + coin: { + denom: + "factory/osmo1rckme96ptawr4zwexxj5g5gej9s2dmud8r2t9j0k0prn5mch5g4snzzwjv/sail", + amount: "456", + }, + cap_value: "20.000000000000000000", + }, + { + coin: { + denom: + "ibc/7ED954CFFFC06EE8419387F3FC688837FF64EF264DE14219935F724EEEDBF8D3", + amount: "123", + }, + cap_value: "30.000000000000000000", + }, + ], + is_best_effort: false, + }, + "unclaimed-rewards": { + capitalization: "5.000000000000000000", + is_best_effort: false, + }, + unstaking: { + capitalization: "5.000000000000000000", + is_best_effort: false, + }, + "user-balances": { + capitalization: "10.000000000000000000", + account_coins_result: [ + { + coin: { + denom: "factory/osmo1pfyxruwvtwk00y8z06dh2lqjdj82ldvy74wzm3/WOSMO", + amount: "789", + }, + cap_value: "10.000000000000000000", + }, + { + coin: { + denom: + "factory/osmo1rckme96ptawr4zwexxj5g5gej9s2dmud8r2t9j0k0prn5mch5g4snzzwjv/sail", + amount: "456", + }, + cap_value: "20.000000000000000000", + }, + ], + is_best_effort: false, + }, + }, +}; + +describe("Allocation Functions", () => { + describe("getAll", () => { + it("should calculate the correct allocation percentages and fiat values", () => { + const result = getAll(MOCK_DATA.categories).map((allocation) => ({ + ...allocation, + percentage: allocation.percentage.toString(), + fiatValue: allocation.fiatValue.toString(), + })); + + expect(result).toEqual([ + { + key: "available", + percentage: "33.333%", + fiatValue: "$10", + }, + { + key: "staked", + percentage: "16.666%", + fiatValue: "$5", + }, + { + key: "unstaking", + percentage: "16.666%", + fiatValue: "$5", + }, + { + key: "unclaimedRewards", + percentage: "16.666%", + fiatValue: "$5", + }, + { + key: "pooled", + percentage: "16.666%", + fiatValue: "$5", + }, + ]); + }); + }); + + describe("calculatePercentAndFiatValues", () => { + it("should calculate the correct asset percentages and fiat values", async () => { + const result = await calculatePercentAndFiatValues( + MOCK_DATA.categories, + assetLists, + "total-assets", + 5 + ).map((allocation) => ({ + ...allocation, + percentage: allocation.percentage.toString(), + fiatValue: allocation.fiatValue.toString(), + })); + + expect(result).toEqual([ + { + key: "CTK", + percentage: "50%", + fiatValue: "$30", + }, + { + key: "SAIL", + percentage: "33.333%", + fiatValue: "$20", + }, + { + key: "WOSMO", + percentage: "16.666%", + fiatValue: "$10", + }, + { + key: "Other", + percentage: "0%", + fiatValue: "$0", + }, + ]); + }); + + it("should calculate the correct asset percentages and fiat values", async () => { + const result = await calculatePercentAndFiatValues( + MOCK_DATA.categories, + assetLists, + "user-balances", + 5 + ).map((allocation) => ({ + ...allocation, + percentage: allocation.percentage.toString(), + fiatValue: allocation.fiatValue.toString(), + })); + + expect(result).toEqual([ + { + key: "SAIL", + percentage: "200%", + fiatValue: "$20", + }, + { + key: "WOSMO", + percentage: "100%", + fiatValue: "$10", + }, + { + key: "Other", + percentage: "0%", + fiatValue: "$0", + }, + ]); + }); + }); +}); diff --git a/packages/server/src/queries/complex/portfolio/allocation.ts b/packages/server/src/queries/complex/portfolio/allocation.ts new file mode 100644 index 0000000000..34211ed718 --- /dev/null +++ b/packages/server/src/queries/complex/portfolio/allocation.ts @@ -0,0 +1,157 @@ +import { CoinPretty, PricePretty } from "@keplr-wallet/unit"; +import { Dec, RatePretty } from "@keplr-wallet/unit"; +import { AssetList } from "@osmosis-labs/types"; +import { sort } from "@osmosis-labs/utils"; + +import { DEFAULT_VS_CURRENCY } from "../../../queries/complex/assets/config"; +import { queryAllocation } from "../../../queries/data-services"; +import { Categories } from "../../../queries/data-services"; +import { AccountCoinsResult } from "../../../queries/data-services"; +import { getAsset } from "../assets"; + +interface FormattedAllocation { + key: string; + percentage: RatePretty; + fiatValue: PricePretty; + asset?: CoinPretty; +} + +export interface GetAllocationResponse { + all: FormattedAllocation[]; + assets: FormattedAllocation[]; + available: FormattedAllocation[]; +} + +export function getAll(categories: Categories): FormattedAllocation[] { + const userBalancesCap = new Dec(categories["user-balances"].capitalization); + const stakedCap = new Dec(categories["staked"].capitalization); + const unstakingCap = new Dec(categories["unstaking"].capitalization); + const unclaimedRewardsCap = new Dec( + categories["unclaimed-rewards"].capitalization + ); + const pooledCap = new Dec(categories["pooled"].capitalization); + + const totalCap = userBalancesCap + .add(stakedCap) + .add(unstakingCap) + .add(unclaimedRewardsCap) + .add(pooledCap); + + return [ + { + key: "available", + percentage: new RatePretty(userBalancesCap.quo(totalCap)), + fiatValue: new PricePretty(DEFAULT_VS_CURRENCY, userBalancesCap), + }, + { + key: "staked", + percentage: new RatePretty(stakedCap.quo(totalCap)), + fiatValue: new PricePretty(DEFAULT_VS_CURRENCY, stakedCap), + }, + { + key: "unstaking", + percentage: new RatePretty(unstakingCap.quo(totalCap)), + fiatValue: new PricePretty(DEFAULT_VS_CURRENCY, unstakingCap), + }, + { + key: "unclaimedRewards", + percentage: new RatePretty(unclaimedRewardsCap.quo(totalCap)), + fiatValue: new PricePretty(DEFAULT_VS_CURRENCY, unclaimedRewardsCap), + }, + { + key: "pooled", + percentage: new RatePretty(pooledCap.quo(totalCap)), + fiatValue: new PricePretty(DEFAULT_VS_CURRENCY, pooledCap), + }, + ]; +} + +export function calculatePercentAndFiatValues( + categories: Categories, + assetLists: AssetList[], + category: "total-assets" | "user-balances", + allocationLimit +) { + const totalAssets = categories[category]; + const totalCap = new Dec(totalAssets.capitalization); + + const sortedAccountCoinsResults = sort( + totalAssets?.account_coins_result || [], + "cap_value", + "asc" + ); + + const topCoinsResults = sortedAccountCoinsResults.slice(0, allocationLimit); + + const assets: FormattedAllocation[] = topCoinsResults.map( + (asset: AccountCoinsResult) => { + const assetFromAssetLists = getAsset({ + assetLists, + anyDenom: asset.coin.denom, + }); + + return { + key: assetFromAssetLists.coinDenom, + percentage: new RatePretty(new Dec(asset.cap_value).quo(totalCap)), + fiatValue: new PricePretty( + DEFAULT_VS_CURRENCY, + new Dec(asset.cap_value) + ), + }; + } + ); + + const otherAssets = sortedAccountCoinsResults.slice(allocationLimit); + + const otherAmount = otherAssets.reduce( + (sum: Dec, asset: AccountCoinsResult) => sum.add(new Dec(asset.cap_value)), + new Dec(0) + ); + + const otherPercentage = new RatePretty(otherAmount).quo(totalCap); + + const other: FormattedAllocation = { + key: "Other", + percentage: otherPercentage, + fiatValue: new PricePretty(DEFAULT_VS_CURRENCY, otherAmount), + }; + + return [...assets, other]; +} + +export async function getAllocation({ + address, + assetLists, + allocationLimit = 5, +}: { + address: string; + assetLists: AssetList[]; + allocationLimit?: number; +}): Promise { + const data = await queryAllocation({ + address, + }); + + const categories = data.categories; + + const all = getAll(categories); + const assets = calculatePercentAndFiatValues( + categories, + assetLists, + "total-assets", + allocationLimit + ); + + const available = calculatePercentAndFiatValues( + categories, + assetLists, + "user-balances", + allocationLimit + ); + + return { + all, + assets, + available, + }; +} diff --git a/packages/server/src/queries/complex/portfolio/index.ts b/packages/server/src/queries/complex/portfolio/index.ts new file mode 100644 index 0000000000..8c741c990c --- /dev/null +++ b/packages/server/src/queries/complex/portfolio/index.ts @@ -0,0 +1 @@ +export * from "./allocation"; diff --git a/packages/server/src/queries/data-services/index.ts b/packages/server/src/queries/data-services/index.ts index d929431f5d..31295a31bf 100644 --- a/packages/server/src/queries/data-services/index.ts +++ b/packages/server/src/queries/data-services/index.ts @@ -1,3 +1,4 @@ +export * from "../sidecar/allocation"; export * from "./earn"; export * from "./filtered-pools"; export * from "./market-cap"; diff --git a/packages/server/src/queries/sidecar/allocation.ts b/packages/server/src/queries/sidecar/allocation.ts new file mode 100644 index 0000000000..5fae475503 --- /dev/null +++ b/packages/server/src/queries/sidecar/allocation.ts @@ -0,0 +1,45 @@ +import { apiClient } from "@osmosis-labs/utils"; + +import { SIDECAR_BASE_URL } from "../../env"; + +interface Coin { + denom: string; + amount: string; +} + +export interface AccountCoinsResult { + coin: Coin; + cap_value: string; +} +export interface Category { + capitalization: string; + is_best_effort: boolean; + account_coins_result?: AccountCoinsResult[]; +} + +export interface Categories { + "in-locks": Category; + pooled: Category; + staked: Category; + "total-assets": Category; + "unclaimed-rewards": Category; + unstaking: Category; + "user-balances": Category; +} + +export interface AllocationResponse { + categories: Categories; +} + +export async function queryAllocation({ + address, +}: { + address: string; +}): Promise { + const url = new URL( + `passthrough/portfolio-assets/${address}`, + SIDECAR_BASE_URL + ); + + return apiClient(url.toString()); +} diff --git a/packages/trpc/src/index.ts b/packages/trpc/src/index.ts index d556a04d45..8b8e389bbe 100644 --- a/packages/trpc/src/index.ts +++ b/packages/trpc/src/index.ts @@ -10,6 +10,7 @@ export * from "./one-click-trading"; export * from "./parameter-types"; export * from "./params"; export * from "./pools"; +export * from "./portfolio"; export * from "./staking"; export * from "./swap"; export * from "./transactions"; diff --git a/packages/trpc/src/portfolio.ts b/packages/trpc/src/portfolio.ts new file mode 100644 index 0000000000..b07f9125fe --- /dev/null +++ b/packages/trpc/src/portfolio.ts @@ -0,0 +1,20 @@ +import { getAllocation } from "@osmosis-labs/server"; +import { z } from "zod"; + +import { createTRPCRouter, publicProcedure } from "./api"; + +export const portfolioRouter = createTRPCRouter({ + getAllocation: publicProcedure + .input( + z.object({ + address: z.string(), + }) + ) + .query(async ({ input: { address }, ctx }) => { + const res = await getAllocation({ + address, + assetLists: ctx.assetLists, + }); + return res; + }), +}); diff --git a/packages/web/components/complex/portfolio-page.tsx b/packages/web/components/complex/portfolio-page.tsx index 431d2d8ab4..2a94fef07b 100644 --- a/packages/web/components/complex/portfolio-page.tsx +++ b/packages/web/components/complex/portfolio-page.tsx @@ -7,6 +7,7 @@ import { useRouter } from "next/router"; import { FunctionComponent, useCallback } from "react"; import { Icon } from "~/components/assets"; +import { Allocation } from "~/components/complex/portfolio/allocation"; import { AssetBalancesTable } from "~/components/table/asset-balances"; import { useDimension, @@ -51,6 +52,17 @@ export const PortfolioPage: FunctionComponent = () => { }, } ); + + const { data: allocation, isLoading: isLoadingAllocation } = + api.local.portfolio.getAllocation.useQuery( + { + address: wallet?.address ?? "", + }, + { + enabled: Boolean(wallet?.isWalletConnected && wallet?.address), + } + ); + const userHasNoAssets = totalValue && totalValue.toDec().isZero(); const [overviewRef, { height: overviewHeight }] = @@ -137,6 +149,11 @@ export const PortfolioPage: FunctionComponent = () => { )} +
+ {!isLoadingAllocation && !userHasNoAssets && ( + + )} +
); }; @@ -374,6 +391,7 @@ function useUserPositionsData(address: string | undefined) { }, } ); + const hasPositions = Boolean(positions?.length); const { data: allMyPoolDetails, isLoading: isLoadingMyPoolDetails } = diff --git a/packages/web/components/complex/portfolio/allocation-tabs.tsx b/packages/web/components/complex/portfolio/allocation-tabs.tsx new file mode 100644 index 0000000000..664d4ab9fd --- /dev/null +++ b/packages/web/components/complex/portfolio/allocation-tabs.tsx @@ -0,0 +1,66 @@ +import classNames from "classnames"; +import { FunctionComponent, useMemo } from "react"; + +import { useTranslation } from "~/hooks"; + +import { AllocationOptions } from "./types"; +export interface AllocationTabProps { + setTab: (tab: AllocationOptions) => void; + activeTab: AllocationOptions; +} + +export const AllocationTabs: FunctionComponent = ({ + setTab, + activeTab, +}) => { + const { t } = useTranslation(); + + const tabs = useMemo( + () => + [ + { + label: t("portfolio.all"), + value: "all", + }, + { + label: t("portfolio.assets"), + value: "assets", + }, + { + label: t("portfolio.available"), + value: "available", + }, + ] as { label: string; value: AllocationOptions }[], + [t] + ); + + return ( +
+ {tabs.map((tab) => { + const isActive = activeTab === tab.value; + return ( + + ); + })} +
+ ); +}; diff --git a/packages/web/components/complex/portfolio/allocation.tsx b/packages/web/components/complex/portfolio/allocation.tsx new file mode 100644 index 0000000000..44932418c6 --- /dev/null +++ b/packages/web/components/complex/portfolio/allocation.tsx @@ -0,0 +1,135 @@ +import { Dec } from "@keplr-wallet/unit"; +import { GetAllocationResponse } from "@osmosis-labs/server"; +import classNames from "classnames"; +import { FunctionComponent, useState } from "react"; + +import { Icon } from "~/components/assets"; +import { AllocationTabs } from "~/components/complex/portfolio/allocation-tabs"; +import { AllocationOptions } from "~/components/complex/portfolio/types"; +import { displayFiatPrice } from "~/components/transactions/transaction-utils"; +import { MultiLanguageT } from "~/hooks"; +import { useTranslation } from "~/hooks"; + +const COLORS: Record = { + all: [ + "bg-wosmongton-500", + "bg-ammelia-400", + "bg-osmoverse-500", + "bg-bullish-500", + "bg-ion-500", + ], + assets: [ + "bg-[#9C01D4]", + "bg-[#E9983D]", + "bg-[#2775CA]", + "bg-[#424667]", + "bg-[#009393]", + "bg-osmoverse-500", + ], + available: [ + "bg-[#9C01D4]", + "bg-[#E9983D]", + "bg-[#2775CA]", + "bg-[#424667]", + "bg-[#009393]", + "bg-osmoverse-500", + ], +}; + +const getTranslation = (key: string, t: MultiLanguageT): string => { + const translationMap: Record = { + available: t("portfolio.available"), + staked: t("portfolio.staked"), + unstaking: t("portfolio.unstaking"), + unclaimedRewards: t("portfolio.unclaimedRewards"), + pooled: t("portfolio.pooled"), + other: t("portfolio.other"), + }; + + return translationMap[key] || key; +}; + +export const Allocation: FunctionComponent<{ + allocation?: GetAllocationResponse; +}> = ({ allocation }) => { + const [selectedOption, setSelectedOption] = + useState("all"); + + const [isOpen, setIsOpen] = useState(true); + + const { t } = useTranslation(); + + if (!allocation) return null; + + const selectedList = allocation[selectedOption]; + + return ( +
+
setIsOpen(!isOpen)} + > +
{t("portfolio.allocation")}
+ +
+ {isOpen && ( + <> +
+ setSelectedOption(option)} + activeTab={selectedOption} + /> +
+
+ {selectedList.map(({ key, percentage }, index) => { + const isNegligiblePercent = percentage.toDec().lt(new Dec(0.01)); + + const width = isNegligiblePercent + ? "0.1%" + : percentage.toString(); + + const colorClass = + COLORS[selectedOption][index % COLORS[selectedOption].length]; + + return ( +
+ ); + })} +
+
+ {selectedList.map(({ key, percentage, fiatValue }, index) => { + const colorClass = + COLORS[selectedOption][index % COLORS[selectedOption].length]; + return ( +
+
+
+ {getTranslation(key, t)} + + {percentage.maxDecimals(0).toString()} + +
+
{displayFiatPrice(fiatValue, "", t)}
+
+ ); + })} +
+ + )} +
+ ); +}; diff --git a/packages/web/components/complex/portfolio/types.ts b/packages/web/components/complex/portfolio/types.ts new file mode 100644 index 0000000000..59f3d7b919 --- /dev/null +++ b/packages/web/components/complex/portfolio/types.ts @@ -0,0 +1 @@ +export type AllocationOptions = "all" | "assets" | "available"; diff --git a/packages/web/components/transactions/transaction-utils.tsx b/packages/web/components/transactions/transaction-utils.tsx index a53da6fda8..929ab8a4c0 100644 --- a/packages/web/components/transactions/transaction-utils.tsx +++ b/packages/web/components/transactions/transaction-utils.tsx @@ -53,8 +53,7 @@ export const displayFiatPrice = ( prefix: "-" | "+" | "", t: MultiLanguageT ): string => { - if (value === undefined || value?.toDec().equals(new Dec(0))) - return t("transactions.noPriceData"); + if (value === undefined) return t("transactions.noPriceData"); const decValue = value.toDec(); const symbol = value.symbol; diff --git a/packages/web/localizations/de.json b/packages/web/localizations/de.json index 582a6d5849..d5c07b8afb 100644 --- a/packages/web/localizations/de.json +++ b/packages/web/localizations/de.json @@ -325,7 +325,16 @@ "noAssets": "Sie haben keine Guthaben auf {osmosis}", "getStarted": "Zahlen Sie Guthaben ein, um loszulegen", "noPositions": "Sie haben keine Positionen", - "unlockPotential": "Schöpfen Sie Ihr volles Ertragspotenzial aus und nutzen Sie Ihr Vermögen mit Liquiditätspools" + "unlockPotential": "Schöpfen Sie Ihr volles Ertragspotenzial aus und nutzen Sie Ihr Vermögen mit Liquiditätspools", + "allocation": "Zuweisung", + "available": "Verfügbar", + "staked": "Abgesteckt", + "unstaking": "Aufheben der Absteckung", + "unclaimedRewards": "Nicht beanspruchte Belohnungen", + "pooled": "Gepoolt", + "other": "Andere", + "all": "Alle", + "assets": "Vermögenswerte" }, "buyTokens": "Kaufen Sie Token", "components": { diff --git a/packages/web/localizations/en.json b/packages/web/localizations/en.json index add7d050c2..adb56301e1 100644 --- a/packages/web/localizations/en.json +++ b/packages/web/localizations/en.json @@ -325,7 +325,16 @@ "noAssets": "You have no balances on {osmosis}", "getStarted": "Deposit assets to get started", "noPositions": "You have no positions", - "unlockPotential": "Unlock your full earning potential and put your assets to work with liquidity pools" + "unlockPotential": "Unlock your full earning potential and put your assets to work with liquidity pools", + "allocation": "Allocation", + "available": "Available", + "staked": "Staked", + "unstaking": "Unstaking", + "unclaimedRewards": "Unclaimed rewards", + "pooled": "Pooled", + "other": "Other", + "all": "All", + "assets": "Assets" }, "buyTokens": "Buy tokens", "components": { diff --git a/packages/web/localizations/es.json b/packages/web/localizations/es.json index c9cb860718..c36b8b25ee 100644 --- a/packages/web/localizations/es.json +++ b/packages/web/localizations/es.json @@ -325,7 +325,16 @@ "noAssets": "No tienes saldos en {osmosis}", "getStarted": "Deposite activos para comenzar", "noPositions": "no tienes posiciones", - "unlockPotential": "Libere todo su potencial de ganancias y ponga sus activos a trabajar con fondos de liquidez" + "unlockPotential": "Libere todo su potencial de ganancias y ponga sus activos a trabajar con fondos de liquidez", + "allocation": "Asignación", + "available": "Disponible", + "staked": "apostado", + "unstaking": "Desestacar", + "unclaimedRewards": "Recompensas no reclamadas", + "pooled": "agrupado", + "other": "Otro", + "all": "Todo", + "assets": "Activos" }, "buyTokens": "Comprar fichas", "components": { diff --git a/packages/web/localizations/fa.json b/packages/web/localizations/fa.json index 7d4c8e8131..b3ff03005c 100644 --- a/packages/web/localizations/fa.json +++ b/packages/web/localizations/fa.json @@ -325,7 +325,16 @@ "noAssets": "هیچ موجودی در {osmosis} ندارید", "getStarted": "برای شروع، دارایی ها را سپرده گذاری کنید", "noPositions": "شما هیچ مقامی ندارید", - "unlockPotential": "پتانسیل درآمد کامل خود را باز کنید و دارایی های خود را با استخرهای نقدینگی کار کنید" + "unlockPotential": "پتانسیل درآمد کامل خود را باز کنید و دارایی های خود را با استخرهای نقدینگی کار کنید", + "allocation": "تخصیص", + "available": "در دسترس", + "staked": "شرط بندی شده است", + "unstaking": "رها کردن", + "unclaimedRewards": "پاداش های بی ادعا", + "pooled": "جمع شده", + "other": "دیگر", + "all": "همه", + "assets": "دارایی های" }, "buyTokens": "خرید توکن", "components": { diff --git a/packages/web/localizations/fr.json b/packages/web/localizations/fr.json index 2dfc9009d7..4a93f9bc7e 100644 --- a/packages/web/localizations/fr.json +++ b/packages/web/localizations/fr.json @@ -325,7 +325,16 @@ "noAssets": "Vous n'avez aucun solde sur {osmosis}", "getStarted": "Déposez des actifs pour commencer", "noPositions": "Vous n'avez aucun poste", - "unlockPotential": "Libérez tout votre potentiel de revenus et faites fructifier vos actifs grâce aux pools de liquidités" + "unlockPotential": "Libérez tout votre potentiel de revenus et faites fructifier vos actifs grâce aux pools de liquidités", + "allocation": "Allocation", + "available": "Disponible", + "staked": "Jalonné", + "unstaking": "Détachement", + "unclaimedRewards": "Récompenses non réclamées", + "pooled": "Regroupé", + "other": "Autre", + "all": "Tous", + "assets": "Actifs" }, "buyTokens": "Acheter jetons", "components": { diff --git a/packages/web/localizations/gu.json b/packages/web/localizations/gu.json index 4111e421e1..7e1b52ba9a 100644 --- a/packages/web/localizations/gu.json +++ b/packages/web/localizations/gu.json @@ -325,7 +325,16 @@ "noAssets": "તમારી પાસે {osmosis} પર કોઈ બેલેન્સ નથી", "getStarted": "પ્રારંભ કરવા માટે અસ્કયામતો જમા કરો", "noPositions": "તમારી પાસે કોઈ હોદ્દા નથી", - "unlockPotential": "તમારી સંપૂર્ણ કમાણીની સંભાવનાને અનલૉક કરો અને તમારી સંપત્તિઓને લિક્વિડિટી પૂલ સાથે કામ કરવા માટે મૂકો" + "unlockPotential": "તમારી સંપૂર્ણ કમાણીની સંભાવનાને અનલૉક કરો અને તમારી સંપત્તિઓને લિક્વિડિટી પૂલ સાથે કામ કરવા માટે મૂકો", + "allocation": "ફાળવણી", + "available": "ઉપલબ્ધ છે", + "staked": "દાવ પર લગાવ્યો", + "unstaking": "અનસ્ટેકિંગ", + "unclaimedRewards": "દાવા વગરના પુરસ્કારો", + "pooled": "પૂલ", + "other": "અન્ય", + "all": "બધા", + "assets": "અસ્કયામતો" }, "buyTokens": "ટોકન્સ ખરીદો", "components": { diff --git a/packages/web/localizations/hi.json b/packages/web/localizations/hi.json index b84d183055..729d99d699 100644 --- a/packages/web/localizations/hi.json +++ b/packages/web/localizations/hi.json @@ -325,7 +325,16 @@ "noAssets": "आपके पास {osmosis} पर कोई शेष राशि नहीं है", "getStarted": "आरंभ करने के लिए संपत्ति जमा करें", "noPositions": "आपके पास कोई पद नहीं है", - "unlockPotential": "अपनी पूरी कमाई क्षमता को अनलॉक करें और अपनी परिसंपत्तियों को तरलता पूल के साथ काम में लगाएं" + "unlockPotential": "अपनी पूरी कमाई क्षमता को अनलॉक करें और अपनी परिसंपत्तियों को तरलता पूल के साथ काम में लगाएं", + "allocation": "आवंटन", + "available": "उपलब्ध", + "staked": "दांव पर लगा दिया", + "unstaking": "अनस्टेकिंग", + "unclaimedRewards": "दावा न किए गए पुरस्कार", + "pooled": "जमा", + "other": "अन्य", + "all": "सभी", + "assets": "संपत्ति" }, "buyTokens": "टोकन खरीदें", "components": { diff --git a/packages/web/localizations/ja.json b/packages/web/localizations/ja.json index d2756372f5..8c2e6b07dc 100644 --- a/packages/web/localizations/ja.json +++ b/packages/web/localizations/ja.json @@ -325,7 +325,16 @@ "noAssets": "{osmosis}に残高がありません", "getStarted": "始めるには資産を預けてください", "noPositions": "ポジションがありません", - "unlockPotential": "流動性プールを活用して収益の可能性を最大限に引き出し、資産を有効活用しましょう" + "unlockPotential": "流動性プールを活用して収益の可能性を最大限に引き出し、資産を有効活用しましょう", + "allocation": "割り当て", + "available": "利用可能", + "staked": "賭けられた", + "unstaking": "ステーキング解除", + "unclaimedRewards": "未請求の報酬", + "pooled": "プール", + "other": "他の", + "all": "全て", + "assets": "資産" }, "buyTokens": "トークンを購入する", "components": { diff --git a/packages/web/localizations/ko.json b/packages/web/localizations/ko.json index d668903097..9453f32e53 100644 --- a/packages/web/localizations/ko.json +++ b/packages/web/localizations/ko.json @@ -325,7 +325,16 @@ "noAssets": "{osmosis} 에 잔액이 없습니다.", "getStarted": "시작하려면 자산을 예치하세요", "noPositions": "직책이 없습니다.", - "unlockPotential": "귀하의 수익 잠재력을 최대한 활용하고 자산을 유동성 풀과 함께 활용하세요" + "unlockPotential": "귀하의 수익 잠재력을 최대한 활용하고 자산을 유동성 풀과 함께 활용하세요", + "allocation": "배당", + "available": "사용 가능", + "staked": "스테이킹됨", + "unstaking": "언스테이킹", + "unclaimedRewards": "청구하지 않은 보상", + "pooled": "풀링됨", + "other": "다른", + "all": "모두", + "assets": "자산" }, "buyTokens": "토큰 구매", "components": { diff --git a/packages/web/localizations/pl.json b/packages/web/localizations/pl.json index 30dc23a193..d791d0fa8a 100644 --- a/packages/web/localizations/pl.json +++ b/packages/web/localizations/pl.json @@ -325,7 +325,16 @@ "noAssets": "Nie masz salda na {osmosis}", "getStarted": "Aby rozpocząć, zdeponuj środki", "noPositions": "Nie masz żadnych stanowisk", - "unlockPotential": "Odblokuj swój pełny potencjał zarobkowy i wykorzystaj swoje aktywa dzięki pulom płynności" + "unlockPotential": "Odblokuj swój pełny potencjał zarobkowy i wykorzystaj swoje aktywa dzięki pulom płynności", + "allocation": "Przydział", + "available": "Dostępny", + "staked": "Postawione", + "unstaking": "Odstawienie", + "unclaimedRewards": "Nieodebrane nagrody", + "pooled": "Połączone", + "other": "Inny", + "all": "Wszystko", + "assets": "Aktywa" }, "buyTokens": "Kup tokeny", "components": { diff --git a/packages/web/localizations/pt-br.json b/packages/web/localizations/pt-br.json index 50d40248c0..00901a42e9 100644 --- a/packages/web/localizations/pt-br.json +++ b/packages/web/localizations/pt-br.json @@ -325,7 +325,16 @@ "noAssets": "Você não tem saldos em {osmosis}", "getStarted": "Deposite ativos para começar", "noPositions": "Você não tem posições", - "unlockPotential": "Desbloqueie todo o seu potencial de ganhos e coloque seus ativos para trabalhar com pools de liquidez" + "unlockPotential": "Desbloqueie todo o seu potencial de ganhos e coloque seus ativos para trabalhar com pools de liquidez", + "allocation": "Alocação", + "available": "Disponível", + "staked": "Apostado", + "unstaking": "Desempilhar", + "unclaimedRewards": "Recompensas não reclamadas", + "pooled": "Agrupados", + "other": "Outro", + "all": "Todos", + "assets": "Ativos" }, "buyTokens": "Comprar tokens", "components": { diff --git a/packages/web/localizations/ro.json b/packages/web/localizations/ro.json index 79e4a4def6..8239cf8e62 100644 --- a/packages/web/localizations/ro.json +++ b/packages/web/localizations/ro.json @@ -325,7 +325,16 @@ "noAssets": "Nu aveți solduri pe {osmosis}", "getStarted": "Depuneți active pentru a începe", "noPositions": "Nu ai posturi", - "unlockPotential": "Deblocați-vă întregul potențial de câștig și puneți-vă activele în funcțiune cu fonduri de lichiditate" + "unlockPotential": "Deblocați-vă întregul potențial de câștig și puneți-vă activele în funcțiune cu fonduri de lichiditate", + "allocation": "Alocare", + "available": "Disponibil", + "staked": "Mizat", + "unstaking": "Dezlegarea", + "unclaimedRewards": "Recompense nerevendicate", + "pooled": "Adunate", + "other": "Alte", + "all": "Toate", + "assets": "Active" }, "buyTokens": "Cumpărați jetoane", "components": { diff --git a/packages/web/localizations/ru.json b/packages/web/localizations/ru.json index ca9e3a7d71..d38464415d 100644 --- a/packages/web/localizations/ru.json +++ b/packages/web/localizations/ru.json @@ -325,7 +325,16 @@ "noAssets": "У вас нет остатков по {osmosis}", "getStarted": "Внесите активы, чтобы начать", "noPositions": "У вас нет должностей", - "unlockPotential": "Раскройте весь свой потенциал заработка и заставьте свои активы работать с пулами ликвидности" + "unlockPotential": "Раскройте весь свой потенциал заработка и заставьте свои активы работать с пулами ликвидности", + "allocation": "Распределение", + "available": "Доступный", + "staked": "Ставка", + "unstaking": "Снять ставку", + "unclaimedRewards": "Невостребованные награды", + "pooled": "Объединенный", + "other": "Другой", + "all": "Все", + "assets": "Ресурсы" }, "buyTokens": "Купить токены", "components": { diff --git a/packages/web/localizations/tr.json b/packages/web/localizations/tr.json index ffe40237cc..c651f1ec76 100644 --- a/packages/web/localizations/tr.json +++ b/packages/web/localizations/tr.json @@ -325,7 +325,16 @@ "noAssets": "{osmosis} üzerinde bakiyeniz yok", "getStarted": "Başlamak için varlık yatırın", "noPositions": "Hiç pozisyonunuz yok", - "unlockPotential": "Tüm kazanç potansiyelinizi ortaya çıkarın ve varlıklarınızı likidite havuzlarıyla çalıştırın" + "unlockPotential": "Tüm kazanç potansiyelinizi ortaya çıkarın ve varlıklarınızı likidite havuzlarıyla çalıştırın", + "allocation": "Paylaştırma", + "available": "Mevcut", + "staked": "Bahis", + "unstaking": "Stake kaldırma", + "unclaimedRewards": "Talep edilmeyen ödüller", + "pooled": "havuzlanmış", + "other": "Diğer", + "all": "Tüm", + "assets": "Varlıklar" }, "buyTokens": "jeton satın al", "components": { diff --git a/packages/web/localizations/zh-cn.json b/packages/web/localizations/zh-cn.json index e31c0d5a83..2c191fc338 100644 --- a/packages/web/localizations/zh-cn.json +++ b/packages/web/localizations/zh-cn.json @@ -325,7 +325,16 @@ "noAssets": "您在{osmosis}上没有余额", "getStarted": "存入资产即可开始", "noPositions": "您没有职位", - "unlockPotential": "释放您的全部盈利潜力,利用流动资金池来运作您的资产" + "unlockPotential": "释放您的全部盈利潜力,利用流动资金池来运作您的资产", + "allocation": "分配", + "available": "可用的", + "staked": "已质押", + "unstaking": "取消质押", + "unclaimedRewards": "未领取的奖励", + "pooled": "池化", + "other": "其他", + "all": "全部", + "assets": "资产" }, "buyTokens": "购买代币", "components": { diff --git a/packages/web/localizations/zh-hk.json b/packages/web/localizations/zh-hk.json index 1b75ccd5a2..be21c92f08 100644 --- a/packages/web/localizations/zh-hk.json +++ b/packages/web/localizations/zh-hk.json @@ -325,7 +325,16 @@ "noAssets": "您{osmosis}沒有餘額", "getStarted": "存入資產即可開始", "noPositions": "您沒有職位", - "unlockPotential": "釋放您的全部盈利潛力,並將您的資產與流動性池結合起來" + "unlockPotential": "釋放您的全部盈利潛力,並將您的資產與流動性池結合起來", + "allocation": "分配", + "available": "可用的", + "staked": "質押", + "unstaking": "取消質押", + "unclaimedRewards": "無人認領的獎勵", + "pooled": "匯集", + "other": "其他", + "all": "全部", + "assets": "資產" }, "buyTokens": "購買代幣", "components": { diff --git a/packages/web/localizations/zh-tw.json b/packages/web/localizations/zh-tw.json index 42b510f1d9..409d4ce9e3 100644 --- a/packages/web/localizations/zh-tw.json +++ b/packages/web/localizations/zh-tw.json @@ -325,7 +325,16 @@ "noAssets": "您{osmosis}沒有餘額", "getStarted": "存入資產即可開始", "noPositions": "您沒有職位", - "unlockPotential": "釋放您的全部盈利潛力,並將您的資產與流動性池結合起來" + "unlockPotential": "釋放您的全部盈利潛力,並將您的資產與流動性池結合起來", + "allocation": "分配", + "available": "可用的", + "staked": "質押", + "unstaking": "取消質押", + "unclaimedRewards": "無人認領的獎勵", + "pooled": "匯集", + "other": "其他", + "all": "全部", + "assets": "資產" }, "buyTokens": "購買代幣", "components": { diff --git a/packages/web/server/api/local-router.ts b/packages/web/server/api/local-router.ts index c978947a59..ab067d113b 100644 --- a/packages/web/server/api/local-router.ts +++ b/packages/web/server/api/local-router.ts @@ -4,6 +4,7 @@ import { concentratedLiquidityRouter, createTRPCRouter, oneClickTradingRouter, + portfolioRouter, swapRouter, } from "@osmosis-labs/trpc"; @@ -19,4 +20,5 @@ export const localRouter = createTRPCRouter({ oneClickTrading: oneClickTradingRouter, cms: cmsRouter, bridgeTransfer: localBridgeTransferRouter, + portfolio: portfolioRouter, }); From 580ffdc0bbcf021b74c83012e7cb3ec72a31eea0 Mon Sep 17 00:00:00 2001 From: Jon Ator Date: Mon, 5 Aug 2024 12:45:28 -0400 Subject: [PATCH 5/5] Jonator/fixes (#3676) * fix param * add invariant --- .../server/src/queries/complex/portfolio/allocation.ts | 2 +- packages/web/server/api/routers/bridge-transfer.ts | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/server/src/queries/complex/portfolio/allocation.ts b/packages/server/src/queries/complex/portfolio/allocation.ts index 34211ed718..662000c5c7 100644 --- a/packages/server/src/queries/complex/portfolio/allocation.ts +++ b/packages/server/src/queries/complex/portfolio/allocation.ts @@ -70,7 +70,7 @@ export function calculatePercentAndFiatValues( categories: Categories, assetLists: AssetList[], category: "total-assets" | "user-balances", - allocationLimit + allocationLimit = 5 ) { const totalAssets = categories[category]; const totalCap = new Dec(totalAssets.capitalization); diff --git a/packages/web/server/api/routers/bridge-transfer.ts b/packages/web/server/api/routers/bridge-transfer.ts index 8b1fb06522..752f7f3f9d 100644 --- a/packages/web/server/api/routers/bridge-transfer.ts +++ b/packages/web/server/api/routers/bridge-transfer.ts @@ -96,6 +96,14 @@ export const bridgeTransferRouter = createTRPCRouter({ /** If the bridge takes longer than 10 seconds to respond, we should timeout that quote. */ const quote = await timeout(quoteFn, 10 * 1000)(); + // Basic circuit breaker to validate some invariants + // from input + given quote + if (input.fromAsset.address !== quote.input.address) { + throw new Error( + `Invalid quote: Expected fromAsset address ${input.fromAsset.address} but got ${quote.input.address} in quote` + ); + } + /** * Since transfer fee is deducted from input amount, * we overwrite the transfer fee asset to be the input