diff --git a/.github/workflows/frontend-e2e-tests.yml b/.github/workflows/frontend-e2e-tests.yml index 25ce07182a..d2ae837888 100644 --- a/.github/workflows/frontend-e2e-tests.yml +++ b/.github/workflows/frontend-e2e-tests.yml @@ -26,7 +26,7 @@ jobs: - name: Install Playwright run: | yarn --cwd packages/web install --frozen-lockfile && npx playwright install --with-deps chromium - - name: Run Select Swap Pair tests on Master + - name: Run Swap Pair tests on Master env: BASE_URL: "https://app.osmosis.zone" PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }} @@ -63,7 +63,7 @@ jobs: - name: Install Playwright run: | yarn --cwd packages/web install --frozen-lockfile && npx playwright install --with-deps chromium - - name: Run Select Swap Pair tests on Stage + - name: Run Swap Pair tests on Stage env: BASE_URL: ${{ github.event.deployment_status.environment_url }} PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }} diff --git a/packages/server/src/env.ts b/packages/server/src/env.ts index 9dd0650d73..2caab21f81 100644 --- a/packages/server/src/env.ts +++ b/packages/server/src/env.ts @@ -22,10 +22,15 @@ export const TIMESERIES_DATA_URL = export const INDEXER_DATA_URL = process.env.NEXT_PUBLIC_INDEXER_DATA_URL ?? "https://stage-proxy-data-indexer.osmosis-labs.workers.dev"; +export const NUMIA_BASE_URL = + process.env.NEXT_PUBLIC_NUMIA_BASE_URL ?? + "https://public-osmosis-api.numia.xyz"; + +// sqs export const SIDECAR_BASE_URL = process.env.NEXT_PUBLIC_SIDECAR_BASE_URL ?? "https://sqs.osmosis.zone/"; export const TFM_BASE_URL = process.env.NEXT_PUBLIC_TFM_API_BASE_URL; -export const NUMIA_BASE_URL = "https://public-osmosis-api.numia.xyz"; + export const KEYBASE_BASE_URL = "https://keybase.io/"; export const KV_STORE_REST_API_URL = process.env.KV_STORE_REST_API_URL; export const KV_STORE_REST_API_TOKEN = process.env.KV_STORE_REST_API_TOKEN; diff --git a/packages/server/src/queries/complex/concentrated-liquidity/index.ts b/packages/server/src/queries/complex/concentrated-liquidity/index.ts index 06e601e835..517a96d72b 100644 --- a/packages/server/src/queries/complex/concentrated-liquidity/index.ts +++ b/packages/server/src/queries/complex/concentrated-liquidity/index.ts @@ -255,10 +255,6 @@ export async function mapGetUserPositionDetails({ ...params, bech32Address: userOsmoAddress, }); - const stakeCurrencyPromise = getAsset({ - ...params, - anyDenom: params.chainList[0].staking.staking_tokens[0].denom, - }); const superfluidPoolIdsPromise = getSuperfluidPoolIds(params); const [ @@ -266,17 +262,20 @@ export async function mapGetUserPositionDetails({ userUnbondingPositions, delegatedPositions, undelegatingPositions, - stakeCurrency, superfluidPoolIds, ] = await Promise.all([ positionsPromise, userUnbondingPositionsPromise, delegatedPositionsPromise, undelegatingPositionsPromise, - stakeCurrencyPromise, superfluidPoolIdsPromise, ]); + const stakeCurrency = getAsset({ + ...params, + anyDenom: params.chainList[0].staking.staking_tokens[0].denom, + }); + const lockableDurations = getLockableDurations(); const longestLockDuration = lockableDurations[lockableDurations.length - 1]; @@ -585,23 +584,28 @@ export type PositionHistoricalPerformance = Awaited< /** Gets a breakdown of current and reward coins, with fiat values, for a single CL position. */ export async function getPositionHistoricalPerformance({ - positionId, + position: givenPosition, ...params }: { assetLists: AssetList[]; chainList: Chain[]; - positionId: string; + /** Position by ID or the returned position object. */ + position: string | LiquidityPosition; }) { - const [{ position }, performance] = await Promise.all([ - queryPositionById({ ...params, id: positionId }), - queryPositionPerformance({ - positionId, - }), - ]); + const { position } = + typeof givenPosition === "string" + ? await queryPositionById({ ...params, id: givenPosition }) + : { position: givenPosition }; + + const performance = await queryPositionPerformance({ + positionId: position.position.position_id, + }); // There is no performance data for this position if (performance.message) { - console.error(`No performance data for position ${positionId}`); + console.error( + `No performance data for position ${position.position.position_id}` + ); } // get all user CL coins, including claimable rewards @@ -651,31 +655,33 @@ export async function getPositionHistoricalPerformance({ // calculate fiat values - const currentValue = new PricePretty( - DEFAULT_VS_CURRENCY, - await calcSumCoinsValue({ ...params, coins: currentCoins }).catch((e) => - captureErrorAndReturn(e, new Dec(0)) - ) - ); - const currentCoinsValues = ( - await Promise.all( + const [ + currentValue, + currentCoinsValues, + principalValue, + claimableRewardsValue, + totalEarnedValue, + ] = await Promise.all([ + calcSumCoinsValue({ ...params, coins: currentCoins }) + .then((value) => new PricePretty(DEFAULT_VS_CURRENCY, value)) + .catch(() => new PricePretty(DEFAULT_VS_CURRENCY, new Dec(0))), + Promise.all( currentCoins .map((coin) => calcCoinValue({ ...params, coin })) .map((p) => p.catch((e) => captureErrorAndReturn(e, 0))) - ) - ).map((p) => new PricePretty(DEFAULT_VS_CURRENCY, p)); - const principalValue = new PricePretty( - DEFAULT_VS_CURRENCY, - await calcSumCoinsValue({ ...params, coins: principalCoins }) - ); - const claimableRewardsValue = new PricePretty( - DEFAULT_VS_CURRENCY, - await calcSumCoinsValue({ ...params, coins: claimableRewardCoins }) - ); - const totalEarnedValue = new PricePretty( - DEFAULT_VS_CURRENCY, - await calcSumCoinsValue({ ...params, coins: totalRewardCoins }) - ); + ).then((values) => + values.map((p) => new PricePretty(DEFAULT_VS_CURRENCY, p)) + ), + calcSumCoinsValue({ ...params, coins: principalCoins }).then( + (value) => new PricePretty(DEFAULT_VS_CURRENCY, value) + ), + calcSumCoinsValue({ ...params, coins: claimableRewardCoins }).then( + (value) => new PricePretty(DEFAULT_VS_CURRENCY, value) + ), + calcSumCoinsValue({ ...params, coins: totalRewardCoins }).then( + (value) => new PricePretty(DEFAULT_VS_CURRENCY, value) + ), + ]); const principalValueDec = principalValue.toDec(); diff --git a/packages/server/src/queries/complex/pools/bonding.ts b/packages/server/src/queries/complex/pools/bonding.ts index 9ad52a6e6e..1e19421ff5 100644 --- a/packages/server/src/queries/complex/pools/bonding.ts +++ b/packages/server/src/queries/complex/pools/bonding.ts @@ -166,9 +166,11 @@ export async function getSharePoolBondDurations({ userLockedLocks.forEach((userDurationLock) => { userDurationLock.coins.forEach((coin) => { if (getShareDenomPoolId(coin.denom) === poolId) { - userShares = userShares.add( - new CoinPretty(userShares.currency, coin.amount) + const lockedShares = new CoinPretty( + userShares.currency, + coin.amount ); + userShares = userShares.add(lockedShares); } }); userLockedLockIds.push(userDurationLock.ID); @@ -334,6 +336,7 @@ export async function getSharePoolBondDurations({ return { duration: dayjs.duration(durationMs), bondable: isSuperfluid ? isLongestDuration : Boolean(durationGauges), + /** Locked shares */ userShares, userLockedShareValue, userLocks: userLockedLockIds.map((lockId) => ({ diff --git a/packages/server/src/queries/complex/pools/user.ts b/packages/server/src/queries/complex/pools/user.ts index 2ad7a4dc33..be812dabdc 100644 --- a/packages/server/src/queries/complex/pools/user.ts +++ b/packages/server/src/queries/complex/pools/user.ts @@ -1,4 +1,4 @@ -import { CoinPretty, Dec, IntPretty, PricePretty } from "@keplr-wallet/unit"; +import { Dec, IntPretty, PricePretty } from "@keplr-wallet/unit"; import { AssetList, Chain } from "@osmosis-labs/types"; import { aggregateRawCoinsByDenom, timeout } from "@osmosis-labs/utils"; @@ -201,44 +201,54 @@ export async function getUserSharePools(params: { // underlying assets behind all shares // when catching: likely shares balance is too small for precision - const underlyingAvailableCoins: CoinPretty[] = available - ? await getGammShareUnderlyingCoins({ ...params, ...available }).catch( - (e) => captureErrorAndReturn(e, []) - ) - : []; - const underlyingLockedCoins: CoinPretty[] = locked - ? await getGammShareUnderlyingCoins({ ...params, ...locked }).catch((e) => - captureErrorAndReturn(e, []) - ) - : []; - const underlyingUnlockingCoins: CoinPretty[] = unlocking - ? await getGammShareUnderlyingCoins({ ...params, ...unlocking }).catch( - (e) => captureErrorAndReturn(e, []) - ) - : []; - const totalCoins: CoinPretty[] = total - ? await getGammShareUnderlyingCoins({ ...params, ...total }).catch((e) => - captureErrorAndReturn(e, []) - ) - : []; + const [ + underlyingAvailableCoins, + underlyingLockedCoins, + underlyingUnlockingCoins, + totalCoins, + ] = await Promise.all([ + available + ? getGammShareUnderlyingCoins({ ...params, ...available }).catch((e) => + captureErrorAndReturn(e, []) + ) + : Promise.resolve([]), + locked + ? getGammShareUnderlyingCoins({ ...params, ...locked }).catch((e) => + captureErrorAndReturn(e, []) + ) + : Promise.resolve([]), + unlocking + ? getGammShareUnderlyingCoins({ ...params, ...unlocking }).catch((e) => + captureErrorAndReturn(e, []) + ) + : Promise.resolve([]), + total + ? getGammShareUnderlyingCoins({ ...params, ...total }).catch((e) => + captureErrorAndReturn(e, []) + ) + : Promise.resolve([]), + ]); // value of all shares - const availableValue = await calcSumCoinsValue({ - ...params, - coins: underlyingAvailableCoins, - }); - const lockedValue = await calcSumCoinsValue({ - ...params, - coins: underlyingLockedCoins, - }); - const unlockingValue = await calcSumCoinsValue({ - ...params, - coins: underlyingUnlockingCoins, - }); - const totalValue = await calcSumCoinsValue({ - ...params, - coins: totalCoins, - }); + const [availableValue, lockedValue, unlockingValue, totalValue] = + await Promise.all([ + calcSumCoinsValue({ + ...params, + coins: underlyingAvailableCoins, + }), + calcSumCoinsValue({ + ...params, + coins: underlyingLockedCoins, + }), + calcSumCoinsValue({ + ...params, + coins: underlyingUnlockingCoins, + }), + calcSumCoinsValue({ + ...params, + coins: totalCoins, + }), + ]); // get locks containing this pool's shares const lockedLocks = userLocks.filter( diff --git a/packages/server/src/queries/complex/staking/index.ts b/packages/server/src/queries/complex/staking/index.ts index 41e864b152..eeab172829 100644 --- a/packages/server/src/queries/complex/staking/index.ts +++ b/packages/server/src/queries/complex/staking/index.ts @@ -1,3 +1,4 @@ export * from "./apr"; +export * from "./superfluid"; export * from "./user"; export * from "./validator"; diff --git a/packages/server/src/queries/complex/staking/superfluid.ts b/packages/server/src/queries/complex/staking/superfluid.ts new file mode 100644 index 0000000000..78f45a5dac --- /dev/null +++ b/packages/server/src/queries/complex/staking/superfluid.ts @@ -0,0 +1,71 @@ +import { CoinPretty, Dec, DecUtils } from "@keplr-wallet/unit"; +import { AssetList, Chain } from "@osmosis-labs/types"; +import cachified, { CacheEntry } from "cachified"; +import { LRUCache } from "lru-cache"; + +import { DEFAULT_LRU_OPTIONS } from "../../../utils"; +import { + querySuperfluidAssetMultiplier, + querySuperfluidParams, +} from "../../osmosis"; +import { getAsset } from "../assets"; +import { getShareDenomPoolId, makeGammShareCurrency } from "../pools"; + +const cache = new LRUCache(DEFAULT_LRU_OPTIONS); + +/** Calculates the OSMO equivalent amount for the given superfluid asset. */ +export async function calcOsmoSuperfluidEquivalent({ + amount, + denom, + chainList, + assetLists, +}: { + amount: string; + denom: string; + chainList: Chain[]; + assetLists: AssetList[]; +}) { + return cachified({ + cache: cache, + key: `osmo-equivalent-${denom}-${amount}`, + ttl: 1000 * 30, // 30 seconds + getFreshValue: async () => { + // primary chain + const chain = chainList[0]; + + const stakeDenom = chain.staking.staking_tokens[0].denom; + const stakeAsset = getAsset({ assetLists, anyDenom: stakeDenom }); + const equivalentAsset = denom.startsWith("gamm") + ? makeGammShareCurrency(getShareDenomPoolId(denom)) + : getAsset({ assetLists, anyDenom: denom }); + + const multipication = DecUtils.getTenExponentN( + equivalentAsset.coinDecimals - stakeAsset.coinDecimals + ); + + const [minimumRiskFactor, assetMultiplier] = await Promise.all([ + querySuperfluidParams({ + chainList, + }).then(({ params }) => new Dec(params.minimum_risk_factor)), + querySuperfluidAssetMultiplier({ + chainList, + denom, + }).then( + ({ osmo_equivalent_multiplier: { multiplier } }) => + new Dec(multiplier) + ), + ]); + + const multiplier = assetMultiplier + .mul(new Dec(1).sub(minimumRiskFactor)) + .mul(multipication); + + return new CoinPretty( + stakeAsset, + new CoinPretty(equivalentAsset, amount) + .mul(multiplier) + .mul(DecUtils.getTenExponentN(stakeAsset.coinDecimals)) + ); + }, + }); +} diff --git a/packages/server/src/queries/complex/staking/validator.ts b/packages/server/src/queries/complex/staking/validator.ts index 7cb057d05e..ae6b99a63a 100644 --- a/packages/server/src/queries/complex/staking/validator.ts +++ b/packages/server/src/queries/complex/staking/validator.ts @@ -36,7 +36,7 @@ export async function getValidatorInfo({ return cachified({ cache: validatorsCache, key: `validator-${validatorBech32Address}`, - ttl: 1000 * 10, // 10 seconds + ttl: 1000 * 30, // 30 seconds getFreshValue: async () => { let jailed = false; let inactive = false; @@ -89,3 +89,29 @@ export async function getValidatorInfo({ }, }); } + +export async function getValidatorsWithInfos({ + chainList, + status, +}: { + chainList: Chain[]; + status: BondStatus; +}) { + return cachified({ + cache: validatorsCache, + key: `validator-infos-${status}`, + ttl: 1000 * 30, // 30 seconds + getFreshValue: async () => { + const validators = await getValidators({ chainList, status }); + + return Promise.all( + validators.map((validator) => + getValidatorInfo({ + chainList, + validatorBech32Address: validator.operator_address, + }).then((info) => ({ ...validator, ...info })) + ) + ); + }, + }); +} diff --git a/packages/server/src/queries/github/token-info.ts b/packages/server/src/queries/github/token-info.ts index f98666636c..60889773b4 100644 --- a/packages/server/src/queries/github/token-info.ts +++ b/packages/server/src/queries/github/token-info.ts @@ -20,13 +20,20 @@ export interface TokenCMSData { } export const getTokenInfo = (denom: string, lang: string) => { - const fileName = `${denom.toLowerCase()}_token_info_${lang.toLowerCase()}.json`; + const fileName = `${denom.toLowerCase()}_asset_detail_${lang.toLowerCase()}.json`; - if (!CMS_REPOSITORY_PATH) - throw new Error("Forgot to set CMS_REPOSITORY_PATH env var"); - if (!GITHUB_URL) throw new Error("Forgot to set GITHUB_URL env var"); + if (!CMS_REPOSITORY_PATH || !GITHUB_URL) { + const missingVars = [ + !CMS_REPOSITORY_PATH ? "CMS_REPOSITORY_PATH" : "", + !GITHUB_URL ? "GITHUB_URL" : "", + ] + .filter(Boolean) + .join(", "); + console.error(`Missing environment variables: ${missingVars}`); + throw new Error(`Missing environment variables: ${missingVars}`); + } return githubApi - .get(`${CMS_REPOSITORY_PATH}/tokens/${fileName}`) + .get(`${CMS_REPOSITORY_PATH}/${fileName}`) .then((r) => r.data); }; diff --git a/packages/server/src/queries/osmosis/superfluid/asset-multiplier.ts b/packages/server/src/queries/osmosis/superfluid/asset-multiplier.ts new file mode 100644 index 0000000000..25af1aea17 --- /dev/null +++ b/packages/server/src/queries/osmosis/superfluid/asset-multiplier.ts @@ -0,0 +1,19 @@ +import { createNodeQuery } from "../../create-node-query"; + +export type SuperfluidAssetMultiplier = { + osmo_equivalent_multiplier: { + // Int + epoch_number: string; + denom: string; + // Dec + multiplier: string; + }; +}; + +export const querySuperfluidAssetMultiplier = createNodeQuery< + SuperfluidAssetMultiplier, + { denom: string } +>({ + path: ({ denom }) => + `/osmosis/superfluid/v1beta1/asset_multiplier?denom=${denom}`, +}); diff --git a/packages/server/src/queries/osmosis/superfluid/index.ts b/packages/server/src/queries/osmosis/superfluid/index.ts index 1617950d5d..dcde046bc2 100644 --- a/packages/server/src/queries/osmosis/superfluid/index.ts +++ b/packages/server/src/queries/osmosis/superfluid/index.ts @@ -1,6 +1,7 @@ export * from "./account-delegated-positions"; export * from "./account-undelegating-positions"; export * from "./all-superfluid-assets"; +export * from "./asset-multiplier"; export * from "./delegations"; export * from "./superfluid-params"; export * from "./types"; diff --git a/packages/stores/src/account/__tests_e2e__/exit-pool.spec.ts b/packages/stores/src/account/__tests_e2e__/exit-pool.spec.ts index 5c4660e384..6de3172396 100644 --- a/packages/stores/src/account/__tests_e2e__/exit-pool.spec.ts +++ b/packages/stores/src/account/__tests_e2e__/exit-pool.spec.ts @@ -58,7 +58,13 @@ describe("Exit Pool Tx", () => { const account = accountStore.getWallet(TestOsmosisChainId); await expect( - account?.osmosis.sendExitPoolMsg(queryPool!.id, "0") + account?.osmosis.sendExitPoolMsg( + queryPool!.id, + "0", + queryPool!.sharePool!.totalShare, + queryPool!.poolAssets.map((a) => a.amount.toCoin()), + queryPool!.sharePool!.exitFee + ) ).rejects.not.toBeNull(); }); @@ -68,10 +74,19 @@ describe("Exit Pool Tx", () => { await expect( new Promise((resolve, rejects) => { account?.osmosis - .sendExitPoolMsg(queryPool!.id, "50", "0", "", (tx) => { - if (tx.code) rejects(tx); - else resolve(tx); - }) + .sendExitPoolMsg( + queryPool!.id, + "50", + queryPool!.sharePool!.totalShare, + queryPool!.poolAssets.map((a) => a.amount.toCoin()), + queryPool!.sharePool!.exitFee, + "0", + "", + (tx) => { + if (tx.code) rejects(tx); + else resolve(tx); + } + ) .catch((e) => rejects(e)); }) ).resolves.toBeDefined(); @@ -83,10 +98,19 @@ describe("Exit Pool Tx", () => { await expect( new Promise((resolve, rejects) => { account?.osmosis - .sendExitPoolMsg(queryPool!.id, "50", "1", "", (tx) => { - if (tx.code) rejects(tx); - else resolve(tx); - }) + .sendExitPoolMsg( + queryPool!.id, + "50", + queryPool!.sharePool!.totalShare, + queryPool!.poolAssets.map((a) => a.amount.toCoin()), + queryPool!.sharePool!.exitFee, + "1", + "", + (tx) => { + if (tx.code) rejects(tx); + else resolve(tx); + } + ) .catch((e) => rejects(e)); }) ).resolves.toBeDefined(); @@ -98,10 +122,19 @@ describe("Exit Pool Tx", () => { await expect( new Promise((resolve, rejects) => { account?.osmosis - .sendExitPoolMsg(queryPool!.id, "100.1", "", "", (tx) => { - if (tx.code) rejects(tx); - else resolve(tx); - }) + .sendExitPoolMsg( + queryPool!.id, + "100.1", + queryPool!.sharePool!.totalShare, + queryPool!.poolAssets.map((a) => a.amount.toCoin()), + queryPool!.sharePool!.exitFee, + "", + "", + (tx) => { + if (tx.code) rejects(tx); + else resolve(tx); + } + ) .catch((e) => rejects(e)); }) ).rejects.not.toBeNull(); diff --git a/packages/stores/src/account/base.ts b/packages/stores/src/account/base.ts index 7068755bf0..8d8fe365d5 100644 --- a/packages/stores/src/account/base.ts +++ b/packages/stores/src/account/base.ts @@ -905,7 +905,7 @@ export class AccountStore[] = []> { }) as Uint8Array; const privateKey = new PrivKeySecp256k1( - fromBase64(oneClickTradingInfo.privateKey) + fromBase64(oneClickTradingInfo.sessionKey) ); const gasLimit = Int53.fromString(String(fee.gas)).toNumber(); diff --git a/packages/stores/src/account/osmosis/index.ts b/packages/stores/src/account/osmosis/index.ts index d18d523e6e..a533fddde1 100644 --- a/packages/stores/src/account/osmosis/index.ts +++ b/packages/stores/src/account/osmosis/index.ts @@ -1300,75 +1300,69 @@ export class OsmosisAccountImpl { async sendExitPoolMsg( poolId: string, shareInAmount: string, + poolTotalShares: Int, + poolAssets: { denom: string; amount: string }[], + poolExitFee: Dec, maxSlippage: string = DEFAULT_SLIPPAGE, memo: string = "", onFulfill?: (tx: DeliverTxResponse) => void ) { - const queries = this.queries; const mkp = this.makeCoinPretty; - await this.base.signAndBroadcast( - this.chainId, - "exitPool", - async () => { - const queryPool = queries.queryPools.getPool(poolId); - - if (!queryPool) { - throw new Error(`Pool #${poolId} not found`); - } - - await queryPool.waitFreshResponse(); - - const pool = queryPool.sharePool; - if (!pool) { - throw new Error("Unknown pool"); - } - - const estimated = OsmosisMath.estimateExitSwap( - pool, - mkp, - shareInAmount, - this.msgOpts.exitPool.shareCoinDecimals - ); - - const maxSlippageDec = new Dec(maxSlippage).quo( - DecUtils.getTenExponentNInPrecisionRange(2) - ); + const estimated = OsmosisMath.estimateExitSwap( + { + totalShare: poolTotalShares, + poolAssets: poolAssets.map((asset) => ({ + ...asset, + amount: new Int(asset.amount), + })), + exitFee: poolExitFee, + }, + mkp, + shareInAmount, + this.msgOpts.exitPool.shareCoinDecimals + ); - const tokenOutMins = maxSlippageDec.equals(new Dec(0)) - ? [] - : estimated.tokenOuts.map((tokenOut) => { - return { - denom: tokenOut.currency.coinMinimalDenom, - amount: tokenOut - .toDec() // TODO: confirm toDec() respects token dec count - .mul(new Dec(1).sub(maxSlippageDec)) - .mul( - DecUtils.getTenExponentNInPrecisionRange( - tokenOut.currency.coinDecimals - ) - ) - .truncate() - .toString(), - }; - }); + const maxSlippageDec = new Dec(maxSlippage).quo( + DecUtils.getTenExponentNInPrecisionRange(2) + ); - const msg = this.msgOpts.exitPool.messageComposer({ - poolId: BigInt(pool.id), - sender: this.address, - shareInAmount: new Dec(shareInAmount) - .mul( - DecUtils.getTenExponentNInPrecisionRange( - this.msgOpts.exitPool.shareCoinDecimals + const tokenOutMins = maxSlippageDec.equals(new Dec(0)) + ? [] + : estimated.tokenOuts.map((tokenOut) => { + return { + denom: tokenOut.currency.coinMinimalDenom, + amount: tokenOut + .toDec() // TODO: confirm toDec() respects token dec count + .mul(new Dec(1).sub(maxSlippageDec)) + .mul( + DecUtils.getTenExponentNInPrecisionRange( + tokenOut.currency.coinDecimals + ) ) - ) - .truncate() - .toString(), - tokenOutMins, + .truncate() + .toString(), + }; }); - return [msg]; - }, + const msg = this.msgOpts.exitPool.messageComposer({ + poolId: BigInt(poolId), + sender: this.address, + shareInAmount: new Dec(shareInAmount) + .mul( + DecUtils.getTenExponentNInPrecisionRange( + this.msgOpts.exitPool.shareCoinDecimals + ) + ) + .truncate() + .toString(), + tokenOutMins, + }); + + await this.base.signAndBroadcast( + this.chainId, + "exitPool", + [msg], memo, undefined, undefined, @@ -1392,7 +1386,7 @@ export class OsmosisAccountImpl { * Lock tokens for some duration into a lock. Useful for allowing the user to capture bonding incentives. * * @param duration Duration, in seconds, to lock up the tokens. - * @param tokens Tokens to lock. `amount`s are not in micro. + * @param tokens Base token amount to lock. * @param memo Transaction memo. * @param onFulfill Callback to handle tx fullfillment given raw response. */ @@ -1406,14 +1400,8 @@ export class OsmosisAccountImpl { onFulfill?: (tx: DeliverTxResponse) => void ) { const primitiveTokens = tokens.map((token) => { - const amount = new Dec(token.amount) - .mul( - DecUtils.getTenExponentNInPrecisionRange(token.currency.coinDecimals) - ) - .truncate(); - return { - amount: amount.toString(), + amount: token.amount, denom: token.currency.coinMinimalDenom, }; }); @@ -1452,7 +1440,7 @@ export class OsmosisAccountImpl { ); } - /** https://docs.osmosis.zone/overview/osmo.html#superfluid-staking + /** * @param lockIds Ids of LP bonded locks. * @param validatorAddress Bech32 address of validator to delegate to. * @param memo Tx memo. @@ -1464,6 +1452,8 @@ export class OsmosisAccountImpl { memo: string = "", onFulfill?: (tx: DeliverTxResponse) => void ) { + if (lockIds.length === 0) throw new Error("No locks to delegate"); + const msgs = lockIds.map((lockId) => { return this.msgOpts.superfluidDelegate.messageComposer({ sender: this.address, @@ -1506,7 +1496,7 @@ export class OsmosisAccountImpl { } /** https://docs.osmosis.zone/overview/osmo.html#superfluid-staking - * @param tokens LP tokens to delegate and lock. `amount`s are not in micro. + * @param tokens LP tokens to delegate and lock. * @param validatorAddress Validator address to delegate to. * @param memo Tx memo. * @param onFulfill Callback to handle tx fullfillment. @@ -1521,14 +1511,8 @@ export class OsmosisAccountImpl { onFulfill?: (tx: DeliverTxResponse) => void ) { const primitiveTokens = tokens.map((token) => { - const amount = new Dec(token.amount) - .mul( - DecUtils.getTenExponentNInPrecisionRange(token.currency.coinDecimals) - ) - .truncate(); - return { - amount: amount.toString(), + amount: token.amount, denom: token.currency.coinMinimalDenom, }; }); diff --git a/packages/stores/src/account/types.ts b/packages/stores/src/account/types.ts index c442b0e51b..d1a945f218 100644 --- a/packages/stores/src/account/types.ts +++ b/packages/stores/src/account/types.ts @@ -7,7 +7,6 @@ import { import { Currency, OneClickTradingHumanizedSessionPeriod, - OneClickTradingResetPeriods, OneClickTradingTimeLimit, } from "@osmosis-labs/types"; import { MsgData } from "cosmjs-types/cosmos/base/abci/v1beta1/abci"; @@ -103,7 +102,7 @@ export interface TxEvents { export interface OneClickTradingInfo { readonly authenticatorId: string; readonly publicKey: string; - readonly privateKey: string; + readonly sessionKey: string; readonly userOsmoAddress: string; networkFeeLimit: Currency & { @@ -115,9 +114,6 @@ export interface OneClickTradingInfo { amount: string; }; - // Period to reset the spend limit quota. - readonly resetPeriod: OneClickTradingResetPeriods; - // Time limit for the session to be considered valid. readonly sessionPeriod: OneClickTradingTimeLimit; readonly humanizedSessionPeriod: OneClickTradingHumanizedSessionPeriod; diff --git a/packages/trpc/src/balances.ts b/packages/trpc/src/balances.ts new file mode 100644 index 0000000000..ff8125a9e5 --- /dev/null +++ b/packages/trpc/src/balances.ts @@ -0,0 +1,47 @@ +import { CoinPretty } from "@keplr-wallet/unit"; +import { + captureIfError, + getAsset, + getShareDenomPoolId, + makeGammShareCurrency, + queryBalances, +} from "@osmosis-labs/server"; +import z from "zod"; + +import { createTRPCRouter, publicProcedure } from "./api"; + +export const balancesRouter = createTRPCRouter({ + getUserBalances: publicProcedure + .input( + z.object({ bech32Address: z.string(), chainId: z.string().optional() }) + ) + .query(({ input, ctx }) => + queryBalances({ + ...input, + ...ctx, + }).then((res) => + res.balances.map(({ denom, amount }) => { + if (denom.startsWith("gamm")) { + return { + denom, + amount, + coin: new CoinPretty( + makeGammShareCurrency(getShareDenomPoolId(denom)), + amount + ), + }; + } else { + const asset = captureIfError(() => + getAsset({ ...ctx, anyDenom: denom }) + ); + + return { + denom, + amount, + coin: asset ? new CoinPretty(asset, amount) : undefined, + }; + } + }) + ) + ), +}); diff --git a/packages/trpc/src/concentrated-liquidity.ts b/packages/trpc/src/concentrated-liquidity.ts index 26a9d425ed..2de64f504e 100644 --- a/packages/trpc/src/concentrated-liquidity.ts +++ b/packages/trpc/src/concentrated-liquidity.ts @@ -10,6 +10,44 @@ import { z } from "zod"; import { createTRPCRouter, publicProcedure } from "./api"; import { UserOsmoAddressSchema } from "./parameter-types"; +const LiquidityPositionSchema = z.object({ + position: z.object({ + position_id: z.string(), + address: z.string(), + join_time: z.string(), + liquidity: z.string(), + lower_tick: z.string(), + pool_id: z.string(), + upper_tick: z.string(), + }), + asset0: z.object({ + amount: z.string(), + denom: z.string(), + }), + asset1: z.object({ + amount: z.string(), + denom: z.string(), + }), + claimable_spread_rewards: z.array( + z.object({ + denom: z.string(), + amount: z.string(), + }) + ), + claimable_incentives: z.array( + z.object({ + denom: z.string(), + amount: z.string(), + }) + ), + forfeited_incentives: z.array( + z.object({ + denom: z.string(), + amount: z.string(), + }) + ), +}); + export const concentratedLiquidityRouter = createTRPCRouter({ getUserPositions: publicProcedure .input( @@ -31,28 +69,33 @@ export const concentratedLiquidityRouter = createTRPCRouter({ .input( z .object({ - positionId: z.string(), + position: z.union([z.string(), LiquidityPositionSchema]), }) .merge(UserOsmoAddressSchema.required()) ) - .query(async ({ input: { positionId, userOsmoAddress }, ctx }) => { - const { position } = await queryPositionById({ ...ctx, id: positionId }); + .query( + async ({ input: { position: givenPosition, userOsmoAddress }, ctx }) => { + const { position } = + typeof givenPosition === "string" + ? await queryPositionById({ ...ctx, id: givenPosition }) + : { position: givenPosition }; - return ( - await mapGetUserPositionDetails({ - ...ctx, - positions: [position], - userOsmoAddress, - }) - )[0]; - }), + return ( + await mapGetUserPositionDetails({ + ...ctx, + positions: [position], + userOsmoAddress, + }) + )[0]; + } + ), getPositionHistoricalPerformance: publicProcedure .input( z.object({ - positionId: z.string(), + position: z.union([z.string(), LiquidityPositionSchema]), }) ) - .query(({ input: { positionId }, ctx }) => - getPositionHistoricalPerformance({ ...ctx, positionId }) + .query(({ input: { position }, ctx }) => + getPositionHistoricalPerformance({ ...ctx, position }) ), }); diff --git a/packages/trpc/src/index.ts b/packages/trpc/src/index.ts index adb234f3f7..e8e2bd47e8 100644 --- a/packages/trpc/src/index.ts +++ b/packages/trpc/src/index.ts @@ -1,5 +1,6 @@ export * from "./api"; export * from "./assets"; +export * from "./balances"; export * from "./cms"; export * from "./concentrated-liquidity"; export * from "./earn"; diff --git a/packages/trpc/src/one-click-trading.ts b/packages/trpc/src/one-click-trading.ts index 419e6c731d..6c50b8701c 100644 --- a/packages/trpc/src/one-click-trading.ts +++ b/packages/trpc/src/one-click-trading.ts @@ -6,7 +6,6 @@ import { getFeeTokenGasPriceStep, getSessionAuthenticator, queryAuthenticatorSpendLimit, - queryBaseAccount, } from "@osmosis-labs/server"; import { AssetList, @@ -27,7 +26,7 @@ export const oneClickTradingRouter = createTRPCRouter({ }): Promise< Pick< OneClickTradingTransactionParams, - "networkFeeLimit" | "resetPeriod" | "spendLimit" | "sessionPeriod" + "networkFeeLimit" | "spendLimit" | "sessionPeriod" > & { spendLimitTokenDecimals: number; } @@ -44,7 +43,6 @@ export const oneClickTradingRouter = createTRPCRouter({ spendLimit: new PricePretty(DEFAULT_VS_CURRENCY, new Dec(5_000)), spendLimitTokenDecimals: usdcAsset.coinDecimals, networkFeeLimit: networkFeeLimitStep.average, - resetPeriod: "day" as const, sessionPeriod: { end: "1hour" as const, }, @@ -77,24 +75,16 @@ export const oneClickTradingRouter = createTRPCRouter({ return sessionAuthenticator; }), - getAccountPubKeyAndAuthenticators: publicProcedure + getAuthenticators: publicProcedure .input(UserOsmoAddressSchema.required()) .query(async ({ input, ctx }) => { - const [cosmosAccount, authenticators] = await Promise.all([ - queryBaseAccount({ - bech32Address: input.userOsmoAddress, - chainList: ctx.chainList, - }), - getAuthenticators({ - userOsmoAddress: input.userOsmoAddress, - chainList: ctx.chainList, - }), - ]); + const authenticators = await getAuthenticators({ + userOsmoAddress: input.userOsmoAddress, + chainList: ctx.chainList, + }); return { - accountPubKey: cosmosAccount.account.pub_key?.key, authenticators, - shouldAddFirstAuthenticator: authenticators.length === 0, }; }), getAmountSpent: publicProcedure diff --git a/packages/trpc/src/staking.ts b/packages/trpc/src/staking.ts index a459553c39..a3dd2b3b3e 100644 --- a/packages/trpc/src/staking.ts +++ b/packages/trpc/src/staking.ts @@ -1,7 +1,14 @@ -import { getAverageStakingApr } from "@osmosis-labs/server"; +import { + calcOsmoSuperfluidEquivalent, + getAverageStakingApr, + getValidatorsWithInfos, + queryDelegations, +} from "@osmosis-labs/server"; +import { BondStatus } from "@osmosis-labs/types"; import { z } from "zod"; import { createTRPCRouter, publicProcedure } from "./api"; +import { UserOsmoAddressSchema } from "./parameter-types"; export const stakingRouter = createTRPCRouter({ getApr: publicProcedure @@ -12,4 +19,34 @@ export const stakingRouter = createTRPCRouter({ }) ) .query(async ({ input }) => getAverageStakingApr(input)), + getValidators: publicProcedure + .input( + z.object({ + status: z.enum(["Bonded", "Unbonded", "Unbonding", "Unspecified"]), + }) + ) + .query(async ({ input, ctx }) => + getValidatorsWithInfos({ ...ctx, status: BondStatus[input.status] }) + ), + getUserDelegations: publicProcedure + .input(UserOsmoAddressSchema.required()) + .query(({ input, ctx }) => + queryDelegations({ + chainList: ctx.chainList, + bech32Address: input.userOsmoAddress, + }).then(({ delegation_responses }) => delegation_responses) + ), + getOsmoEquivalent: publicProcedure + .input( + z.object({ + denom: z.string(), + amount: z.string(), + }) + ) + .query(async ({ input, ctx }) => + calcOsmoSuperfluidEquivalent({ + ...ctx, + ...input, + }) + ), }); diff --git a/packages/types/src/autheticator-types.ts b/packages/types/src/autheticator-types.ts index 05db49f8cd..24d9ab8ea7 100644 --- a/packages/types/src/autheticator-types.ts +++ b/packages/types/src/autheticator-types.ts @@ -12,8 +12,8 @@ export interface RawAuthenticator { } export interface RawNestedAuthenticator { - Config: string; - Type: AuthenticatorType; + config: string; + type: AuthenticatorType; } export interface MessageFilterAuthenticator { diff --git a/packages/types/src/one-click-trading-types.ts b/packages/types/src/one-click-trading-types.ts index 330203a41c..81ded5d591 100644 --- a/packages/types/src/one-click-trading-types.ts +++ b/packages/types/src/one-click-trading-types.ts @@ -25,9 +25,6 @@ export interface OneClickTradingTransactionParams { spendLimit: PricePretty; networkFeeLimit: CoinPretty; - // Period to reset the spend limit quota. - resetPeriod: OneClickTradingResetPeriods; - // Time limit for the session to be considered valid. sessionPeriod: { end: OneClickTradingHumanizedSessionPeriod; diff --git a/packages/utils/src/authenticator.ts b/packages/utils/src/authenticator.ts index 5358f9b5a1..8248d80bcc 100644 --- a/packages/utils/src/authenticator.ts +++ b/packages/utils/src/authenticator.ts @@ -86,16 +86,16 @@ export function parseNestedAuthenticator({ authenticator: RawNestedAuthenticator; }): NestedAuthenticator { try { - if (rawNestedAuthenticator.Type === "SignatureVerification") { + if (rawNestedAuthenticator.type === "SignatureVerification") { return { type: "SignatureVerification", - publicKey: rawNestedAuthenticator.Config, + publicKey: rawNestedAuthenticator.config, }; } - if (rawNestedAuthenticator.Type === "CosmwasmAuthenticatorV1") { + if (rawNestedAuthenticator.type === "CosmwasmAuthenticatorV1") { const parsedData: { contract: string; params: string } = JSON.parse( - Buffer.from(rawNestedAuthenticator.Config, "base64").toString("utf-8") + Buffer.from(rawNestedAuthenticator.config, "base64").toString("utf-8") ); const parsedParams = JSON.parse( Buffer.from(parsedData.params, "base64").toString("utf-8") @@ -107,9 +107,9 @@ export function parseNestedAuthenticator({ } as CosmwasmAuthenticatorV1; } - if (rawNestedAuthenticator.Type === "MessageFilter") { + if (rawNestedAuthenticator.type === "MessageFilter") { const parsedData: { "@type": string } = JSON.parse( - Buffer.from(rawNestedAuthenticator.Config, "base64").toString("utf-8") + Buffer.from(rawNestedAuthenticator.config, "base64").toString("utf-8") ); return { type: "MessageFilter", @@ -118,15 +118,15 @@ export function parseNestedAuthenticator({ } if ( - rawNestedAuthenticator.Type === "AnyOf" || - rawNestedAuthenticator.Type === "AllOf" + rawNestedAuthenticator.type === "AnyOf" || + rawNestedAuthenticator.type === "AllOf" ) { const subAuthenticators: RawNestedAuthenticator[] = JSON.parse( - Buffer.from(rawNestedAuthenticator.Config, "base64").toString("utf-8") + Buffer.from(rawNestedAuthenticator.config, "base64").toString("utf-8") ); return { - type: rawNestedAuthenticator.Type, + type: rawNestedAuthenticator.type, subAuthenticators: subAuthenticators.map( (subAuthenticator: RawNestedAuthenticator) => { return parseNestedAuthenticator({ @@ -138,7 +138,7 @@ export function parseNestedAuthenticator({ } throw new Error( - `Unknown nested authenticator type: ${rawNestedAuthenticator.Type}` + `Unknown nested authenticator type: ${rawNestedAuthenticator.type}` ); } catch (error) { console.error("Error parsing nested authenticator:", { diff --git a/packages/web/.env b/packages/web/.env index 35c781e68e..7182cddb4a 100644 --- a/packages/web/.env +++ b/packages/web/.env @@ -6,7 +6,7 @@ GITHUB_URL=https://raw.githubusercontent.com/osmosis-labs/ -CMS_REPOSITORY_PATH=token-info/main/contents +CMS_REPOSITORY_PATH=assetlists/main/osmosis-1/generated/asset_detail NEXT_PUBLIC_TFM_API_BASE_URL=https://api.tfm.com SENTRY_IGNORE_API_RESOLUTION_ERROR=1 NEXT_PUBLIC_SQUID_INTEGRATOR_ID=osmosis-api diff --git a/packages/web/components/ad-banner/__tests__/ad-banner-content.spec.tsx b/packages/web/components/ad-banner/__tests__/ad-banner-content.spec.tsx index 1342fc32c8..d32e8e4c9c 100644 --- a/packages/web/components/ad-banner/__tests__/ad-banner-content.spec.tsx +++ b/packages/web/components/ad-banner/__tests__/ad-banner-content.spec.tsx @@ -1,4 +1,4 @@ -import { screen } from "@testing-library/react"; +import { act, screen } from "@testing-library/react"; import { renderWithProviders } from "~/__tests__/test-utils"; import { SwapAdBannerResponse } from "~/pages"; @@ -21,7 +21,10 @@ test("renders ad banner content correctly", () => { featured: true, }; - renderWithProviders(); + act(() => { + renderWithProviders(); + }); + const headerElement = screen.getByText(mockAd.headerOrTranslationKey); const subheaderElement = screen.getByText(mockAd.subheaderOrTranslationKey); const imageElement = screen.getByAltText(mockAd.iconImageAltOrTranslationKey); @@ -62,12 +65,15 @@ test("renders ad banner content with localization correctly", () => { const ad = mockAdResponseWithLocalization.banners[0]; - renderWithProviders( - - ); + act(() => { + renderWithProviders( + + ); + }); + const headerElement = screen.getByText("Mock Header"); const subheaderElement = screen.getByText("Mock Subheader"); const imageElement = screen.getByAltText("Mock Icon"); diff --git a/packages/web/components/cards/bond-card.tsx b/packages/web/components/cards/bond-card.tsx index f37ad69201..78285140b3 100644 --- a/packages/web/components/cards/bond-card.tsx +++ b/packages/web/components/cards/bond-card.tsx @@ -1,4 +1,4 @@ -import { CoinPretty, Dec, RatePretty } from "@keplr-wallet/unit"; +import { CoinPretty, RatePretty } from "@keplr-wallet/unit"; import type { BondDuration } from "@osmosis-labs/server"; import classNames from "classnames"; import moment from "dayjs"; @@ -42,10 +42,10 @@ export const BondCard: FunctionComponent< const showGoSuperfluid = superfluid && - userShares.toDec().gt(new Dec(0)) && + userShares.toDec().isPositive() && !superfluid.delegated && !superfluid.undelegating; - const showUnbond = userShares.toDec().gt(new Dec(0)); + const showUnbond = userShares.toDec().isPositive(); // useful for calculating the height of the card const hasThreeButtons = showUnbond && showGoSuperfluid && userUnlockingShares; diff --git a/packages/web/components/cards/my-position/index.tsx b/packages/web/components/cards/my-position/index.tsx index c669eff97a..32bc51e130 100644 --- a/packages/web/components/cards/my-position/index.tsx +++ b/packages/web/components/cards/my-position/index.tsx @@ -23,17 +23,14 @@ export const MyPositionCard: FunctionComponent<{ const { accountStore, chainStore } = useStore(); const { chainId } = chainStore.osmosis; const account = accountStore.getWallet(chainId); + const { showLinkToPool = false, position } = props; const { - showLinkToPool = false, - position: { - id, - poolId, - currentCoins, - currentValue, - priceRange: [lowerPrice, upperPrice], - isFullRange, - }, - } = props; + poolId, + currentCoins, + currentValue, + priceRange: [lowerPrice, upperPrice], + isFullRange, + } = position; const { t } = useTranslation(); const [collapsed, setCollapsed] = useState(true); const featureFlags = useFeatureFlags(); @@ -41,7 +38,7 @@ export const MyPositionCard: FunctionComponent<{ const { data: positionPerformance } = api.local.concentratedLiquidity.getPositionHistoricalPerformance.useQuery( { - positionId: id, + position: position.position, }, { trpc: { @@ -55,7 +52,7 @@ export const MyPositionCard: FunctionComponent<{ const { data: positionDetails, isLoading: isLoadingPositionDetails } = api.local.concentratedLiquidity.getPositionDetails.useQuery( { - positionId: id, + position: position.position, userOsmoAddress: account?.address ?? "", }, { diff --git a/packages/web/components/chart/light-weight-charts/chart.tsx b/packages/web/components/chart/light-weight-charts/chart.tsx index 76047d8d67..0d0ee8b596 100644 --- a/packages/web/components/chart/light-weight-charts/chart.tsx +++ b/packages/web/components/chart/light-weight-charts/chart.tsx @@ -1,7 +1,6 @@ import { ColorType, DeepPartial, - isBusinessDay, LineStyle, MouseEventParams, TickMarkType, @@ -17,6 +16,10 @@ import React, { useSyncExternalStore, } from "react"; +import { + priceFormatter, + timepointToString, +} from "~/components/chart/light-weight-charts/utils"; import { theme } from "~/tailwind.config"; import { @@ -33,38 +36,6 @@ function resizeSubscribe(callback: (this: Window, ev: UIEvent) => unknown) { }; } -const timepointToString = ( - timePoint: Time, - formatOptions: Intl.DateTimeFormatOptions, - locale?: string -) => { - let date = new Date(); - - if (typeof timePoint === "string") { - date = new Date(timePoint); - } else if (!isBusinessDay(timePoint)) { - date = new Date((timePoint as number) * 1000); - } else { - date = new Date( - Date.UTC(timePoint.year, timePoint.month - 1, timePoint.day) - ); - } - - // from given date we should use only as UTC date or timestamp - // but to format as locale date we can convert UTC date to local date - const localDateFromUtc = new Date( - date.getUTCFullYear(), - date.getUTCMonth(), - date.getUTCDate(), - date.getUTCHours(), - date.getUTCMinutes(), - date.getUTCSeconds(), - date.getUTCMilliseconds() - ); - - return localDateFromUtc.toLocaleString(locale, formatOptions); -}; - export const defaultOptions: DeepPartial = { layout: { fontFamily: theme.fontFamily.subtitle1.join(","), @@ -76,15 +47,38 @@ export const defaultOptions: DeepPartial = { fontSize: 14, }, grid: { horzLines: { visible: false }, vertLines: { visible: false } }, - rightPriceScale: { visible: false }, - leftPriceScale: { visible: false }, + rightPriceScale: { + autoScale: true, + borderVisible: false, + ticksVisible: false, + scaleMargins: { + top: 0.25, + bottom: 0, + }, + }, + leftPriceScale: { + autoScale: true, + borderVisible: false, + ticksVisible: false, + scaleMargins: { + top: 0.25, + bottom: 0, + }, + }, crosshair: { - horzLine: { visible: false }, + horzLine: { + labelBackgroundColor: theme.colors.osmoverse[850], + style: LineStyle.LargeDashed, + width: 2, + color: `${theme.colors.osmoverse[300]}33`, + labelVisible: false, + }, vertLine: { labelBackgroundColor: theme.colors.osmoverse[850], style: LineStyle.LargeDashed, width: 2, color: `${theme.colors.osmoverse[300]}33`, + labelVisible: false, }, }, handleScroll: false, @@ -94,6 +88,7 @@ export const defaultOptions: DeepPartial = { mouse: false, }, localization: { + priceFormatter, timeFormatter: (timePoint: Time) => { const formatOptions: Intl.DateTimeFormatOptions = { year: "numeric", @@ -206,7 +201,7 @@ export const Chart = memo( }, []); return ( -
+
{children}
); diff --git a/packages/web/components/chart/light-weight-charts/linear-chart.ts b/packages/web/components/chart/light-weight-charts/linear-chart.ts index f95f473467..c140365ae6 100644 --- a/packages/web/components/chart/light-weight-charts/linear-chart.ts +++ b/packages/web/components/chart/light-weight-charts/linear-chart.ts @@ -63,10 +63,11 @@ export class LinearChartController extends AreaChartController { return `
- $ ${ formatPretty(closeDec, { maxDecimals, + currency: "USD", + style: "currency", ...formatOpts, }) || "" } diff --git a/packages/web/components/chart/light-weight-charts/utils.ts b/packages/web/components/chart/light-weight-charts/utils.ts new file mode 100644 index 0000000000..590f4a70af --- /dev/null +++ b/packages/web/components/chart/light-weight-charts/utils.ts @@ -0,0 +1,41 @@ +import { Dec } from "@keplr-wallet/unit"; +import { isBusinessDay, Time } from "lightweight-charts"; + +import { formatPretty, getPriceExtendedFormatOptions } from "~/utils/formatter"; +import { getDecimalCount } from "~/utils/number"; + +export const priceFormatter = (price: number) => { + const minimumDecimals = 2; + const maxDecimals = Math.max(getDecimalCount(price), minimumDecimals); + + const priceDec = new Dec(price); + + const formatOpts = getPriceExtendedFormatOptions(priceDec); + + return formatPretty(priceDec, { + maxDecimals, + currency: "USD", + style: "currency", + ...formatOpts, + }); +}; + +export const timepointToString = ( + timePoint: Time, + formatOptions: Intl.DateTimeFormatOptions, + locale?: string +) => { + let date = new Date(); + + if (typeof timePoint === "string") { + date = new Date(timePoint); + } else if (!isBusinessDay(timePoint)) { + date = new Date((timePoint as number) * 1000); + } else { + date = new Date( + Date.UTC(timePoint.year, timePoint.month - 1, timePoint.day) + ); + } + + return date.toLocaleString(locale, formatOptions); +}; diff --git a/packages/web/components/chart/price-historical-v2.tsx b/packages/web/components/chart/price-historical-v2.tsx index 3531f413a1..0890cf3086 100644 --- a/packages/web/components/chart/price-historical-v2.tsx +++ b/packages/web/components/chart/price-historical-v2.tsx @@ -2,32 +2,40 @@ import { AreaData, AreaSeriesOptions, DeepPartial, + Time, UTCTimestamp, } from "lightweight-charts"; import React, { FunctionComponent, memo } from "react"; -import { LinearChartController } from "~/components/chart/light-weight-charts/linear-chart"; +import { AreaChartController } from "~/components/chart/light-weight-charts/area-chart"; +import { theme } from "~/tailwind.config"; import { Chart } from "./light-weight-charts/chart"; const seriesOpt: DeepPartial = { - lineColor: "#8C8AF9", - topColor: "rgba(60, 53, 109, 1)", - bottomColor: "rgba(32, 27, 67, 1)", + lineColor: theme.colors.wosmongton[300], + topColor: theme.colors.osmoverse[700], + bottomColor: theme.colors.osmoverse[850], priceLineVisible: false, - priceScaleId: "left", + lastValueVisible: false, + priceScaleId: "right", crosshairMarkerBorderWidth: 0, crosshairMarkerRadius: 8, + priceFormat: { + type: "price", + precision: 10, + minMove: 0.0000001, + }, }; export const HistoricalPriceChartV2: FunctionComponent<{ data: { close: number; time: number }[]; - onPointerHover?: (price: number) => void; + onPointerHover?: (price: number, time: Time) => void; onPointerOut?: () => void; }> = memo(({ data = [], onPointerHover, onPointerOut }) => { return ( 0) { const [data] = [...params.seriesData.values()] as AreaData[]; - onPointerHover?.(data.value); + onPointerHover?.(data.value, data.time); } else { onPointerOut?.(); } diff --git a/packages/web/components/chart/price-historical.tsx b/packages/web/components/chart/price-historical.tsx index 07de50a271..40b7c2439e 100644 --- a/packages/web/components/chart/price-historical.tsx +++ b/packages/web/components/chart/price-historical.tsx @@ -250,6 +250,7 @@ export const PriceChartHeader: FunctionComponent<{ historicalRange: PriceRange; setHistoricalRange: (pr: PriceRange) => void; hoverPrice: number; + hoverDate?: string | null; decimal: number; formatOpts?: FormatOptions; fiatSymbol?: string; @@ -272,6 +273,7 @@ export const PriceChartHeader: FunctionComponent<{ setHistoricalRange, baseDenom, quoteDenom, + hoverDate, hoverPrice, formatOpts, decimal, @@ -318,29 +320,45 @@ export const PriceChartHeader: FunctionComponent<{ classes?.pricesHeaderContainerClass )} > - -

- {fiatSymbol} - {compactZeros ? ( - <> - {significantDigits}. - {Boolean(zeros) && ( - <> - 0{zeros} - - )} - {decimalDigits} - - ) : ( - getFormattedPrice() - )} -

-
+
+ +

+ {fiatSymbol} + {compactZeros ? ( + <> + {significantDigits}. + {Boolean(zeros) && ( + <> + 0{zeros} + + )} + {decimalDigits} + + ) : ( + getFormattedPrice() + )} +

+
+ {hoverDate !== undefined ? ( +

+ {hoverDate} +

+ ) : ( + false + )} +
{baseDenom && quoteDenom ? (
void; + onUpdate: (amount: string) => void; onMax: () => void; currentValue: string; percentage: RatePretty; @@ -32,16 +33,15 @@ export const DepositAmountGroup: FunctionComponent<{ priceInputClass, outOfRangeClassName, }) => { - const { chainStore, queriesStore, accountStore } = useStore(); + const { accountStore } = useStore(); const { t } = useTranslation(); - const { chainId } = chainStore.osmosis; - const account = accountStore.getWallet(chainId); + const account = accountStore.getWallet(accountStore.osmosisChainId); const address = account?.address ?? ""; const { fiatValue: currentValuePrice } = useCoinFiatValue( useMemo( () => - currency + currency && currentValue !== "" && !isNaN(Number(currentValue)) ? new CoinPretty( currency, new Dec(currentValue).mul( @@ -53,19 +53,14 @@ export const DepositAmountGroup: FunctionComponent<{ ) ); - const walletBalance = currency - ? queriesStore - .get(chainId) - .queryBalances.getQueryBech32Address(address) - .getBalanceFromCurrency(currency) - : null; - - const updateValue = useCallback( - (val: string) => { - const newVal = Number(val); - onUpdate(newVal); - }, - [onUpdate] + const { data: walletBalance } = api.local.balances.getUserBalances.useQuery( + { bech32Address: address }, + { + enabled: !!account?.address, + select: (balances) => + balances.find(({ denom }) => denom === currency?.coinMinimalDenom) + ?.coin ?? (currency ? new CoinPretty(currency, 0) : undefined), + } ); if (outOfRange) { @@ -139,7 +134,7 @@ export const DepositAmountGroup: FunctionComponent<{ inputClassName="!leading-4" type="number" currentValue={currentValue} - onInput={updateValue} + onInput={onUpdate} rightEntry />
diff --git a/packages/web/components/complex/add-conc-liquidity.tsx b/packages/web/components/complex/add-conc-liquidity.tsx index 9a191293f8..5b519d2ca7 100644 --- a/packages/web/components/complex/add-conc-liquidity.tsx +++ b/packages/web/components/complex/add-conc-liquidity.tsx @@ -585,7 +585,7 @@ const AddConcLiqView: FunctionComponent< onUpdate={useCallback( (amount) => { setAnchorAsset("base"); - baseDepositAmountIn.setAmount(amount.toString()); + baseDepositAmountIn.setAmount(amount); }, [baseDepositAmountIn, setAnchorAsset] )} @@ -601,7 +601,7 @@ const AddConcLiqView: FunctionComponent< onUpdate={useCallback( (amount) => { setAnchorAsset("quote"); - quoteDepositAmountIn.setAmount(amount.toString()); + quoteDepositAmountIn.setAmount(amount); }, [quoteDepositAmountIn, setAnchorAsset] )} diff --git a/packages/web/components/one-click-trading/__tests__/one-click-trading-settings.spec.ts b/packages/web/components/one-click-trading/__tests__/one-click-trading-settings.spec.ts index 796b92c838..db9851178c 100644 --- a/packages/web/components/one-click-trading/__tests__/one-click-trading-settings.spec.ts +++ b/packages/web/components/one-click-trading/__tests__/one-click-trading-settings.spec.ts @@ -23,7 +23,6 @@ describe("compare1CTTransactionParams", () => { isOneClickEnabled: true, spendLimit: mockPricePretty(5000), networkFeeLimit: mockCoinPretty(13485), - resetPeriod: "day", sessionPeriod: { end: "1hour" }, }; @@ -38,7 +37,6 @@ describe("compare1CTTransactionParams", () => { isOneClickEnabled: true, spendLimit: mockPricePretty(5000), networkFeeLimit: mockCoinPretty(13485), - resetPeriod: "day", sessionPeriod: { end: "1hour" }, }; @@ -56,7 +54,6 @@ describe("compare1CTTransactionParams", () => { isOneClickEnabled: true, spendLimit: mockPricePretty(5000), networkFeeLimit: mockCoinPretty(13485), - resetPeriod: "day", sessionPeriod: { end: "1hour" }, }; @@ -69,30 +66,11 @@ describe("compare1CTTransactionParams", () => { expect(changes).toContain("networkFeeLimit"); }); - it("should detect changes in resetPeriod", () => { - const prevParams: OneClickTradingTransactionParams = { - isOneClickEnabled: true, - spendLimit: mockPricePretty(5000), - networkFeeLimit: mockCoinPretty(13485), - resetPeriod: "day", - sessionPeriod: { end: "1hour" }, - }; - - const nextParams: OneClickTradingTransactionParams = { - ...prevParams, - resetPeriod: "week", - }; - - const changes = compare1CTTransactionParams({ prevParams, nextParams }); - expect(changes).toContain("resetPeriod"); - }); - it("should detect multiple changes including sessionPeriod", () => { const prevParams: OneClickTradingTransactionParams = { isOneClickEnabled: true, spendLimit: mockPricePretty(5000), networkFeeLimit: mockCoinPretty(13485), - resetPeriod: "day", sessionPeriod: { end: "1hour" }, }; @@ -100,14 +78,12 @@ describe("compare1CTTransactionParams", () => { ...prevParams, spendLimit: mockPricePretty(7000), networkFeeLimit: mockCoinPretty(16000), - resetPeriod: "month", sessionPeriod: { end: "3hours" }, }; const changes = compare1CTTransactionParams({ prevParams, nextParams }); expect(changes).toContain("spendLimit"); expect(changes).toContain("networkFeeLimit"); - expect(changes).toContain("resetPeriod"); expect(changes).toContain("sessionPeriod"); }); }); diff --git a/packages/web/components/one-click-trading/one-click-trading-settings.tsx b/packages/web/components/one-click-trading/one-click-trading-settings.tsx index 24cb28aa98..c77cd39646 100644 --- a/packages/web/components/one-click-trading/one-click-trading-settings.tsx +++ b/packages/web/components/one-click-trading/one-click-trading-settings.tsx @@ -16,10 +16,6 @@ import { Icon } from "~/components/assets"; import { Spinner } from "~/components/loaders"; import { SkeletonLoader } from "~/components/loaders/skeleton-loader"; import { NetworkFeeLimitScreen } from "~/components/one-click-trading/screens/network-fee-limit-screen"; -import { - getResetPeriodTranslationKey, - ResetPeriodScreen, -} from "~/components/one-click-trading/screens/reset-period-screen"; import { getSessionPeriodTranslationKey, SessionPeriodScreen, @@ -47,7 +43,6 @@ enum SettingsScreens { SpendLimit = "spendLimit", NetworkFeeLimit = "networkFeeLimit", SessionPeriod = "sessionPeriod", - ResetPeriod = "resetPeriod", } interface OneClickTradingSettingsProps { @@ -93,10 +88,6 @@ export function compare1CTTransactionParams({ changes.add("networkFeeLimit"); } - if (prevParams?.resetPeriod !== nextParams?.resetPeriod) { - changes.add("resetPeriod"); - } - if (prevParams?.sessionPeriod.end !== nextParams?.sessionPeriod.end) { changes.add("sessionPeriod"); } @@ -418,40 +409,6 @@ export const OneClickTradingSettings = ({ } isDisabled={isDisabled} /> - - setCurrentScreen(SettingsScreens.ResetPeriod) - } - content={ - - } - isDisabled={isDisabled} - />
{hasExistingSession && @@ -548,20 +505,6 @@ export const OneClickTradingSettings = ({ />
- - -
- -
-
)} diff --git a/packages/web/components/one-click-trading/profile-one-click-trading-settings.tsx b/packages/web/components/one-click-trading/profile-one-click-trading-settings.tsx index e45811cdad..b4c7e7bd9c 100644 --- a/packages/web/components/one-click-trading/profile-one-click-trading-settings.tsx +++ b/packages/web/components/one-click-trading/profile-one-click-trading-settings.tsx @@ -33,6 +33,7 @@ export const ProfileOneClickTradingSettings = ({ enabled: shouldFetchSessionAuthenticator, cacheTime: 15_000, // 15 seconds staleTime: 15_000, // 15 seconds + retry: false, } ); diff --git a/packages/web/components/one-click-trading/screens/reset-period-screen.tsx b/packages/web/components/one-click-trading/screens/reset-period-screen.tsx deleted file mode 100644 index 6f519458b9..0000000000 --- a/packages/web/components/one-click-trading/screens/reset-period-screen.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { OneClickTradingResetPeriods } from "@osmosis-labs/types"; -import classNames from "classnames"; - -import { Button } from "~/components/buttons"; -import { OneClickTradingBaseScreenProps } from "~/components/one-click-trading/screens/types"; -import { - ScreenGoBackButton, - useScreenManager, -} from "~/components/screen-manager"; -import { useTranslation } from "~/hooks"; - -const ResetPeriods = [ - "day", - "week", - "month", - "year", -] as OneClickTradingResetPeriods[]; - -export function getResetPeriodTranslationKey( - id: OneClickTradingResetPeriods -): string { - switch (id) { - case "day": - return "oneClickTrading.settings.resetPeriodScreen.periods.day"; - case "week": - return "oneClickTrading.settings.resetPeriodScreen.periods.week"; - case "month": - return "oneClickTrading.settings.resetPeriodScreen.periods.month"; - case "year": - return "oneClickTrading.settings.resetPeriodScreen.periods.year"; - default: - throw new Error(`No mapping for ${id}`); - } -} - -interface ResetPeriodScreenProps extends OneClickTradingBaseScreenProps {} - -export const ResetPeriodScreen = ({ - transaction1CTParams, - setTransaction1CTParams, -}: ResetPeriodScreenProps) => { - const { t } = useTranslation(); - const { goBack } = useScreenManager(); - - return ( - <> - -
-

- {t("oneClickTrading.settings.resetPeriodScreen.title")} -

-

- {t("oneClickTrading.settings.resetPeriodScreen.description")} -

- -
- {ResetPeriods.map((id) => ( - - ))} -
-
- - ); -}; diff --git a/packages/web/components/pool-detail/share.tsx b/packages/web/components/pool-detail/share.tsx index 2468e30b81..fdcebbd159 100644 --- a/packages/web/components/pool-detail/share.tsx +++ b/packages/web/components/pool-detail/share.tsx @@ -5,8 +5,7 @@ import { PricePretty, RatePretty, } from "@keplr-wallet/unit"; -import type { Pool } from "@osmosis-labs/server"; -import { BondStatus } from "@osmosis-labs/types"; +import type { BondDuration, Pool } from "@osmosis-labs/server"; import classNames from "classnames"; import { Duration } from "dayjs/plugin/duration"; import { observer } from "mobx-react-lite"; @@ -19,20 +18,18 @@ import { BondCard } from "~/components/cards"; import { AssetBreakdownChart, PriceBreakdownChart } from "~/components/chart"; import { PoolComposition } from "~/components/chart/pool-composition"; import { Disableable } from "~/components/types"; -import { ArrowButton } from "~/components/ui/button"; import { Button } from "~/components/ui/button"; import { EventName } from "~/config"; import { useTranslation, useWalletSelect } from "~/hooks"; import { useAmplitudeAnalytics, - useFeatureFlags, useLockTokenConfig, useSuperfluidPool, useWindowSize, } from "~/hooks"; import { AddLiquidityModal, - LockTokensModal, + LockShares, RemoveLiquidityModal, SuperfluidValidatorModal, } from "~/modals"; @@ -47,17 +44,10 @@ const E = EventName.PoolDetail; export const SharePool: FunctionComponent<{ pool: Pool }> = observer( ({ pool }) => { - const { - chainStore, - queriesStore, - accountStore, - queriesExternalStore: { queryAccountsPoolRewards }, - derivedDataStore, - } = useStore(); + const { accountStore } = useStore(); const { t } = useTranslation(); const { isMobile } = useWindowSize(); const { isLoading: isWalletLoading } = useWalletSelect(); - const { displayDailyEarn } = useFeatureFlags(); const [poolDetailsContainerRef, { y: poolDetailsContainerOffset }] = useMeasure(); @@ -66,37 +56,33 @@ export const SharePool: FunctionComponent<{ pool: Pool }> = observer( const [poolBreakdownRef, { height: poolBreakdownHeight }] = useMeasure(); - const { chainId } = chainStore.osmosis; - - const queryCosmos = queriesStore.get(chainId).cosmos; - const account = accountStore.getWallet(chainStore.osmosis.chainId); + const account = accountStore.getWallet(accountStore.osmosisChainId); const address = account?.address ?? ""; - const queryAccountPoolRewards = queryAccountsPoolRewards.get(address); // queries - const { data: userSharePool, isLoading: isUserSharePoolLoading } = - api.edge.pools.getUserSharePool.useQuery( - { - poolId: pool.id, - userOsmoAddress: address, - }, - { - enabled: !isWalletLoading && Boolean(address), + const { + data: userSharePool, + isLoading: isLoadingUserSharePool, + isRefetching: isRefetchingUserSharePool, + } = api.edge.pools.getUserSharePool.useQuery( + { + poolId: pool.id, + userOsmoAddress: address, + }, + { + enabled: !isWalletLoading && Boolean(address), - // expensive query - trpc: { - context: { - skipBatch: true, - }, + // expensive query + trpc: { + context: { + skipBatch: true, }, - } - ); + }, + } + ); const { data: sharePool } = api.edge.pools.getSharePool.useQuery({ poolId: pool.id, }); - const { data: superfluidPoolIds } = - api.edge.pools.getSuperfluidPoolIds.useQuery(); - const isSuperfluid = superfluidPoolIds?.includes(pool.id) ?? false; const { data: poolIncentives, isLoading: isPoolIncentivesLoading } = api.edge.pools.getPoolIncentives.useQuery({ @@ -105,47 +91,36 @@ export const SharePool: FunctionComponent<{ pool: Pool }> = observer( const { data: poolMarketMetrics, isLoading: isPoolMarketMetricsLoading } = api.edge.pools.getPoolMarketMetrics.useQuery({ poolId: pool.id }); - const { data: bondDurations_, isLoading: isLoadingBondDurations } = - api.edge.pools.getSharePoolBondDurations.useQuery( - { - poolId: pool.id, - userOsmoAddress: Boolean(address) ? address : undefined, - }, - { - enabled: !isWalletLoading, + const { + data: bondDurations_, + isLoading: isLoadingBondDurations, + isRefetching: isRefetchingBondDurations, + } = api.edge.pools.getSharePoolBondDurations.useQuery( + { + poolId: pool.id, + userOsmoAddress: Boolean(address) ? address : undefined, + }, + { + enabled: !isWalletLoading, - // expensive query - trpc: { - context: { - skipBatch: true, - }, + // expensive query + trpc: { + context: { + skipBatch: true, }, - } - ); - const bondDurations = useMemo(() => bondDurations_ ?? [], [bondDurations_]); - - const apiUtils = api.useUtils(); - const invalidateQueries: (value: T) => T = useCallback( - (value) => { - apiUtils.edge.pools.getPool.invalidate({ poolId: pool.id }); - apiUtils.edge.pools.getSharePool.invalidate(); - - apiUtils.edge.pools.getUserSharePool.invalidate(); - apiUtils.edge.pools.getSharePoolBondDurations.invalidate(); + }, + } + ); + const bondDurations = useMemo( + () => bondDurations_ ?? ([] as BondDuration[]), + [bondDurations_] + ); - return value; - }, - [pool.id, apiUtils] + const isSuperfluid = bondDurations.some(({ superfluid }) => + Boolean(superfluid) ); - // initialize pool data stores once root pool store is loaded - const { superfluidPoolDetail, poolBonding } = pool.id - ? derivedDataStore.getForPool(pool.id as string) - : { - superfluidPoolDetail: undefined, - poolBonding: undefined, - }; - const { delegateSharesToValidator } = useSuperfluidPool(); + const { delegateSharesToValidator } = useSuperfluidPool(bondDurations_); // user analytics const { poolName } = useMemo( @@ -181,21 +156,15 @@ export const SharePool: FunctionComponent<{ pool: Pool }> = observer( useState(false); const [showPoolDetails, setShowPoolDetails] = useState(false); - const highestAPRBondableDuration = poolBonding?.highestBondDuration; - - const highestAPRDailyPeriodicRate = - highestAPRBondableDuration?.aggregateApr - .sub(highestAPRBondableDuration?.swapFeeApr) - .quo(new Dec(365)) // get daily periodic rate - .toDec() ?? new Dec(0); - /** * In mainnet, highestAPRBondableDuration should be superfluid as the highest gauge index. */ + const superfluidBondDuration = bondDurations.find((duration) => + Boolean(duration?.superfluid) + ); const isSuperfluidEnabled = - highestAPRBondableDuration?.userShares?.toDec().gt(new Dec(0)) && - (Boolean(highestAPRBondableDuration?.superfluid?.delegated) || - Boolean(highestAPRBondableDuration?.superfluid?.undelegating)); + superfluidBondDuration?.superfluid?.delegated?.toDec().isPositive() || + superfluidBondDuration?.superfluid?.undelegating?.toDec().isPositive(); // handle user actions const baseEventInfo = useMemo( @@ -217,12 +186,11 @@ export const SharePool: FunctionComponent<{ pool: Pool }> = observer( logEvent([E.addLiquidityStarted, poolInfo]); result - .then(invalidateQueries) .then(() => logEvent([E.addLiquidityCompleted, poolInfo])) .catch(console.error) .finally(() => setShowAddLiquidityModal(false)); }, - [baseEventInfo, isSuperfluidEnabled, logEvent, invalidateQueries] + [baseEventInfo, isSuperfluidEnabled, logEvent] ); const onRemoveLiquidity = useCallback( (result: Promise) => { @@ -234,12 +202,11 @@ export const SharePool: FunctionComponent<{ pool: Pool }> = observer( logEvent([E.removeLiquidityStarted, removeLiqInfo]); result - .then(invalidateQueries) .then(() => logEvent([E.removeLiquidityCompleted, removeLiqInfo])) .catch(console.error) .finally(() => setShowRemoveLiquidityModal(false)); }, - [baseEventInfo, isSuperfluidEnabled, logEvent, invalidateQueries] + [baseEventInfo, isSuperfluidEnabled, logEvent] ); const onLockToken = useCallback( (duration: Duration, electSuperfluid?: boolean) => { @@ -249,6 +216,8 @@ export const SharePool: FunctionComponent<{ pool: Pool }> = observer( unbondingPeriod: duration.asDays(), }; + console.log({ electSuperfluid }); + logEvent([E.bondingStarted, lockInfo]); if (electSuperfluid) { @@ -257,12 +226,11 @@ export const SharePool: FunctionComponent<{ pool: Pool }> = observer( // `sendLockAndSuperfluidDelegateMsg` will be sent after superfluid modal } else { lockToken(duration) - .then(invalidateQueries) .then(() => logEvent([E.bondingCompleted, lockInfo])) .finally(() => setShowLockLPTokenModal(false)); } }, - [baseEventInfo, logEvent, lockToken, invalidateQueries] + [baseEventInfo, logEvent, lockToken] ); const onUnlockTokens = useCallback( (duration: Duration) => { @@ -282,42 +250,39 @@ export const SharePool: FunctionComponent<{ pool: Pool }> = observer( }; logEvent([E.unbondAllStarted, unlockEvent]); - unlockTokens(locks) - .then(invalidateQueries) - .then(() => { - logEvent([E.unbondAllCompleted, unlockEvent]); - }); + unlockTokens(locks).then(() => { + logEvent([E.unbondAllCompleted, unlockEvent]); + }); }, - [bondDurations, baseEventInfo, logEvent, unlockTokens, invalidateQueries] + [bondDurations, baseEventInfo, logEvent, unlockTokens] ); const handleSuperfluidDelegateToValidator = useCallback( (validatorAddress: string) => { - if (!baseEventInfo.isSuperfluidPool || !pool.id) return; + console.log("handleSuperfluidDelegateToValidator", { + validatorAddress, + isSuperfluid, + }); + if (!isSuperfluid || !pool.id) return; const poolInfo = { ...baseEventInfo, unbondingPeriod: 14, - validatorName: queryCosmos.queryValidators - .getQueryStatus(BondStatus.Bonded) - .getValidator(validatorAddress)?.description.moniker, isSuperfluidEnabled, }; logEvent([E.superfluidStakeStarted, poolInfo]); delegateSharesToValidator(pool.id, validatorAddress, lockLPTokensConfig) - .then(invalidateQueries) .then(() => logEvent([E.superfluidStakeCompleted, poolInfo])) .finally(() => setShowSuperfluidValidatorsModal(false)); }, [ pool, + isSuperfluid, baseEventInfo, - queryCosmos.queryValidators, isSuperfluidEnabled, logEvent, delegateSharesToValidator, - invalidateQueries, lockLPTokensConfig, ] ); @@ -334,17 +299,13 @@ export const SharePool: FunctionComponent<{ pool: Pool }> = observer( }, [userSharePool?.availableShares, bondDurations]); const level2Disabled = !bondDurations.some((duration) => duration.bondable); - const additionalRewardsByBonding = queryAccountPoolRewards - .getUsdRewardsForPool(pool.id) - ?.day.mul(highestAPRDailyPeriodicRate) - .maxDecimals(3) - .inequalitySymbol(false); - const setShowModal = useCallback( (setter: Function, show: boolean) => () => setter(show), [] ); + console.log("parentBalance", lockLPTokensConfig?.balance?.toString()); + return (
{pool && showAddLiquidityModal && ( @@ -370,13 +331,14 @@ export const SharePool: FunctionComponent<{ pool: Pool }> = observer( /> )} {lockLPTokensConfig && showLockLPTokenModal && ( - setShowLockLPTokenModal(false)} amountConfig={lockLPTokensConfig} onLockToken={onLockToken} + bondDurations={bondDurations} /> )} {isSuperfluid && @@ -390,12 +352,12 @@ export const SharePool: FunctionComponent<{ pool: Pool }> = observer( : t("superfluidValidator.title") } availableBondAmount={ - superfluidPoolDetail?.userUpgradeableSharePoolLockIds - ? superfluidPoolDetail.userUpgradeableSharePoolLockIds.amount // is delegating amount from existing lockup + superfluidBondDuration?.userLocks.length + ? superfluidBondDuration.userShares // is delegating amount from existing lockup : new CoinPretty( sharePool.currency, // is delegating amount from new/pending lockup - lockLPTokensConfig.amount !== "" - ? lockLPTokensConfig.getAmountPrimitive().amount + lockLPTokensConfig.amount + ? lockLPTokensConfig.amount.toCoin().amount : new Dec(0) ) } @@ -598,46 +560,6 @@ export const SharePool: FunctionComponent<{ pool: Pool }> = observer( ]} />
- {displayDailyEarn && ( -
-
- - {t("pool.currentDailyEarn")} - -

- {t("pool.dailyEarnAmount", { - amount: - queryAccountPoolRewards - .getUsdRewardsForPool(pool.id) - ?.day.toString() ?? "$0", - })} -

-
- {userSharePool.availableValue.toDec().isPositive() && - bondDurations.some((duration) => duration.bondable) && ( - { - logEvent([ - E.earnMoreByBondingClicked, - baseEventInfo, - ]); - setShowLockLPTokenModal(true); - }} - > - {t("pool.earnMore", { - amount: additionalRewardsByBonding - ?.toDec() - .gte(new Dec(0.001)) - ? `$${additionalRewardsByBonding?.toString()}/${t( - "pool.day" - )}` - : "", - })} - - )} -
- )} )} @@ -714,8 +636,9 @@ export const SharePool: FunctionComponent<{ pool: Pool }> = observer( className="w-fit shrink-0 xs:w-full" variant="outline" disabled={ - userSharePool?.availableShares?.toDec().isZero() ?? - true + !userSharePool?.availableShares + ?.toDec() + .isPositive() ?? true } onClick={() => { logEvent([ @@ -747,7 +670,8 @@ export const SharePool: FunctionComponent<{ pool: Pool }> = observer(
- {isUserSharePoolLoading && Boolean(account) ? ( + {Boolean(account) && + (isLoadingUserSharePool || isRefetchingUserSharePool) ? ( ) : userSharePool ? ( <> @@ -812,7 +736,8 @@ export const SharePool: FunctionComponent<{ pool: Pool }> = observer( ` ${t("pool.bondSuperfluidLiquidityCaption")}`}
- {isLoadingBondDurations ? ( + {Boolean(account) && + (isLoadingBondDurations || isLoadingUserSharePool) ? ( ) : level2Disabled ? (
@@ -824,7 +749,15 @@ export const SharePool: FunctionComponent<{ pool: Pool }> = observer( "!border-0 bg-gradient-positive text-osmoverse-900": levelCta === 2, })} - disabled={levelCta !== 2} + disabled={ + // isFetchingUserSharePool is checked within levelCta + levelCta !== 2 || isRefetchingBondDurations + } + isLoading={ + Boolean(account) && + (isLoadingBondDurations || isLoadingUserSharePool) + } + loadingText={t("pool.bondShares")} onClick={() => { logEvent([E.bondSharesClicked, baseEventInfo]); setShowLockLPTokenModal(true); diff --git a/packages/web/components/swap-tool/index.tsx b/packages/web/components/swap-tool/index.tsx index 7a5c71ab90..6b762d9985 100644 --- a/packages/web/components/swap-tool/index.tsx +++ b/packages/web/components/swap-tool/index.tsx @@ -979,7 +979,6 @@ export const SwapTool: FunctionComponent = observer( Boolean(swapState.error) || Boolean(swapState.networkFeeError))) } - loadingText={buttonText} onClick={sendSwapTx} > {account?.walletStatus === WalletStatus.Connected ? ( diff --git a/packages/web/config/ibc-overrides.ts b/packages/web/config/ibc-overrides.ts index af6adcbe9d..691460a370 100644 --- a/packages/web/config/ibc-overrides.ts +++ b/packages/web/config/ibc-overrides.ts @@ -267,10 +267,8 @@ const MainnetIBCAdditionalData: Partial< }, }, INJ: { - depositUrlOverride: - "https://hub.injective.network/bridge/?destination=osmosis&origin=injective&token=inj", - withdrawUrlOverride: - "https://hub.injective.network/bridge/?destination=injective&origin=osmosis&token=inj", + depositUrlOverride: "https://bridge.injective.network/", + withdrawUrlOverride: "https://bridge.injective.network/", }, FTM: { sourceChainNameOverride: "Fantom", diff --git a/packages/web/e2e/pages/swap-page.ts b/packages/web/e2e/pages/swap-page.ts index fc2fb20b6b..c23c6fdd2b 100644 --- a/packages/web/e2e/pages/swap-page.ts +++ b/packages/web/e2e/pages/swap-page.ts @@ -127,7 +127,7 @@ export class SwapPage { if (fromTokenText == from && toTokenText == to) { console.log( - "Current pair:" + fromTokenText + toTokenText + " is already matching." + "Current pair: " + fromTokenText + toTokenText + " is already matching." ); return; } @@ -135,7 +135,7 @@ export class SwapPage { if (fromTokenText == to && toTokenText == from) { await this.flipTokenPair(); console.log( - "Current pair:" + fromTokenText + toTokenText + " is fliped." + "Current pair: " + fromTokenText + toTokenText + " is fliped." ); return; } diff --git a/packages/web/e2e/tests/swap.stables.spec.ts b/packages/web/e2e/tests/swap.stables.spec.ts index 28e711b7c3..c03a3c6fc0 100644 --- a/packages/web/e2e/tests/swap.stables.spec.ts +++ b/packages/web/e2e/tests/swap.stables.spec.ts @@ -22,7 +22,7 @@ test.describe("Test Swap Stables feature", () => { let USDT = "ibc/4ABBEF4C8926DDDB320AE5188CFD63267ABBCEFC0583E4AE05D6E5AA2401DDAB"; - test.beforeEach(async () => { + test.beforeAll(async () => { console.log("Before test setup Wallet Extension."); // Launch Chrome with a Keplr wallet extension const pathToExtension = path.join(__dirname, "../keplr-extension"); @@ -51,7 +51,7 @@ test.describe("Test Swap Stables feature", () => { expect(await swapPage.isError(), "Swap is not available!").toBeFalsy(); }); - test.afterEach(async () => { + test.afterAll(async () => { await context.close(); }); diff --git a/packages/web/e2e/tests/swap.wallet.spec.ts b/packages/web/e2e/tests/swap.wallet.spec.ts index d09043f84a..7435cda15c 100644 --- a/packages/web/e2e/tests/swap.wallet.spec.ts +++ b/packages/web/e2e/tests/swap.wallet.spec.ts @@ -24,8 +24,8 @@ test.describe("Test Swap feature", () => { let AKT = "ibc/1480B8FD20AD5FCAE81EA87584D269547DD4D436843C1D20F15E00EB64743EF4"; - test.beforeEach(async () => { - console.log("Before test setup Wallet Extension."); + test.beforeAll(async () => { + console.log("\nBefore test setup Wallet Extension."); // Launch Chrome with a Keplr wallet extension const pathToExtension = path.join(__dirname, "../keplr-extension"); context = await chromium.launchPersistentContext("", { @@ -53,7 +53,7 @@ test.describe("Test Swap feature", () => { expect(await swapPage.isError(), "Swap is not available!").toBeFalsy(); }); - test.afterEach(async () => { + test.afterAll(async () => { await context.close(); }); diff --git a/packages/web/hooks/input/__tests__/use-amount-input.spec.ts b/packages/web/hooks/input/__tests__/use-amount-input.spec.ts index b04a7dbc55..15a40f8ca5 100644 --- a/packages/web/hooks/input/__tests__/use-amount-input.spec.ts +++ b/packages/web/hooks/input/__tests__/use-amount-input.spec.ts @@ -1,9 +1,8 @@ /* eslint-disable import/no-extraneous-dependencies */ import { CoinPretty, Dec, DecUtils, PricePretty } from "@keplr-wallet/unit"; -import { DEFAULT_VS_CURRENCY, queryBalances } from "@osmosis-labs/server"; +import { DEFAULT_VS_CURRENCY } from "@osmosis-labs/server"; import { Currency } from "@osmosis-labs/types"; import { act, waitFor } from "@testing-library/react"; -import { rest } from "msw"; import { server, trpcMsw } from "~/__tests__/msw"; import { @@ -15,8 +14,6 @@ import { ChainList } from "~/config/generated/chain-list"; import { isValidNumericalRawInput, useAmountInput } from "../use-amount-input"; -const osmosisLcdUrl = ChainList[0].apis.rest[0].address; - describe("useAmountInput", () => { const osmoMockCurrency: Currency = { coinDenom: "OSMO", @@ -33,22 +30,18 @@ describe("useAmountInput", () => { ctx.data(new PricePretty(DEFAULT_VS_CURRENCY, new Dec(1))) ); }), - rest.get( - `${osmosisLcdUrl}/cosmos/bank/v1beta1/balances/osmo1cyyzpxplxdzkeea7kwsydadg87357qnahakaks`, - (_req, res, ctx) => { - return res( - ctx.status(200), - ctx.json({ - balances: [ - { - denom: osmoMockCurrency.coinMinimalDenom, - amount: "1000000000", - }, - ], - } as Awaited>) - ); - } - ) + trpcMsw.local.balances.getUserBalances.query((_req, res, ctx) => { + return res( + ctx.status(200), + ctx.data([ + { + denom: osmoMockCurrency.coinMinimalDenom, + amount: "1000000000", + coin: undefined, + }, + ]) + ); + }) ); }); diff --git a/packages/web/hooks/input/use-amount-input.ts b/packages/web/hooks/input/use-amount-input.ts index 4ae8e741c0..2e6e4414c9 100644 --- a/packages/web/hooks/input/use-amount-input.ts +++ b/packages/web/hooks/input/use-amount-input.ts @@ -19,8 +19,7 @@ import { mulPrice } from "~/hooks/queries/assets/use-coin-fiat-value"; import { usePrice } from "~/hooks/queries/assets/use-price"; import { useDebouncedState } from "~/hooks/use-debounced-state"; import { useStore } from "~/stores"; - -import { useBalances } from "../queries/cosmos/use-balances"; +import { api } from "~/utils/trpc"; /** Manages user input for a currency, with helpers for selecting * the user’s currency balance as input. Includes support for debounce on input. @@ -39,13 +38,14 @@ export function useAmountInput({ // query user balance for currency const { chainStore, accountStore } = useStore(); const account = accountStore.getWallet(chainStore.osmosis.chainId); - const { data: balances, isFetched: isBalancesFetched } = useBalances({ - address: account?.address ?? "", - queryOptions: { - enabled: Boolean(account?.address), - }, - }); - const rawCurrencyBalance = balances?.balances.find( + const { data: balances, isFetched: isBalancesFetched } = + api.local.balances.getUserBalances.useQuery( + { + bech32Address: account?.address ?? "", + }, + { enabled: Boolean(account?.address) } + ); + const rawCurrencyBalance = balances?.find( (bal) => bal.denom === currency?.coinMinimalDenom )?.amount; // manage amounts, with ability to set fraction of the amount diff --git a/packages/web/hooks/mutations/one-click-trading/__tests__/use-create-one-click-trading-session.spec.ts b/packages/web/hooks/mutations/one-click-trading/__tests__/use-create-one-click-trading-session.spec.ts index 82ea627434..e9bf3333b6 100644 --- a/packages/web/hooks/mutations/one-click-trading/__tests__/use-create-one-click-trading-session.spec.ts +++ b/packages/web/hooks/mutations/one-click-trading/__tests__/use-create-one-click-trading-session.spec.ts @@ -18,7 +18,6 @@ jest.mock("~/config", () => ({ describe("isAuthenticatorOneClickTradingSession", () => { const key = new PrivKeySecp256k1(Buffer.from("key")); const allowedAmount = "1000"; - const resetPeriod = "day"; const allowedMessages: AvailableOneClickTradingMessages[] = [ "/osmosis.poolmanager.v1beta1.MsgSwapExactAmountIn", "/osmosis.poolmanager.v1beta1.MsgSplitRouteSwapExactAmountIn", @@ -29,7 +28,6 @@ describe("isAuthenticatorOneClickTradingSession", () => { const rawAuthenticator = getOneClickTradingSessionAuthenticator({ key, allowedAmount, - resetPeriod, allowedMessages, sessionPeriod, }); @@ -46,7 +44,7 @@ describe("isAuthenticatorOneClickTradingSession", () => { it("should return false for an invalid 1-Click Trading Session authenticator", () => { const authenticator = { type: "AllOf", - subAuthenticators: [{ type: "InvalidType" }], + subAuthenticators: [{ type: "Invalidtype" }], }; expect( isAuthenticatorOneClickTradingSession({ @@ -60,7 +58,6 @@ describe("getOneClickTradingSessionAuthenticator", () => { it("should generate a correct 1-Click Trading Session authenticator", () => { const key = PrivKeySecp256k1.generateRandomKey(); const allowedAmount = "1000"; - const resetPeriod = "day"; const allowedMessages: AvailableOneClickTradingMessages[] = [ "/osmosis.poolmanager.v1beta1.MsgSwapExactAmountIn", "/osmosis.poolmanager.v1beta1.MsgSplitRouteSwapExactAmountIn", @@ -70,7 +67,6 @@ describe("getOneClickTradingSessionAuthenticator", () => { const result = getOneClickTradingSessionAuthenticator({ key, allowedAmount, - resetPeriod, allowedMessages, sessionPeriod, }); @@ -78,9 +74,9 @@ describe("getOneClickTradingSessionAuthenticator", () => { expect(result.type).toEqual("AllOf"); const data = JSON.parse(Buffer.from(result.data).toString()); expect(data).toHaveLength(3); - expect(data[0].Type).toEqual("SignatureVerification"); - expect(data[1].Type).toEqual("CosmwasmAuthenticatorV1"); - expect(data[2].Type).toEqual("AnyOf"); + expect(data[0].type).toEqual("SignatureVerification"); + expect(data[1].type).toEqual("CosmwasmAuthenticatorV1"); + expect(data[2].type).toEqual("AnyOf"); }); }); diff --git a/packages/web/hooks/mutations/one-click-trading/use-create-one-click-trading-session.tsx b/packages/web/hooks/mutations/one-click-trading/use-create-one-click-trading-session.tsx index 0211a48440..0565e66c1c 100644 --- a/packages/web/hooks/mutations/one-click-trading/use-create-one-click-trading-session.tsx +++ b/packages/web/hooks/mutations/one-click-trading/use-create-one-click-trading-session.tsx @@ -1,4 +1,4 @@ -import { fromBase64, toBase64 } from "@cosmjs/encoding"; +import { toBase64 } from "@cosmjs/encoding"; import { WalletRepo } from "@cosmos-kit/core"; import { PrivKeySecp256k1 } from "@keplr-wallet/crypto"; import { Dec, DecUtils } from "@keplr-wallet/unit"; @@ -37,16 +37,6 @@ export class CreateOneClickSessionError extends Error { } } -function getFirstAuthenticator({ pubKey }: { pubKey: string }): { - type: AuthenticatorType; - data: Uint8Array; -} { - return { - type: "SignatureVerification", - data: fromBase64(pubKey), - }; -} - export function isAuthenticatorOneClickTradingSession({ authenticator, }: { @@ -73,36 +63,34 @@ export function isAuthenticatorOneClickTradingSession({ export function getOneClickTradingSessionAuthenticator({ key, allowedAmount, - resetPeriod, allowedMessages, sessionPeriod, }: { key: PrivKeySecp256k1; allowedMessages: AvailableOneClickTradingMessages[]; allowedAmount: string; - resetPeriod: OneClickTradingResetPeriods; sessionPeriod: OneClickTradingTimeLimit; }): { type: AuthenticatorType; data: Uint8Array; } { const signatureVerification = { - Type: "SignatureVerification", - Config: toBase64(key.getPubKey().toBytes()), + type: "SignatureVerification", + config: toBase64(key.getPubKey().toBytes()), }; const spendLimitParams = toBase64( Buffer.from( JSON.stringify({ limit: allowedAmount, - reset_period: resetPeriod, + reset_period: "day" as OneClickTradingResetPeriods, time_limit: sessionPeriod, }) ) ); const spendLimit = { - Type: "CosmwasmAuthenticatorV1", - Config: toBase64( + type: "CosmwasmAuthenticatorV1", + config: toBase64( Buffer.from( `{"contract": "${SPEND_LIMIT_CONTRACT_ADDRESS}", "params": "${spendLimitParams}"}` ) @@ -110,13 +98,13 @@ export function getOneClickTradingSessionAuthenticator({ }; const messageFilters = allowedMessages.map((message) => ({ - Type: "MessageFilter", - Config: toBase64(Buffer.from(`{"@type":"${message}"}`)), + type: "MessageFilter", + config: toBase64(Buffer.from(`{"@type":"${message}"}`)), })); const messageFilterAnyOf = { - Type: "AnyOf", - Config: Buffer.from(JSON.stringify(messageFilters)).toJSON().data, + type: "AnyOf", + config: toBase64(Buffer.from(JSON.stringify(messageFilters))), }; const compositeAuthData = [ @@ -267,14 +255,12 @@ export const useCreateOneClickTradingSession = ({ } } - let accountPubKey: string | undefined, - shouldAddFirstAuthenticator: boolean, - authenticators: ParsedAuthenticator[]; + let authenticators: ParsedAuthenticator[]; try { - ({ accountPubKey, shouldAddFirstAuthenticator, authenticators } = - await apiUtils.local.oneClickTrading.getAccountPubKeyAndAuthenticators.fetch( - { userOsmoAddress: walletRepo.current.address! } - )); + ({ authenticators } = + await apiUtils.local.oneClickTrading.getAuthenticators.fetch({ + userOsmoAddress: walletRepo.current.address!, + })); } catch (error) { throw new CreateOneClickSessionError( "Failed to fetch account public key and authenticators." @@ -291,7 +277,6 @@ export const useCreateOneClickTradingSession = ({ "/osmosis.poolmanager.v1beta1.MsgSwapExactAmountIn", "/osmosis.poolmanager.v1beta1.MsgSplitRouteSwapExactAmountIn", ]; - const resetPeriod = transaction1CTParams.resetPeriod; let sessionPeriod: OneClickTradingTimeLimit; switch (transaction1CTParams.sessionPeriod.end) { @@ -331,7 +316,6 @@ export const useCreateOneClickTradingSession = ({ key, allowedAmount, allowedMessages, - resetPeriod, sessionPeriod, }); @@ -365,18 +349,10 @@ export const useCreateOneClickTradingSession = ({ authenticatorsToRemove.push(...additionalAuthenticatorsToRemove); } - const authenticatorsToAdd = - shouldAddFirstAuthenticator && accountPubKey - ? [ - getFirstAuthenticator({ pubKey: accountPubKey }), - oneClickTradingAuthenticator, - ] - : [oneClickTradingAuthenticator]; - const tx = await new Promise((resolve, reject) => { account.osmosis .sendAddOrRemoveAuthenticatorsMsg({ - addAuthenticators: authenticatorsToAdd, + addAuthenticators: [oneClickTradingAuthenticator], removeAuthenticators: authenticatorsToRemove, memo: "", @@ -407,9 +383,8 @@ export const useCreateOneClickTradingSession = ({ accountStore.setOneClickTradingInfo({ authenticatorId, publicKey, - privateKey: toBase64(key.toBytes()), + sessionKey: toBase64(key.toBytes()), allowedMessages, - resetPeriod, sessionPeriod, sessionStartedAtUnix: dayjs().unix(), networkFeeLimit: { diff --git a/packages/web/hooks/one-click-trading/use-one-click-trading-params.ts b/packages/web/hooks/one-click-trading/use-one-click-trading-params.ts index d978df2588..8ecc5b6b1f 100644 --- a/packages/web/hooks/one-click-trading/use-one-click-trading-params.ts +++ b/packages/web/hooks/one-click-trading/use-one-click-trading-params.ts @@ -19,7 +19,6 @@ export function getParametersFromOneClickTradingInfo({ oneClickTradingInfo.networkFeeLimit, new Dec(oneClickTradingInfo.networkFeeLimit.amount) ), - resetPeriod: oneClickTradingInfo.resetPeriod, sessionPeriod: { end: oneClickTradingInfo.humanizedSessionPeriod, }, diff --git a/packages/web/hooks/queries/assets/use-price.ts b/packages/web/hooks/queries/assets/use-price.ts index 738102fcbf..9a0352cb96 100644 --- a/packages/web/hooks/queries/assets/use-price.ts +++ b/packages/web/hooks/queries/assets/use-price.ts @@ -6,7 +6,8 @@ export function usePrice(currency?: { coinMinimalDenom: string }) { coinMinimalDenom: currency?.coinMinimalDenom ?? "", }, { - enabled: Boolean(currency), + enabled: + Boolean(currency) && !currency?.coinMinimalDenom.startsWith("gamm"), cacheTime: 1000 * 3, // 3 second staleTime: 1000 * 3, // 3 second } diff --git a/packages/web/hooks/queries/cosmos/use-balances.ts b/packages/web/hooks/queries/cosmos/use-balances.ts deleted file mode 100644 index 7ba53e182a..0000000000 --- a/packages/web/hooks/queries/cosmos/use-balances.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { queryBalances } from "@osmosis-labs/server"; -import { QueryClient, useQuery, UseQueryOptions } from "@tanstack/react-query"; - -import { ChainList } from "~/config/generated/chain-list"; - -type ResponseData = Awaited>; - -function getQueryKey(address: string): string[] { - return ["balances", address]; -} - -export const useBalances = ({ - address, - queryOptions = {}, -}: { - address: string; - queryOptions?: UseQueryOptions; -}) => { - return useQuery( - getQueryKey(address), - () => queryBalances({ bech32Address: address, chainList: ChainList }), - { - enabled: Boolean(address) && typeof address === "string", - ...queryOptions, - } - ); -}; - -useBalances.invalidateQuery = ({ - address, - queryClient, -}: { - queryClient: QueryClient; - address: string; -}) => { - return queryClient.invalidateQueries(getQueryKey(address)); -}; diff --git a/packages/web/hooks/ui-config/use-asset-info-config.ts b/packages/web/hooks/ui-config/use-asset-info-config.ts index 4f0c3b5e07..e2d3983f7b 100644 --- a/packages/web/hooks/ui-config/use-asset-info-config.ts +++ b/packages/web/hooks/ui-config/use-asset-info-config.ts @@ -4,9 +4,11 @@ import { type TokenHistoricalPrice, } from "@osmosis-labs/server"; import dayjs from "dayjs"; +import { Time } from "lightweight-charts"; import { action, computed, makeObservable, observable } from "mobx"; import { useEffect, useMemo } from "react"; +import { timepointToString } from "~/components/chart/light-weight-charts/utils"; import { api } from "~/utils/trpc"; export const useAssetInfoConfig = ( @@ -205,6 +207,9 @@ export class ObservableAssetInfoConfig { @observable protected _hoverPrice: number = 0; + @observable + protected _hoverDate?: Time = undefined; + @observable protected _historicalData: TokenHistoricalPrice[] = []; @@ -275,9 +280,27 @@ export class ObservableAssetInfoConfig { if (!fiat) { return undefined; } + return new PricePretty(fiat, this._hoverPrice); } + @computed + get hoverDate(): string | null { + if (!this._hoverDate) { + return null; + } + + const formatOptions: Intl.DateTimeFormatOptions = { + year: "numeric", + month: "short", + day: "numeric", + hour: "numeric", + minute: "numeric", + }; + + return timepointToString(this._hoverDate, formatOptions, "en-US"); + } + @computed get lastChartPrice(): ChartTick | undefined { const prices: ChartTick[] = [...this.historicalChartData]; @@ -307,8 +330,9 @@ export class ObservableAssetInfoConfig { }; @action - readonly setHoverPrice = (price: number) => { + readonly setHoverPrice = (price: number, time?: Time) => { this._hoverPrice = price; + this._hoverDate = time; }; @action diff --git a/packages/web/hooks/ui-config/use-lock-token-config.ts b/packages/web/hooks/ui-config/use-lock-token-config.ts index 5e3b1ca3d4..202f55bb90 100644 --- a/packages/web/hooks/ui-config/use-lock-token-config.ts +++ b/packages/web/hooks/ui-config/use-lock-token-config.ts @@ -1,50 +1,45 @@ import { AppCurrency } from "@keplr-wallet/types"; -import { AmountConfig } from "@osmosis-labs/keplr-hooks"; -import dayjs from "dayjs"; import { Duration } from "dayjs/plugin/duration"; -import { useCallback, useEffect } from "react"; +import { useCallback } from "react"; -import { useAmountConfig } from "~/hooks/ui-config/use-amount-config"; import { useStore } from "~/stores"; +import { useAmountInput } from "../input/use-amount-input"; + /** UI config for setting valid GAMM token amounts and un/locking them in a lock. */ export function useLockTokenConfig(sendCurrency?: AppCurrency | undefined): { - config: AmountConfig; + config: ReturnType; lockToken: (gaugeDuration: Duration) => Promise; unlockTokens: ( locks: { lockId: string; isSynthetic: boolean }[] ) => Promise<"synthetic" | "normal">; } { - const { chainStore, queriesStore, accountStore } = useStore(); + const { chainStore, accountStore } = useStore(); const { chainId } = chainStore.osmosis; const account = accountStore.getWallet(chainId); - const queryOsmosis = queriesStore.get(chainId).osmosis!; - const address = account?.address ?? ""; - const config = useAmountConfig( - chainStore, - queriesStore, - chainId, - address, - undefined, - sendCurrency - ); + const config = useAmountInput({ + currency: sendCurrency, + }); const lockToken = useCallback( (lockDuration: Duration) => { return new Promise(async (resolve, reject) => { try { - if (!config.sendCurrency.coinMinimalDenom.startsWith("gamm")) { - throw new Error("Tried to lock non-gamm token"); + if (!sendCurrency || !config.amount) + return reject("Invalid send currency or input amount"); + if (!sendCurrency.coinMinimalDenom.startsWith("gamm")) { + return reject("Tried to lock non-gamm token"); } + await account?.osmosis.sendLockTokensMsg( lockDuration.asSeconds(), [ { - currency: config.sendCurrency, - amount: config.amount, + currency: sendCurrency, + amount: config.amount.toCoin().amount, }, ], undefined, @@ -56,7 +51,7 @@ export function useLockTokenConfig(sendCurrency?: AppCurrency | undefined): { } }); }, - [account, config.sendCurrency, config.amount] + [account, sendCurrency, config.amount] ); const unlockTokens = useCallback( @@ -100,41 +95,5 @@ export function useLockTokenConfig(sendCurrency?: AppCurrency | undefined): { [account] ); - // refresh query stores when an unbonding token happens to unbond with window open - useEffect(() => { - if ( - queryOsmosis.queryAccountLocked.get(address).isFetching || - address === "" - ) - return; - - const unlockingTokens = - queryOsmosis.queryAccountLocked.get(address).unlockingCoins; - const now = dayjs().utc(); - let timeoutIds: NodeJS.Timeout[] = []; - - // set a timeout for each unlocking token to trigger a refresh at unbond time - unlockingTokens.forEach(({ endTime }) => { - const diffMs = dayjs(endTime).diff(now, "ms"); - const blockTime = 6_000; // allow one block to process unbond before querying - - timeoutIds.push( - setTimeout(() => { - queryOsmosis.queryGammPoolShare.fetch(address); - }, diffMs + blockTime) - ); - }); - - return () => { - timeoutIds.forEach((timeout) => clearTimeout(timeout)); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [ - // eslint-disable-next-line react-hooks/exhaustive-deps - queryOsmosis.queryAccountLocked.get(address).response, - address, - queryOsmosis.queryAccountLocked, - ]); - return { config, lockToken, unlockTokens }; } diff --git a/packages/web/hooks/ui-config/use-superfluid-pool.ts b/packages/web/hooks/ui-config/use-superfluid-pool.ts index 9824a12442..614d854144 100644 --- a/packages/web/hooks/ui-config/use-superfluid-pool.ts +++ b/packages/web/hooks/ui-config/use-superfluid-pool.ts @@ -1,37 +1,55 @@ -import { AmountConfig } from "@osmosis-labs/keplr-hooks"; +import type { BondDuration } from "@osmosis-labs/server"; import { useCallback } from "react"; import { useStore } from "~/stores"; +import { api } from "~/utils/trpc"; + +import { useAmountInput } from "../input/use-amount-input"; /** Superfluid pool actions. */ -export function useSuperfluidPool(): { +export function useSuperfluidPool(bondDurations?: BondDuration[]): { delegateSharesToValidator: ( poolId: string, validatorAddress: string, - lockLPTokensConfig?: AmountConfig + lockLPTokensConfig?: ReturnType ) => Promise<"delegated" | "locked-and-delegated">; } { - const { chainStore, derivedDataStore, accountStore } = useStore(); - const { chainId } = chainStore.osmosis; - - const account = accountStore.getWallet(chainId); + const { accountStore } = useStore(); + const account = accountStore.getWallet(accountStore.osmosisChainId); + const apiUtils = api.useUtils(); const delegateSharesToValidator = useCallback( ( poolId: string, validatorAddress: string, - lockLPTokensConfig?: AmountConfig + lockLPTokensConfig?: ReturnType ) => { return new Promise<"delegated" | "locked-and-delegated">( async (resolve, reject) => { - const superfluidPoolDetail = - derivedDataStore.superfluidPoolDetails.get(poolId); - if (superfluidPoolDetail.isSuperfluid) { - if (superfluidPoolDetail.userUpgradeableSharePoolLockIds) { + const address = account?.address; + + if (!address) return reject("No account address"); + + const bondDurations_ = + bondDurations ?? + (await apiUtils.edge.pools.getSharePoolBondDurations.fetch({ + poolId, + userOsmoAddress: address, + })); + + const superfluidDuration = bondDurations_.find(({ superfluid }) => + Boolean(superfluid) + ); + + if (superfluidDuration) { + const delegateableLocks = superfluidDuration.userLocks.filter( + ({ isSynthetic }) => !isSynthetic + ); + if (delegateableLocks.length > 0) { // is delegating existing locked shares try { await account?.osmosis.sendSuperfluidDelegateMsg( - superfluidPoolDetail.userUpgradeableSharePoolLockIds.lockIds, + delegateableLocks.map((lock) => lock.lockId), validatorAddress, undefined, () => resolve("delegated") @@ -40,13 +58,19 @@ export function useSuperfluidPool(): { console.error(e); reject(); } - } else if (lockLPTokensConfig) { + } else { try { + const sendCurrency = lockLPTokensConfig?.amount?.currency; + const amount = lockLPTokensConfig?.amount?.toCoin().amount; + + if (!sendCurrency) return reject("No send currency to lock"); + if (!amount) return reject("No amount to lock"); + await account?.osmosis.sendLockAndSuperfluidDelegateMsg( [ { - currency: lockLPTokensConfig.sendCurrency, - amount: lockLPTokensConfig.amount, + currency: sendCurrency, + amount: amount, }, ], validatorAddress, @@ -57,16 +81,12 @@ export function useSuperfluidPool(): { console.error(e); reject(); } - } else { - console.warn( - "Superfluid delegate: amount config for use in sendLockAndSuperfluidDelegateMsg missing" - ); } } } ); }, - [account?.osmosis, derivedDataStore.superfluidPoolDetails] + [account?.osmosis, account?.address, bondDurations, apiUtils] ); return { delegateSharesToValidator }; diff --git a/packages/web/hooks/use-swap.tsx b/packages/web/hooks/use-swap.tsx index d50445c57b..b634cac848 100644 --- a/packages/web/hooks/use-swap.tsx +++ b/packages/web/hooks/use-swap.tsx @@ -18,7 +18,6 @@ import { makeMinimalAsset, } from "@osmosis-labs/utils"; import { sum } from "@osmosis-labs/utils"; -import { useQueryClient } from "@tanstack/react-query"; import { createTRPCReact, TRPCClientError } from "@trpc/react-query"; import { useRouter } from "next/router"; import { useState } from "react"; @@ -45,7 +44,6 @@ import { useStore } from "~/stores"; import { api, RouterInputs, RouterOutputs } from "~/utils/trpc"; import { useAmountInput } from "./input/use-amount-input"; -import { useBalances } from "./queries/cosmos/use-balances"; import { useDebouncedState } from "./use-debounced-state"; import { useFeatureFlags } from "./use-feature-flags"; import { usePreviousWhen } from "./use-previous-when"; @@ -97,7 +95,6 @@ export function useSwap( ) { const { chainStore, accountStore } = useStore(); const account = accountStore.getWallet(chainStore.osmosis.chainId); - const queryClient = useQueryClient(); const featureFlags = useFeatureFlags(); const { isOneClickTradingEnabled, oneClickTradingInfo } = useOneClickTradingSession(); @@ -415,14 +412,7 @@ export function useSwap( reject(new Error("No routes given")); } } - ).finally(() => { - // TODO: Move this logic to osmosis account store - // But for now we will invalidate query data here. - if (!account?.address) return; - useBalances.invalidateQuery({ address: account.address, queryClient }); - - inAmountInput.reset(); - }), + ).finally(() => inAmountInput.reset()), [ maxSlippage, inAmountInput, @@ -438,7 +428,6 @@ export function useSwap( swapAssets.fromAsset, swapAssets.toAsset, t, - queryClient, ] ); @@ -1232,6 +1221,8 @@ function useQueryRouterBestQuote( // the gas simulation will fail due to slippage and the user would see errors staleTime: 5_000, cacheTime: 5_000, + refetchInterval: 5_000, + // Disable retries, as useQueries // will block successfull quotes from being returned // if failed quotes are being returned diff --git a/packages/web/localizations/de.json b/packages/web/localizations/de.json index 9dbbe62710..d5b575cb93 100644 --- a/packages/web/localizations/de.json +++ b/packages/web/localizations/de.json @@ -391,7 +391,7 @@ "noAvailableShares": "Keine verfügbaren {denom} Freigaben", "noRoute": "Keine Route für diesen Handel", "noSendCurrency": "Die zu sendende Währung ist nicht festgelegt", - "notInitialized": "Nicht initialisiert", + "notInitialized": "Wird geladen...", "percentageSum": "Die Summe der Prozentsätze beträgt nicht 100", "scalingFactorTooLow": "Skalierungsfaktor zu niedrig", "zeroAmount": "Betrag ist Null", @@ -531,7 +531,6 @@ "spendLimitTitle": "Ausgabenlimit", "networkFeeLimitTitle": "Begrenzung der Netzwerkgebühren", "sessionPeriodTitle": "Sitzungszeitraum", - "resetPeriodTitle": "Reset-Zeitraum", "startButton": "Starten Sie den 1-Klick-Handel", "editSessionButton": "Änderungen speichern", "endingSession": "Sitzung beenden", @@ -563,16 +562,6 @@ "12hours": "12 Stunden" } }, - "resetPeriodScreen": { - "title": "Reset-Zeitraum", - "description": "Legen Sie einen Zeitraum fest, um das Ausgabenlimit-Kontingent zurückzusetzen.", - "periods": { - "day": "Tag", - "week": "Woche", - "month": "Monat", - "year": "Jahr" - } - }, "discardChanges": { "title": "Änderungen verwerfen?", "message": "Möchten Sie Ihre bestehenden 1-Click-Trading-Einstellungen beibehalten?", @@ -610,12 +599,9 @@ "checkBackForBondingRewards": "Schauen Sie noch einmal vorbei, um Bindungsprämien zu erhalten", "collapseDetails": "Poolzusammensetzung ausblenden", "commission": "Kommission", - "currentDailyEarn": "Sie verdienen derzeit", "dailyEarnAmount": "{amount} / Tag", - "day": "Tag", "delegated": "delegiert", "details": "Einzelheiten", - "earnMore": "Verdienen Sie {amount} mehr durch Bindung", "earnSwapFees": "Verdienen Sie Swap-Gebühren", "earnSwapFeesCaption": "Wandeln Sie Ihre Token in Aktien um und verdienen Sie bei jedem Tausch.", "from": "aus", diff --git a/packages/web/localizations/en.json b/packages/web/localizations/en.json index 3ba1a7cf1f..71fb26c629 100644 --- a/packages/web/localizations/en.json +++ b/packages/web/localizations/en.json @@ -391,7 +391,7 @@ "noAvailableShares": "No available {denom} shares", "noRoute": "No route for this trade", "noSendCurrency": "Currency to send not set", - "notInitialized": "Not initialized", + "notInitialized": "Loading...", "percentageSum": "Sum of percentages is not 100", "scalingFactorTooLow": "Scaling factor too low", "zeroAmount": "Amount is zero", @@ -531,7 +531,6 @@ "spendLimitTitle": "Spend Limit", "networkFeeLimitTitle": "Network fee limit", "sessionPeriodTitle": "Session period", - "resetPeriodTitle": "Reset period", "startButton": "Start 1-Click Trading", "editSessionButton": "Save changes", "endingSession": "Ending session", @@ -563,16 +562,6 @@ "12hours": "12 hours" } }, - "resetPeriodScreen": { - "title": "Reset period", - "description": "Set a duration to reset the spend limit quota.", - "periods": { - "day": "Day", - "week": "Week", - "month": "Month", - "year": "Year" - } - }, "discardChanges": { "title": "Discard Changes?", "message": "Do you wish to keep your existing 1-Click Trading settings?", @@ -610,12 +599,9 @@ "checkBackForBondingRewards": "Check back for bonding rewards", "collapseDetails": "Hide pool composition", "commission": "commission", - "currentDailyEarn": "You're currently earning", "dailyEarnAmount": "{amount} / day", - "day": "day", "delegated": "delegated", "details": "Details", - "earnMore": "Earn {amount} more by bonding", "earnSwapFees": "Earn swap fees", "earnSwapFeesCaption": "Convert your tokens into shares and earn on every swap.", "from": "from", diff --git a/packages/web/localizations/es.json b/packages/web/localizations/es.json index 1a2325e194..e8eb4357d4 100644 --- a/packages/web/localizations/es.json +++ b/packages/web/localizations/es.json @@ -391,7 +391,7 @@ "noAvailableShares": "No hay acciones disponibles {denom}", "noRoute": "No hay ruta para este cambio", "noSendCurrency": "Moneda para enviar no establecida", - "notInitialized": "No inicializado", + "notInitialized": "Cargando...", "percentageSum": "La suma de porcentajes no es 100", "scalingFactorTooLow": "Factor de escala demasiado bajo", "zeroAmount": "La cantidad es cero", @@ -531,7 +531,6 @@ "spendLimitTitle": "Límite de gasto", "networkFeeLimitTitle": "Límite de tarifa de red", "sessionPeriodTitle": "Período de sesión", - "resetPeriodTitle": "Periodo de reinicio", "startButton": "Comience a operar con 1 clic", "editSessionButton": "Guardar cambios", "endingSession": "Finalizando sesión", @@ -563,16 +562,6 @@ "12hours": "12 horas" } }, - "resetPeriodScreen": { - "title": "Periodo de reinicio", - "description": "Establezca una duración para restablecer la cuota de límite de gasto.", - "periods": { - "day": "Día", - "week": "Semana", - "month": "Mes", - "year": "Año" - } - }, "discardChanges": { "title": "¿Descartar los cambios?", "message": "¿Desea mantener su configuración actual de Trading con 1 clic?", @@ -610,12 +599,9 @@ "checkBackForBondingRewards": "Vuelva a consultar las recompensas de vinculación", "collapseDetails": "Ocultar detalles", "commission": "comisión", - "currentDailyEarn": "Actualmente estás ganando", "dailyEarnAmount": "{amount} / día", - "day": "día", "delegated": "delegado", "details": "Detalles", - "earnMore": "Gane {amount} más por abonar", "earnSwapFees": "Ganar tarifas de intercambio", "earnSwapFeesCaption": "Convierta sus tokens en participaciones y gane en cada intercambio.", "from": "de", diff --git a/packages/web/localizations/fa.json b/packages/web/localizations/fa.json index a852c4c7f9..fdd7bc8132 100644 --- a/packages/web/localizations/fa.json +++ b/packages/web/localizations/fa.json @@ -391,7 +391,7 @@ "noAvailableShares": "شما موجودی برای {denom} ندارید", "noRoute": "هیچ مسیر برای تبدیل پیدا نشد", "noSendCurrency": "ارز خروجی مشخص نشده است", - "notInitialized": "ساخته نشده است", + "notInitialized": "بارگذاری...", "percentageSum": "جمع درصد ها 100 نیست", "scalingFactorTooLow": "فاکتور تبدیل کوچک است", "zeroAmount": "مقدار صفر است", @@ -531,7 +531,6 @@ "spendLimitTitle": "محدودیت هزینه", "networkFeeLimitTitle": "محدودیت هزینه شبکه", "sessionPeriodTitle": "دوره جلسه", - "resetPeriodTitle": "بازنشانی دوره", "startButton": "شروع تجارت با 1 کلیک", "editSessionButton": "ذخیره تغییرات", "endingSession": "پایان جلسه", @@ -563,16 +562,6 @@ "12hours": "12 ساعت" } }, - "resetPeriodScreen": { - "title": "بازنشانی دوره", - "description": "مدت زمانی را برای بازنشانی سهمیه محدودیت هزینه تنظیم کنید.", - "periods": { - "day": "روز", - "week": "هفته", - "month": "ماه", - "year": "سال" - } - }, "discardChanges": { "title": "لغو تغییرات؟", "message": "آیا مایلید تنظیمات تجارت 1-کلیک موجود خود را حفظ کنید؟", @@ -610,12 +599,9 @@ "checkBackForBondingRewards": "بررسی مجدد برای جوایز دریافتی", "collapseDetails": "مخفی کردن جزئیات", "commission": "کمسیون", - "currentDailyEarn": "درآمد فغلی شما", "dailyEarnAmount": "{amount} / روز", - "day": "روز", "delegated": "مشارکت شده", "details": "جزئیات", - "earnMore": "دریافت {amount} درصد بازدهی بیشتر با قفل کردن", "earnSwapFees": "دریافت کارمزد تبدیل", "earnSwapFeesCaption": "توکن ها را به سهم تبدیل کنید و کارمزد دریافت کنید.", "from": "از", diff --git a/packages/web/localizations/fr.json b/packages/web/localizations/fr.json index 78310e7724..21fdcd57ff 100644 --- a/packages/web/localizations/fr.json +++ b/packages/web/localizations/fr.json @@ -391,7 +391,7 @@ "noAvailableShares": "Aucun actif {denom} disponible", "noRoute": "Pas d'itinéraire pour ce commerce", "noSendCurrency": "Devise à envoyer non définie", - "notInitialized": "Non initialisé", + "notInitialized": "Chargement...", "percentageSum": "La somme des pourcentages n'est pas égale à 100", "scalingFactorTooLow": "Facteur d'échelle trop faible", "zeroAmount": "Le montant est zéro", @@ -531,7 +531,6 @@ "spendLimitTitle": "Limite de dépenses", "networkFeeLimitTitle": "Limite des frais de réseau", "sessionPeriodTitle": "Période de séance", - "resetPeriodTitle": "Période de réinitialisation", "startButton": "Commencez le trading en 1 clic", "editSessionButton": "Sauvegarder les modifications", "endingSession": "Fin de la séance", @@ -563,16 +562,6 @@ "12hours": "12 heures" } }, - "resetPeriodScreen": { - "title": "Période de réinitialisation", - "description": "Définissez une durée pour réinitialiser le quota de limite de dépenses.", - "periods": { - "day": "Jour", - "week": "Semaine", - "month": "Mois", - "year": "Année" - } - }, "discardChanges": { "title": "Annuler les modifications?", "message": "Souhaitez-vous conserver vos paramètres de trading en 1-Click existants ?", @@ -610,12 +599,9 @@ "checkBackForBondingRewards": "Revenez pour les récompenses de liaison", "collapseDetails": "Cacher details", "commission": "commission", - "currentDailyEarn": "Vous gagnez actuellement", "dailyEarnAmount": "{amount} / jour", - "day": "jour", "delegated": "Déléguer", "details": "Détails", - "earnMore": "Gagnez {amount} plus en blocant", "earnSwapFees": "Gagner des commissions d'échange", "earnSwapFeesCaption": "Convertissez vos jetons en actions et gagnez sur chaque échange.", "from": "de", diff --git a/packages/web/localizations/gu.json b/packages/web/localizations/gu.json index 01c2a0769a..9b278ea467 100644 --- a/packages/web/localizations/gu.json +++ b/packages/web/localizations/gu.json @@ -391,7 +391,7 @@ "noAvailableShares": "કોઈ {denom} શેર ઉપલબ્ધ નથી", "noRoute": "આ વેપાર માટે કોઈ માર્ગ નથી", "noSendCurrency": "મોકલવાનું ચલણ સેટ નથી", - "notInitialized": "આરંભ કરેલ નથી", + "notInitialized": "લોડ કરી રહ્યું છે...", "percentageSum": "ટકાવારીનો સરવાળો 100 નથી", "scalingFactorTooLow": "સ્કેલિંગ પરિબળ ખૂબ ઓછું છે", "zeroAmount": "રકમ શૂન્ય છે", @@ -531,7 +531,6 @@ "spendLimitTitle": "ખર્ચ મર્યાદા", "networkFeeLimitTitle": "નેટવર્ક ફી મર્યાદા", "sessionPeriodTitle": "સત્રનો સમયગાળો", - "resetPeriodTitle": "સમયગાળો રીસેટ કરો", "startButton": "1-ક્લિક ટ્રેડિંગ શરૂ કરો", "editSessionButton": "ફેરફારો સંગ્રહ", "endingSession": "સત્ર સમાપ્ત", @@ -563,16 +562,6 @@ "12hours": "12 કલાક" } }, - "resetPeriodScreen": { - "title": "સમયગાળો રીસેટ કરો", - "description": "ખર્ચ મર્યાદા ક્વોટા રીસેટ કરવા માટે સમયગાળો સેટ કરો.", - "periods": { - "day": "દિવસ", - "week": "અઠવાડિયું", - "month": "માસ", - "year": "વર્ષ" - } - }, "discardChanges": { "title": "ફેરફારોને અવગણો?", "message": "શું તમે તમારી હાલની 1-ક્લિક ટ્રેડિંગ સેટિંગ્સ રાખવા માંગો છો?", @@ -610,12 +599,9 @@ "checkBackForBondingRewards": "બોન્ડિંગ પુરસ્કારો માટે પાછા તપાસો", "collapseDetails": "પૂલ રચના છુપાવો", "commission": "કમિશન", - "currentDailyEarn": "તમે હાલમાં કમાણી કરી રહ્યાં છો", "dailyEarnAmount": "{amount} / દિવસ", - "day": "દિવસ", "delegated": "સોંપેલ", "details": "વિગતો", - "earnMore": "બોન્ડિંગ દ્વારા વધુ {amount} કમાઓ", "earnSwapFees": "સ્વેપ ફી કમાઓ", "earnSwapFeesCaption": "તમારા ટોકન્સને શેરમાં કન્વર્ટ કરો અને દરેક સ્વેપ પર કમાણી કરો.", "from": "થી", diff --git a/packages/web/localizations/hi.json b/packages/web/localizations/hi.json index dcaa5b7440..98f84aa6c5 100644 --- a/packages/web/localizations/hi.json +++ b/packages/web/localizations/hi.json @@ -391,7 +391,7 @@ "noAvailableShares": "कोई उपलब्ध {denom} शेयर नहीं", "noRoute": "इस व्यापार के लिए कोई मार्ग नहीं", "noSendCurrency": "भेजने की मुद्रा निर्धारित नहीं है", - "notInitialized": "प्रारंभ नहीं किया गया", + "notInitialized": "लोड हो रहा है...", "percentageSum": "प्रतिशतों का योग 100 नहीं है", "scalingFactorTooLow": "स्केलिंग कारक बहुत कम है", "zeroAmount": "राशि शून्य है", @@ -531,7 +531,6 @@ "spendLimitTitle": "खर्च सीमा", "networkFeeLimitTitle": "नेटवर्क शुल्क सीमा", "sessionPeriodTitle": "सत्र अवधि", - "resetPeriodTitle": "रीसेट अवधि", "startButton": "1-क्लिक ट्रेडिंग प्रारंभ करें", "editSessionButton": "परिवर्तनों को सुरक्षित करें", "endingSession": "समापन सत्र", @@ -563,16 +562,6 @@ "12hours": "12 घंटे" } }, - "resetPeriodScreen": { - "title": "रीसेट अवधि", - "description": "व्यय सीमा कोटा रीसेट करने के लिए एक अवधि निर्धारित करें।", - "periods": { - "day": "दिन", - "week": "सप्ताह", - "month": "महीना", - "year": "वर्ष" - } - }, "discardChanges": { "title": "परिवर्तनों को निरस्त करें?", "message": "क्या आप अपनी मौजूदा 1-क्लिक ट्रेडिंग सेटिंग बनाए रखना चाहते हैं?", @@ -610,12 +599,9 @@ "checkBackForBondingRewards": "बॉन्डिंग पुरस्कारों के लिए दोबारा जाँचें", "collapseDetails": "पूल संरचना छिपाएँ", "commission": "आयोग", - "currentDailyEarn": "आप वर्तमान में कमा रहे हैं", "dailyEarnAmount": "{amount} / दिन", - "day": "दिन", "delegated": "प्रत्यायोजित", "details": "विवरण", - "earnMore": "बॉन्डिंग द्वारा {amount} अधिक अर्जित करें", "earnSwapFees": "स्वैप शुल्क अर्जित करें", "earnSwapFeesCaption": "अपने टोकन को शेयरों में बदलें और प्रत्येक स्वैप पर कमाएं।", "from": "से", diff --git a/packages/web/localizations/ja.json b/packages/web/localizations/ja.json index 1c53735922..5cdb3701aa 100644 --- a/packages/web/localizations/ja.json +++ b/packages/web/localizations/ja.json @@ -391,7 +391,7 @@ "noAvailableShares": "利用可能な{denom}共有がありません", "noRoute": "この取引にはルートがありません", "noSendCurrency": "送金通貨が設定されていません", - "notInitialized": "初期化されていません", + "notInitialized": "読み込み中...", "percentageSum": "パーセンテージの合計が 100 にならない", "scalingFactorTooLow": "倍率が低すぎる", "zeroAmount": "金額はゼロです", @@ -531,7 +531,6 @@ "spendLimitTitle": "支出制限", "networkFeeLimitTitle": "ネットワーク料金の制限", "sessionPeriodTitle": "会期", - "resetPeriodTitle": "リセット期間", "startButton": "1-Click取引を開始する", "editSessionButton": "変更内容を保存", "endingSession": "セッションの終了", @@ -563,16 +562,6 @@ "12hours": "12時間" } }, - "resetPeriodScreen": { - "title": "リセット期間", - "description": "支出制限割り当てをリセットする期間を設定します。", - "periods": { - "day": "日", - "week": "週", - "month": "月", - "year": "年" - } - }, "discardChanges": { "title": "変更を破棄?", "message": "既存の 1-Click 取引設定を維持しますか?", @@ -610,12 +599,9 @@ "checkBackForBondingRewards": "絆の報酬をもう一度チェックしてください", "collapseDetails": "プール構成を非表示にする", "commission": "手数料", - "currentDailyEarn": "あなたは現在稼いでいます", "dailyEarnAmount": "{amount} / 日", - "day": "日", "delegated": "委任された", "details": "詳細", - "earnMore": "ボンディングによってさらに{amount}獲得しましょう", "earnSwapFees": "スワップ手数料を獲得する", "earnSwapFeesCaption": "トークンを株式に変換し、交換するたびに収益を上げます。", "from": "から", diff --git a/packages/web/localizations/ko.json b/packages/web/localizations/ko.json index b5011ac789..417fe3aaa9 100644 --- a/packages/web/localizations/ko.json +++ b/packages/web/localizations/ko.json @@ -391,7 +391,7 @@ "noAvailableShares": "사용 가능한 {denom}이 없습니다", "noRoute": "이 거래를 위한 경로가 없습니다", "noSendCurrency": "전송할 토큰이 설정되지 않았습니다", - "notInitialized": "초기화되지 않았습니다", + "notInitialized": "로드 중...", "percentageSum": "퍼센티지의 합이 100이 아닙니다", "scalingFactorTooLow": "단위조정인수가 너무 낮습니다", "zeroAmount": "수량이 0입니다.", @@ -531,7 +531,6 @@ "spendLimitTitle": "지출 한도", "networkFeeLimitTitle": "네트워크 수수료 한도", "sessionPeriodTitle": "세션 기간", - "resetPeriodTitle": "재설정 기간", "startButton": "1-클릭 거래 시작", "editSessionButton": "변경 사항을 저장하다", "endingSession": "세션 종료", @@ -563,16 +562,6 @@ "12hours": "12 시간" } }, - "resetPeriodScreen": { - "title": "재설정 기간", - "description": "지출 한도 할당량을 재설정하는 기간을 설정하세요.", - "periods": { - "day": "낮", - "week": "주", - "month": "월", - "year": "년도" - } - }, "discardChanges": { "title": "변경 사항을 취소?", "message": "기존 1-클릭 거래 설정을 유지하시겠습니까?", @@ -610,12 +599,9 @@ "checkBackForBondingRewards": "나중에 다시 방문하여 본딩 보상을 확인하십시오.", "collapseDetails": "닫기", "commission": "수수료", - "currentDailyEarn": "내 일일 보상", "dailyEarnAmount": "{amount} /일", - "day": "일", "delegated": "위임된 자산", "details": "상세정보", - "earnMore": "본딩으로 {amount} 더 벌기", "earnSwapFees": "교환 수수료 수익 받기", "earnSwapFeesCaption": "풀에 유동성을 공급하고, 교환이 발생하면 수수료를 받으세요", "from": "출처:", diff --git a/packages/web/localizations/pl.json b/packages/web/localizations/pl.json index 6dbab55e71..e7628b846e 100644 --- a/packages/web/localizations/pl.json +++ b/packages/web/localizations/pl.json @@ -391,7 +391,7 @@ "noAvailableShares": "Nie ma dostępnych {denom} aktyw", "noRoute": "Brak trasy dla tej transakcji", "noSendCurrency": "Nie ustawiona waluta do wysłania", - "notInitialized": "Nie zinicjalizowane", + "notInitialized": "Ładowanie...", "percentageSum": "Suma procentów nie jest równa 100", "scalingFactorTooLow": "Zbyt niski współczynnik skalowania", "zeroAmount": "Ilość jest zerowa", @@ -531,7 +531,6 @@ "spendLimitTitle": "Limit wydatków", "networkFeeLimitTitle": "Limit opłat sieciowych", "sessionPeriodTitle": "Okres sesji", - "resetPeriodTitle": "Zresetuj okres", "startButton": "Rozpocznij handel jednym kliknięciem", "editSessionButton": "Zapisz zmiany", "endingSession": "Zakończenie sesji", @@ -563,16 +562,6 @@ "12hours": "12 godzin" } }, - "resetPeriodScreen": { - "title": "Zresetuj okres", - "description": "Ustaw czas resetowania limitu wydatków.", - "periods": { - "day": "Dzień", - "week": "Tydzień", - "month": "Miesiąc", - "year": "Rok" - } - }, "discardChanges": { "title": "Odrzucać zmiany?", "message": "Czy chcesz zachować swoje dotychczasowe ustawienia handlu jednym kliknięciem?", @@ -610,12 +599,9 @@ "checkBackForBondingRewards": "Sprawdź ponownie po nagrody za wiązanie", "collapseDetails": "Ukryj szczegóły", "commission": "prowizja", - "currentDailyEarn": "Aktualnie zarabiasz", "dailyEarnAmount": "{amount} / dzień", - "day": "dzień", "delegated": "delegowane", "details": "Szczegóły", - "earnMore": "Zarabiaj {amount} więcej dzięki łączeniu", "earnSwapFees": "Zdobywaj prowizje z wymian", "earnSwapFeesCaption": "Przekształć swoje tokeny w aktywa i zarabiaj na każdej wymianie.", "from": "od", diff --git a/packages/web/localizations/pt-br.json b/packages/web/localizations/pt-br.json index 347e0e3ecc..ae2e421eea 100644 --- a/packages/web/localizations/pt-br.json +++ b/packages/web/localizations/pt-br.json @@ -391,7 +391,7 @@ "noAvailableShares": "Não está disponivel {denom} participações", "noRoute": "Sem rota para esta troca", "noSendCurrency": "Moeda para envio não configurada", - "notInitialized": "Não Inicializado", + "notInitialized": "Carregando...", "percentageSum": "A soma das porcentagens não é 100", "scalingFactorTooLow": "Fator de escalonamento muito baixo", "zeroAmount": "Quantia é zero", @@ -531,7 +531,6 @@ "spendLimitTitle": "Limite de gastos", "networkFeeLimitTitle": "Limite de taxa de rede", "sessionPeriodTitle": "Período da sessão", - "resetPeriodTitle": "Período de redefinição", "startButton": "Comece a negociação com 1 clique", "editSessionButton": "Salvar alterações", "endingSession": "Encerrando sessão", @@ -563,16 +562,6 @@ "12hours": "12 horas" } }, - "resetPeriodScreen": { - "title": "Período de redefinição", - "description": "Defina uma duração para redefinir a cota de limite de gastos.", - "periods": { - "day": "Dia", - "week": "Semana", - "month": "Mês", - "year": "Ano" - } - }, "discardChanges": { "title": "Descartar mudanças?", "message": "Deseja manter suas configurações existentes de negociação em 1 clique?", @@ -610,12 +599,9 @@ "checkBackForBondingRewards": "Confira mais tarde por recompensas de delegação", "collapseDetails": "Esconder composição da piscina", "commission": "comissão", - "currentDailyEarn": "Você está ganhando atualmente", "dailyEarnAmount": "{amount} / dia", - "day": "dia", "delegated": "delegado", "details": "Detalhes", - "earnMore": "Ganhe {amount} mais travando", "earnSwapFees": "Ganhe taxas de troca", "earnSwapFeesCaption": "Converta seus tokens em ações e ganhe em cada troca", "from": "de", diff --git a/packages/web/localizations/ro.json b/packages/web/localizations/ro.json index 114f0db193..07f37c7022 100644 --- a/packages/web/localizations/ro.json +++ b/packages/web/localizations/ro.json @@ -391,7 +391,7 @@ "noAvailableShares": "Actiuni {denom} nu sunt disponibile", "noRoute": "Nicio rută pentru acest comerț", "noSendCurrency": "Moneda de trimis nu este selectata", - "notInitialized": "Neinitializat", + "notInitialized": "Se încarcă...", "percentageSum": "Suma procentelor nu este 100", "scalingFactorTooLow": "Factorul de scalare este prea mic", "zeroAmount": "Suma este 0", @@ -531,7 +531,6 @@ "spendLimitTitle": "Limita de cheltuieli", "networkFeeLimitTitle": "Limită taxă de rețea", "sessionPeriodTitle": "Perioada de sesiune", - "resetPeriodTitle": "Perioada de resetare", "startButton": "Începeți tranzacționarea cu 1 clic", "editSessionButton": "Salvează modificările", "endingSession": "Încheierea sesiunii", @@ -563,16 +562,6 @@ "12hours": "12 ore" } }, - "resetPeriodScreen": { - "title": "Perioada de resetare", - "description": "Setați o durată pentru a reseta cota limită de cheltuieli.", - "periods": { - "day": "Zi", - "week": "Săptămână", - "month": "Lună", - "year": "An" - } - }, "discardChanges": { "title": "Renunțați la modificări?", "message": "Doriți să vă păstrați setările existente de tranzacționare cu 1 clic?", @@ -610,12 +599,9 @@ "checkBackForBondingRewards": "Revino mai tarziu pentru recompense pentru blocare", "collapseDetails": "Ascunde detalii", "commission": "comision", - "currentDailyEarn": "Acum castigi", "dailyEarnAmount": "{amount} /zi", - "day": "zi", "delegated": "Delegate", "details": "Detalii", - "earnMore": "Castiga mai mult prin blocare", "earnSwapFees": "Castiga taxe de tranzactionare", "earnSwapFeesCaption": "Converteste-ti activele in actiuni pentru a castiga din fiecare schimb.", "from": "de la", diff --git a/packages/web/localizations/ru.json b/packages/web/localizations/ru.json index 483fc5021c..3ae9df7a24 100644 --- a/packages/web/localizations/ru.json +++ b/packages/web/localizations/ru.json @@ -391,7 +391,7 @@ "noAvailableShares": "Нет доступных общих ресурсов {denom}", "noRoute": "Нет маршрута для этой сделки", "noSendCurrency": "Валюта для отправки не установлена", - "notInitialized": "Не инициализирован", + "notInitialized": "Загрузка...", "percentageSum": "Сумма процентов не равна 100", "scalingFactorTooLow": "Коэффициент масштабирования слишком низкий", "zeroAmount": "Сумма равна нулю", @@ -531,7 +531,6 @@ "spendLimitTitle": "Лимит расходов", "networkFeeLimitTitle": "Лимит сетевой комиссии", "sessionPeriodTitle": "Период сессии", - "resetPeriodTitle": "Период сброса", "startButton": "Начать торговлю в 1 клик", "editSessionButton": "Сохранить изменения", "endingSession": "Завершение сеанса", @@ -563,16 +562,6 @@ "12hours": "12 часов" } }, - "resetPeriodScreen": { - "title": "Период сброса", - "description": "Установите продолжительность сброса квоты лимита расходов.", - "periods": { - "day": "День", - "week": "Неделя", - "month": "Месяц", - "year": "Год" - } - }, "discardChanges": { "title": "Отменить изменения?", "message": "Хотите ли вы сохранить существующие настройки торговли в 1 клик?", @@ -610,12 +599,9 @@ "checkBackForBondingRewards": "Возвращайтесь, чтобы узнать о наградах за связь", "collapseDetails": "Скрыть состав пула", "commission": "комиссия", - "currentDailyEarn": "В настоящее время вы зарабатываете", "dailyEarnAmount": "{amount} / день", - "day": "день", "delegated": "делегирован", "details": "Подробности", - "earnMore": "Зарабатывайте {amount} больше, связываясь", "earnSwapFees": "Зарабатывайте комиссию за своп", "earnSwapFeesCaption": "Конвертируйте свои токены в акции и зарабатывайте на каждом свопе.", "from": "от", diff --git a/packages/web/localizations/tr.json b/packages/web/localizations/tr.json index 18b628e3f7..3c6aeba867 100644 --- a/packages/web/localizations/tr.json +++ b/packages/web/localizations/tr.json @@ -391,7 +391,7 @@ "noAvailableShares": "Kullanılabilir {denom} hissesi yok", "noRoute": "Bu ticaret için Rota yok", "noSendCurrency": "Gönderilecek varlık seçilmedi", - "notInitialized": "Başlatılmadı", + "notInitialized": "Yükleniyor...", "percentageSum": "Yüzdeler toplamı 100'e eşit değil", "scalingFactorTooLow": "Ölçeklendirme faktörü çok düşük", "zeroAmount": "Miktar sıfır", @@ -531,7 +531,6 @@ "spendLimitTitle": "Harcama Limiti", "networkFeeLimitTitle": "Ağ ücreti limiti", "sessionPeriodTitle": "Oturum dönemi", - "resetPeriodTitle": "Dönemi sıfırla", "startButton": "Tek Tıklamayla Ticareti Başlatın", "editSessionButton": "Değişiklikleri Kaydet", "endingSession": "Oturumu sonlandır", @@ -563,16 +562,6 @@ "12hours": "12 saat" } }, - "resetPeriodScreen": { - "title": "Dönemi sıfırla", - "description": "Harcama sınırı kotasını sıfırlamak için bir süre ayarlayın.", - "periods": { - "day": "Gün", - "week": "Hafta", - "month": "Ay", - "year": "Yıl" - } - }, "discardChanges": { "title": "Değişiklikleri gözardı et?", "message": "Mevcut Tek Tıklamayla Ticaret ayarlarınızı korumak istiyor musunuz?", @@ -610,12 +599,9 @@ "checkBackForBondingRewards": "Bağlanma ödülleri için tekrar kontrol edin", "collapseDetails": "Detayları gizle", "commission": "komisyon", - "currentDailyEarn": "Şu anda kazanıyorsunuz", "dailyEarnAmount": "{amount} / gün", - "day": "gün", "delegated": "delege edilen", "details": "Detaylar", - "earnMore": "Bağlayarak {amount} daha fazla kazanın", "earnSwapFees": "Takastan komisyon kazanın", "earnSwapFeesCaption": "Tokenlerinizi hisseye dönüştürün ve her takasta kazanın.", "from": "kimden", diff --git a/packages/web/localizations/zh-cn.json b/packages/web/localizations/zh-cn.json index a03b59da63..99da9f7658 100644 --- a/packages/web/localizations/zh-cn.json +++ b/packages/web/localizations/zh-cn.json @@ -391,7 +391,7 @@ "noAvailableShares": "没有可用 {denom} 份额", "noRoute": "此交易没有路线", "noSendCurrency": "未设置发送货币", - "notInitialized": "未初始化", + "notInitialized": "加载中...", "percentageSum": "百分比总和不是 100", "scalingFactorTooLow": "比例因子太低", "zeroAmount": "数量为零", @@ -531,7 +531,6 @@ "spendLimitTitle": "消费限额", "networkFeeLimitTitle": "网络费用限额", "sessionPeriodTitle": "会议期间", - "resetPeriodTitle": "重置期", "startButton": "开始一键交易", "editSessionButton": "保存更改", "endingSession": "结束会议", @@ -563,16 +562,6 @@ "12hours": "12小时" } }, - "resetPeriodScreen": { - "title": "重置期", - "description": "设置重置支出限额配额的持续时间。", - "periods": { - "day": "天", - "week": "星期", - "month": "月", - "year": "年" - } - }, "discardChanges": { "title": "放弃更改?", "message": "您希望保留现有的一键交易设置吗?", @@ -610,12 +599,9 @@ "checkBackForBondingRewards": "查看绑定奖励", "collapseDetails": "收起详情", "commission": "佣金", - "currentDailyEarn": "您当前正在赚取", "dailyEarnAmount": "{amount} /天", - "day": "天", "delegated": "委托", "details": "详情", - "earnMore": "通过结合赚取 {amount}", "earnSwapFees": "赚取交易费", "earnSwapFeesCaption": "将您的代币转为份额并在每次兑换中赚取收益.", "from": "来自", diff --git a/packages/web/localizations/zh-hk.json b/packages/web/localizations/zh-hk.json index 91acff5496..1e9f42a4dc 100644 --- a/packages/web/localizations/zh-hk.json +++ b/packages/web/localizations/zh-hk.json @@ -391,7 +391,7 @@ "noAvailableShares": "冇可用嘅 {denom} 份額喎", "noRoute": "此交易沒有路線", "noSendCurrency": "未設定要傳送嘅貨幣", - "notInitialized": "未初始化", + "notInitialized": "載入中...", "percentageSum": "所有百份比值總和非100", "scalingFactorTooLow": "縮放因數過低", "zeroAmount": "未提供幣額", @@ -531,7 +531,6 @@ "spendLimitTitle": "消費限額", "networkFeeLimitTitle": "網路費用限額", "sessionPeriodTitle": "會議期間", - "resetPeriodTitle": "重置期", "startButton": "開始一鍵交易", "editSessionButton": "儲存變更", "endingSession": "結束會議", @@ -563,16 +562,6 @@ "12hours": "12小時" } }, - "resetPeriodScreen": { - "title": "重置期", - "description": "設定重置支出限額配額的持續時間。", - "periods": { - "day": "天", - "week": "星期", - "month": "月", - "year": "年" - } - }, "discardChanges": { "title": "放棄更改?", "message": "您希望保留現有的一鍵交易設定嗎?", @@ -610,12 +599,9 @@ "checkBackForBondingRewards": "稍後回來確認鎖定獎勵", "collapseDetails": "收起詳情", "commission": "佣金", - "currentDailyEarn": "你現在正賺取", "dailyEarnAmount": "{amount} /天", - "day": "日", "delegated": "已質押", "details": "詳情", - "earnMore": "通過鎖定賺取 {amount}", "earnSwapFees": "賺取兌換費", "earnSwapFeesCaption": "轉換你嘅代幣成流動性池份額並賺取每次兌換嘅兌換費", "from": "來自", diff --git a/packages/web/localizations/zh-tw.json b/packages/web/localizations/zh-tw.json index 8a9afb689f..8e26b8c99e 100644 --- a/packages/web/localizations/zh-tw.json +++ b/packages/web/localizations/zh-tw.json @@ -391,7 +391,7 @@ "noAvailableShares": "沒有可用的 {denom} 份額", "noRoute": "此交易沒有路線", "noSendCurrency": "未設定要傳送的貨幣", - "notInitialized": "未初始化", + "notInitialized": "載入中...", "percentageSum": "所有百份比值總和非100", "scalingFactorTooLow": "縮放因數過低", "zeroAmount": "未提供幣額", @@ -531,7 +531,6 @@ "spendLimitTitle": "消費限額", "networkFeeLimitTitle": "網路費用限額", "sessionPeriodTitle": "會議期間", - "resetPeriodTitle": "重置期", "startButton": "開始一鍵交易", "editSessionButton": "儲存變更", "endingSession": "結束會議", @@ -563,16 +562,6 @@ "12hours": "12小時" } }, - "resetPeriodScreen": { - "title": "重置期", - "description": "設定重置支出限額配額的持續時間。", - "periods": { - "day": "天", - "week": "星期", - "month": "月", - "year": "年" - } - }, "discardChanges": { "title": "放棄更改?", "message": "您希望保留現有的一鍵交易設定嗎?", @@ -610,12 +599,9 @@ "checkBackForBondingRewards": "稍後回來確認鎖定獎勵", "collapseDetails": "收起詳情", "commission": "佣金", - "currentDailyEarn": "你現在正賺取", "dailyEarnAmount": "{amount} /天", - "day": "日", "delegated": "已質押", "details": "詳情", - "earnMore": "通過鎖定賺取 {amount}", "earnSwapFees": "賺取兌換費", "earnSwapFeesCaption": "轉換你的代幣成流動性池份額並賺取每次兌換的兌換費", "from": "來自", diff --git a/packages/web/modals/add-liquidity.tsx b/packages/web/modals/add-liquidity.tsx index ed09003ebc..e4658e6cc0 100644 --- a/packages/web/modals/add-liquidity.tsx +++ b/packages/web/modals/add-liquidity.tsx @@ -1,4 +1,7 @@ -import { ObservableAddLiquidityConfig } from "@osmosis-labs/stores"; +import { + NotInitializedError, + ObservableAddLiquidityConfig, +} from "@osmosis-labs/stores"; import { observer } from "mobx-react-lite"; import { useState } from "react"; import { FunctionComponent } from "react"; @@ -74,6 +77,7 @@ export const AddLiquidityModal: FunctionComponent< props.onAddLiquidity(addLiquidityResult); } }, + isLoading: config.error && config.error instanceof NotInitializedError, children: config.error ? t(...tError(config.error)) : pool?.type === "concentrated" && diff --git a/packages/web/modals/increase-concentrated-liquidity.tsx b/packages/web/modals/increase-concentrated-liquidity.tsx index 05855ebe48..f57b4b4fe6 100644 --- a/packages/web/modals/increase-concentrated-liquidity.tsx +++ b/packages/web/modals/increase-concentrated-liquidity.tsx @@ -239,7 +239,7 @@ export const IncreaseConcentratedLiquidityModal: FunctionComponent< onUpdate={useCallback( (amount) => { config.setAnchorAsset("base"); - config.baseDepositAmountIn.setAmount(amount.toString()); + config.baseDepositAmountIn.setAmount(amount); }, [config] )} @@ -256,7 +256,7 @@ export const IncreaseConcentratedLiquidityModal: FunctionComponent< onUpdate={useCallback( (amount) => { config.setAnchorAsset("quote"); - config.quoteDepositAmountIn.setAmount(amount.toString()); + config.quoteDepositAmountIn.setAmount(amount); }, [config] )} diff --git a/packages/web/modals/lock-tokens.tsx b/packages/web/modals/lock-tokens.tsx index 443885d7b8..2ecacc0562 100644 --- a/packages/web/modals/lock-tokens.tsx +++ b/packages/web/modals/lock-tokens.tsx @@ -1,4 +1,4 @@ -import { AmountConfig } from "@osmosis-labs/keplr-hooks"; +import { BondDuration } from "@osmosis-labs/server"; import classNames from "classnames"; import { Duration } from "dayjs/plugin/duration"; import { observer } from "mobx-react-lite"; @@ -8,51 +8,64 @@ import { InputBox } from "~/components/input"; import { tError } from "~/components/localization"; import { Checkbox } from "~/components/ui/checkbox"; import { useTranslation } from "~/hooks"; -import { useConnectWalletModalRedirect, useCurrentLanguage } from "~/hooks"; +import { useConnectWalletModalRedirect } from "~/hooks"; +import { useAmountInput } from "~/hooks/input/use-amount-input"; import { ModalBase, ModalBaseProps } from "~/modals/base"; import { useStore } from "~/stores"; +import { api } from "~/utils/trpc"; -export const LockTokensModal: FunctionComponent< +export const LockShares: FunctionComponent< { poolId: string; - amountConfig: AmountConfig; + amountConfig: ReturnType; /** `electSuperfluid` is left undefined if it is irrelevant- if the user has already opted into superfluid in the past. */ onLockToken: (duration: Duration, electSuperfluid?: boolean) => void; + bondDurations?: BondDuration[]; } & ModalBaseProps > = observer((props) => { - const { poolId, amountConfig: config, onLockToken } = props; + const { + poolId, + amountConfig: config, + onLockToken, + bondDurations: givenBondDurations, + } = props; const { t } = useTranslation(); - const locale = useCurrentLanguage(); - const { chainStore, accountStore, queriesStore, derivedDataStore } = - useStore(); + const { accountStore } = useStore(); - const { chainId } = chainStore.osmosis; - const queryOsmosis = queriesStore.get(chainId).osmosis!; - const account = accountStore.getWallet(chainId); + const account = accountStore.getWallet(accountStore.osmosisChainId); const address = account?.address ?? ""; - // initialize pool data stores once root pool store is loaded - const { sharePoolDetail, superfluidPoolDetail, poolBonding } = - derivedDataStore.getForPool(poolId); + const { data: bondDurations_ } = + api.edge.pools.getSharePoolBondDurations.useQuery( + { + poolId: poolId, + userOsmoAddress: address, + }, + { + enabled: !givenBondDurations && Boolean(address), + // expensive query + trpc: { + context: { + skipBatch: true, + }, + }, + } + ); const bondDurations = useMemo( - () => poolBonding?.bondDurations.filter((bd) => bd.bondable) ?? [], - [poolBonding?.bondDurations] - ); - const availableToken = queryOsmosis.queryGammPoolShare.getAvailableGammShare( - address, - poolId + () => + (givenBondDurations ?? bondDurations_ ?? []).filter((bd) => bd.bondable), + [givenBondDurations, bondDurations_] ); - const isSendingMsg = account?.txTypeInProgress !== ""; + /** If they have a superfluid validator already, they will automatically SFS stake if they select the highest gauge. (Cant be undone) * TODO: perhaps we should display this in the view somehow */ - const hasSuperfluidValidator = - superfluidPoolDetail?.userSharesDelegations && - superfluidPoolDetail.userSharesDelegations.length > 0; - const superfluidApr = - bondDurations[bondDurations.length - 1]?.superfluid?.apr; + const superfluidBondDuration = + bondDurations[bondDurations.length - 1]?.superfluid; + const isSuperfluid = Boolean(superfluidBondDuration); + const hasSuperfluidValidator = Boolean(superfluidBondDuration?.delegated); // component state const [selectedDurationIndex, setSelectedDurationIndex] = useState< @@ -62,20 +75,25 @@ export const LockTokensModal: FunctionComponent< /** Superfluid duration assumed to be longest duration in lockableDurations * chain parameter. */ - const longestDuration = sharePoolDetail?.longestDuration; + const selectedDuration = bondDurations.find( + (_, index) => index === selectedDurationIndex + ); const superfluidDurationSelected = selectedDurationIndex !== null && bondDurations.length > selectedDurationIndex && - bondDurations[selectedDurationIndex].duration.asMilliseconds() === - longestDuration?.asMilliseconds(); + selectedDuration && + selectedDuration.duration.asMilliseconds() === + superfluidBondDuration?.duration?.asMilliseconds(); const [electSuperfluid, setElectSuperfluid] = useState(false); useEffect(() => { - if (superfluidPoolDetail?.isSuperfluid) { + if (isSuperfluid) { setElectSuperfluid(true); } - }, [superfluidPoolDetail?.isSuperfluid]); + }, [isSuperfluid]); + const superfluidApr = + bondDurations[bondDurations.length - 1]?.superfluid?.apr; let selectedApr = selectedDurationIndex !== null ? bondDurations[selectedDurationIndex]?.aggregateApr @@ -94,32 +112,22 @@ export const LockTokensModal: FunctionComponent< const { showModalBase, accountActionButton } = useConnectWalletModalRedirect( { disabled: - config.error !== undefined || - selectedDurationIndex === null || - isSendingMsg, + Boolean(config.error) || + !selectedDuration || + Boolean(account?.txTypeInProgress), onClick: () => { - const selectedDuration = bondDurations.find( - (_, index) => index === selectedDurationIndex - ); if (selectedDuration) { onLockToken( selectedDuration.duration, - // Allow superfluid only for the highest gauge index. - // On the mainnet, this standard works well - // Logically it could be a problem if it's not the mainnet - hasSuperfluidValidator || - !superfluidPoolDetail?.isSuperfluid || - !superfluidDurationSelected - ? undefined - : electSuperfluid + !hasSuperfluidValidator && superfluidInEffect ); + } else { + console.warn("No duration selected"); } }, children: (config.error ? t(...tError(config.error)) : false) || - (electSuperfluid && - !hasSuperfluidValidator && - superfluidDurationSelected + (superfluidInEffect && !hasSuperfluidValidator ? t("lockToken.buttonNext") : superfluidInEffect ? t("lockToken.buttonBondStake") @@ -154,15 +162,15 @@ export const LockTokensModal: FunctionComponent<
{bondDurations.map(({ duration, aggregateApr }, index) => ( setSelectedDurationIndex(index)} apr={aggregateApr?.maxDecimals(2).trim(true).toString()} /> ))}
- {superfluidPoolDetail?.isSuperfluid && ( + {isSuperfluid && (
- {sharePoolDetail?.longestDuration && ( + {superfluidBondDuration && ( {t("lockToken.bondingRequirement", { - numDays: sharePoolDetail.longestDuration + numDays: superfluidBondDuration.duration .asDays() .toString(), })} @@ -197,15 +205,15 @@ export const LockTokensModal: FunctionComponent<
{t("lockToken.amountToBond")} - {availableToken && ( + {config.balance && (
{t("lockToken.availableToken")} config.setIsMax(true)} + onClick={() => config.setFraction(1)} > {t("pool.sharesAmount", { - shares: availableToken + shares: config.balance .trim(true) .hideDenom(true) .toString(), @@ -216,7 +224,7 @@ export const LockTokensModal: FunctionComponent<
config.setAmount(value)} placeholder="" rightEntry diff --git a/packages/web/modals/remove-liquidity.tsx b/packages/web/modals/remove-liquidity.tsx index 0162284834..15905aa536 100644 --- a/packages/web/modals/remove-liquidity.tsx +++ b/packages/web/modals/remove-liquidity.tsx @@ -1,4 +1,5 @@ -import { Dec } from "@keplr-wallet/unit"; +import { Dec, Int } from "@keplr-wallet/unit"; +import { WeightedPoolRawResponse } from "@osmosis-labs/server"; import { NoAvailableSharesError } from "@osmosis-labs/stores"; import { observer } from "mobx-react-lite"; import { FunctionComponent, useCallback, useState } from "react"; @@ -12,6 +13,7 @@ import { useTranslation } from "~/hooks"; import { useConnectWalletModalRedirect } from "~/hooks"; import { ModalBase, ModalBaseProps } from "~/modals/base"; import { useStore } from "~/stores"; +import { api } from "~/utils/trpc"; export const RemoveLiquidityModal: FunctionComponent< { @@ -21,19 +23,30 @@ export const RemoveLiquidityModal: FunctionComponent< RemovableShareLiquidity > = observer((props) => { const { poolId } = props; - const { chainStore, accountStore } = useStore(); + const { accountStore } = useStore(); const { t } = useTranslation(); - const { chainId } = chainStore.osmosis; - const account = accountStore.getWallet(chainId); + const account = accountStore.getWallet(accountStore.osmosisChainId); const isSendingMsg = account?.txTypeInProgress !== ""; const [percentage, setPercentage] = useState("50"); + const { data: pool, isLoading: isLoadingPool } = + api.edge.pools.getSharePool.useQuery({ + poolId, + }); + const removeLiquidity = useCallback( () => new Promise((resolve, reject) => { if (!account) return reject("No account"); + if (!pool) return reject("Pool pool found. ID: " + poolId); + + const exitFee = + pool.type === "weighted" + ? (pool.raw as WeightedPoolRawResponse).pool_params.exit_fee + : "0"; + account.osmosis .sendExitPoolMsg( poolId, @@ -41,6 +54,9 @@ export const RemoveLiquidityModal: FunctionComponent< .mul(new Dec(percentage).quo(new Dec(100))) .toDec() .toString(), + new Int(pool.raw.total_shares.amount), + pool.reserveCoins.map((coin) => coin.toCoin()), + new Dec(exitFee), undefined, undefined, (tx) => { @@ -50,12 +66,12 @@ export const RemoveLiquidityModal: FunctionComponent< ) .catch(reject); }), - [account, poolId, percentage, props.shares] + [account, pool, poolId, percentage, props.shares] ); const { showModalBase, accountActionButton } = useConnectWalletModalRedirect( { - disabled: props.shares.toDec().isZero() || isSendingMsg, + disabled: props.shares.toDec().isZero() || isSendingMsg || isLoadingPool, onClick: () => { const removeLiquidityResult = removeLiquidity().finally(() => props.onRequestClose() diff --git a/packages/web/modals/superfluid-validator.tsx b/packages/web/modals/superfluid-validator.tsx index 4652aedc64..08024bd95e 100644 --- a/packages/web/modals/superfluid-validator.tsx +++ b/packages/web/modals/superfluid-validator.tsx @@ -5,6 +5,8 @@ import { observer } from "mobx-react-lite"; import { FunctionComponent, useMemo, useState } from "react"; import { SearchBox } from "~/components/input"; +import { Spinner } from "~/components/loaders"; +import { SkeletonLoader } from "~/components/loaders/skeleton-loader"; import { Table } from "~/components/table"; import { ValidatorInfoCell } from "~/components/table/cells/"; import { InfoTooltip } from "~/components/tooltip"; @@ -14,6 +16,7 @@ import { useWindowSize } from "~/hooks"; import { useFilteredData, useSortedData } from "~/hooks/data"; import { ModalBase, ModalBaseProps } from "~/modals/base"; import { useStore } from "~/stores"; +import { api } from "~/utils/trpc"; export const SuperfluidValidatorModal: FunctionComponent< { @@ -32,27 +35,41 @@ export const SuperfluidValidatorModal: FunctionComponent< ctaLabel, } = props; const { t } = useTranslation(); - const { chainStore, queriesStore, accountStore } = useStore(); + const { accountStore } = useStore(); const { isMobile } = useWindowSize(); - const { chainId } = chainStore.osmosis; - const account = accountStore.getWallet(chainId); - const queries = queriesStore.get(chainId); - const queryValidators = queries.cosmos.queryValidators.getQueryStatus( - BondStatus.Bonded - ); + const account = accountStore.getWallet(accountStore.osmosisChainId); + + const { data: validators, isLoading: isLoadingAllValidators } = + api.edge.staking.getValidators.useQuery({ + status: BondStatus.Bonded, + }); + + const { data: userValidatorDelegations, isLoading: isLoadingUserValidators } = + api.edge.staking.getUserDelegations.useQuery( + { + userOsmoAddress: account?.address ?? "", + }, + { + enabled: !!account?.address, + } + ); + + const isLoadingValidators = isLoadingAllValidators || isLoadingUserValidators; - const activeValidators = queryValidators.validators; - const userValidatorDelegations = - queries.cosmos.queryDelegations.getQueryBech32Address( - account?.address ?? "" - ).delegations; - const isSendingMsg = account?.txTypeInProgress !== ""; + const { data: osmoEquivalent, isLoading: isLoadingOsmoEquivalent } = + api.edge.staking.getOsmoEquivalent.useQuery( + availableBondAmount?.toCoin() ?? { denom: "", amount: "" }, + { + enabled: + !!availableBondAmount && availableBondAmount.toDec().isPositive(), + } + ); // vals from 0..<1 used to initially & randomly sort validators in `isDelegated` key const randomSortVals = useMemo( - () => activeValidators.map(() => Math.random()), - [activeValidators] + () => validators?.map(() => Math.random()) ?? [], + [validators] ); // get minimum info for display, mark validators users are delegated to @@ -62,25 +79,33 @@ export const SuperfluidValidatorModal: FunctionComponent< validatorImgSrc?: string; validatorCommission: RatePretty; isDelegated: number; - }[] = activeValidators.map( - ({ operator_address, description, commission }, index) => { - const validatorImg = - queryValidators.getValidatorThumbnail(operator_address); - return { - address: operator_address, - validatorName: description.moniker, - validatorImgSrc: validatorImg === "" ? undefined : validatorImg, - validatorCommission: new RatePretty(commission.commission_rates.rate), - isDelegated: !showDelegated - ? 1 - : userValidatorDelegations.some( - ({ delegation }) => - delegation.validator_address === operator_address - ) - ? 1 // = new Dec(1) - : randomSortVals[index], // = new Dec(0..<1) - }; - } + }[] = useMemo( + () => + validators?.map( + ( + { operator_address, description, commission, validatorImgSrc }, + index + ) => { + return { + address: operator_address, + validatorName: description.moniker, + validatorImgSrc: + validatorImgSrc === "" ? undefined : validatorImgSrc, + validatorCommission: new RatePretty( + commission.commission_rates.rate + ), + isDelegated: !showDelegated + ? 1 + : userValidatorDelegations?.some( + ({ delegation }) => + delegation.validator_address === operator_address + ) + ? 1 // = new Dec(1) + : randomSortVals[index], // = new Dec(0..<1) + }; + } + ) ?? [], + [validators, userValidatorDelegations, showDelegated, randomSortVals] ); const [ @@ -118,61 +143,68 @@ export const SuperfluidValidatorModal: FunctionComponent< />
- setSortKey("validatorName") }, - displayCell: ValidatorInfoCell, - }, - { - display: t("superfluidValidator.columns.commission"), - className: classNames( - "text-right !pr-3", - isMobile ? "caption" : undefined - ), - sort: - sortKey === "validatorCommission" - ? { - onClickHeader: toggleSortDirection, - currentDirection: sortDirection, - } - : { - onClickHeader: () => setSortKey("validatorCommission"), - }, - }, - ]} - rowDefs={searchedValidators.map(({ address, isDelegated }) => ({ - makeClass: () => - `!h-fit ${ - address === selectedValidatorAddress - ? "bg-osmoverse-800 border border-osmoverse-500" - : isDelegated === 1 - ? "bg-osmoverse-800" - : "bg-osmoverse-900" - }`, - makeHoverClass: () => "bg-osmoverse-900", - onClick: () => setSelectedValidatorAddress(address), - }))} - data={searchedValidators.map( - ({ validatorName, validatorImgSrc, validatorCommission }) => [ - { value: validatorName, imgSrc: validatorImgSrc }, - { value: validatorCommission.toString() }, - ] - )} - /> + {isLoadingValidators ? ( +
+ +
+ ) : ( +
setSortKey("validatorName") }, + displayCell: ValidatorInfoCell, + }, + { + display: t("superfluidValidator.columns.commission"), + className: classNames( + "text-right !pr-3", + isMobile ? "caption" : undefined + ), + sort: + sortKey === "validatorCommission" + ? { + onClickHeader: toggleSortDirection, + currentDirection: sortDirection, + } + : { + onClickHeader: () => + setSortKey("validatorCommission"), + }, + }, + ]} + rowDefs={searchedValidators.map(({ address, isDelegated }) => ({ + makeClass: () => + `!h-fit ${ + address === selectedValidatorAddress + ? "border border-osmoverse-500" + : isDelegated === 1 + ? "bg-osmoverse-800" + : "bg-osmoverse-900" + }`, + makeHoverClass: () => "bg-osmoverse-900", + onClick: () => setSelectedValidatorAddress(address), + }))} + data={searchedValidators.map( + ({ validatorName, validatorImgSrc, validatorCommission }) => [ + { value: validatorName, imgSrc: validatorImgSrc }, + { value: validatorCommission.toString() }, + ] + )} + /> + )} {availableBondAmount && (
@@ -187,12 +219,13 @@ export const SuperfluidValidatorModal: FunctionComponent< : t("superfluidValidator.estimation")} - ~ - {queries.osmosis?.querySuperfluidOsmoEquivalent - .calculateOsmoEquivalent(availableBondAmount) - .maxDecimals(3) - .trim(true) - .toString() ?? "0"} + + ~ + {osmoEquivalent?.maxDecimals(3).trim(true).toString() ?? null} + )}