diff --git a/packages/bridge/package.json b/packages/bridge/package.json
index d5e4225513..30b14a7ac1 100644
--- a/packages/bridge/package.json
+++ b/packages/bridge/package.json
@@ -40,7 +40,7 @@
"launchdarkly-node-client-sdk": "^3.3.0",
"long": "^5.2.3",
"lru-cache": "^10.0.1",
- "nomic-bitcoin": "^4.2.0",
+ "nomic-bitcoin": "^5.0.0-pre.0",
"viem": "^2.21.19",
"zod": "^3.22.4"
},
diff --git a/packages/bridge/src/nomic/index.ts b/packages/bridge/src/nomic/index.ts
index aa67f7d939..cfdab75f4b 100644
--- a/packages/bridge/src/nomic/index.ts
+++ b/packages/bridge/src/nomic/index.ts
@@ -1,17 +1,27 @@
import type { Registry } from "@cosmjs/proto-signing";
import { Dec, RatePretty } from "@keplr-wallet/unit";
import { getRouteTokenOutGivenIn } from "@osmosis-labs/server";
-import { estimateGasFee, getSwapMessages } from "@osmosis-labs/tx";
+import {
+ estimateGasFee,
+ getSwapMessages,
+ makeSkipIbcHookSwapMemo,
+ SkipSwapIbcHookContractAddress,
+} from "@osmosis-labs/tx";
import { IbcTransferMethod } from "@osmosis-labs/types";
import {
deriveCosmosAddress,
+ getAllBtcMinimalDenom,
+ getnBTCMinimalDenom,
isCosmosAddressValid,
timeout,
} from "@osmosis-labs/utils";
import {
+ BaseDepositOptions,
buildDestination,
+ DepositInfo,
generateDepositAddressIbc,
getPendingDeposits,
+ IbcDepositOptions,
} from "nomic-bitcoin";
import { BridgeQuoteError } from "../errors";
@@ -40,13 +50,15 @@ export class NomicBridgeProvider implements BridgeProvider {
readonly relayers: string[];
readonly nBTCMinimalDenom: string;
+ readonly allBtcMinimalDenom: string | undefined;
protected protoRegistry: Registry | null = null;
constructor(protected readonly ctx: BridgeProviderContext) {
- this.nBTCMinimalDenom =
- this.ctx.env === "mainnet"
- ? "ibc/75345531D87BD90BF108BE7240BD721CB2CB0A1F16D4EBA71B09EC3C43E15C8F" // nBTC
- : "ibc/72D483F0FD4229DBF3ACC78E648F0399C4ACADDFDBCDD9FE791FEE4443343422"; // Testnet nBTC
+ this.allBtcMinimalDenom = getAllBtcMinimalDenom({ env: this.ctx.env });
+ this.nBTCMinimalDenom = getnBTCMinimalDenom({
+ env: this.ctx.env,
+ });
+
this.relayers =
this.ctx.env === "testnet"
? ["https://testnet-relayer.nomic.io:8443"]
@@ -111,7 +123,9 @@ export class NomicBridgeProvider implements BridgeProvider {
| Awaited>
| undefined;
- if (fromAsset.address !== this.nBTCMinimalDenom) {
+ if (
+ fromAsset.address.toLowerCase() === this.allBtcMinimalDenom?.toLowerCase()
+ ) {
swapRoute = await getRouteTokenOutGivenIn({
assetLists: this.ctx.assetLists,
tokenInAmount: fromAmount,
@@ -264,6 +278,7 @@ export class NomicBridgeProvider implements BridgeProvider {
async getDepositAddress({
fromChain,
toAddress,
+ toAsset,
}: GetDepositAddressParams): Promise {
if (!isCosmosAddressValid({ address: toAddress, bech32Prefix: "osmo" })) {
throw new BridgeQuoteError({
@@ -287,11 +302,11 @@ export class NomicBridgeProvider implements BridgeProvider {
});
}
- const transferMethod = nomicBtc.transferMethods.find(
+ const nomicIbcTransferMethod = nomicBtc.transferMethods.find(
(method): method is IbcTransferMethod => method.type === "ibc"
);
- if (!transferMethod) {
+ if (!nomicIbcTransferMethod) {
throw new BridgeQuoteError({
bridgeId: "Nomic",
errorType: "UnsupportedQuoteError",
@@ -307,11 +322,52 @@ export class NomicBridgeProvider implements BridgeProvider {
});
}
+ const nomicChain = this.ctx.chainList.find(
+ ({ chain_name }) =>
+ chain_name === nomicIbcTransferMethod.counterparty.chainName
+ );
+
+ if (!nomicChain) {
+ throw new BridgeQuoteError({
+ bridgeId: "Nomic",
+ errorType: "UnsupportedQuoteError",
+ message: "Nomic chain not found in chain list.",
+ });
+ }
+
+ const userWantsAllBtc =
+ this.allBtcMinimalDenom && toAsset.address === this.allBtcMinimalDenom;
+
+ const now = Date.now();
+ const timeoutTimestampFiveDaysFromNow =
+ Number(now + 86_400 * 5 * 1_000 - (now % (60 * 60 * 1_000))) * 1_000_000;
+ const swapMemo = userWantsAllBtc
+ ? makeSkipIbcHookSwapMemo({
+ denomIn: this.nBTCMinimalDenom,
+ denomOut:
+ this.ctx.env === "mainnet" ? this.allBtcMinimalDenom : "uosmo",
+ env: this.ctx.env,
+ minAmountOut: "1",
+ poolId:
+ this.ctx.env === "mainnet"
+ ? "1868" // nBTC/allBTC pool on Osmosis
+ : "663", // nBTC/osmo pool on Osmosis. Since there's no alloyed btc in testnet, we'll use these pool instead
+ receiverOsmoAddress: toAddress,
+ timeoutTimestamp: timeoutTimestampFiveDaysFromNow,
+ })
+ : undefined;
+
const depositInfo = await generateDepositAddressIbc({
relayers: this.relayers,
- channel: transferMethod.counterparty.channelId, // IBC channel ID on Nomic
+ channel: nomicIbcTransferMethod.counterparty.channelId, // IBC channel ID on Nomic
bitcoinNetwork: this.ctx.env === "testnet" ? "testnet" : "bitcoin",
- receiver: toAddress,
+ sender: deriveCosmosAddress({
+ address: toAddress,
+ desiredBech32Prefix: nomicChain.bech32_prefix,
+ }),
+ receiver:
+ userWantsAllBtc && swapMemo ? swapMemo.wasm.contract : toAddress,
+ ...(swapMemo ? { memo: JSON.stringify(swapMemo) } : {}),
});
if (depositInfo.code === 1) {
@@ -387,18 +443,31 @@ export class NomicBridgeProvider implements BridgeProvider {
const isNomicBtc =
assetListAsset.coinMinimalDenom.toLowerCase() ===
this.nBTCMinimalDenom.toLowerCase();
+ const isAllBtc =
+ this.allBtcMinimalDenom &&
+ assetListAsset.coinMinimalDenom.toLowerCase() ===
+ this.allBtcMinimalDenom.toLowerCase();
- const nomicWithdrawAmountEnabled = await getLaunchDarklyFlagValue({
+ const nomicWithdrawEnabled = await getLaunchDarklyFlagValue({
key: "nomicWithdrawAmount",
});
if (bitcoinCounterparty || isNomicBtc) {
+ let transferTypes: BridgeSupportedAsset["transferTypes"] = [];
+
+ if (direction === "withdraw" && nomicWithdrawEnabled) {
+ transferTypes = ["quote"];
+ } else if (direction === "deposit" && (isNomicBtc || isAllBtc)) {
+ transferTypes = ["deposit-address"];
+ }
+
+ if (transferTypes.length === 0) {
+ return [];
+ }
+
return [
{
- transferTypes:
- direction === "withdraw" && nomicWithdrawAmountEnabled
- ? ["quote"]
- : ["deposit-address"],
+ transferTypes,
chainId: "bitcoin",
chainName: "Bitcoin",
chainType: "bitcoin",
@@ -437,10 +506,43 @@ export class NomicBridgeProvider implements BridgeProvider {
async getPendingDeposits({ address }: { address: string }) {
try {
- const pendingDeposits = await timeout(
- () => getPendingDeposits(this.relayers, address),
- 10000
- )();
+ const [pendingDeposits, skipSwapPendingDeposits] = await Promise.all([
+ timeout(() => getPendingDeposits(this.relayers, address), 10000)(),
+ /**
+ * We need to check all deposits to skip contract since we set the receiver to the skip contract address.
+ * So, we need to filter out any deposits that are not intended for the user.
+ */
+ timeout(
+ () =>
+ getPendingDeposits(this.relayers, SkipSwapIbcHookContractAddress),
+ 10000
+ )(),
+ ]);
+
+ /**
+ * Filter out deposits that are not intended for the user
+ */
+ const filteredSkipSwapPendingDeposits = skipSwapPendingDeposits
+ .filter((deposit) => {
+ try {
+ if (!("dest" in deposit)) return false;
+ const dest = deposit.dest as {
+ data: BaseDepositOptions & IbcDepositOptions;
+ };
+ const memo = JSON.parse(dest.data.memo ?? "{}");
+ return (
+ memo.wasm.msg.swap_and_action.post_swap_action.transfer
+ .to_address === address
+ );
+ } catch (error) {
+ console.error("Error parsing memo:", error);
+ return false;
+ }
+ })
+ .map((deposit) => ({
+ ...deposit,
+ __type: "contract-deposit" as const,
+ }));
const nomicBtc = this.ctx.assetLists
.flatMap(({ assets }) => assets)
@@ -452,7 +554,12 @@ export class NomicBridgeProvider implements BridgeProvider {
throw new Error("Nomic Bitcoin asset not found in asset list.");
}
- return pendingDeposits.map((deposit) => ({
+ const deposits = [
+ ...pendingDeposits,
+ ...filteredSkipSwapPendingDeposits,
+ ] as (DepositInfo & { __type?: "contract-deposit" })[];
+
+ return deposits.map((deposit) => ({
transactionId: deposit.txid,
amount: deposit.amount,
confirmations: deposit.confirmations,
@@ -460,16 +567,16 @@ export class NomicBridgeProvider implements BridgeProvider {
address: nomicBtc.coinMinimalDenom,
amount: ((deposit.minerFeeRate ?? 0) * 1e8).toString(),
decimals: 8,
- // TODO: Handle case when we can receive allBTC
- denom: nomicBtc.symbol,
+ denom:
+ deposit.__type === "contract-deposit" ? "BTC" : nomicBtc.symbol,
coinGeckoId: nomicBtc.coingeckoId,
},
providerFee: {
address: nomicBtc.coinMinimalDenom,
amount: ((deposit.bridgeFeeRate ?? 0) * deposit.amount).toString(),
decimals: 8,
- // TODO: Handle case when we can receive allBTC
- denom: nomicBtc.symbol,
+ denom:
+ deposit.__type === "contract-deposit" ? "BTC" : nomicBtc.symbol,
coinGeckoId: nomicBtc.coingeckoId,
},
}));
diff --git a/packages/server/src/queries/complex/portfolio/__tests__/assets.spec.ts b/packages/server/src/queries/complex/portfolio/__tests__/assets.spec.ts
index afb8b16a13..74efc0d074 100644
--- a/packages/server/src/queries/complex/portfolio/__tests__/assets.spec.ts
+++ b/packages/server/src/queries/complex/portfolio/__tests__/assets.spec.ts
@@ -262,16 +262,19 @@ describe("Has Asset Variants", () => {
denom:
"ibc/498A0751C798A0D9A389AA3691123DADA57DAA4FE165D5C75894505B876BA6E4", // USDC
amount: "0.2",
+ fiatValue: "0.2",
},
{
denom:
"factory/osmo1rckme96ptawr4zwexxj5g5gej9s2dmud8r2t9j0k0prn5mch5g4snzzwjv/sail", // SAIL
amount: "0.2",
+ fiatValue: "0.2",
},
{
denom:
"ibc/EA1D43981D5C9A1C4AAEA9C23BB1D4FA126BA9BC7020A25E0AE4AA841EA25DC5", // ETH.axl <- this is the variant
amount: "0.2",
+ fiatValue: "0.2",
},
];
@@ -284,11 +287,13 @@ describe("Has Asset Variants", () => {
{
denom: "uosmo", // OSMO
amount: "0.2",
+ fiatValue: "0.2",
},
{
denom:
"factory/osmo1rckme96ptawr4zwexxj5g5gej9s2dmud8r2t9j0k0prn5mch5g4snzzwjv/sail", // SAIL
amount: "0.2",
+ fiatValue: "0.2",
},
];
@@ -297,7 +302,11 @@ describe("Has Asset Variants", () => {
});
it("should return empty array when user has no asset variants", () => {
- const userCoinMinimalDenoms: { denom: string; amount: string }[] = [];
+ const userCoinMinimalDenoms: {
+ denom: string;
+ amount: string;
+ fiatValue: string;
+ }[] = [];
const result = checkAssetVariants(userCoinMinimalDenoms, assetLists);
expect(result.length).toBe(0);
diff --git a/packages/server/src/queries/complex/portfolio/assets.ts b/packages/server/src/queries/complex/portfolio/assets.ts
index 0656321440..fadd377815 100644
--- a/packages/server/src/queries/complex/portfolio/assets.ts
+++ b/packages/server/src/queries/complex/portfolio/assets.ts
@@ -5,7 +5,6 @@ import { sort } from "@osmosis-labs/utils";
import { captureIfError } from "../../../utils";
import { Categories, queryPortfolioAssets } from "../../sidecar";
-import { AccountCoinsResultDec } from "../../sidecar/portfolio-assets";
import { getAsset } from "../assets";
import { DEFAULT_VS_CURRENCY } from "../assets/config";
@@ -19,6 +18,7 @@ export interface Allocation {
export interface AssetVariant {
name: string;
amount: CoinPretty;
+ fiatValue: PricePretty;
canonicalAsset: MinimalAsset;
}
@@ -86,21 +86,21 @@ export function calculatePercentAndFiatValues(
const account_coins_result = (totalAssets?.account_coins_result || []).map(
(asset) => ({
- ...asset,
- cap_value: new Dec(asset.cap_value),
+ coin: asset.coin,
+ fiatValue: new Dec(asset.cap_value),
})
);
const sortedAccountCoinsResults = sort(
account_coins_result || [],
- "cap_value",
+ "fiatValue",
"desc"
);
const topCoinsResults = sortedAccountCoinsResults.slice(0, allocationLimit);
const assets: Allocation[] = topCoinsResults
- .map((asset: AccountCoinsResultDec) => {
+ .map((asset) => {
const assetFromAssetLists = captureIfError(() =>
getAsset({
assetLists,
@@ -116,8 +116,8 @@ export function calculatePercentAndFiatValues(
key: assetFromAssetLists.coinDenom,
percentage: totalCap.isZero()
? new RatePretty(0)
- : new RatePretty(asset.cap_value.quo(totalCap)),
- fiatValue: new PricePretty(DEFAULT_VS_CURRENCY, asset.cap_value),
+ : new RatePretty(asset.fiatValue.quo(totalCap)),
+ fiatValue: new PricePretty(DEFAULT_VS_CURRENCY, asset.fiatValue),
asset: new CoinPretty(assetFromAssetLists, asset.coin.amount),
};
})
@@ -126,7 +126,7 @@ export function calculatePercentAndFiatValues(
const otherAssets = sortedAccountCoinsResults.slice(allocationLimit);
const otherAmount = otherAssets.reduce(
- (sum: Dec, asset: AccountCoinsResultDec) => sum.add(asset.cap_value),
+ (sum: Dec, asset) => sum.add(asset.fiatValue),
new Dec(0)
);
@@ -195,6 +195,7 @@ export async function getPortfolioAssets({
categories["user-balances"]?.account_coins_result?.map((result) => ({
denom: result.coin.denom,
amount: result.coin.amount, // Assuming amount is stored in result.coin.amount
+ fiatValue: result.cap_value,
})) ?? [];
// check for asset variants, alloys and canonical assets such as USDC
@@ -210,13 +211,13 @@ export async function getPortfolioAssets({
}
export function checkAssetVariants(
- userBalanceDenoms: { denom: string; amount: string }[],
+ userBalanceDenoms: { denom: string; amount: string; fiatValue: string }[],
assetLists: AssetList[]
): AssetVariant[] {
const assetListAssets = assetLists.flatMap((list) => list.assets);
return userBalanceDenoms
- .map(({ denom, amount }) => {
+ .map(({ denom, amount, fiatValue }) => {
const asset = assetListAssets.find(
(asset) => asset.coinMinimalDenom === denom
);
@@ -240,6 +241,7 @@ export function checkAssetVariants(
return {
name: userAsset.coinName,
amount: new CoinPretty(userAsset, amount),
+ fiatValue: new PricePretty(DEFAULT_VS_CURRENCY, new Dec(fiatValue)),
canonicalAsset: canonicalAsset,
};
}
diff --git a/packages/server/src/queries/sidecar/portfolio-assets.ts b/packages/server/src/queries/sidecar/portfolio-assets.ts
index 7104e64f39..eb7323ef63 100644
--- a/packages/server/src/queries/sidecar/portfolio-assets.ts
+++ b/packages/server/src/queries/sidecar/portfolio-assets.ts
@@ -1,4 +1,3 @@
-import { Dec } from "@keplr-wallet/unit";
import { apiClient } from "@osmosis-labs/utils";
import { SIDECAR_BASE_URL } from "../../env";
@@ -13,10 +12,6 @@ export interface AccountCoinsResult {
cap_value: string;
}
-export interface AccountCoinsResultDec {
- coin: Coin;
- cap_value: Dec;
-}
export interface Category {
capitalization: string;
is_best_effort: boolean;
diff --git a/packages/stores/src/account/base.ts b/packages/stores/src/account/base.ts
index bd72913f15..b09fc7b4ac 100644
--- a/packages/stores/src/account/base.ts
+++ b/packages/stores/src/account/base.ts
@@ -1032,12 +1032,12 @@ export class AccountStore[] = []> {
}
if (memo === "") {
- // If the memo is empty, set it to "FE" so we know it originated from the frontend for
+ // If the memo is empty, set it to "OsmosisFE" so we know it originated from the frontend for
// QA purposes.
- memo = "FE";
+ memo = "OsmosisFE";
} else {
- // Otherwise, tack on "FE" to the end of the memo.
- memo += " \nFE";
+ // Otherwise, tack on "OsmosisFE" to the end of the memo.
+ memo += " \nOsmosisFE";
}
const [
@@ -1230,12 +1230,12 @@ export class AccountStore[] = []> {
pubkey.typeUrl = pubKeyTypeUrl;
if (memo === "") {
- // If the memo is empty, set it to "FE" so we know it originated from the frontend for
+ // If the memo is empty, set it to "OsmosisFE" so we know it originated from the frontend for
// QA purposes.
- memo = "FE";
+ memo = "OsmosisFE";
} else {
- // Otherwise, tack on "FE" to the end of the memo.
- memo += " \nFE";
+ // Otherwise, tack on "OsmosisFE" to the end of the memo.
+ memo += " \nOsmosisFE";
}
const txBodyEncodeObject = {
diff --git a/packages/trpc/src/assets.ts b/packages/trpc/src/assets.ts
index a70ee7930c..8df816c203 100644
--- a/packages/trpc/src/assets.ts
+++ b/packages/trpc/src/assets.ts
@@ -22,13 +22,20 @@ import {
getPoolAssetPairHistoricalPrice,
getUpcomingAssets,
getUserAssetsTotal,
+ IS_TESTNET,
mapGetAssetsWithUserBalances,
mapGetMarketAssets,
maybeCachePaginatedItems,
TimeDuration,
TimeFrame,
} from "@osmosis-labs/server";
-import { compareCommon, isNil, sort } from "@osmosis-labs/utils";
+import {
+ compareCommon,
+ getAllBtcMinimalDenom,
+ getnBTCMinimalDenom,
+ isNil,
+ sort,
+} from "@osmosis-labs/utils";
import { z } from "zod";
import { createTRPCRouter, publicProcedure } from "./api";
@@ -107,15 +114,55 @@ export const assetsRouter = createTRPCRouter({
limit,
})
),
- getCanonicalAssetWithVariants: publicProcedure
- .input(
- z.object({
- findMinDenomOrSymbol: z.string(),
- })
- )
- .query(async ({ input: { findMinDenomOrSymbol }, ctx }) =>
- getAssetWithVariants({ ...ctx, anyDenom: findMinDenomOrSymbol })
- ),
+
+ getBridgeAssetWithVariants: publicProcedure
+ .input(z.object({ findMinDenomOrSymbol: z.string() }))
+ .query(async ({ input: { findMinDenomOrSymbol }, ctx }) => {
+ const canonicalAssetWithVariants = getAssetWithVariants({
+ ...ctx,
+ anyDenom: findMinDenomOrSymbol,
+ });
+
+ /**
+ * Manually include Nomic and BTC assets in the list.
+ * These assets are not variants of each other and cannot be automatically linked
+ * from our asset list. We do this to display them in the receive asset dropdown
+ * and enable conversion to alloy or native.
+ */
+ if (
+ canonicalAssetWithVariants[0].coinMinimalDenom.toLowerCase() ===
+ getnBTCMinimalDenom({
+ env: IS_TESTNET ? "testnet" : "mainnet",
+ }).toLowerCase()
+ ) {
+ const allBtcAsset = getAsset({
+ ...ctx,
+ anyDenom:
+ getAllBtcMinimalDenom({
+ env: IS_TESTNET ? "testnet" : "mainnet",
+ }) ?? "",
+ });
+ if (allBtcAsset) canonicalAssetWithVariants.push(allBtcAsset);
+ }
+
+ if (
+ canonicalAssetWithVariants[0].coinMinimalDenom.toLowerCase() ===
+ (
+ getAllBtcMinimalDenom({ env: IS_TESTNET ? "testnet" : "mainnet" }) ??
+ ""
+ ).toLowerCase()
+ ) {
+ const nBTCAsset = getAsset({
+ ...ctx,
+ anyDenom: getnBTCMinimalDenom({
+ env: IS_TESTNET ? "testnet" : "mainnet",
+ }),
+ });
+ if (nBTCAsset) canonicalAssetWithVariants.push(nBTCAsset);
+ }
+
+ return canonicalAssetWithVariants;
+ }),
getAssetPrice: publicProcedure
.input(
z.object({
diff --git a/packages/tx/src/message-composers/complex/swap.ts b/packages/tx/src/message-composers/complex/swap.ts
index bf5a63746f..78ad23ed13 100644
--- a/packages/tx/src/message-composers/complex/swap.ts
+++ b/packages/tx/src/message-composers/complex/swap.ts
@@ -246,3 +246,66 @@ export async function getSwapMessages({
throw new Error(`Unsupported quote type ${quoteType}`);
}
+
+export const SkipSwapIbcHookContractAddress =
+ "osmo1vkdakqqg5htq5c3wy2kj2geq536q665xdexrtjuwqckpads2c2nsvhhcyv";
+
+export function makeSkipIbcHookSwapMemo({
+ denomIn,
+ denomOut,
+ minAmountOut: amountOut,
+ poolId,
+ receiverOsmoAddress,
+ env,
+ timeoutTimestamp,
+}: {
+ denomIn: string;
+ denomOut: string;
+ minAmountOut: string;
+ poolId: string;
+ timeoutTimestamp: number;
+ receiverOsmoAddress: string;
+ env: "testnet" | "mainnet";
+}) {
+ return {
+ wasm: {
+ contract:
+ env === "testnet"
+ ? // osmosis-1 and osmo-test-5 share the same contract address
+ SkipSwapIbcHookContractAddress // https://celatone.osmosis.zone/osmo-test-5/contracts/osmo1vkdakqqg5htq5c3wy2kj2geq536q665xdexrtjuwqckpads2c2nsvhhcyv
+ : SkipSwapIbcHookContractAddress, // https://celatone.osmosis.zone/osmo-1/contracts/osmo1vkdakqqg5htq5c3wy2kj2geq536q665xdexrtjuwqckpads2c2nsvhhcyv
+ msg: {
+ swap_and_action: {
+ user_swap: {
+ swap_exact_asset_in: {
+ swap_venue_name:
+ env === "testnet"
+ ? "testnet-osmosis-poolmanager"
+ : "osmosis-poolmanager",
+ operations: [
+ {
+ denom_in: denomIn,
+ denom_out: denomOut,
+ pool: poolId,
+ },
+ ],
+ },
+ },
+ timeout_timestamp: timeoutTimestamp,
+ min_asset: {
+ native: {
+ denom: denomOut,
+ amount: amountOut,
+ },
+ },
+ post_swap_action: {
+ transfer: {
+ to_address: receiverOsmoAddress,
+ },
+ },
+ affiliates: [],
+ },
+ },
+ },
+ };
+}
diff --git a/packages/types/src/feature-flags-types.ts b/packages/types/src/feature-flags-types.ts
index 986737bfb6..34dafa8661 100644
--- a/packages/types/src/feature-flags-types.ts
+++ b/packages/types/src/feature-flags-types.ts
@@ -22,4 +22,5 @@ export type AvailableFlags =
| "sqsActiveOrders"
| "alloyedAssets"
| "bridgeDepositAddress"
- | "nomicWithdrawAmount";
+ | "nomicWithdrawAmount"
+ | "swapToolTopGainers";
diff --git a/packages/utils/src/bitcoin.ts b/packages/utils/src/bitcoin.ts
index 6baf706d74..b72303af94 100644
--- a/packages/utils/src/bitcoin.ts
+++ b/packages/utils/src/bitcoin.ts
@@ -21,3 +21,23 @@ export const getBitcoinExplorerUrl = ({
? BitcoinTestnetExplorerUrl.replace("{txHash}", txHash)
: BitcoinMainnetExplorerUrl.replace("{txHash}", txHash);
};
+
+export const getAllBtcMinimalDenom = ({
+ env,
+}: {
+ env: "mainnet" | "testnet";
+}) => {
+ return env === "mainnet"
+ ? "factory/osmo1z6r6qdknhgsc0zeracktgpcxf43j6sekq07nw8sxduc9lg0qjjlqfu25e3/alloyed/allBTC"
+ : undefined; // No testnet allBTC for now.
+};
+
+export const getnBTCMinimalDenom = ({
+ env,
+}: {
+ env: "mainnet" | "testnet";
+}) => {
+ return env === "mainnet"
+ ? "ibc/75345531D87BD90BF108BE7240BD721CB2CB0A1F16D4EBA71B09EC3C43E15C8F" // nBTC
+ : "ibc/72D483F0FD4229DBF3ACC78E648F0399C4ACADDFDBCDD9FE791FEE4443343422"; // Testnet nBTC
+};
diff --git a/packages/web/components/assets/highlights-categories.tsx b/packages/web/components/assets/highlights-categories.tsx
index 5c001aa727..0ca1797176 100644
--- a/packages/web/components/assets/highlights-categories.tsx
+++ b/packages/web/components/assets/highlights-categories.tsx
@@ -94,7 +94,7 @@ const HighlightsGrid: FunctionComponent = ({
);
};
-function highlightPrice24hChangeAsset(asset: PriceChange24hAsset) {
+export function highlightPrice24hChangeAsset(asset: PriceChange24hAsset) {
return {
asset: {
...asset,
@@ -129,7 +129,7 @@ function highlightUpcomingReleaseAsset(asset: UpcomingReleaseAsset) {
};
}
-const AssetHighlights: FunctionComponent<
+export const AssetHighlights: FunctionComponent<
{
title: string;
subtitle?: string;
@@ -145,6 +145,7 @@ const AssetHighlights: FunctionComponent<
isLoading?: boolean;
disableLinking?: boolean;
highlight: Highlight;
+ onClickAsset?: (asset: HighlightAsset) => void;
} & CustomClasses
> = ({
title,
@@ -154,6 +155,7 @@ const AssetHighlights: FunctionComponent<
isLoading = false,
className,
highlight,
+ onClickAsset,
}) => {
const { t } = useTranslation();
@@ -192,6 +194,7 @@ const AssetHighlights: FunctionComponent<
asset={asset}
extraInfo={extraInfo}
highlight={highlight}
+ onClick={onClickAsset}
/>
))}
>
@@ -201,21 +204,21 @@ const AssetHighlights: FunctionComponent<
);
};
+type HighlightAsset = {
+ coinDenom: string;
+ coinName: string;
+ coinImageUrl?: string;
+ href?: string;
+ externalLink?: boolean;
+};
+
const AssetHighlightRow: FunctionComponent<{
- asset: {
- coinDenom: string;
- coinName: string;
- coinImageUrl?: string;
- href?: string;
- externalLink?: boolean;
- };
+ asset: HighlightAsset;
extraInfo: ReactNode;
highlight: Highlight;
-}> = ({
- asset: { coinDenom, coinName, coinImageUrl, href, externalLink },
- extraInfo,
- highlight,
-}) => {
+ onClick?: (asset: HighlightAsset) => void;
+}> = ({ asset, extraInfo, highlight, onClick }) => {
+ const { coinDenom, coinName, coinImageUrl, href, externalLink } = asset;
const { logEvent } = useAmplitudeAnalytics();
const AssetContent = (
@@ -234,7 +237,10 @@ const AssetHighlightRow: FunctionComponent<{
);
return !href ? (
-
+
onClick?.(asset)}
+ >
{AssetContent}
) : (
@@ -245,6 +251,7 @@ const AssetHighlightRow: FunctionComponent<{
className="-mx-2 flex items-center justify-between gap-4 rounded-lg p-2 transition-colors duration-200 ease-in-out hover:cursor-pointer hover:bg-osmoverse-850"
onClick={() => {
logEvent([EventName.Assets.assetClicked, { coinDenom, highlight }]);
+ onClick?.(asset);
}}
>
{AssetContent}
diff --git a/packages/web/components/bridge/amount-and-review-screen.tsx b/packages/web/components/bridge/amount-and-review-screen.tsx
index 02ac700a81..77982cd28b 100644
--- a/packages/web/components/bridge/amount-and-review-screen.tsx
+++ b/packages/web/components/bridge/amount-and-review-screen.tsx
@@ -102,7 +102,7 @@ export const AmountAndReviewScreen = observer(
: toChainCosmosAccount?.walletInfo.name;
const { data: assetsInOsmosis, isLoading: isLoadingAssetsInOsmosis } =
- api.edge.assets.getCanonicalAssetWithVariants.useQuery(
+ api.edge.assets.getBridgeAssetWithVariants.useQuery(
{
findMinDenomOrSymbol: selectedAssetDenom ?? "",
},
diff --git a/packages/web/components/bridge/amount-screen.tsx b/packages/web/components/bridge/amount-screen.tsx
index ba7fb8f1c2..783aef9911 100644
--- a/packages/web/components/bridge/amount-screen.tsx
+++ b/packages/web/components/bridge/amount-screen.tsx
@@ -2,10 +2,6 @@ import {
Disclosure,
DisclosureButton,
DisclosurePanel,
- Menu,
- MenuButton,
- MenuItem,
- MenuItems,
} from "@headlessui/react";
import { IntPretty } from "@keplr-wallet/unit";
import { MinimalAsset } from "@osmosis-labs/types";
@@ -26,6 +22,7 @@ import {
import { useMeasure } from "react-use";
import { Icon } from "~/components/assets";
+import { BridgeReceiveAssetDropdown } from "~/components/bridge/bridge-receive-asset-dropdown";
import { DepositAddressScreen } from "~/components/bridge/deposit-address-screen";
import { CryptoFiatInput } from "~/components/control/crypto-fiat-input";
import { SkeletonLoader, Spinner } from "~/components/loaders";
@@ -34,7 +31,6 @@ import {
ScreenManager,
useScreenManager,
} from "~/components/screen-manager";
-import { Tooltip } from "~/components/tooltip";
import { Button } from "~/components/ui/button";
import { EventName } from "~/config";
import { EthereumChainIds } from "~/config/wagmi";
@@ -770,6 +766,25 @@ export const AmountScreen = observer(
);
+ const assetDropdown = fromAsset &&
+ toAsset &&
+ assetsInOsmosis &&
+ shouldShowAssetDropdown &&
+ !isLoading && (
+
+ );
+
+ console.log(fromAsset);
+
if (
featureFlags.bridgeDepositAddress &&
!quote.enabled &&
@@ -786,6 +801,7 @@ export const AmountScreen = observer(
canonicalAsset={canonicalAsset}
direction={direction}
chainSelection={chainSelection}
+ assetDropdown={assetDropdown}
fromChain={fromChain}
toChain={toChain}
fromAsset={fromAsset}
@@ -1085,218 +1101,7 @@ export const AmountScreen = observer(
>
)}
- {fromAsset &&
- toAsset &&
- assetsInOsmosis &&
- shouldShowAssetDropdown &&
- !isLoading && (
-
- )}
+ {assetDropdown}
{fromChain && (
void;
+ assetsInOsmosis: MinimalAsset[];
+ counterpartySupportedAssetsByChainId: Record;
+}
+
+export const BridgeReceiveAssetDropdown: FunctionComponent =
+ observer(
+ ({
+ direction,
+ fromAsset,
+ toAsset,
+ setToAsset,
+ assetsInOsmosis,
+ counterpartySupportedAssetsByChainId,
+ }) => {
+ const { logEvent } = useAmplitudeAnalytics();
+ const { accountStore } = useStore();
+ const { t } = useTranslation();
+
+ return (
+
+ );
+ }
+ );
diff --git a/packages/web/components/bridge/deposit-address-screen.tsx b/packages/web/components/bridge/deposit-address-screen.tsx
index 5de0536b9b..a0964e0b9a 100644
--- a/packages/web/components/bridge/deposit-address-screen.tsx
+++ b/packages/web/components/bridge/deposit-address-screen.tsx
@@ -44,6 +44,7 @@ interface DepositAddressScreenProps {
direction: "deposit" | "withdraw";
canonicalAsset: MinimalAsset;
chainSelection: React.ReactNode;
+ assetDropdown: React.ReactNode;
fromChain: BridgeChainWithDisplayInfo;
toChain: BridgeChainWithDisplayInfo;
fromAsset: BridgeAsset;
@@ -56,6 +57,7 @@ export const DepositAddressScreen = observer(
direction,
canonicalAsset,
chainSelection,
+ assetDropdown,
fromChain,
bridge,
toChain,
@@ -87,6 +89,8 @@ export const DepositAddressScreen = observer(
enabled: !!osmosisAddress,
refetchOnWindowFocus: false,
useErrorBoundary: true,
+ cacheTime: 0,
+ staleTime: 0,
}
);
@@ -324,18 +328,13 @@ export const DepositAddressScreen = observer(
) : (
<>
- {t("transfer.receiveAsset")}}>
- {/* TODO: Remove this conditional when alloyed BTC is supported */}
-
- {bridge === "Nomic" ? "nBTC" : toAsset.denom}
-
-
+ {assetDropdown}
{t("transfer.minimumDeposit")}}>
-
+
{data?.depositData?.minimumDeposit.amount.toString()}{" "}
{data?.depositData?.minimumDeposit.fiatValue ? (
<>
@@ -369,7 +368,7 @@ const DepositInfoRow: FunctionComponent<{
}> = ({ label, children }) => {
return (
-
{label}
+
{label}
{children}
);
@@ -414,13 +413,15 @@ const TransferDetails: FunctionComponent<{
) : open ? (
- {t("transfer.transferDetails")}
+
+ {t("transfer.transferDetails")}
+
) : null}
{!isLoading && depositData?.estimatedTime && !open && (
-
+
{t(depositData?.estimatedTime)}
@@ -436,7 +437,7 @@ const TransferDetails: FunctionComponent<{
{!open && totalFees && (
-
+
{!showTotalFeeIneqSymbol && "~"}{" "}
{totalFees
.inequalitySymbol(showTotalFeeIneqSymbol)
diff --git a/packages/web/components/bridge/review-screen.tsx b/packages/web/components/bridge/review-screen.tsx
index 0c7775ab60..e8b7d4e3f5 100644
--- a/packages/web/components/bridge/review-screen.tsx
+++ b/packages/web/components/bridge/review-screen.tsx
@@ -82,7 +82,7 @@ export const ReviewScreen: FunctionComponent = ({
const { t } = useTranslation();
const { data: assetsInOsmosis } =
- api.edge.assets.getCanonicalAssetWithVariants.useQuery(
+ api.edge.assets.getBridgeAssetWithVariants.useQuery(
{
findMinDenomOrSymbol: selectedDenom!,
},
diff --git a/packages/web/components/cards/my-position/__tests__/expanded.spec.tsx b/packages/web/components/cards/my-position/__tests__/expanded.spec.tsx
new file mode 100644
index 0000000000..fb612ed5a7
--- /dev/null
+++ b/packages/web/components/cards/my-position/__tests__/expanded.spec.tsx
@@ -0,0 +1,115 @@
+import { CoinPretty, Dec, DecUtils, PricePretty } from "@keplr-wallet/unit";
+import { DEFAULT_VS_CURRENCY } from "@osmosis-labs/server";
+import { render, screen } from "@testing-library/react";
+import React from "react";
+
+import { AssetsInfo } from "../expanded";
+
+jest.mock("~/hooks", () => ({
+ useTranslation: () => ({
+ t: (key: string) => key,
+ }),
+}));
+
+describe("AssetsInfo", () => {
+ it("renders with assets and total value", () => {
+ const assets = [
+ new CoinPretty(
+ { coinDenom: "ATOM", coinDecimals: 6, coinMinimalDenom: "uatom" },
+ new Dec(100).mul(DecUtils.getTenExponentN(6))
+ ),
+ new CoinPretty(
+ { coinDenom: "OSMO", coinDecimals: 6, coinMinimalDenom: "uosmo" },
+ new Dec(200).mul(DecUtils.getTenExponentN(6))
+ ),
+ new CoinPretty(
+ { coinDenom: "PEPE", coinDecimals: 6, coinMinimalDenom: "upepe" },
+ new Dec(200.002).mul(DecUtils.getTenExponentN(6))
+ ),
+ new CoinPretty(
+ { coinDenom: "USDC", coinDecimals: 6, coinMinimalDenom: "uusdc" },
+ new Dec(200.02).mul(DecUtils.getTenExponentN(6))
+ ),
+ ];
+ const totalValue = new PricePretty(DEFAULT_VS_CURRENCY, "300");
+
+ render(
+
+ );
+
+ expect(screen.getByText("Test Title")).toBeInTheDocument();
+ expect(screen.getByText("100 ATOM")).toBeInTheDocument();
+ expect(screen.getByText("200 OSMO")).toBeInTheDocument();
+ expect(screen.getByText("200.002 PEPE")).toBeInTheDocument();
+ expect(screen.getByText("200.02 USDC")).toBeInTheDocument();
+ expect(screen.getByText("($300)")).toBeInTheDocument();
+ });
+
+ it("should handle assets with very small decimals", () => {
+ const assets = [
+ new CoinPretty(
+ { coinDenom: "BTC", coinDecimals: 8, coinMinimalDenom: "sat" },
+ new Dec(0.02123451).mul(DecUtils.getTenExponentN(8))
+ ),
+ new CoinPretty(
+ { coinDenom: "BTC2", coinDecimals: 8, coinMinimalDenom: "sat" },
+ new Dec(0.00000001).mul(DecUtils.getTenExponentN(8))
+ ),
+ new CoinPretty(
+ { coinDenom: "BTC3", coinDecimals: 8, coinMinimalDenom: "sat" },
+ new Dec(0.62345678).mul(DecUtils.getTenExponentN(8))
+ ),
+ ];
+ const totalValue = new PricePretty(DEFAULT_VS_CURRENCY, "300");
+
+ render(
+
+ );
+
+ expect(screen.getByText("0.021234 BTC")).toBeInTheDocument();
+ expect(screen.getByText("0.00000001 BTC2")).toBeInTheDocument();
+ expect(screen.getByText("0.623456 BTC3")).toBeInTheDocument();
+ expect(screen.getByText("($300)")).toBeInTheDocument();
+ });
+
+ it("renders with empty text when no assets", () => {
+ render(
+
+ );
+
+ expect(screen.getByText("Test Title")).toBeInTheDocument();
+ expect(
+ screen.getByText("clPositions.checkBackForRewards")
+ ).toBeInTheDocument();
+ });
+
+ it("renders without total value when not provided", () => {
+ const assets = [
+ new CoinPretty(
+ { coinDenom: "ATOM", coinDecimals: 6, coinMinimalDenom: "uatom" },
+ "100"
+ ),
+ ];
+
+ render(
+
+ );
+
+ expect(screen.queryByText("(100 USD)")).not.toBeInTheDocument();
+ });
+});
diff --git a/packages/web/components/cards/my-position/expanded.tsx b/packages/web/components/cards/my-position/expanded.tsx
index 57b24a6c6c..0f4aad7e0c 100644
--- a/packages/web/components/cards/my-position/expanded.tsx
+++ b/packages/web/components/cards/my-position/expanded.tsx
@@ -444,7 +444,7 @@ const PositionButton: FunctionComponent> = (
);
};
-const AssetsInfo: FunctionComponent<
+export const AssetsInfo: FunctionComponent<
{
title: string;
assets?: CoinPretty[];
@@ -479,7 +479,7 @@ const AssetsInfo: FunctionComponent<
)}
- {formatPretty(asset, { maxDecimals: 2 })}
+ {formatPretty(asset, { maxDecimals: 6 })}
))}
diff --git a/packages/web/components/nomic/nomic-pending-transfers.tsx b/packages/web/components/nomic/nomic-pending-transfers.tsx
index 8a583c2c26..09d2fd5ef2 100644
--- a/packages/web/components/nomic/nomic-pending-transfers.tsx
+++ b/packages/web/components/nomic/nomic-pending-transfers.tsx
@@ -364,7 +364,9 @@ const TransactionDetailsModal = ({
{t("transfer.nomic.asset")}
- nBTC
+
+ {depositData.networkFee.amount.denom}
+
diff --git a/packages/web/components/table/asset-info.tsx b/packages/web/components/table/asset-info.tsx
index ad6a0eceef..45883ce1e3 100644
--- a/packages/web/components/table/asset-info.tsx
+++ b/packages/web/components/table/asset-info.tsx
@@ -19,6 +19,7 @@ import {
useMemo,
useState,
} from "react";
+import { useMount } from "react-use";
import { HighlightsCategories } from "~/components/assets/highlights-categories";
import { AssetCell } from "~/components/table/cells/asset";
@@ -39,6 +40,7 @@ import { UnverifiedAssetsState } from "~/stores/user-settings";
import { theme } from "~/tailwind.config";
import { formatPretty } from "~/utils/formatter";
import { api, RouterInputs, RouterOutputs } from "~/utils/trpc";
+import { removeQueryParam } from "~/utils/url";
import { AssetCategoriesSelectors } from "../assets/categories";
import { HistoricalPriceSparkline, PriceChange } from "../assets/price";
@@ -66,8 +68,6 @@ export const AssetsInfoTable: FunctionComponent<{
const { t } = useTranslation();
const { logEvent } = useAmplitudeAnalytics();
- // State
-
// category
const [selectedCategory, setCategory] = useState();
const selectCategory = useCallback(
@@ -97,6 +97,15 @@ export const AssetsInfoTable: FunctionComponent<{
[selectedCategory]
);
+ useMount(() => {
+ const urlParams = new URLSearchParams(window.location.search);
+ const category = urlParams.get("category");
+ if (category) {
+ setCategory(category);
+ removeQueryParam("category");
+ }
+ });
+
// search
const [searchQuery, setSearchQuery] = useState();
const onSearchInput = useCallback((input: string) => {
diff --git a/packages/web/config/analytics-events.ts b/packages/web/config/analytics-events.ts
index bef4462ef1..24f2f6a510 100644
--- a/packages/web/config/analytics-events.ts
+++ b/packages/web/config/analytics-events.ts
@@ -82,6 +82,7 @@ export type EventProperties = {
section: string;
tokenIn: string;
tokenOut: string;
+ token: string;
option: string;
numberOfValidators: number;
validatorNames: string[];
@@ -114,6 +115,7 @@ export const EventName = {
swapCompleted: "Swap: Swap completed",
swapFailed: "Swap: Swap failed",
dropdownAssetSelected: "Swap: Dropdown asset selected",
+ checkTopGainers: "Swap: Check Top Gainers",
},
// Events in Sidebar UI
Sidebar: {
diff --git a/packages/web/hooks/use-convert-variant.ts b/packages/web/hooks/use-convert-variant.ts
index 6697ef1f2c..b6c77bc3fc 100644
--- a/packages/web/hooks/use-convert-variant.ts
+++ b/packages/web/hooks/use-convert-variant.ts
@@ -126,6 +126,7 @@ export function useConvertVariant(
{
fromToken: variant.amount.currency.coinDenom,
toToken: variant.canonicalAsset.coinDenom,
+ valueUsd: Number(variant.fiatValue.toDec().toString()),
},
]);
resolve();
@@ -140,10 +141,10 @@ export function useConvertVariant(
}),
[
variant,
+ account?.address,
quote,
accountStore,
variantTransactionIdentifier,
- account?.address,
logEvent,
]
);
diff --git a/packages/web/hooks/use-feature-flags.ts b/packages/web/hooks/use-feature-flags.ts
index 85addd6368..f4a5079ec3 100644
--- a/packages/web/hooks/use-feature-flags.ts
+++ b/packages/web/hooks/use-feature-flags.ts
@@ -29,6 +29,7 @@ const defaultFlags: Record = {
alloyedAssets: false,
bridgeDepositAddress: false,
nomicWithdrawAmount: false,
+ swapToolTopGainers: false,
};
export function useFeatureFlags() {
diff --git a/packages/web/next-env.d.ts b/packages/web/next-env.d.ts
index a4a7b3f5cf..4f11a03dc6 100644
--- a/packages/web/next-env.d.ts
+++ b/packages/web/next-env.d.ts
@@ -2,4 +2,4 @@
///
// NOTE: This file should not be edited
-// see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information.
+// see https://nextjs.org/docs/basic-features/typescript for more information.
diff --git a/packages/web/pages/index.tsx b/packages/web/pages/index.tsx
index 17be86468c..2547cd34ee 100644
--- a/packages/web/pages/index.tsx
+++ b/packages/web/pages/index.tsx
@@ -1,14 +1,23 @@
import dayjs from "dayjs";
import { observer } from "mobx-react-lite";
import Image from "next/image";
+import { useRouter } from "next/router";
import { useLocalStorage } from "react-use";
import { AdBanners } from "~/components/ad-banner";
+import {
+ AssetHighlights,
+ highlightPrice24hChangeAsset,
+} from "~/components/assets/highlights-categories";
import { ClientOnly } from "~/components/client-only";
import { ErrorBoundary } from "~/components/error/error-boundary";
import { TradeTool } from "~/components/trade-tool";
import { EventName } from "~/config";
-import { useAmplitudeAnalytics, useFeatureFlags } from "~/hooks";
+import {
+ useAmplitudeAnalytics,
+ useFeatureFlags,
+ useTranslation,
+} from "~/hooks";
import { api } from "~/utils/trpc";
export const SwapPreviousTradeKey = "swap-previous-trade";
@@ -52,6 +61,7 @@ const Home = () => {
setPreviousTrade={setPreviousTrade}
/>
+ {featureFlags.swapToolTopGainers && }
@@ -59,6 +69,35 @@ const Home = () => {
);
};
+const TopGainers = () => {
+ const { t } = useTranslation();
+ const router = useRouter();
+ const { logEvent } = useAmplitudeAnalytics();
+
+ const { data: topGainerAssets, isLoading: isTopGainerAssetsLoading } =
+ api.edge.assets.getTopGainerAssets.useQuery({
+ topN: 4,
+ });
+
+ return (
+ {
+ logEvent([EventName.Swap.checkTopGainers, { token: "All" }]);
+ router.push(`/assets?category=topGainers`);
+ }}
+ onClickAsset={(asset) => {
+ logEvent([EventName.Swap.checkTopGainers, { token: asset.coinDenom }]);
+ }}
+ highlight="topGainers"
+ />
+ );
+};
+
const SwapAdsBanner = observer(() => {
const { data, isLoading } = api.local.cms.getSwapAdBanners.useQuery(
undefined,
diff --git a/yarn.lock b/yarn.lock
index c6f2313b3d..706aaff503 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -17309,10 +17309,10 @@ nodemailer@^6.9.14:
resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.9.14.tgz#845fda981f9fd5ac264f4446af908a7c78027f75"
integrity sha512-Dobp/ebDKBvz91sbtRKhcznLThrKxKt97GI2FAlAyy+fk19j73Uz3sBXolVtmcXjaorivqsbbbjDY+Jkt4/bQA==
-nomic-bitcoin@^4.2.0:
- version "4.2.0"
- resolved "https://registry.yarnpkg.com/nomic-bitcoin/-/nomic-bitcoin-4.2.0.tgz#85794508e200fc14541e6b0b1a4302b3ee147f93"
- integrity sha512-ChAkC4JbIfGXWzrGn8Aw+EIJcPzpGhC9cokXOlJhnKz5XGAzuE540kJai6PU+eJPRX1sB8Vg80ac4YjZuZw56Q==
+nomic-bitcoin@^5.0.0-pre.0:
+ version "5.0.0-pre.0"
+ resolved "https://registry.yarnpkg.com/nomic-bitcoin/-/nomic-bitcoin-5.0.0-pre.0.tgz#e99f798d0ab0fcef83bfefd57bd26f77572f25ac"
+ integrity sha512-h/WbHEw5tlkfV6LxtFf/BKKUnf64LBY+4DaYtEuNsRTeqvFZbY8KWWb0BCx0b5MngumSmrge+0sGViWkud88nA==
dependencies:
"@cosmjs/encoding" "^0.31.1"
bitcoinjs-lib "^6.1.3"