diff --git a/.github/workflows/jest-tests.yml b/.github/workflows/jest-tests.yml index 721d3bf816..9bfb061357 100644 --- a/.github/workflows/jest-tests.yml +++ b/.github/workflows/jest-tests.yml @@ -33,4 +33,4 @@ jobs: run: yarn install --frozen-lockfile - name: Run ${{ matrix.package }} tests - run: yarn test --filter=${{ matrix.package }} + run: yarn test --filter=@osmosis-labs/${{ matrix.package }} diff --git a/.github/workflows/lint-and-check-format.yml b/.github/workflows/lint-and-check-format.yml index b3eb58501e..de5df0bc2a 100644 --- a/.github/workflows/lint-and-check-format.yml +++ b/.github/workflows/lint-and-check-format.yml @@ -31,4 +31,4 @@ jobs: run: yarn install --frozen-lockfile - name: Lint ${{ matrix.package }} package - run: yarn lint --filter=${{ matrix.package }} + run: yarn lint --filter=@osmosis-labs/${{ matrix.package }} diff --git a/.github/workflows/monitoring-e2e-tests.yml b/.github/workflows/monitoring-e2e-tests.yml index b3ecfe138b..e78431e575 100644 --- a/.github/workflows/monitoring-e2e-tests.yml +++ b/.github/workflows/monitoring-e2e-tests.yml @@ -16,7 +16,7 @@ jobs: echo "matrix={\"include\":[{ \"base-url\":\"https://app.osmosis.zone\", \"server-url\":\"https://sqs.osmosis.zone\", \"env\": \"production\", \"timeseries-url\":\"https://stage-proxy-data-api.osmosis-labs.workers.dev\"}, { \"base-url\":\"https://stage.osmosis.zone\", \"server-url\":\"https://sqs.stage.osmosis.zone\", \"env\": \"staging\", \"timeseries-url\":\"https://stage-proxy-data-api.osmosis-labs.workers.dev\"}]}" >> "$GITHUB_OUTPUT" server-e2e-tests: - name: ${{ matrix.env }} + name: ${{ matrix.env }}-server-tests needs: setup-matrix runs-on: ubuntu-latest strategy: @@ -47,7 +47,7 @@ jobs: - name: Run Tests id: tests - run: yarn test:e2e --filter=server + run: yarn test:e2e --filter=@osmosis-labs/server env: NEXT_PUBLIC_SIDECAR_BASE_URL: ${{ matrix.server-url }} NEXT_PUBLIC_TIMESERIES_DATA_URL: ${{ matrix.timeseries-url }} @@ -90,7 +90,7 @@ jobs: SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK fe-quote-tests: - name: ${{ matrix.env }} + name: ${{ matrix.env }}-quote-tests needs: setup-matrix runs-on: ubuntu-latest strategy: diff --git a/.github/workflows/monitoring-geo-e2e-tests.yml b/.github/workflows/monitoring-geo-e2e-tests.yml index bd3c6ddb34..bbbd8c96ae 100644 --- a/.github/workflows/monitoring-geo-e2e-tests.yml +++ b/.github/workflows/monitoring-geo-e2e-tests.yml @@ -13,13 +13,13 @@ jobs: steps: - id: set-matrix run: | - echo "matrix={\"include\":[{\"test-proxy\":\"use\", \"server-url\":\"http://138.68.112.16:8888\", \"env\": \"production-fra1\"}, {\"test-proxy\":\"use\", \"server-url\":\"http://139.59.218.19:8888\", \"env\": \"production-sgp1\"}, {\"test-proxy\":\"no\", \"server-url\":\"http://139.59.218.19:8888\", \"env\": \"production-usa1\"}]}" >> "$GITHUB_OUTPUT" + echo "matrix={\"include\":[{\"test-proxy\":\"use\", \"server-url\":\"http://138.68.112.16:8888\", \"env\": \"prod-fra1\"}, {\"test-proxy\":\"use\", \"server-url\":\"http://139.59.218.19:8888\", \"env\": \"prod-sgp1\"}, {\"test-proxy\":\"no\", \"server-url\":\"http://139.59.218.19:8888\", \"env\": \"prod-usa1\"}]}" >> "$GITHUB_OUTPUT" fe-quote-tests: - timeout-minutes: 10 + timeout-minutes: 15 name: ${{ matrix.env }}-fe-quote-tests needs: setup-matrix - runs-on: buildjet-4vcpu-ubuntu-2204 + runs-on: ubuntu-latest strategy: fail-fast: false matrix: ${{fromJson(needs.setup-matrix.outputs.matrix)}} @@ -49,6 +49,7 @@ jobs: TEST_PROXY_USERNAME: ${{secrets.TEST_PROXY_USERNAME}} TEST_PROXY_PASSWORD: ${{secrets.TEST_PROXY_PASSWORD}} USE_TEST_PROXY: ${{ matrix.test-proxy }} + USE_TRADE: ${{ matrix.test-proxy }} run: | cd packages/web npx playwright test select pools @@ -62,7 +63,7 @@ jobs: fe-swap-us-tests: timeout-minutes: 10 - name: fe-swap-us-tests + name: prod-fe-swap-us-tests runs-on: macos-latest steps: - name: Echo IP @@ -87,6 +88,7 @@ jobs: env: BASE_URL: "https://app.osmosis.zone" PRIVATE_KEY_S: ${{ secrets.TEST_PRIVATE_KEY_1 }} + USE_TRADE: "no" run: | cd packages/web npx playwright test -g "Test Swap Stables feature" @@ -104,20 +106,19 @@ jobs: with: payload: | { - "text": "🚨 Synthetic Geo Monitoring Tests Failure Alert 🚨", "blocks": [ { "type": "header", "text": { "type": "plain_text", - "text": "Synthetic Geo Monitoring Tests Failure" + "text": "🚨 Synthetic Geo Swap Monitoring Tests Failure Alert 🚨" } }, { "type": "section", "text": { "type": "mrkdwn", - "text": "*Environment:* production\n*App URL:* https://app.osmosis.zone" + "text": "*Production App URL:* https://app.osmosis.zone" } }, { @@ -134,8 +135,8 @@ jobs: SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK fe-swap-eu-tests: - timeout-minutes: 10 - name: fe-swap-eu-tests + timeout-minutes: 15 + name: prod-fe-swap-eu-tests needs: fe-swap-us-tests runs-on: macos-latest steps: @@ -157,14 +158,15 @@ jobs: - name: Install Playwright run: | yarn --cwd packages/web install --frozen-lockfile && npx playwright install --with-deps chromium - - name: Run Swap tests in US + - name: Run Swap tests in EU env: BASE_URL: "https://app.osmosis.zone" TEST_PROXY: "http://138.68.112.16:8888" TEST_PROXY_USERNAME: ${{ secrets.TEST_PROXY_USERNAME }} TEST_PROXY_PASSWORD: ${{ secrets.TEST_PROXY_PASSWORD }} PRIVATE_KEY_S: ${{ secrets.TEST_PRIVATE_KEY_1 }} - USE_TEST_PROXY: "true" + USE_TEST_PROXY: "use" + USE_TRADE: "use" run: | cd packages/web npx playwright test -g "Test Swap Stables feature" @@ -177,8 +179,8 @@ jobs: path: packages/web/playwright-report fe-swap-sg-tests: - timeout-minutes: 10 - name: fe-swap-sg-tests + timeout-minutes: 15 + name: prod-fe-swap-sg-tests needs: fe-swap-us-tests runs-on: macos-latest steps: @@ -200,14 +202,15 @@ jobs: - name: Install Playwright run: | yarn --cwd packages/web install --frozen-lockfile && npx playwright install --with-deps chromium - - name: Run Swap tests in US + - name: Run Swap tests in SG env: BASE_URL: "https://app.osmosis.zone" TEST_PROXY: "http://139.59.218.19:8888" TEST_PROXY_USERNAME: ${{ secrets.TEST_PROXY_USERNAME }} TEST_PROXY_PASSWORD: ${{ secrets.TEST_PROXY_PASSWORD }} PRIVATE_KEY_S: ${{ secrets.TEST_PRIVATE_KEY_2 }} - USE_TEST_PROXY: "true" + USE_TEST_PROXY: "use" + USE_TRADE: "use" run: | cd packages/web npx playwright test -g "Test Swap Stables feature" @@ -219,11 +222,91 @@ jobs: name: sg-swap-test-results path: packages/web/playwright-report + fe-trade-eu-tests: + timeout-minutes: 10 + name: prod-fe-trade-eu-tests + runs-on: macos-latest + steps: + - name: Check out repository + uses: actions/checkout@v4 + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20.x + - name: Cache dependencies + uses: actions/cache@v4 + with: + path: "**/node_modules" + key: ${{ runner.OS }}-20.x-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.OS }}-20.x- + - name: Install Playwright + run: | + yarn --cwd packages/web install --frozen-lockfile && npx playwright install --with-deps chromium + - name: Run Swap tests in EU + env: + BASE_URL: "https://app.osmosis.zone" + TEST_PROXY: "http://138.68.112.16:8888" + TEST_PROXY_USERNAME: ${{ secrets.TEST_PROXY_USERNAME }} + TEST_PROXY_PASSWORD: ${{ secrets.TEST_PROXY_PASSWORD }} + PRIVATE_KEY: ${{ secrets.TEST_PRIVATE_KEY_2 }} + USE_TEST_PROXY: "use" + run: | + cd packages/web + npx playwright test monitoring --timeout 180000 + - name: upload monitoring test results + if: failure() + id: monitoring-test-results + uses: actions/upload-artifact@v4 + with: + name: eu-trade-test-results + path: packages/web/playwright-report + - name: Send Slack alert if test fails + id: slack + if: failure() + uses: slackapi/slack-github-action@v1.26.0 + with: + payload: | + { + "blocks": [ + { + "type": "header", + "text": { + "type": "plain_text", + "text": "🚨 Synthetic EU Trade Monitoring Tests Failure Alert 🚨" + } + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "*Production App URL:* https://app.osmosis.zone" + } + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "Click here to view the run: <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|GitHub Actions Run>" + } + } + ] + } + env: + SLACK_WEBHOOK_URL: ${{ secrets.SERVER_E2E_TESTS_SLACK_WEBHOOK_URL }} + SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK + delete-deployments: runs-on: ubuntu-latest if: always() needs: - [fe-quote-tests, fe-swap-us-tests, fe-swap-sg-tests, fe-swap-eu-tests] + [ + fe-quote-tests, + fe-swap-us-tests, + fe-swap-sg-tests, + fe-swap-eu-tests, + fe-trade-eu-tests, + ] steps: - name: Delete Previous deployments uses: actions/github-script@v7 diff --git a/.github/workflows/server-e2e-tests.yml b/.github/workflows/server-e2e-tests.yml index 2a0f1ff7a6..ea118d0f2e 100644 --- a/.github/workflows/server-e2e-tests.yml +++ b/.github/workflows/server-e2e-tests.yml @@ -52,7 +52,7 @@ jobs: - name: Run Tests id: tests - run: yarn test:e2e --filter=server + run: yarn test:e2e --filter=@osmosis-labs/server env: NEXT_PUBLIC_SIDECAR_BASE_URL: ${{ matrix.server-url }} NEXT_PUBLIC_TIMESERIES_DATA_URL: ${{ matrix.timeseries-url }} diff --git a/.gitignore b/.gitignore index 2ea73ecdb1..60d27eeb36 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,11 @@ coverage tsconfig.tsbuildinfo packages/web/e2e/keplr-extension-manifest + +# Vim backup, undo files +*.orig +*~ + +# Vim swap files +.*.swp +.*.swo diff --git a/.vscode/settings.json b/.vscode/settings.json index 7ebd218ec8..12fb8f404f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -14,5 +14,6 @@ "typescript.tsdk": "node_modules/typescript/lib", "search.exclude": { "build/": true - } + }, + "cSpell.words": ["opentelemetry"] } diff --git a/package.json b/package.json index 28b431a4dc..5fc64b6e8b 100644 --- a/package.json +++ b/package.json @@ -69,8 +69,8 @@ "pre-commit": "^1.2.2", "prettier": "^2.8.8", "ts-jest": "^29.1.2", - "turbo": "^1.13.3", - "typescript": "^5.4.5" + "turbo": "^2.0.14", + "typescript": "5.4.5" }, "packageManager": "yarn@1.22.22", "resolutions": { @@ -82,4 +82,4 @@ "@keplr-wallet/types": "0.10.24-ibc.go.v7.hot.fix", "@keplr-wallet/unit": "0.10.24-ibc.go.v7.hot.fix" } -} \ No newline at end of file +} diff --git a/packages/bridge/package.json b/packages/bridge/package.json index d559347db6..bca3545f7c 100644 --- a/packages/bridge/package.json +++ b/packages/bridge/package.json @@ -46,7 +46,7 @@ "@types/jest-in-case": "^1.0.6", "jest-in-case": "^1.0.2", "ts-jest": "^29.1.2", - "typescript": "^5.4.5" + "typescript": "5.4.5" }, "lint-staged": { "*": [ diff --git a/packages/bridge/src/axelar/index.ts b/packages/bridge/src/axelar/index.ts index 59c77e4bf1..d0f720a061 100644 --- a/packages/bridge/src/axelar/index.ts +++ b/packages/bridge/src/axelar/index.ts @@ -2,10 +2,9 @@ import type { AxelarAssetTransfer, AxelarQueryAPI, } from "@axelar-network/axelarjs-sdk"; -import { Registry } from "@cosmjs/proto-signing"; +import type { Registry } from "@cosmjs/proto-signing"; import { CoinPretty, Dec, IntPretty } from "@keplr-wallet/unit"; -import { ibcProtoRegistry } from "@osmosis-labs/proto-codecs"; -import { cosmosMsgOpts, estimateGasFee } from "@osmosis-labs/tx"; +import { estimateGasFee, makeIBCTransferMsg } from "@osmosis-labs/tx"; import type { IbcTransferMethod } from "@osmosis-labs/types"; import { EthereumChainInfo, @@ -50,7 +49,7 @@ export class AxelarBridgeProvider implements BridgeProvider { // initialized via dynamic import protected _queryClient: AxelarQueryAPI | null = null; protected _assetTransferClient: AxelarAssetTransfer | null = null; - protected protoRegistry = new Registry(ibcProtoRegistry); + protected protoRegistry: Registry | null = null; protected axelarChainId: string; protected readonly axelarScanBaseUrl: string; @@ -420,14 +419,16 @@ export class AxelarBridgeProvider implements BridgeProvider { chainList: this.ctx.chainList, body: { messages: [ - this.protoRegistry.encodeAsAny({ + ( + await this.getProtoRegistry() + ).encodeAsAny({ typeUrl: transactionData.msgTypeUrl, value: transactionData.msg, }), ], }, bech32Address: params.fromAddress, - fallbackGasLimit: cosmosMsgOpts.ibcTransfer.gas, + fallbackGasLimit: makeIBCTransferMsg.gas, }).catch((e) => { if ( e instanceof Error && @@ -594,21 +595,19 @@ export class AxelarBridgeProvider implements BridgeProvider { ); } - const { typeUrl, value: msg } = cosmosMsgOpts.ibcTransfer.messageComposer( - { - receiver: depositAddress, - sender: fromAddress, - sourceChannel: ibcTransferMethod.chain.channelId, - sourcePort: "transfer", - timeoutTimestamp: "0" as any, - // @ts-ignore - timeoutHeight, - token: { - amount: fromAmount, - denom: fromAsset.address, - }, - } - ); + const { typeUrl, value: msg } = await makeIBCTransferMsg({ + receiver: depositAddress, + sender: fromAddress, + sourceChannel: ibcTransferMethod.chain.channelId, + sourcePort: "transfer", + timeoutTimestamp: "0" as any, + // @ts-ignore + timeoutHeight, + token: { + amount: fromAmount, + denom: fromAsset.address, + }, + }); return { type: "cosmos", @@ -809,6 +808,17 @@ export class AxelarBridgeProvider implements BridgeProvider { return this._assetTransferClient!; } + async getProtoRegistry() { + if (!this.protoRegistry) { + const [{ ibcProtoRegistry }, { Registry }] = await Promise.all([ + import("@osmosis-labs/proto-codecs"), + import("@cosmjs/proto-signing"), + ]); + this.protoRegistry = new Registry(ibcProtoRegistry); + } + return this.protoRegistry; + } + async getExternalUrl({ fromChain, toChain, diff --git a/packages/bridge/src/ibc/index.ts b/packages/bridge/src/ibc/index.ts index aa645bb9fc..130a896b68 100644 --- a/packages/bridge/src/ibc/index.ts +++ b/packages/bridge/src/ibc/index.ts @@ -1,5 +1,4 @@ -import { Registry } from "@cosmjs/proto-signing"; -import { ibcProtoRegistry } from "@osmosis-labs/proto-codecs"; +import type { Registry } from "@cosmjs/proto-signing"; import { Chain, queryGeneratedChains, @@ -7,8 +6,8 @@ import { } from "@osmosis-labs/server"; import { calcAverageBlockTimeMs, - cosmosMsgOpts, estimateGasFee, + makeIBCTransferMsg, } from "@osmosis-labs/tx"; import { IbcTransferMethod } from "@osmosis-labs/types"; import cachified from "cachified"; @@ -31,7 +30,7 @@ export class IbcBridgeProvider implements BridgeProvider { static readonly ID = "IBC"; readonly providerName = IbcBridgeProvider.ID; - protected protoRegistry = new Registry(ibcProtoRegistry); + protected protoRegistry: Registry | null = null; constructor(protected readonly ctx: BridgeProviderContext) {} @@ -149,7 +148,7 @@ export class IbcBridgeProvider implements BridgeProvider { chainId: params.toChain.chainId.toString(), }); - const { typeUrl, value: msg } = cosmosMsgOpts.ibcTransfer.messageComposer({ + const { typeUrl, value: msg } = await makeIBCTransferMsg({ receiver: params.toAddress, sender: params.fromAddress, sourceChannel, @@ -168,14 +167,16 @@ export class IbcBridgeProvider implements BridgeProvider { chainList: this.ctx.chainList, body: { messages: [ - this.protoRegistry.encodeAsAny({ + ( + await this.getProtoRegistry() + ).encodeAsAny({ typeUrl: typeUrl, value: msg, }), ], }, bech32Address: params.fromAddress, - fallbackGasLimit: cosmosMsgOpts.ibcTransfer.gas, + fallbackGasLimit: makeIBCTransferMsg.gas, }).catch((e) => { if ( e instanceof Error && @@ -223,7 +224,7 @@ export class IbcBridgeProvider implements BridgeProvider { // try to get asset list fee asset first, or otherwise the chain fee currency const assetListAsset = this.ctx.assetLists .flatMap(({ assets }) => assets) - .find((asset) => asset.coinMinimalDenom); + .find((asset) => asset.coinMinimalDenom.toLowerCase() === denom); if (assetListAsset) { return { @@ -409,6 +410,17 @@ export class IbcBridgeProvider implements BridgeProvider { queryGeneratedChains({ zoneChainId: this.ctx.chainList[0].chain_id }), }); } + + async getProtoRegistry() { + if (!this.protoRegistry) { + const [{ ibcProtoRegistry }, { Registry }] = await Promise.all([ + import("@osmosis-labs/proto-codecs"), + import("@cosmjs/proto-signing"), + ]); + this.protoRegistry = new Registry(ibcProtoRegistry); + } + return this.protoRegistry; + } } export * from "./transfer-status"; diff --git a/packages/bridge/src/skip/index.ts b/packages/bridge/src/skip/index.ts index a29b81303f..761588e931 100644 --- a/packages/bridge/src/skip/index.ts +++ b/packages/bridge/src/skip/index.ts @@ -1,13 +1,8 @@ -import { fromBech32, toBech32 } from "@cosmjs/encoding"; -import { Registry } from "@cosmjs/proto-signing"; +import type { Registry } from "@cosmjs/proto-signing"; import { - cosmwasmProtoRegistry, - ibcProtoRegistry, -} from "@osmosis-labs/proto-codecs"; -import { - cosmosMsgOpts, - cosmwasmMsgOpts, estimateGasFee, + makeExecuteCosmwasmContractMsg, + makeIBCTransferMsg, } from "@osmosis-labs/tx"; import { CosmosCounterparty, EVMCounterparty } from "@osmosis-labs/types"; import { @@ -55,10 +50,7 @@ export class SkipBridgeProvider implements BridgeProvider { readonly providerName = SkipBridgeProvider.ID; readonly skipClient: SkipApiClient; - protected protoRegistry = new Registry([ - ...ibcProtoRegistry, - ...cosmwasmProtoRegistry, - ]); + protected protoRegistry: Registry | null = null; constructor(protected readonly ctx: BridgeProviderContext) { this.skipClient = new SkipApiClient(ctx.env); @@ -314,47 +306,44 @@ export class SkipBridgeProvider implements BridgeProvider { ? counterparty.address : counterparty.sourceDenom; const skipCounterparty = assets[counterparty.chainId]?.assets.find( - (a) => a.denom.toLowerCase() === address.toLowerCase() + (a) => + counterparty.chainType === "evm" && + address === NativeEVMTokenConstantAddress + ? /** + * Skip labels native tokens as "native" and uses the symbol of the counterparty + */ + a.denom.toLowerCase() === address.toLowerCase() || + (a.denom.includes("native") && + a.symbol?.toLowerCase() === counterparty.symbol.toLowerCase()) + : a.denom.toLowerCase() === address.toLowerCase() ); + if (!skipCounterparty) continue; if (counterparty.chainType === "cosmos") { const c = counterparty as CosmosCounterparty; - // check if supported by skip - if ( - assets[c.chainId].assets.some( - (a) => a.denom.toLowerCase() === address.toLowerCase() - ) - ) { - foundVariants.setAsset(c.chainId, address, { - chainId: c.chainId, - chainType: "cosmos", - address: address, - denom: c.symbol, - decimals: c.decimals, - coinGeckoId: skipCounterparty.coingecko_id, - }); - } + foundVariants.setAsset(c.chainId, address, { + chainId: c.chainId, + chainType: "cosmos", + address: address, + denom: c.symbol, + decimals: c.decimals, + coinGeckoId: skipCounterparty.coingecko_id, + }); } + if (counterparty.chainType === "evm") { const c = counterparty as EVMCounterparty; - // check if supported by skip - if ( - assets[c.chainId].assets.some( - (a) => a.denom.toLowerCase() === address.toLowerCase() - ) - ) { - foundVariants.setAsset(c.chainId.toString(), address, { - chainId: c.chainId, - chainType: "evm", - address: address, - denom: c.symbol, - decimals: c.decimals, - coinGeckoId: skipCounterparty.coingecko_id, - }); - } + foundVariants.setAsset(c.chainId.toString(), address, { + chainId: c.chainId, + chainType: "evm", + address: address, + denom: c.symbol, + decimals: c.decimals, + coinGeckoId: skipCounterparty.coingecko_id, + }); } } @@ -485,19 +474,18 @@ export class SkipBridgeProvider implements BridgeProvider { }[]; }; - const { typeUrl, value: msg } = - cosmwasmMsgOpts.executeWasm.messageComposer({ - sender: cosmwasmData.sender, - contract: cosmwasmData.contract, - msg: Buffer.from(JSON.stringify(cosmwasmData.msg)), - funds: cosmwasmData.funds, - }); + const { typeUrl, value: msg } = await makeExecuteCosmwasmContractMsg({ + sender: cosmwasmData.sender, + contract: cosmwasmData.contract, + msg: cosmwasmData.msg, + funds: cosmwasmData.funds, + }); return { type: "cosmos", msgTypeUrl: typeUrl, msg, - fallbackGasLimit: cosmwasmMsgOpts.executeWasm.gas, + fallbackGasLimit: makeExecuteCosmwasmContractMsg.gas, }; } else { // is an ibc transfer @@ -511,7 +499,7 @@ export class SkipBridgeProvider implements BridgeProvider { : { destinationAddress: messageData.receiver } ); - const { typeUrl, value } = cosmosMsgOpts.ibcTransfer.messageComposer({ + const { typeUrl, value } = await makeIBCTransferMsg({ sourcePort: messageData.source_port, sourceChannel: messageData.source_channel, token: { @@ -530,7 +518,7 @@ export class SkipBridgeProvider implements BridgeProvider { type: "cosmos", msgTypeUrl: typeUrl, msg: value, - fallbackGasLimit: cosmosMsgOpts.ibcTransfer.gas, + fallbackGasLimit: makeIBCTransferMsg.gas, }; } } @@ -673,7 +661,10 @@ export class SkipBridgeProvider implements BridgeProvider { fromChain: BridgeChain, toChain: BridgeChain ) { - const allSkipChains = await this.getChains(); + const [{ fromBech32, toBech32 }, allSkipChains] = await Promise.all([ + import("@cosmjs/encoding"), + this.getChains(), + ]); const sourceChain = allSkipChains.find((c) => c.chain_id === chainIDs[0]); if (!sourceChain) { @@ -802,7 +793,9 @@ export class SkipBridgeProvider implements BridgeProvider { chainList: this.ctx.chainList, body: { messages: [ - this.protoRegistry.encodeAsAny({ + ( + await this.getProtoRegistry() + ).encodeAsAny({ typeUrl: txData.msgTypeUrl, value: txData.msg, }), @@ -906,6 +899,22 @@ export class SkipBridgeProvider implements BridgeProvider { } } + async getProtoRegistry() { + if (!this.protoRegistry) { + const [{ ibcProtoRegistry, cosmwasmProtoRegistry }, { Registry }] = + await Promise.all([ + import("@osmosis-labs/proto-codecs"), + import("@cosmjs/proto-signing"), + ]); + this.protoRegistry = new Registry([ + ...ibcProtoRegistry, + ...cosmwasmProtoRegistry, + ]); + } + + return this.protoRegistry; + } + async getExternalUrl({ fromChain, toChain, diff --git a/packages/bridge/src/squid/index.ts b/packages/bridge/src/squid/index.ts index 3245a3f097..6fac829d07 100644 --- a/packages/bridge/src/squid/index.ts +++ b/packages/bridge/src/squid/index.ts @@ -7,7 +7,10 @@ import { type TransactionRequest, } from "@0xsquid/sdk"; import { Dec } from "@keplr-wallet/unit"; -import { cosmosMsgOpts, cosmwasmMsgOpts } from "@osmosis-labs/tx"; +import { + makeExecuteCosmwasmContractMsg, + makeIBCTransferMsg, +} from "@osmosis-labs/tx"; import { CosmosCounterparty, EVMCounterparty } from "@osmosis-labs/types"; import { apiClient, @@ -542,22 +545,21 @@ export class SquidBridgeProvider implements BridgeProvider { : { destinationAddress: ibcData.msg.receiver } ); - const { typeUrl, value: msg } = - cosmosMsgOpts.ibcTransfer.messageComposer({ - memo: ibcData.msg.memo, - receiver: ibcData.msg.receiver, - sender: ibcData.msg.sender, - sourceChannel: ibcData.msg.sourceChannel, - sourcePort: ibcData.msg.sourcePort, - timeoutTimestamp: new Long( - ibcData.msg.timeoutTimestamp.low, - ibcData.msg.timeoutTimestamp.high, - ibcData.msg.timeoutTimestamp.unsigned - ).toString() as any, - // @ts-ignore - timeoutHeight, - token: ibcData.msg.token, - }); + const { typeUrl, value: msg } = await makeIBCTransferMsg({ + memo: ibcData.msg.memo, + receiver: ibcData.msg.receiver, + sender: ibcData.msg.sender, + sourceChannel: ibcData.msg.sourceChannel, + sourcePort: ibcData.msg.sourcePort, + timeoutTimestamp: new Long( + ibcData.msg.timeoutTimestamp.low, + ibcData.msg.timeoutTimestamp.high, + ibcData.msg.timeoutTimestamp.unsigned + ).toString() as any, + // @ts-ignore + timeoutHeight, + token: ibcData.msg.token, + }); return { type: "cosmos", @@ -576,13 +578,12 @@ export class SquidBridgeProvider implements BridgeProvider { }; }; - const { typeUrl, value: msg } = - cosmwasmMsgOpts.executeWasm.messageComposer({ - sender: fromAddress, - contract: cosmwasmData.msg.wasm.contract, - msg: Buffer.from(JSON.stringify(cosmwasmData.msg.wasm.msg)), - funds: [fromCoin], - }); + const { typeUrl, value: msg } = await makeExecuteCosmwasmContractMsg({ + sender: fromAddress, + contract: cosmwasmData.msg.wasm.contract, + msg: cosmwasmData.msg.wasm.msg, + funds: [fromCoin], + }); return { type: "cosmos", diff --git a/packages/keplr-hooks/src/tx/fee.ts b/packages/keplr-hooks/src/tx/fee.ts index c622dd3541..8880a49956 100644 --- a/packages/keplr-hooks/src/tx/fee.ts +++ b/packages/keplr-hooks/src/tx/fee.ts @@ -15,7 +15,7 @@ import { action, computed, makeObservable, observable } from "mobx"; import { Coin, CoinPretty, Dec, DecUtils, Int } from "@keplr-wallet/unit"; import { Currency } from "@keplr-wallet/types"; import { computedFn } from "mobx-utils"; -import { StdFee } from "@cosmjs/launchpad"; +import type { StdFee } from "@cosmjs/launchpad"; import { useState } from "react"; import { InsufficientFeeError, NotLoadedFeeError } from "./errors"; diff --git a/packages/keplr-hooks/src/tx/gas-simulator.ts b/packages/keplr-hooks/src/tx/gas-simulator.ts index e0aa1d8eb9..f4383d45a7 100644 --- a/packages/keplr-hooks/src/tx/gas-simulator.ts +++ b/packages/keplr-hooks/src/tx/gas-simulator.ts @@ -13,7 +13,7 @@ import { KVStore } from "@keplr-wallet/common"; import { ChainIdHelper } from "@keplr-wallet/cosmos"; import { TxChainSetter } from "./chain"; import { ChainGetter, MakeTxResponse } from "@osmosis-labs/keplr-stores"; -import { Coin, StdFee } from "@cosmjs/launchpad"; +import type { Coin, StdFee } from "@cosmjs/launchpad"; import Axios, { AxiosResponse } from "axios"; type TxSimulate = Pick; diff --git a/packages/keplr-hooks/src/tx/types.ts b/packages/keplr-hooks/src/tx/types.ts index 138e3b629f..e5fc9e6555 100644 --- a/packages/keplr-hooks/src/tx/types.ts +++ b/packages/keplr-hooks/src/tx/types.ts @@ -1,5 +1,5 @@ import { AppCurrency, Currency } from "@keplr-wallet/types"; -import { StdFee } from "@cosmjs/launchpad"; +import type { StdFee } from "@cosmjs/launchpad"; import { CoinPretty } from "@keplr-wallet/unit"; import { CoinPrimitive } from "@osmosis-labs/keplr-stores"; diff --git a/packages/keplr-stores/src/account/base.ts b/packages/keplr-stores/src/account/base.ts index d42d103be9..5c0e30fb0e 100644 --- a/packages/keplr-stores/src/account/base.ts +++ b/packages/keplr-stores/src/account/base.ts @@ -3,7 +3,7 @@ import { AppCurrency, Keplr, KeplrSignOptions } from "@keplr-wallet/types"; import { ChainGetter } from "../common"; import { DenomHelper, toGenerator } from "@keplr-wallet/common"; import { Bech32Address } from "@keplr-wallet/cosmos"; -import { StdFee } from "@cosmjs/launchpad"; +import type { StdFee } from "@cosmjs/launchpad"; import { MakeTxResponse } from "./types"; export enum WalletStatus { diff --git a/packages/keplr-stores/src/account/cosmos.ts b/packages/keplr-stores/src/account/cosmos.ts index 84ac65226b..c6685142ab 100644 --- a/packages/keplr-stores/src/account/cosmos.ts +++ b/packages/keplr-stores/src/account/cosmos.ts @@ -1,12 +1,6 @@ import { AccountSetBaseSuper, MsgOpt, WalletStatus } from "./base"; import { AppCurrency, Keplr, KeplrSignOptions } from "@keplr-wallet/types"; -import { - BroadcastMode, - makeSignDoc, - Msg, - StdFee, - StdSignDoc, -} from "@cosmjs/launchpad"; +import type { BroadcastMode, Msg, StdFee, StdSignDoc } from "@cosmjs/launchpad"; import { DenomHelper, escapeHTML } from "@keplr-wallet/common"; import { Dec, DecUtils, Int } from "@keplr-wallet/unit"; import { Any } from "@keplr-wallet/proto-types/google/protobuf/any"; @@ -535,6 +529,7 @@ export class CosmosAccountImpl { ); } + const { makeSignDoc } = await import("@cosmjs/launchpad"); const signDoc = makeSignDoc( aminoMsgs, fee, diff --git a/packages/keplr-stores/src/account/cosmwasm.ts b/packages/keplr-stores/src/account/cosmwasm.ts index 07af1b4ad6..fa7c5fb6fa 100644 --- a/packages/keplr-stores/src/account/cosmwasm.ts +++ b/packages/keplr-stores/src/account/cosmwasm.ts @@ -1,7 +1,7 @@ import { AccountSetBase, AccountSetBaseSuper, MsgOpt } from "./base"; import { CosmwasmQueries, IQueriesStore, QueriesSetBase } from "../query"; import { ChainGetter, CoinPrimitive } from "../common"; -import { StdFee } from "@cosmjs/launchpad"; +import type { StdFee } from "@cosmjs/launchpad"; import { DenomHelper } from "@keplr-wallet/common"; import { Dec, DecUtils } from "@keplr-wallet/unit"; import { AppCurrency, KeplrSignOptions } from "@keplr-wallet/types"; diff --git a/packages/keplr-stores/src/account/types.ts b/packages/keplr-stores/src/account/types.ts index 3b8da4c3e8..c9ee3eaa04 100644 --- a/packages/keplr-stores/src/account/types.ts +++ b/packages/keplr-stores/src/account/types.ts @@ -1,7 +1,7 @@ -import { Msg, StdFee } from "@cosmjs/launchpad"; -import { Any } from "@keplr-wallet/proto-types/google/protobuf/any"; +import type { Msg, StdFee } from "@cosmjs/launchpad"; +import type { Any } from "@keplr-wallet/proto-types/google/protobuf/any"; import { Dec } from "@keplr-wallet/unit"; -import { KeplrSignOptions } from "@keplr-wallet/types"; +import type { KeplrSignOptions } from "@keplr-wallet/types"; export type ProtoMsgsOrWithAminoMsgs = { aminoMsgs?: Msg[]; diff --git a/packages/pools/src/router/__tests__/pool.ts b/packages/pools/src/router/__tests__/pool.ts index 6b257b8803..6727f38736 100644 --- a/packages/pools/src/router/__tests__/pool.ts +++ b/packages/pools/src/router/__tests__/pool.ts @@ -11,7 +11,6 @@ import { RoutablePool, Route, RouteWithInAmount, - TokenOutGivenInRouter, } from ".."; // Mock RoutablePool for testing purposes @@ -82,10 +81,7 @@ export const makeMockRoutablePool = ( */ // Mock OptimizedRoutes for testing purposes to get access to protected methods -export class TestOptimizedRoutes - extends OptimizedRoutes - implements TokenOutGivenInRouter -{ +export class TestOptimizedRoutes extends OptimizedRoutes { constructor(...args: ConstructorParameters) { super(...args); } @@ -125,10 +121,7 @@ export function makeDefaultTestRouterParams( return new TestOptimizedRoutes(params); } -export class RoutesTestOptimizedRoutes - extends OptimizedRoutes - implements TokenOutGivenInRouter -{ +export class RoutesTestOptimizedRoutes extends OptimizedRoutes { private testRoutes: Route[]; constructor( diff --git a/packages/pools/src/router/routes.ts b/packages/pools/src/router/routes.ts index 5f55a4a875..7af0274f72 100644 --- a/packages/pools/src/router/routes.ts +++ b/packages/pools/src/router/routes.ts @@ -10,14 +10,7 @@ import { RouteWithInAmount, validateRoute, } from "./route"; -import { - Logger, - Quote, - RoutablePool, - SplitTokenInQuote, - Token, - TokenOutGivenInRouter, -} from "./types"; +import { Logger, Quote, RoutablePool, SplitTokenInQuote, Token } from "./types"; import { cacheKeyForTokenOutGivenIn, invertRoute, @@ -60,7 +53,7 @@ export type OptimizedRoutesParams = { * @throws NotEnoughLiquidityError if there is not enough liquidity in a route. * @throws NoRouteError if there is no route between the tokens. */ -export class OptimizedRoutes implements TokenOutGivenInRouter { +export class OptimizedRoutes { protected readonly _sortedPools: RoutablePool[]; protected readonly _preferredPoolIds?: string[]; protected readonly _getPoolTotalValueLocked: (poolId: string) => Dec; diff --git a/packages/pools/src/router/types.ts b/packages/pools/src/router/types.ts index 70f28dfca2..9cb9e608af 100644 --- a/packages/pools/src/router/types.ts +++ b/packages/pools/src/router/types.ts @@ -9,15 +9,6 @@ export type Token = { amount: Int; }; -export interface TokenOutGivenInRouter { - /** Route, with splits, given an in token and out denom. */ - routeByTokenIn( - tokenIn: Token, - tokenOutDenom: string, - forcePoolId?: string - ): Promise; -} - export interface RoutablePool { /** Unique identifier across pools. */ id: string; diff --git a/packages/server/README.md b/packages/server/README.md index 21fd194db0..5eef8308c3 100644 --- a/packages/server/README.md +++ b/packages/server/README.md @@ -33,4 +33,4 @@ Various utils for working queries and tRPC. All utils are compatible with popula - Remote cache client for Vercel KV - Search - Pagination via tRPC & React Queries infinite query (cursor-based) -- Error handling and reporting via Sentry +- Error handling and reporting via Opentelemetry diff --git a/packages/server/package.json b/packages/server/package.json index 3ed3eb9fe5..fee5fc6ec8 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -35,7 +35,6 @@ "@osmosis-labs/pools": "^5.1.0", "@osmosis-labs/types": "^1.0.0", "@osmosis-labs/utils": "^1.0.0", - "@sentry/core": "^7.109.0", "@upstash/redis": "^1.31.5", "axios": "^0.27.2", "cachified": "^3.5.4", @@ -46,13 +45,14 @@ "lru-cache": "^10.0.1", "superjson": "^2.2.1", "zod": "^3.22.4", - "viem": "2.16.4" + "viem": "2.16.4", + "@opentelemetry/api": "^1.9.0" }, "devDependencies": { "@types/jest-in-case": "^1.0.6", "jest-in-case": "^1.0.2", "ts-jest": "^29.1.2", - "typescript": "^5.4.5" + "typescript": "5.4.5" }, "lint-staged": { "*": [ diff --git a/packages/server/src/queries/complex/assets/price/index.ts b/packages/server/src/queries/complex/assets/price/index.ts index ec1abe35f2..d05ea20884 100644 --- a/packages/server/src/queries/complex/assets/price/index.ts +++ b/packages/server/src/queries/complex/assets/price/index.ts @@ -13,8 +13,6 @@ import { getPriceFromSidecar } from "./providers/sidecar"; /** Provides a price (no caching) given a valid asset from asset list and a fiat currency code. * @throws if there's an issue getting the price. */ export type PriceProvider = ( - assetLists: AssetList[], - chainList: Chain[], asset: Asset, currency?: CoingeckoVsCurrencies ) => Promise; @@ -25,7 +23,6 @@ const pricesCache = new LRUCache(DEFAULT_LRU_OPTIONS); * @throws If the asset is not found in the asset list registry or the asset's price info is not found (missing in asset list or can't get price). */ export async function getAssetPrice({ assetLists, - chainList, asset, currency = "usd", priceProvider = getPriceFromSidecar, @@ -34,7 +31,6 @@ export async function getAssetPrice({ assetLists: AssetList[]; asset: { coinDenom?: string } & ( | { coinMinimalDenom: string } - | { sourceDenom: string } | { chainId: number | string; address: string } | { coinGeckoId: string } ); @@ -43,7 +39,6 @@ export async function getAssetPrice({ }): Promise { const coinMinimalDenom = "coinMinimalDenom" in asset ? asset.coinMinimalDenom : undefined; - const sourceDenom = "sourceDenom" in asset ? asset.sourceDenom : undefined; const { chainId, address } = "chainId" in asset && "address" in asset ? asset @@ -56,7 +51,6 @@ export async function getAssetPrice({ .find( (asset) => (coinMinimalDenom && asset.coinMinimalDenom === coinMinimalDenom) || - (sourceDenom && asset.sourceDenom === sourceDenom) || (chainId && address && asset.counterparty.some( @@ -77,7 +71,7 @@ export async function getAssetPrice({ if (!foundAsset) throw new Error( `Asset ${ - asset.coinDenom ?? coinMinimalDenom ?? sourceDenom + asset.coinDenom ?? coinMinimalDenom } not found in asset list registry.` ); @@ -85,8 +79,7 @@ export async function getAssetPrice({ key: `asset-price-${foundAsset.coinMinimalDenom}`, cache: pricesCache, ttl: 1000 * 10, // 10 seconds - getFreshValue: () => - priceProvider(assetLists, chainList, foundAsset, currency), + getFreshValue: () => priceProvider(foundAsset, currency), }); } diff --git a/packages/server/src/queries/complex/assets/price/providers/pools.ts b/packages/server/src/queries/complex/assets/price/providers/pools.ts deleted file mode 100644 index a147492ea3..0000000000 --- a/packages/server/src/queries/complex/assets/price/providers/pools.ts +++ /dev/null @@ -1,157 +0,0 @@ -import { Dec, DecUtils, IntPretty } from "@keplr-wallet/unit"; -import { makeStaticPoolFromRaw, PoolRaw } from "@osmosis-labs/pools"; -import { Asset, AssetList, Chain } from "@osmosis-labs/types"; -import { getAssetFromAssetList, isNil } from "@osmosis-labs/utils"; - -import { CoingeckoVsCurrencies } from "../../../../coingecko"; -import { queryPaginatedPools } from "../../../../complex/pools/providers/indexer"; -import { getCoingeckoPrice, getPriceFromCoinGecko } from "./coingecko"; - -/** Calculates prices by querying pools and finding spot prices through routes in those pools. Falls back to CoinGecko if price not found. - * @throws if there's no price info for that asset, or there's an issue calculating the price. */ -export async function getPriceFromPools( - assetLists: AssetList[], - chainList: Chain[], - asset: Asset, - currency: CoingeckoVsCurrencies = "usd" -): Promise { - if (asset.price) { - return await calculatePriceThroughPools({ - assetLists, - chainList, - asset, - currency, - }); - } - - return await getPriceFromCoinGecko(asset, currency); -} - -/** - * Calculates the price of a pool price info. - * - * To calculate the price it: - * 1. Finds the pool price route - * 2. Gets the spot price of the pool - * 3. Calculated the price of the destination coin recursively (i.e. usd-coin or another pool like uosmo) - * - * Note: For now only "usd" is supported. - * - * @throws If the asset is not found in the asset list registry or the asset's price info is not found. Or if there's an issue getting the price from CoinGecko. - */ -async function calculatePriceThroughPools({ - assetLists, - chainList, - asset, - currency, -}: { - assetLists: AssetList[]; - chainList: Chain[]; - asset: Pick; - currency: "usd"; -}): Promise { - if (!asset.price && !asset.coingeckoId) - throw new Error( - "No price info or coingecko id for " + asset.coinMinimalDenom - ); - - /** - * Fetch directly from coingecko if there's no price info. - */ - if (!asset.price && asset.coingeckoId) { - return await getCoingeckoPrice({ - coinGeckoId: asset.coingeckoId, - currency, - }); - } - - if (!asset.price) - throw new Error("No price info for " + asset.coinMinimalDenom); - - const poolPriceRoute = { - destCoinMinimalDenom: asset.price.denom, - poolId: asset.price.poolId, - sourceCoinMinimalDenom: asset.coinMinimalDenom, - }; - - if (!poolPriceRoute) - throw new Error("No pool price route for " + asset.coinMinimalDenom); - - const tokenInIbc = poolPriceRoute.sourceCoinMinimalDenom; - const tokenOutIbc = poolPriceRoute.destCoinMinimalDenom; - - const tokenInAsset = getAssetFromAssetList({ - coinMinimalDenom: poolPriceRoute.sourceCoinMinimalDenom, - assetLists, - }); - - const tokenOutAsset = getAssetFromAssetList({ - coinMinimalDenom: poolPriceRoute.destCoinMinimalDenom, - assetLists, - }); - - if (!tokenInAsset) - throw new Error( - poolPriceRoute.sourceCoinMinimalDenom + - " price source coin not in asset list." - ); - - if (!tokenOutAsset) - throw new Error( - poolPriceRoute.destCoinMinimalDenom + - " price dest coin not in asset list." - ); - - const rawPool: PoolRaw = ( - await queryPaginatedPools({ - chainList, - poolId: poolPriceRoute.poolId, - }) - )?.pools[0]; - - if (!rawPool) - throw new Error( - "Pool " + poolPriceRoute.poolId + " not found for calculating price." - ); - - const pool = makeStaticPoolFromRaw(rawPool); - - if (isNil(tokenOutAsset.decimals) || isNil(tokenInAsset.decimals)) - throw new Error( - "Invalid decimals in " + - tokenOutAsset.symbol + - " or " + - tokenInAsset.symbol - ); - - const multiplication = DecUtils.getTenExponentN( - tokenOutAsset.decimals - tokenInAsset.decimals - ); - - // TODO: get spot price from token amounts instead - // of instantiating pool models with client - // side simulation logic. - const inSpotPrice = new IntPretty( - pool - .getSpotPriceInOverOutWithoutSwapFee(tokenInIbc, tokenOutIbc) - .mulTruncate(multiplication) - ); - - const spotPriceDec = inSpotPrice.toDec().equals(new Dec(0)) - ? new Dec(0) - : new Dec(1).quo(inSpotPrice.toDec()); - - const destCoinPrice = await calculatePriceThroughPools({ - assetLists, - chainList, - asset: tokenOutAsset.rawAsset, - currency, - }); - - if (!destCoinPrice) - throw new Error( - "No destination coin price found for " + tokenOutAsset.symbol - ); - - return spotPriceDec.mul(destCoinPrice); -} diff --git a/packages/server/src/queries/complex/assets/price/providers/sidecar.ts b/packages/server/src/queries/complex/assets/price/providers/sidecar.ts index 50bf5e1b9c..90c72c30b8 100644 --- a/packages/server/src/queries/complex/assets/price/providers/sidecar.ts +++ b/packages/server/src/queries/complex/assets/price/providers/sidecar.ts @@ -1,5 +1,5 @@ import { Dec } from "@keplr-wallet/unit"; -import { Asset, AssetList, Chain } from "@osmosis-labs/types"; +import { Asset } from "@osmosis-labs/types"; import cachified, { CacheEntry } from "cachified"; import { LRUCache } from "lru-cache"; @@ -9,26 +9,14 @@ import { } from "../../../../../queries/sidecar/prices"; import { EdgeDataLoader } from "../../../../../utils/batching"; import { LARGE_LRU_OPTIONS } from "../../../../../utils/cache"; -import { captureError } from "../../../../../utils/error"; -import { getPriceFromPools } from "./pools"; const sidecarCache = new LRUCache(LARGE_LRU_OPTIONS); /** Gets price from SQS query server. Currently only supports prices in USDC with decimals. Falls back to pools then querying CoinGecko if not available. * @throws if there's an issue getting the price. */ -export function getPriceFromSidecar( - assetLists: AssetList[], - chainList: Chain[], - asset: Asset -) { +export function getPriceFromSidecar(asset: Asset) { return getBatchLoader().then((loader) => - loader - .load(asset.coinMinimalDenom) - .then((price) => new Dec(price)) - .catch((e) => { - captureError(e); - return getPriceFromPools(assetLists, chainList, asset); - }) + loader.load(asset.coinMinimalDenom).then((price) => new Dec(price)) ); } diff --git a/packages/server/src/queries/complex/index.ts b/packages/server/src/queries/complex/index.ts index 426bd6d761..32a99fa012 100644 --- a/packages/server/src/queries/complex/index.ts +++ b/packages/server/src/queries/complex/index.ts @@ -9,5 +9,4 @@ export * from "./osmosis"; export * from "./pools"; export * from "./portfolio"; export * from "./staking"; -export * from "./swap-routers"; export * from "./transactions"; diff --git a/packages/server/src/queries/complex/osmosis/epochs.ts b/packages/server/src/queries/complex/osmosis/epochs.ts index efb6b96fb7..0bcc64c7a1 100644 --- a/packages/server/src/queries/complex/osmosis/epochs.ts +++ b/packages/server/src/queries/complex/osmosis/epochs.ts @@ -23,7 +23,8 @@ export function getEpochs({ return cachified({ cache: epochsCache, key: "epochs", - ttl: 1000 * 60, // 60 seconds + ttl: 1000 * 60 * 3, // 3 minutes + staleWhileRevalidate: 500, // Return stale data for 500ms while revalidating getFreshValue: () => queryEpochs({ chainList }).then(({ epochs }) => epochs.map((e) => { diff --git a/packages/server/src/queries/complex/pools/incentives.ts b/packages/server/src/queries/complex/pools/incentives.ts index d34651a861..69b0e83bf3 100644 --- a/packages/server/src/queries/complex/pools/incentives.ts +++ b/packages/server/src/queries/complex/pools/incentives.ts @@ -48,16 +48,15 @@ export type PoolIncentives = Partial<{ }>; export const IncentivePoolFilterSchema = z.object({ - /** Only include pools of given incentive types.s */ + /** Only include pools of given incentive types. */ incentiveTypes: z.array(z.enum(allPoolIncentiveTypes)).optional(), }); /** Params for filtering pools. */ export type IncentivePoolFilter = z.infer; -export async function getPoolIncentives(poolId: string) { - const map = await getCachedPoolIncentivesMap(); - return map.get(poolId); +export function getPoolIncentives(poolId: string) { + return getCachedPoolIncentivesMap().then((map) => map.get(poolId)); } /** Checks a pool's incentive data againt a given filter to determine if it's filtered out. */ diff --git a/packages/server/src/queries/complex/pools/index.ts b/packages/server/src/queries/complex/pools/index.ts index 2d0f3fd9a2..4af5eb4922 100644 --- a/packages/server/src/queries/complex/pools/index.ts +++ b/packages/server/src/queries/complex/pools/index.ts @@ -5,6 +5,7 @@ import { z } from "zod"; import { IS_TESTNET } from "../../../env"; import { search, SearchSchema } from "../../../utils/search"; import { PoolRawResponse } from "../../osmosis"; +import { PoolIncentives } from "./incentives"; import { getPoolsFromSidecar } from "./providers"; const allPooltypes = [ @@ -18,6 +19,15 @@ const allPooltypes = [ ] as const; export type PoolType = (typeof allPooltypes)[number]; +// PoolMarketMetrics is a partial type that contains the market metrics of a pool. +type PoolMarketMetrics = Partial<{ + volume7dUsd: PricePretty; + volume24hUsd: PricePretty; + volume24hChange: RatePretty; + feesSpent24hUsd: PricePretty; + feesSpent7dUsd: PricePretty; +}>; + export type Pool = { id: string; type: PoolType; @@ -25,6 +35,8 @@ export type Pool = { spreadFactor: RatePretty; reserveCoins: CoinPretty[]; totalFiatValueLocked: PricePretty; + incentives?: PoolIncentives; + market?: PoolMarketMetrics; }; /** Async function that provides simplified pools from any data source. @@ -78,11 +90,7 @@ export async function getPools( params: Partial & { assetLists: AssetList[]; chainList: Chain[] }, poolProvider: PoolProvider = getPoolsFromSidecar ): Promise { - let pools = await poolProvider({ - ...params, - poolIds: params?.poolIds, - minLiquidityUsd: params?.minLiquidityUsd, - }); + let pools = await poolProvider(params); if (params?.types) { pools = pools.filter(({ type }) => @@ -114,9 +122,8 @@ export async function getPools( ]), })); - if (params.denoms) { - const denoms = params.denoms; - + const denoms = params.denoms; + if (denoms) { denomPools = denomPools.filter((denomPool) => denomPool.coinDenoms.some((denom) => denoms.includes(denom)) ); @@ -151,9 +158,7 @@ export async function getPools( export * from "./bonding"; export * from "./env"; export * from "./incentives"; -export * from "./market"; export * from "./providers"; -export * from "./route-token-out-given-in"; export * from "./share"; export * from "./superfluid"; export * from "./transmuter"; diff --git a/packages/server/src/queries/complex/pools/market.ts b/packages/server/src/queries/complex/pools/market.ts deleted file mode 100644 index 9f1d3e9567..0000000000 --- a/packages/server/src/queries/complex/pools/market.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { PricePretty, RatePretty } from "@keplr-wallet/unit"; -import cachified, { CacheEntry } from "cachified"; -import { LRUCache } from "lru-cache"; - -import { DEFAULT_LRU_OPTIONS } from "../../../utils/cache"; -import { queryPoolsFees } from "../../data-services"; -import { DEFAULT_VS_CURRENCY } from "../assets/config"; - -export type PoolMarketMetrics = Partial<{ - volume7dUsd: PricePretty; - volume24hUsd: PricePretty; - volume24hChange: RatePretty; - feesSpent24hUsd: PricePretty; - feesSpent7dUsd: PricePretty; -}>; - -const metricPoolsCache = new LRUCache(DEFAULT_LRU_OPTIONS); -/** Get a cached Map with pool IDs mapped to market metrics for that pool. */ -export function getCachedPoolMarketMetricsMap(): Promise< - Map -> { - return cachified({ - cache: metricPoolsCache, - key: "pools-metrics-map", - ttl: 1000 * 60 * 5, // 5 mins - getFreshValue: async () => { - const map = new Map(); - - // append fee revenue data to volume data - const poolsFees = await queryPoolsFees(); - poolsFees.data.forEach( - ({ pool_id, volume_24h, volume_7d, fees_spent_24h, fees_spent_7d }) => { - map.set(pool_id, { - volume24hUsd: new PricePretty(DEFAULT_VS_CURRENCY, volume_24h), - volume7dUsd: new PricePretty(DEFAULT_VS_CURRENCY, volume_7d), - feesSpent24hUsd: new PricePretty( - DEFAULT_VS_CURRENCY, - fees_spent_24h - ), - feesSpent7dUsd: new PricePretty(DEFAULT_VS_CURRENCY, fees_spent_7d), - }); - } - ); - - return map; - }, - }); -} diff --git a/packages/server/src/queries/complex/pools/providers/__tests__/indexer.spec.ts b/packages/server/src/queries/complex/pools/providers/__tests__/indexer.spec.ts deleted file mode 100644 index 3f2709ecc2..0000000000 --- a/packages/server/src/queries/complex/pools/providers/__tests__/indexer.spec.ts +++ /dev/null @@ -1,335 +0,0 @@ -import { AssetLists as MockAssetLists } from "../../../../__tests__/mock-asset-lists"; -import { getAsset } from "../../../assets"; -import { makePoolFromIndexerPool } from "../indexer"; - -export const mockAsset = { - coinDenom: "mockCoinDenom", - coinName: "mockCoinName", - coinMinimalDenom: "mockCoinMinimalDenom", - coinDecimals: 0, - coinGeckoId: "mockCoinGeckoId", - coinImageUrl: "mockCoinImageUrl", - isVerified: true, -}; - -jest.mock("../../../assets", () => ({ - getAsset: jest.fn(), -})); - -describe("makePoolFromIndexerPool", () => { - beforeEach(() => { - // Mock the getAsset function before calling getPoolsFromSidecar - (getAsset as jest.Mock).mockImplementation(() => { - return mockAsset; - }); - }); - - it("should return a valid pool object for a weighted pool", () => { - const result = makePoolFromIndexerPool(MockAssetLists, weightedPool as any); - - if (!result) throw new Error("result is undefined"); - - expect(result.id).toBe(weightedPool.pool_id.toString()); - expect(result.type).toBe("weighted"); - expect((result.raw as any).pool_assets).toBeDefined(); - expect(result.spreadFactor.toDec().toString()).toBe("0.002000000000000000"); - }); - - it("should return a valid pool object for a stable pool", () => { - const result = makePoolFromIndexerPool(MockAssetLists, stablePool as any); - - if (!result) throw new Error("result is undefined"); - - expect(result.id).toBe(stablePool.pool_id.toString()); - expect(result.type).toBe("stable"); - expect((result.raw as any).pool_liquidity).toBeDefined(); - expect(result.reserveCoins.length).toBe(2); - expect(result.spreadFactor.toDec().toString()).toBe("0.003000000000000000"); - }); - - it("should return a valid pool object for a concentrated liquidity pool", () => { - const result = makePoolFromIndexerPool( - MockAssetLists, - concentratedPool as any - ); - - if (!result) throw new Error("result is undefined"); - - expect(result.id).toBe(concentratedPool.pool_id.toString()); - expect(result.type).toBe("concentrated"); - expect((result.raw as any).address).toBe(concentratedPool.address); - expect(result.spreadFactor.toDec().toString()).toBe("0.002000000000000000"); - }); - - it("should return a valid pool object for a cosmwasm transmuter pool", () => { - const result = makePoolFromIndexerPool( - MockAssetLists, - cosmwasmTransmuterPool as any - ); - - if (!result) throw new Error("result is undefined"); - - expect(result.id).toBe(cosmwasmTransmuterPool.pool_id.toString()); - expect(result.type).toBe("cosmwasm-transmuter"); - expect((result.raw as any).code_id).toBe("148"); - }); - - it("should return a valid pool object for a cosmwasm pool", () => { - const result = makePoolFromIndexerPool(MockAssetLists, cosmwasmPool as any); - - if (!result) throw new Error("result is undefined"); - - expect(result.id).toBe(cosmwasmPool.pool_id.toString()); - expect(result.type).toBe("cosmwasm"); - expect((result.raw as any).code_id).toBe("1438"); - }); -}); - -// Mock response objects copied from response - -const weightedPool = { - pool_id: 1, - type: "osmosis.gamm.v1beta1.Pool", - address: "osmo1mw0ac6rwlp5r8wapwk3zs6g29h8fcscxqakdzw9emkne6c8wjp9q0t3v8t", - liquidity: 18957010.255080372, - liquidity_24h_change: -2.725693822474902, - volume_24h: 0, - volume_24h_change: 0, - volume_7d: 0, - swap_fees: 0.2, - exit_fees: 0, - weight_or_scaling: "1073741824000000", - future_pool_governor: "24h", - total_shares: { denom: "gamm/pool/1", amount: "67826531094152564176736079" }, - total_weight_or_scaling: 1073741824000000, - pool_tokens: [ - { - symbol: "ATOM", - amount: 1045161.532924, - denom: - "ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2", - coingecko_id: "cosmos", - price: 9.068937986347601, - price_24h_change: -1.951955737154605, - exponent: 6, - display: "atom", - name: "Cosmos", - percent: 50, - weight_or_scaling: 536870912000000, - }, - { - symbol: "OSMO", - amount: 6726846.469052, - denom: "uosmo", - coingecko_id: "osmosis", - price: 1.40905626, - price_24h_change: -3.388661993867734, - exponent: 6, - display: "osmo", - name: "Osmosis", - percent: 50, - weight_or_scaling: 536870912000000, - }, - ], -}; - -const stablePool = { - pool_id: 833, - type: "osmosis.gamm.poolmodels.stableswap.v1beta1.Pool", - address: "osmo15v4mn84s9flhzpstkf9ql2mu0rnxh42pm8zhq47kh2fzs5zlwjsqaterkr", - liquidity: 34405851.11347548, - liquidity_24h_change: -3.4392819372126704, - volume_24h: 0, - volume_24h_change: 0, - volume_7d: 0, - swap_fees: 0.3, - exit_fees: 0, - weight_or_scaling: 1, - future_pool_governor: "", - total_shares: { denom: "gamm/pool/833", amount: "16589246236428673102621" }, - scaling_factors: ["100000", "120066"], - scaling_factor_controller: - "osmo12yvjuy69ynnts95ensss4q6480wkvkpnq2z2ntxmfa2qp860xsmq9mzlpn", - total_weight_or_scaling: 220066, - pool_tokens: [ - { - symbol: "STOSMO", - amount: 12590903.548151, - denom: - "ibc/D176154B0C63D1F9C6DCFB4F70349EBF2E2B5A87A05902F57A6AE92B863E9AEC", - coingecko_id: "stride-staked-osmo", - price: 1.655234170052112, - price_24h_change: -3.458955400176939, - exponent: 6, - display: "stosmo", - name: "Stride Staked osmo", - percent: 50, - weight_or_scaling: 100000, - }, - { - symbol: "OSMO", - amount: 9626980.635071, - denom: "uosmo", - coingecko_id: "osmosis", - price: 1.40905626, - price_24h_change: -3.388661993867734, - exponent: 6, - display: "osmo", - name: "Osmosis", - percent: 50, - weight_or_scaling: 120066, - }, - ], -}; - -const concentratedPool = { - pool_id: 1135, - type: "osmosis.concentratedliquidity.v1beta1.Pool", - address: "osmo1cr0fq7pfhpw08as5dsthzvjnlfdtvsq7kw98lwuqxjzv5jm3j5mqf9k6as", - liquidity: 10147269.146227572, - liquidity_24h_change: -3.00343230474741, - volume_24h: 0, - volume_24h_change: 0, - volume_7d: 0, - swap_fees: 0.2, - exit_fees: 0, - incentives_address: - "osmo195e7qr4l7z9rtnc8cwm9646pk9slu8ve8c0pjmvt6hzqsmy04uzqe530fs", - spread_rewards_address: - "osmo1y53uxff98xn2xftdxk2du85j88r8cxg6hndldl0uxln52ds4ukvs8zt345", - current_tick_liquidity: "30789048742588.342048812040469387", - current_sqrt_price: "0.394208958394857218523464106504557767", - current_tick: "-8445993", - tick_spacing: "100", - exponent_at_price_one: "-6", - spread_factor: "0.002000000000000000", - last_liquidity_update: "2024-01-23T22:10:35.523468841Z", - total_weight_or_scaling: 1, - pool_tokens: { - asset1: { - symbol: "ATOM", - amount: 560541.161363, - denom: - "ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2", - coingecko_id: "cosmos", - price: 9.068937986347601, - price_24h_change: -1.951955737154605, - exponent: 6, - display: "atom", - name: "Cosmos", - percent: 50, - weight_or_scaling: 0.5, - }, - asset0: { - symbol: "OSMO", - amount: 3593721.740416, - denom: "uosmo", - coingecko_id: "osmosis", - price: 1.40905626, - price_24h_change: -3.388661993867734, - exponent: 6, - display: "osmo", - name: "Osmosis", - percent: 50, - weight_or_scaling: 0.5, - }, - }, -}; - -const cosmwasmTransmuterPool = { - pool_id: 1211, - type: "osmosis.cosmwasmpool.v1beta1.CosmWasmPool", - address: "osmo1gyg0pys40ex2f6a4dytd3ewpx2xfrsnt3rdc2t4j3s3jc9qx8kqsny066c", - liquidity: 1994.2497781334503, - liquidity_24h_change: -0.2871113542754226, - volume_24h: 0, - volume_24h_change: 0, - volume_7d: 0, - swap_fees: 0, - exit_fees: 0, - contract_address: - "osmo1gyg0pys40ex2f6a4dytd3ewpx2xfrsnt3rdc2t4j3s3jc9qx8kqsny066c", - code_id: "148", - instantiate_msg: - "eyJwb29sX2Fzc2V0X2Rlbm9tcyI6IFsiaWJjLzgyNDJBRDI0MDA4MDMyRTQ1N0QyRTEyRDQ2NTg4RkQzOUZCNTRGQjI5NjgwQzZDNzY2M0QyOTZCMzgzQzM3QzQiLCJpYmMvNEFCQkVGNEM4OTI2REREQjMyMEFFNTE4OENGRDYzMjY3QUJCQ0VGQzA1ODNFNEFFMDVENkU1QUEyNDAxRERBQiJdLCJhZG1pbiI6ICJvc21vMWQ2aDltbDk5bndwZWR4ZTNnaGpneXFuY3d6ZXl0ZHo1MDdlOWY0c3Nza2VqNnl3OW1tZ3MzOXN0OTgifQ==", - total_weight_or_scaling: 1, - pool_tokens: [ - { - symbol: "USDT.KAVA", - amount: 1956.090752, - denom: - "ibc/4ABBEF4C8926DDDB320AE5188CFD63267ABBCEFC0583E4AE05D6E5AA2401DDAB", - coingecko_id: "", - price: 0.9965591172667299, - price_24h_change: -0.2815344146468132, - exponent: 6, - display: "usdtkava", - name: "Tether USD", - percent: 50, - weight_or_scaling: 0.5, - }, - { - symbol: "AXLUSDT", - amount: 44.919248, - denom: - "ibc/8242AD24008032E457D2E12D46588FD39FB54FB29680C6C7663D296B383C37C4", - coingecko_id: "tether", - price: 0.9993423092638707, - price_24h_change: -0.05811349990110085, - exponent: 6, - display: "axlusdt", - name: "Tether USD", - percent: 50, - weight_or_scaling: 0.5, - }, - ], -}; - -const cosmwasmPool = { - pool_id: 1212, - type: "osmosis.cosmwasmpool.v1beta1.CosmWasmPool", - address: "osmo1gyg0pys40ex2f6a4dytd3ewpx2xfrsnt3rdc2t4j3s3jc9qx8kqsny066c", - liquidity: 1994.2497781334503, - liquidity_24h_change: -0.2871113542754226, - volume_24h: 0, - volume_24h_change: 0, - volume_7d: 0, - swap_fees: 0, - exit_fees: 0, - contract_address: - "osmo1gyg0pys40ex2f6a4dytd3ewpx2xfrsnt3rdc2t4j33s3jc9qx8kqsny066c", - code_id: "1438", - instantiate_msg: - "eyJwb29sX2Fzc2V0X2Rlbm9tcyI6IFsiaWJjLzgyNDJBRDI0MDA4MDMyRTQ1N0QyRTEyRDQ2NTg4RkQzOUZCNTRGQjI5NjgwQzZDNzY2M0QyOTZCMzgzQzM3QzQiLCJpYmMvNEFCQkVGNEM4OTI2REREQjMyMEFFNTE4OENGRDYzMjY3QUJCQ0VGQzA1ODNFNEFFMDVENkU1QUEyNDAxRERBQiJdLCJhZG1pbiI6ICJvc21vMWQ2aDltbDk5bndwZWR4ZTNnaGpneXFuY3d6ZXl0ZHo1MDdlOWY0c3Nza2VqNnl3OW1tZ3MzOXN0OTgifQ==", - total_weight_or_scaling: 1, - pool_tokens: [ - { - symbol: "USDT.KAVA", - amount: 1956.090752, - denom: - "ibc/4ABBEF4C8926DDDB320AE5188CFD63267ABBCEFC0583E4AE05D6E5AA2401DDAB", - coingecko_id: "", - price: 0.9965591172667299, - price_24h_change: -0.2815344146468132, - exponent: 6, - display: "usdtkava", - name: "Tether USD", - percent: 50, - weight_or_scaling: 0.5, - }, - { - symbol: "AXLUSDT", - amount: 44.919248, - denom: - "ibc/8242AD24008032E457D2E12D46588FD39FB54FB29680C6C7663D296B383C37C4", - coingecko_id: "tether", - price: 0.9993423092638707, - price_24h_change: -0.05811349990110085, - exponent: 6, - display: "axlusdt", - name: "Tether USD", - percent: 50, - weight_or_scaling: 0.5, - }, - ], -}; diff --git a/packages/server/src/queries/complex/pools/providers/index.ts b/packages/server/src/queries/complex/pools/providers/index.ts index d2ce2a7646..5c91fc5ce3 100644 --- a/packages/server/src/queries/complex/pools/providers/index.ts +++ b/packages/server/src/queries/complex/pools/providers/index.ts @@ -1,2 +1 @@ -export * from "./indexer"; export * from "./sidecar"; diff --git a/packages/server/src/queries/complex/pools/providers/indexer.ts b/packages/server/src/queries/complex/pools/providers/indexer.ts deleted file mode 100644 index 0c337ab620..0000000000 --- a/packages/server/src/queries/complex/pools/providers/indexer.ts +++ /dev/null @@ -1,565 +0,0 @@ -import { - CoinPretty, - Dec, - DecUtils, - PricePretty, - RatePretty, -} from "@keplr-wallet/unit"; -import { - ConcentratedLiquidityPoolRaw, - CosmwasmPoolRaw, - PoolRaw, - StablePoolRaw, - WeightedPoolRaw, -} from "@osmosis-labs/pools"; -import { AssetList, Chain } from "@osmosis-labs/types"; -import { CacheEntry, cachified } from "cachified"; -import { LRUCache } from "lru-cache"; - -import { DEFAULT_LRU_OPTIONS } from "../../../../utils/cache"; -import { - FilteredPoolsResponse, - PoolToken, - queryFilteredPools, -} from "../../../data-services"; -import { - ConcentratedPoolRawResponse, - queryNumPools, - queryPoolmanagerParams, -} from "../../../osmosis"; -import { getAsset } from "../../assets"; -import { DEFAULT_VS_CURRENCY } from "../../assets/config"; -import { Pool } from ".."; -import { getCosmwasmPoolTypeFromCodeId } from "../env"; - -const poolsCache = new Map(); -const smallQueriesPoolCache = new LRUCache( - DEFAULT_LRU_OPTIONS -); - -function getNumPools(chainList: Chain[]) { - return cachified({ - cache: smallQueriesPoolCache, - key: "num-pools", - ttl: 1000 * 60 * 5, // 5 minutes - getFreshValue: () => queryNumPools({ chainList }), - }); -} -function getPoolmanagerParams(chainList: Chain[]) { - return cachified({ - cache: smallQueriesPoolCache, - key: "pool-manager-params", - ttl: 1000 * 60 * 5, // 5 minutes - getFreshValue: () => queryPoolmanagerParams({ chainList }), - }); -} - -/** Get pools from indexer that are listed in asset list. */ -export async function getPoolsFromIndexer({ - assetLists, - chainList, - poolIds, -}: { - assetLists: AssetList[]; - chainList: Chain[]; - poolIds?: string[]; -}): Promise { - return cachified({ - cache: poolsCache, - key: "indexer-pools", - ttl: 5_000, // 5 seconds - getFreshValue: async () => { - const numPools = await getNumPools(chainList); - const { pools } = await queryFilteredPools( - { - min_liquidity: 0, - order_by: "desc", - order_key: "liquidity", - }, - { offset: 0, limit: Number(numPools.num_pools) } - ); - return pools - .map((pool) => makePoolFromIndexerPool(assetLists, pool)) - .filter((pool): pool is Pool => !!pool); - }, - }).then((pools) => - pools.filter((pool): pool is Pool => - poolIds ? poolIds.includes(pool.id) : true - ) - ); -} - -/** @deprecated Fetches pools from indexer. */ -export async function queryPaginatedPools({ - chainList, - page, - limit, - minimumLiquidity, - poolId: poolIdParam, - poolIds: poolIdsParam, -}: { - chainList: Chain[]; - page?: number; - limit?: number; - minimumLiquidity?: number; - poolId?: string; - poolIds?: string[]; -}): Promise<{ - status: number; - pools: PoolRaw[]; - totalNumberOfPools: string; - pageInfo?: { - hasNextPage: boolean; - }; -}> { - // Fetch the pools data from your database or other source - // This is just a placeholder, replace it with your actual data fetching logic - const { pools: allPools, totalNumberOfPools } = await fetchAndProcessAllPools( - { - chainList, - minimumLiquidity, - } - ); - - // Handle the case where specific pool ID is requested - if (poolIdParam) { - const pool = allPools.find( - (pool) => ("pool_id" in pool ? pool.pool_id : pool.id) === poolIdParam - ); - if (!pool) { - throw new Error("Pool not found: " + poolIdParam); - } - return { status: 200, pools: [pool], totalNumberOfPools }; - } - - // Handle the case where specific pool IDs are requested - if (poolIdsParam) { - const pools = allPools.filter((pool) => - poolIdsParam.includes("pool_id" in pool ? pool.pool_id : pool.id) - ); - return { status: 200, pools, totalNumberOfPools }; - } - - // Pagination - if (page && limit) { - const startIndex = (page - 1) * limit; - - // Slice the data based on the page and limit - const pools = allPools.slice(startIndex, startIndex + limit); - - // Return the paginated data - return { - status: 200, - pools, - totalNumberOfPools, - pageInfo: { - hasNextPage: startIndex + limit < allPools.length, - }, - }; - } - - return { status: 200, pools: allPools, totalNumberOfPools }; -} - -/** Cache on this current edge function instance. */ -const allPoolsLruCache = new LRUCache({ - max: 2, -}); - -async function fetchAndProcessAllPools({ - chainList, - minimumLiquidity = 0, -}: { - chainList: Chain[]; - minimumLiquidity?: number; -}): Promise<{ pools: PoolRaw[]; totalNumberOfPools: string }> { - return cachified({ - key: `all-pools-${minimumLiquidity}`, - cache: allPoolsLruCache, - ttl: 1000 * 5, // 5 seconds - async getFreshValue() { - const poolManagerParamsPromise = getPoolmanagerParams(chainList); - const numPoolsPromise = getNumPools(chainList); - - const [poolManagerParams, numPools] = await Promise.all([ - poolManagerParamsPromise, - numPoolsPromise, - ]); - - // Fetch all pools from imperator, except cosmwasm pools for now - const filteredPoolsResponse = await queryFilteredPools( - { - min_liquidity: minimumLiquidity, - order_by: "desc", - order_key: "liquidity", - }, - { offset: 0, limit: Number(numPools.num_pools) } - ); - const queryPoolRawResults = filteredPoolsResponse.pools.map((pool) => - makePoolRawFromIndexerPool( - pool, - poolManagerParams.params.taker_fee_params.default_taker_fee - ) - ); - - return { - pools: queryPoolRawResults.filter( - ( - poolRaw - ): poolRaw is - | StablePoolRaw - | ConcentratedLiquidityPoolRaw - | WeightedPoolRaw - | CosmwasmPoolRaw => !!poolRaw - ), - totalNumberOfPools: - filteredPoolsResponse.pagination.total_pools.toString(), - }; - }, - }); -} - -/** @deprecated */ -function makePoolRawFromIndexerPool( - filteredPool: FilteredPoolsResponse["pools"][number], - takerFeeRaw: string -): - | StablePoolRaw - | ConcentratedLiquidityPoolRaw - | WeightedPoolRaw - | CosmwasmPoolRaw - | undefined { - // deny pools containing tokens with gamm denoms - if ( - Array.isArray(filteredPool.pool_tokens) && - filteredPool.pool_tokens.some( - (token) => "denom" in token && token.denom.includes("gamm") - ) - ) { - return; - } - - /** Metrics common to all pools. */ - const poolMetrics: { - liquidityUsd: number; - liquidity24hUsdChange: number; - - volume24hUsd: number; - volume24hUsdChange: number; - - volume7dUsd: number; - - taker_fee: string; - } = { - liquidityUsd: filteredPool.liquidity, - liquidity24hUsdChange: filteredPool.liquidity_24h_change, - volume24hUsd: filteredPool.volume_24h, - volume24hUsdChange: filteredPool.volume_24h_change, - volume7dUsd: filteredPool.volume_7d, - taker_fee: takerFeeRaw, - }; - - if ( - filteredPool.type === "osmosis.concentratedliquidity.v1beta1.Pool" && - !Array.isArray(filteredPool.pool_tokens) - ) { - if (!filteredPool.pool_tokens.asset0 || !filteredPool.pool_tokens.asset1) - return; - - const token0 = filteredPool.pool_tokens.asset0.denom; - const token1 = filteredPool.pool_tokens.asset1.denom; - - return { - "@type": `/${filteredPool.type}`, - address: filteredPool.address, - id: filteredPool.pool_id.toString(), - current_tick_liquidity: filteredPool.current_tick_liquidity, - token0, - token0Amount: makeCoinFromToken(filteredPool.pool_tokens.asset0).amount, - token1, - token1Amount: makeCoinFromToken(filteredPool.pool_tokens.asset1).amount, - current_sqrt_price: filteredPool.current_sqrt_price, - current_tick: filteredPool.current_tick, - tick_spacing: filteredPool.tick_spacing, - exponent_at_price_one: filteredPool.exponent_at_price_one, - spread_factor: filteredPool.spread_factor, - ...poolMetrics, - }; - } - - if ( - filteredPool.type === "osmosis.cosmwasmpool.v1beta1.CosmWasmPool" && - Array.isArray(filteredPool.pool_tokens) - ) { - return { - "@type": "/osmosis.cosmwasmpool.v1beta1.CosmWasmPool", - contract_address: filteredPool.contract_address, - pool_id: filteredPool.pool_id.toString(), - code_id: filteredPool.code_id, - instantiate_msg: filteredPool.instantiate_msg, - tokens: filteredPool.pool_tokens.map(makeCoinFromToken), - ...poolMetrics, - }; - } - - const sharePoolBase = { - "@type": `/${filteredPool.type}`, - id: filteredPool.pool_id.toString(), - pool_params: { - exit_fee: new Dec(filteredPool.exit_fees.toString()) - .mul(DecUtils.getTenExponentN(-2)) - .toString(), - swap_fee: new Dec(filteredPool.swap_fees.toString()) - .mul(DecUtils.getTenExponentN(-2)) - .toString(), - smooth_weight_change_params: null, - }, - total_shares: filteredPool.total_shares, - ...poolMetrics, - }; - - if ( - filteredPool.type === "osmosis.gamm.v1beta1.Pool" && - Array.isArray(filteredPool.pool_tokens) - ) { - return { - ...sharePoolBase, - pool_assets: filteredPool.pool_tokens.map((token) => ({ - token: { - denom: token.denom, - amount: floatNumberToStringInt(token.amount, token.exponent), - }, - weight: token.weight_or_scaling.toString(), - })), - total_weight: filteredPool.total_weight_or_scaling.toString(), - }; - } - - if ( - filteredPool.type === "osmosis.gamm.poolmodels.stableswap.v1beta1.Pool" && - Array.isArray(filteredPool.pool_tokens) - ) { - return { - ...sharePoolBase, - pool_liquidity: filteredPool.pool_tokens.map((token) => ({ - denom: token.denom, - amount: floatNumberToStringInt(token.amount, token.exponent), - })), - scaling_factors: filteredPool.pool_tokens.map((token) => - token.weight_or_scaling.toString() - ), - scaling_factor_controller: filteredPool.scaling_factor_controller ?? "", - }; - } - - throw new Error("Filtered pool not properly serialized as a valid pool."); -} - -/** Converts a number with exponent decimals into a whole integer. */ -function floatNumberToStringInt(number: number, exponent: number): string { - return new Dec(number.toString()) - .mul(DecUtils.getTenExponentN(exponent)) - .truncate() - .toString(); -} - -function makeCoinFromToken(poolToken: PoolToken): { - denom: string; - amount: string; -} { - return { - denom: poolToken.denom, - amount: floatNumberToStringInt(poolToken.amount, poolToken.exponent), - }; -} - -export function makePoolFromIndexerPool( - assetLists: AssetList[], - filteredPool: FilteredPoolsResponse["pools"][number] -): Pool | undefined { - // deny pools containing tokens with gamm denoms - if ( - Array.isArray(filteredPool.pool_tokens) && - filteredPool.pool_tokens.some( - (token) => "denom" in token && token.denom.includes("gamm") - ) - ) { - return; - } - - /** Metrics common to all pools. */ - const basePool: { - spreadFactor: RatePretty; - totalFiatValueLocked: PricePretty; - } = { - spreadFactor: new RatePretty( - new Dec(filteredPool.swap_fees.toString()).mul( - DecUtils.getTenExponentN(-2) - ) - ), - totalFiatValueLocked: new PricePretty( - DEFAULT_VS_CURRENCY, - filteredPool.liquidity - ), - }; - - if ( - filteredPool.type === "osmosis.concentratedliquidity.v1beta1.Pool" && - !Array.isArray(filteredPool.pool_tokens) - ) { - if (!filteredPool.pool_tokens.asset0 || !filteredPool.pool_tokens.asset1) - return; - - const token0 = filteredPool.pool_tokens.asset0.denom; - const token1 = filteredPool.pool_tokens.asset1.denom; - - let token0Asset; - let token1Asset; - try { - token0Asset = getAsset({ assetLists, anyDenom: token0 }); - token1Asset = getAsset({ assetLists, anyDenom: token1 }); - } catch { - // Do nothing as it's expected to get unlisted assets from low liq pools - return; - } - - return { - id: filteredPool.pool_id.toString(), - type: "concentrated", - raw: { - address: filteredPool.address, - id: filteredPool.pool_id.toString(), - current_tick_liquidity: filteredPool.current_tick_liquidity, - token0, - token1, - current_sqrt_price: filteredPool.current_sqrt_price, - current_tick: filteredPool.current_tick, - tick_spacing: filteredPool.tick_spacing, - exponent_at_price_one: filteredPool.exponent_at_price_one, - spread_factor: filteredPool.spread_factor, - } as ConcentratedPoolRawResponse, - reserveCoins: [ - new CoinPretty(token0Asset, filteredPool.pool_tokens.asset0.amount), - new CoinPretty(token1Asset, filteredPool.pool_tokens.asset0.amount), - ], - ...basePool, - spreadFactor: new RatePretty(filteredPool.spread_factor), - }; - } - - if ( - filteredPool.type === "osmosis.cosmwasmpool.v1beta1.CosmWasmPool" && - Array.isArray(filteredPool.pool_tokens) - ) { - const reserveCoins = getReservesFromPoolTokens( - assetLists, - filteredPool.pool_tokens - ); - if (!reserveCoins) return; - - return { - id: filteredPool.pool_id.toString(), - type: getCosmwasmPoolTypeFromCodeId(filteredPool.code_id), - raw: { - contract_address: filteredPool.contract_address, - pool_id: filteredPool.pool_id.toString(), - code_id: filteredPool.code_id, - instantiate_msg: filteredPool.instantiate_msg, - }, - reserveCoins: reserveCoins, - ...basePool, - }; - } - - const sharePoolRawBase = { - id: filteredPool.pool_id.toString(), - pool_params: { - exit_fee: new Dec(filteredPool.exit_fees.toString()) - .mul(DecUtils.getTenExponentN(-2)) - .toString(), - swap_fee: new Dec(filteredPool.swap_fees.toString()) - .mul(DecUtils.getTenExponentN(-2)) - .toString(), - smooth_weight_change_params: null, - }, - total_shares: filteredPool.total_shares, - }; - - if ( - filteredPool.type === "osmosis.gamm.v1beta1.Pool" && - Array.isArray(filteredPool.pool_tokens) - ) { - const reserveCoins = getReservesFromPoolTokens( - assetLists, - filteredPool.pool_tokens - ); - if (!reserveCoins) return; - - return { - id: filteredPool.pool_id.toString(), - type: "weighted", - raw: { - pool_assets: filteredPool.pool_tokens.map((token) => ({ - token: { - denom: token.denom, - amount: floatNumberToStringInt(token.amount, token.exponent), - }, - weight: token.weight_or_scaling.toString(), - })), - total_weight: filteredPool.total_weight_or_scaling.toString(), - ...sharePoolRawBase, - }, - reserveCoins, - ...basePool, - }; - } - - if ( - filteredPool.type === "osmosis.gamm.poolmodels.stableswap.v1beta1.Pool" && - Array.isArray(filteredPool.pool_tokens) - ) { - const reserveCoins = getReservesFromPoolTokens( - assetLists, - filteredPool.pool_tokens - ); - if (!reserveCoins) return; - - return { - id: filteredPool.pool_id.toString(), - type: "stable", - raw: { - pool_liquidity: filteredPool.pool_tokens.map((token) => ({ - denom: token.denom, - amount: floatNumberToStringInt(token.amount, token.exponent), - })), - scaling_factors: filteredPool.pool_tokens.map((token) => - token.weight_or_scaling.toString() - ), - scaling_factor_controller: filteredPool.scaling_factor_controller ?? "", - ...sharePoolRawBase, - }, - reserveCoins, - ...basePool, - }; - } - - throw new Error("Filtered pool not properly serialized as a valid pool."); -} - -/** Get's reserves from asset list and returns them as CoinPretty objects, or undefined if an asset is not listed. */ -function getReservesFromPoolTokens( - assetLists: AssetList[], - poolTokens: PoolToken[] -) { - const coins = poolTokens.map(makeCoinFromToken).map((coin) => { - try { - const asset = getAsset({ assetLists, anyDenom: coin.denom }); - return new CoinPretty(asset, coin.amount); - } catch { - // Do nothing as it's expected to get unlisted assets from low liq pools - } - }); - - if (coins.some((asset) => !asset)) return; - else return coins as CoinPretty[]; -} diff --git a/packages/server/src/queries/complex/pools/providers/sidecar.ts b/packages/server/src/queries/complex/pools/providers/sidecar.ts index 3ca9cddac3..db7ca03ba7 100644 --- a/packages/server/src/queries/complex/pools/providers/sidecar.ts +++ b/packages/server/src/queries/complex/pools/providers/sidecar.ts @@ -1,43 +1,56 @@ -import { CoinPretty, PricePretty, RatePretty } from "@keplr-wallet/unit"; +import { CoinPretty, Dec, PricePretty, RatePretty } from "@keplr-wallet/unit"; import { AssetList, Chain } from "@osmosis-labs/types"; import { timeout } from "@osmosis-labs/utils"; import cachified, { CacheEntry } from "cachified"; import { LRUCache } from "lru-cache"; -import { IS_TESTNET } from "../../../../env"; +import { EXCLUDED_EXTERNAL_BOOSTS_POOL_IDS, IS_TESTNET } from "../../../../env"; import { PoolRawResponse } from "../../../../queries/osmosis"; import { queryPools } from "../../../../queries/sidecar"; import { DEFAULT_LRU_OPTIONS } from "../../../../utils/cache"; import { getAsset } from "../../assets"; import { DEFAULT_VS_CURRENCY } from "../../assets/config"; import { getCosmwasmPoolTypeFromCodeId } from "../env"; -import { Pool, PoolType } from "../index"; +import { Pool, PoolIncentiveType, PoolType } from "../index"; type SidecarPool = Awaited>[number]; const poolsCache = new LRUCache(DEFAULT_LRU_OPTIONS); +/** + * Pools that are excluded from showing external boost incentives APRs. + */ +const ExcludedExternalBoostPools: string[] = + (EXCLUDED_EXTERNAL_BOOSTS_POOL_IDS ?? []) as string[]; + /** Lightly cached pools from sidecar service. */ export function getPoolsFromSidecar({ assetLists, poolIds, minLiquidityUsd, + withMarketIncentives = true, }: { assetLists: AssetList[]; chainList: Chain[]; poolIds?: string[]; minLiquidityUsd?: number; + withMarketIncentives?: boolean; }): Promise { return cachified({ cache: poolsCache, key: (poolIds ? `sidecar-pools-${poolIds.join(",")}` : "sidecar-pools") + - minLiquidityUsd, + minLiquidityUsd + + withMarketIncentives.toString(), ttl: 5_000, // 5 seconds getFreshValue: async () => { const sidecarPools = await timeout( () => - queryPools({ poolIds, minLiquidityCap: minLiquidityUsd?.toString() }), + queryPools({ + poolIds, + minLiquidityCap: minLiquidityUsd?.toString(), + withMarketIncentives, + }), 9_000, // 9 seconds "sidecarQueryPools" )(); @@ -74,6 +87,9 @@ function makePoolFromSidecarPool({ // to ease integrations. if (!reserveCoins && !IS_TESTNET) return; + const pool_id = getPoolIdFromChainPool(sidecarPool.chain_model); + const data = getMarketIncentivesData(pool_id, sidecarPool); + return { id: getPoolIdFromChainPool(sidecarPool.chain_model), type: getPoolTypeFromChainPool(sidecarPool.chain_model), @@ -86,9 +102,119 @@ function makePoolFromSidecarPool({ DEFAULT_VS_CURRENCY, sidecarPool.liquidity_cap ), + ...data, + }; +} + +// getMarketIncentivesData is a function that returns the incentives and market data for a pool +// based on the pool_id and the apr_data and fees_data from the sidecar response +function getMarketIncentivesData( + pool_id: string, + { apr_data: aprs, fees_data: fees }: SidecarPool +) { + let totalUpper = maybeMakeRatePretty(aprs?.total_apr.upper ?? 0); + let totalLower = maybeMakeRatePretty(aprs?.total_apr.lower ?? 0); + const swapFeeUpper = maybeMakeRatePretty(aprs?.swap_fees.upper ?? 0); + const swapFeeLower = maybeMakeRatePretty(aprs?.swap_fees.lower ?? 0); + const superfluidUpper = maybeMakeRatePretty(aprs?.superfluid.upper ?? 0); + const superfluidLower = maybeMakeRatePretty(aprs?.superfluid.lower ?? 0); + const osmosisUpper = maybeMakeRatePretty(aprs?.osmosis.upper ?? 0); + const osmosisLower = maybeMakeRatePretty(aprs?.osmosis.lower ?? 0); + let boostUpper = maybeMakeRatePretty(aprs?.boost.upper ?? 0); + let boostLower = maybeMakeRatePretty(aprs?.boost.lower ?? 0); + + // Temporarily exclude pools in this array from showing boost incentives given an issue on chain + if ( + ExcludedExternalBoostPools.includes(pool_id) && + totalUpper && + totalLower && + boostUpper && + boostLower + ) { + totalUpper = new RatePretty(totalUpper.toDec().sub(totalUpper.toDec())); + totalLower = new RatePretty(totalLower.toDec().sub(totalLower.toDec())); + boostUpper = undefined; + boostLower = undefined; + } + + // add list of incentives that are defined + const incentiveTypes: PoolIncentiveType[] = []; + if (superfluidUpper && superfluidLower) incentiveTypes.push("superfluid"); + if (osmosisUpper && osmosisLower) incentiveTypes.push("osmosis"); + if (boostUpper && osmosisLower) incentiveTypes.push("boost"); + if ( + !superfluidUpper && + !superfluidLower && + !osmosisUpper && + !osmosisLower && + !boostUpper && + !boostLower + ) + incentiveTypes.push("none"); + const hasBreakdownData = + totalUpper || + totalLower || + swapFeeUpper || + swapFeeLower || + superfluidUpper || + superfluidLower || + osmosisUpper || + osmosisLower || + boostUpper || + boostLower; + + return { + incentives: { + aprBreakdown: hasBreakdownData + ? { + total: { + upper: totalUpper, + lower: totalLower, + }, + swapFee: { + upper: swapFeeUpper, + lower: swapFeeLower, + }, + superfluid: { + upper: superfluidUpper, + lower: superfluidLower, + }, + osmosis: { + upper: osmosisUpper, + lower: osmosisLower, + }, + boost: { + upper: boostUpper, + lower: boostLower, + }, + } + : undefined, + incentiveTypes, + }, + market: { + volume24hUsd: new PricePretty(DEFAULT_VS_CURRENCY, fees?.volume_24h ?? 0), + volume7dUsd: new PricePretty(DEFAULT_VS_CURRENCY, fees?.volume_7d ?? 0), + feesSpent24hUsd: new PricePretty( + DEFAULT_VS_CURRENCY, + fees?.fees_spent_24h ?? 0 + ), + feesSpent7dUsd: new PricePretty( + DEFAULT_VS_CURRENCY, + fees?.fees_spent_7d ?? 0 + ), + }, }; } +/** Checks explicitly for 0 or null before creating rate display object. */ +function maybeMakeRatePretty(value: number): RatePretty | undefined { + if (value === 0 || value === null) { + return undefined; + } + + return new RatePretty(new Dec(value).quo(new Dec(100))); +} + function getPoolIdFromChainPool( chain_model: SidecarPool["chain_model"] ): string { diff --git a/packages/server/src/queries/complex/pools/route-token-out-given-in.ts b/packages/server/src/queries/complex/pools/route-token-out-given-in.ts deleted file mode 100644 index 588436b898..0000000000 --- a/packages/server/src/queries/complex/pools/route-token-out-given-in.ts +++ /dev/null @@ -1,157 +0,0 @@ -import { Dec } from "@keplr-wallet/unit"; -import { - CONCENTRATED_LIQ_POOL_TYPE, - ConcentratedLiquidityPool, - ConcentratedLiquidityPoolRaw, - COSMWASM_POOL_TYPE, - CosmwasmPoolRaw, - FetchTickDataProvider, - OptimizedRoutes, - STABLE_POOL_TYPE, - StablePool, - StablePoolRaw, - Token, - TransmuterPool, - WEIGHTED_POOL_TYPE, - WeightedPool, - WeightedPoolRaw, -} from "@osmosis-labs/pools"; -import { Chain } from "@osmosis-labs/types"; -import cachified, { CacheEntry } from "cachified"; -import { LRUCache } from "lru-cache"; - -import { IS_TESTNET } from "../../../env"; -import { DEFAULT_LRU_OPTIONS } from "../../../utils/cache"; -import { queryNumPools } from "../../osmosis"; -import { queryPaginatedPools } from "./providers/indexer"; - -/** - * This function routes a given token to a specified output token denomination. - * It fetches the router, gets a quote for the route and retrieves candidate routes. - * - * @param token - The token to be routed. - * @param tokenOutDenom - The output token denomination. - * @returns Returns a promise that resolves with the quote and candidate routes. */ -export async function routeTokenOutGivenIn({ - chainList, - token, - tokenOutDenom, - forcePoolId, -}: { - chainList: Chain[]; - token: Token; - tokenOutDenom: string; - forcePoolId?: string; -}) { - // get quote - const router = await getRouter(chainList, forcePoolId ? 0 : undefined); - const quote = await router.routeByTokenIn(token, tokenOutDenom, forcePoolId); - const candidateRoutes = router.getCandidateRoutes(token.denom, tokenOutDenom); - - return { - quote: { - ...quote, - split: quote.split.map((split) => ({ - initialAmount: split.initialAmount, - pools: split.pools.map((pool) => ({ id: pool.id })), - tokenOutDenoms: split.tokenOutDenoms, - tokenInDenom: split.tokenInDenom, - })), - }, - candidateRoutes, - }; -} - -const routerCache = new LRUCache(DEFAULT_LRU_OPTIONS); - -/** Gets pools and returns a cached router instance. */ -export async function getRouter( - chainList: Chain[], - minLiquidityUsd = IS_TESTNET ? 0 : 1000, - routerCacheTtl = 30 * 1000 -): Promise { - return cachified({ - key: "router" + minLiquidityUsd, - cache: routerCache, - ttl: routerCacheTtl, - async getFreshValue() { - // fetch pool data - const numPoolsResponse = await queryNumPools({ chainList }); - const poolsResponse = await queryPaginatedPools({ - chainList, - page: 1, - limit: Number(numPoolsResponse.num_pools), - minimumLiquidity: minLiquidityUsd, - }); - - // create routable pool impls from response - const routablePools = poolsResponse.pools - .map((pool) => { - if (pool["@type"] === CONCENTRATED_LIQ_POOL_TYPE) { - pool = pool as ConcentratedLiquidityPoolRaw; - return new ConcentratedLiquidityPool( - pool, - new FetchTickDataProvider( - chainList[0].apis.rest[0].address, - pool.id - ) - ); - } - - if (pool["@type"] === WEIGHTED_POOL_TYPE) { - return new WeightedPool(pool as WeightedPoolRaw); - } - - if (pool["@type"] === STABLE_POOL_TYPE) { - return new StablePool(pool as StablePoolRaw); - } - - if (pool["@type"] === COSMWASM_POOL_TYPE) { - return new TransmuterPool(pool as CosmwasmPoolRaw); - } - }) - .filter( - ( - pool - ): pool is - | ConcentratedLiquidityPool - | WeightedPool - | StablePool - | TransmuterPool => pool !== undefined - ); - - // prep router params - const preferredPoolIds = routablePools.reduce( - (preferredPoolIds, pool) => { - if (pool.type === "concentrated") { - preferredPoolIds.push(pool.id); - } - if (pool.type === "transmuter") { - preferredPoolIds.unshift(pool.id); - } - - return preferredPoolIds; - }, - [] as string[] - ); - const getPoolTotalValueLocked = (poolId: string) => { - const pool = poolsResponse.pools.find((pool) => - "pool_id" in pool ? pool.pool_id : pool.id === poolId - ); - if (!pool) { - console.warn("No pool found for pool", poolId); - return new Dec(0); - } - if (!pool.liquidityUsd) { - return new Dec(0); - } else return new Dec(pool.liquidityUsd.toString()); - }; - - return new OptimizedRoutes({ - pools: routablePools, - preferredPoolIds, - getPoolTotalValueLocked, - }); - }, - }); -} diff --git a/packages/server/src/queries/complex/portfolio/__tests__/allocation.spec.ts b/packages/server/src/queries/complex/portfolio/__tests__/allocation.spec.ts index 4f061b41a0..1bc9641511 100644 --- a/packages/server/src/queries/complex/portfolio/__tests__/allocation.spec.ts +++ b/packages/server/src/queries/complex/portfolio/__tests__/allocation.spec.ts @@ -117,7 +117,7 @@ describe("Allocation Functions", () => { }); describe("calculatePercentAndFiatValues", () => { - it("should calculate the correct asset percentages and fiat values", async () => { + it("should calculate the correct asset percentages and fiat values - Total Assets", async () => { const result = await calculatePercentAndFiatValues( MOCK_DATA.categories, assetLists, @@ -131,9 +131,9 @@ describe("Allocation Functions", () => { expect(result).toEqual([ { - key: "CTK", - percentage: "50%", fiatValue: "$30", + percentage: "50%", + key: "CTK", }, { key: "SAIL", @@ -141,9 +141,9 @@ describe("Allocation Functions", () => { fiatValue: "$20", }, { + fiatValue: "$10", key: "WOSMO", percentage: "16.666%", - fiatValue: "$10", }, { key: "Other", @@ -153,7 +153,7 @@ describe("Allocation Functions", () => { ]); }); - it("should calculate the correct asset percentages and fiat values", async () => { + it("should calculate the correct asset percentages and fiat values - User Balances", async () => { const result = await calculatePercentAndFiatValues( MOCK_DATA.categories, assetLists, @@ -167,9 +167,9 @@ describe("Allocation Functions", () => { expect(result).toEqual([ { + fiatValue: "$20", key: "SAIL", percentage: "200%", - fiatValue: "$20", }, { key: "WOSMO", diff --git a/packages/server/src/queries/complex/portfolio/allocation.ts b/packages/server/src/queries/complex/portfolio/allocation.ts index 662000c5c7..5d2bd4cf12 100644 --- a/packages/server/src/queries/complex/portfolio/allocation.ts +++ b/packages/server/src/queries/complex/portfolio/allocation.ts @@ -6,7 +6,7 @@ import { sort } from "@osmosis-labs/utils"; import { DEFAULT_VS_CURRENCY } from "../../../queries/complex/assets/config"; import { queryAllocation } from "../../../queries/data-services"; import { Categories } from "../../../queries/data-services"; -import { AccountCoinsResult } from "../../../queries/data-services"; +import { AccountCoinsResultDec } from "../../../queries/sidecar/allocation"; import { getAsset } from "../assets"; interface FormattedAllocation { @@ -20,6 +20,7 @@ export interface GetAllocationResponse { all: FormattedAllocation[]; assets: FormattedAllocation[]; available: FormattedAllocation[]; + totalCap: PricePretty; } export function getAll(categories: Categories): FormattedAllocation[] { @@ -37,33 +38,40 @@ export function getAll(categories: Categories): FormattedAllocation[] { .add(unclaimedRewardsCap) .add(pooledCap); - return [ + const allocations: { key: string; fiatValue: Dec }[] = [ { key: "available", - percentage: new RatePretty(userBalancesCap.quo(totalCap)), - fiatValue: new PricePretty(DEFAULT_VS_CURRENCY, userBalancesCap), + fiatValue: userBalancesCap, }, { key: "staked", - percentage: new RatePretty(stakedCap.quo(totalCap)), - fiatValue: new PricePretty(DEFAULT_VS_CURRENCY, stakedCap), + fiatValue: stakedCap, }, { key: "unstaking", - percentage: new RatePretty(unstakingCap.quo(totalCap)), - fiatValue: new PricePretty(DEFAULT_VS_CURRENCY, unstakingCap), + fiatValue: unstakingCap, }, { key: "unclaimedRewards", - percentage: new RatePretty(unclaimedRewardsCap.quo(totalCap)), - fiatValue: new PricePretty(DEFAULT_VS_CURRENCY, unclaimedRewardsCap), + fiatValue: unclaimedRewardsCap, }, { key: "pooled", - percentage: new RatePretty(pooledCap.quo(totalCap)), - fiatValue: new PricePretty(DEFAULT_VS_CURRENCY, pooledCap), + fiatValue: pooledCap, }, ]; + + const sortedAllocation = sort(allocations, "fiatValue", "desc"); + + const formattedAllocations: FormattedAllocation[] = sortedAllocation.map( + (allocation) => ({ + key: allocation.key, + percentage: new RatePretty(allocation.fiatValue.quo(totalCap)), + fiatValue: new PricePretty(DEFAULT_VS_CURRENCY, allocation.fiatValue), + }) + ); + + return formattedAllocations; } export function calculatePercentAndFiatValues( @@ -75,16 +83,23 @@ export function calculatePercentAndFiatValues( const totalAssets = categories[category]; const totalCap = new Dec(totalAssets.capitalization); + const account_coins_result = (totalAssets?.account_coins_result || []).map( + (asset) => ({ + ...asset, + cap_value: new Dec(asset.cap_value), + }) + ); + const sortedAccountCoinsResults = sort( - totalAssets?.account_coins_result || [], + account_coins_result || [], "cap_value", - "asc" + "desc" ); const topCoinsResults = sortedAccountCoinsResults.slice(0, allocationLimit); const assets: FormattedAllocation[] = topCoinsResults.map( - (asset: AccountCoinsResult) => { + (asset: AccountCoinsResultDec) => { const assetFromAssetLists = getAsset({ assetLists, anyDenom: asset.coin.denom, @@ -92,11 +107,8 @@ export function calculatePercentAndFiatValues( return { key: assetFromAssetLists.coinDenom, - percentage: new RatePretty(new Dec(asset.cap_value).quo(totalCap)), - fiatValue: new PricePretty( - DEFAULT_VS_CURRENCY, - new Dec(asset.cap_value) - ), + percentage: new RatePretty(asset.cap_value.quo(totalCap)), + fiatValue: new PricePretty(DEFAULT_VS_CURRENCY, asset.cap_value), }; } ); @@ -104,7 +116,7 @@ export function calculatePercentAndFiatValues( const otherAssets = sortedAccountCoinsResults.slice(allocationLimit); const otherAmount = otherAssets.reduce( - (sum: Dec, asset: AccountCoinsResult) => sum.add(new Dec(asset.cap_value)), + (sum: Dec, asset: AccountCoinsResultDec) => sum.add(asset.cap_value), new Dec(0) ); @@ -149,9 +161,15 @@ export async function getAllocation({ allocationLimit ); + const totalCap = new PricePretty( + DEFAULT_VS_CURRENCY, + new Dec(categories["total-assets"].capitalization) + ); + return { all, assets, available, + totalCap, }; } diff --git a/packages/server/src/queries/complex/swap-routers.ts b/packages/server/src/queries/complex/swap-routers.ts deleted file mode 100644 index 085dc49dac..0000000000 --- a/packages/server/src/queries/complex/swap-routers.ts +++ /dev/null @@ -1,58 +0,0 @@ -import type { TokenOutGivenInRouter } from "@osmosis-labs/pools"; -import { AssetList, Chain } from "@osmosis-labs/types"; -import { z } from "zod"; - -import { SIDECAR_BASE_URL, TFM_BASE_URL } from "../../env"; -import { routeTokenOutGivenIn } from "../complex/pools/route-token-out-given-in"; -import { OsmosisSidecarRemoteRouter } from "../sidecar/router"; -import { TfmRemoteRouter } from "../tfm/router"; -import { calcAssetValue } from "./assets"; - -export const availableRoutersSchema = z.enum(["tfm", "sidecar", "legacy"]); -export type RouterKey = z.infer; - -export function getRouters( - osmosisChainId: string, - assetLists: AssetList[], - chainList: Chain[] -) { - return [ - { - name: "tfm", - router: new TfmRemoteRouter( - osmosisChainId, - TFM_BASE_URL ?? "https://api.tfm.com", - (coinMinimalDenom, amount) => - calcAssetValue({ - assetLists, - chainList, - anyDenom: coinMinimalDenom, - amount, - }) - ), - }, - { - name: "sidecar", - router: new OsmosisSidecarRemoteRouter( - SIDECAR_BASE_URL ?? "https://sqs.osmosis.zone" - ), - }, - { - name: "legacy", - router: { - routeByTokenIn: async (tokenIn, tokenOutDenom, forcePoolId) => - ( - await routeTokenOutGivenIn({ - chainList, - token: tokenIn, - tokenOutDenom, - forcePoolId, - }) - ).quote, - } as TokenOutGivenInRouter, - }, - ] as { - name: RouterKey; - router: TokenOutGivenInRouter; - }[]; -} diff --git a/packages/server/src/queries/index.ts b/packages/server/src/queries/index.ts index 2759531421..6aba59626a 100644 --- a/packages/server/src/queries/index.ts +++ b/packages/server/src/queries/index.ts @@ -9,4 +9,5 @@ export * from "./github"; export * from "./ibc"; export * from "./keybase"; export * from "./osmosis"; +export * from "./sidecar"; export * from "./twitter"; diff --git a/packages/server/src/queries/osmosis/poolmanager/pools.ts b/packages/server/src/queries/osmosis/poolmanager/pools.ts index 88b95ea653..2f1d485a4a 100644 --- a/packages/server/src/queries/osmosis/poolmanager/pools.ts +++ b/packages/server/src/queries/osmosis/poolmanager/pools.ts @@ -107,6 +107,6 @@ export type PoolsResponse = { pools: PoolRawResponse[]; }; -export const queryPools = createNodeQuery({ +export const queryPoolsChain = createNodeQuery({ path: "/osmosis/poolmanager/v1beta1/all-pools", }); diff --git a/packages/server/src/queries/sidecar/allocation.ts b/packages/server/src/queries/sidecar/allocation.ts index 5fae475503..48b92658e4 100644 --- a/packages/server/src/queries/sidecar/allocation.ts +++ b/packages/server/src/queries/sidecar/allocation.ts @@ -1,3 +1,4 @@ +import { Dec } from "@keplr-wallet/unit"; import { apiClient } from "@osmosis-labs/utils"; import { SIDECAR_BASE_URL } from "../../env"; @@ -11,6 +12,11 @@ export interface AccountCoinsResult { coin: Coin; cap_value: string; } + +export interface AccountCoinsResultDec { + coin: Coin; + cap_value: Dec; +} export interface Category { capitalization: string; is_best_effort: boolean; diff --git a/packages/server/src/queries/sidecar/index.ts b/packages/server/src/queries/sidecar/index.ts index 2c821e4faa..5a791d856d 100644 --- a/packages/server/src/queries/sidecar/index.ts +++ b/packages/server/src/queries/sidecar/index.ts @@ -1,2 +1,3 @@ export * from "./pools"; export * from "./prices"; +export * from "./router"; diff --git a/packages/server/src/queries/sidecar/pools.ts b/packages/server/src/queries/sidecar/pools.ts index 6e110cec67..83b52b7e42 100644 --- a/packages/server/src/queries/sidecar/pools.ts +++ b/packages/server/src/queries/sidecar/pools.ts @@ -48,6 +48,38 @@ export type ChainPool = | ChainConcentratedPool | ChainCosmwasmPool; +export type SQSAprData = { + swap_fees: { + upper?: number; + lower?: number; + }; + superfluid: { + upper?: number; + lower?: number; + }; + osmosis: { + upper?: number; + lower?: number; + }; + boost: { + upper?: number; + lower: number; + }; + total_apr: { + upper?: number; + lower?: number; + }; +}; + +export type SQSPoolFeesData = { + pool_id?: string; + volume_24h?: number; + volume_7d?: number; + fees_spent_24h?: number; + fees_spent_7d?: number; + fees_percentage?: string; +}; + export type SqsPool = { /** Sidecar returns the same pool models as the node. */ chain_model: ChainPool; @@ -59,12 +91,20 @@ export type SqsPool = { /** Int: capitalization in USD. Will be `"0"` if liquidity_cap_error is present. */ liquidity_cap: string; liquidity_cap_error: string; + + apr_data?: SQSAprData; + fees_data?: SQSPoolFeesData; }; export function queryPools({ poolIds, minLiquidityCap, -}: { poolIds?: string[]; minLiquidityCap?: string } = {}) { + withMarketIncentives, +}: { + poolIds?: string[]; + minLiquidityCap?: string; + withMarketIncentives?: boolean; +} = {}) { const url = new URL("/pools", SIDECAR_BASE_URL); const params = new URLSearchParams(); @@ -74,6 +114,9 @@ export function queryPools({ if (minLiquidityCap) { params.append("min_liquidity_cap", minLiquidityCap); } + if (withMarketIncentives) { + params.append("with_market_incentives", "true"); + } url.search = params.toString(); return apiClient(url.toString()); diff --git a/packages/server/src/queries/sidecar/router.ts b/packages/server/src/queries/sidecar/router.ts index a229a8e966..5101c169e1 100644 --- a/packages/server/src/queries/sidecar/router.ts +++ b/packages/server/src/queries/sidecar/router.ts @@ -8,14 +8,19 @@ import { NoRouteError, SplitTokenInQuote, Token, - TokenOutGivenInRouter, } from "@osmosis-labs/pools/build/router"; import { apiClient } from "@osmosis-labs/utils"; +import { SIDECAR_BASE_URL } from "../../env"; import { SidecarPoolType, SidecarQuoteResponse } from "./types"; +/** Get a client for the sidecar router. */ +export function getSidecarRouter() { + return new OsmosisSidecarRemoteRouter(SIDECAR_BASE_URL); +} + /** Use this as a client for generating quotes from a sidecar query server. */ -export class OsmosisSidecarRemoteRouter implements TokenOutGivenInRouter { +class OsmosisSidecarRemoteRouter { protected readonly baseUrl: URL; constructor(protected readonly sidecarBaseUrl: string) { diff --git a/packages/server/src/queries/tfm/router.ts b/packages/server/src/queries/tfm/router.ts deleted file mode 100644 index 72a4d084d2..0000000000 --- a/packages/server/src/queries/tfm/router.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { Dec, Int } from "@keplr-wallet/unit"; -import { - NoRouteError, - SplitTokenInQuote, - Token, - TokenOutGivenInRouter, -} from "@osmosis-labs/pools/build/router"; -import { apiClient } from "@osmosis-labs/utils"; - -import { GetSwapRouteResponse } from "./types"; - -// TFM tends to frequently return quotes with too high price impact -// If we don't limit it, it will result in failed tx for many concurrent users. -// A single swap with high price impact would invalidate the route. -const maxAllowedPriceImpact = 0.5; - -export class TfmRemoteRouter implements TokenOutGivenInRouter { - protected readonly baseUrl: URL; - - constructor( - protected readonly osmosisChainId: string, - protected readonly tfmBaseUrl: string, - protected readonly calcAssetValue: ( - coinMinimalDenom: string, - amount: Int - ) => Promise - ) { - this.baseUrl = new URL(tfmBaseUrl); - } - - async routeByTokenIn( - tokenIn: Token, - tokenOutDenom: string, - forcePoolId?: string - ): Promise { - // return empty quote since TFM doesn't support forced swap through a pool - if (forcePoolId) { - return { - amount: new Int(0), - split: [], - }; - } - - // fetch quote - const tokenInDenomEncoded = encodeURIComponent(tokenIn.denom); - const tokenOutDenomEncoded = encodeURIComponent(tokenOutDenom); - const queryUrl = new URL( - `/api/v1/ibc/swap/route/${this.osmosisChainId}/${this.osmosisChainId}/${tokenInDenomEncoded}/${tokenOutDenomEncoded}/${tokenIn.amount}`, - this.baseUrl.toString() - ); - queryUrl.searchParams.append("swapMode", "Turbo"); - - try { - const result = await apiClient(queryUrl.toString()); - const amount = new Int(result.returnAmount); - - const priceImpactTokenOut = await this.calculatePriceImpact(tokenIn, { - denom: tokenOutDenom, - amount, - }); - - // TFM will always return the max out that can be swapped - // But since it will result in failed tx, return an error - if (priceImpactTokenOut?.gt(new Dec(maxAllowedPriceImpact))) { - throw new Error( - `{Price impact ${priceImpactTokenOut} is greater than max allowed of ${maxAllowedPriceImpact}` - ); - } - - // convert quote response to SplitTokenInQuote - return { - amount, - split: result.routes[0].routes.map(({ inputAmount, operations }) => { - return { - initialAmount: new Int(inputAmount), - pools: operations.map((op) => ({ id: op.poolId.toString() })), - tokenOutDenoms: operations.map((op) => op.askToken), - tokenInDenom: operations[0].offerToken, - }; - }), - priceImpactTokenOut, - }; - } catch (e) { - // TFM responded with an error as custom formatted JSON - const tfmJsonError = e as { - data: { error: { code: number; message: string } }; - status: number; - statusText: string; - }; - - if (tfmJsonError.status === 504) { - throw new Error("TFM timed out"); - } - - if (tfmJsonError?.data?.error?.code === 500) { - // consider a no router error - throw new NoRouteError(); - } - - if (tfmJsonError?.data?.error?.message) { - throw new Error(tfmJsonError.data.error.message); - } - - // if not a custom TFM error, throw the original error - throw e as Error; - } - } - - protected async calculatePriceImpact( - tokenIn: Token, - tokenOut: Token - ): Promise { - const tokenInValue = await this.calcAssetValue( - tokenIn.denom, - tokenIn.amount - ); - const tokenOutValue = await this.calcAssetValue( - tokenOut.denom, - tokenOut.amount - ); - - if (!tokenInValue || !tokenOutValue) { - return undefined; - } - - return tokenOutValue.sub(tokenInValue).quo(tokenInValue); - } -} diff --git a/packages/server/src/queries/tfm/types.ts b/packages/server/src/queries/tfm/types.ts deleted file mode 100644 index b2ffb69158..0000000000 --- a/packages/server/src/queries/tfm/types.ts +++ /dev/null @@ -1,32 +0,0 @@ -export type GetSwapRouteResponse = { - returnAmount: number; - askToken: string; - offerToken: string; - routes: { - inputAmount: number; - returnAmount: number; - priceImpact: number; - inputPercentage: number; - routes: { - inputAmount: number; - returnAmount: number; - priceImpact: number; - inputPercentage: number; - operations: { - askToken: string; - offerToken: string; - poolId: number; - }[]; - }[]; - }[]; -}; - -export type GetTokensResponse = { - name: string; - symbol: string; - contractAddr: string; - decimals: number; - numberOfPools: number; - imageUrl: string | null; - isTrading: boolean; -}[]; diff --git a/packages/server/src/utils/__tests__/error.spec.ts b/packages/server/src/utils/__tests__/error.spec.ts index c227d6d610..0d3cb5b961 100644 --- a/packages/server/src/utils/__tests__/error.spec.ts +++ b/packages/server/src/utils/__tests__/error.spec.ts @@ -1,21 +1,20 @@ -/* eslint-disable import/no-extraneous-dependencies */ -import * as Sentry from "@sentry/core"; +import { context, Span, trace } from "@opentelemetry/api"; import { captureError, captureErrorAndReturn, captureIfError } from "../error"; -jest.mock("@sentry/core"); +jest.mock("@opentelemetry/api"); describe("captureErrorAndReturn", () => { it("should capture the error and return the provided value", () => { const mockError = new Error("Test error"); const returnValue = "Return value"; - - // Mock the captureError function to just return the error - jest.spyOn(Sentry, "captureException").mockImplementation(() => "error"); + const mockSpan = { recordException: jest.fn() }; + jest.spyOn(trace, "getSpan").mockReturnValue(mockSpan as unknown as Span); + jest.spyOn(context, "active").mockReturnValue({} as any); const result = captureErrorAndReturn(mockError, returnValue); - expect(Sentry.captureException).toHaveBeenCalledWith(mockError); + expect(mockSpan.recordException).toHaveBeenCalledWith(mockError); expect(result).toBe(returnValue); }); }); @@ -27,16 +26,22 @@ describe("captureError", () => { test("captures Error instances", () => { const error = new Error("Test error"); + const mockSpan = { recordException: jest.fn() }; + jest.spyOn(trace, "getSpan").mockReturnValue(mockSpan as unknown as Span); + jest.spyOn(context, "active").mockReturnValue({} as any); captureError(error); - expect(Sentry.captureException).toHaveBeenCalledWith(error); + expect(mockSpan.recordException).toHaveBeenCalledWith(error); }); test("does not capture non-Error instances", () => { const notAnError = "Not an error"; + const mockSpan = { recordException: jest.fn() }; + jest.spyOn(trace, "getSpan").mockReturnValue(mockSpan as unknown as Span); + jest.spyOn(context, "active").mockReturnValue({} as any); captureError(notAnError); - expect(Sentry.captureException).not.toHaveBeenCalled(); + expect(mockSpan.recordException).not.toHaveBeenCalled(); }); }); @@ -47,15 +52,22 @@ describe("captureIfError", () => { test("captures Error thrown in closure", () => { const error = new Error("Test error"); + const mockSpan = { recordException: jest.fn() }; + jest.spyOn(trace, "getSpan").mockReturnValue(mockSpan as unknown as Span); + jest.spyOn(context, "active").mockReturnValue({} as any); captureIfError(() => { throw error; }); - expect(Sentry.captureException).toHaveBeenCalledWith(error); + expect(mockSpan.recordException).toHaveBeenCalledWith(error); }); test("calls throwable function that doesn't throw", () => { + const mockSpan = { recordException: jest.fn() }; + jest.spyOn(trace, "getSpan").mockReturnValue(mockSpan as unknown as Span); + jest.spyOn(context, "active").mockReturnValue({} as any); + captureIfError(() => {}); - expect(Sentry.captureException).not.toHaveBeenCalled(); + expect(mockSpan.recordException).not.toHaveBeenCalled(); }); }); diff --git a/packages/server/src/utils/error.ts b/packages/server/src/utils/error.ts index d4b7a2f9de..8faa1742e3 100644 --- a/packages/server/src/utils/error.ts +++ b/packages/server/src/utils/error.ts @@ -1,4 +1,4 @@ -import * as Sentry from "@sentry/core"; +import { context, trace } from "@opentelemetry/api"; export function captureErrorAndReturn(e: Error, returnValue: TReturn) { captureError(e); @@ -6,8 +6,12 @@ export function captureErrorAndReturn(e: Error, returnValue: TReturn) { } export function captureError(e: any) { + const activeSpan = trace.getSpan(context.active()); if (e instanceof Error) { - Sentry.captureException(e); + if (activeSpan) { + // Reuse the existing active span + activeSpan.recordException(e); + } if (process.env.NODE_ENV === "development") console.warn("Captured:", e); } else if (process.env.NODE_ENV === "development") { console.warn("Did not capture non-Error:", e); diff --git a/packages/server/tsconfig.json b/packages/server/tsconfig.json index f94b2526a2..11f48d2cf5 100644 --- a/packages/server/tsconfig.json +++ b/packages/server/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "build", "declaration": true, "rootDir": "src", - "module": "ES6" + "module": "esnext" }, "include": ["src/**/*"] } diff --git a/packages/stores/src/account/__tests_e2e__/collect-rewards.spec.ts b/packages/stores/src/account/__tests_e2e__/collect-rewards.spec.ts index 6afba37a58..faea2b1e6d 100644 --- a/packages/stores/src/account/__tests_e2e__/collect-rewards.spec.ts +++ b/packages/stores/src/account/__tests_e2e__/collect-rewards.spec.ts @@ -137,7 +137,10 @@ describe("Collect Cl Fees Txs", () => { account!.osmosis .sendSwapExactAmountInMsg( [{ id: poolId, tokenOutDenom: "uion" }], - { currency: osmoCurrency, amount: osmoSwapAmount }, + { + coinMinimalDenom: osmoCurrency.coinMinimalDenom, + amount: osmoSwapAmount, + }, "9", undefined, undefined, @@ -154,7 +157,10 @@ describe("Collect Cl Fees Txs", () => { account!.osmosis .sendSwapExactAmountInMsg( [{ id: poolId, tokenOutDenom: "uion" }], - { currency: osmoCurrency, amount: osmoSwapAmount }, + { + coinMinimalDenom: osmoCurrency.coinMinimalDenom, + amount: osmoSwapAmount, + }, "9", undefined, undefined, diff --git a/packages/stores/src/account/__tests_e2e__/swap-exact-in-positions.spec.ts b/packages/stores/src/account/__tests_e2e__/swap-exact-in-positions.spec.ts index 8123e7ba7a..ecc0cf5fcd 100644 --- a/packages/stores/src/account/__tests_e2e__/swap-exact-in-positions.spec.ts +++ b/packages/stores/src/account/__tests_e2e__/swap-exact-in-positions.spec.ts @@ -568,7 +568,7 @@ describe("Test Swap Exact In - Concentrated Liquidity", () => { .sendSwapExactAmountInMsg( [{ id: queryPool!.id, tokenOutDenom }], { - currency: tokenInCurrency, + coinMinimalDenom: tokenInCurrency.coinMinimalDenom, amount: amountIn, }, minAmountOut, diff --git a/packages/stores/src/account/__tests_e2e__/swap-exact-in.spec.ts b/packages/stores/src/account/__tests_e2e__/swap-exact-in.spec.ts index c9edb668e2..2d45549e24 100644 --- a/packages/stores/src/account/__tests_e2e__/swap-exact-in.spec.ts +++ b/packages/stores/src/account/__tests_e2e__/swap-exact-in.spec.ts @@ -74,11 +74,7 @@ describe("Test Osmosis Swap Exact Amount In Tx", () => { .sendSwapExactAmountInMsg( [{ id: queryPool!.id, tokenOutDenom: "ubar" }], { - currency: { - coinDenom: "FOO", - coinMinimalDenom: "ufoo", - coinDecimals: 6, - }, + coinMinimalDenom: "ufoo", amount: "10", }, "1", @@ -101,11 +97,7 @@ describe("Test Osmosis Swap Exact Amount In Tx", () => { .sendSwapExactAmountInMsg( [{ id: queryPool!.id, tokenOutDenom: "uatom" }], { - currency: { - coinDenom: "ION", - coinMinimalDenom: "uion", - coinDecimals: 6, - }, + coinMinimalDenom: "uion", amount: "10", }, "1", @@ -151,7 +143,10 @@ describe("Test Osmosis Swap Exact Amount In Tx", () => { tokenOutDenom: tokenOutCurrency.coinMinimalDenom, }, ], - tokenIn, + { + coinMinimalDenom: tokenIn.currency.coinMinimalDenom, + amount: tokenIn.amount, + }, "1", undefined, undefined, @@ -244,7 +239,10 @@ describe("Test Osmosis Swap Exact Amount In Tx", () => { tokenOutDenom: tokenOutCurrency.coinMinimalDenom, }, ], - tokenIn, + { + coinMinimalDenom: tokenIn.currency.coinMinimalDenom, + amount: tokenIn.amount, + }, "1", undefined, undefined, @@ -330,7 +328,10 @@ describe("Test Osmosis Swap Exact Amount In Tx", () => { tokenOutDenom: tokenOutCurrency.coinMinimalDenom, }, ], - tokenIn, + { + coinMinimalDenom: tokenIn.currency.coinMinimalDenom, + amount: tokenIn.amount, + }, "1", undefined, undefined, @@ -432,7 +433,10 @@ describe("Test Osmosis Swap Exact Amount In Tx", () => { tokenOutDenom: tokenOutCurrency.coinMinimalDenom, }, ], - tokenIn, + { + coinMinimalDenom: tokenIn.currency.coinMinimalDenom, + amount: tokenIn.amount, + }, outWithLess.toString(), undefined, undefined, @@ -527,7 +531,9 @@ describe("Test Osmosis Swap Exact Amount In Tx", () => { })), tokenInAmount: route.initialAmount.toString(), })), - tokenIn, + { + coinMinimalDenom: tokenIn.currency.coinMinimalDenom, + }, amount.toString(), undefined, undefined, diff --git a/packages/stores/src/account/amino-converters.ts b/packages/stores/src/account/amino-converters.ts index 394eddebb8..202958398c 100644 --- a/packages/stores/src/account/amino-converters.ts +++ b/packages/stores/src/account/amino-converters.ts @@ -1,122 +1,133 @@ -import { AminoMsgTransfer } from "@cosmjs/stargate"; -import { - cosmos, - cosmosAminoConverters, - cosmwasmAminoConverters, - ibcAminoConverters as originalIbcAminoConverters, - osmosisAminoConverters as originalOsmosisAminoConverters, -} from "@osmosis-labs/proto-codecs"; -import { MsgTransfer } from "cosmjs-types/ibc/applications/transfer/v1/tx"; +import type { AminoMsgTransfer } from "@cosmjs/stargate"; +import type { MsgTransfer } from "cosmjs-types/ibc/applications/transfer/v1/tx"; import Long from "long"; -const osmosisAminoConverters: Record< - keyof typeof originalOsmosisAminoConverters, - { - aminoType: string; - toAmino: (msg: any) => any; - fromAmino: (msg: any) => any; - } -> = { - ...originalOsmosisAminoConverters, - "/osmosis.concentratedliquidity.poolmodel.concentrated.v1beta1.MsgCreateConcentratedPool": - { - ...originalOsmosisAminoConverters[ - "/osmosis.concentratedliquidity.poolmodel.concentrated.v1beta1.MsgCreateConcentratedPool" - ], - aminoType: "osmosis/cl-create-pool", - }, - "/osmosis.superfluid.MsgUnbondConvertAndStake": { - ...originalOsmosisAminoConverters[ - "/osmosis.superfluid.MsgUnbondConvertAndStake" - ], - fromAmino: (msg: any) => ({ - lockId: BigInt(msg?.lock_id ?? 0), - sender: msg.sender, - valAddr: msg?.val_addr ?? "", - minAmtToStake: msg.min_amt_to_stake, - sharesToConvert: ( - msg === null || msg === void 0 ? void 0 : msg.shares_to_convert - ) - ? cosmos.base.v1beta1.Coin.fromAmino(msg.shares_to_convert) - : undefined, - }), - }, -}; +let aminoConverters: Record; -const ibcAminoConverters: Record< - keyof typeof originalIbcAminoConverters, - { - aminoType: string; - toAmino: (msg: any) => any; - fromAmino: (msg: any) => any; - } -> = { - ...originalIbcAminoConverters, - "/ibc.applications.transfer.v1.MsgTransfer": { - ...originalIbcAminoConverters["/ibc.applications.transfer.v1.MsgTransfer"], - // Remove timeout_timestamp as it is not used by our transactions. - toAmino: ({ - sourcePort, - sourceChannel, - token, - sender, - receiver, - timeoutHeight, - }: MsgTransfer) => ({ - source_port: sourcePort, - source_channel: sourceChannel, - token: { - denom: token?.denom, - amount: token?.amount ?? "0", +export async function getAminoConverters() { + if (!aminoConverters) { + const { + cosmos, + cosmosAminoConverters, + cosmwasmAminoConverters, + ibcAminoConverters: originalIbcAminoConverters, + osmosisAminoConverters: originalOsmosisAminoConverters, + } = await import("@osmosis-labs/proto-codecs"); + + const osmosisAminoConverters: Record< + keyof typeof originalOsmosisAminoConverters, + { + aminoType: string; + toAmino: (msg: any) => any; + fromAmino: (msg: any) => any; + } + > = { + ...originalOsmosisAminoConverters, + "/osmosis.concentratedliquidity.poolmodel.concentrated.v1beta1.MsgCreateConcentratedPool": + { + ...originalOsmosisAminoConverters[ + "/osmosis.concentratedliquidity.poolmodel.concentrated.v1beta1.MsgCreateConcentratedPool" + ], + aminoType: "osmosis/cl-create-pool", + }, + "/osmosis.superfluid.MsgUnbondConvertAndStake": { + ...originalOsmosisAminoConverters[ + "/osmosis.superfluid.MsgUnbondConvertAndStake" + ], + fromAmino: (msg: any) => ({ + lockId: BigInt(msg?.lock_id ?? 0), + sender: msg.sender, + valAddr: msg?.val_addr ?? "", + minAmtToStake: msg.min_amt_to_stake, + sharesToConvert: ( + msg === null || msg === void 0 ? void 0 : msg.shares_to_convert + ) + ? cosmos.base.v1beta1.Coin.fromAmino(msg.shares_to_convert) + : undefined, + }), }, - sender, - receiver, - timeout_height: timeoutHeight - ? { - revision_height: timeoutHeight.revisionHeight?.toString(), - revision_number: timeoutHeight.revisionNumber?.toString(), - } - : {}, - }), - fromAmino: ({ - source_port, - source_channel, - token, - sender, - receiver, - timeout_height, - timeout_timestamp, - }: AminoMsgTransfer["value"]): MsgTransfer => { - return { - sourcePort: source_port, - sourceChannel: source_channel, - token: { - denom: token?.denom ?? "", - amount: token?.amount ?? "", + }; + + const ibcAminoConverters: Record< + keyof typeof originalIbcAminoConverters, + { + aminoType: string; + toAmino: (msg: any) => any; + fromAmino: (msg: any) => any; + } + > = { + ...originalIbcAminoConverters, + "/ibc.applications.transfer.v1.MsgTransfer": { + ...originalIbcAminoConverters[ + "/ibc.applications.transfer.v1.MsgTransfer" + ], + // Remove timeout_timestamp as it is not used by our transactions. + toAmino: ({ + sourcePort, + sourceChannel, + token, + sender, + receiver, + timeoutHeight, + }: MsgTransfer) => ({ + source_port: sourcePort, + source_channel: sourceChannel, + token: { + denom: token?.denom, + amount: token?.amount ?? "0", + }, + sender, + receiver, + timeout_height: timeoutHeight + ? { + revision_height: timeoutHeight.revisionHeight?.toString(), + revision_number: timeoutHeight.revisionNumber?.toString(), + } + : {}, + }), + fromAmino: ({ + source_port, + source_channel, + token, + sender, + receiver, + timeout_height, + timeout_timestamp, + }: AminoMsgTransfer["value"]): MsgTransfer => { + return { + sourcePort: source_port, + sourceChannel: source_channel, + token: { + denom: token?.denom ?? "", + amount: token?.amount ?? "", + }, + sender, + receiver, + timeoutHeight: timeout_height + ? { + revisionHeight: Long.fromString( + timeout_height.revision_height || "0", + true + ), + revisionNumber: Long.fromString( + timeout_height.revision_number || "0", + true + ), + } + : undefined, + timeoutTimestamp: Long.fromString(timeout_timestamp ?? "0"), + }; }, - sender, - receiver, - timeoutHeight: timeout_height - ? { - revisionHeight: Long.fromString( - timeout_height.revision_height || "0", - true - ), - revisionNumber: Long.fromString( - timeout_height.revision_number || "0", - true - ), - } - : undefined, - timeoutTimestamp: Long.fromString(timeout_timestamp ?? "0"), - }; - }, - }, -}; + }, + }; + + aminoConverters = { + ...cosmwasmAminoConverters, + ...cosmosAminoConverters, + ...ibcAminoConverters, + ...osmosisAminoConverters, + }; + } -export const aminoConverters = { - ...cosmwasmAminoConverters, - ...cosmosAminoConverters, - ...ibcAminoConverters, - ...osmosisAminoConverters, -}; + return aminoConverters; +} diff --git a/packages/stores/src/account/base.ts b/packages/stores/src/account/base.ts index eff1abd711..ccf5c786fd 100644 --- a/packages/stores/src/account/base.ts +++ b/packages/stores/src/account/base.ts @@ -1,27 +1,12 @@ import type { AssetList as CosmologyAssetList } from "@chain-registry/types"; -import { - AminoMsg, - encodeSecp256k1Pubkey, - encodeSecp256k1Signature, - OfflineAminoSigner, -} from "@cosmjs/amino"; -import { fromBase64 } from "@cosmjs/encoding"; -import { StdFee } from "@cosmjs/launchpad"; -import { Int53 } from "@cosmjs/math"; -import { +import type { OfflineAminoSigner } from "@cosmjs/amino"; +import type { StdFee } from "@cosmjs/launchpad"; +import type { EncodeObject, - encodePubkey, - makeAuthInfoBytes, - makeSignDoc, OfflineDirectSigner, Registry, } from "@cosmjs/proto-signing"; -import { - AminoTypes, - BroadcastTxError, - SignerData, - SigningStargateClient, -} from "@cosmjs/stargate"; +import type { AminoTypes, SignerData } from "@cosmjs/stargate"; import { MainWalletBase, WalletConnectOptions, @@ -41,17 +26,10 @@ import { Functionify, QueriesStore, } from "@osmosis-labs/keplr-stores"; -import { - cosmosProtoRegistry, - cosmwasmProtoRegistry, - ibcProtoRegistry, - osmosis, - osmosisProtoRegistry, -} from "@osmosis-labs/proto-codecs"; -import { TxExtension } from "@osmosis-labs/proto-codecs/build/codegen/osmosis/smartaccount/v1beta1/tx"; import { queryRPCStatus } from "@osmosis-labs/server"; import { encodeAnyBase64, + getOsmosisCodec, QuoteStdFee, SimulateNotAvailableError, TxTracer, @@ -67,8 +45,7 @@ import { } from "@osmosis-labs/utils"; import axios from "axios"; import { Buffer } from "buffer/"; -import { SignMode } from "cosmjs-types/cosmos/tx/signing/v1beta1/signing"; -import { TxRaw } from "cosmjs-types/cosmos/tx/v1beta1/tx"; +import type { TxRaw } from "cosmjs-types/cosmos/tx/v1beta1/tx"; import dayjs from "dayjs"; import { action, autorun, makeObservable, observable, runInAction } from "mobx"; import { fromPromise, IPromiseBasedObservable } from "mobx-utils"; @@ -77,7 +54,7 @@ import { Optional, UnionToIntersection } from "utility-types"; import { makeLocalStorageKVStore } from "../kv-store"; import { OsmosisQueries } from "../queries"; import { InsufficientBalanceForFeeError } from "../ui-config"; -import { aminoConverters } from "./amino-converters"; +import { getAminoConverters } from "./amino-converters"; import { AccountStoreWallet, CosmosRegistryWallet, @@ -146,13 +123,44 @@ export class AccountStore[] = []> { IPromiseBasedObservable >(); - private aminoTypes = new AminoTypes(aminoConverters); - private registry = new Registry([ - ...cosmwasmProtoRegistry, - ...cosmosProtoRegistry, - ...ibcProtoRegistry, - ...osmosisProtoRegistry, - ]); + private _aminoTypes: AminoTypes | null = null; + private _registry: Registry | null = null; + + private async getAminoTypes() { + if (!this._aminoTypes) { + const [{ AminoTypes }, aminoConverters] = await Promise.all([ + import("@cosmjs/stargate"), + getAminoConverters(), + ]); + this._aminoTypes = new AminoTypes(aminoConverters); + } + return this._aminoTypes; + } + + private async getRegistry() { + if (!this._registry) { + const [ + { + cosmosProtoRegistry, + cosmwasmProtoRegistry, + ibcProtoRegistry, + osmosisProtoRegistry, + }, + { Registry }, + ] = await Promise.all([ + import("@osmosis-labs/proto-codecs"), + import("@cosmjs/proto-signing"), + ]); + + this._registry = new Registry([ + ...cosmwasmProtoRegistry, + ...cosmosProtoRegistry, + ...ibcProtoRegistry, + ...osmosisProtoRegistry, + ]); + } + return this._registry; + } /** * We make sure that the 'base' field always has as its value the native chain parameter @@ -230,13 +238,7 @@ export class AccountStore[] = []> { this.walletManagerAssets, "icns", this.options.walletConnectOptions, - { - signingStargate: () => ({ - aminoTypes: this.aminoTypes, - registry: this - .registry as unknown as SigningStargateClient["registry"], - }), - }, + undefined, { endpoints: getWalletEndpoints(this.chains), }, @@ -593,6 +595,7 @@ export class AccountStore[] = []> { messages: msgs, signOptions: mergedSignOptions, }); + const { TxRaw } = await import("cosmjs-types/cosmos/tx/v1beta1/tx"); const encodedTx = TxRaw.encode(txRaw).finish(); const restEndpoint = getEndpointString( @@ -630,6 +633,7 @@ export class AccountStore[] = []> { }); if (broadcasted.code) { + const { BroadcastTxError } = await import("@cosmjs/stargate"); throw new BroadcastTxError(broadcasted.code, "", broadcasted.raw_log); } @@ -818,6 +822,8 @@ export class AccountStore[] = []> { } } + const osmosis = await getOsmosisCodec(); + /** * If the message is an authenticator message, force the direct signing. * This is because the authenticator message should be signed with proto for now. @@ -894,6 +900,24 @@ export class AccountStore[] = []> { memo += " \n1CT"; } + const [ + { encodeSecp256k1Pubkey, encodeSecp256k1Signature }, + { TxExtension }, + { fromBase64 }, + { Int53 }, + { makeAuthInfoBytes, makeSignDoc, encodePubkey }, + { TxRaw }, + ] = await Promise.all([ + import("@cosmjs/amino"), + import( + "@osmosis-labs/proto-codecs/build/codegen/osmosis/smartaccount/v1beta1/tx" + ), + import("@cosmjs/encoding"), + import("@cosmjs/math"), + import("@cosmjs/proto-signing"), + import("cosmjs-types/cosmos/tx/v1beta1/tx"), + ]); + const pubkey = encodePubkey( encodeSecp256k1Pubkey(accountFromSigner.pubkey) ); @@ -907,7 +931,8 @@ export class AccountStore[] = []> { pubkey.typeUrl = pubKeyTypeUrl; - const txBodyBytes = wallet?.signingStargateOptions?.registry?.encodeTxBody({ + const registry = await this.getRegistry(); + const txBodyBytes = registry.encodeTxBody({ messages, memo, nonCriticalExtensionOptions: [ @@ -1011,6 +1036,22 @@ export class AccountStore[] = []> { memo += " \nFE"; } + const [ + { encodeSecp256k1Pubkey }, + { encodePubkey, makeAuthInfoBytes }, + { fromBase64 }, + { Int53 }, + { TxRaw }, + { SignMode }, + ] = await Promise.all([ + import("@cosmjs/amino"), + import("@cosmjs/proto-signing"), + import("@cosmjs/encoding"), + import("@cosmjs/math"), + import("cosmjs-types/cosmos/tx/v1beta1/tx"), + import("cosmjs-types/cosmos/tx/signing/v1beta1/signing"), + ]); + const pubkey = encodePubkey( encodeSecp256k1Pubkey(accountFromSigner.pubkey) ); @@ -1025,18 +1066,19 @@ export class AccountStore[] = []> { pubkey.typeUrl = pubKeyTypeUrl; const signMode = SignMode.SIGN_MODE_LEGACY_AMINO_JSON; + const aminoTypes = await this.getAminoTypes(); const msgs = messages.map((msg) => { - const res: any = wallet?.signingStargateOptions?.aminoTypes?.toAmino(msg); + const res = aminoTypes.toAmino(msg); // Include the 'memo' field again because the 'registry' omits it if (msg.value.memo) { res.value.memo = msg.value.memo; } return res; - }) as AminoMsg[]; + }); const timeoutHeight = await this.getTimeoutHeight(chainId); - const signDoc = makeSignDocAmino( + const signDoc = await makeSignDocAmino( msgs, fee, chainId, @@ -1058,10 +1100,10 @@ export class AccountStore[] = []> { signDoc )); - const signedTxBodyBytes = this.registry?.encodeTxBody({ + const registry = await this.getRegistry(); + const signedTxBodyBytes = registry.encodeTxBody({ messages: signed.msgs.map((msg) => { - const res: any = - wallet?.signingStargateOptions?.aminoTypes?.fromAmino(msg); + const res = aminoTypes.fromAmino(msg); // Include the 'memo' field again because the 'registry' omits it if (msg.value.memo) { res.value.memo = msg.value.memo; @@ -1137,6 +1179,23 @@ export class AccountStore[] = []> { if (!accountFromSigner) { throw new Error("Failed to retrieve account from signer"); } + + const [ + { encodeSecp256k1Pubkey }, + { encodePubkey }, + { fromBase64 }, + { Int53 }, + { makeAuthInfoBytes, makeSignDoc }, + { TxRaw }, + ] = await Promise.all([ + import("@cosmjs/amino"), + import("@cosmjs/proto-signing"), + import("@cosmjs/encoding"), + import("@cosmjs/math"), + import("@cosmjs/proto-signing"), + import("cosmjs-types/cosmos/tx/v1beta1/tx"), + ]); + const pubkey = encodePubkey( encodeSecp256k1Pubkey(accountFromSigner.pubkey) ); @@ -1166,7 +1225,9 @@ export class AccountStore[] = []> { memo: memo, }, }; - const txBodyBytes = this.registry?.encode(txBodyEncodeObject) as Uint8Array; + + const registry = await this.getRegistry(); + const txBodyBytes = registry.encode(txBodyEncodeObject) as Uint8Array; const gasLimit = Int53.fromString(String(fee.gas)).toNumber(); const authInfoBytes = makeAuthInfoBytes( [{ pubkey, sequence }], @@ -1283,7 +1344,8 @@ export class AccountStore[] = []> { if (!wallet.address) throw new Error("No wallet address available."); try { - const encodedMessages = messages.map((m) => this.registry.encodeAsAny(m)); + const registry = await this.getRegistry(); + const encodedMessages = messages.map((m) => registry.encodeAsAny(m)); // check for one click trading tx decoration const shouldBeSignedWithOneClickTrading = @@ -1413,6 +1475,9 @@ export class AccountStore[] = []> { oneClickTradingInfo: OneClickTradingInfo | undefined; }) { if (!oneClickTradingInfo) return undefined; + const { TxExtension } = await import( + "@osmosis-labs/proto-codecs/build/codegen/osmosis/smartaccount/v1beta1/tx" + ); return [ { typeUrl: "/osmosis.smartaccount.v1beta1.TxExtension", diff --git a/packages/stores/src/account/cosmos/index.ts b/packages/stores/src/account/cosmos.ts similarity index 73% rename from packages/stores/src/account/cosmos/index.ts rename to packages/stores/src/account/cosmos.ts index 21237404f2..bdc0affe85 100644 --- a/packages/stores/src/account/cosmos/index.ts +++ b/packages/stores/src/account/cosmos.ts @@ -7,18 +7,15 @@ import { IQueriesStore, txEventsWithPreOnFulfill, } from "@osmosis-labs/keplr-stores"; -import deepmerge from "deepmerge"; -import Long from "long"; -import { DeepPartial } from "utility-types"; +import { makeIBCTransferMsg } from "@osmosis-labs/tx"; import { AccountStore, CosmwasmAccount, DeliverTxResponse, OsmosisAccount, -} from "../../account"; -import { OsmosisQueries } from "../../queries"; -import { cosmosMsgOpts } from "./types"; +} from "../account"; +import { OsmosisQueries } from "../queries"; export interface CosmosAccount { cosmos: CosmosAccountImpl; @@ -26,9 +23,6 @@ export interface CosmosAccount { export const CosmosAccount = { use(options: { - msgOptsCreator?: ( - chainId: string - ) => DeepPartial | undefined; queriesStore: IQueriesStore; }): ( base: AccountStore<[OsmosisAccount, CosmosAccount, CosmwasmAccount]>, @@ -36,20 +30,12 @@ export const CosmosAccount = { chainId: string ) => CosmosAccount { return (base, chainGetter, chainId) => { - const msgOptsFromCreator = options.msgOptsCreator - ? options.msgOptsCreator(chainId) - : undefined; - return { cosmos: new CosmosAccountImpl( base, chainGetter, chainId, - options.queriesStore, - deepmerge>( - cosmosMsgOpts, - msgOptsFromCreator ? msgOptsFromCreator : {} - ) + options.queriesStore ), }; }; @@ -65,8 +51,7 @@ export class CosmosAccountImpl { protected readonly chainId: string, protected readonly queriesStore: IQueriesStore< CosmosQueries & OsmosisQueries - >, - readonly msgOpts: typeof cosmosMsgOpts + > ) {} private get address() { @@ -74,13 +59,14 @@ export class CosmosAccountImpl { } /** - * Send a IBC transfer transaction. + * Send an IBC transfer transaction. * - * @param channel the channel to send the IBC transfer transaction. - * @param amount the amount to send. - * @param currency the currency to send. - * @param recipient the recipient address. - * @param onTxEvents the callback function to handle the transaction events. + * @param channel - The channel information for the IBC transfer. + * @param amount - The amount to send. + * @param currency - The currency to send. + * @param recipient - The recipient address. + * @param onTxEvents - Optional callback function or object to handle transaction events. + * @param memo - Optional memo to include with the transaction. */ async sendIBCTransferMsg( channel: { @@ -129,7 +115,7 @@ export class CosmosAccountImpl { destinationInfo.network ).version.toString(); - const msg = this.msgOpts.ibcTransfer.messageComposer({ + const msg = await makeIBCTransferMsg({ sourcePort: channel.portId, sourceChannel: channel.channelId, token: { @@ -139,14 +125,8 @@ export class CosmosAccountImpl { receiver: recipient, sender: this.address, timeoutHeight: { - /** - * Omit the revision_number if the chain's version is 0. - * Sending the value as 0 will cause the transaction to fail. - */ revisionNumber: - revisionNumber !== "0" - ? Long.fromString(revisionNumber) - : (undefined as any), + revisionNumber !== "0" ? BigInt(revisionNumber) : undefined, revisionHeight: BigInt( destinationInfo.latestBlockHeight.add(new Int("150")).toString() ), @@ -183,5 +163,3 @@ export class CosmosAccountImpl { ); } } - -export * from "./types"; diff --git a/packages/stores/src/account/cosmos/types.ts b/packages/stores/src/account/cosmos/types.ts deleted file mode 100644 index d9f52b4efd..0000000000 --- a/packages/stores/src/account/cosmos/types.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { ibc } from "@osmosis-labs/proto-codecs"; - -import { createMsgOpts } from "../utils"; - -export const cosmosMsgOpts = createMsgOpts({ - ibcTransfer: { - gas: 450000, - messageComposer: - ibc.applications.transfer.v1.MessageComposer.withTypeUrl.transfer, - }, -}); diff --git a/packages/stores/src/account/cosmwasm/index.ts b/packages/stores/src/account/cosmwasm.ts similarity index 70% rename from packages/stores/src/account/cosmwasm/index.ts rename to packages/stores/src/account/cosmwasm.ts index ec9e70e67c..4c951926f0 100644 --- a/packages/stores/src/account/cosmwasm/index.ts +++ b/packages/stores/src/account/cosmwasm.ts @@ -1,21 +1,20 @@ -import { StdFee } from "@cosmjs/amino"; +import type { StdFee } from "@cosmjs/amino"; import { ChainGetter, CoinPrimitive, CosmosQueries, IQueriesStore, } from "@osmosis-labs/keplr-stores"; -import deepmerge from "deepmerge"; -import { DeepPartial, Optional } from "utility-types"; +import { makeExecuteCosmwasmContractMsg } from "@osmosis-labs/tx"; +import { Optional } from "utility-types"; import { AccountStore, CosmosAccount, DeliverTxResponse, OsmosisAccount, -} from "../../account"; -import { OsmosisQueries } from "../../queries"; -import { cosmwasmMsgOpts } from "./types"; +} from "../account"; +import { OsmosisQueries } from "../queries"; export interface CosmwasmAccount { cosmwasm: CosmwasmAccountImpl; @@ -23,9 +22,6 @@ export interface CosmwasmAccount { export const CosmwasmAccount = { use(options: { - msgOptsCreator?: ( - chainId: string - ) => DeepPartial | undefined; queriesStore: IQueriesStore; }): ( base: AccountStore<[OsmosisAccount, CosmosAccount, CosmwasmAccount]>, @@ -33,20 +29,12 @@ export const CosmwasmAccount = { chainId: string ) => CosmwasmAccount { return (base, chainGetter, chainId) => { - const msgOptsFromCreator = options.msgOptsCreator - ? options.msgOptsCreator(chainId) - : undefined; - return { cosmwasm: new CosmwasmAccountImpl( base, chainGetter, chainId, - options.queriesStore, - deepmerge< - typeof cosmwasmMsgOpts, - DeepPartial - >(cosmwasmMsgOpts, msgOptsFromCreator ? msgOptsFromCreator : {}) + options.queriesStore ), }; }; @@ -62,8 +50,7 @@ export class CosmwasmAccountImpl { protected readonly chainId: string, protected readonly queriesStore: IQueriesStore< CosmosQueries & OsmosisQueries - >, - readonly msgOpts: typeof cosmwasmMsgOpts + > ) {} private get address() { @@ -80,7 +67,7 @@ export class CosmwasmAccountImpl { * @param onTxEvents The callback function to be called when the transaction is broadcasted or fulfilled. */ async sendExecuteContractMsg( - type: keyof typeof this.msgOpts | "unknown" = "executeWasm", + type = "executeWasm", contractAddress: string, obj: object, funds: CoinPrimitive[], @@ -92,10 +79,10 @@ export class CosmwasmAccountImpl { onFulfill?: (tx: DeliverTxResponse) => void; } ) { - const msg = this.msgOpts.executeWasm.messageComposer({ + const msg = await makeExecuteCosmwasmContractMsg({ sender: this.address, contract: contractAddress, - msg: Buffer.from(JSON.stringify(obj)), + msg: obj, funds, }); @@ -116,7 +103,7 @@ export class CosmwasmAccountImpl { } async sendMultiExecuteContractMsg( - type: keyof typeof this.msgOpts | "unknown" = "executeWasm", + type = "executeWasm", msgs: { contractAddress: string; msg: object; @@ -130,14 +117,16 @@ export class CosmwasmAccountImpl { onFulfill?: (tx: DeliverTxResponse) => void; } ) { - const mappedMsgs = msgs.map(({ msg, funds, contractAddress }) => { - return this.msgOpts.executeWasm.messageComposer({ - sender: this.address, - contract: contractAddress, - msg: Buffer.from(JSON.stringify(msg)), - funds, - }); - }); + const mappedMsgs = await Promise.all( + msgs.map(async ({ msg, funds, contractAddress }) => { + return await makeExecuteCosmwasmContractMsg({ + sender: this.address, + contract: contractAddress, + msg, + funds, + }); + }) + ); await this.base.signAndBroadcast( this.chainId, @@ -155,5 +144,3 @@ export class CosmwasmAccountImpl { ); } } - -export * from "./types"; diff --git a/packages/stores/src/account/cosmwasm/types.ts b/packages/stores/src/account/cosmwasm/types.ts deleted file mode 100644 index 110ed8a4b3..0000000000 --- a/packages/stores/src/account/cosmwasm/types.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { cosmwasm } from "@osmosis-labs/proto-codecs"; - -import { createMsgOpts } from "../utils"; - -export const cosmwasmMsgOpts = createMsgOpts({ - executeWasm: { - gas: 0, - messageComposer: - cosmwasm.wasm.v1.MessageComposer.withTypeUrl.executeContract, - }, -}); diff --git a/packages/stores/src/account/index.ts b/packages/stores/src/account/index.ts index 892df8bf51..cf5adb45f1 100644 --- a/packages/stores/src/account/index.ts +++ b/packages/stores/src/account/index.ts @@ -2,7 +2,6 @@ export * from "./amino-converters"; export * from "./base"; export * from "./cosmos"; export * from "./cosmwasm"; -export * from "./message-composers"; export * from "./osmosis"; export * from "./types"; export * from "./utils"; diff --git a/packages/stores/src/account/message-composers/index.ts b/packages/stores/src/account/message-composers/index.ts deleted file mode 100644 index 8892a3c7a8..0000000000 --- a/packages/stores/src/account/message-composers/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./osmosis"; diff --git a/packages/stores/src/account/message-composers/osmosis/index.ts b/packages/stores/src/account/message-composers/osmosis/index.ts deleted file mode 100644 index d713672c2c..0000000000 --- a/packages/stores/src/account/message-composers/osmosis/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./authenticator"; -export * from "./poolmanager"; diff --git a/packages/stores/src/account/osmosis/index.ts b/packages/stores/src/account/osmosis/index.ts index 36e9bf5c02..5f0c4c750e 100644 --- a/packages/stores/src/account/osmosis/index.ts +++ b/packages/stores/src/account/osmosis/index.ts @@ -1,5 +1,5 @@ -import { StdFee } from "@cosmjs/amino"; -import { EncodeObject } from "@cosmjs/proto-signing"; +import type { StdFee } from "@cosmjs/amino"; +import type { EncodeObject } from "@cosmjs/proto-signing"; import { AppCurrency, Currency } from "@keplr-wallet/types"; import { Coin, CoinPretty, Dec, DecUtils, Int } from "@keplr-wallet/unit"; import { @@ -9,27 +9,54 @@ import { } from "@osmosis-labs/keplr-stores"; import * as OsmosisMath from "@osmosis-labs/math"; import { maxTick, minTick } from "@osmosis-labs/math"; -import { Duration } from "@osmosis-labs/proto-codecs/build/codegen/google/protobuf/duration"; +import { + makeAddAuthenticatorMsg, + makeAddToConcentratedLiquiditySuperfluidPositionMsg, + makeAddToPositionMsg, + makeBeginUnlockingMsg, + makeCollectIncentivesMsg, + makeCollectSpreadRewardsMsg, + makeCreateBalancerPoolMsg, + makeCreateConcentratedPoolMsg, + makeCreateFullRangePositionAndSuperfluidDelegateMsg, + makeCreatePositionMsg, + makeCreateStableswapPoolMsg, + makeDelegateToValidatorSetMsg, + makeExitPoolMsg, + makeJoinPoolMsg, + makeJoinSwapExternAmountInMsg, + makeLockAndSuperfluidDelegateMsg, + makeLockTokensMsg, + makeRemoveAuthenticatorMsg, + makeSetValidatorSetPreferenceMsg, + makeSplitRoutesSwapExactAmountInMsg, + makeSuperfluidDelegateMsg, + makeSuperfluidUnbondLockMsg, + makeSuperfluidUndelegateMsg, + makeSwapExactAmountInMsg, + makeSwapExactAmountOutMsg, + makeUndelegateFromRebalancedValidatorSetMsg, + makeUndelegateFromValidatorSetMsg, + makeWithdrawDelegationRewardsMsg, + makeWithdrawPositionMsg, +} from "@osmosis-labs/tx"; import { BondStatus } from "@osmosis-labs/types"; -import deepmerge from "deepmerge"; import Long from "long"; -import { DeepPartial } from "utility-types"; import { AccountStore, CosmosAccount, CosmwasmAccount } from "../../account"; import { OsmosisQueries } from "../../queries"; import { QueriesExternalStore } from "../../queries-external"; import { DeliverTxResponse, SignOptions } from "../types"; import { findNewClPositionId } from "./tx-response"; -import { DEFAULT_SLIPPAGE, osmosisMsgOpts } from "./types"; + +const DEFAULT_SLIPPAGE = "2.5"; export interface OsmosisAccount { osmosis: OsmosisAccountImpl; } + export const OsmosisAccount = { use(options: { - msgOptsCreator?: ( - chainId: string - ) => DeepPartial | undefined; queriesStore: IQueriesStore; queriesExternalStore?: QueriesExternalStore; }): ( @@ -38,20 +65,12 @@ export const OsmosisAccount = { chainId: string ) => OsmosisAccount { return (base, chainGetter, chainId) => { - const msgOptsFromCreator = options.msgOptsCreator - ? options.msgOptsCreator(chainId) - : undefined; - return { osmosis: new OsmosisAccountImpl( base, chainGetter, chainId, options.queriesStore, - deepmerge>( - osmosisMsgOpts, - msgOptsFromCreator ? msgOptsFromCreator : {} - ), options.queriesExternalStore ), }; @@ -69,7 +88,6 @@ export class OsmosisAccountImpl { protected readonly queriesStore: IQueriesStore< CosmosQueries & OsmosisQueries >, - readonly msgOpts: typeof osmosisMsgOpts, protected readonly queriesExternalStore?: QueriesExternalStore ) {} @@ -123,7 +141,7 @@ export class OsmosisAccountImpl { }); } - const msg = this.msgOpts.createBalancerPool.messageComposer({ + const msg = await makeCreateBalancerPoolMsg({ futurePoolGovernor: "24h", poolAssets, sender: this.address, @@ -185,7 +203,7 @@ export class OsmosisAccountImpl { memo: string = "", onFulfill?: (tx: DeliverTxResponse) => void ) { - const msg = this.msgOpts.createConcentratedPool.messageComposer({ + const msg = await makeCreateConcentratedPoolMsg({ denom0, denom1, sender: this.address, @@ -291,7 +309,7 @@ export class OsmosisAccountImpl { sortedScalingFactors.push(BigInt(scalingFactor.toString())); }); - const msg = this.msgOpts.createStableswapPool.messageComposer({ + const msg = await makeCreateStableswapPoolMsg({ sender: this.address, futurePoolGovernor: "24h", scalingFactors: sortedScalingFactors, @@ -382,7 +400,7 @@ export class OsmosisAccountImpl { pool.poolAssets, mkp, shareOutAmount, - this.msgOpts.joinPool.shareCoinDecimals + makeJoinPoolMsg.shareCoinDecimals ); const tokenInMaxs = maxSlippageDec.equals(new Dec(0)) @@ -405,13 +423,13 @@ export class OsmosisAccountImpl { }; }); - const msg = this.msgOpts.joinPool.messageComposer({ + const msg = await makeJoinPoolMsg({ poolId: BigInt(poolId), sender: this.address, shareOutAmount: new Dec(shareOutAmount) .mul( DecUtils.getTenExponentNInPrecisionRange( - this.msgOpts.joinPool.shareCoinDecimals + makeJoinPoolMsg.shareCoinDecimals ) ) .truncate() @@ -509,7 +527,7 @@ export class OsmosisAccountImpl { swapFee: pool.swapFee, }, tokenIn, - this.msgOpts.joinPool.shareCoinDecimals + makeJoinSwapExternAmountInMsg.shareCoinDecimals ); const amount = new Dec(tokenIn.amount) @@ -527,7 +545,7 @@ export class OsmosisAccountImpl { .mul(outRatio) .truncate(); - const msg = this.msgOpts.joinSwapExternAmountIn.messageComposer({ + const msg = await makeJoinSwapExternAmountInMsg({ poolId: BigInt(poolId), sender: this.address, tokenIn: { @@ -630,7 +648,7 @@ export class OsmosisAccountImpl { let msg; if (superfluidValidatorAddress) { // send superfluid delegate version (full range only) - msg = this.msgOpts.clCreateSuperfluidPosition.messageComposer({ + msg = await makeCreateFullRangePositionAndSuperfluidDelegateMsg({ valAddr: superfluidValidatorAddress, coins: sortedCoins, poolId: BigInt(poolId), @@ -693,7 +711,7 @@ export class OsmosisAccountImpl { } // create position message with custom price range - msg = this.msgOpts.clCreatePosition.messageComposer({ + msg = await makeCreatePositionMsg({ poolId: BigInt(poolId), lowerTick: BigInt(lowerTick.toString()), upperTick: BigInt(upperTick.toString()), @@ -778,7 +796,7 @@ export class OsmosisAccountImpl { .toString(), })); - const msg = this.msgOpts.clCreatePosition.messageComposer({ + const msg = await makeCreatePositionMsg({ poolId: BigInt(poolId), lowerTick: BigInt(minTick.toString()), upperTick: BigInt(maxTick.toString()), @@ -830,19 +848,17 @@ export class OsmosisAccountImpl { if (!fullLiquidityAmount) throw new Error("No liquidity amount found"); if (!poolId) throw new Error("No pool ID found"); - const withdrawPositionMsg = this.msgOpts.clWithdrawPosition.messageComposer( - { - positionId: BigInt(positionId), - sender: this.address, - liquidityAmount: fullLiquidityAmount.toString(), - } - ); + const withdrawPositionMsg = await makeWithdrawPositionMsg({ + positionId: BigInt(positionId), + sender: this.address, + liquidityAmount: fullLiquidityAmount.toString(), + }); if (!baseAsset || !quoteAsset) throw new Error("No assets found in position"); const createAndSfDelegateMsg = - this.msgOpts.clCreateAndSuperfluidDelegatePosition.messageComposer({ + await makeCreateFullRangePositionAndSuperfluidDelegateMsg({ poolId: BigInt(poolId), coins: [ queryPosition.baseAsset.toCoin(), @@ -916,7 +932,7 @@ export class OsmosisAccountImpl { queryDelegatedPositions.delegatedPositionIds.includes(positionId); const msg = isSuperfluidStaked - ? this.msgOpts.clAddToConcentatedSuperfluidPosition.messageComposer({ + ? await makeAddToConcentratedLiquiditySuperfluidPositionMsg({ positionId: BigInt(positionId), sender: this.address, tokenDesired0: { @@ -928,7 +944,7 @@ export class OsmosisAccountImpl { amount: amount1WithSlippage, }, }) - : this.msgOpts.clAddToConcentratedPosition.messageComposer({ + : await makeAddToPositionMsg({ amount0: coin0.amount, amount1: coin1.amount, positionId: BigInt(positionId), @@ -994,7 +1010,7 @@ export class OsmosisAccountImpl { memo: string = "", onFulfill?: (tx: DeliverTxResponse) => void ) { - const msg = this.msgOpts.clWithdrawPosition.messageComposer({ + const msg = await makeWithdrawPositionMsg({ liquidityAmount: liquidityAmount.toString(), positionId: BigInt(positionId), sender: this.address, @@ -1058,16 +1074,11 @@ export class OsmosisAccountImpl { memo: string = "", onFulfill?: (tx: DeliverTxResponse) => void ) { - // get msgs info, calculate estimated gas amount based on the number of positions - const spreadRewardsMsgOpts = this.msgOpts.clCollectPositionsSpreadRewards; - const incentiveRewardsMsgOpts = - this.msgOpts.clCollectPositionsIncentivesRewards; - - const spreadRewardsMsg = spreadRewardsMsgOpts.messageComposer({ + const spreadRewardsMsg = await makeCollectSpreadRewardsMsg({ positionIds: positionIdsWithSpreadRewards.map((val) => BigInt(val)), sender: this.address, }); - const incentiveRewardsMsg = incentiveRewardsMsgOpts.messageComposer({ + const incentiveRewardsMsg = await makeCollectIncentivesMsg({ positionIds: positionIdsWithIncentiveRewards.map((val) => BigInt(val)), sender: this.address, }); @@ -1141,13 +1152,13 @@ export class OsmosisAccountImpl { }[]; tokenInAmount: string; }[], - tokenIn: { currency: Currency }, + tokenIn: { coinMinimalDenom: string }, tokenOutMinAmount: string, memo: string = "", signOptions?: SignOptions & { fee?: StdFee }, onFulfill?: (tx: DeliverTxResponse) => void ) { - const msg = this.msgOpts.splitRouteSwapExactAmountIn.messageComposer({ + const msg = await makeSplitRoutesSwapExactAmountInMsg({ routes, tokenIn, tokenOutMinAmount, @@ -1169,8 +1180,7 @@ export class OsmosisAccountImpl { .getQueryBech32Address(this.address) .balances.forEach((bal) => { if ( - bal.currency.coinMinimalDenom === - tokenIn.currency.coinMinimalDenom || + bal.currency.coinMinimalDenom === tokenIn.coinMinimalDenom || routes .flatMap(({ pools }) => pools) .find( @@ -1213,13 +1223,13 @@ export class OsmosisAccountImpl { id: string; tokenOutDenom: string; }[], - tokenIn: { currency: Currency; amount: string }, + tokenIn: { coinMinimalDenom: string; amount: string }, tokenOutMinAmount: string, memo: string = "", signOptions?: SignOptions & { fee?: StdFee }, onFulfill?: (tx: DeliverTxResponse) => void ) { - const msg = this.msgOpts.swapExactAmountIn.messageComposer({ + const msg = await makeSwapExactAmountInMsg({ pools, tokenIn, tokenOutMinAmount, @@ -1241,8 +1251,7 @@ export class OsmosisAccountImpl { .getQueryBech32Address(this.address) .balances.forEach((bal) => { if ( - bal.currency.coinMinimalDenom === - tokenIn.currency.coinMinimalDenom || + bal.currency.coinMinimalDenom === tokenIn.coinMinimalDenom || pools.find( (pool) => pool.tokenOutDenom === bal.currency.coinMinimalDenom ) @@ -1297,7 +1306,7 @@ export class OsmosisAccountImpl { .truncate(); const coin = new Coin(tokenOut.currency.coinMinimalDenom, outUAmount); - const msg = this.msgOpts.swapExactAmountOut.messageComposer({ + const msg = await makeSwapExactAmountOutMsg({ sender: this.address, tokenInMaxAmount, tokenOut: { @@ -1378,7 +1387,7 @@ export class OsmosisAccountImpl { }, mkp, shareInAmount, - this.msgOpts.exitPool.shareCoinDecimals + makeExitPoolMsg.shareCoinDecimals ); const maxSlippageDec = new Dec(maxSlippage).quo( @@ -1403,13 +1412,13 @@ export class OsmosisAccountImpl { }; }); - const msg = this.msgOpts.exitPool.messageComposer({ + const msg = await makeExitPoolMsg({ poolId: BigInt(poolId), sender: this.address, shareInAmount: new Dec(shareInAmount) .mul( DecUtils.getTenExponentNInPrecisionRange( - this.msgOpts.exitPool.shareCoinDecimals + makeExitPoolMsg.shareCoinDecimals ) ) .truncate() @@ -1464,7 +1473,11 @@ export class OsmosisAccountImpl { }; }); - const msg = this.msgOpts.lockTokens.messageComposer({ + const { Duration } = await import( + "@osmosis-labs/proto-codecs/build/codegen/google/protobuf/duration" + ); + + const msg = await makeLockTokensMsg({ owner: this.address, coins: primitiveTokens, duration: Duration.fromPartial({ @@ -1512,13 +1525,15 @@ export class OsmosisAccountImpl { ) { if (lockIds.length === 0) throw new Error("No locks to delegate"); - const msgs = lockIds.map((lockId) => { - return this.msgOpts.superfluidDelegate.messageComposer({ - sender: this.address, - lockId: BigInt(lockId), - valAddr: validatorAddress, - }); - }); + const msgs = await Promise.all( + lockIds.map(async (lockId) => { + return await makeSuperfluidDelegateMsg({ + sender: this.address, + lockId: BigInt(lockId), + valAddr: validatorAddress, + }); + }) + ); await this.base.signAndBroadcast( this.chainId, @@ -1575,7 +1590,7 @@ export class OsmosisAccountImpl { }; }); - const msg = this.msgOpts.lockAndSuperfluidDelegate.messageComposer({ + const msg = await makeLockAndSuperfluidDelegateMsg({ sender: this.address, coins: primitiveTokens, valAddr: validatorAddress, @@ -1625,13 +1640,15 @@ export class OsmosisAccountImpl { memo: string = "", onFulfill?: (tx: DeliverTxResponse) => void ) { - const msgs = lockIds.map((lockId) => { - return this.msgOpts.beginUnlocking.messageComposer({ - owner: this.address, - ID: BigInt(lockId), - coins: [], - }); - }); + const msgs = await Promise.all( + lockIds.map(async (lockId) => { + return makeBeginUnlockingMsg({ + owner: this.address, + ID: BigInt(lockId), + coins: [], + }); + }) + ); await this.base.signAndBroadcast( this.chainId, @@ -1676,11 +1693,12 @@ export class OsmosisAccountImpl { memo: string = "", onFulfill?: (tx: DeliverTxResponse) => void ) { - const msgs = locks.reduce((msgs, lock) => { + const msgs = await locks.reduce(async (msgsPromise, lock) => { + const msgs = await msgsPromise; if (!lock.isSynthetic) { // normal unlock msgs.push( - this.msgOpts.beginUnlocking.messageComposer({ + await makeBeginUnlockingMsg({ owner: this.address, ID: BigInt(lock.lockId), coins: [], @@ -1689,18 +1707,18 @@ export class OsmosisAccountImpl { } else { // unbond and unlock msgs.push( - this.msgOpts.superfluidUndelegate.messageComposer({ + await makeSuperfluidUndelegateMsg({ sender: this.address, lockId: BigInt(lock.lockId), }), - this.msgOpts.superfluidUnbondLock.messageComposer({ + await makeSuperfluidUnbondLockMsg({ sender: this.address, lockId: BigInt(lock.lockId), }) ); } return msgs; - }, [] as EncodeObject[]); + }, Promise.resolve([] as EncodeObject[])); await this.base.signAndBroadcast( this.chainId, @@ -1767,7 +1785,7 @@ export class OsmosisAccountImpl { this.chainId, "undelegateFromValidatorSet", [ - this.msgOpts.undelegateFromRebalancedValidatorSet.messageComposer({ + await makeUndelegateFromRebalancedValidatorSetMsg({ delegator: this.address, coin: { denom: coin.denom.coinMinimalDenom, @@ -1817,7 +1835,7 @@ export class OsmosisAccountImpl { this.chainId, "undelegateFromValidatorSet", [ - this.msgOpts.undelegateFromValidatorSet.messageComposer({ + await makeUndelegateFromValidatorSetMsg({ delegator: this.address, coin: { denom: coin.denom.coinMinimalDenom, @@ -1867,7 +1885,7 @@ export class OsmosisAccountImpl { this.chainId, "delegateToValidatorSet", [ - this.msgOpts.delegateToValidatorSet.messageComposer({ + await makeDelegateToValidatorSetMsg({ delegator: this.address, coin: { denom: coin.denom.coinMinimalDenom, @@ -1912,7 +1930,7 @@ export class OsmosisAccountImpl { this.chainId, "withdrawDelegationRewards", [ - this.msgOpts.withdrawDelegationRewards.messageComposer({ + await makeWithdrawDelegationRewardsMsg({ delegator: this.address, }), ], @@ -1962,7 +1980,7 @@ export class OsmosisAccountImpl { this.chainId, "setValidatorSetPreference", [ - this.msgOpts.setValidatorSetPreference.messageComposer({ + await makeSetValidatorSetPreferenceMsg({ delegator: this.address, preferences: validators.map((validator) => ({ weight, @@ -2017,23 +2035,23 @@ export class OsmosisAccountImpl { "Please provide 1 or more validator address to set as preference" ); - const setValidatorSetPreferenceMsg = - this.msgOpts.setValidatorSetPreference.messageComposer({ + const setValidatorSetPreferenceMsg = await makeSetValidatorSetPreferenceMsg( + { delegator: this.address, preferences: validators.map((validator) => ({ weight, valOperAddress: validator, })), - }); + } + ); - const setDelegateToValidatorSetMsg = - this.msgOpts.delegateToValidatorSet.messageComposer({ - delegator: this.address, - coin: { - denom: coin.denom.coinMinimalDenom, - amount: coin.amount, - }, - }); + const setDelegateToValidatorSetMsg = await makeDelegateToValidatorSetMsg({ + delegator: this.address, + coin: { + denom: coin.denom.coinMinimalDenom, + amount: coin.amount, + }, + }); await this.base.signAndBroadcast( this.chainId, @@ -2081,19 +2099,19 @@ export class OsmosisAccountImpl { memo: string = "", onFulfill?: (tx: DeliverTxResponse) => void ) { - const withdrawDelegationRewardsMsg = - this.msgOpts.withdrawDelegationRewards.messageComposer({ + const withdrawDelegationRewardsMsg = await makeWithdrawDelegationRewardsMsg( + { delegator: this.address, - }); + } + ); - const delegateToValidatorSetMsg = - this.msgOpts.delegateToValidatorSet.messageComposer({ - delegator: this.address, - coin: { - denom: coin.denom.coinMinimalDenom, - amount: coin.amount, - }, - }); + const delegateToValidatorSetMsg = await makeDelegateToValidatorSetMsg({ + delegator: this.address, + coin: { + denom: coin.denom.coinMinimalDenom, + amount: coin.amount, + }, + }); await this.base.signAndBroadcast( this.chainId, @@ -2140,19 +2158,22 @@ export class OsmosisAccountImpl { signOptions?: SignOptions; }) { const addAuthenticatorMsgs = addAuthenticators.map((authenticator) => - this.msgOpts.addAuthenticator.messageComposer({ + makeAddAuthenticatorMsg({ type: authenticator.type, data: authenticator.data, sender: this.address, }) ); const removeAuthenticatorMsgs = removeAuthenticators.map((id) => - this.msgOpts.removeAuthenticator.messageComposer({ + makeRemoveAuthenticatorMsg({ id, sender: this.address, }) ); - const msgs = [...removeAuthenticatorMsgs, ...addAuthenticatorMsgs]; + const msgs = await Promise.all([ + ...removeAuthenticatorMsgs, + ...addAuthenticatorMsgs, + ]); await this.base.signAndBroadcast( this.chainId, @@ -2185,17 +2206,20 @@ export class OsmosisAccountImpl { } ); } + async sendAddAuthenticatorsMsg( authenticators: { type: string; data: any }[], memo: string = "", onFulfill?: (tx: DeliverTxResponse) => void ) { - const addAuthenticatorMsgs = authenticators.map((authenticator) => - this.msgOpts.addAuthenticator.messageComposer({ - type: authenticator.type, - data: authenticator.data, - sender: this.address, - }) + const addAuthenticatorMsgs = await Promise.all( + authenticators.map((authenticator) => + makeAddAuthenticatorMsg({ + type: authenticator.type, + data: authenticator.data, + sender: this.address, + }) + ) ); await this.base.signAndBroadcast( @@ -2232,11 +2256,10 @@ export class OsmosisAccountImpl { memo: string = "", onFulfill?: (tx: DeliverTxResponse) => void ) { - const removeAuthenticatorMsg = - this.msgOpts.removeAuthenticator.messageComposer({ - id: id, - sender: this.address, - }); + const removeAuthenticatorMsg = await makeRemoveAuthenticatorMsg({ + id: id, + sender: this.address, + }); await this.base.signAndBroadcast( this.chainId, @@ -2268,7 +2291,6 @@ export class OsmosisAccountImpl { } protected get queries() { - // eslint-disable-next-line return this.queriesStore.get(this.chainId).osmosis!; } @@ -2282,5 +2304,3 @@ export class OsmosisAccountImpl { return new CoinPretty(currency, coin.amount); }; } - -export * from "./types"; diff --git a/packages/stores/src/account/osmosis/types.ts b/packages/stores/src/account/osmosis/types.ts deleted file mode 100644 index 4d45bde30a..0000000000 --- a/packages/stores/src/account/osmosis/types.ts +++ /dev/null @@ -1,166 +0,0 @@ -import { osmosis } from "@osmosis-labs/proto-codecs"; - -import { - makeAddAuthenticatorMsg, - makeRemoveAuthenticatorMsg, - makeSplitRoutesSwapExactAmountInMsg, - makeSwapExactAmountInMsg, -} from "../message-composers"; -import { createMsgOpts } from "../utils"; - -export const osmosisMsgOpts = createMsgOpts({ - createBalancerPool: { - messageComposer: - osmosis.gamm.poolmodels.balancer.v1beta1.MessageComposer.withTypeUrl - .createBalancerPool, - }, - createStableswapPool: { - messageComposer: - osmosis.gamm.poolmodels.stableswap.v1beta1.MessageComposer.withTypeUrl - .createStableswapPool, - }, - createConcentratedPool: { - messageComposer: - osmosis.concentratedliquidity.poolmodel.concentrated.v1beta1 - .MessageComposer.withTypeUrl.createConcentratedPool, - }, - joinPool: { - shareCoinDecimals: 18, - messageComposer: osmosis.gamm.v1beta1.MessageComposer.withTypeUrl.joinPool, - }, - joinSwapExternAmountIn: { - shareCoinDecimals: 18, - messageComposer: - osmosis.gamm.v1beta1.MessageComposer.withTypeUrl.joinSwapExternAmountIn, - }, - exitPool: { - shareCoinDecimals: 18, - messageComposer: osmosis.gamm.v1beta1.MessageComposer.withTypeUrl.exitPool, - }, - splitRouteSwapExactAmountIn: { - messageComposer: makeSplitRoutesSwapExactAmountInMsg, - }, - swapExactAmountIn: { - messageComposer: makeSwapExactAmountInMsg, - }, - swapExactAmountOut: { - messageComposer: - osmosis.poolmanager.v1beta1.MessageComposer.withTypeUrl - .swapExactAmountOut, - }, - lockTokens: { - messageComposer: osmosis.lockup.MessageComposer.withTypeUrl.lockTokens, - }, - superfluidDelegate: { - messageComposer: - osmosis.superfluid.MessageComposer.withTypeUrl.superfluidDelegate, - }, - lockAndSuperfluidDelegate: { - messageComposer: - osmosis.superfluid.MessageComposer.withTypeUrl.lockAndSuperfluidDelegate, - }, - beginUnlocking: { - messageComposer: osmosis.lockup.MessageComposer.withTypeUrl.beginUnlocking, - }, - superfluidUndelegate: { - messageComposer: - osmosis.superfluid.MessageComposer.withTypeUrl.superfluidUndelegate, - }, - superfluidUnbondLock: { - // Gas per msg - messageComposer: - osmosis.superfluid.MessageComposer.withTypeUrl.superfluidUnbondLock, - }, - unlockPeriodLock: { - // Gas per msg - gas: 140000, - }, - unPoolWhitelistedPool: { - messageComposer: - osmosis.superfluid.MessageComposer.withTypeUrl.unPoolWhitelistedPool, - }, - clCreatePosition: { - messageComposer: - osmosis.concentratedliquidity.v1beta1.MessageComposer.withTypeUrl - .createPosition, - gas: 3_000_000, - }, - clCreateSuperfluidPosition: { - messageComposer: - osmosis.superfluid.MessageComposer.withTypeUrl - .createFullRangePositionAndSuperfluidDelegate, - }, - clCollectPositionsSpreadRewards: { - messageComposer: - osmosis.concentratedliquidity.v1beta1.MessageComposer.withTypeUrl - .collectSpreadRewards, - }, - clCollectPositionsIncentivesRewards: { - messageComposer: - osmosis.concentratedliquidity.v1beta1.MessageComposer.withTypeUrl - .collectIncentives, - }, - unlockAndMigrateSharesToFullRangeConcentratedPosition: { - messageComposer: - osmosis.superfluid.MessageComposer.withTypeUrl - .unlockAndMigrateSharesToFullRangeConcentratedPosition, - }, - unbondAndConvertAndStake: { - messageComposer: - osmosis.superfluid.MessageComposer.withTypeUrl.unbondConvertAndStake, - }, - clWithdrawPosition: { - messageComposer: - osmosis.concentratedliquidity.v1beta1.MessageComposer.withTypeUrl - .withdrawPosition, - }, - clAddToConcentratedPosition: { - messageComposer: - osmosis.concentratedliquidity.v1beta1.MessageComposer.withTypeUrl - .addToPosition, - }, - clAddToConcentatedSuperfluidPosition: { - messageComposer: - osmosis.superfluid.MessageComposer.withTypeUrl - .addToConcentratedLiquiditySuperfluidPosition, - }, - clCreateAndSuperfluidDelegatePosition: { - messageComposer: - osmosis.superfluid.MessageComposer.withTypeUrl - .createFullRangePositionAndSuperfluidDelegate, - }, - undelegateFromValidatorSet: { - messageComposer: - osmosis.valsetpref.v1beta1.MessageComposer.withTypeUrl - .undelegateFromValidatorSet, - }, - delegateToValidatorSet: { - gas: 500000, - messageComposer: - osmosis.valsetpref.v1beta1.MessageComposer.withTypeUrl - .delegateToValidatorSet, - }, - withdrawDelegationRewards: { - messageComposer: - osmosis.valsetpref.v1beta1.MessageComposer.withTypeUrl - .withdrawDelegationRewards, - }, - setValidatorSetPreference: { - messageComposer: - osmosis.valsetpref.v1beta1.MessageComposer.withTypeUrl - .setValidatorSetPreference, - }, - undelegateFromRebalancedValidatorSet: { - messageComposer: - osmosis.valsetpref.v1beta1.MessageComposer.withTypeUrl - .undelegateFromRebalancedValidatorSet, - }, - addAuthenticator: { - messageComposer: makeAddAuthenticatorMsg, - }, - removeAuthenticator: { - messageComposer: makeRemoveAuthenticatorMsg, - }, -}); - -export const DEFAULT_SLIPPAGE = "2.5"; diff --git a/packages/stores/src/account/types.ts b/packages/stores/src/account/types.ts index e3c6881305..fe8ea8faee 100644 --- a/packages/stores/src/account/types.ts +++ b/packages/stores/src/account/types.ts @@ -1,4 +1,4 @@ -import { AminoMsg, StdFee } from "@cosmjs/amino"; +import type { AminoMsg, StdFee } from "@cosmjs/amino"; import { ChainWalletBase, SignOptions as CosmoskitSignOptions, @@ -8,7 +8,7 @@ import { OneClickTradingHumanizedSessionPeriod, OneClickTradingTimeLimit, } from "@osmosis-labs/types"; -import { MsgData } from "cosmjs-types/cosmos/base/abci/v1beta1/abci"; +import type { MsgData } from "cosmjs-types/cosmos/base/abci/v1beta1/abci"; import { UnionToIntersection } from "utility-types"; import { WalletConnectionInProgressError } from "./wallet-errors"; diff --git a/packages/stores/src/account/utils.ts b/packages/stores/src/account/utils.ts index 40085bba07..d3823d0187 100644 --- a/packages/stores/src/account/utils.ts +++ b/packages/stores/src/account/utils.ts @@ -1,7 +1,6 @@ import type { Chain } from "@chain-registry/types"; -import { AminoMsg } from "@cosmjs/amino/build/signdoc"; -import { Uint53 } from "@cosmjs/math"; -import { StdFee } from "@cosmjs/stargate"; +import type { AminoMsg } from "@cosmjs/amino/build/signdoc"; +import type { StdFee } from "@cosmjs/stargate"; import { ChainName, Endpoints, @@ -70,7 +69,7 @@ export function changeDecStringToProtoBz(decStr: string): string { } // Creates the document to be signed from given parameters. -export function makeSignDocAmino( +export async function makeSignDocAmino( msgs: readonly AminoMsg[], fee: StdFee, chainId: string, @@ -78,7 +77,8 @@ export function makeSignDocAmino( accountNumber: number | string, sequence: number | string, timeout_height?: bigint -): StdSignDoc { +): Promise { + const { Uint53 } = await import("@cosmjs/math"); return { chain_id: chainId, account_number: Uint53.fromString(accountNumber.toString()).toString(), diff --git a/packages/stores/src/tests/test-env.ts b/packages/stores/src/tests/test-env.ts index 97daa136c4..2c8742990c 100644 --- a/packages/stores/src/tests/test-env.ts +++ b/packages/stores/src/tests/test-env.ts @@ -69,7 +69,6 @@ export class RootStore { OsmosisAccount.use({ queriesStore: this.queriesStore }), CosmosAccount.use({ queriesStore: this.queriesStore, - msgOptsCreator: () => ({ ibcTransfer: { gas: 130000 } }), }), CosmwasmAccount.use({ queriesStore: this.queriesStore }) ); diff --git a/packages/stores/src/tests/test-wallet.ts b/packages/stores/src/tests/test-wallet.ts index e5ea486f23..609afbe5d9 100644 --- a/packages/stores/src/tests/test-wallet.ts +++ b/packages/stores/src/tests/test-wallet.ts @@ -1,6 +1,6 @@ // eslint-disable-next-line import/no-extraneous-dependencies -import { OfflineAminoSigner, StdSignDoc, StdTx } from "@cosmjs/amino"; -import { Algo, OfflineDirectSigner } from "@cosmjs/proto-signing"; +import type { OfflineAminoSigner, StdSignDoc, StdTx } from "@cosmjs/amino"; +import type { Algo, OfflineDirectSigner } from "@cosmjs/proto-signing"; import { BroadcastMode, ChainRecord, diff --git a/packages/stores/src/tx/ibc/ibc-transfer.ts b/packages/stores/src/tx/ibc/ibc-transfer.ts index 5e132db6f9..0cca38cfcf 100644 --- a/packages/stores/src/tx/ibc/ibc-transfer.ts +++ b/packages/stores/src/tx/ibc/ibc-transfer.ts @@ -126,7 +126,7 @@ export async function basicIbcTransfer( }; await cosmwasmAccount?.cosmwasm.sendExecuteContractMsg( - "ibcTransfer" as any, + "ibcTransfer", contractAddress, { send: { diff --git a/packages/stores/src/ui-config/fake-fee-config.ts b/packages/stores/src/ui-config/fake-fee-config.ts index 94e76c33bb..0a82a7d999 100644 --- a/packages/stores/src/ui-config/fake-fee-config.ts +++ b/packages/stores/src/ui-config/fake-fee-config.ts @@ -1,4 +1,4 @@ -import { StdFee } from "@cosmjs/amino"; +import type { StdFee } from "@cosmjs/amino"; import { Currency } from "@keplr-wallet/types"; import { CoinPretty, Dec, Int } from "@keplr-wallet/unit"; import { diff --git a/packages/stores/tsconfig.json b/packages/stores/tsconfig.json index 152bd077b5..7ce7c0983c 100644 --- a/packages/stores/tsconfig.json +++ b/packages/stores/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "build", "declaration": true, "rootDir": "src", - "module": "ES6", + "module": "esnext", "allowJs": true }, "include": ["src/**/*"] diff --git a/packages/trpc/package.json b/packages/trpc/package.json index 427ac3e21c..6e1934461a 100644 --- a/packages/trpc/package.json +++ b/packages/trpc/package.json @@ -36,17 +36,16 @@ "@osmosis-labs/types": "^1.0.0", "@osmosis-labs/utils": "^1.0.0", "@osmosis-labs/server": "^1.0.0", - "@sentry/core": "^7.109.0", - "@sentry/utils": "^7.109.0", "@trpc/client": "^10.45.1", "@trpc/server": "^10.45.1", - "zod": "^3.22.4" + "zod": "^3.22.4", + "@opentelemetry/api": "^1.9.0" }, "devDependencies": { "@types/jest-in-case": "^1.0.6", "jest-in-case": "^1.0.2", "ts-jest": "^29.1.2", - "typescript": "^5.4.5" + "typescript": "5.4.5" }, "lint-staged": { "*": [ diff --git a/packages/trpc/src/__tests_e2e__/swap-router.spec.ts b/packages/trpc/src/__tests_e2e__/swap-router.spec.ts index 3cf0c4cd4c..275f72c263 100644 --- a/packages/trpc/src/__tests_e2e__/swap-router.spec.ts +++ b/packages/trpc/src/__tests_e2e__/swap-router.spec.ts @@ -5,12 +5,7 @@ * functionality and stability. */ import { CoinPretty, Dec, DecUtils, Int, RatePretty } from "@keplr-wallet/unit"; -import { - getAssetPrice, - getCachedPoolMarketMetricsMap, - getPools, - superjson, -} from "@osmosis-labs/server"; +import { getAssetPrice, getPools, superjson } from "@osmosis-labs/server"; import { Asset } from "@osmosis-labs/types"; import { getAssetFromAssetList, @@ -18,7 +13,7 @@ import { makeMinimalAsset, sort, } from "@osmosis-labs/utils"; -import { inferRouterInputs, inferRouterOutputs, initTRPC } from "@trpc/server"; +import { inferRouterOutputs, initTRPC } from "@trpc/server"; import { createInnerTRPCContext, swapRouter } from ".."; import { AssetLists } from "./mock-asset-lists"; @@ -39,10 +34,10 @@ const caller = createCaller({ */ assetLists: AssetLists, chainList: MockChains, + opentelemetryServiceName: undefined, }); type RouterOutputs = inferRouterOutputs; -type RouterInputs = inferRouterInputs; const atomAsset = getAssetFromAssetList({ assetLists: AssetLists, @@ -84,13 +79,11 @@ function assertValidQuote({ tokenIn, tokenOut, tokenInAmount, - router, }: { quote: RouterOutputs["swapRouter"]["routeTokenOutGivenIn"]; tokenInAmount: string; tokenIn: Asset; tokenOut: Asset; - router: RouterInputs["swapRouter"]["routeTokenOutGivenIn"]["preferredRouter"]; }) { // Amount expect(quote.amount).toBeInstanceOf(CoinPretty); @@ -161,12 +154,10 @@ it("Sidecar - ATOM <> OSMO - should return valid quote", async () => { const tokenInAmount = "1000000"; const tokenIn = atomAsset; const tokenOut = osmoAsset; - const preferredRouter = "sidecar"; const reply = await caller.swapRouter.routeTokenOutGivenIn({ tokenInDenom: tokenIn.coinMinimalDenom, tokenInAmount, tokenOutDenom: tokenOut.coinMinimalDenom, - preferredRouter, }); assertValidQuote({ @@ -174,7 +165,6 @@ it("Sidecar - ATOM <> OSMO - should return valid quote", async () => { tokenInAmount, tokenIn: tokenIn.rawAsset, tokenOut: tokenOut.rawAsset, - router: preferredRouter, }); }); @@ -182,12 +172,10 @@ it("Sidecar - OSMO <> ATOM - should return valid quote", async () => { const tokenInAmount = "1000000"; const tokenIn = osmoAsset; const tokenOut = atomAsset; - const preferredRouter = "sidecar"; const reply = await caller.swapRouter.routeTokenOutGivenIn({ tokenInDenom: tokenIn.coinMinimalDenom, tokenInAmount, tokenOutDenom: tokenOut.coinMinimalDenom, - preferredRouter, }); assertValidQuote({ @@ -195,7 +183,6 @@ it("Sidecar - OSMO <> ATOM - should return valid quote", async () => { tokenInAmount, tokenIn: tokenIn.rawAsset, tokenOut: tokenOut.rawAsset, - router: preferredRouter, }); }); @@ -203,12 +190,10 @@ it("Sidecar - USDC <> USDT - should return valid quote. Token in amount differen const tokenInAmount = "1000000"; const tokenIn = usdcAsset; const tokenOut = usdtAsset; - const preferredRouter = "sidecar"; const reply = await caller.swapRouter.routeTokenOutGivenIn({ tokenInDenom: tokenIn.coinMinimalDenom, tokenInAmount, tokenOutDenom: tokenOut.coinMinimalDenom, - preferredRouter, }); assertValidQuote({ @@ -216,7 +201,6 @@ it("Sidecar - USDC <> USDT - should return valid quote. Token in amount differen tokenInAmount, tokenIn: tokenIn.rawAsset, tokenOut: tokenOut.rawAsset, - router: preferredRouter, }); const tokenInAmountDec = new Dec(tokenInAmount).quo( @@ -235,12 +219,10 @@ it("Sidecar - USDT <> USDC - should return valid quote. Token in amount differen const tokenInAmount = "1000000"; const tokenIn = usdtAsset; const tokenOut = usdcAsset; - const preferredRouter = "sidecar"; const reply = await caller.swapRouter.routeTokenOutGivenIn({ tokenInDenom: tokenIn.coinMinimalDenom, tokenInAmount, tokenOutDenom: tokenOut.coinMinimalDenom, - preferredRouter, }); assertValidQuote({ @@ -248,7 +230,6 @@ it("Sidecar - USDT <> USDC - should return valid quote. Token in amount differen tokenInAmount, tokenIn: tokenIn.rawAsset, tokenOut: tokenOut.rawAsset, - router: preferredRouter, }); const tokenInAmountDec = new Dec(tokenInAmount).quo( @@ -267,12 +248,10 @@ it("Sidecar - OSMO <> USK - should return valid quote even if the token price is const tokenInAmount = "1000000"; const tokenIn = osmoAsset; const tokenOut = uskAsset; - const preferredRouter = "sidecar"; const reply = await caller.swapRouter.routeTokenOutGivenIn({ tokenInDenom: tokenIn.coinMinimalDenom, tokenInAmount, tokenOutDenom: tokenOut.coinMinimalDenom, - preferredRouter, }); assertValidQuote({ @@ -280,7 +259,6 @@ it("Sidecar - OSMO <> USK - should return valid quote even if the token price is tokenInAmount, tokenIn: tokenIn.rawAsset, tokenOut: tokenOut.rawAsset, - router: preferredRouter, }); }); @@ -288,12 +266,10 @@ it("Sidecar — USDC.axl <> USDC — Should return valid quote for possible allo const tokenInAmount = "1000000"; const tokenIn = usdcAxelarAsset; const tokenOut = usdcAsset; - const preferredRouter = "sidecar"; const reply = await caller.swapRouter.routeTokenOutGivenIn({ tokenInDenom: tokenIn.coinMinimalDenom, tokenInAmount, tokenOutDenom: tokenOut.coinMinimalDenom, - preferredRouter, }); assertValidQuote({ @@ -301,7 +277,6 @@ it("Sidecar — USDC.axl <> USDC — Should return valid quote for possible allo tokenInAmount, tokenIn: tokenIn.rawAsset, tokenOut: tokenOut.rawAsset, - router: preferredRouter, }); const amountDec: Dec = reply.amount.toDec(); @@ -324,12 +299,10 @@ it.skip("Sidecar — ASTRO <> OSMO — Should return valid quote for PCL pool", const tokenInAmount = "1000000"; const tokenIn = astroAsset; const tokenOut = osmoAsset; - const preferredRouter = "sidecar"; const reply = await caller.swapRouter.routeTokenOutGivenIn({ tokenInDenom: tokenIn.coinMinimalDenom, tokenInAmount, tokenOutDenom: tokenOut.coinMinimalDenom, - preferredRouter, }); assertValidQuote({ @@ -337,7 +310,6 @@ it.skip("Sidecar — ASTRO <> OSMO — Should return valid quote for PCL pool", tokenInAmount, tokenIn: tokenIn.rawAsset, tokenOut: tokenOut.rawAsset, - router: preferredRouter, }); let pclPool: @@ -355,90 +327,6 @@ it.skip("Sidecar — ASTRO <> OSMO — Should return valid quote for PCL pool", expect(pclPool!.outCurrency).toEqual(makeMinimalAsset(osmoAsset.rawAsset)); }); -it.skip("TFM - ATOM <> OSMO - should return valid partial quote (no swap fee)", async () => { - const tokenInAmount = "1000000"; - const tokenIn = atomAsset; - const tokenOut = osmoAsset; - const preferredRouter = "tfm"; - const reply = await caller.swapRouter.routeTokenOutGivenIn({ - tokenInDenom: tokenIn.coinMinimalDenom, - tokenInAmount, - tokenOutDenom: tokenOut.coinMinimalDenom, - preferredRouter, - }); - - // Amount - expect(reply.amount).toBeInstanceOf(CoinPretty); - expect(reply.amount.currency).toEqual(makeMinimalAsset(tokenOut.rawAsset)); - - const amount = reply.amount.toDec().toString(); - expect(isNumeric(amount)).toBeTruthy(); - // Make sure amount is not negative - expect(parseFloat(amount)).toBeGreaterThan(0); - - // Swap fee - // expect(reply.swapFee).toBeInstanceOf(RatePretty); - // // Should match with the format of "0.1%" - // expect(reply.swapFee?.toString()).toMatch(percentageRegex); - - // const swapFee = reply.swapFee!.toDec().toString(); - // expect(isNumeric(swapFee)).toBeTruthy(); - // expect(parseFloat(swapFee)).toBeGreaterThan(0); - - // Price impact token out - expect(reply.priceImpactTokenOut).toBeInstanceOf(RatePretty); - // Should match with the format of "0.1%" - expect(reply.priceImpactTokenOut?.toString()).toMatch(percentageRegex); - - const priceImpactTokenOut = reply.priceImpactTokenOut!.toDec().toString(); - expect(isNumeric(priceImpactTokenOut)).toBeTruthy(); - expect(parseFloat(priceImpactTokenOut)).toBeGreaterThan(0); - - // Token in fee amount - // expect(reply.tokenInFeeAmount).toBeInstanceOf(Int); - // const tokenInFeeAmount = reply.tokenInFeeAmount!.toString(); - // expect(isNumeric(tokenInFeeAmount)).toBeTruthy(); - // expect(parseFloat(tokenInFeeAmount)).toBeGreaterThan(0); - - // Split - expect(Array.isArray(reply.split)).toBeTruthy(); - expect(reply.split.length).toBeGreaterThan(0); - - for (const split of reply.split) { - expect(split.initialAmount).toBeInstanceOf(Int); - - expect(Array.isArray(split.pools)).toBeTruthy(); - expect(split.pools.length).toBeGreaterThan(0); - - expect(split.tokenInDenom).toBe(tokenIn.coinMinimalDenom); - - expect(Array.isArray(split.tokenOutDenoms)).toBeTruthy(); - expect(split.tokenOutDenoms.length).toBeGreaterThan(0); - } - - // Sum of all split amount should equal token in amount - const splitAmountSum = reply.split.reduce( - (acc, split) => acc.add(split.initialAmount), - new Int(0) - ); - expect(splitAmountSum.equals(new Int(tokenInAmount))).toBeTruthy(); - - // name - expect(reply.name).toBe("tfm"); - - // timeMs - expect(isNumeric(reply.timeMs)).toBeTruthy(); - - // Token in fee amount fiat value - // expect(reply.tokenInFeeAmountFiatValue).toBeInstanceOf(PricePretty); - - // const tokenInFeeAmountFiatValue = reply - // .tokenInFeeAmountFiatValue!.toDec() - // .toString(); - // expect(isNumeric(tokenInFeeAmountFiatValue)).toBeTruthy(); - // expect(parseFloat(tokenInFeeAmountFiatValue)).toBeGreaterThan(0); -}); - /** * Retrieves and sorts liquidity pools by their 24-hour USD volume from the indexer, * calculates the total volume and the average volume across all pools. @@ -452,32 +340,20 @@ it.skip("TFM - ATOM <> OSMO - should return valid partial quote (no swap fee)", * - Low volume tokens have a 24-hour USD volume less than or equal to 40% of average volume */ async function getSortedPoolsWithVolume() { - const [pools, marketMetrics] = await Promise.all([ + const [pools] = await Promise.all([ getPools({ assetLists: AssetLists, chainList: MockChains, }), - getCachedPoolMarketMetricsMap(), ]); let totalVolume = new Dec(0); - const poolsWithVolume = pools - .map((pool) => { - const metricsForPool = marketMetrics.get(pool.id); - if (!metricsForPool) return undefined; - - const volume24hUsdDec = - metricsForPool.volume24hUsd?.toDec() ?? new Dec(0); - totalVolume = totalVolume.add(volume24hUsdDec); - return { - ...pool, - volume24hUsdDec, - }; - }) - .filter((pool): pool is NonNullable => !!pool); + pools.forEach((pool: any) => { + totalVolume = totalVolume.add(pool.volume24hUsdDec); + }); - const sortedPoolsWithVolume = sort(poolsWithVolume, "volume24hUsdDec"); + const sortedPoolsWithVolume = sort(pools, "volume24hUsdDec"); const averageVolume = totalVolume.quo(new Dec(sortedPoolsWithVolume.length)); @@ -489,7 +365,7 @@ it("Sidecar — Should return valid quote for medium volume token", async () => await getSortedPoolsWithVolume(); const mediumVolumePool = sortedPoolsWithVolume.find( - (pool) => + (pool: any) => pool.volume24hUsdDec.lte(averageVolume) && pool.reserveCoins.length === 2 )!; @@ -518,12 +394,10 @@ it("Sidecar — Should return valid quote for medium volume token", async () => .truncate() .toString(); - const preferredRouter = "sidecar"; const reply = await caller.swapRouter.routeTokenOutGivenIn({ tokenInDenom: tokenIn.currency.coinMinimalDenom, tokenInAmount, tokenOutDenom: tokenOut.currency.coinMinimalDenom, - preferredRouter, forcePoolId: mediumVolumePool.id, }); @@ -538,7 +412,6 @@ it("Sidecar — Should return valid quote for medium volume token", async () => coinMinimalDenom: tokenOut.currency.coinMinimalDenom, assetLists: AssetLists, })!.rawAsset, - router: preferredRouter, }); }); @@ -547,7 +420,7 @@ it("Sidecar — Should return valid quote for low volume token", async () => { await getSortedPoolsWithVolume(); const lowVolumeTokenPool = sortedPoolsWithVolume.find( - (pool) => + (pool: any) => // Find a token that less than or equal to 40% of the average volume pool.volume24hUsdDec.lte(averageVolume.mul(new Dec(0.4))) && pool.reserveCoins.length === 2 @@ -578,12 +451,10 @@ it("Sidecar — Should return valid quote for low volume token", async () => { .truncate() .toString(); - const preferredRouter = "sidecar"; const reply = await caller.swapRouter.routeTokenOutGivenIn({ tokenInDenom: tokenIn.currency.coinMinimalDenom, tokenInAmount, tokenOutDenom: tokenOut.currency.coinMinimalDenom, - preferredRouter, forcePoolId: lowVolumeTokenPool.id, }); @@ -595,6 +466,5 @@ it("Sidecar — Should return valid quote for low volume token", async () => { coinMinimalDenom: tokenOut.currency.coinMinimalDenom, assetLists: AssetLists, })!.rawAsset, - router: preferredRouter, }); }); diff --git a/packages/trpc/src/api.ts b/packages/trpc/src/api.ts index d422a7f359..3f5e8fee81 100644 --- a/packages/trpc/src/api.ts +++ b/packages/trpc/src/api.ts @@ -1,3 +1,4 @@ +import { SpanStatusCode, trace } from "@opentelemetry/api"; import { superjson } from "@osmosis-labs/server"; import { AssetList, Chain } from "@osmosis-labs/types"; import { timeout } from "@osmosis-labs/utils"; @@ -13,14 +14,13 @@ import { type AnyRouter, initTRPC } from "@trpc/server"; import { observable } from "@trpc/server/observable"; import { ZodError } from "zod"; -import { trpcMiddleware } from "./middleware"; - /** * Pass asset lists and chain list to be used cas context in backend service. */ type CreateContextOptions = { assetLists: AssetList[]; chainList: Chain[]; + opentelemetryServiceName: string | undefined; }; /** @@ -81,11 +81,47 @@ export const createTRPCRouter = t.router; * are logged in. */ export const publicProcedure = t.procedure - .use( - trpcMiddleware({ - attachRpcInput: true, - }) - ) + /** + * Opentelemetry tRPC middleware that names the handling transaction after the called procedure. + */ + .use(async ({ path, rawInput, type, next, ctx }) => { + const serviceName = + ctx.opentelemetryServiceName ?? "fallback-osmosis-frontend-service-name"; + const tracer = trace.getTracer(serviceName); + + return tracer.startActiveSpan(`trpc/${path}`, async (span) => { + try { + span.setAttribute("procedure_type", type); + span.setAttribute("input", JSON.stringify(rawInput)); + + const result = await next(); + + if ( + typeof result === "object" && + result !== null && + "ok" in result && + !result.ok + ) { + span.setStatus({ code: SpanStatusCode.ERROR }); + if ("error" in result && result.error instanceof Error) { + span.recordException(result.error); + } + } else { + span.setStatus({ code: SpanStatusCode.OK }); + } + + return result; + } catch (e) { + if (e instanceof Error) { + span.recordException(e); + span.setStatus({ code: SpanStatusCode.ERROR, message: e.message }); + } + throw e; + } finally { + span.end(); + } + }); + }) .use(async (opts) => { /** * Default timeout for all procedures @@ -105,10 +141,12 @@ export function localLink({ router, assetLists, chainList, + opentelemetryServiceName, }: { router: TRouter; assetLists: AssetList[]; chainList: Chain[]; + opentelemetryServiceName: string | undefined; }): TRPCLink { return () => ({ op }) => @@ -119,6 +157,7 @@ export function localLink({ const caller = createCaller({ assetLists, chainList, + opentelemetryServiceName, }); try { // Attempt to execute the operation using the router's caller. diff --git a/packages/trpc/src/index.ts b/packages/trpc/src/index.ts index 9d4cc1a8b0..cf574164e4 100644 --- a/packages/trpc/src/index.ts +++ b/packages/trpc/src/index.ts @@ -5,7 +5,6 @@ export * from "./chains"; export * from "./cms"; export * from "./concentrated-liquidity"; export * from "./earn"; -export * from "./middleware"; export * from "./one-click-trading"; export * from "./orderbook-router"; export * from "./parameter-types"; diff --git a/packages/trpc/src/middleware.ts b/packages/trpc/src/middleware.ts deleted file mode 100644 index cf0ba00874..0000000000 --- a/packages/trpc/src/middleware.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { - captureException, - getClient, - SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, - SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, - setContext, - startSpanManual, -} from "@sentry/core"; -// eslint-disable-next-line import/no-extraneous-dependencies -import { isThenable, normalize } from "@sentry/utils"; - -interface SentryTrpcMiddlewareOptions { - /** Whether to include procedure inputs in reported events. Defaults to `false`. */ - attachRpcInput?: boolean; -} - -interface TrpcMiddlewareArguments { - path: string; - type: string; - next: () => T; - rawInput: unknown; -} - -const trpcCaptureContext = { - mechanism: { handled: false, data: { function: "trpcMiddleware" } }, -}; - -/** - * Sentry tRPC middleware that names the handling transaction after the called procedure. - * - * Use the Sentry tRPC middleware in combination with the Sentry server integration, - * e.g. Express Request Handlers or Next.js SDK. - */ -export function trpcMiddleware(options: SentryTrpcMiddlewareOptions = {}) { - return function ({ - path, - type, - next, - rawInput, - }: TrpcMiddlewareArguments): T { - const client = getClient(); - const clientOptions = client?.getOptions(); - - const trpcContext: Record = { - procedure_type: type, - }; - - if ( - options.attachRpcInput !== undefined - ? options.attachRpcInput - : clientOptions && clientOptions.sendDefaultPii - ) { - trpcContext.input = normalize(rawInput); - } - - setContext("trpc", trpcContext); - - function captureIfError(nextResult: unknown): void { - // TODO: Set span status based on what TRPCError was encountered - if ( - typeof nextResult === "object" && - nextResult !== null && - "ok" in nextResult && - !nextResult.ok && - "error" in nextResult - ) { - captureException(nextResult.error, trpcCaptureContext); - } - } - - return startSpanManual( - { - name: `trpc/${path}`, - op: "rpc.server", - forceTransaction: true, - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: "route", - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: "auto.rpc.trpc", - }, - }, - (span) => { - let maybePromiseResult; - try { - maybePromiseResult = next(); - } catch (e) { - captureException(e, trpcCaptureContext); - span?.end(); - throw e; - } - - if (isThenable(maybePromiseResult)) { - return maybePromiseResult.then( - (nextResult) => { - captureIfError(nextResult); - span?.end(); - return nextResult; - }, - (e) => { - captureException(e, trpcCaptureContext); - span?.end(); - throw e; - } - ) as T; - } else { - captureIfError(maybePromiseResult); - span?.end(); - return maybePromiseResult; - } - } - ); - }; -} diff --git a/packages/trpc/src/pools.ts b/packages/trpc/src/pools.ts index bbce0120d6..2fd98dec99 100644 --- a/packages/trpc/src/pools.ts +++ b/packages/trpc/src/pools.ts @@ -2,7 +2,6 @@ import { createSortSchema, CursorPaginationSchema, getCachedPoolIncentivesMap, - getCachedPoolMarketMetricsMap, getCachedTransmuterTotalPoolLiquidity, getPool, getPools, @@ -12,7 +11,6 @@ import { getUserPools, getUserSharePools, IncentivePoolFilterSchema, - isIncentivePoolFiltered, maybeCachePaginatedItems, PoolFilterSchema, } from "@osmosis-labs/server"; @@ -28,11 +26,11 @@ const GetInfinitePoolsSchema = CursorPaginationSchema.and(PoolFilterSchema).and( const marketIncentivePoolsSortKeys = [ "totalFiatValueLocked", - "feesSpent7dUsd", - "feesSpent24hUsd", - "volume7dUsd", - "volume24hUsd", - "aprBreakdown.total.upper", + "market.feesSpent7dUsd", + "market.feesSpent24hUsd", + "market.volume7dUsd", + "market.volume24hUsd", + "incentives.aprBreakdown.total.upper", ] as const; export type MarketIncentivePoolSortKey = (typeof marketIncentivePoolsSortKeys)[number]; @@ -84,7 +82,7 @@ export const poolsRouter = createTRPCRouter({ bech32Address: userOsmoAddress, }) ), - getMarketIncentivePools: publicProcedure + getPools: publicProcedure .input( GetInfinitePoolsSchema.and( z.object({ @@ -110,52 +108,23 @@ export const poolsRouter = createTRPCRouter({ }) => maybeCachePaginatedItems({ getFreshItems: async () => { - const [pools, incentives, marketMetrics] = await Promise.all([ - getPools({ - ...ctx, - search, - minLiquidityUsd, - types, - denoms, - }), - getCachedPoolIncentivesMap(), - getCachedPoolMarketMetricsMap(), - ]); + const pools = await getPools({ + ...ctx, + search, + minLiquidityUsd, + types, + denoms, + }); - const marketIncentivePools = pools - .map((pool) => { - const incentivesForPool = incentives.get(pool.id); - const metricsForPool = marketMetrics.get(pool.id) ?? {}; - - const isIncentiveFiltered = - incentivesForPool && - isIncentivePoolFiltered(incentivesForPool, { - incentiveTypes, - }); - - if (isIncentiveFiltered) return; - - return { - ...pool, - ...incentivesForPool, - ...metricsForPool, - }; - }) - .filter((pool): pool is NonNullable => !!pool); - - if (search) return marketIncentivePools; - else - return sort( - marketIncentivePools, - sortInput.keyPath, - sortInput.direction - ); + if (search) return pools; + else return sort(pools, sortInput.keyPath, sortInput.direction); }, cacheKey: JSON.stringify({ search, sortInput, minLiquidityUsd, types, + denoms, incentiveTypes, }), cursor, @@ -165,11 +134,6 @@ export const poolsRouter = createTRPCRouter({ getSuperfluidPoolIds: publicProcedure.query(({ ctx }) => getSuperfluidPoolIds(ctx) ), - getPoolMarketMetrics: publicProcedure - .input(z.object({ poolId: z.string() })) - .query(({ input: { poolId } }) => - getCachedPoolMarketMetricsMap().then((map) => map.get(poolId) ?? null) - ), getPoolIncentives: publicProcedure .input(z.object({ poolId: z.string() })) .query(({ input: { poolId } }) => diff --git a/packages/trpc/src/swap.ts b/packages/trpc/src/swap.ts index 34f53ba8f0..8db5c7bcd8 100644 --- a/packages/trpc/src/swap.ts +++ b/packages/trpc/src/swap.ts @@ -1,11 +1,10 @@ import { CoinPretty, Int, RatePretty } from "@keplr-wallet/unit"; import type { SplitTokenInQuote } from "@osmosis-labs/pools"; import { - availableRoutersSchema, captureIfError, getAsset, getCosmwasmPoolTypeFromCodeId, - getRouters, + getSidecarRouter, Pool, } from "@osmosis-labs/server"; import { AssetList } from "@osmosis-labs/types"; @@ -20,32 +19,15 @@ export const swapRouter = createTRPCRouter({ tokenInDenom: z.string(), tokenInAmount: z.string(), tokenOutDenom: z.string(), - preferredRouter: availableRoutersSchema, forcePoolId: z.string().optional(), }) ) .query( async ({ - input: { - tokenInDenom, - tokenInAmount, - tokenOutDenom, - preferredRouter, - forcePoolId, - }, + input: { tokenInDenom, tokenInAmount, tokenOutDenom, forcePoolId }, ctx, }) => { - const osmosisChainId = ctx.chainList[0].chain_id; - - const routers = getRouters( - osmosisChainId, - ctx.assetLists, - ctx.chainList - ); - - const { name, router } = routers.find( - ({ name }) => name === preferredRouter - )!; + const router = getSidecarRouter(); // send to router const startTime = Date.now(); diff --git a/packages/tx/package.json b/packages/tx/package.json index 7b390abd37..040e3afb0a 100644 --- a/packages/tx/package.json +++ b/packages/tx/package.json @@ -42,7 +42,7 @@ "@types/jest-in-case": "^1.0.6", "jest-in-case": "^1.0.2", "ts-jest": "^29.1.2", - "typescript": "^5.4.5" + "typescript": "5.4.5" }, "lint-staged": { "*": [ diff --git a/packages/tx/src/__tests__/gas.spec.ts b/packages/tx/src/__tests__/gas.spec.ts index fd3bd9c933..a6b4ac8dbc 100644 --- a/packages/tx/src/__tests__/gas.spec.ts +++ b/packages/tx/src/__tests__/gas.spec.ts @@ -10,7 +10,7 @@ import { sendTxSimulate, } from "@osmosis-labs/server"; import { ApiClientError } from "@osmosis-labs/utils"; -import { Any } from "cosmjs-types/google/protobuf/any"; +import type { Any } from "cosmjs-types/google/protobuf/any"; import { getDefaultGasPrice, diff --git a/packages/tx/src/codec.ts b/packages/tx/src/codec.ts index 401b5d4bda..7fb2020edf 100644 --- a/packages/tx/src/codec.ts +++ b/packages/tx/src/codec.ts @@ -1,5 +1,5 @@ import { Buffer } from "buffer/"; -import { Any } from "cosmjs-types/google/protobuf/any"; +import type { Any } from "cosmjs-types/google/protobuf/any"; export function encodeAnyBase64({ typeUrl, value }: Any): { typeUrl: string; @@ -25,3 +25,15 @@ export function decodeAnyBase64({ value: Buffer.from(value, "base64"), }; } + +export async function getOsmosisCodec() { + return import("@osmosis-labs/proto-codecs").then((module) => module.osmosis); +} + +export async function getCosmwasmCodec() { + return import("@osmosis-labs/proto-codecs").then((module) => module.cosmwasm); +} + +export async function getIbcCodec() { + return import("@osmosis-labs/proto-codecs").then((module) => module.ibc); +} diff --git a/packages/tx/src/gas.ts b/packages/tx/src/gas.ts index 8b85cd29e8..e3e755217e 100644 --- a/packages/tx/src/gas.ts +++ b/packages/tx/src/gas.ts @@ -16,14 +16,7 @@ import type { Chain } from "@osmosis-labs/types"; import { ApiClientError } from "@osmosis-labs/utils"; import { Buffer } from "buffer/"; import cachified, { CacheEntry } from "cachified"; -import { SignMode } from "cosmjs-types/cosmos/tx/signing/v1beta1/signing"; -import { - AuthInfo, - Fee, - SignerInfo, - TxBody, - TxRaw, -} from "cosmjs-types/cosmos/tx/v1beta1/tx"; +import type { TxBody } from "cosmjs-types/cosmos/tx/v1beta1/tx"; import { LRUCache } from "lru-cache"; import { getSumTotalSpenderCoinsSpent } from "./events"; @@ -173,6 +166,11 @@ export async function generateCosmosUnsignedTx({ }); const sequence: number = parseSequenceFromAccount(account); + const [{ SignMode }, { TxBody, TxRaw, AuthInfo, SignerInfo, Fee }] = + await Promise.all([ + import("cosmjs-types/cosmos/tx/signing/v1beta1/signing"), + import("cosmjs-types/cosmos/tx/v1beta1/tx"), + ]); // create placeholder transaction document const rawUnsignedTx = TxRaw.encode({ diff --git a/packages/tx/src/index.ts b/packages/tx/src/index.ts index 18c8619fbd..f5ece960ec 100644 --- a/packages/tx/src/index.ts +++ b/packages/tx/src/index.ts @@ -1,6 +1,7 @@ export * from "./codec"; export * from "./error"; +export * from "./events"; export * from "./gas"; -export * from "./msg"; +export * from "./message-composers"; export * from "./poll-status"; export * from "./tracer"; diff --git a/packages/tx/src/message-composers/cosmos.ts b/packages/tx/src/message-composers/cosmos.ts new file mode 100644 index 0000000000..da0588937b --- /dev/null +++ b/packages/tx/src/message-composers/cosmos.ts @@ -0,0 +1,57 @@ +import type { Coin } from "@osmosis-labs/proto-codecs/build/codegen/cosmos/base/v1beta1/coin"; +import type { Height } from "@osmosis-labs/proto-codecs/build/codegen/ibc/core/client/v1/client"; + +import { getIbcCodec } from "../codec"; + +export async function makeIBCTransferMsg({ + sourcePort, + sourceChannel, + token, + receiver, + sender, + timeoutHeight, + timeoutTimestamp, + memo, +}: { + /** the port on which the packet will be sent */ + sourcePort: string; + /** the channel by which the packet will be sent */ + sourceChannel: string; + /** the tokens to be transferred */ + token: Coin; + /** the sender address */ + sender: string; + /** the recipient address on the destination chain */ + receiver: string; + /** + * Timeout height relative to the current block height. + * The timeout is disabled when set to 0. + */ + timeoutHeight: Partial; + /** + * Timeout timestamp in absolute nanoseconds since unix epoch. + * The timeout is disabled when set to 0. + */ + timeoutTimestamp: bigint; + /** optional memo */ + memo: string; +}) { + const ibcCodec = await getIbcCodec(); + return ibcCodec.applications.transfer.v1.MessageComposer.withTypeUrl.transfer( + { + sourcePort, + sourceChannel, + token, + receiver, + sender, + // Revision number can be undefined, but our proto makes it required + // so we need to cast it to Partial to make it optional + // @ts-expect-error + timeoutHeight, + timeoutTimestamp, + memo, + } + ); +} + +makeIBCTransferMsg.gas = 250_000; diff --git a/packages/tx/src/message-composers/cosmwasm.ts b/packages/tx/src/message-composers/cosmwasm.ts new file mode 100644 index 0000000000..b56388bf1e --- /dev/null +++ b/packages/tx/src/message-composers/cosmwasm.ts @@ -0,0 +1,27 @@ +import type { Coin } from "@osmosis-labs/proto-codecs/build/codegen/cosmos/base/v1beta1/coin"; + +import { getCosmwasmCodec } from "../codec"; + +export async function makeExecuteCosmwasmContractMsg< + Obj extends Record +>({ + sender, + contract, + msg, + funds, +}: { + sender: string; + contract: string; + msg: Obj; + funds: Coin[]; +}) { + const cosmwasm = await getCosmwasmCodec(); + return cosmwasm.wasm.v1.MessageComposer.withTypeUrl.executeContract({ + sender, + contract, + msg: Buffer.from(JSON.stringify(msg)), + funds, + }); +} + +makeExecuteCosmwasmContractMsg.gas = 0; diff --git a/packages/tx/src/message-composers/index.ts b/packages/tx/src/message-composers/index.ts new file mode 100644 index 0000000000..d66b00e986 --- /dev/null +++ b/packages/tx/src/message-composers/index.ts @@ -0,0 +1,3 @@ +export * from "./cosmos"; +export * from "./cosmwasm"; +export * from "./osmosis"; diff --git a/packages/stores/src/account/message-composers/osmosis/authenticator.ts b/packages/tx/src/message-composers/osmosis/authenticator.ts similarity index 64% rename from packages/stores/src/account/message-composers/osmosis/authenticator.ts rename to packages/tx/src/message-composers/osmosis/authenticator.ts index f11db45e53..e620fa6156 100644 --- a/packages/stores/src/account/message-composers/osmosis/authenticator.ts +++ b/packages/tx/src/message-composers/osmosis/authenticator.ts @@ -1,6 +1,6 @@ -import { osmosis } from "@osmosis-labs/proto-codecs"; +import { getOsmosisCodec } from "../../codec"; -export function makeAddAuthenticatorMsg({ +export async function makeAddAuthenticatorMsg({ type, data, sender, @@ -9,6 +9,7 @@ export function makeAddAuthenticatorMsg({ data: Uint8Array; sender: string; }) { + const osmosis = await getOsmosisCodec(); return osmosis.smartaccount.v1beta1.MessageComposer.withTypeUrl.addAuthenticator( { data, @@ -18,13 +19,14 @@ export function makeAddAuthenticatorMsg({ ); } -export function makeRemoveAuthenticatorMsg({ +export async function makeRemoveAuthenticatorMsg({ id, sender, }: { id: bigint; sender: string; }) { + const osmosis = await getOsmosisCodec(); return osmosis.smartaccount.v1beta1.MessageComposer.withTypeUrl.removeAuthenticator( { id, diff --git a/packages/tx/src/message-composers/osmosis/balancer.ts b/packages/tx/src/message-composers/osmosis/balancer.ts new file mode 100644 index 0000000000..a3f5b51481 --- /dev/null +++ b/packages/tx/src/message-composers/osmosis/balancer.ts @@ -0,0 +1,20 @@ +import type { MsgCreateBalancerPool } from "@osmosis-labs/proto-codecs/build/codegen/osmosis/gamm/poolmodels/balancer/v1beta1/tx"; + +import { getOsmosisCodec } from "../../codec"; + +export async function makeCreateBalancerPoolMsg({ + futurePoolGovernor, + poolAssets, + sender, + poolParams, +}: MsgCreateBalancerPool) { + const osmosis = await getOsmosisCodec(); + return osmosis.gamm.poolmodels.balancer.v1beta1.MessageComposer.withTypeUrl.createBalancerPool( + { + futurePoolGovernor, + poolAssets, + sender, + poolParams, + } + ); +} diff --git a/packages/tx/src/message-composers/osmosis/concentrated.ts b/packages/tx/src/message-composers/osmosis/concentrated.ts new file mode 100644 index 0000000000..04ae148d72 --- /dev/null +++ b/packages/tx/src/message-composers/osmosis/concentrated.ts @@ -0,0 +1,116 @@ +import type { MsgCreateConcentratedPool } from "@osmosis-labs/proto-codecs/build/codegen/osmosis/concentratedliquidity/poolmodel/concentrated/v1beta1/tx"; +import type { + MsgAddToPosition, + MsgCollectIncentives, + MsgCollectSpreadRewards, + MsgCreatePosition, + MsgWithdrawPosition, +} from "@osmosis-labs/proto-codecs/build/codegen/osmosis/concentratedliquidity/v1beta1/tx"; + +import { getOsmosisCodec } from "../../codec"; + +export async function makeCreateConcentratedPoolMsg({ + denom0, + denom1, + sender, + spreadFactor, + tickSpacing, +}: MsgCreateConcentratedPool) { + const osmosis = await getOsmosisCodec(); + return osmosis.concentratedliquidity.poolmodel.concentrated.v1beta1.MessageComposer.withTypeUrl.createConcentratedPool( + { + denom0, + denom1, + sender, + spreadFactor, + tickSpacing, + } + ); +} + +export async function makeCreatePositionMsg({ + poolId, + sender, + lowerTick, + upperTick, + tokensProvided, + tokenMinAmount0, + tokenMinAmount1, +}: MsgCreatePosition) { + const osmosis = await getOsmosisCodec(); + return osmosis.concentratedliquidity.v1beta1.MessageComposer.withTypeUrl.createPosition( + { + poolId, + sender, + lowerTick, + upperTick, + tokensProvided, + tokenMinAmount0, + tokenMinAmount1, + } + ); +} + +makeCreatePositionMsg.gas = 3_000_000 as const; + +export async function makeCollectSpreadRewardsMsg({ + positionIds, + sender, +}: MsgCollectSpreadRewards) { + const osmosis = await getOsmosisCodec(); + return osmosis.concentratedliquidity.v1beta1.MessageComposer.withTypeUrl.collectSpreadRewards( + { + positionIds, + sender, + } + ); +} + +export async function makeCollectIncentivesMsg({ + positionIds, + sender, +}: MsgCollectIncentives) { + const osmosis = await getOsmosisCodec(); + return osmosis.concentratedliquidity.v1beta1.MessageComposer.withTypeUrl.collectIncentives( + { + positionIds, + sender, + } + ); +} + +export async function makeWithdrawPositionMsg({ + positionId, + sender, + liquidityAmount, +}: MsgWithdrawPosition) { + const osmosis = await getOsmosisCodec(); + return osmosis.concentratedliquidity.v1beta1.MessageComposer.withTypeUrl.withdrawPosition( + { + positionId, + sender, + liquidityAmount, + } + ); +} + +export async function makeAddToPositionMsg({ + positionId, + sender, + amount0, + amount1, + tokenMinAmount0, + tokenMinAmount1, +}: MsgAddToPosition) { + const osmosis = await getOsmosisCodec(); + return osmosis.concentratedliquidity.v1beta1.MessageComposer.withTypeUrl.addToPosition( + { + positionId, + sender, + amount0, + amount1, + tokenMinAmount0, + tokenMinAmount1, + } + ); +} diff --git a/packages/tx/src/message-composers/osmosis/gamm.ts b/packages/tx/src/message-composers/osmosis/gamm.ts new file mode 100644 index 0000000000..96b352147a --- /dev/null +++ b/packages/tx/src/message-composers/osmosis/gamm.ts @@ -0,0 +1,60 @@ +import type { + MsgExitPool, + MsgJoinPool, + MsgJoinSwapExternAmountIn, +} from "@osmosis-labs/proto-codecs/build/codegen/osmosis/gamm/v1beta1/tx"; + +import { getOsmosisCodec } from "../../codec"; + +export async function makeJoinPoolMsg({ + poolId, + sender, + shareOutAmount, + tokenInMaxs, +}: MsgJoinPool) { + const osmosis = await getOsmosisCodec(); + return osmosis.gamm.v1beta1.MessageComposer.withTypeUrl.joinPool({ + poolId, + sender, + shareOutAmount, + tokenInMaxs, + }); +} + +makeJoinPoolMsg.shareCoinDecimals = 18 as const; + +export async function makeJoinSwapExternAmountInMsg({ + poolId, + sender, + tokenIn, + shareOutMinAmount, +}: MsgJoinSwapExternAmountIn) { + const osmosis = await getOsmosisCodec(); + return osmosis.gamm.v1beta1.MessageComposer.withTypeUrl.joinSwapExternAmountIn( + { + poolId, + sender, + tokenIn, + shareOutMinAmount, + } + ); +} + +makeJoinSwapExternAmountInMsg.shareCoinDecimals = 18; + +export async function makeExitPoolMsg({ + poolId, + sender, + shareInAmount, + tokenOutMins, +}: MsgExitPool) { + const osmosis = await getOsmosisCodec(); + return osmosis.gamm.v1beta1.MessageComposer.withTypeUrl.exitPool({ + poolId, + sender, + shareInAmount, + tokenOutMins, + }); +} + +makeExitPoolMsg.shareCoinDecimals = 18 as const; diff --git a/packages/tx/src/message-composers/osmosis/index.ts b/packages/tx/src/message-composers/osmosis/index.ts new file mode 100644 index 0000000000..2c9efb8f3a --- /dev/null +++ b/packages/tx/src/message-composers/osmosis/index.ts @@ -0,0 +1,9 @@ +export * from "./authenticator"; +export * from "./balancer"; +export * from "./concentrated"; +export * from "./gamm"; +export * from "./lockup"; +export * from "./poolmanager"; +export * from "./stableswap"; +export * from "./superfluid"; +export * from "./valsetpref"; diff --git a/packages/tx/src/message-composers/osmosis/lockup.ts b/packages/tx/src/message-composers/osmosis/lockup.ts new file mode 100644 index 0000000000..731e66cca7 --- /dev/null +++ b/packages/tx/src/message-composers/osmosis/lockup.ts @@ -0,0 +1,32 @@ +import type { + MsgBeginUnlocking, + MsgLockTokens, +} from "@osmosis-labs/proto-codecs/build/codegen/osmosis/lockup/tx"; + +import { getOsmosisCodec } from "../../codec"; + +export async function makeLockTokensMsg({ + owner, + coins, + duration, +}: MsgLockTokens) { + const osmosis = await getOsmosisCodec(); + return osmosis.lockup.MessageComposer.withTypeUrl.lockTokens({ + owner, + coins, + duration, + }); +} + +export async function makeBeginUnlockingMsg({ + owner, + ID, + coins, +}: MsgBeginUnlocking) { + const osmosis = await getOsmosisCodec(); + return osmosis.lockup.MessageComposer.withTypeUrl.beginUnlocking({ + owner, + ID, + coins, + }); +} diff --git a/packages/stores/src/account/message-composers/osmosis/poolmanager.ts b/packages/tx/src/message-composers/osmosis/poolmanager.ts similarity index 67% rename from packages/stores/src/account/message-composers/osmosis/poolmanager.ts rename to packages/tx/src/message-composers/osmosis/poolmanager.ts index 644eff9221..cba012f217 100644 --- a/packages/stores/src/account/message-composers/osmosis/poolmanager.ts +++ b/packages/tx/src/message-composers/osmosis/poolmanager.ts @@ -1,5 +1,6 @@ -import { osmosis } from "@osmosis-labs/proto-codecs"; -import { Currency } from "@osmosis-labs/types"; +import type { MsgSwapExactAmountOut } from "@osmosis-labs/proto-codecs/build/codegen/osmosis/gamm/v1beta1/tx"; + +import { getOsmosisCodec } from "../../codec"; /** * Constructs a message for performing a split route swap with an exact input amount across @@ -7,7 +8,7 @@ import { Currency } from "@osmosis-labs/types"; * for another through a series of split routes, specifying the minimum amount of * the output token they are willing to accept. */ -export function makeSplitRoutesSwapExactAmountInMsg({ +export async function makeSplitRoutesSwapExactAmountInMsg({ routes, tokenIn, tokenOutMinAmount, @@ -20,10 +21,11 @@ export function makeSplitRoutesSwapExactAmountInMsg({ }[]; tokenInAmount: string; }[]; - tokenIn: { currency: Currency }; + tokenIn: { coinMinimalDenom: string }; tokenOutMinAmount: string; userOsmoAddress: string; }) { + const osmosis = await getOsmosisCodec(); return osmosis.poolmanager.v1beta1.MessageComposer.withTypeUrl.splitRouteSwapExactAmountIn( { sender: userOsmoAddress, @@ -34,7 +36,7 @@ export function makeSplitRoutesSwapExactAmountInMsg({ })), tokenInAmount: tokenInAmount, })), - tokenInDenom: tokenIn.currency.coinMinimalDenom, + tokenInDenom: tokenIn.coinMinimalDenom, tokenOutMinAmount, } ); @@ -46,7 +48,7 @@ export function makeSplitRoutesSwapExactAmountInMsg({ * amount of one token for another through specified liquidity pools, with a minimum * acceptable amount of the output token. */ -export function makeSwapExactAmountInMsg({ +export async function makeSwapExactAmountInMsg({ pools, tokenIn, tokenOutMinAmount, @@ -56,10 +58,11 @@ export function makeSwapExactAmountInMsg({ id: string; tokenOutDenom: string; }[]; - tokenIn: { currency: Currency; amount: string }; + tokenIn: { coinMinimalDenom: string; amount: string }; tokenOutMinAmount: string; userOsmoAddress: string; }) { + const osmosis = await getOsmosisCodec(); return osmosis.poolmanager.v1beta1.MessageComposer.withTypeUrl.swapExactAmountIn( { sender: userOsmoAddress, @@ -70,10 +73,27 @@ export function makeSwapExactAmountInMsg({ }; }), tokenIn: { - denom: tokenIn.currency.coinMinimalDenom, + denom: tokenIn.coinMinimalDenom, amount: tokenIn.amount.toString(), }, tokenOutMinAmount, } ); } + +export async function makeSwapExactAmountOutMsg({ + sender, + routes, + tokenInMaxAmount, + tokenOut, +}: MsgSwapExactAmountOut) { + const osmosis = await getOsmosisCodec(); + return osmosis.poolmanager.v1beta1.MessageComposer.withTypeUrl.swapExactAmountOut( + { + sender, + routes, + tokenInMaxAmount, + tokenOut, + } + ); +} diff --git a/packages/tx/src/message-composers/osmosis/stableswap.ts b/packages/tx/src/message-composers/osmosis/stableswap.ts new file mode 100644 index 0000000000..60b7ec4367 --- /dev/null +++ b/packages/tx/src/message-composers/osmosis/stableswap.ts @@ -0,0 +1,24 @@ +import type { MsgCreateStableswapPool } from "@osmosis-labs/proto-codecs/build/codegen/osmosis/gamm/poolmodels/stableswap/v1beta1/tx"; + +import { getOsmosisCodec } from "../../codec"; + +export async function makeCreateStableswapPoolMsg({ + sender, + poolParams, + initialPoolLiquidity, + scalingFactors, + scalingFactorController, + futurePoolGovernor, +}: MsgCreateStableswapPool) { + const osmosis = await getOsmosisCodec(); + return osmosis.gamm.poolmodels.stableswap.v1beta1.MessageComposer.withTypeUrl.createStableswapPool( + { + sender, + poolParams, + initialPoolLiquidity, + scalingFactors, + scalingFactorController, + futurePoolGovernor, + } + ); +} diff --git a/packages/tx/src/message-composers/osmosis/superfluid.ts b/packages/tx/src/message-composers/osmosis/superfluid.ts new file mode 100644 index 0000000000..e83268766f --- /dev/null +++ b/packages/tx/src/message-composers/osmosis/superfluid.ts @@ -0,0 +1,94 @@ +import type { + MsgAddToConcentratedLiquiditySuperfluidPosition, + MsgCreateFullRangePositionAndSuperfluidDelegate, + MsgLockAndSuperfluidDelegate, + MsgSuperfluidDelegate, + MsgSuperfluidUnbondLock, + MsgSuperfluidUndelegate, +} from "@osmosis-labs/proto-codecs/build/codegen/osmosis/superfluid/tx"; + +import { getOsmosisCodec } from "../../codec"; + +export async function makeSuperfluidDelegateMsg({ + sender, + lockId, + valAddr, +}: MsgSuperfluidDelegate) { + const osmosis = await getOsmosisCodec(); + return osmosis.superfluid.MessageComposer.withTypeUrl.superfluidDelegate({ + sender, + lockId, + valAddr, + }); +} + +export async function makeLockAndSuperfluidDelegateMsg({ + sender, + coins, + valAddr, +}: MsgLockAndSuperfluidDelegate) { + const osmosis = await getOsmosisCodec(); + return osmosis.superfluid.MessageComposer.withTypeUrl.lockAndSuperfluidDelegate( + { + sender, + coins, + valAddr, + } + ); +} + +export async function makeSuperfluidUndelegateMsg({ + sender, + lockId, +}: MsgSuperfluidUndelegate) { + const osmosis = await getOsmosisCodec(); + return osmosis.superfluid.MessageComposer.withTypeUrl.superfluidUndelegate({ + sender, + lockId, + }); +} + +export async function makeSuperfluidUnbondLockMsg({ + sender, + lockId, +}: MsgSuperfluidUnbondLock) { + const osmosis = await getOsmosisCodec(); + return osmosis.superfluid.MessageComposer.withTypeUrl.superfluidUnbondLock({ + sender, + lockId, + }); +} + +export async function makeCreateFullRangePositionAndSuperfluidDelegateMsg({ + poolId, + sender, + valAddr, + coins, +}: MsgCreateFullRangePositionAndSuperfluidDelegate) { + const osmosis = await getOsmosisCodec(); + return osmosis.superfluid.MessageComposer.withTypeUrl.createFullRangePositionAndSuperfluidDelegate( + { + poolId, + sender, + valAddr, + coins, + } + ); +} + +export async function makeAddToConcentratedLiquiditySuperfluidPositionMsg({ + positionId, + sender, + tokenDesired0, + tokenDesired1, +}: MsgAddToConcentratedLiquiditySuperfluidPosition) { + const osmosis = await getOsmosisCodec(); + return osmosis.superfluid.MessageComposer.withTypeUrl.addToConcentratedLiquiditySuperfluidPosition( + { + positionId, + sender, + tokenDesired0, + tokenDesired1, + } + ); +} diff --git a/packages/tx/src/message-composers/osmosis/valsetpref.ts b/packages/tx/src/message-composers/osmosis/valsetpref.ts new file mode 100644 index 0000000000..37b824e003 --- /dev/null +++ b/packages/tx/src/message-composers/osmosis/valsetpref.ts @@ -0,0 +1,74 @@ +import type { + MsgDelegateToValidatorSet, + MsgSetValidatorSetPreference, + MsgUndelegateFromRebalancedValidatorSet, + MsgUndelegateFromValidatorSet, + MsgWithdrawDelegationRewards, +} from "@osmosis-labs/proto-codecs/build/codegen/osmosis/valsetpref/v1beta1/tx"; + +import { getOsmosisCodec } from "../../codec"; + +export async function makeUndelegateFromValidatorSetMsg({ + delegator, + coin, +}: MsgUndelegateFromValidatorSet) { + const osmosis = await getOsmosisCodec(); + return osmosis.valsetpref.v1beta1.MessageComposer.withTypeUrl.undelegateFromValidatorSet( + { + delegator, + coin, + } + ); +} + +export async function makeDelegateToValidatorSetMsg({ + delegator, + coin, +}: MsgDelegateToValidatorSet) { + const osmosis = await getOsmosisCodec(); + return osmosis.valsetpref.v1beta1.MessageComposer.withTypeUrl.delegateToValidatorSet( + { + delegator, + coin, + } + ); +} + +makeDelegateToValidatorSetMsg.gas = 500_000 as const; + +export async function makeWithdrawDelegationRewardsMsg({ + delegator, +}: MsgWithdrawDelegationRewards) { + const osmosis = await getOsmosisCodec(); + return osmosis.valsetpref.v1beta1.MessageComposer.withTypeUrl.withdrawDelegationRewards( + { + delegator, + } + ); +} + +export async function makeSetValidatorSetPreferenceMsg({ + delegator, + preferences, +}: MsgSetValidatorSetPreference) { + const osmosis = await getOsmosisCodec(); + return osmosis.valsetpref.v1beta1.MessageComposer.withTypeUrl.setValidatorSetPreference( + { + delegator, + preferences, + } + ); +} + +export async function makeUndelegateFromRebalancedValidatorSetMsg({ + delegator, + coin, +}: MsgUndelegateFromRebalancedValidatorSet) { + const osmosis = await getOsmosisCodec(); + return osmosis.valsetpref.v1beta1.MessageComposer.withTypeUrl.undelegateFromRebalancedValidatorSet( + { + delegator, + coin, + } + ); +} diff --git a/packages/tx/src/msg.ts b/packages/tx/src/msg.ts deleted file mode 100644 index 7b34966dfa..0000000000 --- a/packages/tx/src/msg.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { cosmwasm, ibc } from "@osmosis-labs/proto-codecs"; - -interface AccountMsgOpt { - /** - * In cases where fee estimation isn't supported, gas can be included as a fallback option. - * This proves particularly beneficial for accounts like CosmosAccount that depend on external chains. - */ - gas?: number; - messageComposer?: any; -} - -export const createMsgOpts = < - Dict extends Record< - string, - AccountMsgOpt | ((param: number) => AccountMsgOpt) - > ->( - dict: Dict -) => dict; - -/** Core message options for cosmos-based chains. */ -export const cosmosMsgOpts = createMsgOpts({ - ibcTransfer: { - gas: 250000, - messageComposer: - ibc.applications.transfer.v1.MessageComposer.withTypeUrl.transfer, - }, -}); - -export const cosmwasmMsgOpts = createMsgOpts({ - executeWasm: { - gas: 0, - messageComposer: - cosmwasm.wasm.v1.MessageComposer.withTypeUrl.executeContract, - }, -}); diff --git a/packages/tx/tsconfig.json b/packages/tx/tsconfig.json index f94b2526a2..11f48d2cf5 100644 --- a/packages/tx/tsconfig.json +++ b/packages/tx/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "build", "declaration": true, "rootDir": "src", - "module": "ES6" + "module": "esnext" }, "include": ["src/**/*"] } diff --git a/packages/web/.env b/packages/web/.env index 910040e38d..d0f78286ff 100644 --- a/packages/web/.env +++ b/packages/web/.env @@ -8,7 +8,6 @@ GITHUB_URL=https://raw.githubusercontent.com/osmosis-labs/ 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 NEXT_PUBLIC_SIDECAR_BASE_URL=https://sqs.osmosis.zone diff --git a/packages/web/.eslintrc.json b/packages/web/.eslintrc.json index 64943dc141..fe4abab7cc 100644 --- a/packages/web/.eslintrc.json +++ b/packages/web/.eslintrc.json @@ -26,6 +26,12 @@ "simple-import-sort/exports": "error", "unused-imports/no-unused-imports": "error", "react/no-unescaped-entities": "off", + "react-hooks/exhaustive-deps": [ + "warn", + { + "additionalHooks": "^use(Async|AsyncFn|AsyncRetry|UpdateEffect|IsomorphicLayoutEffect|DeepCompareEffect|ShallowCompareEffect)$" + } + ], "import/no-default-export": "error" // see https://stackoverflow.com/questions/44378395/how-to-configure-eslint-so-that-it-disallows-default-exports }, diff --git a/packages/web/.gitignore b/packages/web/.gitignore index 7cd9a2a556..fc6b939f78 100644 --- a/packages/web/.gitignore +++ b/packages/web/.gitignore @@ -38,9 +38,6 @@ yarn-error.log* # typescript *.tsbuildinfo -# Sentry Auth Token -.sentryclirc - # config files generated diff --git a/packages/web/components/bridge/amount-screen.tsx b/packages/web/components/bridge/amount-screen.tsx index 3ddbc367e7..2e9cedaf3b 100644 --- a/packages/web/components/bridge/amount-screen.tsx +++ b/packages/web/components/bridge/amount-screen.tsx @@ -586,7 +586,6 @@ export const AmountScreen = observer( ) { const firstChain = supportedChains[0]; setChain(firstChain); - checkChainAndConnectWallet(firstChain); } }, [ checkChainAndConnectWallet, diff --git a/packages/web/components/bridge/bridge-wallet-select-modal.tsx b/packages/web/components/bridge/bridge-wallet-select-modal.tsx index 57fd8588e7..a4546a42f9 100644 --- a/packages/web/components/bridge/bridge-wallet-select-modal.tsx +++ b/packages/web/components/bridge/bridge-wallet-select-modal.tsx @@ -215,8 +215,6 @@ export const BridgeWalletSelectScreens: FunctionComponent< const showEvmWallets = !isNil(evmChain) && !isNil(evmWallets); const transferWithSameWallet = fromChain.chainType === toChain.chainType; - console.log({ fromChain, toChain }); - return ( {({ setCurrentScreen }) => ( diff --git a/packages/web/components/cards/my-position/expanded.tsx b/packages/web/components/cards/my-position/expanded.tsx index 0270ce3f06..76ca2adce5 100644 --- a/packages/web/components/cards/my-position/expanded.tsx +++ b/packages/web/components/cards/my-position/expanded.tsx @@ -22,6 +22,7 @@ import React, { import { FallbackImg } from "~/components/assets"; import { ChartUnavailable, PriceChartHeader } from "~/components/chart"; import { Spinner } from "~/components/loaders"; +import { Tooltip } from "~/components/tooltip"; import { CustomClasses } from "~/components/types"; import { ChartButton } from "~/components/ui/button"; import { ArrowButton, Button } from "~/components/ui/button"; @@ -61,6 +62,8 @@ export const MyPositionCardExpandedSection: FunctionComponent<{ positionDetails: UserPositionDetails | undefined; positionPerformance: PositionHistoricalPerformance | undefined; showLinkToPool?: boolean; + isLoadingPositionDetails: boolean; + hasPositionDetailsError: boolean; }> = observer( ({ poolId, @@ -68,6 +71,8 @@ export const MyPositionCardExpandedSection: FunctionComponent<{ showLinkToPool = false, positionDetails, positionPerformance, + isLoadingPositionDetails, + hasPositionDetailsError, }) => { const { chainStore: { @@ -351,44 +356,58 @@ export const MyPositionCardExpandedSection: FunctionComponent<{ > {t("clPositions.collectRewards")} - { - if (superfluidData?.delegationLockId) { - account!.osmosis - .sendBeginUnlockingMsgOrSuperfluidUnbondLockMsgIfSyntheticLock( - [ - { - lockId: superfluidData.delegationLockId, - isSynthetic: true, - }, - ] - ) - .catch(console.error); - } else setActiveModal("remove"); - }, [account, superfluidData])} + - {Boolean(superfluidData?.delegationLockId) - ? t("clPositions.unstake") - : t("clPositions.removeLiquidity")} - - setActiveModal("increase"), [])} + { + if (superfluidData?.delegationLockId) { + account!.osmosis + .sendBeginUnlockingMsgOrSuperfluidUnbondLockMsgIfSyntheticLock( + [ + { + lockId: superfluidData.delegationLockId, + isSynthetic: true, + }, + ] + ) + .catch(console.error); + } else setActiveModal("remove"); + }, [account, superfluidData])} + isLoading={isLoadingPositionDetails} + > + {Boolean(superfluidData?.delegationLockId) + ? t("clPositions.unstake") + : t("clPositions.removeLiquidity")} + + + - {t("clPositions.increaseLiquidity")} - + setActiveModal("increase"), [])} + isLoading={isLoadingPositionDetails} + > + {t("clPositions.increaseLiquidity")} + + {activeModal === "increase" && !!status && ( asset.denom)} /> - - {positionDetails?.spreadFactor.toString() ?? ""}{" "} - {t("clPositions.spreadFactor")} - + {!hasPositionDetailsError && ( + + {positionDetails?.spreadFactor.toString() ?? ""}{" "} + {t("clPositions.spreadFactor")} + + )}
- {positionPerformance && featureFlags.positionRoi && ( + {/* {positionPerformance && featureFlags.positionRoi && ( + /> */} - + {!hasPositionDetailsError && ( + + )}
@@ -160,9 +167,11 @@ export const MyPositionCard: FunctionComponent<{ )} @@ -193,35 +202,35 @@ const PositionDataGroup: FunctionComponent<{ ); -const RangeDataGroup: FunctionComponent<{ - lowerPrice: Dec; - upperPrice: Dec; - isFullRange: boolean; -}> = ({ lowerPrice, upperPrice, isFullRange }) => { - const { t } = useTranslation(); +// const RangeDataGroup: FunctionComponent<{ +// lowerPrice: Dec; +// upperPrice: Dec; +// isFullRange: boolean; +// }> = ({ lowerPrice, upperPrice, isFullRange }) => { +// const { t } = useTranslation(); - return ( - -
- {isFullRange - ? "0" - : formatPretty(lowerPrice, { - scientificMagnitudeThreshold: 4, - })} -
- -
- {isFullRange - ? "∞" - : formatPretty(upperPrice, { - scientificMagnitudeThreshold: 4, - })} -
- - } - /> - ); -}; +// return ( +// +//
+// {isFullRange +// ? "0" +// : formatPretty(lowerPrice, { +// scientificMagnitudeThreshold: 4, +// })} +//
+// +//
+// {isFullRange +// ? "∞" +// : formatPretty(upperPrice, { +// scientificMagnitudeThreshold: 4, +// })} +//
+// +// } +// /> +// ); +// }; diff --git a/packages/web/components/chart/historical-chart.tsx b/packages/web/components/chart/historical-chart.tsx index 1dda33947b..e6fff6fac2 100644 --- a/packages/web/components/chart/historical-chart.tsx +++ b/packages/web/components/chart/historical-chart.tsx @@ -46,7 +46,7 @@ const getSeriesOpt = (config: Style): DeepPartial => { return { lineColor, lineWidth: 2, - lineType: LineType.Curved, + lineType: LineType.Simple, topColor, bottomColor, priceLineVisible: false, @@ -179,8 +179,8 @@ export const HistoricalChartHeader: FunctionComponent<{ export const HistoricalChartSkeleton = ({ hideScales = false }) => { return ( -
-
+
+
= { - theme: "dark", - overrides: { - "paneProperties.background": theme.colors.osmoverse[900], - "paneProperties.horzGridProperties.color": theme.colors.osmoverse[900], - "paneProperties.vertGridProperties.color": theme.colors.osmoverse[900], - "linetoolarc.backgroundColor": theme.colors.osmoverse[850], - "linetoolnote.backgroundColor": theme.colors.osmoverse[850], - "linetooltext.backgroundColor": theme.colors.osmoverse[850], - "linetoolbrush.backgroundColor": theme.colors.osmoverse[850], - "paneProperties.crossHairProperties.style": 1, - "paneProperties.legendProperties.showBarChange": false, - "paneProperties.backgroundType": "solid", - - "mainSeriesProperties.style": 1, - "mainSeriesProperties.candleStyle.upColor": theme.colors.bullish[400], - "mainSeriesProperties.candleStyle.borderUpColor": theme.colors.bullish[400], - "mainSeriesProperties.candleStyle.wickUpColor": theme.colors.bullish[400], - "mainSeriesProperties.statusViewStyle.symbolTextSource": "ticker", - - "scalesProperties.textColor": theme.colors.white.full, - "scalesProperties.backgroundColor": theme.colors.osmoverse[900], - "scalesProperties.lineColor": theme.colors.osmoverse[900], - }, - studies_overrides: { - "volume.volume.color.1": theme.colors.bullish[400], - "volume.volume ma.visible": false, - }, - loading_screen: { - backgroundColor: theme.colors.osmoverse[900], - foregroundColor: theme.colors.osmoverse[900], - }, -}; - type AdvancedChartProps = Omit< Partial, "symbol" @@ -52,14 +19,60 @@ export const AdvancedChart = (props: AdvancedChartProps) => { const [container, setContainer] = useState(null); const chart = useRef(); - if (container && chart.current === undefined) { + const featureFlags = useFeatureFlags(); + const themeOptions: Partial = { + theme: "dark", + overrides: { + "paneProperties.background": + theme.colors.osmoverse[featureFlags.limitOrders ? 1000 : 900], + "paneProperties.horzGridProperties.color": + theme.colors.osmoverse[featureFlags.limitOrders ? 1000 : 900], + "paneProperties.vertGridProperties.color": + theme.colors.osmoverse[featureFlags.limitOrders ? 1000 : 900], + "linetoolarc.backgroundColor": theme.colors.osmoverse[850], + "linetoolnote.backgroundColor": theme.colors.osmoverse[850], + "linetooltext.backgroundColor": theme.colors.osmoverse[850], + "linetoolbrush.backgroundColor": theme.colors.osmoverse[850], + "paneProperties.crossHairProperties.style": 1, + "paneProperties.legendProperties.showBarChange": false, + "paneProperties.backgroundType": "solid", + + "mainSeriesProperties.style": 1, + "mainSeriesProperties.candleStyle.upColor": theme.colors.bullish[400], + "mainSeriesProperties.candleStyle.borderUpColor": + theme.colors.bullish[400], + "mainSeriesProperties.candleStyle.wickUpColor": theme.colors.bullish[400], + "mainSeriesProperties.statusViewStyle.symbolTextSource": "ticker", + + "scalesProperties.textColor": theme.colors.white.full, + "scalesProperties.backgroundColor": + theme.colors.osmoverse[featureFlags.limitOrders ? 1000 : 900], + "scalesProperties.lineColor": + theme.colors.osmoverse[featureFlags.limitOrders ? 1000 : 900], + }, + studies_overrides: { + "volume.volume.color.1": theme.colors.bullish[400], + "volume.volume ma.visible": false, + }, + + loading_screen: { + backgroundColor: + theme.colors.osmoverse[featureFlags.limitOrders ? 1000 : 900], + foregroundColor: + theme.colors.osmoverse[featureFlags.limitOrders ? 1000 : 900], + }, + }; + + if (container && chart.current === undefined && featureFlags._isInitialized) { const widgetOptions: ChartingLibraryWidgetOptions = { symbol: props.coinDenom, datafeed: props.datafeed!, interval: "1d" as ResolutionString, container, library_path: "/tradingview/", - custom_css_url: "/tradingview/custom.css", + custom_css_url: featureFlags.limitOrders + ? "/tradingview/custom-limit.css" + : "/tradingview/custom.css", custom_font_family: '"Inter", sans-serif', locale: "en", disabled_features: [ diff --git a/packages/web/components/complex/all-pools-table.tsx b/packages/web/components/complex/all-pools-table.tsx index 575cf386ee..aaa0c88dc4 100644 --- a/packages/web/components/complex/all-pools-table.tsx +++ b/packages/web/components/complex/all-pools-table.tsx @@ -26,7 +26,7 @@ const useAllPoolsTable = () => { { allPoolsSort: parseAsStringLiteral( marketIncentivePoolsSortKeys - ).withDefault("volume24hUsd"), + ).withDefault("market.volume24hUsd"), allPoolsSortDir: parseAsStringEnum([ "asc", "desc", diff --git a/packages/web/components/complex/asset-fieldset.tsx b/packages/web/components/complex/asset-fieldset.tsx index 8b0b0de253..3c8cfb0e60 100644 --- a/packages/web/components/complex/asset-fieldset.tsx +++ b/packages/web/components/complex/asset-fieldset.tsx @@ -8,6 +8,7 @@ import { forwardRef, PropsWithChildren, ReactNode, + useCallback, } from "react"; import { Icon } from "~/components/assets"; @@ -57,9 +58,14 @@ const AssetFieldsetHeaderBalance = observer( }) => { const { t } = useTranslation(); const { accountStore } = useStore(); - + const { logEvent } = useAmplitudeAnalytics(); const wallet = accountStore.getWallet(accountStore.osmosisChainId); + const onClickAddFunds = useCallback(() => { + logEvent([EventName.LimitOrder.addFunds]); + openAddFundsModal?.(); + }, [openAddFundsModal, logEvent]); + return (
{t("limitOrders.addFunds")} @@ -163,6 +169,8 @@ interface TokenSelectProps { isFetchingNextPageAssets?: boolean; isLoadingSelectAssets?: boolean; page?: EventPage; + assetQueryInput?: string; + setAssetQueryInput?: (input: string) => void; } const AssetFieldsetTokenSelector = ({ @@ -178,12 +186,13 @@ const AssetFieldsetTokenSelector = ({ isFetchingNextPageAssets, page = "Swap Page", isLoadingSelectAssets, + assetQueryInput, + setAssetQueryInput, ...rest }: TokenSelectProps) => { const { t } = useTranslation(); const { logEvent } = useAmplitudeAnalytics(); const { isMobile } = useWindowSize(Breakpoint.sm); - const { isOpen: isSelectOpen, onOpen: openSelect, @@ -260,6 +269,8 @@ const AssetFieldsetTokenSelector = ({ hasNextPageAssets={hasNextPageAssets} isFetchingNextPageAssets={isFetchingNextPageAssets} isLoadingSelectAssets={isLoadingSelectAssets} + assetQueryInput={assetQueryInput} + setAssetQueryInput={setAssetQueryInput} /> )} diff --git a/packages/web/components/complex/my-pools-card-grid.tsx b/packages/web/components/complex/my-pools-card-grid.tsx index ba2e89e8e2..3ae21c4d56 100644 --- a/packages/web/components/complex/my-pools-card-grid.tsx +++ b/packages/web/components/complex/my-pools-card-grid.tsx @@ -68,7 +68,7 @@ export const MyPoolsCardsGrid = observer(() => { return (
-
+
{isLoadingMyPoolDetails ? ( <> {new Array(6).fill(undefined).map((_, i) => ( diff --git a/packages/web/components/complex/orders-history/cells/actions.tsx b/packages/web/components/complex/orders-history/cells/actions.tsx index 9cfde67df4..6e810a4a0a 100644 --- a/packages/web/components/complex/orders-history/cells/actions.tsx +++ b/packages/web/components/complex/orders-history/cells/actions.tsx @@ -110,7 +110,9 @@ const ClaimAndCloseButton = observer( > {claiming && } - {t("limitOrders.claimAndClose")} + {order.percentFilled > order.percentClaimed + ? t("limitOrders.claimAndClose") + : t("limitOrders.close")} ); diff --git a/packages/web/components/complex/orders-history/order-modal.tsx b/packages/web/components/complex/orders-history/order-modal.tsx index 556db07c8f..d077ef616a 100644 --- a/packages/web/components/complex/orders-history/order-modal.tsx +++ b/packages/web/components/complex/orders-history/order-modal.tsx @@ -172,7 +172,9 @@ const OrderDetails = observer( return order?.status === "open" ? "limitOrders.cancel" - : "limitOrders.claimAndClose"; + : order?.percentFilled > order?.percentClaimed + ? "limitOrders.claimAndClose" + : "limitOrders.close"; }, [order]); const orderAmount = useMemo(() => { diff --git a/packages/web/components/complex/pools-table.tsx b/packages/web/components/complex/pools-table.tsx index 7852a4b785..1e5ac1ef07 100644 --- a/packages/web/components/complex/pools-table.tsx +++ b/packages/web/components/complex/pools-table.tsx @@ -36,11 +36,12 @@ import { api, RouterOutputs } from "~/utils/trpc"; import { Tooltip } from "../tooltip"; -export type Pool = - RouterOutputs["edge"]["pools"]["getMarketIncentivePools"]["items"][number]; +export type Pool = RouterOutputs["edge"]["pools"]["getPools"]["items"][number]; /** UI doesn't support cosmwasm pools as first class so exclude it from list of filter options. */ export type PoolTypeFilter = Exclude; -export type PoolIncentiveFilter = NonNullable[number]; +export type PoolIncentiveFilter = NonNullable< + NonNullable["incentiveTypes"] +>[number]; // These are the options for filtering the pools. export const poolFilterTypes: PoolTypeFilter[] = [ @@ -52,11 +53,11 @@ export const poolFilterTypes: PoolTypeFilter[] = [ export const marketIncentivePoolsSortKeys = [ "totalFiatValueLocked", - "feesSpent7dUsd", - "feesSpent24hUsd", - "volume7dUsd", - "volume24hUsd", - "aprBreakdown.total.upper", + "market.feesSpent7dUsd", + "market.feesSpent24hUsd", + "market.volume7dUsd", + "market.volume24hUsd", + "incentives.aprBreakdown.total.upper", ] as const; export type MarketIncentivePoolsSortKey = @@ -110,7 +111,7 @@ export const PoolsTable = (props: PropsWithChildren) => { denoms: [], }, sortParams = { - allPoolsSort: "volume24hUsd", + allPoolsSort: "market.volume24hUsd", allPoolsSortDir: "desc", }, emptyResultsText, @@ -134,7 +135,7 @@ export const PoolsTable = (props: PropsWithChildren) => { isFetchingNextPage, hasNextPage, fetchNextPage, - } = api.edge.pools.getMarketIncentivePools.useInfiniteQuery( + } = api.edge.pools.getPools.useInfiniteQuery( { limit, search: filters.searchQuery @@ -186,11 +187,11 @@ export const PoolsTable = (props: PropsWithChildren) => { let volumePresenceCount = 0; let feesPresenceCount = 0; poolsData.forEach((pool) => { - if (pool.volume24hUsd) { + if (pool.market?.volume24hUsd) { volumePresenceCount++; } - if (pool.feesSpent7dUsd) { + if (pool.market?.feesSpent7dUsd) { feesPresenceCount++; } }); @@ -216,20 +217,23 @@ export const PoolsTable = (props: PropsWithChildren) => { // Only show volume if more than half of the pools have volume data. if (shouldDisplayVolumeData) { allColumns.push( - columnHelper.accessor((row) => row.volume24hUsd?.toString() ?? "N/A", { - id: "volume24hUsd", - header: () => ( - - label={t("pools.allPools.sort.volume24h")} - sortKey="volume24hUsd" - disabled={isLoading} - currentSortKey={sortKey} - currentDirection={sortParams.allPoolsSortDir} - setSortDirection={setSortDirection} - setSortKey={setSortKey} - /> - ), - }) as (typeof allColumns)[number] + columnHelper.accessor( + (row) => row.market?.volume24hUsd?.toString() ?? "N/A", + { + id: "market.volume24hUsd", + header: () => ( + + label={t("pools.allPools.sort.volume24h")} + sortKey="market.volume24hUsd" + disabled={isLoading} + currentSortKey={sortKey} + currentDirection={sortParams.allPoolsSortDir} + setSortDirection={setSortDirection} + setSortKey={setSortKey} + /> + ), + } + ) as (typeof allColumns)[number] ); } @@ -257,13 +261,13 @@ export const PoolsTable = (props: PropsWithChildren) => { if (shouldDisplayFeesData) { allColumns.push( columnHelper.accessor( - (row) => row.feesSpent7dUsd?.toString() ?? "N/A", + (row) => row.market?.feesSpent7dUsd?.toString() ?? "N/A", { - id: "feesSpent7dUsd", + id: "market.feesSpent7dUsd", header: () => ( ) => { header: () => ( ) => { const collapsedColumns = useMemo(() => { const collapsedColIds: string[] = []; if (width < Breakpoint.xxl && shouldDisplayFeesData) - collapsedColIds.push("feesSpent7dUsd"); + collapsedColIds.push("market.feesSpent7dUsd"); if (width < Breakpoint.xlg) collapsedColIds.push("totalFiatValueLocked"); if (width < Breakpoint.lg && shouldDisplayVolumeData) - collapsedColIds.push("volume24hUsd"); + collapsedColIds.push("market.volume24hUsd"); if (width < Breakpoint.md) collapsedColIds.push("poolQuickActions"); return columns.filter(({ id }) => id && !collapsedColIds.includes(id)); }, [columns, width, shouldDisplayVolumeData, shouldDisplayFeesData]); @@ -604,9 +608,10 @@ export function getPoolTypeTarget(pool: Pool) { export const AprBreakdownCell: PoolCellComponent = ({ row: { - original: { aprBreakdown }, + original: { incentives }, }, }) => { + const aprBreakdown = incentives?.aprBreakdown; if (!aprBreakdown) { return null; } diff --git a/packages/web/components/complex/portfolio/allocation.tsx b/packages/web/components/complex/portfolio/allocation.tsx index ea31670b19..b9ee982292 100644 --- a/packages/web/components/complex/portfolio/allocation.tsx +++ b/packages/web/components/complex/portfolio/allocation.tsx @@ -1,15 +1,20 @@ import { Dec } from "@keplr-wallet/unit"; import { GetAllocationResponse } from "@osmosis-labs/server"; import classNames from "classnames"; -import { FunctionComponent, useState } from "react"; +import { FunctionComponent, useEffect, useState } from "react"; import { Icon } from "~/components/assets"; import { AllocationTabs } from "~/components/complex/portfolio/allocation-tabs"; import { AllocationOptions } from "~/components/complex/portfolio/types"; import { displayFiatPrice } from "~/components/transactions/transaction-utils"; import { EventName } from "~/config"; -import { MultiLanguageT } from "~/hooks"; -import { useAmplitudeAnalytics, useTranslation } from "~/hooks"; +import { + Breakpoint, + MultiLanguageT, + useAmplitudeAnalytics, + useTranslation, + useWindowSize, +} from "~/hooks"; const COLORS: Record = { all: [ @@ -55,11 +60,19 @@ export const Allocation: FunctionComponent<{ }> = ({ allocation }) => { const { logEvent } = useAmplitudeAnalytics(); + const { width } = useWindowSize(); + const [selectedOption, setSelectedOption] = useState("all"); const [isOpen, setIsOpen] = useState(true); + useEffect(() => { + if (width > Breakpoint.xl) { + setIsOpen(true); + } + }, [width]); + const { t } = useTranslation(); if (!allocation) return null; @@ -73,12 +86,14 @@ export const Allocation: FunctionComponent<{ onClick={() => setIsOpen((prev) => !prev)} >
{t("portfolio.allocation")}
- + {width > Breakpoint.xl && ( + + )}
{isOpen && ( <> diff --git a/packages/web/components/complex/portfolio/assets-overview.tsx b/packages/web/components/complex/portfolio/assets-overview.tsx index 0860f2a17e..0283326cba 100644 --- a/packages/web/components/complex/portfolio/assets-overview.tsx +++ b/packages/web/components/complex/portfolio/assets-overview.tsx @@ -11,10 +11,11 @@ import dayjs from "dayjs"; import { AreaData, Time } from "lightweight-charts"; import { observer } from "mobx-react-lite"; import { FunctionComponent, useState } from "react"; +import { useEffect } from "react"; +import { useMemo } from "react"; import { Icon } from "~/components/assets"; import { CreditCardIcon } from "~/components/assets/credit-card-icon"; -import { GetStartedWithOsmosis } from "~/components/complex/portfolio/get-started-with-osmosis"; import { PortfolioHistoricalChart, PortfolioHistoricalChartMinimized, @@ -25,7 +26,12 @@ import { SkeletonLoader } from "~/components/loaders/skeleton-loader"; import { useFormatDate } from "~/components/transactions/transaction-utils"; import { CustomClasses } from "~/components/types"; import { Button } from "~/components/ui/button"; -import { useTranslation, useWalletSelect, useWindowSize } from "~/hooks"; +import { + Breakpoint, + useTranslation, + useWalletSelect, + useWindowSize, +} from "~/hooks"; import { useBridge } from "~/hooks/bridge"; import { useStore } from "~/stores"; import { api } from "~/utils/trpc"; @@ -65,9 +71,24 @@ const calculatePortfolioPerformance = ( }; }; +const timeToLocal = (originalTime: number) => { + const d = new Date(originalTime * 1000); + return ( + Date.UTC( + d.getFullYear(), + d.getMonth(), + d.getDate(), + d.getHours(), + d.getMinutes(), + d.getSeconds(), + d.getMilliseconds() + ) / 1000 + ); +}; + export const AssetsOverview: FunctionComponent< { - totalValue: PricePretty; + totalValue?: PricePretty; isTotalValueFetched?: boolean; } & CustomClasses > = observer(({ totalValue, isTotalValueFetched }) => { @@ -76,7 +97,7 @@ export const AssetsOverview: FunctionComponent< const { t } = useTranslation(); const { startBridge, fiatRampSelection } = useBridge(); const { isLoading: isWalletLoading } = useWalletSelect(); - const { isMobile } = useWindowSize(); + const { isMobile, width } = useWindowSize(); const formatDate = useFormatDate(); const address = wallet?.address ?? ""; @@ -85,10 +106,10 @@ export const AssetsOverview: FunctionComponent< const [dataPoint, setDataPoint] = useState({ time: dayjs().unix() as Time, - value: 0, + value: undefined, }); - const [range, setRange] = useState("1mo"); + const [range, setRange] = useState("1d"); const { data: portfolioOverTimeData, @@ -101,15 +122,6 @@ export const AssetsOverview: FunctionComponent< }, { enabled: Boolean(wallet?.isWalletConnected && wallet?.address), - onSuccess: (data) => { - if (data && data.length > 0) { - const lastItem = data[data.length - 1]; - setDataPoint({ - time: lastItem.time as Time, - value: lastItem.value, - }); - } - }, } ); @@ -121,14 +133,30 @@ export const AssetsOverview: FunctionComponent< : undefined; const totalDisplayValue = - new PricePretty(DEFAULT_VS_CURRENCY, new Dec(dataPoint.value || 0)) || - totalValue?.toString(); + dataPoint.value !== undefined + ? new PricePretty(DEFAULT_VS_CURRENCY, new Dec(dataPoint.value)) + : totalValue?.toString(); - const [isChartMinimized, setIsChartMinimized] = useState(true); + const [isChartMinimized, setIsChartMinimized] = useState( + width < Breakpoint.lg ? false : true + ); - if (isWalletLoading) return null; + useEffect(() => { + if (width < Breakpoint.lg) setIsChartMinimized(false); + }, [isMobile, width]); - return ( + const localizedPortfolioOverTimeData = useMemo( + () => + portfolioOverTimeData?.map((data) => { + return { + time: timeToLocal(data.time), + value: data.value, + }; + }), + [portfolioOverTimeData] + ); + + return isWalletLoading ? null : (
- {wallet && wallet.isWalletConnected && wallet.address ? ( - <> -
-
- - {t("assets.totalBalance")} - - - - {isMobile ? ( -
{totalDisplayValue?.toString()}
- ) : ( -

{totalDisplayValue?.toString()}

- )} -
- - - -
- - - -
-
- +
+ + {t("assets.totalBalance")} + + + {isMobile ? ( +

{totalDisplayValue?.toString()}

+ ) : ( +

{totalDisplayValue?.toString()}

+ )} +
+ + + +
+ - -
- - +
{t("assets.table.depositButton")}
+ + + +
+
+ + + + + + + []} + isFetched={isPortfolioOverTimeDataIsFetched} + setDataPoint={setDataPoint} + range={range} + setRange={setRange} + error={error} + setShowDate={setShowDate} + resetDataPoint={() => { + if (totalValue) { + setDataPoint({ + time: dayjs().unix() as Time, + value: +totalValue?.toDec()?.toString(), + }); + setShowDate(false); + } + }} + /> +
); }); diff --git a/packages/web/components/complex/portfolio/cypher-card.tsx b/packages/web/components/complex/portfolio/cypher-card.tsx index 7d9202078a..1bcadf7ce3 100644 --- a/packages/web/components/complex/portfolio/cypher-card.tsx +++ b/packages/web/components/complex/portfolio/cypher-card.tsx @@ -1,24 +1,30 @@ import Link from "next/link"; import { Icon } from "~/components/assets"; +import { useTranslation } from "~/hooks"; export const CypherCard = () => { + const { t } = useTranslation(); return ( -
+
-

Spend with OSMO

-

Order your card now

+

+ {t("portfolio.cypherSpend")} +

+

+ {t("portfolio.cypherOrder")} +

- Beta + {t("portfolio.cypherBeta")}
diff --git a/packages/web/components/complex/portfolio/historical-chart.tsx b/packages/web/components/complex/portfolio/historical-chart.tsx index 721fc19a59..9cfabcbec7 100644 --- a/packages/web/components/complex/portfolio/historical-chart.tsx +++ b/packages/web/components/complex/portfolio/historical-chart.tsx @@ -40,15 +40,15 @@ export const PortfolioHistoricalChart = ({ const { logEvent } = useAmplitudeAnalytics(); return ( -
-
- {error ? ( -
- {t("errors.generic")} -
- ) : !isFetched ? ( - - ) : ( +
+ {error ? ( +
+ {t("errors.generic")} +
+ ) : !isFetched ? ( + + ) : ( + <> []} onPointerHover={(value, time) => { @@ -58,27 +58,27 @@ export const PortfolioHistoricalChart = ({ }} onPointerOut={resetDataPoint} /> - )} -
-
- - + + + } + onClick={() => setIsChartMinimized(true)} /> - } - onClick={() => setIsChartMinimized(true)} - /> -
+
+ + )} ); }; @@ -94,7 +94,7 @@ export const PortfolioHistoricalChartMinimized = ({ }) => { const { t } = useTranslation(); return ( -
+
{error ? (
{t("errors.generic")} diff --git a/packages/web/components/complex/portfolio/performance.tsx b/packages/web/components/complex/portfolio/performance.tsx index 91a165eab9..8c31f95287 100644 --- a/packages/web/components/complex/portfolio/performance.tsx +++ b/packages/web/components/complex/portfolio/performance.tsx @@ -11,14 +11,14 @@ export const PortfolioPerformance: FunctionComponent<{ showDate: boolean; }> = ({ selectedDifference, selectedPercentage, formattedDate, showDate }) => { return ( -
+
{showDate && formattedDate && ( - {formattedDate} + {formattedDate} )}
); diff --git a/packages/web/components/complex/portfolio/portfolio-page.tsx b/packages/web/components/complex/portfolio/portfolio-page.tsx index 508fedc4fe..f7a8fdae33 100644 --- a/packages/web/components/complex/portfolio/portfolio-page.tsx +++ b/packages/web/components/complex/portfolio/portfolio-page.tsx @@ -1,15 +1,14 @@ import { Tab, TabGroup, TabList, TabPanel, TabPanels } from "@headlessui/react"; -import { Dec, PricePretty } from "@keplr-wallet/unit"; -import { DEFAULT_VS_CURRENCY } from "@osmosis-labs/server"; import classNames from "classnames"; -import { FunctionComponent, useCallback } from "react"; +import { observer } from "mobx-react-lite"; +import { FunctionComponent } from "react"; import { Allocation } from "~/components/complex/portfolio/allocation"; import { AssetsOverview } from "~/components/complex/portfolio/assets-overview"; import { UserPositionsSection } from "~/components/complex/portfolio/user-positions"; import { UserZeroBalanceTableSplash } from "~/components/complex/portfolio/user-zero-balance-table-splash"; import { WalletDisconnectedSplash } from "~/components/complex/portfolio/wallet-disconnected-splash"; -import { Spinner } from "~/components/loaders"; +import { SkeletonLoader, Spinner } from "~/components/loaders"; import { AssetBalancesTable } from "~/components/table/asset-balances"; import { RecentActivity } from "~/components/transactions/recent-activity/recent-activity"; import { EventName } from "~/config"; @@ -20,171 +19,162 @@ import { useTranslation, useWalletSelect, } from "~/hooks"; -import { useBridge } from "~/hooks/bridge"; import { useStore } from "~/stores"; import { api } from "~/utils/trpc"; import { CypherCard } from "./cypher-card"; +import { GetStartedWithOsmosis } from "./get-started-with-osmosis"; -export const PortfolioPage: FunctionComponent = () => { +export const PortfolioPage: FunctionComponent = observer(() => { const { t } = useTranslation(); - const { bridgeAsset } = useBridge(); const { accountStore } = useStore(); const wallet = accountStore.getWallet(accountStore.osmosisChainId); const featureFlags = useFeatureFlags(); - const { isLoading: isWalletLoading } = useWalletSelect(); useAmplitudeAnalytics({ onLoadEvent: [EventName.Portfolio.pageViewed], }); - const { data: totalValueData, isFetched: isTotalValueFetched } = - api.edge.assets.getUserAssetsTotal.useQuery( - { - userOsmoAddress: wallet?.address ?? "", - }, - { - enabled: Boolean(wallet?.isWalletConnected && wallet?.address), - select: ({ value }) => value, + const { + data: allocation, + isLoading: isLoadingAllocation, + isFetched: isFetchedAllocation, + } = api.local.portfolio.getAllocation.useQuery( + { + address: wallet?.address ?? "", + }, + { + enabled: Boolean(wallet?.isWalletConnected && wallet?.address), + } + ); - // expensive query - trpc: { - context: { - skipBatch: true, - }, - }, - } - ); - const userHasNoAssets = totalValueData && totalValueData.toDec().isZero(); + const totalCap = allocation?.totalCap; - const { data: allocation, isLoading: isLoadingAllocation } = - api.local.portfolio.getAllocation.useQuery( - { - address: wallet?.address ?? "", - }, - { - enabled: Boolean(wallet?.isWalletConnected && wallet?.address), - } - ); + const userHasNoAssets = allocation && totalCap?.toDec()?.isZero(); const [overviewRef, { height: overviewHeight }] = useDimension(); const [tabsRef, { height: tabsHeight }] = useDimension(); - // these useCallbacks are key to prevent unnecessary rerenders of page + table - // this prevents flickering - const onDeposit = useCallback( - (coinDenom: string) => { - bridgeAsset({ anyDenom: coinDenom, direction: "deposit" }); - }, - [bridgeAsset] - ); - const onWithdraw = useCallback( - (coinDenom: string) => { - bridgeAsset({ anyDenom: coinDenom, direction: "withdraw" }); - }, - [bridgeAsset] - ); - const { logEvent } = useAmplitudeAnalytics(); + const isWalletConnected = + wallet && wallet.isWalletConnected && wallet.address; + + const { isLoading: isWalletLoading } = useWalletSelect(); + return ( -
-
- -
- - {wallet && wallet.isWalletConnected && wallet.address ? ( + {isWalletLoading ? ( + + ) : isWalletConnected ? ( <> -
- - - { - logEvent([ - EventName.Portfolio.tabClicked, - { section: "Your assets" }, - ]); - }} - > - {({ selected }) => ( -
- {t("portfolio.yourAssets")} -
- )} -
- { - logEvent([ - EventName.Portfolio.tabClicked, - { section: "Your positions" }, - ]); - }} - > - {({ selected }) => ( -
- {t("portfolio.yourPositions")} -
- )} -
-
- {!isTotalValueFetched ? ( -
- -
- ) : userHasNoAssets ? ( - - ) : ( - - - - - - - - +
+
+ +
+
+ + + { + logEvent([ + EventName.Portfolio.tabClicked, + { section: "Your balances" }, + ]); + }} + > + {({ selected }) => ( +
+ {t("portfolio.yourBalances")} +
+ )} +
+ { + logEvent([ + EventName.Portfolio.tabClicked, + { section: "Your positions" }, + ]); + }} + > + {({ selected }) => ( +
+ {t("portfolio.yourPositions")} +
+ )} +
+
+ {!isFetchedAllocation ? ( +
+ +
+ ) : userHasNoAssets ? ( + + ) : ( + + + + + + + + + )} +
+
+
+ +
- - ) : isWalletLoading ? null : ( - + ) : ( +
+ + +
)} -
+
); -}; +}); diff --git a/packages/web/components/complex/portfolio/types.ts b/packages/web/components/complex/portfolio/types.ts index 0db23271fc..7e9234821f 100644 --- a/packages/web/components/complex/portfolio/types.ts +++ b/packages/web/components/complex/portfolio/types.ts @@ -1,8 +1,8 @@ import { Time } from "lightweight-charts"; export type DataPoint = { - value: number; - time: Time; + value?: number; + time?: Time; }; export type AllocationOptions = "all" | "assets" | "available"; diff --git a/packages/web/components/earn/rewards/index.tsx b/packages/web/components/earn/rewards/index.tsx index 988abc96a5..ebc541f898 100644 --- a/packages/web/components/earn/rewards/index.tsx +++ b/packages/web/components/earn/rewards/index.tsx @@ -2,6 +2,10 @@ import { sleep } from "@axelar-network/axelarjs-sdk"; import type { EncodeObject } from "@cosmjs/proto-signing"; import { PricePretty } from "@keplr-wallet/unit"; import { EarnStrategy } from "@osmosis-labs/server"; +import { + makeExecuteCosmwasmContractMsg, + makeWithdrawDelegationRewardsMsg, +} from "@osmosis-labs/tx"; import { useCallback } from "react"; import { Button } from "~/components/ui/button"; @@ -41,18 +45,18 @@ export const EarnRewards = ({ if (!account) return; - filteredUnclaimedRewards.forEach(({ id, platform }) => { + for (const { id, platform } of filteredUnclaimedRewards) { switch (platform) { case "Osmosis": messages.push( - account?.osmosis.msgOpts.withdrawDelegationRewards.messageComposer({ + await makeWithdrawDelegationRewardsMsg({ delegator: account.address ?? "", }) ); break; case "Quasar": messages.push( - account.cosmwasm.msgOpts.executeWasm.messageComposer({ + await makeExecuteCosmwasmContractMsg({ contract: id, msg: Buffer.from( JSON.stringify({ @@ -68,7 +72,7 @@ export const EarnRewards = ({ break; case "Levana": messages.push( - account.cosmwasm.msgOpts.executeWasm.messageComposer({ + await makeExecuteCosmwasmContractMsg({ contract: id.split("-")[0], // this strips the -x|lp part of the contract id msg: Buffer.from( JSON.stringify({ @@ -81,7 +85,7 @@ export const EarnRewards = ({ ); break; } - }); + } try { await accountStore.signAndBroadcast( @@ -120,19 +124,6 @@ export const EarnRewards = ({
{t("earnPage.rewards")}
- {/*
-
$221.64
-
- - {t("earnPage.estimatedInYearlyRewards")} - - - - -
-
*/}

{formatPretty(totalUnclaimedRewards)} 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 cd8c102232..668b2f3eea 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 @@ -1,5 +1,5 @@ import { Dec, PricePretty } from "@keplr-wallet/unit"; -import { makeRemoveAuthenticatorMsg } from "@osmosis-labs/stores"; +import { makeRemoveAuthenticatorMsg } from "@osmosis-labs/tx"; import { OneClickTradingTransactionParams } from "@osmosis-labs/types"; import { noop, runIfFn } from "@osmosis-labs/utils"; import classNames from "classnames"; @@ -11,6 +11,7 @@ import React, { useEffect, useState, } from "react"; +import { useAsync } from "react-use"; import { Icon } from "~/components/assets"; import { Spinner } from "~/components/loaders"; @@ -128,16 +129,17 @@ export const OneClickTradingSettings = ({ } ); + const { value: removeAuthenticatorMsg } = useAsync(async () => { + if (!oneClickTradingInfo) return; + return await makeRemoveAuthenticatorMsg({ + id: BigInt(oneClickTradingInfo.authenticatorId), + sender: oneClickTradingInfo.userOsmoAddress, + }); + }, [oneClickTradingInfo]); + const { data: estimateRemoveTxData, isLoading: isLoadingEstimateRemoveTx } = useEstimateTxFees({ - messages: oneClickTradingInfo - ? [ - makeRemoveAuthenticatorMsg({ - id: BigInt(oneClickTradingInfo.authenticatorId), - sender: oneClickTradingInfo.userOsmoAddress, - }), - ] - : [], + messages: removeAuthenticatorMsg ? [removeAuthenticatorMsg] : [], chainId: chainStore.osmosis.chainId, enabled: !!oneClickTradingInfo && isOneClickTradingEnabled, }); diff --git a/packages/web/components/overview/pools.tsx b/packages/web/components/overview/pools.tsx index f04e3309e2..0804e1dac1 100644 --- a/packages/web/components/overview/pools.tsx +++ b/packages/web/components/overview/pools.tsx @@ -24,7 +24,7 @@ export const PoolsOverview: FunctionComponent< coinMinimalDenom: "uosmo", } ); - const { data: epochs } = api.edge.params.getEpochs.useQuery(); + const { data: epochs } = api.local.params.getEpochs.useQuery(); // update time every second const [timeRemaining, setTimeRemaining] = useState(null); diff --git a/packages/web/components/pages/asset-info-page/pools.tsx b/packages/web/components/pages/asset-info-page/pools.tsx index 1e67b7a546..25425f4ebc 100644 --- a/packages/web/components/pages/asset-info-page/pools.tsx +++ b/packages/web/components/pages/asset-info-page/pools.tsx @@ -23,7 +23,7 @@ const defaultFilters: PoolsTableFilters = { }; const sortParams: PoolsTabelSortParams = { - allPoolsSort: "volume24hUsd", + allPoolsSort: "market.volume24hUsd", allPoolsSortDir: "desc", }; diff --git a/packages/web/components/place-limit-tool/index.tsx b/packages/web/components/place-limit-tool/index.tsx index 39406d25fd..452d057088 100644 --- a/packages/web/components/place-limit-tool/index.tsx +++ b/packages/web/components/place-limit-tool/index.tsx @@ -27,6 +27,7 @@ import { LimitPriceSelector } from "~/components/place-limit-tool/limit-price-se import { TRADE_TYPES } from "~/components/swap-tool/order-type-selector"; import { PriceSelector } from "~/components/swap-tool/price-selector"; import { TradeDetails } from "~/components/swap-tool/trade-details"; +import { GenericDisclaimer } from "~/components/tooltip/generic-disclaimer"; import { Button } from "~/components/ui/button"; import { EventPage } from "~/config"; import { @@ -35,7 +36,7 @@ import { useTranslation, useWalletSelect, } from "~/hooks"; -import { usePlaceLimit } from "~/hooks/limit-orders"; +import { MIN_ORDER_VALUE, usePlaceLimit } from "~/hooks/limit-orders"; import { AddFundsModal } from "~/modals/add-funds"; import { ReviewOrder } from "~/modals/review-order"; import { useStore } from "~/stores"; @@ -44,7 +45,9 @@ import { countDecimals, trimPlaceholderZeros } from "~/utils/number"; export interface PlaceLimitToolProps { page: EventPage; - refetchOrders: () => Promise; + initialBaseDenom?: string; + initialQuoteDenom?: string; + onOrderSuccess?: (baseDenom?: string, quoteDenom?: string) => void; } const fixDecimalCount = (value: string, decimalCount = 18) => { @@ -75,11 +78,17 @@ const transformAmount = (value: string, decimalCount = 18) => { const NON_DISPLAY_ERRORS = [ "errors.zeroAmount", "errors.emptyAmount", - "errors.generic", + "limitOrders.priceTooLow", + "limitOrders.priceTooHigh", ]; export const PlaceLimitTool: FunctionComponent = observer( - ({ page, refetchOrders }: PlaceLimitToolProps) => { + ({ + page, + initialBaseDenom = "ATOM", + initialQuoteDenom = "USDC", + onOrderSuccess, + }: PlaceLimitToolProps) => { const { accountStore } = useStore(); const { t } = useTranslation(); const [reviewOpen, setReviewOpen] = useState(false); @@ -91,8 +100,8 @@ export const PlaceLimitTool: FunctionComponent = observer( const inputRef = useRef(null); const [{ from, quote, tab, type }, set] = useQueryStates({ - from: parseAsString.withDefault("ATOM"), - quote: parseAsString.withDefault("USDC"), + from: parseAsString.withDefault(initialBaseDenom || "ATOM"), + quote: parseAsString.withDefault(initialQuoteDenom || "USDC"), type: parseAsStringLiteral(TRADE_TYPES).withDefault("market"), tab: parseAsString, to: parseAsString, @@ -403,6 +412,17 @@ export const PlaceLimitTool: FunctionComponent = observer( const errorDisplay = useMemo(() => { if (swapState.error && !NON_DISPLAY_ERRORS.includes(swapState.error)) { + if (swapState.error === "errors.generic") { + return t("errors.uhOhSomethingWentWrong"); + } + + if (swapState.error === "limitOrders.belowMinimumAmount") { + return t("limitOrders.belowMinimumAmount", { + amount: formatFiatPrice( + new PricePretty(DEFAULT_VS_CURRENCY, MIN_ORDER_VALUE) + ), + }); + } return t(swapState.error); } }, [swapState.error, t]); @@ -484,6 +504,8 @@ export const PlaceLimitTool: FunctionComponent = observer( swapState.marketState.isLoadingSelectAssets } data-testid="token-in" + setAssetQueryInput={swapState.marketState.setAssetsQueryInput} + assetQueryInput={swapState.marketState.assetsQueryInput} />

@@ -508,7 +530,7 @@ export const PlaceLimitTool: FunctionComponent = observer( )} = observer( ).toString() )}{" "} {focused === "fiat" && ( - {swapState.baseAsset?.coinDenom} + + {swapState.baseAsset?.coinDenom} + )}{" "} {type === "market" && swapState.marketState.quote?.priceImpactTokenOut && ( - + (- {formatPretty( swapState.marketState.quote?.priceImpactTokenOut )} ) - + )} - + {type === "limit" && ( @@ -586,7 +629,12 @@ export const PlaceLimitTool: FunctionComponent = observer( treatAsStable={tab === "buy" ? "in" : "out"} tab={tab as "buy" | "sell"} priceOverride={ - type === "limit" ? swapState.priceState.priceFiat : undefined + type === "limit" + ? new PricePretty( + DEFAULT_VS_CURRENCY, + swapState.priceState.spotPrice + ) + : undefined } />
@@ -596,11 +644,14 @@ export const PlaceLimitTool: FunctionComponent = observer( confirmAction={async () => { setIsSendingTx(true); await swapState.placeLimit(); - refetchOrders(); swapState.reset(); setAmountSafe("fiat", ""); setReviewOpen(false); setIsSendingTx(false); + onOrderSuccess?.( + swapState.baseAsset?.coinDenom, + swapState.quoteAsset?.coinDenom + ); }} outAmountLessSlippage={outAmountLessSlippage} outFiatAmountLessSlippage={outFiatAmountLessSlippage} diff --git a/packages/web/components/place-limit-tool/limit-price-selector.tsx b/packages/web/components/place-limit-tool/limit-price-selector.tsx index c1b1b867b5..454831b1d7 100644 --- a/packages/web/components/place-limit-tool/limit-price-selector.tsx +++ b/packages/web/components/place-limit-tool/limit-price-selector.tsx @@ -97,15 +97,13 @@ export const LimitPriceSelector: FC = ({ )}`; } - return priceState.percentAdjusted.isZero() - ? t("limitOrders.marketPrice") - : `${trimPlaceholderZeros( - formatPretty(priceState.percentAdjusted.mul(new Dec(100)).abs(), { - ...getPriceExtendedFormatOptions(priceState.percentAdjusted), - maxDecimals: 3, - }) - )}%`; - }, [inputMode, priceState.percentAdjusted, priceState.priceFiat, t]); + return `${trimPlaceholderZeros( + formatPretty(priceState.percentAdjusted.mul(new Dec(100)).abs(), { + ...getPriceExtendedFormatOptions(priceState.percentAdjusted), + maxDecimals: 3, + }) + )}%`; + }, [inputMode, priceState.percentAdjusted, priceState.priceFiat]); const percentageSuffix = useMemo(() => { if (priceState.percentAdjusted.isZero()) @@ -153,42 +151,42 @@ export const LimitPriceSelector: FC = ({ className="absolute top-0 h-0.5 w-[512px] -translate-x-5 bg-[#3C356D4A]" style={{ width: width + 40 }} /> - - {orderDirection === "bid" - ? t("limitOrders.aboveMarket.title") - : t("limitOrders.belowMarket.title")} - - } - body={ - - {orderDirection === "bid" - ? t("limitOrders.aboveMarket.description") - : t("limitOrders.belowMarket.description")} - - } - > - - + + + + + ) : ( + + {t(priceState.priceError)} + + )}
- - {poolMarketMetrics?.volume24hUsd && ( - - )} - + {pool?.market?.volume24hUsd && ( + + )} = observer( poolId: pool.id, }); - const { data: poolIncentives, isLoading: isPoolIncentivesLoading } = - api.edge.pools.getPoolIncentives.useQuery({ - poolId: pool.id, - }); - const { data: poolMarketMetrics, isLoading: isPoolMarketMetricsLoading } = - api.edge.pools.getPoolMarketMetrics.useQuery({ poolId: pool.id }); - const { data: bondDurations_, isLoading: isLoadingBondDurations, @@ -434,24 +426,14 @@ export const SharePool: FunctionComponent<{ pool: Pool }> = observer(
- {(poolMarketMetrics?.volume24hUsd || - isPoolMarketMetricsLoading) && ( + {pool.market?.volume24hUsd && (
{t("pool.24hrTradingVolume")} - - {poolMarketMetrics?.volume24hUsd && ( -

- {poolMarketMetrics.volume24hUsd.toString()} -

- )} -
+

+ {pool.market.volume24hUsd.toString()} +

)} @@ -608,14 +590,10 @@ export const SharePool: FunctionComponent<{ pool: Pool }> = observer(
{t("pool.earnSwapFees")}
- {isPoolIncentivesLoading ? ( - - ) : ( - poolIncentives?.aprBreakdown?.swapFee?.upper && ( -
{`${poolIncentives.aprBreakdown.swapFee.upper - .maxDecimals(2) - .toString()} ${t("pool.APR")}`}
- ) + {pool.incentives?.aprBreakdown?.swapFee?.upper && ( +
{`${pool.incentives.aprBreakdown.swapFee.upper + .maxDecimals(2) + .toString()} ${t("pool.APR")}`}
)}
diff --git a/packages/web/components/swap-tool/alt.tsx b/packages/web/components/swap-tool/alt.tsx index 3137771c48..96550606d0 100644 --- a/packages/web/components/swap-tool/alt.tsx +++ b/packages/web/components/swap-tool/alt.tsx @@ -27,6 +27,7 @@ import { } from "~/components/complex/asset-fieldset"; import { tError } from "~/components/localization"; import { TradeDetails } from "~/components/swap-tool/trade-details"; +import { GenericDisclaimer } from "~/components/tooltip/generic-disclaimer"; import { Button } from "~/components/ui/button"; import { EventName, EventPage } from "~/config"; import { @@ -215,7 +216,6 @@ export const AltSwapTool: FunctionComponent = observer( feeValueUsd: Number(swapState.totalFee?.toString() ?? "0"), page, quoteTimeMilliseconds: swapState.quote?.timeMs, - router: swapState.quote?.name, }; logEvent([EventName.Swap.swapStarted, baseEvent]); setIsSendingTx(true); @@ -482,9 +482,9 @@ export const AltSwapTool: FunctionComponent = observer( />
- = observer( )} > {swapState.tokenOutFiatValue ? ( - - {formatPretty( - swapState.tokenOutFiatValue, - swapState.tokenOutFiatValue?.toDec().gt(new Dec(0)) - ? { - ...getPriceExtendedFormatOptions( - swapState.tokenOutFiatValue.toDec() - ), - } - : undefined - )} + <> + + {formatPretty( + swapState.tokenOutFiatValue, + swapState.tokenOutFiatValue?.toDec().gt(new Dec(0)) + ? { + ...getPriceExtendedFormatOptions( + swapState.tokenOutFiatValue.toDec() + ), + } + : undefined + )} + = observer( "text-rust-400": showOutputDifferenceWarning, "text-osmoverse-600": !showOutputDifferenceWarning, + hidden: outputDifference + .toDec() + .lt(new Dec(0.01)), } )} - >{` (-${outputDifference})`} - + > + {` (-${outputDifference})`} + + ) : ( "" )} - +
@@ -545,8 +556,10 @@ export const AltSwapTool: FunctionComponent = observer( (account?.walletStatus === WalletStatus.Connected && (swapState.inAmountInput.isEmpty || !Boolean(swapState.quote) || + isSwapToolLoading || Boolean(swapState.error) || - account?.txTypeInProgress !== "")) + (Boolean(swapState.networkFeeError) && + !swapState.hasOverSpendLimitError))) } isLoading={ /** @@ -575,8 +588,7 @@ export const AltSwapTool: FunctionComponent = observer( data-testid="trade-button-swap" >
- {account?.walletStatus === WalletStatus.Connected || - isSwapToolLoading + {account?.walletStatus === WalletStatus.Connected ? buttonText : t("connectWallet")}
diff --git a/packages/web/components/swap-tool/index.tsx b/packages/web/components/swap-tool/index.tsx index f6fe961807..f1c009d0a2 100644 --- a/packages/web/components/swap-tool/index.tsx +++ b/packages/web/components/swap-tool/index.tsx @@ -201,7 +201,6 @@ export const SwapTool: FunctionComponent = observer( feeValueUsd: Number(swapState.totalFee?.toString() ?? "0"), page, quoteTimeMilliseconds: swapState.quote?.timeMs, - router: swapState.quote?.name, }; logEvent([EventName.Swap.swapStarted, baseEvent]); setIsSendingTx(true); diff --git a/packages/web/components/swap-tool/order-type-selector.tsx b/packages/web/components/swap-tool/order-type-selector.tsx index 113311fb5e..27640dd658 100644 --- a/packages/web/components/swap-tool/order-type-selector.tsx +++ b/packages/web/components/swap-tool/order-type-selector.tsx @@ -13,9 +13,17 @@ interface UITradeType { disabled: boolean; } +interface OrderTypeSelectorProps { + initialQuoteDenom?: string; + initialBaseDenom?: string; +} + export const TRADE_TYPES = ["market", "limit"] as const; -export const OrderTypeSelector = () => { +export const OrderTypeSelector = ({ + initialQuoteDenom, + initialBaseDenom, +}: OrderTypeSelectorProps) => { const { t } = useTranslation(); const { logEvent } = useAmplitudeAnalytics(); @@ -23,13 +31,16 @@ export const OrderTypeSelector = () => { "type", parseAsStringLiteral(TRADE_TYPES).withDefault("market") ); - const [base] = useQueryState("from", parseAsString.withDefault("ATOM")); + const [base] = useQueryState( + "from", + parseAsString.withDefault(initialBaseDenom || "ATOM") + ); const [quote, setQuote] = useQueryState( "quote", - parseAsString.withDefault("USDC") + parseAsString.withDefault(initialQuoteDenom || "USDC") ); - const { selectableBaseAssets, selectableQuoteDenoms } = + const { selectableBaseAssets, selectableQuoteDenoms, isLoading } = useOrderbookSelectableDenoms(); const hasOrderbook = useMemo( @@ -42,7 +53,7 @@ export const OrderTypeSelector = () => { }, [base, selectableQuoteDenoms]); useEffect(() => { - if (type === "limit" && !hasOrderbook) { + if (type === "limit" && !hasOrderbook && !isLoading) { setType("market"); } else if ( type === "limit" && @@ -51,7 +62,15 @@ export const OrderTypeSelector = () => { ) { setQuote(selectableQuotes[0].coinDenom); } - }, [hasOrderbook, setType, type, selectableQuotes, setQuote, quote]); + }, [ + hasOrderbook, + setType, + type, + selectableQuotes, + setQuote, + quote, + isLoading, + ]); useEffect(() => { switch (type) { @@ -80,10 +99,10 @@ export const OrderTypeSelector = () => { { id: "limit", title: t("limitOrders.limit"), - disabled: !hasOrderbook, + disabled: isLoading || !hasOrderbook, }, ], - [hasOrderbook, t] + [hasOrderbook, isLoading, t] ); return ( @@ -96,7 +115,9 @@ export const OrderTypeSelector = () => { disabled={!disabled} title={t("limitOrders.unavailable", { denom: base })} key={`order-type-selector-${id}`} - containerClassName="!w-fit" + containerClassName={classNames("!w-fit", { + hidden: isLoading, + })} > + )} - )} - -
- - - - )} - - - - ); -}); + + + + + )} + + + + ); + } +); function HighestBalanceAssetsIcons({ userOsmoAddress, diff --git a/packages/web/components/swap-tool/trade-details.tsx b/packages/web/components/swap-tool/trade-details.tsx index 3d8367f15a..e48df100fd 100644 --- a/packages/web/components/swap-tool/trade-details.tsx +++ b/packages/web/components/swap-tool/trade-details.tsx @@ -49,7 +49,7 @@ export const TradeDetails = observer( const { t } = useTranslation(); const routesVisDisclosure = useDisclosure(); - const [outAsBase, setOutAsBase] = useState(!tab || tab === "buy"); + const [outAsBase, setOutAsBase] = useState(tab === "buy"); const [details, { height: detailsHeight }] = useMeasure(); @@ -94,8 +94,8 @@ export const TradeDetails = observer( isLoaded={Boolean(swapState?.inBaseOutQuoteSpotPrice)} >
{isLoading && ( @@ -170,8 +170,8 @@ export const TradeDetails = observer( {t("assets.transfer.priceImpact")} @@ -221,8 +221,8 @@ export const TradeDetails = observer( {t("pools.aprBreakdown.swapFees")} @@ -279,19 +279,13 @@ export const TradeDetails = observer( <> - If there’s no direct market between the - assets you’re trading, Osmosis will try to - make the trade happen by making a series of - trades with other assets to get the best - price at any given time. + {t("tradeDetails.tradeRoute.contentTop")}

- For optimal efficiency based on available - liquidity, sometimes trades will be split - into multiple routes with different assets. + {t("tradeDetails.tradeRoute.contentBottom")} } > diff --git a/packages/web/components/table/asset-balances.tsx b/packages/web/components/table/asset-balances.tsx index 1affa9ec67..d49cf2705f 100644 --- a/packages/web/components/table/asset-balances.tsx +++ b/packages/web/components/table/asset-balances.tsx @@ -29,6 +29,7 @@ import { useWalletSelect, useWindowSize, } from "~/hooks"; +import { useBridge } from "~/hooks/bridge"; import { useShowPreviewAssets } from "~/hooks/use-show-preview-assets"; import { ActivateUnverifiedTokenConfirmation, @@ -59,11 +60,7 @@ const DUST_THRESHOLD = new Dec(0.01); export const AssetBalancesTable: FunctionComponent<{ /** Height of elements above the table in the window. Nav bar is already included. */ tableTopPadding?: number; - /** Memoized function for handling deposits from table row. */ - onDeposit: (coinDenom: string) => void; - /** Memoized function for handling withdrawals from table row. */ - onWithdraw: (coinDenom: string) => void; -}> = observer(({ tableTopPadding = 0, onDeposit, onWithdraw }) => { +}> = observer(({ tableTopPadding = 0 }) => { const { accountStore, userSettings } = useStore(); const account = accountStore.getWallet(accountStore.osmosisChainId); const { isLoading: isLoadingWallet } = useWalletSelect(); @@ -147,7 +144,7 @@ export const AssetBalancesTable: FunctionComponent<{ } ); - const [hideDust, setHideDust] = useState(false); + const [hideDust, setHideDust] = useState(true); const assetsData = useMemo( () => assetPagesData?.pages.flatMap((page) => page?.items) ?? [], @@ -218,24 +215,13 @@ export const AssetBalancesTable: FunctionComponent<{ cell: ({ row: { original: asset } }) => ( ), }), ]; - }, [ - sortKey, - sortDirection, - showUnverifiedAssets, - onDeposit, - onWithdraw, - setSortKey, - t, - ]); + }, [sortKey, sortDirection, showUnverifiedAssets, setSortKey, t]); /** Columns collapsed for screen size responsiveness. */ const collapsedColumns = useMemo(() => { @@ -322,7 +308,7 @@ export const AssetBalancesTable: FunctionComponent<{ className="my-3 !w-[33.25rem] xl:!w-96" currentValue={searchQuery?.query ?? ""} onInput={onSearchInput} - placeholder={t("assets.table.search")} + placeholder={t("portfolio.searchBalances")} debounce={500} />
- {filteredAssetsData.length > 0 && ( + {assetsData.length > 0 && (

)} - {!needsActivation && - Boolean(counterparty.length) && - Boolean(transferMethods.length) && ( - + + )} - {!needsActivation && - amount?.toDec().isPositive() && - Boolean(counterparty.length) && - Boolean(transferMethods.length) && ( - + + )} +

); }; diff --git a/packages/web/components/table/cells/asset.tsx b/packages/web/components/table/cells/asset.tsx index 876aa4e1a9..6c7b19878f 100644 --- a/packages/web/components/table/cells/asset.tsx +++ b/packages/web/components/table/cells/asset.tsx @@ -27,7 +27,7 @@ export const AssetCell: FunctionComponent< const { t } = useTranslation(); return ( -
+
{isInUserWatchlist !== undefined && onClickWatchlist && (
>) { return ( @@ -32,9 +36,12 @@ export function GenericDisclaimer({
} + className={tooltipClassName} enablePropagation > -
{children}
+
+ {children} +
); } diff --git a/packages/web/components/trade-tool/index.tsx b/packages/web/components/trade-tool/index.tsx index 2a0fe60ca2..d171de8325 100644 --- a/packages/web/components/trade-tool/index.tsx +++ b/packages/web/components/trade-tool/index.tsx @@ -15,16 +15,18 @@ import { } from "~/components/swap-tool/swap-tool-tabs"; import { EventName, EventPage } from "~/config"; import { useAmplitudeAnalytics, useTranslation } from "~/hooks"; -import { useOrderbookAllActiveOrders } from "~/hooks/limit-orders/use-orderbook"; +import { PreviousTrade } from "~/pages"; import { useStore } from "~/stores"; export interface TradeToolProps { swapToolProps?: SwapToolProps; page: EventPage; + previousTrade?: PreviousTrade; + setPreviousTrade: (trade: PreviousTrade) => void; } export const TradeTool: FunctionComponent = observer( - ({ page, swapToolProps }) => { + ({ page, swapToolProps, previousTrade, setPreviousTrade }) => { const { logEvent } = useAmplitudeAnalytics(); const { t } = useTranslation(); const [tab, setTab] = useQueryState( @@ -37,12 +39,6 @@ export const TradeTool: FunctionComponent = observer( const { accountStore } = useStore(); const wallet = accountStore.getWallet(accountStore.osmosisChainId); - const { orders, refetch } = useOrderbookAllActiveOrders({ - userAddress: wallet?.address ?? "", - pageSize: 10, - refetchInterval: 4000, - }); - useEffect(() => { switch (tab) { case SwapToolTab.BUY: @@ -68,7 +64,12 @@ export const TradeTool: FunctionComponent = observer(
- {tab !== SwapToolTab.SWAP && } + {tab !== SwapToolTab.SWAP && ( + + )}
{useMemo(() => { @@ -78,7 +79,16 @@ export const TradeTool: FunctionComponent = observer( { + setPreviousTrade({ + sendTokenDenom: quoteDenom ?? "", + outTokenDenom: baseDenom ?? "", + baseDenom: baseDenom ?? "", + quoteDenom: quoteDenom ?? "", + }); + }} /> ); case SwapToolTab.SELL: @@ -86,7 +96,16 @@ export const TradeTool: FunctionComponent = observer( { + setPreviousTrade({ + sendTokenDenom: baseDenom ?? "", + outTokenDenom: quoteDenom ?? "", + baseDenom: baseDenom ?? "", + quoteDenom: quoteDenom ?? "", + }); + }} /> ); case SwapToolTab.SWAP: @@ -96,13 +115,23 @@ export const TradeTool: FunctionComponent = observer( useOtherCurrencies useQueryParams page={page} + onSwapSuccess={({ sendTokenDenom, outTokenDenom }) => { + setPreviousTrade({ + sendTokenDenom, + outTokenDenom, + baseDenom: previousTrade?.baseDenom ?? "", + quoteDenom: previousTrade?.quoteDenom ?? "", + }); + }} + initialSendTokenDenom={previousTrade?.sendTokenDenom} + initialOutTokenDenom={previousTrade?.outTokenDenom} {...swapToolProps} /> ); } - }, [page, swapToolProps, tab, refetch])} + }, [page, swapToolProps, tab, previousTrade, setPreviousTrade])}
- {wallet?.isWalletConnected && orders.length > 0 && ( + {wallet?.isWalletConnected && ( = ({

{title[status]}

{displayFiatPrice(tokenConversion.tokenIn?.value, "", t)}{" "} - {tokenConversion.tokenOut.amount.denom}{" "} + {tokenConversion.tokenIn.amount.denom}{" "} {" "} - {tokenConversion.tokenIn.amount.denom} + {tokenConversion.tokenOut.amount.denom}
) : null; @@ -43,8 +43,8 @@ export const SwapRow: FunctionComponent = ({ const rightComponent = tokenConversion ? (
= ({ className="my-[8px] mx-[4px] text-osmoverse-500" /> window.scrollBy(0, 250)); const bal = this.page .locator(`//tbody/tr//a[contains(@href, "/assets/${token}")]`) .nth(1); @@ -61,4 +64,10 @@ export class PortfolioPage extends BasePage { await this.page.waitForTimeout(1000); } } + + async searchForToken(tokenName: string) { + await this.searchInput.fill(tokenName); + // we expect that after 2 seconds tokens are loaded and any failure after this point should be considered a bug. + await this.page.waitForTimeout(2000); + } } diff --git a/packages/web/e2e/pages/trade-page.ts b/packages/web/e2e/pages/trade-page.ts index b43510a371..145994084b 100644 --- a/packages/web/e2e/pages/trade-page.ts +++ b/packages/web/e2e/pages/trade-page.ts @@ -164,11 +164,14 @@ export class TradePage extends BasePage { async selectPair(from: string, to: string) { // Filter does not show already selected tokens console.log("Select pair " + from + " to " + to); - const tokenLocator = "//div//button[@type]//img[@alt]"; - const fromToken = this.page.locator(tokenLocator).nth(0); - const toToken = this.page.locator(tokenLocator).nth(1); + const fromToken = this.page.locator( + "//div//button[@data-testid='token-in']//img[@alt]" + ); + const toToken = this.page.locator( + "//div//button[@data-testid='token-out']//img[@alt]" + ); // Select From Token - await fromToken.click(); + await fromToken.click({ timeout: 4000 }); // we expect that after 1 second token filter is displayed. await this.page.waitForTimeout(1000); await this.page.getByPlaceholder("Search").fill(from); @@ -177,9 +180,9 @@ export class TradePage extends BasePage { `//div/button[@data-testid='token-select-asset']//span[.='${from}']` ) .first(); - await fromLocator.click(); + await fromLocator.click({ timeout: 4000 }); // Select To Token - await toToken.click(); + await toToken.click({ timeout: 4000 }); // we expect that after 1 second token filter is displayed. await this.page.waitForTimeout(1000); await this.page.getByPlaceholder("Search").fill(to); diff --git a/packages/web/e2e/pages/transactions-page.ts b/packages/web/e2e/pages/transactions-page.ts index 1072e0d141..d3ff0d68be 100644 --- a/packages/web/e2e/pages/transactions-page.ts +++ b/packages/web/e2e/pages/transactions-page.ts @@ -71,7 +71,7 @@ export class TransactionsPage extends BasePage { ) { const cancelBtn = `//td//span[.='${amount}']/../../../../..//td//p[.='$${price}']/../../..//button`; console.log("Use locator for a cancel btn: " + cancelBtn); - await this.page.locator(cancelBtn).click(); + await this.page.locator(cancelBtn).first().click(); const pageApprove = context.waitForEvent("page"); const approvePage = await pageApprove; await approvePage.waitForLoadState(); diff --git a/packages/web/e2e/test-config.ts b/packages/web/e2e/test-config.ts index 422f7b8d6d..a72bf1ff44 100644 --- a/packages/web/e2e/test-config.ts +++ b/packages/web/e2e/test-config.ts @@ -50,7 +50,7 @@ export class TestConfig { } getBrowserConfig(headless: boolean) { - const USE_PROXY: boolean = process.env.USE_TEST_PROXY === "true"; + const USE_PROXY: boolean = process.env.USE_TEST_PROXY === "use"; const viewport = { width: 1440, height: 1280 }; if (USE_PROXY) { console.info( diff --git a/packages/web/e2e/tests/filled.wallet.spec.ts b/packages/web/e2e/tests/monitoring.wallet.spec.ts similarity index 86% rename from packages/web/e2e/tests/filled.wallet.spec.ts rename to packages/web/e2e/tests/monitoring.wallet.spec.ts index a817090e7a..c7c952351d 100644 --- a/packages/web/e2e/tests/filled.wallet.spec.ts +++ b/packages/web/e2e/tests/monitoring.wallet.spec.ts @@ -30,7 +30,7 @@ test.describe("Test Filled Order feature", () => { const walletPage = new WalletPage(page); // Import existing Wallet (could be aggregated in one function). await walletPage.importWalletWithPrivateKey(privateKey); - await walletPage.setWalletNameAndPassword("Test Trades", password); + await walletPage.setWalletNameAndPassword("Monitoring E2E Tests", password); await walletPage.selectChainsAndSave(); await walletPage.finish(); // Switch to Application @@ -49,16 +49,17 @@ test.describe("Test Filled Order feature", () => { await tradePage.openSellTab(); await tradePage.openLimit(); await tradePage.selectAsset("OSMO"); - await tradePage.enterAmount("0.35"); - await tradePage.setLimitPriceChange("5%"); + await tradePage.enterAmount("2.99"); + await tradePage.setLimitPriceChange("Market"); const { msgContentAmount } = await tradePage.limitSellAndGetWalletMsg( context ); expect(msgContentAmount).toBeTruthy(); - expect(msgContentAmount).toContain("0.35 OSMO"); + expect(msgContentAmount).toContain("2.99 OSMO"); expect(msgContentAmount).toContain("place_limit"); expect(msgContentAmount).toContain('"order_direction": "ask"'); await tradePage.isTransactionSuccesful(); + await tradePage.getTransactionUrl(); }); test("User should be able to limit buy OSMO", async () => { @@ -66,19 +67,20 @@ test.describe("Test Filled Order feature", () => { await tradePage.openBuyTab(); await tradePage.openLimit(); await tradePage.selectAsset("OSMO"); - await tradePage.enterAmount("0.11"); + await tradePage.enterAmount("1.05"); await tradePage.setLimitPriceChange("Market"); const limitPrice = Number(await tradePage.getLimitPrice()); - const highLimitPrice = (limitPrice * 1.2).toFixed(4); + const highLimitPrice = (limitPrice * 1.1).toFixed(4); await tradePage.setLimitPrice(String(highLimitPrice)); const { msgContentAmount } = await tradePage.limitBuyAndGetWalletMsg( context ); expect(msgContentAmount).toBeTruthy(); - expect(msgContentAmount).toContain("0.11 USDC (Noble/channel-750)"); + expect(msgContentAmount).toContain('"quantity": "1050000"'); expect(msgContentAmount).toContain("place_limit"); expect(msgContentAmount).toContain('"order_direction": "bid"'); await tradePage.isTransactionSuccesful(); + await tradePage.getTransactionUrl(); await tradePage.gotoOrdersHistory(30); const p = context.pages()[0]; const trxPage = new TransactionsPage(p); diff --git a/packages/web/e2e/tests/pools.spec.ts b/packages/web/e2e/tests/pools.spec.ts index 2e2fde176d..ca0b547c9e 100644 --- a/packages/web/e2e/tests/pools.spec.ts +++ b/packages/web/e2e/tests/pools.spec.ts @@ -34,8 +34,10 @@ test.describe("Test Select Pool feature", () => { }); test("User should be able to select ATOM/USDC pool", async () => { + const poolName = "ATOM/USDC"; await poolsPage.goto(); - const poolPage = await poolsPage.viewPool(1282, "ATOM/USDC"); + await poolsPage.searchForPool(poolName); + const poolPage = await poolsPage.viewPool(1282, poolName); const balance = await poolPage.getBalance(); expect(balance).toEqual("$0"); const tradeModal = await poolPage.getTradeModal(); @@ -46,8 +48,10 @@ test.describe("Test Select Pool feature", () => { }); test("User should be able to select OSMO/USDC pool", async () => { + const poolName = "OSMO/USDC"; await poolsPage.goto(); - const poolPage = await poolsPage.viewPool(1464, "OSMO/USDC"); + await poolsPage.searchForPool(poolName); + const poolPage = await poolsPage.viewPool(1464, poolName); const balance = await poolPage.getBalance(); expect(balance).toEqual("$0"); const tradeModal = await poolPage.getTradeModal(); diff --git a/packages/web/e2e/tests/portfolio.wallet.spec.ts b/packages/web/e2e/tests/portfolio.wallet.spec.ts index 7a1a6db745..1a7dd6502b 100644 --- a/packages/web/e2e/tests/portfolio.wallet.spec.ts +++ b/packages/web/e2e/tests/portfolio.wallet.spec.ts @@ -57,35 +57,34 @@ test.describe("Test Portfolio feature", () => { await context.close(); }); - test("User should be able to see native balances", async () => { - const osmoBalance = await portfolioPage.getBalanceFor("OSMO"); - expect(osmoBalance).toMatch(dollarBalanceRegEx); - const atomBalance = await portfolioPage.getBalanceFor("ATOM"); - expect(atomBalance).toMatch(dollarBalanceRegEx); - const usdtBalance = await portfolioPage.getBalanceFor("USDT"); - expect(usdtBalance).toMatch(dollarBalanceRegEx); - const usdcBalance = await portfolioPage.getBalanceFor("USDC"); - expect(usdcBalance).toMatch(dollarBalanceRegEx); - const tiaBalance = await portfolioPage.getBalanceFor("TIA"); - expect(tiaBalance).toMatch(dollarBalanceRegEx); - const daiBalance = await portfolioPage.getBalanceFor("DAI"); - expect(daiBalance).toMatch(dollarBalanceRegEx); + [ + { name: "OSMO" }, + { name: "ATOM" }, + { name: "USDT" }, + { name: "USDC" }, + { name: "TIA" }, + { name: "DAI" }, + ].forEach(({ name }) => { + test(`User should be able to see native balances for ${name}`, async () => { + await portfolioPage.searchForToken(name); + const osmoBalance = await portfolioPage.getBalanceFor(name); + expect(osmoBalance).toMatch(dollarBalanceRegEx); + }); }); - test("User should be able to see bridged balances", async () => { - const injBalance = await portfolioPage.getBalanceFor("INJ"); - expect(injBalance).toMatch(dollarBalanceRegEx); - const ethBalance = await portfolioPage.getBalanceFor("ETH"); - expect(ethBalance).toMatch(dollarBalanceRegEx); - const kujiBalance = await portfolioPage.getBalanceFor("KUJI"); - expect(kujiBalance).toMatch(dollarBalanceRegEx); - const solBalance = await portfolioPage.getBalanceFor("SOL"); - expect(solBalance).toMatch(dollarBalanceRegEx); - const milkTIABalance = await portfolioPage.getBalanceFor("milkTIA"); - expect(milkTIABalance).toMatch(dollarBalanceRegEx); - const abtcBalance = await portfolioPage.getBalanceFor("BTC"); - expect(abtcBalance).toMatch(dollarBalanceRegEx); - const wbtcBalance = await portfolioPage.getBalanceFor("WBTC"); - expect(wbtcBalance).toMatch(dollarBalanceRegEx); + [ + { name: "INJ" }, + { name: "ETH.axl" }, + { name: "KUJI" }, + { name: "SOL" }, + { name: "milkTIA" }, + { name: "BTC" }, + { name: "WBTC" }, + ].forEach(({ name }) => { + test(`User should be able to see bridged balances for ${name}`, async () => { + await portfolioPage.searchForToken(name); + const osmoBalance = await portfolioPage.getBalanceFor(name); + expect(osmoBalance).toMatch(dollarBalanceRegEx); + }); }); }); diff --git a/packages/web/e2e/tests/swap.stables.spec.ts b/packages/web/e2e/tests/swap.stables.spec.ts index 399f62218f..2add868af0 100644 --- a/packages/web/e2e/tests/swap.stables.spec.ts +++ b/packages/web/e2e/tests/swap.stables.spec.ts @@ -67,9 +67,8 @@ test.describe("Test Swap Stables feature", () => { expect(msgContentAmount).toContain("denom: " + USDC); //expect(msgContentAmount).toContain("sender: " + walletId); expect(msgContentAmount).toContain("token_out_denom: " + USDCa); - expect(swapPage.isTransactionBroadcasted(10)); - expect(swapPage.isTransactionSuccesful(10)); - expect(swapPage.getTransactionUrl()).toBeTruthy(); + await swapPage.isTransactionSuccesful(); + await swapPage.getTransactionUrl(); }); test("User should be able to swap USDC.eth.axl to USDC", async () => { @@ -82,9 +81,8 @@ test.describe("Test Swap Stables feature", () => { expect(msgContentAmount).toContain("denom: " + USDCa); //expect(msgContentAmount).toContain("sender: " + walletId); expect(msgContentAmount).toContain("token_out_denom: " + USDC); - expect(swapPage.isTransactionBroadcasted(10)); - expect(swapPage.isTransactionSuccesful(10)); - expect(swapPage.getTransactionUrl()).toBeTruthy(); + await swapPage.isTransactionSuccesful(); + await swapPage.getTransactionUrl(); }); test("User should be able to swap USDT to USDC", async () => { @@ -97,9 +95,8 @@ test.describe("Test Swap Stables feature", () => { expect(msgContentAmount).toContain(USDC); //expect(msgContentAmount).toContain("sender: " + walletId); expect(msgContentAmount).toContain(allUSDT); - expect(swapPage.isTransactionBroadcasted(10)); - expect(swapPage.isTransactionSuccesful(10)); - expect(swapPage.getTransactionUrl()).toBeTruthy(); + await swapPage.isTransactionSuccesful(); + await swapPage.getTransactionUrl(); }); test("User should be able to swap USDC to USDT", async () => { @@ -112,8 +109,7 @@ test.describe("Test Swap Stables feature", () => { expect(msgContentAmount).toContain("denom: " + USDC); //expect(msgContentAmount).toContain("sender: " + walletId); expect(msgContentAmount).toContain(allUSDT); - expect(swapPage.isTransactionBroadcasted(10)); - expect(swapPage.isTransactionSuccesful(10)); - expect(swapPage.getTransactionUrl()).toBeTruthy(); + await swapPage.isTransactionSuccesful(); + await swapPage.getTransactionUrl(); }); }); diff --git a/packages/web/e2e/tests/trade.wallet.spec.ts b/packages/web/e2e/tests/trade.wallet.spec.ts index 310ed250d3..8ec907efb6 100644 --- a/packages/web/e2e/tests/trade.wallet.spec.ts +++ b/packages/web/e2e/tests/trade.wallet.spec.ts @@ -11,8 +11,6 @@ import { WalletPage } from "../pages/wallet-page"; test.describe("Test Trade feature", () => { let context: BrowserContext; - const walletId = - process.env.WALLET_ID ?? "osmo1ka7q9tykdundaanr07taz3zpt5k72c0ut5r4xa"; const privateKey = process.env.PRIVATE_KEY ?? "private_key"; const password = process.env.PASSWORD ?? "TestPassword2024."; let tradePage: TradePage; @@ -54,11 +52,10 @@ test.describe("Test Trade feature", () => { await tradePage.goto(); await tradePage.openBuyTab(); await tradePage.selectAsset("OSMO"); - await tradePage.enterAmount("0.1"); + await tradePage.enterAmount("1.1"); const { msgContentAmount } = await tradePage.buyAndGetWalletMsg(context); expect(msgContentAmount).toBeTruthy(); expect(msgContentAmount).toContain("token_out_denom: uosmo"); - expect(msgContentAmount).toContain("sender: " + walletId); expect(msgContentAmount).toContain("type: osmosis/poolmanager/"); expect(msgContentAmount).toContain("denom: " + USDC); await tradePage.isTransactionSuccesful(); @@ -70,11 +67,10 @@ test.describe("Test Trade feature", () => { await tradePage.goto(); await tradePage.openSellTab(); await tradePage.selectAsset("ATOM"); - await tradePage.enterAmount("0.01"); + await tradePage.enterAmount("0.25"); const { msgContentAmount } = await tradePage.sellAndGetWalletMsg(context); expect(msgContentAmount).toBeTruthy(); expect(msgContentAmount).toContain("token_out_denom: " + USDC); - expect(msgContentAmount).toContain("sender: " + walletId); expect(msgContentAmount).toContain("type: osmosis/poolmanager/"); expect(msgContentAmount).toContain("denom: " + ATOM); await tradePage.isTransactionSuccesful(); @@ -83,40 +79,51 @@ test.describe("Test Trade feature", () => { test("User should be able to limit sell ATOM", async () => { await tradePage.goto(); + const amount = "0.25"; await tradePage.openSellTab(); await tradePage.openLimit(); await tradePage.selectAsset("ATOM"); - await tradePage.enterAmount("0.01"); + await tradePage.enterAmount(amount); await tradePage.setLimitPriceChange("5%"); + const limitPrice = await tradePage.getLimitPrice(); const { msgContentAmount } = await tradePage.limitSellAndGetWalletMsg( context ); expect(msgContentAmount).toBeTruthy(); - expect(msgContentAmount).toContain("0.01 ATOM (Cosmos Hub/channel-0)"); + expect(msgContentAmount).toContain(amount + " ATOM (Cosmos Hub/channel-0)"); expect(msgContentAmount).toContain("place_limit"); expect(msgContentAmount).toContain('"order_direction": "ask"'); await tradePage.isTransactionSuccesful(); + await tradePage.getTransactionUrl(); + await tradePage.gotoOrdersHistory(); + const trxPage = new TransactionsPage(context.pages()[0]); + await trxPage.cancelLimitOrder(`${amount} ATOM`, limitPrice, context); + await tradePage.isTransactionSuccesful(); + await tradePage.getTransactionUrl(); }); test("User should be able to cancel limit sell OSMO", async () => { await tradePage.goto(); + const amount = "2.59"; await tradePage.openSellTab(); await tradePage.openLimit(); await tradePage.selectAsset("OSMO"); - await tradePage.enterAmount("0.2"); + await tradePage.enterAmount(amount); await tradePage.setLimitPriceChange("10%"); const limitPrice = await tradePage.getLimitPrice(); const { msgContentAmount } = await tradePage.limitSellAndGetWalletMsg( context ); expect(msgContentAmount).toBeTruthy(); - expect(msgContentAmount).toContain("0.2 OSMO"); + expect(msgContentAmount).toContain(`${amount} OSMO`); expect(msgContentAmount).toContain("place_limit"); expect(msgContentAmount).toContain('"order_direction": "ask"'); await tradePage.isTransactionSuccesful(); + await tradePage.getTransactionUrl(); await tradePage.gotoOrdersHistory(); const trxPage = new TransactionsPage(context.pages()[0]); - await trxPage.cancelLimitOrder("0.2 OSMO", limitPrice, context); + await trxPage.cancelLimitOrder(`${amount} OSMO`, limitPrice, context); await tradePage.isTransactionSuccesful(); + await tradePage.getTransactionUrl(); }); }); diff --git a/packages/web/e2e/tests/transactions.wallet.spec.ts b/packages/web/e2e/tests/transactions.wallet.spec.ts index f62944b009..1f0f4ea2e4 100644 --- a/packages/web/e2e/tests/transactions.wallet.spec.ts +++ b/packages/web/e2e/tests/transactions.wallet.spec.ts @@ -58,7 +58,7 @@ test.describe("Test Transactions feature", () => { await transactionsPage.viewTransactionByNumber(20); await transactionsPage.viewOnExplorerIsVisible(); await transactionsPage.closeTransaction(); - await transactionsPage.viewTransactionByNumber(55); + await transactionsPage.viewTransactionByNumber(45); await transactionsPage.viewOnExplorerIsVisible(); await transactionsPage.closeTransaction(); }); diff --git a/packages/web/hooks/input/use-amount-input.ts b/packages/web/hooks/input/use-amount-input.ts index 38076ccf9a..cb1617dd33 100644 --- a/packages/web/hooks/input/use-amount-input.ts +++ b/packages/web/hooks/input/use-amount-input.ts @@ -54,12 +54,13 @@ export function useAmountInput({ const setAmount = useCallback( (amount: string) => { let updatedAmount = amount.trim(); - // check validity of raw input - if (!isValidNumericalRawInput(updatedAmount)) return; if (updatedAmount.startsWith(".")) { updatedAmount = "0" + updatedAmount; } + // check validity of raw input + if (!isValidNumericalRawInput(updatedAmount)) return; + if (fraction != null) { setFraction(null); } diff --git a/packages/web/hooks/limit-orders/use-orderbook.ts b/packages/web/hooks/limit-orders/use-orderbook.ts index 5f92b1b949..215c20d642 100644 --- a/packages/web/hooks/limit-orders/use-orderbook.ts +++ b/packages/web/hooks/limit-orders/use-orderbook.ts @@ -203,17 +203,16 @@ export const useOrderbook = ({ const error = useMemo(() => { if ( - !Boolean(orderbook) || - !Boolean(orderbook!.poolId) || - orderbook!.poolId === "" + !isOrderbookLoading && + (!orderbook || !orderbook!.poolId || orderbook!.poolId === "") ) { return "errors.noOrderbook"; } - if (Boolean(makerFeeError)) { + if (makerFeeError) { return makerFeeError?.message; } - }, [orderbook, makerFeeError]); + }, [orderbook, makerFeeError, isOrderbookLoading]); return { orderbook, diff --git a/packages/web/hooks/limit-orders/use-place-limit.ts b/packages/web/hooks/limit-orders/use-place-limit.ts index e016f4c898..aa611e00f6 100644 --- a/packages/web/hooks/limit-orders/use-place-limit.ts +++ b/packages/web/hooks/limit-orders/use-place-limit.ts @@ -1,8 +1,9 @@ import { CoinPretty, Dec, Int, PricePretty } from "@keplr-wallet/unit"; import { priceToTick } from "@osmosis-labs/math"; import { DEFAULT_VS_CURRENCY } from "@osmosis-labs/server"; -import { cosmwasmMsgOpts } from "@osmosis-labs/stores"; +import { makeExecuteCosmwasmContractMsg } from "@osmosis-labs/tx"; import { useCallback, useEffect, useMemo, useState } from "react"; +import { useAsync } from "react-use"; import { tError } from "~/components/localization"; import { EventName, EventPage } from "~/config"; @@ -29,6 +30,9 @@ function getNormalizationFactor( export type OrderDirection = "bid" | "ask"; +export const MIN_ORDER_VALUE = + process.env.NEXT_PUBLIC_LIMIT_ORDER_MIN_AMOUNT ?? ""; + export interface UsePlaceLimitParams { osmosisChainId: string; orderDirection: OrderDirection; @@ -248,13 +252,13 @@ export const usePlaceLimit = ({ isMarket, ]); - const encodedMsg = useMemo(() => { + const { value: encodedMsg } = useAsync(async () => { if (!placeLimitMsg) return; - return cosmwasmMsgOpts.executeWasm.messageComposer({ + return await makeExecuteCosmwasmContractMsg({ contract: orderbookContractAddress, sender: account?.address ?? "", - msg: Buffer.from(JSON.stringify(placeLimitMsg)), + msg: placeLimitMsg, funds: [ { denom: paymentTokenValue.toCoin().denom, @@ -293,7 +297,6 @@ export const usePlaceLimit = ({ feeValueUsd: Number(marketState.totalFee?.toString() ?? "0"), page, quoteTimeMilliseconds: marketState.quote?.timeMs, - router: marketState.quote?.name, }; try { logEvent([EventName.Swap.swapStarted, baseEvent]); @@ -375,33 +378,43 @@ export const usePlaceLimit = ({ placeLimitMsg, ]); - const { data: balances, isLoading: isBalancesLoading } = - api.local.balances.getUserBalances.useQuery( - { bech32Address: account?.address ?? "" }, + const { data, isLoading: isBalancesLoading } = + api.edge.assets.getUserAssets.useQuery( + { + userOsmoAddress: account?.address ?? "", + }, { enabled: !!account?.address, - select: (balances) => - balances.filter( - ({ denom }) => - denom === baseAsset?.coinMinimalDenom || - denom === quoteAsset?.coinMinimalDenom + select: ({ items }) => + items.filter( + ({ coinMinimalDenom }) => + coinMinimalDenom.toLowerCase() === + baseAsset?.coinMinimalDenom?.toLowerCase() || + coinMinimalDenom.toLowerCase() === + quoteAsset?.coinMinimalDenom?.toLowerCase() ), } ); - const quoteTokenBalance = useMemo(() => { - if (!balances) return; - - return balances.find(({ denom }) => denom === quoteAsset?.coinMinimalDenom) - ?.coin; - }, [balances, quoteAsset]); - - const baseTokenBalance = useMemo(() => { - if (!balances) return; + const baseTokenBalance = useMemo( + () => + data?.find( + ({ coinMinimalDenom }) => + coinMinimalDenom.toLowerCase() === + baseAsset?.coinMinimalDenom.toLowerCase() + )?.amount, + [data, baseAsset] + ); + const quoteTokenBalance = useMemo( + () => + data?.find( + ({ coinMinimalDenom }) => + coinMinimalDenom.toLowerCase() === + quoteAsset?.coinMinimalDenom?.toLowerCase() + )?.amount, + [data, quoteAsset] + ); - return balances.find(({ denom }) => denom === baseAsset?.coinMinimalDenom) - ?.coin; - }, [balances, baseAsset]); const insufficientFunds = useMemo(() => { return orderDirection === "bid" ? (quoteTokenBalance?.toDec() ?? new Dec(0)).lt( @@ -470,6 +483,7 @@ export const usePlaceLimit = ({ priceState.reset(); marketState.inAmountInput.reset(); }, [inAmountInput, priceState, marketState]); + const error = useMemo(() => { if (!isMarket && orderbookError) { return orderbookError; @@ -492,6 +506,16 @@ export const usePlaceLimit = ({ return priceState.priceError; } + if ( + !isMarket && + !!MIN_ORDER_VALUE && + isValidNumericalRawInput(MIN_ORDER_VALUE) && + !!paymentFiatValue && + paymentFiatValue?.toDec().lt(new Dec(MIN_ORDER_VALUE)) + ) { + return "limitOrders.belowMinimumAmount"; + } + return; }, [ insufficientFunds, @@ -500,6 +524,7 @@ export const usePlaceLimit = ({ paymentTokenValue, orderbookError, priceState.priceError, + paymentFiatValue, ]); const shouldEstimateLimitGas = useMemo(() => { diff --git a/packages/web/hooks/ui-config/use-add-concentrated-liquidity-config.ts b/packages/web/hooks/ui-config/use-add-concentrated-liquidity-config.ts index 537b5722a2..42e509dd84 100644 --- a/packages/web/hooks/ui-config/use-add-concentrated-liquidity-config.ts +++ b/packages/web/hooks/ui-config/use-add-concentrated-liquidity-config.ts @@ -368,21 +368,15 @@ export class ObservableAddConcentratedLiquidityConfig { /** Current price adjusted with base and quote token decimals. */ @computed get currentPriceWithDecimals(): Dec { - const queryPool = this.queriesStore - .get(this.chainId) - .osmosis!.queryPools.getPool(this.poolId); - - return queryPool?.concentratedLiquidityPoolInfo?.currentPrice ?? new Dec(0); + return this.pool?.currentPrice ?? new Dec(0); } /** Current price, without currency decimals. */ @computed get currentPrice(): Dec { - return ( - this.pool?.currentSqrtPrice - .mul(this.pool?.currentSqrtPrice ?? new Dec(0)) - .toDec() ?? new Dec(0) - ); + const currentSqrtPrice = this.pool?.currentSqrtPrice; + if (!currentSqrtPrice) return new Dec(0); + return currentSqrtPrice.mul(currentSqrtPrice).toDec(); } /** Moderate price range, without currency decimals. */ diff --git a/packages/web/hooks/ui-config/use-remove-concentrated-liquidity-config.ts b/packages/web/hooks/ui-config/use-remove-concentrated-liquidity-config.ts index 5521c74bca..508bba00bf 100644 --- a/packages/web/hooks/ui-config/use-remove-concentrated-liquidity-config.ts +++ b/packages/web/hooks/ui-config/use-remove-concentrated-liquidity-config.ts @@ -18,12 +18,11 @@ export function useRemoveConcentratedLiquidityConfig( config: ObservableRemoveConcentratedLiquidityConfig; removeLiquidity: () => Promise; } { - const { accountStore, queriesStore } = useStore(); + const { accountStore } = useStore(); const { logEvent } = useAmplitudeAnalytics(); const apiUtils = api.useUtils(); const account = accountStore.getWallet(osmosisChainId); - const osmosisQueries = queriesStore.get(osmosisChainId).osmosis!; const [config] = useState( () => @@ -77,13 +76,6 @@ export function useRemoveConcentratedLiquidityConfig( if (tx.code) { reject(tx.rawLog); } else { - // refresh tick data - apiUtils.local.concentratedLiquidity.getLiquidityPerTickRange - .invalidate({ poolId }) - .then(() => resolve()); - // get latest price, if position removed - osmosisQueries.queryPools.getPool(poolId)?.fetch(); - logEvent([ EventName.ConcentratedLiquidity.removeLiquidityCompleted, { @@ -94,7 +86,10 @@ export function useRemoveConcentratedLiquidityConfig( }, ]); - resolve(); + // refresh tick data + apiUtils.local.concentratedLiquidity.getLiquidityPerTickRange + .invalidate({ poolId }) + .finally(() => resolve()); } } ) @@ -110,8 +105,7 @@ export function useRemoveConcentratedLiquidityConfig( logEvent, poolId, position.id, - apiUtils.local.concentratedLiquidity.getLiquidityPerTickRange, - osmosisQueries.queryPools, + apiUtils, ] ); diff --git a/packages/web/hooks/use-feature-flags.ts b/packages/web/hooks/use-feature-flags.ts index 5ae5603b0d..804c4d6cb5 100644 --- a/packages/web/hooks/use-feature-flags.ts +++ b/packages/web/hooks/use-feature-flags.ts @@ -15,9 +15,6 @@ export type AvailableFlags = | "multiBridgeProviders" | "earnPage" | "transactionsPage" - | "sidecarRouter" - | "legacyRouter" - | "tfmRouter" | "osmosisUpdatesPopUp" | "aprBreakdown" | "topAnnouncementBanner" @@ -31,14 +28,10 @@ export type AvailableFlags = | "oneClickTrading" | "limitOrders" | "advancedChart" - | "cypherCard"; + | "cypherCard" + | "newPortfolioPage"; -type ModifiedFlags = - | Exclude - | "_isInitialized" - | "_isClientIDPresent"; - -const defaultFlags: Record = { +const defaultFlags: Record = { staking: true, swapsAdBanner: true, tokenInfo: true, @@ -46,9 +39,6 @@ const defaultFlags: Record = { multiBridgeProviders: true, earnPage: false, transactionsPage: true, - sidecarRouter: true, - legacyRouter: true, - tfmRouter: true, osmosisUpdatesPopUp: false, aprBreakdown: true, topAnnouncementBanner: true, @@ -62,9 +52,8 @@ const defaultFlags: Record = { oneClickTrading: false, limitOrders: true, advancedChart: false, - _isInitialized: false, - _isClientIDPresent: false, cypherCard: false, + newPortfolioPage: false, }; const LIMIT_ORDER_COUNTRY_CODES = @@ -72,7 +61,7 @@ const LIMIT_ORDER_COUNTRY_CODES = s.trim() ) ?? []; -export const useFeatureFlags = () => { +export function useFeatureFlags() { const launchdarklyFlags: Record = useFlags(); const { isMobile } = useWindowSize(); const [isInitialized, setIsInitialized] = useState(false); @@ -120,5 +109,8 @@ export const useFeatureFlags = () => { launchdarklyFlags.limitOrders && (LIMIT_ORDER_COUNTRY_CODES.length === 0 || LIMIT_ORDER_COUNTRY_CODES.includes(levanaGeoblock?.countryCode ?? "")), - } as Record; -}; + } as Record< + AvailableFlags | "_isInitialized" | "_isClientIDPresent", + boolean + >; +} diff --git a/packages/web/hooks/use-ibc-transfer/index.ts b/packages/web/hooks/use-ibc-transfer/index.ts index 03e9591774..c1cbf630da 100644 --- a/packages/web/hooks/use-ibc-transfer/index.ts +++ b/packages/web/hooks/use-ibc-transfer/index.ts @@ -7,6 +7,7 @@ import { OsmosisAccount, UncommitedHistory, } from "@osmosis-labs/stores"; +import { makeIBCTransferMsg } from "@osmosis-labs/tx"; import { useCallback } from "react"; import { useMount, usePrevious, useUpdateEffect } from "react-use"; @@ -71,9 +72,7 @@ export function useIbcTransfer({ const feeConfig = useFakeFeeConfig( chainStore, isWithdraw ? chainId : counterpartyChainId, - isWithdraw - ? account?.cosmos.msgOpts.ibcTransfer.gas ?? 0 - : counterpartyAccount?.cosmos.msgOpts.ibcTransfer.gas ?? 0 + makeIBCTransferMsg.gas ?? 0 ); const amountConfig = useAmountConfig( chainStore, diff --git a/packages/web/hooks/use-swap.tsx b/packages/web/hooks/use-swap.tsx index 91e59f2fb7..9d60003e01 100644 --- a/packages/web/hooks/use-swap.tsx +++ b/packages/web/hooks/use-swap.tsx @@ -1,27 +1,27 @@ -import { StdFee } from "@cosmjs/amino"; +import type { StdFee } from "@cosmjs/amino"; import { CoinPretty, Dec, DecUtils, PricePretty } from "@keplr-wallet/unit"; import { NoRouteError, NotEnoughLiquidityError, NotEnoughQuotedError, } from "@osmosis-labs/pools"; -import type { RouterKey } from "@osmosis-labs/server"; +import { SignOptions } from "@osmosis-labs/stores"; import { makeSplitRoutesSwapExactAmountInMsg, makeSwapExactAmountInMsg, - SignOptions, -} from "@osmosis-labs/stores"; -import { Currency, MinimalAsset } from "@osmosis-labs/types"; +} from "@osmosis-labs/tx"; +import { MinimalAsset } from "@osmosis-labs/types"; import { getAssetFromAssetList, isNil, makeMinimalAsset, sum, } from "@osmosis-labs/utils"; -import { createTRPCReact, TRPCClientError } from "@trpc/react-query"; +import { createTRPCReact } from "@trpc/react-query"; import { parseAsString, useQueryState } from "nuqs"; import { useCallback, useEffect, useMemo, useState } from "react"; import { toast } from "react-toastify"; +import { useAsync } from "react-use"; import { displayToast, ToastType } from "~/components/alert"; import { isOverspendErrorMessage } from "~/components/alert/prettify"; @@ -128,7 +128,7 @@ export function useSwap( const { data: quote, isLoading: isQuoteLoading_, - error: quoteError, + errorMsg: quoteErrorMsg, } = useQueryRouterBestQuote( { tokenIn: swapAssets.fromAsset!, @@ -146,7 +146,7 @@ export function useSwap( const { data: spotPriceQuote, isLoading: isSpotPriceQuoteLoading, - error: spotPriceQuoteError, + errorMsg: spotPriceQuoteErrorMsg, } = useQueryRouterBestQuote( { tokenIn: swapAssets.fromAsset!, @@ -167,25 +167,25 @@ export function useSwap( const precedentError: | (NoRouteError | NotEnoughLiquidityError | Error | undefined) | typeof inAmountInput.error = useMemo(() => { - let error = quoteError; + let errorMsg = quoteErrorMsg; // only show spot price error if there's no quote if ( - (quote && !quote.amount.toDec().isPositive() && !error) || - (!quote && spotPriceQuoteError) + (quote && !quote.amount.toDec().isPositive() && !errorMsg) || + (!quote && spotPriceQuoteErrorMsg) ) - error = spotPriceQuoteError; + errorMsg = spotPriceQuoteErrorMsg; - const errorFromTrpc = makeRouterErrorFromTrpcError(error)?.error; + const errorFromTrpc = makeRouterErrorFromTrpcError(errorMsg)?.error; if (errorFromTrpc) return errorFromTrpc; // prioritize router errors over user input errors if (!inAmountInput.isEmpty && inAmountInput.error) return inAmountInput.error; }, [ - quoteError, + quoteErrorMsg, quote, - spotPriceQuoteError, + spotPriceQuoteErrorMsg, inAmountInput.error, inAmountInput.isEmpty, ]); @@ -257,9 +257,9 @@ export function useSwap( try { txParams = getSwapTxParameters({ coinAmount: inAmountInput.amount.toCoin().amount, - maxSlippage, - fromAsset: swapAssets.fromAsset, - toAsset: swapAssets.toAsset, + maxSlippage: maxSlippage.toString(), + tokenInCoinMinimalDenom: swapAssets.fromAsset.coinMinimalDenom, + tokenOutCoinDecimals: swapAssets.toAsset.coinDecimals, quote, }); } catch (e) { @@ -416,9 +416,9 @@ export function useSwap( useCallback( () => Boolean(quote?.amount.toDec().isPositive()) && - !quoteError && + !quoteErrorMsg && !inAmountInput.isEmpty, - [quote, quoteError, inAmountInput.isEmpty] + [quote, quoteErrorMsg, inAmountInput.isEmpty] ) ); @@ -485,7 +485,7 @@ export function useSwap( quote: isQuoteLoading || inAmountInput.isTyping ? positivePrevQuote - : !Boolean(quoteError) + : !quoteErrorMsg ? quote : undefined, inBaseOutQuoteSpotPrice, @@ -500,7 +500,7 @@ export function useSwap( error: precedentError, spotPriceQuote, isSpotPriceQuoteLoading, - spotPriceQuoteError, + spotPriceQuoteErrorMsg, isQuoteLoading, sendTradeTokenInTx, hasOverSpendLimitError, @@ -786,7 +786,7 @@ export function useSwapAmountInput({ const { data: quoteForCurrentBalance, isLoading: isQuoteForCurrentBalanceLoading_, - error: quoteForCurrentBalanceError, + errorMsg: quoteForCurrentBalanceErrorMsg, } = useQueryRouterBestQuote( { tokenIn: swapAssets.fromAsset!, @@ -822,8 +822,8 @@ export function useSwapAmountInput({ networkFeeQueryEnabled && isLoadingCurrentBalanceNetworkFee_; const hasErrorWithCurrentBalanceQuote = useMemo(() => { - return !!currentBalanceNetworkFeeError || !!quoteForCurrentBalanceError; - }, [currentBalanceNetworkFeeError, quoteForCurrentBalanceError]); + return !!currentBalanceNetworkFeeError || !!quoteForCurrentBalanceErrorMsg; + }, [currentBalanceNetworkFeeError, quoteForCurrentBalanceErrorMsg]); const notEnoughBalanceForMax = useMemo( () => @@ -836,13 +836,10 @@ export function useSwapAmountInput({ currentBalanceNetworkFeeError?.message.includes( "Insufficient alternative balance for transaction fees" ) || - quoteForCurrentBalanceError?.message.includes( + quoteForCurrentBalanceErrorMsg?.includes( "Not enough quoted. Try increasing amount." ), - [ - currentBalanceNetworkFeeError?.message, - quoteForCurrentBalanceError?.message, - ] + [currentBalanceNetworkFeeError?.message, quoteForCurrentBalanceErrorMsg] ); useEffect(() => { @@ -971,33 +968,25 @@ function getSwapTxParameters({ coinAmount, maxSlippage, quote, - fromAsset, - toAsset, + tokenInCoinMinimalDenom, + tokenOutCoinDecimals, }: { coinAmount: string; - maxSlippage: Dec; + maxSlippage: string; quote: | RouterOutputs["local"]["quoteRouter"]["routeTokenOutGivenIn"] | undefined; - fromAsset: MinimalAsset & - Partial<{ - amount: CoinPretty; - usdValue: PricePretty; - }>; - toAsset: MinimalAsset & - Partial<{ - amount: CoinPretty; - usdValue: PricePretty; - }>; + tokenInCoinMinimalDenom: string; + tokenOutCoinDecimals: number; }) { - if (!quote) { + if (isNil(quote)) { throw new Error( "User input should be disabled if no route is found or is being generated" ); } - if (!coinAmount) throw new Error("No input"); - if (!fromAsset) throw new Error("No from asset"); - if (!toAsset) throw new Error("No to asset"); + if (isNil(coinAmount)) throw new Error("No input"); + if (isNil(tokenInCoinMinimalDenom)) throw new Error("No from asset"); + if (isNil(tokenOutCoinDecimals)) throw new Error("No to asset"); /** * Prepare swap data @@ -1034,15 +1023,15 @@ function getSwapTxParameters({ /** In amount converted to integer (remove decimals) */ const tokenIn = { - currency: fromAsset as Currency, + coinMinimalDenom: tokenInCoinMinimalDenom, amount: coinAmount, }; /** Out amount with slippage included */ const tokenOutMinAmount = quote.amount .toDec() - .mul(DecUtils.getTenExponentNInPrecisionRange(toAsset.coinDecimals)) - .mul(new Dec(1).sub(maxSlippage)) + .mul(DecUtils.getTenExponentNInPrecisionRange(tokenOutCoinDecimals)) + .mul(new Dec(1).sub(new Dec(maxSlippage))) .truncate() .toString(); @@ -1053,29 +1042,21 @@ function getSwapTxParameters({ }; } -function getSwapMessages({ +async function getSwapMessages({ coinAmount, maxSlippage, quote, - fromAsset, - toAsset, + tokenInCoinMinimalDenom, + tokenOutCoinDecimals, userOsmoAddress, }: { coinAmount: string; - maxSlippage: Dec | undefined; + maxSlippage: string | undefined; quote: | RouterOutputs["local"]["quoteRouter"]["routeTokenOutGivenIn"] | undefined; - fromAsset: MinimalAsset & - Partial<{ - amount: CoinPretty; - usdValue: PricePretty; - }>; - toAsset: MinimalAsset & - Partial<{ - amount: CoinPretty; - usdValue: PricePretty; - }>; + tokenInCoinMinimalDenom: string; + tokenOutCoinDecimals: number; userOsmoAddress: string | undefined; }) { if (!userOsmoAddress || !quote || !maxSlippage) return undefined; @@ -1086,8 +1067,8 @@ function getSwapMessages({ txParams = getSwapTxParameters({ coinAmount, maxSlippage, - fromAsset, - toAsset, + tokenInCoinMinimalDenom, + tokenOutCoinDecimals, quote, }); } catch { @@ -1104,13 +1085,13 @@ function getSwapMessages({ return [ routes.length === 1 - ? makeSwapExactAmountInMsg({ + ? await makeSwapExactAmountInMsg({ pools, tokenIn, tokenOutMinAmount, userOsmoAddress, }) - : makeSplitRoutesSwapExactAmountInMsg({ + : await makeSplitRoutesSwapExactAmountInMsg({ routes, tokenIn, tokenOutMinAmount, @@ -1125,7 +1106,7 @@ function getSwapMessages({ function useQueryRouterBestQuote( input: Omit< RouterInputs["local"]["quoteRouter"]["routeTokenOutGivenIn"], - "preferredRouter" | "tokenInDenom" | "tokenOutDenom" + "preferredRouter" | "tokenInDenom" | "tokenOutDenom" | "maxSlippage" > & { tokenIn: MinimalAsset & Partial<{ @@ -1139,142 +1120,88 @@ function useQueryRouterBestQuote( }>; maxSlippage: Dec | undefined; }, - enabled: boolean, - routerKeys = ["legacy", "sidecar", "tfm"] as RouterKey[] + enabled: boolean ) { const { chainStore, accountStore } = useStore(); const account = accountStore.getWallet(chainStore.osmosis.chainId); - const featureFlags = useFeatureFlags(); - - const availableRouterKeys: RouterKey[] = useMemo( - () => - !featureFlags._isInitialized - ? [] - : routerKeys.filter((key) => { - if (!featureFlags.sidecarRouter && key === "sidecar") return false; - if (!featureFlags.legacyRouter && key === "legacy") return false; - // TFM doesn't support force swap through pool - if ((!featureFlags.tfmRouter || input.forcePoolId) && key === "tfm") - return false; - return true; - }), - [ - featureFlags._isInitialized, - featureFlags.sidecarRouter, - featureFlags.legacyRouter, - featureFlags.tfmRouter, - routerKeys, - input.forcePoolId, - ] - ); const trpcReact = createTRPCReact(); - const routerResults = trpcReact.useQueries((t) => - availableRouterKeys.map((key) => - t.local.quoteRouter.routeTokenOutGivenIn( - { - tokenInAmount: input.tokenInAmount, - tokenInDenom: input.tokenIn?.coinMinimalDenom ?? "", - tokenOutDenom: input.tokenOut?.coinMinimalDenom ?? "", - forcePoolId: input.forcePoolId, - preferredRouter: key, + const quoteResult = trpcReact.local.quoteRouter.routeTokenOutGivenIn.useQuery( + { + tokenInAmount: input.tokenInAmount, + tokenInDenom: input.tokenIn?.coinMinimalDenom ?? "", + tokenOutDenom: input.tokenOut?.coinMinimalDenom ?? "", + forcePoolId: input.forcePoolId, + }, + { + enabled: enabled, + + // quotes should not be considered fresh for long, otherwise + // 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 + // until retry starts returning false. + // This causes slow UX even though there's a + // quote that the user can use. + retry: false, + + // prevent batching so that fast routers can + // return requests faster than the slowest router + trpc: { + context: { + skipBatch: true, }, - { - enabled: enabled && Boolean(availableRouterKeys.length), - - select: (quote) => { - return { - ...quote, - messages: getSwapMessages({ - quote, - toAsset: input.tokenOut, - fromAsset: input.tokenIn, - maxSlippage: input.maxSlippage, - coinAmount: input.tokenInAmount, - userOsmoAddress: account?.address, - }), - }; - }, - - // quotes should not be considered fresh for long, otherwise - // 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 - // until retry starts returning false. - // This causes slow UX even though there's a - // quote that the user can use. - retry: false, - - // prevent batching so that fast routers can - // return requests faster than the slowest router - trpc: { - context: { - skipBatch: true, - }, - }, - } - ) - ) + }, + } ); - // reduce the results' data to that with the highest out amount - const bestData = useMemo(() => { - return ( - routerResults - // only those that have fetched - .filter((routerResults) => Boolean(routerResults.isFetched)) - // only those that have returned a result without error - .map(({ data }) => data) - // only the best quote data - .reduce((best, cur) => { - if (!best) return cur; - if (cur && best.amount.toDec().lt(cur.amount.toDec())) return cur; - return best; - }, undefined) - ); - }, [routerResults]); - - const numSucceeded = routerResults.filter( - ({ isSuccess }) => isSuccess - ).length; - const isOneSuccessful = Boolean(numSucceeded); - const numError = routerResults.filter(({ isError }) => isError).length; - const isOneErrored = Boolean(numError); - - // if none have returned a resulting quote, find some error - const someError = useMemo( - () => - !isOneSuccessful && isOneErrored - ? routerResults.find((routerResults) => Boolean(routerResults.error)) - ?.error - : undefined, - [isOneSuccessful, isOneErrored, routerResults] - ); + const { + data: quote, + isSuccess, + isError, + error, + } = useMemo(() => { + return quoteResult; + }, [quoteResult]); + + const { value: messages } = useAsync(async () => { + if (!quote) return undefined; + const messages = await getSwapMessages({ + quote: quote, + tokenOutCoinDecimals: input.tokenOut.coinDecimals, + tokenInCoinMinimalDenom: input.tokenIn.coinMinimalDenom, + maxSlippage: input.maxSlippage?.toString(), + coinAmount: input.tokenInAmount, + userOsmoAddress: account?.address, + }); + return messages; + }, [ + account?.address, + quote, + input.maxSlippage, + input.tokenIn.coinMinimalDenom, + input.tokenInAmount, + input.tokenOut.coinDecimals, + ]); return { - data: bestData, - isLoading: !isOneSuccessful, - error: someError, - numSucceeded, - numError, - numAvailableRouters: availableRouterKeys.length, + data: quote ? { ...quote, messages } : undefined, + isLoading: !isSuccess, + errorMsg: error?.message, + numSucceeded: isSuccess ? 1 : 0, + numError: isError ? 1 : 0, }; } /** Various router clients on server should reconcile their error messages * into the following error messages or instances on the server. * Then we can show the user a useful translated error message vs just "Error". */ -function makeRouterErrorFromTrpcError( - error: - | TRPCClientError - | null - | undefined -): +function makeRouterErrorFromTrpcError(errorMsg: string | null | undefined): | { error: | NoRouteError @@ -1284,21 +1211,20 @@ function makeRouterErrorFromTrpcError( isUnexpected: boolean; } | undefined { - if (isNil(error)) return; - const tprcShapeMsg = error?.message; + if (isNil(errorMsg)) return; - if (tprcShapeMsg?.includes(NoRouteError.defaultMessage)) { + if (errorMsg?.includes(NoRouteError.defaultMessage)) { return { error: new NoRouteError(), isUnexpected: false }; } - if (tprcShapeMsg?.includes(NotEnoughLiquidityError.defaultMessage)) { + if (errorMsg?.includes(NotEnoughLiquidityError.defaultMessage)) { return { error: new NotEnoughLiquidityError(), isUnexpected: false }; } - if (tprcShapeMsg?.includes(NotEnoughQuotedError.defaultMessage)) { + if (errorMsg?.includes(NotEnoughQuotedError.defaultMessage)) { return { error: new NotEnoughQuotedError(), isUnexpected: false }; } - if (error) { + if (errorMsg) { return { - error: new Error("Unexpected router error" + (tprcShapeMsg ?? "")), + error: new Error("Unexpected router error" + (errorMsg ?? "")), isUnexpected: true, }; } diff --git a/packages/web/instrumentation.ts b/packages/web/instrumentation.ts new file mode 100644 index 0000000000..baf63752b3 --- /dev/null +++ b/packages/web/instrumentation.ts @@ -0,0 +1,14 @@ +import { registerOTel } from "@vercel/otel"; + +import { getOpentelemetryServiceName } from "~/utils/service-name"; + +export function register() { + registerOTel({ + serviceName: getOpentelemetryServiceName(), + instrumentationConfig: { + fetch: { + propagateContextUrls: ["*"], + }, + }, + }); +} diff --git a/packages/web/integrations/axelar/transfer.tsx b/packages/web/integrations/axelar/transfer.tsx index 86c4e9da08..8a33b856e1 100644 --- a/packages/web/integrations/axelar/transfer.tsx +++ b/packages/web/integrations/axelar/transfer.tsx @@ -2,6 +2,7 @@ import { Environment } from "@axelar-network/axelarjs-sdk"; import { WalletStatus } from "@cosmos-kit/core"; import { CoinPretty, Dec, DecUtils } from "@keplr-wallet/unit"; import { basicIbcTransfer } from "@osmosis-labs/stores"; +import { makeIBCTransferMsg } from "@osmosis-labs/tx"; import { AxelarSourceChain, getKeyByValue } from "@osmosis-labs/utils"; import dayjs from "dayjs"; import { observer } from "mobx-react-lite"; @@ -192,7 +193,7 @@ export const AxelarTransfer: FunctionComponent< const feeConfig = useFakeFeeConfig( chainStore, chainId, - osmosisAccount?.cosmos.msgOpts.ibcTransfer.gas ?? 0 + makeIBCTransferMsg.gas ?? 0 ); const withdrawAmountConfig = useAmountConfig( chainStore, diff --git a/packages/web/integrations/core-walletconnect/client.ts b/packages/web/integrations/core-walletconnect/client.ts index f255cee463..26b29838a3 100644 --- a/packages/web/integrations/core-walletconnect/client.ts +++ b/packages/web/integrations/core-walletconnect/client.ts @@ -1,9 +1,9 @@ -import { +import type { AminoSignResponse, OfflineAminoSigner, StdSignDoc, } from "@cosmjs/amino"; -import { +import type { Algo, DirectSignResponse, OfflineDirectSigner, diff --git a/packages/web/integrations/core-walletconnect/types.ts b/packages/web/integrations/core-walletconnect/types.ts index 4d0475f46c..bcaa80307a 100644 --- a/packages/web/integrations/core-walletconnect/types.ts +++ b/packages/web/integrations/core-walletconnect/types.ts @@ -1,4 +1,4 @@ -import { StdSignature } from "@cosmjs/amino"; +import type { StdSignature } from "@cosmjs/amino"; import { ChainRecord, Wallet } from "@cosmos-kit/core"; import { ChainWC } from "~/integrations/core-walletconnect/chain-wallet"; diff --git a/packages/web/integrations/nomic/transfer.tsx b/packages/web/integrations/nomic/transfer.tsx index 7ddc835dba..bfd8d57e3e 100644 --- a/packages/web/integrations/nomic/transfer.tsx +++ b/packages/web/integrations/nomic/transfer.tsx @@ -1,3 +1,4 @@ +import { makeIBCTransferMsg } from "@osmosis-labs/tx"; import { AxelarSourceChain } from "@osmosis-labs/utils"; import { Network, validate } from "bitcoin-address-validation"; import classNames from "classnames"; @@ -157,7 +158,7 @@ export const NomicTransfer: FunctionComponent< const feeConfig = useFakeFeeConfig( chainStore, chainId, - osmosisAccount?.cosmos.msgOpts.ibcTransfer.gas ?? 0 + makeIBCTransferMsg.gas ?? 0 ); const withdrawAmountConfig = useAmountConfig( chainStore, diff --git a/packages/web/localizations/de.json b/packages/web/localizations/de.json index 4c9d93180d..01288c7002 100644 --- a/packages/web/localizations/de.json +++ b/packages/web/localizations/de.json @@ -95,7 +95,8 @@ "totalBalance": "Gesamtsaldo", "numPositions": "{numPositions} Positionen", "onePosition": "1 Position", - "goToPool": "Gehen Sie zu Pool #{poolId}" + "goToPool": "Gehen Sie zu Pool #{poolId}", + "errorFetchingPositions": "Positionsdetails konnten nicht abgerufen werden. Versuchen Sie, die Seite zu aktualisieren. Wenn das Problem weiterhin besteht, wenden Sie sich an den Support." }, "copied": "Kopiert!", "app": { @@ -302,8 +303,9 @@ }, "portfolio": { "buy": "Kaufen", - "yourAssets": "Ihr Vermögen", + "yourBalances": "Ihre Guthaben", "yourPositions": "Ihre Positionen", + "searchBalances": "Guthaben suchen", "yourSuperchargedPositions": "Ihre Supercharged-Positionen", "yourLiquidityPools": "Ihre Liquiditätspools", "connectWallet": "Verbinden Sie Ihr Portemonnaie, um mit der Verfolgung Ihrer Guthaben und Positionen auf Osmosis zu beginnen.", @@ -324,7 +326,10 @@ "seeAll": "Alles sehen", "show": "Zeigen", "hide": "Verstecken", - "hidden": "Versteckt" + "hidden": "Versteckt", + "cypherSpend": "Osmose-Bezahlung", + "cypherOrder": "Bestellen Sie jetzt Ihre Karte", + "cypherBeta": "Beta" }, "buyTokens": "Kaufen Sie Token", "components": { @@ -1106,7 +1111,6 @@ "resetFilters": "Filter zurücksetzen", "saveFilters": "Filter speichern", "rewards": "Belohnung", - "estimatedInYearlyRewards": "Geschätzte jährliche Prämie", "availableToClaim": "zur Inanspruchnahme verfügbar", "claimAllRewards": "Fordern Sie alle Belohnungen an", "join": "Verbinden", @@ -1249,6 +1253,7 @@ "filled": "Gefüllt", "claimable": "Einforderbar", "claimAndClose": "Beanspruchen und schließen", + "close": "Schließen", "claim": "Beanspruchen", "accept": "Akzeptieren", "cancelled": "Abgesagt", @@ -1299,7 +1304,6 @@ "connectYourWallet": "Verbinde dein Wallet", "toSeeYourBalances": "um Ihre Guthaben einzusehen", "searchAssets": "Assets suchen", - "marketPrice": "Marktpreis", "below": "unten", "above": "über", "currentPrice": "derzeitiger Preis", @@ -1318,6 +1322,33 @@ "past": "Vergangenheit", "pending": "Ausstehend", "filled": "Ausgeführte Bestellungen zum Einfordern" + }, + "priceReaches": "Wenn der Preis {denom} {price} erreicht", + "belowMinimumAmount": "{amount} Minimum" + }, + "tradeDetails": { + "priceImpact": { + "header": "Was ist Preiseinfluss?", + "content": "Dies ist der Einfluss der Marktliquidität auf Ihren Ausführungspreis. Größere Trades haben tendenziell einen größeren Einfluss auf den Markt." + }, + "expectedRate": { + "header": "Was ist der erwartete Zinssatz?", + "content": "Dies ist der Preis, den Sie voraussichtlich erhalten werden. Preise ändern sich häufig. Wenn Sie also zu einem bestimmten Preis handeln möchten, versuchen Sie es stattdessen mit einer Limit-Order." + }, + "swapFees": { + "header": "Was sind Swap-Gebühren?", + "content": "Dies ist die vom Osmosis-Protokoll erhobene Gebühr, um Liquiditätsanbieter zu belohnen und das Netzwerk aufrechtzuerhalten." + }, + "tradeRoute": { + "header": "Was ist eine Handelsroute?", + "contentTop": " Wenn zwischen den von Ihnen gehandelten Vermögenswerten kein direkter Markt besteht, versucht Osmosis, den Handel durch eine Reihe von Handelsgeschäften mit anderen Vermögenswerten zustande zu bringen, um jederzeit den besten Preis zu erzielen.", + "contentBottom": "Um eine optimale Effizienz auf Grundlage der verfügbaren Liquidität zu erzielen, werden Handelsgeschäfte manchmal auf mehrere Routen mit unterschiedlichen Vermögenswerten aufgeteilt." + }, + "outputDifference": { + "header": "Was ist der Ausgabeunterschied?", + "content": "Dies ist die Wertdifferenz zwischen dem, was Sie bezahlen und erhalten. Positive Zahlen bedeuten, dass der Betrag, den Sie kaufen, mehr wert ist, während negative Zahlen bedeuten, dass der Betrag, den Sie verkaufen, mehr wert ist." } - } + }, + "tradeFee": "Handelsgebühr", + "receiveAtLeast": "Erhalten Sie mindestens" } diff --git a/packages/web/localizations/en.json b/packages/web/localizations/en.json index 632314acdb..85190df6fb 100644 --- a/packages/web/localizations/en.json +++ b/packages/web/localizations/en.json @@ -95,7 +95,8 @@ "totalBalance": "Total balance", "numPositions": "{numPositions} positions", "onePosition": "1 position", - "goToPool": "Go to pool #{poolId}" + "goToPool": "Go to pool #{poolId}", + "errorFetchingPositions": "Failed to fetch position details. Please try refreshing the page. If the problem persists, contact support." }, "copied": "Copied!", "app": { @@ -302,8 +303,9 @@ }, "portfolio": { "buy": "Buy", - "yourAssets": "Your assets", + "yourBalances": "Your balances", "yourPositions": "Your positions", + "searchBalances": "Search balances", "yourSuperchargedPositions": "Your Supercharged positions", "yourLiquidityPools": "Your liquidity pools", "connectWallet": "Connect your wallet to get started with tracking your balances and positions on Osmosis.", @@ -324,7 +326,10 @@ "seeAll": "See all", "show": "Show", "hide": "Hide", - "hidden": "Hidden" + "hidden": "Hidden", + "cypherSpend": "Osmosis Pay", + "cypherOrder": "Order your card now", + "cypherBeta": "Beta" }, "buyTokens": "Buy tokens", "components": { @@ -1106,7 +1111,6 @@ "resetFilters": "Reset filters", "saveFilters": "Save filters", "rewards": "Rewards", - "estimatedInYearlyRewards": "Estimated in yearly rewards", "availableToClaim": "available to claim", "claimAllRewards": "Claim all rewards", "join": "Join", @@ -1249,6 +1253,7 @@ "filled": "Filled", "claimable": "Claimable", "claimAndClose": "Claim and close", + "close": "Close", "claim": "Claim", "accept": "Accept", "cancelled": "Cancelled", @@ -1299,7 +1304,6 @@ "connectYourWallet": "Connect your wallet", "toSeeYourBalances": "to see your balances", "searchAssets": "Search assets", - "marketPrice": "market price", "below": "below", "above": "above", "currentPrice": "current price", @@ -1318,6 +1322,33 @@ "past": "Past", "pending": "Pending", "filled": "Filled orders to claim" + }, + "priceReaches": "If {denom} price reaches {price}", + "belowMinimumAmount": "{amount} minimum" + }, + "tradeDetails": { + "priceImpact": { + "header": "What is price impact?", + "content": "This is the impact that market liquidity has on your execution price. Larger trades tend to have larger market impact." + }, + "expectedRate": { + "header": "What is expected rate?", + "content": "This is the price you are expected to receive. Prices are frequently changing, so if you wish to trade at a specific price, try a limit order instead." + }, + "swapFees": { + "header": "What are swap fees?", + "content": "This is the fee charged by the Osmosis protocol in order to reward liquidity providers and maintain the network." + }, + "tradeRoute": { + "header": "What is a trade route?", + "contentTop": " If there's no direct market between the assets you're trading, Osmosis will try to make the trade happen by making a series of trades with other assets to get the best price at any given time.", + "contentBottom": "For optimal efficiency based on available liquidity, sometimes trades will be split into multiple routes with different assets." + }, + "outputDifference": { + "header": "What is output difference?", + "content": "This is the difference in value between what you pay and receive. Positive numbers mean the amount you're buying is worth more, while negative numbers mean the amount you're selling is worth more." } - } + }, + "tradeFee": "Trade fee", + "receiveAtLeast": "Receive at least" } diff --git a/packages/web/localizations/es.json b/packages/web/localizations/es.json index 33141be0d7..a8eb598d73 100644 --- a/packages/web/localizations/es.json +++ b/packages/web/localizations/es.json @@ -95,7 +95,8 @@ "totalBalance": "Balance total", "numPositions": "indefinido", "onePosition": "1 posición", - "goToPool": "Ir a la piscina #{poolId}" + "goToPool": "Ir a la piscina #{poolId}", + "errorFetchingPositions": "No se pudieron obtener los detalles de la posición. Intente actualizar la página. Si el problema persiste, comuníquese con el servicio de asistencia." }, "copied": "¡Copiado!", "app": { @@ -302,8 +303,9 @@ }, "portfolio": { "buy": "Comprar", - "yourAssets": "Tus activos", + "yourBalances": "Sus saldos", "yourPositions": "Tus posiciones", + "searchBalances": "Buscar saldos", "yourSuperchargedPositions": "Tus posiciones sobrealimentadas", "yourLiquidityPools": "Tus fondos de liquidez", "connectWallet": "Conecte su billetera para comenzar a rastrear sus saldos y posiciones en Osmosis.", @@ -324,7 +326,10 @@ "seeAll": "Ver todo", "show": "Espectáculo", "hide": "Esconder", - "hidden": "Oculto" + "hidden": "Oculto", + "cypherSpend": "Pago por ósmosis", + "cypherOrder": "Pide tu tarjeta ahora", + "cypherBeta": "Beta" }, "buyTokens": "Comprar fichas", "components": { @@ -1106,7 +1111,6 @@ "resetFilters": "Restablecer filtros", "saveFilters": "Guardar filtros", "rewards": "Recompensas", - "estimatedInYearlyRewards": "Estimado en recompensas anuales", "availableToClaim": "disponible para reclamar", "claimAllRewards": "Reclama todas las recompensas", "join": "Unirse", @@ -1249,6 +1253,7 @@ "filled": "Completado", "claimable": "Reclamable", "claimAndClose": "Reclamar y cerrar", + "close": "Cerca", "claim": "Afirmar", "accept": "Aceptar", "cancelled": "Cancelado", @@ -1299,7 +1304,6 @@ "connectYourWallet": "Conecta tu billetera", "toSeeYourBalances": "para ver tus saldos", "searchAssets": "Buscar activos", - "marketPrice": "precio de mercado", "below": "abajo", "above": "arriba", "currentPrice": "precio actual", @@ -1318,6 +1322,33 @@ "past": "Pasado", "pending": "Pendiente", "filled": "Pedidos completados para reclamar" + }, + "priceReaches": "Si el precio {denom} alcanza {price}", + "belowMinimumAmount": "{amount} mínimo" + }, + "tradeDetails": { + "priceImpact": { + "header": "¿Qué es el impacto en el precio?", + "content": "Este es el impacto que tiene la liquidez del mercado en el precio de ejecución. Las operaciones más grandes tienden a tener un mayor impacto en el mercado." + }, + "expectedRate": { + "header": "¿Qué es la tasa esperada?", + "content": "Este es el precio que se espera que recibas. Los precios cambian con frecuencia, por lo que si deseas operar a un precio específico, prueba con una orden limitada." + }, + "swapFees": { + "header": "¿Qué son las tarifas de swap?", + "content": "Esta es la tarifa que cobra el protocolo Osmosis para recompensar a los proveedores de liquidez y mantener la red." + }, + "tradeRoute": { + "header": "¿Qué es una ruta comercial?", + "contentTop": " Si no existe un mercado directo entre los activos que estás negociando, Osmosis intentará que la transacción se realice realizando una serie de transacciones con otros activos para obtener el mejor precio en cualquier momento.", + "contentBottom": "Para lograr una eficiencia óptima en función de la liquidez disponible, a veces las operaciones se dividirán en múltiples rutas con diferentes activos." + }, + "outputDifference": { + "header": "¿Qué es la diferencia de salida?", + "content": "Esta es la diferencia de valor entre lo que pagas y lo que recibes. Los números positivos significan que la cantidad que compras vale más, mientras que los números negativos significan que la cantidad que vendes vale más." } - } + }, + "tradeFee": "Tarifa de comercio", + "receiveAtLeast": "Recibir al menos" } diff --git a/packages/web/localizations/fa.json b/packages/web/localizations/fa.json index 245051810b..d6f7f6a319 100644 --- a/packages/web/localizations/fa.json +++ b/packages/web/localizations/fa.json @@ -95,7 +95,8 @@ "totalBalance": "کل موجودی", "numPositions": "تعریف نشده", "onePosition": "1 موقعیت", - "goToPool": "رفتن به استخر #{poolId}" + "goToPool": "رفتن به استخر #{poolId}", + "errorFetchingPositions": "جزئیات موقعیت واکشی نشد. لطفاً صفحه را بازخوانی کنید. اگر مشکل برطرف نشد، با پشتیبانی تماس بگیرید." }, "copied": "کپی شده!", "app": { @@ -302,8 +303,9 @@ }, "portfolio": { "buy": "خرید کنید", - "yourAssets": "دارایی های شما", + "yourBalances": "موجودی شما", "yourPositions": "موقعیت های شما", + "searchBalances": "جستجوی موجودی", "yourSuperchargedPositions": "موقعیت های سوپرشارژ شما", "yourLiquidityPools": "استخرهای نقدینگی شما", "connectWallet": "کیف پول خود را وصل کنید تا ردیابی موجودی ها و موقعیت های خود را در اسموز شروع کنید.", @@ -324,7 +326,10 @@ "seeAll": "همه را ببینید", "show": "نمایش دهید", "hide": "پنهان کردن", - "hidden": "پنهان شده است" + "hidden": "پنهان شده است", + "cypherSpend": "اسمز پرداخت", + "cypherOrder": "همین الان کارت خود را سفارش دهید", + "cypherBeta": "بتا" }, "buyTokens": "خرید توکن", "components": { @@ -1106,7 +1111,6 @@ "resetFilters": "بازنشانی فیلترها", "saveFilters": "ذخیره فیلترها", "rewards": "پاداش", - "estimatedInYearlyRewards": "بر اساس جوایز سالانه تخمین زده می شود", "availableToClaim": "در دسترس برای ادعا", "claimAllRewards": "تمام جوایز را مطالبه کنید", "join": "پیوستن", @@ -1249,6 +1253,7 @@ "filled": "پر شده است", "claimable": "قابل ادعا", "claimAndClose": "ادعا کنید و ببندید", + "close": "بستن", "claim": "ادعا کنید", "accept": "تایید کنید", "cancelled": "لغو شد", @@ -1299,7 +1304,6 @@ "connectYourWallet": "کیف پول خود را وصل کنید", "toSeeYourBalances": "برای دیدن موجودی خود", "searchAssets": "جستجوی دارایی ها", - "marketPrice": "قیمت بازار", "below": "زیر", "above": "در بالا", "currentPrice": "قیمت فعلی", @@ -1318,6 +1322,33 @@ "past": "گذشته", "pending": "انتظار", "filled": "سفارشات تکمیل شده برای ادعا" + }, + "priceReaches": "اگر قیمت {denom} به {price} برسد.", + "belowMinimumAmount": "حداقل {amount}" + }, + "tradeDetails": { + "priceImpact": { + "header": "تاثیر قیمت چیست؟", + "content": "این تاثیری است که نقدینگی بازار بر قیمت اجرایی شما می گذارد. معاملات بزرگتر تاثیر بیشتری بر بازار دارند." + }, + "expectedRate": { + "header": "نرخ مورد انتظار چقدر است؟", + "content": "این قیمتی است که انتظار می رود دریافت کنید. قیمت‌ها مرتباً در حال تغییر هستند، بنابراین اگر می‌خواهید با قیمت خاصی معامله کنید، به جای آن یک سفارش محدود را امتحان کنید." + }, + "swapFees": { + "header": "کارمزد سوآپ چیست؟", + "content": "این هزینه ای است که توسط پروتکل اسموز به منظور پاداش دادن به ارائه دهندگان نقدینگی و حفظ شبکه دریافت می شود." + }, + "tradeRoute": { + "header": "مسیر تجاری چیست؟", + "contentTop": " اگر بازار مستقیمی بین دارایی‌هایی که معامله می‌کنید وجود نداشته باشد، اسموز سعی می‌کند با انجام یک سری معاملات با سایر دارایی‌ها، معامله را انجام دهد تا بهترین قیمت را در هر زمان معین به دست آورد.", + "contentBottom": "برای بهره وری بهینه بر اساس نقدینگی موجود، گاهی اوقات معاملات به چندین مسیر با دارایی های مختلف تقسیم می شوند." + }, + "outputDifference": { + "header": "تفاوت خروجی چیست؟", + "content": "این تفاوت در ارزش چیزی است که می پردازید و دریافت می کنید. اعداد مثبت به این معنی است که مقداری که می‌خرید ارزش بیشتری دارد، در حالی که اعداد منفی به این معنی است که مقداری که می‌فروشید ارزش بیشتری دارد." } - } + }, + "tradeFee": "کارمزد تجارت", + "receiveAtLeast": "حداقل دریافت کنید" } diff --git a/packages/web/localizations/fr.json b/packages/web/localizations/fr.json index 6ab8fe56e1..82211fe88b 100644 --- a/packages/web/localizations/fr.json +++ b/packages/web/localizations/fr.json @@ -95,7 +95,8 @@ "totalBalance": "Solde total", "numPositions": "indéfini", "onePosition": "1 poste", - "goToPool": "Aller à la piscine #{poolId}" + "goToPool": "Aller à la piscine #{poolId}", + "errorFetchingPositions": "Impossible de récupérer les informations de position. Veuillez essayer d'actualiser la page. Si le problème persiste, contactez le support." }, "copied": "Copié!", "app": { @@ -302,8 +303,9 @@ }, "portfolio": { "buy": "Acheter", - "yourAssets": "Vos atouts", + "yourBalances": "Vos soldes", "yourPositions": "Vos postes", + "searchBalances": "Rechercher des soldes", "yourSuperchargedPositions": "Vos positions suralimentées", "yourLiquidityPools": "Vos pools de liquidité", "connectWallet": "Connectez votre portefeuille pour commencer à suivre vos soldes et positions sur Osmosis.", @@ -324,7 +326,10 @@ "seeAll": "Tout voir", "show": "Montrer", "hide": "Cacher", - "hidden": "Caché" + "hidden": "Caché", + "cypherSpend": "Paiement par osmose", + "cypherOrder": "Commandez votre carte maintenant", + "cypherBeta": "Bêta" }, "buyTokens": "Acheter jetons", "components": { @@ -1106,7 +1111,6 @@ "resetFilters": "Réinitialiser les filtres", "saveFilters": "Enregistrer les filtres", "rewards": "Récompenses", - "estimatedInYearlyRewards": "Estimé en récompenses annuelles", "availableToClaim": "disponible pour réclamer", "claimAllRewards": "Réclamez toutes les récompenses", "join": "Rejoindre", @@ -1249,6 +1253,7 @@ "filled": "Rempli", "claimable": "Réclamable", "claimAndClose": "Réclamez et clôturez", + "close": "Fermer", "claim": "Réclamer", "accept": "Accepter", "cancelled": "Annulé", @@ -1299,7 +1304,6 @@ "connectYourWallet": "Connectez votre portefeuille", "toSeeYourBalances": "pour voir vos soldes", "searchAssets": "Rechercher des ressources", - "marketPrice": "prix du marché", "below": "ci-dessous", "above": "au-dessus de", "currentPrice": "prix actuel", @@ -1318,6 +1322,33 @@ "past": "Passé", "pending": "En attente", "filled": "Commandes exécutées à réclamer" + }, + "priceReaches": "Si le prix {denom} atteint {price}", + "belowMinimumAmount": "{amount} minimum" + }, + "tradeDetails": { + "priceImpact": { + "header": "Qu'est-ce que l'impact des prix ?", + "content": "Il s'agit de l'impact de la liquidité du marché sur votre prix d'exécution. Les transactions plus importantes ont tendance à avoir un impact plus important sur le marché." + }, + "expectedRate": { + "header": "Quel est le taux attendu ?", + "content": "Il s'agit du prix que vous êtes censé recevoir. Les prix changent fréquemment, donc si vous souhaitez négocier à un prix spécifique, essayez plutôt un ordre à cours limité." + }, + "swapFees": { + "header": "Que sont les frais de swap ?", + "content": "Il s'agit des frais facturés par le protocole Osmosis afin de récompenser les fournisseurs de liquidités et de maintenir le réseau." + }, + "tradeRoute": { + "header": "Qu'est-ce qu'une route commerciale ?", + "contentTop": " S'il n'y a pas de marché direct entre les actifs que vous négociez, Osmosis essaiera de réaliser la transaction en effectuant une série de transactions avec d'autres actifs pour obtenir le meilleur prix à tout moment.", + "contentBottom": "Pour une efficacité optimale en fonction de la liquidité disponible, les transactions seront parfois divisées en plusieurs itinéraires avec des actifs différents." + }, + "outputDifference": { + "header": "Quelle est la différence de sortie ?", + "content": "Il s'agit de la différence de valeur entre ce que vous payez et ce que vous recevez. Les nombres positifs signifient que le montant que vous achetez vaut plus, tandis que les nombres négatifs signifient que le montant que vous vendez vaut plus." } - } + }, + "tradeFee": "Frais de transaction", + "receiveAtLeast": "Recevez au moins" } diff --git a/packages/web/localizations/gu.json b/packages/web/localizations/gu.json index 1a77ae8e06..93347813ca 100644 --- a/packages/web/localizations/gu.json +++ b/packages/web/localizations/gu.json @@ -95,7 +95,8 @@ "totalBalance": "કુલ બેલેન્સ", "numPositions": "{numPositions} સ્થિતિ", "onePosition": "1 સ્થિતિ", - "goToPool": "પૂલ #{poolId} પર જાઓ" + "goToPool": "પૂલ #{poolId} પર જાઓ", + "errorFetchingPositions": "સ્થિતિ વિગતો મેળવવામાં નિષ્ફળ. કૃપા કરીને પૃષ્ઠને તાજું કરવાનો પ્રયાસ કરો. જો સમસ્યા ચાલુ રહે, તો સપોર્ટનો સંપર્ક કરો." }, "copied": "નકલ કરી!", "app": { @@ -302,8 +303,9 @@ }, "portfolio": { "buy": "ખરીદો", - "yourAssets": "તમારી સંપત્તિ", + "yourBalances": "તમારું બેલેન્સ", "yourPositions": "તમારી સ્થિતિ", + "searchBalances": "બેલેન્સ શોધો", "yourSuperchargedPositions": "તમારી સુપરચાર્જ્ડ પોઝિશન્સ", "yourLiquidityPools": "તમારા લિક્વિડિટી પૂલ", "connectWallet": "ઓસ્મોસિસ પર તમારા બેલેન્સ અને સ્થિતિને ટ્રૅક કરવાનું પ્રારંભ કરવા માટે તમારા વૉલેટને કનેક્ટ કરો.", @@ -324,7 +326,10 @@ "seeAll": "બધા જુઓ", "show": "બતાવો", "hide": "છુપાવો", - "hidden": "છુપાયેલ" + "hidden": "છુપાયેલ", + "cypherSpend": "ઓસ્મોસિસ પે", + "cypherOrder": "હવે તમારું કાર્ડ ઓર્ડર કરો", + "cypherBeta": "બેટા" }, "buyTokens": "ટોકન્સ ખરીદો", "components": { @@ -1106,7 +1111,6 @@ "resetFilters": "ફિલ્ટર્સ રીસેટ કરો", "saveFilters": "ફિલ્ટર્સ સાચવો", "rewards": "પારિતોષિકો", - "estimatedInYearlyRewards": "વાર્ષિક પુરસ્કારોમાં અંદાજિત", "availableToClaim": "દાવો કરવા માટે ઉપલબ્ધ છે", "claimAllRewards": "બધા પુરસ્કારોનો દાવો કરો", "join": "જોડાઓ", @@ -1249,6 +1253,7 @@ "filled": "ભરેલ", "claimable": "દાવો કરવા યોગ્ય", "claimAndClose": "દાવો કરો અને બંધ કરો", + "close": "બંધ કરો", "claim": "દાવો કરો", "accept": "સ્વીકારો", "cancelled": "રદ કરેલ", @@ -1299,7 +1304,6 @@ "connectYourWallet": "તમારું વૉલેટ કનેક્ટ કરો", "toSeeYourBalances": "તમારા બેલેન્સ જોવા માટે", "searchAssets": "સંપત્તિ શોધો", - "marketPrice": "બજાર કિંમત", "below": "નીચે", "above": "ઉપર", "currentPrice": "વર્તમાન ભાવ", @@ -1318,6 +1322,33 @@ "past": "ભૂતકાળ", "pending": "બાકી છે", "filled": "દાવો કરવાના આદેશો ભર્યા" + }, + "priceReaches": "જો {denom} કિંમત {price} સુધી પહોંચે છે", + "belowMinimumAmount": "{amount} ન્યૂનતમ" + }, + "tradeDetails": { + "priceImpact": { + "header": "ભાવની અસર શું છે?", + "content": "બજારની તરલતાની તમારી એક્ઝિક્યુશન કિંમત પર આ અસર પડે છે. મોટા સોદાઓ મોટા બજાર પર અસર કરે છે." + }, + "expectedRate": { + "header": "અપેક્ષિત દર શું છે?", + "content": "આ તે કિંમત છે જે તમને પ્રાપ્ત થવાની અપેક્ષા છે. કિંમતો વારંવાર બદલાતી રહે છે, તેથી જો તમે ચોક્કસ કિંમતે વેપાર કરવા માંગતા હો, તો તેના બદલે મર્યાદા ઓર્ડર અજમાવો." + }, + "swapFees": { + "header": "સ્વેપ ફી શું છે?", + "content": "તરલતા પ્રદાતાઓને પુરસ્કાર આપવા અને નેટવર્ક જાળવવા માટે આ ઓસ્મોસિસ પ્રોટોકોલ દ્વારા લેવામાં આવતી ફી છે." + }, + "tradeRoute": { + "header": "વેપાર માર્ગ શું છે?", + "contentTop": " જો તમે જે અસ્કયામતોનો વેપાર કરી રહ્યાં છો તે વચ્ચે કોઈ સીધું બજાર ન હોય, તો ઓસ્મોસિસ કોઈપણ સમયે શ્રેષ્ઠ કિંમત મેળવવા માટે અન્ય અસ્કયામતો સાથે શ્રેણીબદ્ધ સોદા કરીને વેપાર થાય તેવો પ્રયાસ કરશે.", + "contentBottom": "ઉપલબ્ધ તરલતાના આધારે શ્રેષ્ઠ કાર્યક્ષમતા માટે, કેટલીકવાર વિવિધ અસ્કયામતો સાથે સોદાને બહુવિધ રૂટમાં વિભાજિત કરવામાં આવશે." + }, + "outputDifference": { + "header": "આઉટપુટ તફાવત શું છે?", + "content": "તમે જે ચૂકવો છો અને મેળવો છો તે વચ્ચેના મૂલ્યમાં આ તફાવત છે. સકારાત્મક સંખ્યાઓનો અર્થ એ છે કે તમે જે રકમ ખરીદી રહ્યાં છો તે વધુ મૂલ્યવાન છે, જ્યારે નકારાત્મક સંખ્યાઓનો અર્થ છે કે તમે જે રકમ વેચી રહ્યાં છો તે વધુ મૂલ્યવાન છે." } - } + }, + "tradeFee": "વેપાર ફી", + "receiveAtLeast": "ઓછામાં ઓછું પ્રાપ્ત કરો" } diff --git a/packages/web/localizations/hi.json b/packages/web/localizations/hi.json index d2beaa19c2..21d52347f8 100644 --- a/packages/web/localizations/hi.json +++ b/packages/web/localizations/hi.json @@ -95,7 +95,8 @@ "totalBalance": "कुल शेष", "numPositions": "{numPositions} पद", "onePosition": "1 पद", - "goToPool": "पूल # पर जाएँ #{poolId}" + "goToPool": "पूल # पर जाएँ #{poolId}", + "errorFetchingPositions": "स्थिति विवरण प्राप्त करने में विफल। कृपया पृष्ठ को ताज़ा करने का प्रयास करें। यदि समस्या बनी रहती है, तो सहायता से संपर्क करें।" }, "copied": "कॉपी किया गया!", "app": { @@ -302,8 +303,9 @@ }, "portfolio": { "buy": "खरीदना", - "yourAssets": "आपकी संपत्ति", + "yourBalances": "आपका शेष", "yourPositions": "आपके पद", + "searchBalances": "शेष राशि खोजें", "yourSuperchargedPositions": "आपकी सुपरचार्ज्ड स्थिति", "yourLiquidityPools": "आपकी तरलता पूल", "connectWallet": "ओस्मोसिस पर अपने बैलेंस और पोजीशन पर नज़र रखने के लिए अपने वॉलेट को कनेक्ट करें।", @@ -324,7 +326,10 @@ "seeAll": "सभी देखें", "show": "दिखाओ", "hide": "छिपाना", - "hidden": "छिपा हुआ" + "hidden": "छिपा हुआ", + "cypherSpend": "ऑस्मोसिस पे", + "cypherOrder": "अपना कार्ड अभी ऑर्डर करें", + "cypherBeta": "बीटा" }, "buyTokens": "टोकन खरीदें", "components": { @@ -1106,7 +1111,6 @@ "resetFilters": "फ़िल्टर रीसेट करें", "saveFilters": "फ़िल्टर सहेजें", "rewards": "पुरस्कार", - "estimatedInYearlyRewards": "वार्षिक पुरस्कारों में अनुमानित", "availableToClaim": "दावा करने के लिए उपलब्ध", "claimAllRewards": "सभी पुरस्कारों का दावा करें", "join": "जोड़ना", @@ -1249,6 +1253,7 @@ "filled": "भरा हुआ", "claimable": "दावा योग्य", "claimAndClose": "दावा करें और बंद करें", + "close": "बंद करना", "claim": "दावा", "accept": "स्वीकार करना", "cancelled": "रद्द", @@ -1299,7 +1304,6 @@ "connectYourWallet": "अपना वॉलेट कनेक्ट करें", "toSeeYourBalances": "अपना शेष देखने के लिए", "searchAssets": "संपत्ति खोजें", - "marketPrice": "बाजार कीमत", "below": "नीचे", "above": "ऊपर", "currentPrice": "मौजूदा कीमत", @@ -1318,6 +1322,33 @@ "past": "अतीत", "pending": "लंबित", "filled": "दावा करने के लिए भरे गए ऑर्डर" + }, + "priceReaches": "यदि {denom} मूल्य {price} तक पहुँच जाता है", + "belowMinimumAmount": "{amount} न्यूनतम" + }, + "tradeDetails": { + "priceImpact": { + "header": "मूल्य प्रभाव क्या है?", + "content": "यह वह प्रभाव है जो बाजार की तरलता आपके निष्पादन मूल्य पर डालती है। बड़े ट्रेडों का बाजार पर बड़ा प्रभाव पड़ता है।" + }, + "expectedRate": { + "header": "अपेक्षित दर क्या है?", + "content": "यह वह कीमत है जो आपको मिलने की उम्मीद है। कीमतें अक्सर बदलती रहती हैं, इसलिए अगर आप किसी खास कीमत पर ट्रेड करना चाहते हैं, तो इसके बजाय लिमिट ऑर्डर आज़माएँ।" + }, + "swapFees": { + "header": "स्वैप शुल्क क्या हैं?", + "content": "यह शुल्क ओस्मोसिस प्रोटोकॉल द्वारा तरलता प्रदाताओं को पुरस्कृत करने और नेटवर्क को बनाए रखने के लिए लिया जाता है।" + }, + "tradeRoute": { + "header": "व्यापार मार्ग क्या है?", + "contentTop": " यदि आप जिन परिसंपत्तियों का व्यापार कर रहे हैं उनके बीच कोई प्रत्यक्ष बाजार नहीं है, तो ओस्मोसिस किसी भी समय सर्वोत्तम मूल्य प्राप्त करने के लिए अन्य परिसंपत्तियों के साथ व्यापार की एक श्रृंखला बनाकर व्यापार को संभव बनाने का प्रयास करेगा।", + "contentBottom": "उपलब्ध तरलता के आधार पर इष्टतम दक्षता के लिए, कभी-कभी ट्रेडों को अलग-अलग परिसंपत्तियों के साथ कई मार्गों में विभाजित किया जाएगा।" + }, + "outputDifference": { + "header": "आउटपुट अंतर क्या है?", + "content": "यह आपके द्वारा भुगतान की गई राशि और प्राप्त की गई राशि के बीच के मूल्य का अंतर है। सकारात्मक संख्याओं का मतलब है कि आप जो खरीद रहे हैं उसकी कीमत ज़्यादा है, जबकि नकारात्मक संख्याओं का मतलब है कि आप जो बेच रहे हैं उसकी कीमत ज़्यादा है।" } - } + }, + "tradeFee": "व्यापार शुल्क", + "receiveAtLeast": "कम से कम प्राप्त करें" } diff --git a/packages/web/localizations/ja.json b/packages/web/localizations/ja.json index d1341d5f75..518ff845a3 100644 --- a/packages/web/localizations/ja.json +++ b/packages/web/localizations/ja.json @@ -95,7 +95,8 @@ "totalBalance": "総合収支", "numPositions": "{numPositions}位置", "onePosition": "1 ポジション", - "goToPool": "プール #{poolId}に移動します" + "goToPool": "プール #{poolId}に移動します", + "errorFetchingPositions": "ポジションの詳細を取得できませんでした。ページを更新してください。問題が解決しない場合は、サポートにお問い合わせください。" }, "copied": "コピーしました!", "app": { @@ -302,8 +303,9 @@ }, "portfolio": { "buy": "買う", - "yourAssets": "あなたの資産", + "yourBalances": "残高", "yourPositions": "あなたの立場", + "searchBalances": "残高を検索", "yourSuperchargedPositions": "あなたのスーパーチャージされたポジション", "yourLiquidityPools": "流動性プール", "connectWallet": "ウォレットを接続して、Osmosis で残高とポジションの追跡を開始します。", @@ -324,7 +326,10 @@ "seeAll": "すべて見る", "show": "見せる", "hide": "隠れる", - "hidden": "隠れた" + "hidden": "隠れた", + "cypherSpend": "オズモーシスペイ", + "cypherOrder": "今すぐカードを注文する", + "cypherBeta": "ベータ" }, "buyTokens": "トークンを購入する", "components": { @@ -1106,7 +1111,6 @@ "resetFilters": "フィルターをリセットする", "saveFilters": "フィルターを保存する", "rewards": "報酬", - "estimatedInYearlyRewards": "年間報酬で見積もる", "availableToClaim": "請求可能", "claimAllRewards": "すべての報酬を受け取る", "join": "参加する", @@ -1249,6 +1253,7 @@ "filled": "満たされた", "claimable": "請求可能", "claimAndClose": "請求して終了", + "close": "近い", "claim": "請求", "accept": "受け入れる", "cancelled": "キャンセル", @@ -1299,7 +1304,6 @@ "connectYourWallet": "ウォレットを接続する", "toSeeYourBalances": "残高を確認する", "searchAssets": "アセットを検索", - "marketPrice": "市場価格", "below": "下に", "above": "その上", "currentPrice": "現在の価格", @@ -1318,6 +1322,33 @@ "past": "過去", "pending": "保留中", "filled": "請求する注文を完了" + }, + "priceReaches": "{denom}価格が{price}に達した場合", + "belowMinimumAmount": "{amount}最小" + }, + "tradeDetails": { + "priceImpact": { + "header": "価格影響とは何ですか?", + "content": "これは、市場の流動性が約定価格に与える影響です。取引規模が大きいほど、市場への影響も大きくなります。" + }, + "expectedRate": { + "header": "予想レートはいくらですか?", + "content": "これは、受け取る予定の価格です。価格は頻繁に変更されるため、特定の価格で取引したい場合は、代わりに指値注文を試してください。" + }, + "swapFees": { + "header": "スワップ手数料とは何ですか?", + "content": "これは、流動性プロバイダーに報酬を与え、ネットワークを維持するために Osmosis プロトコルによって請求される手数料です。" + }, + "tradeRoute": { + "header": "貿易ルートとは何ですか?", + "contentTop": " 取引する資産間に直接的な市場がない場合、Osmosis は他の資産との一連の取引を行って、特定の時点での最良の価格を実現することで、取引を実現しようとします。", + "contentBottom": "利用可能な流動性に基づいて最適な効率性を実現するために、取引が異なる資産を持つ複数のルートに分割されることがあります。" + }, + "outputDifference": { + "header": "出力の違いとは何ですか?", + "content": "これは、支払う金額と受け取る金額の差です。正の数値は購入する金額の方が価値が高いことを意味し、負の数値は販売する金額の方が価値が高いことを意味します。" } - } + }, + "tradeFee": "取引手数料", + "receiveAtLeast": "少なくとも受け取る" } diff --git a/packages/web/localizations/ko.json b/packages/web/localizations/ko.json index 4a1f5b27e8..2a2d91a5a3 100644 --- a/packages/web/localizations/ko.json +++ b/packages/web/localizations/ko.json @@ -95,7 +95,8 @@ "totalBalance": "전체 자산", "numPositions": "한정되지 않은", "onePosition": "1 위치", - "goToPool": "풀 번호로 이동 #{poolId}" + "goToPool": "풀 번호로 이동 #{poolId}", + "errorFetchingPositions": "직책 세부 정보를 가져오지 못했습니다. 페이지를 새로 고침해 보세요. 문제가 지속되면 지원팀에 문의하세요." }, "copied": "복사되었습니다!", "app": { @@ -302,8 +303,9 @@ }, "portfolio": { "buy": "구입하다", - "yourAssets": "귀하의 자산", + "yourBalances": "잔액", "yourPositions": "귀하의 직위", + "searchBalances": "잔액 검색", "yourSuperchargedPositions": "귀하의 슈퍼차지 위치", "yourLiquidityPools": "귀하의 유동성 풀", "connectWallet": "Osmosis에서 잔액과 포지션 추적을 시작하려면 지갑을 연결하세요.", @@ -324,7 +326,10 @@ "seeAll": "모두 보기", "show": "보여주다", "hide": "숨다", - "hidden": "숨겨진" + "hidden": "숨겨진", + "cypherSpend": "오스모시스 페이", + "cypherOrder": "지금 카드를 주문하세요", + "cypherBeta": "베타" }, "buyTokens": "토큰 구매", "components": { @@ -1106,7 +1111,6 @@ "resetFilters": "필터 재설정", "saveFilters": "필터 저장", "rewards": "보상", - "estimatedInYearlyRewards": "연간 보상으로 추정", "availableToClaim": "청구 가능", "claimAllRewards": "모든 보상을 받으세요", "join": "가입하다", @@ -1249,6 +1253,7 @@ "filled": "채우는", "claimable": "청구 가능", "claimAndClose": "청구 및 종료", + "close": "닫다", "claim": "주장하다", "accept": "수용하다", "cancelled": "취소 된", @@ -1299,7 +1304,6 @@ "connectYourWallet": "지갑을 연결하세요", "toSeeYourBalances": "잔액을 보려면", "searchAssets": "자산 검색", - "marketPrice": "시장 가격", "below": "아래에", "above": "~ 위에", "currentPrice": "현재 가격", @@ -1318,6 +1322,33 @@ "past": "과거", "pending": "보류 중", "filled": "주문이 채워져서 청구됩니다" + }, + "priceReaches": "{denom} 가격 {price} 도달하면", + "belowMinimumAmount": "{amount} 최소" + }, + "tradeDetails": { + "priceImpact": { + "header": "가격 영향이란 무엇인가?", + "content": "이는 시장 유동성이 귀하의 실행 가격에 미치는 영향입니다. 거래 규모가 클수록 시장에 미치는 영향이 더 큰 경향이 있습니다." + }, + "expectedRate": { + "header": "예상 가격이란 무엇인가요?", + "content": "이것은 당신이 받을 것으로 예상되는 가격입니다. 가격은 자주 바뀌므로 특정 가격으로 거래하고 싶다면 대신 제한 주문을 시도하세요." + }, + "swapFees": { + "header": "스왑 수수료는 무엇인가요?", + "content": "이는 유동성 공급자에게 보상하고 네트워크를 유지하기 위해 Osmosis 프로토콜이 부과하는 수수료입니다." + }, + "tradeRoute": { + "header": "무역로란 무엇인가?", + "contentTop": " 거래하는 자산 간에 직접적인 시장이 없는 경우 Osmosis는 다른 자산과 일련의 거래를 통해 언제나 가장 좋은 가격을 얻어 거래를 성사시키려고 시도합니다.", + "contentBottom": "이용 가능한 유동성에 따른 최적의 효율성을 위해, 때로는 거래가 다양한 자산을 대상으로 여러 경로로 분할될 것입니다." + }, + "outputDifference": { + "header": "출력 차이란 무엇인가요?", + "content": "이것은 당신이 지불하고 받는 것의 가치 차이입니다. 양수는 당신이 사는 금액이 더 가치가 있다는 것을 의미하고, 음수는 당신이 판매하는 금액이 더 가치가 있다는 것을 의미합니다." } - } + }, + "tradeFee": "거래 수수료", + "receiveAtLeast": "최소한 받다" } diff --git a/packages/web/localizations/pl.json b/packages/web/localizations/pl.json index 5a11c6e208..80fd769368 100644 --- a/packages/web/localizations/pl.json +++ b/packages/web/localizations/pl.json @@ -95,7 +95,8 @@ "totalBalance": "Całkowity bilans", "numPositions": "nieokreślony", "onePosition": "1 pozycja", - "goToPool": "Idź do basenu #{poolId}" + "goToPool": "Idź do basenu #{poolId}", + "errorFetchingPositions": "Nie udało się pobrać szczegółów pozycji. Spróbuj odświeżyć stronę. Jeśli problem będzie się powtarzał, skontaktuj się z pomocą techniczną." }, "copied": "Skopiowano!", "app": { @@ -302,8 +303,9 @@ }, "portfolio": { "buy": "Kupić", - "yourAssets": "Twoje aktywa", + "yourBalances": "Twoje saldo", "yourPositions": "Twoje pozycje", + "searchBalances": "Wyszukaj salda", "yourSuperchargedPositions": "Twoje doładowane pozycje", "yourLiquidityPools": "Twoje pule płynności", "connectWallet": "Podłącz swój portfel, aby rozpocząć śledzenie sald i pozycji w Osmozie.", @@ -324,7 +326,10 @@ "seeAll": "Zobacz wszystko", "show": "Pokazywać", "hide": "Ukrywać", - "hidden": "Ukryty" + "hidden": "Ukryty", + "cypherSpend": "Osmoza Płaca", + "cypherOrder": "Zamów swoją kartę już teraz", + "cypherBeta": "Beta" }, "buyTokens": "Kup tokeny", "components": { @@ -1106,7 +1111,6 @@ "resetFilters": "Zresetuj filtry", "saveFilters": "Zapisz filtry", "rewards": "Nagrody", - "estimatedInYearlyRewards": "Szacowane w rocznych nagrodach", "availableToClaim": "dostępne do reklamacji", "claimAllRewards": "Odbierz wszystkie nagrody", "join": "Dołączyć", @@ -1249,6 +1253,7 @@ "filled": "Wypełniony", "claimable": "Możliwość roszczenia", "claimAndClose": "Złóż wniosek i zamknij", + "close": "Zamknąć", "claim": "Prawo", "accept": "Zaakceptować", "cancelled": "Odwołany", @@ -1299,7 +1304,6 @@ "connectYourWallet": "Podłącz swój portfel", "toSeeYourBalances": "aby zobaczyć saldo", "searchAssets": "Wyszukaj zasoby", - "marketPrice": "Cena rynkowa", "below": "poniżej", "above": "powyżej", "currentPrice": "aktualna cena", @@ -1318,6 +1322,33 @@ "past": "Przeszłość", "pending": "Aż do", "filled": "Wypełnione zamówienia do odbioru" + }, + "priceReaches": "Jeśli cena {denom} osiągnie {price}", + "belowMinimumAmount": "{amount} minimalna" + }, + "tradeDetails": { + "priceImpact": { + "header": "Czym jest wpływ na cenę?", + "content": "To jest wpływ płynności rynku na cenę wykonania. Większe transakcje mają tendencję do większego wpływu na rynek." + }, + "expectedRate": { + "header": "Jaka jest oczekiwana stawka?", + "content": "To jest cena, którą powinieneś otrzymać. Ceny często się zmieniają, więc jeśli chcesz handlować po określonej cenie, spróbuj zamiast tego zlecenia limit." + }, + "swapFees": { + "header": "Czym są opłaty swapowe?", + "content": "Jest to opłata pobierana przez protokół Osmosis w celu nagradzania dostawców płynności i utrzymania sieci." + }, + "tradeRoute": { + "header": "Czym jest szlak handlowy?", + "contentTop": " Jeśli nie ma bezpośredniego rynku pomiędzy aktywami, którymi handlujesz, Osmosis spróbuje doprowadzić do transakcji poprzez przeprowadzenie serii transakcji z udziałem innych aktywów, aby uzyskać najlepszą cenę w danym momencie.", + "contentBottom": "Aby osiągnąć optymalną wydajność w oparciu o dostępną płynność, transakcje będą czasami dzielone na wiele tras obejmujących różne aktywa." + }, + "outputDifference": { + "header": "Czym jest różnica wyjściowa?", + "content": "Jest to różnica wartości między tym, co płacisz, a tym, co otrzymujesz. Liczby dodatnie oznaczają, że kwota, którą kupujesz, jest warta więcej, podczas gdy liczby ujemne oznaczają, że kwota, którą sprzedajesz, jest warta więcej." } - } + }, + "tradeFee": "Opłata za handel", + "receiveAtLeast": "Otrzymaj co najmniej" } diff --git a/packages/web/localizations/pt-br.json b/packages/web/localizations/pt-br.json index 136fe57588..dc0b446f49 100644 --- a/packages/web/localizations/pt-br.json +++ b/packages/web/localizations/pt-br.json @@ -95,7 +95,8 @@ "totalBalance": "Balanço total", "numPositions": "indefinido", "onePosition": "1 posição", - "goToPool": "Vá para a piscina #{poolId}" + "goToPool": "Vá para a piscina #{poolId}", + "errorFetchingPositions": "Falha ao buscar detalhes da posição. Tente atualizar a página. Se o problema persistir, entre em contato com o suporte." }, "copied": "Copiado!", "app": { @@ -302,8 +303,9 @@ }, "portfolio": { "buy": "Comprar", - "yourAssets": "Seus ativos", + "yourBalances": "Seus saldos", "yourPositions": "Suas posições", + "searchBalances": "Pesquisar saldos", "yourSuperchargedPositions": "Suas posições sobrecarregadas", "yourLiquidityPools": "Seus pools de liquidez", "connectWallet": "Conecte sua carteira para começar a rastrear seus saldos e posições no Osmosis.", @@ -324,7 +326,10 @@ "seeAll": "Ver tudo", "show": "Mostrar", "hide": "Esconder", - "hidden": "Escondido" + "hidden": "Escondido", + "cypherSpend": "Pagamento por osmose", + "cypherOrder": "Peça já o seu cartão", + "cypherBeta": "Beta" }, "buyTokens": "Comprar tokens", "components": { @@ -1106,7 +1111,6 @@ "resetFilters": "Redefinir filtros", "saveFilters": "Salvar filtros", "rewards": "Recompensas", - "estimatedInYearlyRewards": "Estimado em recompensas anuais", "availableToClaim": "disponível para reivindicar", "claimAllRewards": "Reivindique todas as recompensas", "join": "Juntar", @@ -1249,6 +1253,7 @@ "filled": "Preenchido", "claimable": "Exigível", "claimAndClose": "Reivindique e feche", + "close": "Fechar", "claim": "Alegar", "accept": "Aceitar", "cancelled": "Cancelado", @@ -1299,7 +1304,6 @@ "connectYourWallet": "Conecte sua carteira", "toSeeYourBalances": "para ver seus saldos", "searchAssets": "Pesquisar ativos", - "marketPrice": "preço de mercado", "below": "abaixo", "above": "acima", "currentPrice": "preço atual", @@ -1318,6 +1322,33 @@ "past": "Passado", "pending": "Pendente", "filled": "Pedidos preenchidos para reivindicar" + }, + "priceReaches": "Se o preço {denom} atingir {price}", + "belowMinimumAmount": "{amount} mínimo" + }, + "tradeDetails": { + "priceImpact": { + "header": "O que é impacto de preço?", + "content": "Este é o impacto que a liquidez do mercado tem no seu preço de execução. Negociações maiores tendem a ter maior impacto no mercado." + }, + "expectedRate": { + "header": "Qual é a taxa esperada?", + "content": "Este é o preço que você espera receber. Os preços mudam frequentemente, então se você deseja negociar a um preço específico, tente uma ordem limite." + }, + "swapFees": { + "header": "O que são taxas de swap?", + "content": "Esta é a taxa cobrada pelo protocolo Osmosis para recompensar os provedores de liquidez e manter a rede." + }, + "tradeRoute": { + "header": "O que é uma rota comercial?", + "contentTop": " Se não houver um mercado direto entre os ativos que você está negociando, a Osmosis tentará fazer a negociação acontecer realizando uma série de negociações com outros ativos para obter o melhor preço em qualquer momento.", + "contentBottom": "Para eficiência ideal com base na liquidez disponível, às vezes as negociações serão divididas em várias rotas com ativos diferentes." + }, + "outputDifference": { + "header": "O que é diferença de saída?", + "content": "Essa é a diferença de valor entre o que você paga e recebe. Números positivos significam que a quantia que você está comprando vale mais, enquanto números negativos significam que a quantia que você está vendendo vale mais." } - } + }, + "tradeFee": "Taxa de negociação", + "receiveAtLeast": "Receba pelo menos" } diff --git a/packages/web/localizations/ro.json b/packages/web/localizations/ro.json index 20a51c27e2..2a66637478 100644 --- a/packages/web/localizations/ro.json +++ b/packages/web/localizations/ro.json @@ -95,7 +95,8 @@ "totalBalance": "Sold total", "numPositions": "nedefinit", "onePosition": "1 pozitie", - "goToPool": "Mergi la piscina #{poolId}" + "goToPool": "Mergi la piscina #{poolId}", + "errorFetchingPositions": "Nu s-au putut prelua detaliile despre poziție. Vă rugăm să încercați să reîmprospătați pagina. Dacă problema persistă, contactați asistența." }, "copied": "Copiat!", "app": { @@ -302,8 +303,9 @@ }, "portfolio": { "buy": "Cumpără", - "yourAssets": "Activele tale", + "yourBalances": "Soldurile tale", "yourPositions": "Pozițiile tale", + "searchBalances": "Căutați solduri", "yourSuperchargedPositions": "Pozițiile tale Supercharged", "yourLiquidityPools": "Fondurile dvs. de lichiditate", "connectWallet": "Conectați-vă portofelul pentru a începe să vă urmăriți soldurile și pozițiile pe Osmosis.", @@ -324,7 +326,10 @@ "seeAll": "Vezi toate", "show": "Spectacol", "hide": "Ascunde", - "hidden": "Ascuns" + "hidden": "Ascuns", + "cypherSpend": "Plată osmoză", + "cypherOrder": "Comanda cardul tau acum", + "cypherBeta": "Beta" }, "buyTokens": "Cumpărați jetoane", "components": { @@ -1106,7 +1111,6 @@ "resetFilters": "Resetați filtrele", "saveFilters": "Salvați filtrele", "rewards": "Recompense", - "estimatedInYearlyRewards": "Estimat în recompense anuale", "availableToClaim": "disponibil pentru revendicare", "claimAllRewards": "Revendicați toate recompensele", "join": "A te alatura", @@ -1249,6 +1253,7 @@ "filled": "Umplut", "claimable": "Revendicabil", "claimAndClose": "Revendicați și închideți", + "close": "Aproape", "claim": "Revendicare", "accept": "Accept", "cancelled": "Anulat", @@ -1299,7 +1304,6 @@ "connectYourWallet": "Conectați-vă portofelul", "toSeeYourBalances": "pentru a vă vedea soldurile", "searchAssets": "Căutați active", - "marketPrice": "pretul din magazin", "below": "de mai jos", "above": "de mai sus", "currentPrice": "pretul curent", @@ -1318,6 +1322,33 @@ "past": "Trecut", "pending": "In asteptarea", "filled": "Comenzi completate pentru revendicare" + }, + "priceReaches": "Dacă prețul {denom} atinge {price}", + "belowMinimumAmount": "{amount} minim" + }, + "tradeDetails": { + "priceImpact": { + "header": "Ce este impactul prețului?", + "content": "Acesta este impactul pe care lichiditatea pieței îl are asupra prețului de execuție. Tranzacțiile mai mari tind să aibă un impact mai mare pe piață." + }, + "expectedRate": { + "header": "Care este rata așteptată?", + "content": "Acesta este prețul pe care vă așteptați să îl primiți. Prețurile se schimbă frecvent, așa că dacă doriți să tranzacționați la un anumit preț, încercați în schimb un ordin limită." + }, + "swapFees": { + "header": "Ce sunt comisioanele de swap?", + "content": "Acesta este comisionul perceput de protocolul Osmosis pentru a recompensa furnizorii de lichidități și a menține rețeaua." + }, + "tradeRoute": { + "header": "Ce este o rută comercială?", + "contentTop": " Dacă nu există o piață directă între activele pe care le tranzacționați, Osmosis va încerca să facă tranzacția efectuând o serie de tranzacții cu alte active pentru a obține cel mai bun preț la un moment dat.", + "contentBottom": "Pentru o eficiență optimă pe baza lichidității disponibile, uneori tranzacțiile vor fi împărțite în mai multe rute cu active diferite." + }, + "outputDifference": { + "header": "Care este diferența de ieșire?", + "content": "Aceasta este diferența de valoare dintre ceea ce plătești și ceea ce primești. Cifrele pozitive înseamnă că suma pe care o cumpărați valorează mai mult, în timp ce cifrele negative înseamnă că suma pe care o vindeți valorează mai mult." } - } + }, + "tradeFee": "Comision comercială", + "receiveAtLeast": "Primește cel puțin" } diff --git a/packages/web/localizations/ru.json b/packages/web/localizations/ru.json index 8cdbcf2643..1f9817c0cf 100644 --- a/packages/web/localizations/ru.json +++ b/packages/web/localizations/ru.json @@ -95,7 +95,8 @@ "totalBalance": "Итоговый баланс", "numPositions": "{numPositions} позиции", "onePosition": "1 позиция", - "goToPool": "Перейти к пулу № #{poolId}" + "goToPool": "Перейти к пулу № #{poolId}", + "errorFetchingPositions": "Не удалось получить данные о позиции. Попробуйте обновить страницу. Если проблема не устранена, обратитесь в службу поддержки." }, "copied": "Скопировано!", "app": { @@ -302,8 +303,9 @@ }, "portfolio": { "buy": "Купить", - "yourAssets": "Ваши активы", + "yourBalances": "Ваши балансы", "yourPositions": "Ваши позиции", + "searchBalances": "Поиск остатков", "yourSuperchargedPositions": "Ваши суперзаряженные позиции", "yourLiquidityPools": "Ваши пулы ликвидности", "connectWallet": "Подключите свой кошелек, чтобы начать отслеживать свои балансы и позиции на Osmosis.", @@ -324,7 +326,10 @@ "seeAll": "Смотреть все", "show": "Показывать", "hide": "Скрывать", - "hidden": "Скрытый" + "hidden": "Скрытый", + "cypherSpend": "Платить за осмос", + "cypherOrder": "Закажите карту сейчас", + "cypherBeta": "Бета" }, "buyTokens": "Купить токены", "components": { @@ -1106,7 +1111,6 @@ "resetFilters": "Сбросить фильтры", "saveFilters": "Сохранить фильтры", "rewards": "Награды", - "estimatedInYearlyRewards": "Расчетное годовое вознаграждение", "availableToClaim": "доступен для претензии", "claimAllRewards": "Получите все награды", "join": "Присоединиться", @@ -1249,6 +1253,7 @@ "filled": "Заполненный", "claimable": "Заявленный", "claimAndClose": "Заявить права и закрыть", + "close": "Закрывать", "claim": "Требовать", "accept": "Принимать", "cancelled": "Отменено", @@ -1299,7 +1304,6 @@ "connectYourWallet": "Подключите свой кошелек", "toSeeYourBalances": "чтобы увидеть свой баланс", "searchAssets": "Поиск активов", - "marketPrice": "рыночная цена", "below": "ниже", "above": "выше", "currentPrice": "текущая цена", @@ -1318,6 +1322,33 @@ "past": "Прошлое", "pending": "В ожидании", "filled": "Выполненные заказы на претензии" + }, + "priceReaches": "Если цена {denom} достигает {price}", + "belowMinimumAmount": "{amount} минимум" + }, + "tradeDetails": { + "priceImpact": { + "header": "Что такое влияние на цену?", + "content": "Это влияние рыночной ликвидности на цену исполнения. Более крупные сделки, как правило, оказывают большее влияние на рынок." + }, + "expectedRate": { + "header": "Какова ожидаемая ставка?", + "content": "Это цена, которую вы ожидаете получить. Цены часто меняются, поэтому, если вы хотите торговать по определенной цене, попробуйте вместо этого лимитный ордер." + }, + "swapFees": { + "header": "Что такое комиссии за своп?", + "content": "Это комиссия, взимаемая протоколом Osmosis для вознаграждения поставщиков ликвидности и поддержания работы сети." + }, + "tradeRoute": { + "header": "Что такое торговый путь?", + "contentTop": " Если между активами, которыми вы торгуете, нет прямого рынка, Osmosis попытается осуществить сделку, совершив серию сделок с другими активами, чтобы получить лучшую цену в любой момент времени.", + "contentBottom": "Для достижения оптимальной эффективности на основе доступной ликвидности иногда сделки разделяются на несколько маршрутов с разными активами." + }, + "outputDifference": { + "header": "Какова разница на выходе?", + "content": "Это разница в стоимости между тем, что вы платите и получаете. Положительные числа означают, что сумма, которую вы покупаете, стоит дороже, а отрицательные числа означают, что сумма, которую вы продаете, стоит дороже." } - } + }, + "tradeFee": "Торговый сбор", + "receiveAtLeast": "Получите по крайней мере" } diff --git a/packages/web/localizations/tr.json b/packages/web/localizations/tr.json index cd30fea18b..ccad2b60ad 100644 --- a/packages/web/localizations/tr.json +++ b/packages/web/localizations/tr.json @@ -95,7 +95,8 @@ "totalBalance": "Toplam bakiye", "numPositions": "Tanımsız", "onePosition": "1 pozisyon", - "goToPool": "havuza git #{poolId}" + "goToPool": "havuza git #{poolId}", + "errorFetchingPositions": "Pozisyon detayları alınamadı. Lütfen sayfayı yenilemeyi deneyin. Sorun devam ederse, destekle iletişime geçin." }, "copied": "Kopyalandı!", "app": { @@ -302,8 +303,9 @@ }, "portfolio": { "buy": "Satın almak", - "yourAssets": "Varlıklarınız", + "yourBalances": "Bakiyeleriniz", "yourPositions": "Pozisyonlarınız", + "searchBalances": "Bakiyeleri ara", "yourSuperchargedPositions": "Süper Yüklü pozisyonlarınız", "yourLiquidityPools": "Likidite havuzlarınız", "connectWallet": "Osmosis'te bakiyelerinizi ve pozisyonlarınızı takip etmeye başlamak için cüzdanınızı bağlayın.", @@ -324,7 +326,10 @@ "seeAll": "Tümünü gör", "show": "Göstermek", "hide": "Saklamak", - "hidden": "Gizlenmiş" + "hidden": "Gizlenmiş", + "cypherSpend": "Osmoz Ödeme", + "cypherOrder": "Kartınızı şimdi sipariş edin", + "cypherBeta": "Beta" }, "buyTokens": "jeton satın al", "components": { @@ -1106,7 +1111,6 @@ "resetFilters": "Filtreleri sıfırla", "saveFilters": "Filtreleri kaydet", "rewards": "Ödüller", - "estimatedInYearlyRewards": "Yıllık ödüllerde tahmin edilir", "availableToClaim": "talep edilebilir", "claimAllRewards": "Tüm ödülleri talep edin", "join": "Katılmak", @@ -1249,6 +1253,7 @@ "filled": "Dolu", "claimable": "Hak talebinde bulunulabilir", "claimAndClose": "Talep et ve kapat", + "close": "Kapalı", "claim": "İddia", "accept": "Kabul etmek", "cancelled": "İptal edildi", @@ -1299,7 +1304,6 @@ "connectYourWallet": "Cüzdanınızı bağlayın", "toSeeYourBalances": "bakiyenizi görmek için", "searchAssets": "Varlıkları arayın", - "marketPrice": "Market fiyatı", "below": "altında", "above": "üstünde", "currentPrice": "Mevcut fiyat", @@ -1318,6 +1322,33 @@ "past": "Geçmiş", "pending": "Askıda olması", "filled": "Talep edilecek siparişler tamamlandı" + }, + "priceReaches": "{denom} fiyatı {price} ulaşırsa", + "belowMinimumAmount": "{amount} minimum" + }, + "tradeDetails": { + "priceImpact": { + "header": "Fiyat etkisi nedir?", + "content": "Bu, piyasa likiditesinin yürütme fiyatınız üzerindeki etkisidir. Daha büyük işlemlerin daha büyük piyasa etkisine sahip olma eğilimi vardır." + }, + "expectedRate": { + "header": "Beklenen oran nedir?", + "content": "Bu, almanız beklenen fiyattır. Fiyatlar sıklıkla değişir, bu nedenle belirli bir fiyattan işlem yapmak istiyorsanız, bunun yerine bir limit emri deneyin." + }, + "swapFees": { + "header": "Swap ücretleri nelerdir?", + "content": "Bu, likidite sağlayıcılarını ödüllendirmek ve ağı sürdürmek amacıyla Osmosis protokolü tarafından tahsil edilen ücrettir." + }, + "tradeRoute": { + "header": "Ticaret yolu nedir?", + "contentTop": " İşlem yaptığınız varlıklar arasında doğrudan bir piyasa yoksa Osmosis, belirli bir zamanda en iyi fiyatı elde etmek için diğer varlıklarla bir dizi işlem yaparak işlemi gerçekleştirmeye çalışacaktır.", + "contentBottom": "Mevcut likiditeye bağlı olarak optimum verimlilik için, bazen işlemler farklı varlıklarla birden fazla rotaya bölünecektir." + }, + "outputDifference": { + "header": "Çıktı farkı nedir?", + "content": "Bu, ödediğiniz ve aldığınız arasındaki değer farkıdır. Pozitif sayılar satın aldığınız miktarın daha değerli olduğu anlamına gelirken, negatif sayılar sattığınız miktarın daha değerli olduğu anlamına gelir." } - } + }, + "tradeFee": "Ticaret ücreti", + "receiveAtLeast": "En azından al" } diff --git a/packages/web/localizations/zh-cn.json b/packages/web/localizations/zh-cn.json index dbc59f8dec..073ec705bf 100644 --- a/packages/web/localizations/zh-cn.json +++ b/packages/web/localizations/zh-cn.json @@ -95,7 +95,8 @@ "totalBalance": "总余额", "numPositions": "不明确的", "onePosition": "1 个职位", - "goToPool": "前往泳池#{poolId}" + "goToPool": "前往泳池#{poolId}", + "errorFetchingPositions": "无法获取职位详情。请尝试刷新页面。如果问题仍然存在,请联系支持人员。" }, "copied": "已复制!", "app": { @@ -302,8 +303,9 @@ }, "portfolio": { "buy": "买", - "yourAssets": "您的资产", + "yourBalances": "您的余额", "yourPositions": "您的职位", + "searchBalances": "搜索余额", "yourSuperchargedPositions": "您的超级职位", "yourLiquidityPools": "您的流动资金池", "connectWallet": "连接您的钱包,开始跟踪 Osmosis 上的余额和头寸。", @@ -324,7 +326,10 @@ "seeAll": "查看全部", "show": "展示", "hide": "隐藏", - "hidden": "隐" + "hidden": "隐", + "cypherSpend": "渗透支付", + "cypherOrder": "立即订购您的卡", + "cypherBeta": "测试版" }, "buyTokens": "购买代币", "components": { @@ -1106,7 +1111,6 @@ "resetFilters": "重置过滤器", "saveFilters": "保存过滤器", "rewards": "奖励", - "estimatedInYearlyRewards": "预计每年奖励", "availableToClaim": "可索赔", "claimAllRewards": "领取所有奖励", "join": "加入", @@ -1249,6 +1253,7 @@ "filled": "填充", "claimable": "可索偿", "claimAndClose": "认领并关闭", + "close": "关闭", "claim": "宣称", "accept": "接受", "cancelled": "取消", @@ -1299,7 +1304,6 @@ "connectYourWallet": "连接你的钱包", "toSeeYourBalances": "查看你的余额", "searchAssets": "搜索资产", - "marketPrice": "市场价", "below": "以下", "above": "多于", "currentPrice": "时价", @@ -1318,6 +1322,33 @@ "past": "过去的", "pending": "待办的", "filled": "已填写订单并领取" + }, + "priceReaches": "如果{denom}价格达到{price}", + "belowMinimumAmount": "{amount}最小值" + }, + "tradeDetails": { + "priceImpact": { + "header": "什么是价格影响?", + "content": "这是市场流动性对执行价格的影响。交易量越大,市场影响就越大。" + }, + "expectedRate": { + "header": "預期利率是多少?", + "content": "这是您预期收到的价格。价格经常变化,因此如果您希望以特定价格进行交易,请尝试限价订单。" + }, + "swapFees": { + "header": "什么是掉期费?", + "content": "这是 Osmosis 协议为了奖励流动性提供者和维护网络而收取的费用。" + }, + "tradeRoute": { + "header": "什么是贸易路线?", + "contentTop": " 如果您交易的资产之间没有直接市场,Osmosis 将尝试通过与其他资产进行一系列交易来实现交易,以便在任何给定时间获得最佳价格。", + "contentBottom": "为了根据可用流动性实现最佳效率,有时交易会分成具有不同资产的多条路线。" + }, + "outputDifference": { + "header": "产量差异是什么?", + "content": "这是您支付和收到的金额之间的差额。正数表示您购买的金额价值更高,而负数表示您出售的金额价值更高。" } - } + }, + "tradeFee": "交易费", + "receiveAtLeast": "至少收到" } diff --git a/packages/web/localizations/zh-hk.json b/packages/web/localizations/zh-hk.json index e57f44cab3..965d0cb028 100644 --- a/packages/web/localizations/zh-hk.json +++ b/packages/web/localizations/zh-hk.json @@ -95,7 +95,8 @@ "totalBalance": "總餘額", "numPositions": "不明確的", "onePosition": "1 個位置", - "goToPool": "前往泳池 #{poolId}" + "goToPool": "前往泳池 #{poolId}", + "errorFetchingPositions": "無法取得職位詳細資料。請嘗試刷新頁面。如果問題仍然存在,請聯絡支援人員。" }, "copied": "複製了!", "app": { @@ -302,8 +303,9 @@ }, "portfolio": { "buy": "買", - "yourAssets": "您的資產", + "yourBalances": "您的餘額", "yourPositions": "您的職位", + "searchBalances": "搜尋餘額", "yourSuperchargedPositions": "您的超級職位", "yourLiquidityPools": "您的流動資金池", "connectWallet": "連接您的錢包,開始追蹤您在 Osmosis 上的餘額和部位。", @@ -324,7 +326,10 @@ "seeAll": "看全部", "show": "展示", "hide": "隱藏", - "hidden": "隱" + "hidden": "隱", + "cypherSpend": "滲透支付", + "cypherOrder": "立即訂購您的卡", + "cypherBeta": "貝塔" }, "buyTokens": "購買代幣", "components": { @@ -1106,7 +1111,6 @@ "resetFilters": "重置過濾器", "saveFilters": "保存過濾器", "rewards": "獎勵", - "estimatedInYearlyRewards": "預計每年獎勵", "availableToClaim": "可索賠", "claimAllRewards": "領取所有獎勵", "join": "加入", @@ -1249,6 +1253,7 @@ "filled": "填充", "claimable": "可索賠", "claimAndClose": "索賠並關閉", + "close": "關閉", "claim": "宣稱", "accept": "接受", "cancelled": "取消", @@ -1299,7 +1304,6 @@ "connectYourWallet": "連接你的錢包", "toSeeYourBalances": "查看您的餘額", "searchAssets": "搜尋資產", - "marketPrice": "市價", "below": "以下", "above": "多於", "currentPrice": "時價", @@ -1318,6 +1322,33 @@ "past": "過去的", "pending": "待辦的", "filled": "已填寫訂單以領取" + }, + "priceReaches": "如果{denom}價格達到{price}", + "belowMinimumAmount": "{amount}最小值" + }, + "tradeDetails": { + "priceImpact": { + "header": "什麼是價格影響?", + "content": "這就是市場流動性對您的執行價格的影響。較大的交易往往會產生較大的市場影響。" + }, + "expectedRate": { + "header": "預期利率是多少?", + "content": "這是您預計收到的價格。價格經常變化,因此如果您希望以特定價格進行交易,請嘗試限價訂單。" + }, + "swapFees": { + "header": "什麼是掉期費用?", + "content": "這是 Osmosis 協議為了獎勵流動性提供者和維護網路而收取的費用。" + }, + "tradeRoute": { + "header": "什麼是貿易路線?", + "contentTop": " 如果您正在交易的資產之間沒有直接市場,Osmosis 將嘗試透過與其他資產進行一系列交易來實現交易,以便在任何給定時間獲得最佳價格。", + "contentBottom": "為了根據可用流動性實現最佳效率,有時交易會被分成具有不同資產的多條路線。" + }, + "outputDifference": { + "header": "什麼是輸出差異?", + "content": "這是您支付和收到的價值之間的差異。正數意味著您購買的金額更值錢,而負數意味著您賣出的金額更有價值。" } - } + }, + "tradeFee": "交易費", + "receiveAtLeast": "至少收到" } diff --git a/packages/web/localizations/zh-tw.json b/packages/web/localizations/zh-tw.json index 497a3ae88d..e446ff9a2f 100644 --- a/packages/web/localizations/zh-tw.json +++ b/packages/web/localizations/zh-tw.json @@ -95,7 +95,8 @@ "totalBalance": "總餘額", "numPositions": "不明確的", "onePosition": "1 個位置", - "goToPool": "前往泳池#{poolId}" + "goToPool": "前往泳池#{poolId}", + "errorFetchingPositions": "無法取得職位詳細資料。請嘗試刷新頁面。如果問題仍然存在,請聯絡支援人員。" }, "copied": "複製了!", "app": { @@ -302,8 +303,9 @@ }, "portfolio": { "buy": "買", - "yourAssets": "您的資產", + "yourBalances": "您的餘額", "yourPositions": "您的職位", + "searchBalances": "搜尋餘額", "yourSuperchargedPositions": "您的超級職位", "yourLiquidityPools": "您的流動資金池", "connectWallet": "連接您的錢包,開始追蹤您在 Osmosis 上的餘額和部位。", @@ -324,7 +326,10 @@ "seeAll": "看全部", "show": "展示", "hide": "隱藏", - "hidden": "隱" + "hidden": "隱", + "cypherSpend": "滲透支付", + "cypherOrder": "立即訂購您的卡", + "cypherBeta": "貝塔" }, "buyTokens": "購買代幣", "components": { @@ -1106,7 +1111,6 @@ "resetFilters": "重置過濾器", "saveFilters": "保存過濾器", "rewards": "獎勵", - "estimatedInYearlyRewards": "預計每年獎勵", "availableToClaim": "可索賠", "claimAllRewards": "領取所有獎勵", "join": "加入", @@ -1249,6 +1253,7 @@ "filled": "填充", "claimable": "可索賠", "claimAndClose": "索賠並關閉", + "close": "關閉", "claim": "宣稱", "accept": "接受", "cancelled": "取消", @@ -1299,7 +1304,6 @@ "connectYourWallet": "連接你的錢包", "toSeeYourBalances": "查看您的餘額", "searchAssets": "搜尋資產", - "marketPrice": "市價", "below": "以下", "above": "多於", "currentPrice": "時價", @@ -1318,6 +1322,33 @@ "past": "過去的", "pending": "待辦的", "filled": "已填寫訂單以領取" + }, + "priceReaches": "如果{denom}價格達到{price}", + "belowMinimumAmount": "{amount}最小值" + }, + "tradeDetails": { + "priceImpact": { + "header": "什麼是價格影響?", + "content": "這就是市場流動性對您的執行價格的影響。較大的交易往往會產生較大的市場影響。" + }, + "expectedRate": { + "header": "預期利率是多少?", + "content": "這是您預計收到的價格。價格經常變化,因此如果您希望以特定價格進行交易,請嘗試限價訂單。" + }, + "swapFees": { + "header": "什麼是掉期費用?", + "content": "這是 Osmosis 協議為了獎勵流動性提供者和維護網路而收取的費用。" + }, + "tradeRoute": { + "header": "什麼是貿易路線?", + "contentTop": " 如果您正在交易的資產之間沒有直接市場,Osmosis 將嘗試透過與其他資產進行一系列交易來實現交易,以便在任何給定時間獲得最佳價格。", + "contentBottom": "為了根據可用流動性實現最佳效率,有時交易會被分成具有不同資產的多條路線。" + }, + "outputDifference": { + "header": "什麼是輸出差異?", + "content": "這是您支付和收到的價值之間的差異。正數意味著您購買的金額更值錢,而負數意味著您賣出的金額更有價值。" } - } + }, + "tradeFee": "交易費", + "receiveAtLeast": "至少收到" } diff --git a/packages/web/middleware.ts b/packages/web/middleware.ts index 197094d790..e1dc0cc548 100644 --- a/packages/web/middleware.ts +++ b/packages/web/middleware.ts @@ -12,10 +12,8 @@ export function middleware(request: NextRequest) { export const config = { matcher: [ /* - * Match all request paths except for the ones starting with: - * - monitoring: Required in order to get Sentry data. - * See https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/#configure-tunneling-to-avoid-ad-blockers) + * Match all request paths */ - "/((?!monitoring).*)", + "/(.*)", ], }; diff --git a/packages/web/modals/bridge-transfer-v2.tsx b/packages/web/modals/bridge-transfer-v2.tsx index 948f397151..cb16b2f6a3 100644 --- a/packages/web/modals/bridge-transfer-v2.tsx +++ b/packages/web/modals/bridge-transfer-v2.tsx @@ -9,6 +9,7 @@ import type { SourceChainTokenConfig, } from "@osmosis-labs/bridge"; import { DeliverTxResponse } from "@osmosis-labs/stores"; +import { makeIBCTransferMsg } from "@osmosis-labs/tx"; import { Currency } from "@osmosis-labs/types"; import { AxelarSourceChain, @@ -441,7 +442,7 @@ export const TransferContent: FunctionComponent< const feeConfig = useFakeFeeConfig( chainStore, osmosisChainId, - osmosisAccount?.cosmos.msgOpts.ibcTransfer.gas ?? 0 + makeIBCTransferMsg.gas ?? 0 ); // WITHDRAW diff --git a/packages/web/modals/review-order.tsx b/packages/web/modals/review-order.tsx index 8fdb74185d..3f704445f6 100644 --- a/packages/web/modals/review-order.tsx +++ b/packages/web/modals/review-order.tsx @@ -162,7 +162,8 @@ export function ReviewOrder({ ); useEffect(() => { - if (limitSetPriceLock && orderType === "limit") limitSetPriceLock(isOpen); + if (limitSetPriceLock && orderType === "limit" && isOpen) + limitSetPriceLock(true); }, [limitSetPriceLock, isOpen, orderType]); const gasFeeError = useMemo(() => { @@ -248,12 +249,15 @@ export function ReviewOrder({ )}
- If {baseDenom} price reaches{" "} - {limitPriceFiat && - formatPretty( - limitPriceFiat, - getPriceExtendedFormatOptions(limitPriceFiat.toDec()) - )} + {t("limitOrders.priceReaches", { + denom: baseDenom ?? "", + price: limitPriceFiat + ? formatPretty( + limitPriceFiat, + getPriceExtendedFormatOptions(limitPriceFiat.toDec()) + ) + : "", + })} {percentAdjusted && (
@@ -555,7 +559,7 @@ export function ReviewOrder({ )} {orderType === "market" ? ( {outAmountLessSlippage && @@ -584,7 +588,7 @@ export function ReviewOrder({ /> ) : ( {t("transfer.free")} @@ -595,13 +599,12 @@ export function ReviewOrder({ - {!isGasLoading ? ( - GasEstimation - ) : ( - - )} - + // Do not show skeleton unless there has been no estimation/error yet + !(isGasLoading && !(!!gasAmount || !!gasError)) ? ( + GasEstimation + ) : ( + + ) } />
diff --git a/packages/web/next.config.js b/packages/web/next.config.js index 8e3af0d46a..3ab2d65125 100644 --- a/packages/web/next.config.js +++ b/packages/web/next.config.js @@ -79,6 +79,9 @@ const config = { return config; }, + experimental: { + instrumentationHook: true, + }, }; const withBundleAnalyzer = require("@next/bundle-analyzer")({ @@ -86,41 +89,3 @@ const withBundleAnalyzer = require("@next/bundle-analyzer")({ }); module.exports = withBundleAnalyzer(config); - -// Injected content via Sentry wizard below - -const { withSentryConfig } = require("@sentry/nextjs"); - -module.exports = withSentryConfig( - module.exports, - { - // For all available options, see: - // https://github.com/getsentry/sentry-webpack-plugin#options - - // Suppresses source map uploading logs during build - silent: true, - - authToken: process.env.SENTRY_AUTH_TOKEN, - - org: "osmosis-labs", - project: "osmosis-web", - }, - { - // For all available options, see: - // https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/ - - // Upload a larger set of source maps for prettier stack traces (increases build time) - widenClientFileUpload: true, - - transpileClientSDK: false, - - // Routes browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers (increases server load) - tunnelRoute: "/monitoring", - - // Hides source maps from generated client bundles - hideSourceMaps: true, - - // Automatically tree-shake Sentry logger statements to reduce bundle size - disableLogger: true, - } -); diff --git a/packages/web/package.json b/packages/web/package.json index fa98a4a187..d1d0106a9c 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -51,6 +51,8 @@ "@keplr-wallet/wc-client": "0.10.24-ibc.go.v7.hot.fix", "@metamask/onboarding": "^1.0.1", "@next/bundle-analyzer": "^12.1.6", + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/api-logs": "^0.52.1", "@osmosis-labs/bridge": "^1.0.0", "@osmosis-labs/keplr-hooks": "0.10.24-ibc.go.v7.hot.fix", "@osmosis-labs/keplr-stores": "0.10.24-ibc.go.v7.hot.fix", @@ -71,7 +73,6 @@ "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-switch": "^1.0.3", "@react-spring/web": "^9.6.1", - "@sentry/nextjs": "^7.109.0", "@tanstack/match-sorter-utils": "^8.8.4", "@tanstack/query-async-storage-persister": "^4.36.1", "@tanstack/react-query": "^4.32.6", @@ -84,6 +85,7 @@ "@trpc/next": "^10.45.1", "@trpc/react-query": "^10.45.1", "@trpc/server": "^10.45.1", + "@vercel/otel": "^1.9.1", "@visx/curve": "^2.17.0", "@visx/gradient": "^3.3.0", "@visx/responsive": "^2.17.0", @@ -189,7 +191,7 @@ "tailwindcss": "^3.2.4", "ts-jest": "^29.1.2", "tsconfig-paths": "^4.2.0", - "typescript": "^5.4.5", + "typescript": "5.4.5", "use-debugger-hooks": "^1.3.0" } } diff --git a/packages/web/pages/assets/[denom].tsx b/packages/web/pages/assets/[denom].tsx index 7288d3a80d..128d04389f 100644 --- a/packages/web/pages/assets/[denom].tsx +++ b/packages/web/pages/assets/[denom].tsx @@ -17,7 +17,7 @@ import Script from "next/script"; import { NextSeo } from "next-seo"; import { useQueryState } from "nuqs"; import { FunctionComponent, useEffect, useMemo } from "react"; -import { useUnmount } from "react-use"; +import { useLocalStorage, useUnmount } from "react-use"; import { AlloyedAssetsSection } from "~/components/alloyed-assets"; import { LinkButton } from "~/components/buttons/link-button"; @@ -44,6 +44,7 @@ import { } from "~/hooks"; import { useAssetInfo } from "~/hooks/use-asset-info"; import { AssetInfoViewProvider } from "~/hooks/use-asset-info-view"; +import { PreviousTrade, SwapPreviousTradeKey } from "~/pages"; import { SUPPORTED_LANGUAGES } from "~/stores/user-settings"; import { trpcHelpers } from "~/utils/helpers"; @@ -75,6 +76,8 @@ const AssetInfoView: FunctionComponent = observer( const { t } = useTranslation(); const router = useRouter(); const featureFlags = useFeatureFlags(); + const [previousTrade, setPreviousTrade] = + useLocalStorage(SwapPreviousTradeKey); const { title, details, coinGeckoId, asset: asset } = useAssetInfo(); @@ -145,7 +148,17 @@ const AssetInfoView: FunctionComponent = observer( ); const SwapTool_ = featureFlags.limitOrders ? ( - + ) : ( ); diff --git a/packages/web/pages/index.tsx b/packages/web/pages/index.tsx index e647d9178a..4592945e49 100644 --- a/packages/web/pages/index.tsx +++ b/packages/web/pages/index.tsx @@ -16,6 +16,8 @@ export const SwapPreviousTradeKey = "swap-previous-trade"; export type PreviousTrade = { sendTokenDenom: string; outTokenDenom: string; + baseDenom: string; + quoteDenom: string; }; const Home = () => { @@ -27,6 +29,9 @@ const Home = () => { const HomeNew = () => { const featureFlags = useFeatureFlags(); + const [previousTrade, setPreviousTrade] = + useLocalStorage(SwapPreviousTradeKey); + useAmplitudeAnalytics({ onLoadEvent: [EventName.Swap.pageViewed, { isOnHome: true }], }); @@ -48,7 +53,11 @@ const HomeNew = () => {
{featureFlags.swapsAdBanner && } - +
@@ -102,7 +111,12 @@ const HomeV1 = () => { useQueryParams useOtherCurrencies onSwapSuccess={({ sendTokenDenom, outTokenDenom }) => { - setPreviousTrade({ sendTokenDenom, outTokenDenom }); + setPreviousTrade({ + sendTokenDenom, + outTokenDenom, + baseDenom: previousTrade?.baseDenom ?? "", + quoteDenom: previousTrade?.quoteDenom ?? "", + }); }} initialSendTokenDenom={previousTrade?.sendTokenDenom} initialOutTokenDenom={previousTrade?.outTokenDenom} diff --git a/packages/web/pages/portfolio/index.tsx b/packages/web/pages/portfolio/index.tsx index eab96d8e8e..c5bf62e87b 100644 --- a/packages/web/pages/portfolio/index.tsx +++ b/packages/web/pages/portfolio/index.tsx @@ -1,7 +1,5 @@ import type { NextPage } from "next"; -import { useRouter } from "next/router"; import { NextSeo } from "next-seo"; -import { useEffect } from "react"; import { AssetsPageV1 } from "~/components/complex/assets-page-v1"; import { PortfolioPage } from "~/components/complex/portfolio/portfolio-page"; @@ -15,8 +13,6 @@ const Portfolio: NextPage = () => { const { t } = useTranslation(); const featureFlags = useFeatureFlags(); - useRedirectToAssetsPage(); - if (!featureFlags._isInitialized) { return ( { title={t("seo.portfolio.title")} description={t("seo.portfolio.description")} /> - {featureFlags.portfolioPageAndNewAssetsPage ? ( - - ) : ( - - )} + {featureFlags.newPortfolioPage ? : } ); }; -/** Redirect to assets page if neither new assets page (old page moves here) - or new portfolio page is enabled */ -function useRedirectToAssetsPage() { - const featureFlags = useFeatureFlags(); - const router = useRouter(); - - useEffect(() => { - if ( - featureFlags._isInitialized && - !featureFlags.portfolioPageAndNewAssetsPage && - !featureFlags.newAssetsPage && - router.isReady - ) { - router.push("/assets"); - } - }, [ - featureFlags._isInitialized, - featureFlags.portfolioPageAndNewAssetsPage, - featureFlags.newAssetsPage, - router, - ]); -} - export default Portfolio; diff --git a/packages/web/pages/stake.tsx b/packages/web/pages/stake.tsx index 94efb6a0cc..68c5792d66 100644 --- a/packages/web/pages/stake.tsx +++ b/packages/web/pages/stake.tsx @@ -1,6 +1,7 @@ import { CoinPretty, Dec } from "@keplr-wallet/unit"; import { Staking as StakingType } from "@osmosis-labs/keplr-stores"; import { DeliverTxResponse } from "@osmosis-labs/stores"; +import { makeDelegateToValidatorSetMsg } from "@osmosis-labs/tx"; import { BondStatus } from "@osmosis-labs/types"; import { observer } from "mobx-react-lite"; import React, { useCallback, useEffect, useMemo, useState } from "react"; @@ -86,7 +87,7 @@ export const Staking: React.FC = observer(() => { const feeConfig = useFakeFeeConfig( chainStore, osmosisChainId, - account?.osmosis.msgOpts.delegateToValidatorSet.gas || 0 + makeDelegateToValidatorSetMsg.gas || 0 ); // wallet balance diff --git a/packages/web/playwright.config.ts b/packages/web/playwright.config.ts index 11a3dfbcad..a202669c5d 100644 --- a/packages/web/playwright.config.ts +++ b/packages/web/playwright.config.ts @@ -23,7 +23,7 @@ export default defineConfig({ }, ], ], - timeout: 90000, + timeout: 30000, testDir: "./e2e/tests", /* Run tests in files in parallel */ fullyParallel: false, diff --git a/packages/web/public/icons/sprite.svg b/packages/web/public/icons/sprite.svg index ca34761844..23ae925294 100644 --- a/packages/web/public/icons/sprite.svg +++ b/packages/web/public/icons/sprite.svg @@ -1043,8 +1043,6 @@ diff --git a/packages/web/public/tradingview/custom-limit.css b/packages/web/public/tradingview/custom-limit.css new file mode 100644 index 0000000000..0cd070c2df --- /dev/null +++ b/packages/web/public/tradingview/custom-limit.css @@ -0,0 +1,64 @@ +@import url("https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap"); + +:root { + --tv-color-platform-background: #0a0824; + --tv-color-pane-background: #0a0824; + --tv-color-toolbar-toggle-button-background-active: #201b43; + --tv-color-toolbar-toggle-button-background-active-hover: #201b43; + --tv-color-toolbar-button-text: #b0aadc; + --tv-color-toolbar-button-text-hover: #462adf; + --tv-color-toolbar-button-text-active: #462adf; + --tv-color-toolbar-button-text-active-hover: #462adf; + --tv-color-toolbar-button-background-active: #201b43; + --tv-color-toolbar-button-background-hover: #201b43; + --tv-color-toolbar-button-background-expanded: #201b43; + --tv-toolbar-opened-element-hover-border-radius: 6px; + + --tv-color-popup-background: #201b43; + --tv-color-popup-element-background-active: #0a0824; + --tv-color-popup-element-background-hover: #0a0824; + --tv-color-popup-element-toolbox-background-hover: #201b43; + --tv-color-popup-element-toolbox-background-active-hover: #201b43; +} + +// POPUP SETTINGS CONFIG +html.theme-dark .title-BZKENkhT { + color: #fff; +} + +html.theme-dark .dialog-aRAWUDhF { + background: #201b43; +} + +html.theme-dark .withSidebar-F0WBLDV5 .content-F0WBLDV5, +html.theme-dark .container-nGEmjtaX, +html.theme-dark .footer-PhMf7PhQ { + border-color: #0a0824; +} + +html.theme-dark .color-brand-D4RPB3ZC.variant-primary-D4RPB3ZC { + --ui-lib-button-default-color-border: #0a0824 !important; + --ui-lib-button-default-color-bg: #0a0824 !important; + --ui-lib-button-default-color-content: #fff !important; +} + +html.theme-dark .color-brand-D4RPB3ZC.variant-secondary-D4RPB3ZC { + --ui-lib-button-default-color-border: #b0aadc !important; + --ui-lib-button-default-color-content: #b0aadc !important; + --ui-lib-button-default-color-bg: transparent !important; +} + +html.theme-dark .container-WDZ0PRNh { + --ui-lib-intent-color: #b0aadc !important; + --ui-lib-control-default-slot-color: #b0aadc !important; +} + +html.theme-dark .accessible-nGEmjtaX.active-nGEmjtaX { + background-color: #282750; +} + +@media (any-hover: hover) { + html.theme-dark .accessible-nGEmjtaX:hover { + background-color: #282750; + } +} diff --git a/packages/web/sentry.client.config.ts b/packages/web/sentry.client.config.ts deleted file mode 100644 index e342e10512..0000000000 --- a/packages/web/sentry.client.config.ts +++ /dev/null @@ -1,63 +0,0 @@ -// This file configures the initialization of Sentry on the client. -// The config you add here will be used whenever a users loads a page in their browser. -// https://docs.sentry.io/platforms/javascript/guides/nextjs/ - -import * as Sentry from "@sentry/nextjs"; - -import { getValidSwapTRPCRoutesForSentry } from "~/utils/sentry-init"; - -Sentry.init({ - dsn: "https://c696452bb7ce4cc98150142ebea1c32f@o4505285755600896.ingest.us.sentry.io/4505285757698048", - - environment: - process.env.NEXT_PUBLIC_VERCEL_ENV || process.env.NODE_ENV || "development", - - // Only send 5% of error events to Sentry - sampleRate: 0.05, - - // Adjust this value in production, or use tracesSampler for greater control - tracesSampler: (samplingContext) => { - const validPaths = ["/", "/pool/[id]", "/assets/[denom]"]; - const validTrpcRoutes = getValidSwapTRPCRoutesForSentry(); - - // Log 0.5% of transactions related to swap - if ( - validPaths.includes(samplingContext.transactionContext.name) || - validTrpcRoutes.includes(samplingContext.transactionContext.name) - ) { - return 0.005; - } - - // Log 0.01% of all other transactions - return 0.0001; - }, - - replaysOnErrorSampleRate: 1.0, - - // This sets the sample rate to be 10%. You may want this to be 100% while - // in development and sample at a lower rate in production - replaysSessionSampleRate: 0.1, - - // Temporarily disable due to client-side noise coming from extensions, Cosmos Kit, etc. - enabled: - process.env.NODE_ENV !== "development" && process.env.NODE_ENV !== "test", - - // Setting this option to true will print useful information to the console while you're setting up Sentry. - debug: false, - - // You can remove this option if you're not planning to use the Sentry Session Replay feature: - integrations: [ - Sentry.replayIntegration({ - // Additional Replay configuration goes in here, for example: - maskAllText: true, - blockAllMedia: true, - }), - ], - - /** - * Propagate traces to the sidecar in order to setup distributed tracing. - */ - tracePropagationTargets: [process.env.NEXT_PUBLIC_SIDECAR_BASE_URL].filter( - (val): val is NonNullable => !!val - ), -}); diff --git a/packages/web/sentry.edge.config.ts b/packages/web/sentry.edge.config.ts deleted file mode 100644 index f1301d758a..0000000000 --- a/packages/web/sentry.edge.config.ts +++ /dev/null @@ -1,44 +0,0 @@ -// This file configures the initialization of Sentry for edge features (middleware, edge routes, and so on). -// The config you add here will be used whenever one of the edge features is loaded. -// Note that this config is unrelated to the Vercel Edge Runtime and is also required when running locally. -// https://docs.sentry.io/platforms/javascript/guides/nextjs/ - -import * as Sentry from "@sentry/nextjs"; - -import { getValidSwapTRPCRoutesForSentry } from "~/utils/sentry-init"; - -Sentry.init({ - dsn: "https://c696452bb7ce4cc98150142ebea1c32f@o4505285755600896.ingest.us.sentry.io/4505285757698048", - - environment: - process.env.NEXT_PUBLIC_VERCEL_ENV || process.env.NODE_ENV || "development", - - // Only send 5% of error events to Sentry - sampleRate: 0.05, - - // Adjust this value in production, or use tracesSampler for greater control - tracesSampler: (samplingContext) => { - const validTrpcRoutes = getValidSwapTRPCRoutesForSentry(); - - // Log 0.5% of transactions related to swap - if (validTrpcRoutes.includes(samplingContext.transactionContext.name)) { - return 0.005; - } - - // Log 0.01% of all other transactions - return 0.0001; - }, - - enabled: - process.env.NODE_ENV !== "development" && process.env.NODE_ENV !== "test", - - // Setting this option to true will print useful information to the console while you're setting up Sentry. - debug: false, - - /** - * Propagate traces to the sidecar in order to setup distributed tracing. - */ - tracePropagationTargets: [process.env.NEXT_PUBLIC_SIDECAR_BASE_URL].filter( - (val): val is NonNullable => !!val - ), -}); diff --git a/packages/web/sentry.server.config.ts b/packages/web/sentry.server.config.ts deleted file mode 100644 index 79dd47a623..0000000000 --- a/packages/web/sentry.server.config.ts +++ /dev/null @@ -1,31 +0,0 @@ -// This file configures the initialization of Sentry on the server. -// The config you add here will be used whenever the server handles a request. -// https://docs.sentry.io/platforms/javascript/guides/nextjs/ - -import * as Sentry from "@sentry/nextjs"; - -Sentry.init({ - dsn: "https://c696452bb7ce4cc98150142ebea1c32f@o4505285755600896.ingest.us.sentry.io/4505285757698048", - - environment: - process.env.NEXT_PUBLIC_VERCEL_ENV || process.env.NODE_ENV || "development", - - // Adjust this value in production, or use tracesSampler for greater control - tracesSampleRate: 0.01, - - /** - * Disable server-side (Node.js serverless) tracing since we are only interested - * in tracing swap transactions. This sentry.server.ts only applies to bridge - * transfer transactions. - */ - enabled: false, - // enabled: - // process.env.NODE_ENV !== "development" && process.env.NODE_ENV !== "test", - - // Setting this option to true will print useful information to the console while you're setting up Sentry. - debug: false, - - tracePropagationTargets: [process.env.NEXT_PUBLIC_SIDECAR_BASE_URL].filter( - (val): val is NonNullable => !!val - ), -}); diff --git a/packages/web/server/api/edge-router.ts b/packages/web/server/api/edge-router.ts index e25eb2ac24..d064c68daa 100644 --- a/packages/web/server/api/edge-router.ts +++ b/packages/web/server/api/edge-router.ts @@ -4,7 +4,6 @@ import { createTRPCRouter, earnRouter, orderbookRouter, - paramsRouter, poolsRouter, stakingRouter, transactionsRouter, @@ -19,5 +18,4 @@ export const edgeRouter = createTRPCRouter({ transactions: transactionsRouter, orderbooks: orderbookRouter, chains: chainsRouter, - params: paramsRouter, }); diff --git a/packages/web/server/api/local-router.ts b/packages/web/server/api/local-router.ts index ab067d113b..34ced5f62d 100644 --- a/packages/web/server/api/local-router.ts +++ b/packages/web/server/api/local-router.ts @@ -4,6 +4,7 @@ import { concentratedLiquidityRouter, createTRPCRouter, oneClickTradingRouter, + paramsRouter, portfolioRouter, swapRouter, } from "@osmosis-labs/trpc"; @@ -21,4 +22,5 @@ export const localRouter = createTRPCRouter({ cms: cmsRouter, bridgeTransfer: localBridgeTransferRouter, portfolio: portfolioRouter, + params: paramsRouter, }); diff --git a/packages/web/server/api/routers/bridge-transfer.ts b/packages/web/server/api/routers/bridge-transfer.ts index 2ade64c0f5..470ed2ba49 100644 --- a/packages/web/server/api/routers/bridge-transfer.ts +++ b/packages/web/server/api/routers/bridge-transfer.ts @@ -145,7 +145,6 @@ export const bridgeTransferRouter = createTRPCRouter({ ...ctx, asset: { coinMinimalDenom: input.toAsset.address, - sourceDenom: input.toAsset.address, chainId: input.toChain.chainId, address: input.toAsset.address, coinGeckoId: input.toAsset.coinGeckoId, @@ -169,7 +168,6 @@ export const bridgeTransferRouter = createTRPCRouter({ ...ctx, asset: { coinMinimalDenom: input.fromAsset.address, - sourceDenom: input.fromAsset.address, chainId: input.fromChain.chainId, address: input.fromAsset.address, coinGeckoId: input.fromAsset.coinGeckoId, @@ -180,7 +178,6 @@ export const bridgeTransferRouter = createTRPCRouter({ ...ctx, asset: { coinMinimalDenom: feeCoin.address, - sourceDenom: feeCoin.address, chainId: quote.transferFee.chainId, address: quote.transferFee.address, coinGeckoId: quote.transferFee.coinGeckoId, @@ -194,7 +191,6 @@ export const bridgeTransferRouter = createTRPCRouter({ { bridge: input.bridge, coinMinimalDenom: feeCoin.address, - sourceDenom: feeCoin.address, chainId: quote.transferFee.chainId, address: quote.transferFee.address, coinGeckoId: quote.transferFee.coinGeckoId, @@ -208,7 +204,6 @@ export const bridgeTransferRouter = createTRPCRouter({ ...ctx, asset: { coinMinimalDenom: quote.estimatedGasFee.address, - sourceDenom: quote.estimatedGasFee.address, chainId: quote.fromChain.chainId, address: quote.estimatedGasFee.address, coinGeckoId: quote.estimatedGasFee.coinGeckoId, diff --git a/packages/web/server/api/trpc.ts b/packages/web/server/api/trpc.ts index 4b054461c1..5b175ede1a 100644 --- a/packages/web/server/api/trpc.ts +++ b/packages/web/server/api/trpc.ts @@ -4,12 +4,14 @@ import { type CreateNextContextOptions } from "@trpc/server/adapters/next"; import { AssetLists } from "~/config/generated/asset-lists"; import { ChainList } from "~/config/generated/chain-list"; +import { getOpentelemetryServiceName } from "~/utils/service-name"; /** tRPC context for Next.js endpoints. */ export const createNextTrpcContext = (_opts: CreateNextContextOptions) => { return createInnerTRPCContext({ assetLists: AssetLists, chainList: ChainList, + opentelemetryServiceName: getOpentelemetryServiceName(), }); }; @@ -18,5 +20,6 @@ export const createEdgeTrpcContext = (_opts: FetchCreateContextFnOptions) => { return createInnerTRPCContext({ assetLists: AssetLists, chainList: ChainList, + opentelemetryServiceName: getOpentelemetryServiceName(), }); }; diff --git a/packages/web/stores/root.ts b/packages/web/stores/root.ts index 93f1dce524..cd58c88026 100644 --- a/packages/web/stores/root.ts +++ b/packages/web/stores/root.ts @@ -213,16 +213,6 @@ export class RootStore { }), CosmosAccount.use({ queriesStore: this.queriesStore, - msgOptsCreator(chainId) { - if (chainId.startsWith("osmosis")) { - return { ibcTransfer: { gas: 300000 } }; - } - if (chainId.startsWith("evmos_")) { - return { ibcTransfer: { gas: 250000 } }; - } else { - return { ibcTransfer: { gas: 210000 } }; - } - }, }), CosmwasmAccount.use({ queriesStore: this.queriesStore }) ); diff --git a/packages/web/utils/__tests__/sentry-init.spec.ts b/packages/web/utils/__tests__/sentry-init.spec.ts deleted file mode 100644 index 24f0fba0b6..0000000000 --- a/packages/web/utils/__tests__/sentry-init.spec.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { getValidSwapTRPCRoutesForSentry } from "../sentry-init"; - -describe("getValidTRPCSentryRoutes", () => { - it("should correctly transform TRPC route keys to Sentry route format", () => { - const result = getValidSwapTRPCRoutesForSentry(); - expect(result).toMatchInlineSnapshot(` - [ - "trpc/local.quoteRouter.routeTokenOutGivenIn", - "trpc/assets.getAssetPrice", - "trpc/assets.getUserAsset", - "trpc/assets.getUserAssets", - "trpc/assets.getAssetHistoricalPrice", - "trpc/assets.getAssetWithPrice", - ] - `); - }); -}); diff --git a/packages/web/utils/error.ts b/packages/web/utils/error.ts index d192c8e713..687d21d466 100644 --- a/packages/web/utils/error.ts +++ b/packages/web/utils/error.ts @@ -1,8 +1,12 @@ -import * as Sentry from "@sentry/nextjs"; +import { context, trace } from "@opentelemetry/api"; export function captureError(e: any) { + const activeSpan = trace.getSpan(context.active()); if (e instanceof Error) { - Sentry.captureException(e); + if (activeSpan) { + // Reuse the existing active span + activeSpan.recordException(e); + } if (process.env.NODE_ENV === "development") console.warn("Captured:", e); } else if (process.env.NODE_ENV === "development") { console.warn("Did not capture non-Error:", e); diff --git a/packages/web/utils/helpers.ts b/packages/web/utils/helpers.ts index 2437d8e650..6b4319c0e8 100644 --- a/packages/web/utils/helpers.ts +++ b/packages/web/utils/helpers.ts @@ -4,6 +4,7 @@ import { createServerSideHelpers } from "@trpc/react-query/server"; import { AssetLists } from "~/config/generated/asset-lists"; import { ChainList } from "~/config/generated/chain-list"; import { appRouter } from "~/server/api/root-router"; +import { getOpentelemetryServiceName } from "~/utils/service-name"; /** * tRPC helpers for SSR queries, useful for data prefetching @@ -13,6 +14,7 @@ export const trpcHelpers = createServerSideHelpers({ ctx: { assetLists: AssetLists, chainList: ChainList, + opentelemetryServiceName: getOpentelemetryServiceName(), }, transformer: superjson, }); diff --git a/packages/web/utils/sentry-init.ts b/packages/web/utils/sentry-init.ts deleted file mode 100644 index cd37ca3c53..0000000000 --- a/packages/web/utils/sentry-init.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { RouterKeys } from "~/utils/trpc"; - -/** For now, filter non-swap related routes */ -export function getValidSwapTRPCRoutesForSentry() { - const validTrpcRoutes: RouterKeys[] = [ - "local.quoteRouter.routeTokenOutGivenIn", - "edge.assets.getAssetPrice", - "edge.assets.getUserAsset", - "edge.assets.getUserAssets", - "edge.assets.getAssetHistoricalPrice", - "edge.assets.getAssetWithPrice", - ]; - - return validTrpcRoutes.map( - (route) => - "trpc/" + - (route.startsWith("edge") - ? route.split(".").slice(1).join(".") // Remove the "edge" prefix from the route - : route) - ); -} diff --git a/packages/web/utils/service-name.ts b/packages/web/utils/service-name.ts new file mode 100644 index 0000000000..c5bee480d7 --- /dev/null +++ b/packages/web/utils/service-name.ts @@ -0,0 +1,16 @@ +export function getOpentelemetryServiceName(): string { + const branchUrl = process.env.NEXT_PUBLIC_VERCEL_BRANCH_URL; + const urlParts = branchUrl?.split("-"); + + // Find the index of 'git' and check if the next part is 'stage' + const gitIndex = urlParts?.indexOf("git"); + const isStage = + gitIndex !== undefined && + gitIndex !== -1 && + urlParts?.[gitIndex + 1] === "stage"; + + return isStage + ? "osmosis-frontend-stage" // If it does, return "stage" + : `osmosis-frontend-${process.env.NEXT_PUBLIC_VERCEL_ENV}` ?? // Otherwise, return the Vercel environment (production, preview, or development) + "fallback-osmosis-frontend-service-name"; +} diff --git a/packages/web/utils/trpc.ts b/packages/web/utils/trpc.ts index d78b95b903..fad931a1bd 100644 --- a/packages/web/utils/trpc.ts +++ b/packages/web/utils/trpc.ts @@ -16,6 +16,7 @@ import type { import { AssetLists } from "~/config/generated/asset-lists"; import { ChainList } from "~/config/generated/chain-list"; import { type AppRouter, appRouter } from "~/server/api/root-router"; +import { getOpentelemetryServiceName } from "~/utils/service-name"; import { constructEdgeRouterKey, constructEdgeUrlPathname, @@ -122,6 +123,7 @@ export const api = createTRPCNext({ router: appRouter, assetLists: AssetLists, chainList: ChainList, + opentelemetryServiceName: getOpentelemetryServiceName(), })(runtime), /** diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000000..082f38503f --- /dev/null +++ b/shell.nix @@ -0,0 +1,15 @@ +with import {}; +mkShell { + packages = [ + yarn + nodejs + ]; + + # Enable nix-ld, read here more: + # https://github.com/Mic92/nix-ld?tab=readme-ov-file#nix-ld + NIX_LD_LIBRARY_PATH = lib.makeLibraryPath [ + stdenv.cc.cc + # ... + ]; + NIX_LD = lib.fileContents "${stdenv.cc}/nix-support/dynamic-linker"; +} diff --git a/turbo.json b/turbo.json index accce594fa..06489f72a0 100644 --- a/turbo.json +++ b/turbo.json @@ -1,6 +1,7 @@ { "$schema": "https://turbo.build/schema.json", - "pipeline": { + "ui": "tui", + "tasks": { "build": { "outputs": [ "build/**", @@ -16,11 +17,55 @@ }, "lint": {}, "lint:fix": {}, - "dev": { "persistent": true }, - "start": { "dependsOn": ["build"] }, - "clean": { "cache": false }, - "test": { "dependsOn": ["^build"] }, - "test:e2e": { "dependsOn": ["^build"] }, + "dev": { + "persistent": true + }, + "start": { + "dependsOn": ["build"] + }, + "clean": { + "cache": false + }, + "test": { + "dependsOn": ["^build"] + }, + "test:e2e": { + "dependsOn": ["^build"] + }, "analyze": {} - } + }, + "globalEnv": [ + "NODE_ENV", + "NEXTAUTH_URL", + "VERCEL_URL", + "PORT", + "GITHUB_URL", + "CMS_REPOSITORY_PATH", + "NEXT_PUBLIC_TFM_API_BASE_URL", + "NEXT_PUBLIC_SQUID_INTEGRATOR_ID", + "NEXT_PUBLIC_SIDECAR_BASE_URL", + "TWITTER_API_URL", + "BLOCKAID_BASE_URL", + "NEXT_PUBLIC_SPEND_LIMIT_CONTRACT_ADDRESS", + "NEXT_PUBLIC_LAUNCH_DARKLY_CLIENT_SIDE_ID", + "NEXT_PUBLIC_IS_TESTNET", + "NEXT_PUBLIC_OSMOSIS_RPC_OVERWRITE", + "NEXT_PUBLIC_OSMOSIS_REST_OVERWRITE", + "NEXT_PUBLIC_OSMOSIS_CHAIN_ID_OVERWRITE", + "NEXT_PUBLIC_OSMOSIS_EXPLORER_URL_OVERWRITE", + "NEXT_PUBLIC_OSMOSIS_CHAIN_NAME_OVERWRITE", + "NEXT_PUBLIC_WALLETCONNECT_PROJECT_KEY", + "NEXT_PUBLIC_WALLETCONNECT_RELAY_URL", + "NEXT_PUBLIC_TIMESERIES_DATA_URL", + "NEXT_PUBLIC_INDEXER_DATA_URL", + "TWITTER_API_ACCESS_TOKEN", + "TWITTER_KV_STORE_REST_API_URL", + "TWITTER_KV_STORE_REST_API_TOKEN", + "COINGECKO_API_KEY", + "ASSET_LIST_COMMIT_HASH", + "GITHUB_API_TOKEN", + "NEXT_PUBLIC_FE_CONTENT_COMMIT_HASH", + "NEXT_PUBLIC_EXCLUDED_EXTERNAL_BOOSTS_POOL_IDS", + "BLOCKAID_API_KEY" + ] } diff --git a/yarn.lock b/yarn.lock index 90e9c82401..a711292c66 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4287,7 +4287,7 @@ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== -"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.13", "@jridgewell/sourcemap-codec@^1.4.14": +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": version "1.4.15" resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== @@ -5717,6 +5717,18 @@ resolved "https://registry.yarnpkg.com/@open-draft/until/-/until-1.0.3.tgz#db9cc719191a62e7d9200f6e7bab21c5b848adca" integrity sha512-Aq58f5HiWdyDlFffbbSjAlv596h/cOnt2DO1w3DOC7OJ5EHs0hd/nycJfiu9RJbT6Yk6F1knnRRXNSpxoIVZ9Q== +"@opentelemetry/api-logs@^0.52.1": + version "0.52.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/api-logs/-/api-logs-0.52.1.tgz#52906375da4d64c206b0c4cb8ffa209214654ecc" + integrity sha512-qnSqB2DQ9TPP96dl8cDubDvrUyWc0/sK81xHTK8eSUspzDM3bsewX903qclQFvVhgStjRWdC5bLb3kQqMkfV5A== + dependencies: + "@opentelemetry/api" "^1.0.0" + +"@opentelemetry/api@^1.0.0", "@opentelemetry/api@^1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.9.0.tgz#d03eba68273dc0f7509e2a3d5cba21eae10379fe" + integrity sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg== + "@parcel/watcher-android-arm64@2.4.1": version "2.4.1" resolved "https://registry.yarnpkg.com/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.4.1.tgz#c2c19a3c442313ff007d2d7a9c2c1dd3e1c9ca84" @@ -6340,27 +6352,6 @@ resolved "https://registry.yarnpkg.com/@react-types/shared/-/shared-3.23.1.tgz#2f23c81d819d0ef376df3cd4c944be4d6bce84c3" integrity sha512-5d+3HbFDxGZjhbMBeFHRQhexMFt4pUce3okyRtUVKbbedQFUrtXSBg9VszgF2RTeQDKDkMCIQDtz5ccP/Lk1gw== -"@rollup/plugin-commonjs@24.0.0": - version "24.0.0" - resolved "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-24.0.0.tgz" - integrity sha512-0w0wyykzdyRRPHOb0cQt14mIBLujfAv6GgP6g8nvg/iBxEm112t3YPPq+Buqe2+imvElTka+bjNlJ/gB56TD8g== - dependencies: - "@rollup/pluginutils" "^5.0.1" - commondir "^1.0.1" - estree-walker "^2.0.2" - glob "^8.0.3" - is-reference "1.2.1" - magic-string "^0.27.0" - -"@rollup/pluginutils@^5.0.1": - version "5.0.2" - resolved "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.2.tgz" - integrity sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA== - dependencies: - "@types/estree" "^1.0.0" - estree-walker "^2.0.2" - picomatch "^2.3.1" - "@rushstack/eslint-patch@^1.3.3": version "1.5.1" resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.5.1.tgz#5f1b518ec5fa54437c0b7c4a821546c64fed6922" @@ -6443,169 +6434,6 @@ "@noble/hashes" "~1.4.0" "@scure/base" "~1.1.6" -"@sentry-internal/feedback@7.109.0": - version "7.109.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-7.109.0.tgz#4657d7f36a1de3be466f42735d295e212b7eca11" - integrity sha512-EL7N++poxvJP9rYvh6vSu24tsKkOveNCcCj4IM7+irWPjsuD2GLYYlhp/A/Mtt9l7iqO4plvtiQU5HGk7smcTQ== - dependencies: - "@sentry/core" "7.109.0" - "@sentry/types" "7.109.0" - "@sentry/utils" "7.109.0" - -"@sentry-internal/replay-canvas@7.109.0": - version "7.109.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-7.109.0.tgz#9a00857994a9487428296feed4a9ddf2d62bab84" - integrity sha512-Lh/K60kmloR6lkPUcQP0iamw7B/MdEUEx/ImAx4tUSMrLj+IoUEcq/ECgnnVyQkJq59+8nPEKrVLt7x6PUPEjw== - dependencies: - "@sentry/core" "7.109.0" - "@sentry/replay" "7.109.0" - "@sentry/types" "7.109.0" - "@sentry/utils" "7.109.0" - -"@sentry-internal/tracing@7.109.0": - version "7.109.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/tracing/-/tracing-7.109.0.tgz#3effaa132c41a65378fa98146aea61228d528953" - integrity sha512-PzK/joC5tCuh2R/PRh+7dp+uuZl7pTsBIjPhVZHMTtb9+ls65WkdZJ1/uKXPouyz8NOo9Xok7aEvEo9seongyw== - dependencies: - "@sentry/core" "7.109.0" - "@sentry/types" "7.109.0" - "@sentry/utils" "7.109.0" - -"@sentry/browser@7.109.0": - version "7.109.0" - resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-7.109.0.tgz#13b2623f43047f292cf7d6070128a7501e008693" - integrity sha512-yx+OFG+Ab9qUDDgV9ZDv8M9O9Mqr0fjKta/LMlWALYLjzkMvxsPlRPFj7oMBlHqOTVLDeg7lFYmsA8wyWQ8Z8g== - dependencies: - "@sentry-internal/feedback" "7.109.0" - "@sentry-internal/replay-canvas" "7.109.0" - "@sentry-internal/tracing" "7.109.0" - "@sentry/core" "7.109.0" - "@sentry/replay" "7.109.0" - "@sentry/types" "7.109.0" - "@sentry/utils" "7.109.0" - -"@sentry/cli@^1.77.1": - version "1.77.3" - resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-1.77.3.tgz#c40b4d09b0878d6565d42a915855add99db4fec3" - integrity sha512-c3eDqcDRmy4TFz2bFU5Y6QatlpoBPPa8cxBooaS4aMQpnIdLYPF1xhyyiW0LQlDUNc3rRjNF7oN5qKoaRoMTQQ== - dependencies: - https-proxy-agent "^5.0.0" - mkdirp "^0.5.5" - node-fetch "^2.6.7" - progress "^2.0.3" - proxy-from-env "^1.1.0" - which "^2.0.2" - -"@sentry/core@7.109.0", "@sentry/core@^7.109.0": - version "7.109.0" - resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.109.0.tgz#7a02f4af4a676950f6555f552a2a232d4458fcd5" - integrity sha512-xwD4U0IlvvlE/x/g/W1I8b4Cfb16SsCMmiEuBf6XxvAa3OfWBxKoqLifb3GyrbxMC4LbIIZCN/SvLlnGJPgszA== - dependencies: - "@sentry/types" "7.109.0" - "@sentry/utils" "7.109.0" - -"@sentry/integrations@7.109.0": - version "7.109.0" - resolved "https://registry.yarnpkg.com/@sentry/integrations/-/integrations-7.109.0.tgz#36f8233f55e6b0d4bdb7e7466714575a1d65f3cf" - integrity sha512-8GwPFlUu4rB1Dx3e9tc3gCMmzC5Bd5lzThhg3tMBmzCCEp7UeA4u7eUuKJ5g49vjdznPDRG2p3PcRsApFZNPSg== - dependencies: - "@sentry/core" "7.109.0" - "@sentry/types" "7.109.0" - "@sentry/utils" "7.109.0" - localforage "^1.8.1" - -"@sentry/nextjs@^7.109.0": - version "7.109.0" - resolved "https://registry.yarnpkg.com/@sentry/nextjs/-/nextjs-7.109.0.tgz#f29c62e5a7581ee20f9add73b1de7c2cd3667b3b" - integrity sha512-AT0jhMDj7N57z8+XfgEyTJBogpU64z4mQpfOsSF5uuequzo3IlVVoJcu88jdqUkaVFxBJp3aF2T4nz65OHLoeA== - dependencies: - "@rollup/plugin-commonjs" "24.0.0" - "@sentry/core" "7.109.0" - "@sentry/integrations" "7.109.0" - "@sentry/node" "7.109.0" - "@sentry/react" "7.109.0" - "@sentry/types" "7.109.0" - "@sentry/utils" "7.109.0" - "@sentry/vercel-edge" "7.109.0" - "@sentry/webpack-plugin" "1.21.0" - chalk "3.0.0" - resolve "1.22.8" - rollup "2.78.0" - stacktrace-parser "^0.1.10" - -"@sentry/node@7.109.0": - version "7.109.0" - resolved "https://registry.yarnpkg.com/@sentry/node/-/node-7.109.0.tgz#dbf152212e42a9b1648ff758ec5bffcb6bb0fa49" - integrity sha512-tqMNAES4X/iBl1eZRCmc29p//0id01FBLEiesNo5nk6ECl6/SaGMFAEwu1gsn90h/Bjgr04slwFOS4cR45V2PQ== - dependencies: - "@sentry-internal/tracing" "7.109.0" - "@sentry/core" "7.109.0" - "@sentry/types" "7.109.0" - "@sentry/utils" "7.109.0" - -"@sentry/react@7.109.0": - version "7.109.0" - resolved "https://registry.yarnpkg.com/@sentry/react/-/react-7.109.0.tgz#ae8a2950d2022e83f1bccd8b994f0096f3dd1682" - integrity sha512-KqXoDh6LVhNO+FLdM5LiTGpuorFvjoBPQ4nPGIVbjeMY/KZIau3kFxR142EvCApxmD69yvS5EhMnEqlNdaQPGw== - dependencies: - "@sentry/browser" "7.109.0" - "@sentry/core" "7.109.0" - "@sentry/types" "7.109.0" - "@sentry/utils" "7.109.0" - hoist-non-react-statics "^3.3.2" - -"@sentry/replay@7.109.0": - version "7.109.0" - resolved "https://registry.yarnpkg.com/@sentry/replay/-/replay-7.109.0.tgz#f50fb0140c81fce660c44cc93c35988898b8348b" - integrity sha512-hCDjbTNO7ErW/XsaBXlyHFsUhneyBUdTec1Swf98TFEfVqNsTs6q338aUcaR8dGRLbLrJ9YU9D1qKq++v5h2CA== - dependencies: - "@sentry-internal/tracing" "7.109.0" - "@sentry/core" "7.109.0" - "@sentry/types" "7.109.0" - "@sentry/utils" "7.109.0" - -"@sentry/types@7.109.0": - version "7.109.0" - resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.109.0.tgz#d8778358114ed05be734661cc9e1e261f4494947" - integrity sha512-egCBnDv3YpVFoNzRLdP0soVrxVLCQ+rovREKJ1sw3rA2/MFH9WJ+DZZexsX89yeAFzy1IFsCp7/dEqudusml6g== - -"@sentry/types@7.116.0": - version "7.116.0" - resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.116.0.tgz#0be3434e7e53c86db4993e668af1c3a65bfb7519" - integrity sha512-QCCvG5QuQrwgKzV11lolNQPP2k67Q6HHD9vllZ/C4dkxkjoIym8Gy+1OgAN3wjsR0f/kG9o5iZyglgNpUVRapQ== - -"@sentry/utils@7.109.0": - version "7.109.0" - resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.109.0.tgz#7078e1400197abc1b0c436679bef980639500a86" - integrity sha512-3RjxMOLMBwZ5VSiH84+o/3NY2An4Zldjz0EbfEQNRY9yffRiCPJSQiCJID8EoylCFOh/PAhPimBhqbtWJxX6iw== - dependencies: - "@sentry/types" "7.109.0" - -"@sentry/utils@^7.109.0": - version "7.116.0" - resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.116.0.tgz#f32463ab10f76f464274233a9df202e5357d17ff" - integrity sha512-Vn9fcvwTq91wJvCd7WTMWozimqMi+dEZ3ie3EICELC2diONcN16ADFdzn65CQQbYwmUzRjN9EjDN2k41pKZWhQ== - dependencies: - "@sentry/types" "7.116.0" - -"@sentry/vercel-edge@7.109.0": - version "7.109.0" - resolved "https://registry.yarnpkg.com/@sentry/vercel-edge/-/vercel-edge-7.109.0.tgz#4e3e1fd5b05be3a59ddc6d6b40407dd929f75b3d" - integrity sha512-0I+pLZPkD0vSlSLwBx9XAs17WXHimGhHIMki/YH5Y007i1iykkMItoDx//Y3PPjZiJu+deO7l4wKO2J1lJW6jg== - dependencies: - "@sentry-internal/tracing" "7.109.0" - "@sentry/core" "7.109.0" - "@sentry/types" "7.109.0" - "@sentry/utils" "7.109.0" - -"@sentry/webpack-plugin@1.21.0": - version "1.21.0" - resolved "https://registry.yarnpkg.com/@sentry/webpack-plugin/-/webpack-plugin-1.21.0.tgz#bbe7cb293751f80246a4a56f9a7dd6de00f14b58" - integrity sha512-x0PYIMWcsTauqxgl7vWUY6sANl+XGKtx7DCVnnY7aOIIlIna0jChTAPANTfA2QrK+VK+4I/4JxatCEZBnXh3Og== - dependencies: - "@sentry/cli" "^1.77.1" - webpack-sources "^2.0.0 || ^3.0.0" - "@sideway/address@^4.1.3": version "4.1.3" resolved "https://registry.npmjs.org/@sideway/address/-/address-4.1.3.tgz" @@ -7267,11 +7095,6 @@ dependencies: "@types/trusted-types" "*" -"@types/estree@*", "@types/estree@^1.0.0": - version "1.0.1" - resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz" - integrity sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA== - "@types/glob@^7.1.3": version "7.2.0" resolved "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz" @@ -7766,6 +7589,11 @@ dependencies: crypto-js "^4.2.0" +"@vercel/otel@^1.9.1": + version "1.9.1" + resolved "https://registry.yarnpkg.com/@vercel/otel/-/otel-1.9.1.tgz#5ec83247f307738c95a0c7ddd3993fe4a0aac719" + integrity sha512-ZSTqgvd+w/lcB1nxEW8EHSBBqd4ZdeJ1t5op1CFo/nKFdG/EshwMon0qKc2ZxVEZXOZJI/x+LKf8Y5/Y/VHqEA== + "@visx/annotation@2.18.0": version "2.18.0" resolved "https://registry.npmjs.org/@visx/annotation/-/annotation-2.18.0.tgz" @@ -9958,25 +9786,10 @@ camelcase@^6.2.0: resolved "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -caniuse-lite@^1.0.30001286: - version "1.0.30001562" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001562.tgz#9d16c5fd7e9c592c4cd5e304bc0f75b0008b2759" - integrity sha512-kfte3Hym//51EdX4239i+Rmp20EsLIYGdPkERegTgU19hQWCRhsRFGKHTliUlsry53tv17K7n077Kqa0WJU4ng== - -caniuse-lite@^1.0.30001297, caniuse-lite@^1.0.30001449: - version "1.0.30001478" - resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001478.tgz" - integrity sha512-gMhDyXGItTHipJj2ApIvR+iVB5hd0KP3svMWWXDvZOmjzJJassGLMfxRkQCSYgGd2gtdL/ReeiyvMSFD1Ss6Mw== - -caniuse-lite@^1.0.30001579: - version "1.0.30001596" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001596.tgz#da06b79c3d9c3d9958eb307aa832ac68ead79bee" - integrity sha512-zpkZ+kEr6We7w63ORkoJ2pOfBwBkY/bJrG/UZ90qNb45Isblu8wzDgevEOrRL1r9dWayHjYiiyCMEXPn4DweGQ== - -caniuse-lite@^1.0.30001587: - version "1.0.30001617" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001617.tgz#809bc25f3f5027ceb33142a7d6c40759d7a901eb" - integrity sha512-mLyjzNI9I+Pix8zwcrpxEbGlfqOkF9kM3ptzmKNw5tizSyYwMe+nGLTqMK9cO+0E+Bh6TsBxNAaHWEM8xwSsmA== +caniuse-lite@^1.0.30001286, caniuse-lite@^1.0.30001297, caniuse-lite@^1.0.30001449, caniuse-lite@^1.0.30001579, caniuse-lite@^1.0.30001587: + version "1.0.30001650" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001650.tgz" + integrity sha512-fgEc7hP/LB7iicdXHUI9VsBsMZmUmlVJeQP2qqQW+3lkqVhbmjEU8zp+h5stWeilX+G7uXuIUIIlWlDw9jdt8g== case@1.6.3: version "1.6.3" @@ -10011,14 +9824,6 @@ chain-registry@^1.20.0: "@babel/runtime" "^7.21.0" "@chain-registry/types" "^0.17.0" -chalk@3.0.0, chalk@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz" - integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - chalk@4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" @@ -10047,6 +9852,14 @@ chalk@^2.0.0, chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" +chalk@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz" + integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: version "4.1.2" resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" @@ -10474,11 +10287,6 @@ commander@^8.3.0: resolved "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz" integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== -commondir@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz" - integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg== - compare-func@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz" @@ -12241,11 +12049,6 @@ estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0: resolved "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz" integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== -estree-walker@^2.0.2: - version "2.0.2" - resolved "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz" - integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== - esutils@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" @@ -13270,7 +13073,7 @@ glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^8.0.1, glob@^8.0.3: +glob@^8.0.1: version "8.1.0" resolved "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz" integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== @@ -13785,11 +13588,6 @@ ignore@^5.2.0: resolved "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz" integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== -immediate@~3.0.5: - version "3.0.6" - resolved "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz" - integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ== - immutable@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/immutable/-/immutable-4.0.0.tgz" @@ -14303,13 +14101,6 @@ is-promise@^2.2.2: resolved "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz" integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ== -is-reference@1.2.1: - version "1.2.1" - resolved "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz" - integrity sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ== - dependencies: - "@types/estree" "*" - is-regex@^1.1.4: version "1.1.4" resolved "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz" @@ -15825,13 +15616,6 @@ libsodium@^0.7.0: resolved "https://registry.npmjs.org/libsodium/-/libsodium-0.7.9.tgz" integrity sha512-gfeADtR4D/CM0oRUviKBViMGXZDgnFdMKMzHsvBdqLBHd9ySi6EtYnmuhHVDDYgYpAO8eU8hEY+F8vIUAPh08A== -lie@3.1.1: - version "3.1.1" - resolved "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz" - integrity sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw== - dependencies: - immediate "~3.0.5" - lightweight-charts@^4.1.4: version "4.1.4" resolved "https://registry.yarnpkg.com/lightweight-charts/-/lightweight-charts-4.1.4.tgz#8a41df9183a1af43dde6c12e2475a02fc64661dd" @@ -15961,13 +15745,6 @@ load-json-file@^4.0.0: pify "^3.0.0" strip-bom "^3.0.0" -localforage@^1.8.1: - version "1.10.0" - resolved "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz" - integrity sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg== - dependencies: - lie "3.1.1" - locate-path@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz" @@ -16201,13 +15978,6 @@ magic-string@^0.16.0: dependencies: vlq "^0.2.1" -magic-string@^0.27.0: - version "0.27.0" - resolved "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz" - integrity sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA== - dependencies: - "@jridgewell/sourcemap-codec" "^1.4.13" - make-dir@4.0.0, make-dir@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" @@ -16941,13 +16711,6 @@ mkdirp@3.0.0: resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.0.tgz" integrity sha512-7+JDnNsyCvZXoUJdkMR0oUE2AmAdsNXGTmRbiOjYIwQ6q+bL6NwrozGQdPcmYaNcrhH37F50HHBUzoaBV6FITQ== -mkdirp@^0.5.5: - version "0.5.5" - resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz" - integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== - dependencies: - minimist "^1.2.5" - mlly@^1.2.0, mlly@^1.6.1: version "1.6.1" resolved "https://registry.yarnpkg.com/mlly/-/mlly-1.6.1.tgz#0983067dc3366d6314fc5e12712884e6978d028f" @@ -18567,11 +18330,6 @@ process@^0.11.10: resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== -progress@^2.0.3: - version "2.0.3" - resolved "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz" - integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== - promise-inflight@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz" @@ -19573,13 +19331,6 @@ rollup-plugin-visualizer@^5.9.2: source-map "^0.7.4" yargs "^17.5.1" -rollup@2.78.0: - version "2.78.0" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.78.0.tgz#00995deae70c0f712ea79ad904d5f6b033209d9e" - integrity sha512-4+YfbQC9QEVvKTanHhIAFVUFSRsezvQF8vFOJwtGfb9Bb+r014S+qryr9PSmw8x6sMnPkmFBGAvIFVQxvJxjtg== - optionalDependencies: - fsevents "~2.3.2" - rtl-css-js@^1.14.0: version "1.16.1" resolved "https://registry.npmjs.org/rtl-css-js/-/rtl-css-js-1.16.1.tgz" @@ -20329,13 +20080,6 @@ stacktrace-js@^2.0.2: stack-generator "^2.0.5" stacktrace-gps "^3.0.4" -stacktrace-parser@^0.1.10: - version "0.1.10" - resolved "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.10.tgz" - integrity sha512-KJP1OCML99+8fhOHxwwzyWrlUuVX5GQ0ZpJTd1DFXhdkrvg1szxfHhawXUZ3g9TkXORQd4/WG68jMlQZ2p8wlg== - dependencies: - type-fest "^0.7.1" - "standard-error@>= 1.1.0 < 2": version "1.1.0" resolved "https://registry.yarnpkg.com/standard-error/-/standard-error-1.1.0.tgz#23e5168fa1c0820189e5812701a79058510d0d34" @@ -21191,47 +20935,47 @@ tunnel@0.0.6: resolved "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz" integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg== -turbo-darwin-64@1.13.3: - version "1.13.3" - resolved "https://registry.yarnpkg.com/turbo-darwin-64/-/turbo-darwin-64-1.13.3.tgz#01d750e0f9ce4fced510357f1a9f7fe6312756ba" - integrity sha512-glup8Qx1qEFB5jerAnXbS8WrL92OKyMmg5Hnd4PleLljAeYmx+cmmnsmLT7tpaVZIN58EAAwu8wHC6kIIqhbWA== - -turbo-darwin-arm64@1.13.3: - version "1.13.3" - resolved "https://registry.yarnpkg.com/turbo-darwin-arm64/-/turbo-darwin-arm64-1.13.3.tgz#a99950c8aff83d14070eeca987b0ee53dc881b2d" - integrity sha512-/np2xD+f/+9qY8BVtuOQXRq5f9LehCFxamiQnwdqWm5iZmdjygC5T3uVSYuagVFsZKMvX3ycySwh8dylGTl6lg== - -turbo-linux-64@1.13.3: - version "1.13.3" - resolved "https://registry.yarnpkg.com/turbo-linux-64/-/turbo-linux-64-1.13.3.tgz#a8ea12c3d79f5bbc78b2ef37f47019edb8928219" - integrity sha512-G+HGrau54iAnbXLfl+N/PynqpDwi/uDzb6iM9hXEDG+yJnSJxaHMShhOkXYJPk9offm9prH33Khx2scXrYVW1g== - -turbo-linux-arm64@1.13.3: - version "1.13.3" - resolved "https://registry.yarnpkg.com/turbo-linux-arm64/-/turbo-linux-arm64-1.13.3.tgz#e8691ebfab0e31e276020deee83b3866b4eb966f" - integrity sha512-qWwEl5VR02NqRyl68/3pwp3c/olZuSp+vwlwrunuoNTm6JXGLG5pTeme4zoHNnk0qn4cCX7DFrOboArlYxv0wQ== - -turbo-windows-64@1.13.3: - version "1.13.3" - resolved "https://registry.yarnpkg.com/turbo-windows-64/-/turbo-windows-64-1.13.3.tgz#6629174c8f654e75c342a0e1b826620cb6e2795b" - integrity sha512-Nudr4bRChfJzBPzEmpVV85VwUYRCGKecwkBFpbp2a4NtrJ3+UP1VZES653ckqCu2FRyRuS0n03v9euMbAvzH+Q== - -turbo-windows-arm64@1.13.3: - version "1.13.3" - resolved "https://registry.yarnpkg.com/turbo-windows-arm64/-/turbo-windows-arm64-1.13.3.tgz#327b8c87d8a01533deb3b7c3a108855aa7b6611d" - integrity sha512-ouJCgsVLd3icjRLmRvHQDDZnmGzT64GBupM1Y+TjtYn2LVaEBoV6hicFy8x5DUpnqdLy+YpCzRMkWlwhmkX7sQ== - -turbo@^1.13.3: - version "1.13.3" - resolved "https://registry.yarnpkg.com/turbo/-/turbo-1.13.3.tgz#afb7bee4fa9f5b6041dac5b4a7d35fb98f279827" - integrity sha512-n17HJv4F4CpsYTvKzUJhLbyewbXjq1oLCi90i5tW1TiWDz16ML1eDG7wi5dHaKxzh5efIM56SITnuVbMq5dk4g== +turbo-darwin-64@2.0.14: + version "2.0.14" + resolved "https://registry.yarnpkg.com/turbo-darwin-64/-/turbo-darwin-64-2.0.14.tgz#f12bce31709f86656c34e037f0bb7d579206b619" + integrity sha512-kwfDmjNwlNfvtrvT29+ZBg5n1Wvxl891bFHchMJyzMoR0HOE9N1NSNdSZb9wG3e7sYNIu4uDkNk+VBEqJW0HzQ== + +turbo-darwin-arm64@2.0.14: + version "2.0.14" + resolved "https://registry.yarnpkg.com/turbo-darwin-arm64/-/turbo-darwin-arm64-2.0.14.tgz#001afca42de5b1e78841626241af09dfed004cda" + integrity sha512-m3LXYEshCx3wc4ZClM6gb01KYpFmtjQ9IBF3A7ofjb6ahux3xlYZJZ3uFCLAGHuvGLuJ3htfiPbwlDPTdknqqw== + +turbo-linux-64@2.0.14: + version "2.0.14" + resolved "https://registry.yarnpkg.com/turbo-linux-64/-/turbo-linux-64-2.0.14.tgz#fbc5358227d28d8d82994885a48832159fc31920" + integrity sha512-7vBzCPdoTtR92SNn2JMgj1FlMmyonGmpMaQdgAB1OVYtuQ6NVGoh7/lODfaILqXjpvmFSVbpBIDrKOT6EvcprQ== + +turbo-linux-arm64@2.0.14: + version "2.0.14" + resolved "https://registry.yarnpkg.com/turbo-linux-arm64/-/turbo-linux-arm64-2.0.14.tgz#62299c377b34ae72330b875c87440694c22940f9" + integrity sha512-jwH+c0bfjpBf26K/tdEFatmnYyXwGROjbr6bZmNcL8R+IkGAc/cglL+OToqJnQZTgZvH7uDGbeSyUo7IsHyjuA== + +turbo-windows-64@2.0.14: + version "2.0.14" + resolved "https://registry.yarnpkg.com/turbo-windows-64/-/turbo-windows-64-2.0.14.tgz#e0c3a8c6beb213187df4b8cffff284c84e05b2f1" + integrity sha512-w9/XwkHSzvLjmioo6cl3S1yRfI6swxsV1j1eJwtl66JM4/pn0H2rBa855R0n7hZnmI6H5ywLt/nLt6Ae8RTDmw== + +turbo-windows-arm64@2.0.14: + version "2.0.14" + resolved "https://registry.yarnpkg.com/turbo-windows-arm64/-/turbo-windows-arm64-2.0.14.tgz#076ed8c841154e0b82f297ab8c1a01925bcf6cc0" + integrity sha512-XaQlyYk+Rf4xS5XWCo8XCMIpssgGGy8blzLfolN6YBp4baElIWMlkLZHDbGyiFmCbNf9I9gJI64XGRG+LVyyjA== + +turbo@^2.0.14: + version "2.0.14" + resolved "https://registry.yarnpkg.com/turbo/-/turbo-2.0.14.tgz#fb3df4d795998536e38f13f3109b1e02f5e811a4" + integrity sha512-00JjdCMD/cpsjP0Izkjcm8Oaor5yUCfDwODtaLb+WyblyadkaDEisGhy3Dbd5az9n+5iLSPiUgf+WjPbns6MRg== optionalDependencies: - turbo-darwin-64 "1.13.3" - turbo-darwin-arm64 "1.13.3" - turbo-linux-64 "1.13.3" - turbo-linux-arm64 "1.13.3" - turbo-windows-64 "1.13.3" - turbo-windows-arm64 "1.13.3" + turbo-darwin-64 "2.0.14" + turbo-darwin-arm64 "2.0.14" + turbo-linux-64 "2.0.14" + turbo-linux-arm64 "2.0.14" + turbo-windows-64 "2.0.14" + turbo-windows-arm64 "2.0.14" turbogrid@^3.2.0: version "3.2.0" @@ -21287,11 +21031,6 @@ type-fest@^0.6.0: resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz" integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg== -type-fest@^0.7.1: - version "0.7.1" - resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz" - integrity sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg== - type-fest@^0.8.1: version "0.8.1" resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz" @@ -21386,7 +21125,7 @@ typeforce@^1.11.3, typeforce@^1.11.5: resolved "https://registry.npmjs.org/typeforce/-/typeforce-1.18.0.tgz" integrity sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g== -"typescript@>=3 < 6", typescript@^5.4.5: +typescript@5.4.5, "typescript@>=3 < 6": version "5.4.5" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.5.tgz#42ccef2c571fdbd0f6718b1d1f5e6e5ef006f611" integrity sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ== @@ -21977,11 +21716,6 @@ webpack-bundle-analyzer@4.3.0: sirv "^1.0.7" ws "^7.3.1" -"webpack-sources@^2.0.0 || ^3.0.0": - version "3.2.3" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" - integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== - whatwg-encoding@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz" @@ -22095,7 +21829,7 @@ which@^1.2.9: dependencies: isexe "^2.0.0" -which@^2.0.1, which@^2.0.2: +which@^2.0.1: version "2.0.2" resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz" integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==