diff --git a/packages/bridge/src/bridge-providers.ts b/packages/bridge/src/bridge-providers.ts index e3bd7d95aa..1d2c81a5e6 100644 --- a/packages/bridge/src/bridge-providers.ts +++ b/packages/bridge/src/bridge-providers.ts @@ -3,6 +3,7 @@ import { IbcBridgeProvider } from "./ibc"; import { BridgeProviderContext } from "./interface"; import { NitroBridgeProvider } from "./nitro"; import { NomicBridgeProvider } from "./nomic"; +import { PenumbraBridgeProvider } from "./penumbra"; import { PicassoBridgeProvider } from "./picasso"; import { SkipBridgeProvider } from "./skip"; import { SquidBridgeProvider } from "./squid"; @@ -21,6 +22,7 @@ export class BridgeProviders { [WormholeBridgeProvider.ID]: WormholeBridgeProvider; [NitroBridgeProvider.ID]: NitroBridgeProvider; [PicassoBridgeProvider.ID]: PicassoBridgeProvider; + [PenumbraBridgeProvider.ID]: PenumbraBridgeProvider; }; constructor(integratorId: string, commonContext: BridgeProviderContext) { @@ -40,6 +42,7 @@ export class BridgeProviders { [WormholeBridgeProvider.ID]: new WormholeBridgeProvider(commonContext), [NitroBridgeProvider.ID]: new NitroBridgeProvider(commonContext), [PicassoBridgeProvider.ID]: new PicassoBridgeProvider(commonContext), + [PenumbraBridgeProvider.ID]: new PenumbraBridgeProvider(commonContext), }; } } diff --git a/packages/bridge/src/interface.ts b/packages/bridge/src/interface.ts index 24c4929ce3..33a8395980 100644 --- a/packages/bridge/src/interface.ts +++ b/packages/bridge/src/interface.ts @@ -185,12 +185,19 @@ const tronChainSchema = z.object({ chainType: z.literal("tron"), }); +const penumbraChainSchema = z.object({ + chainId: z.string(), + chainName: z.string(), + chainType: z.literal("penumbra"), +}); + export const bridgeChainSchema = z.discriminatedUnion("chainType", [ cosmosChainSchema, evmChainSchema, solanaChainSchema, bitcoinChainSchema, tronChainSchema, + penumbraChainSchema, ]); export type BridgeChain = z.infer; @@ -510,6 +517,7 @@ const txSnapshotSchema = z.object({ }) ), estimatedArrivalUnix: z.number(), + nomicCheckpointIndex: z.number().optional(), }); export type TxSnapshot = z.infer; diff --git a/packages/bridge/src/nomic/index.ts b/packages/bridge/src/nomic/index.ts index cfdab75f4b..a7928d7c67 100644 --- a/packages/bridge/src/nomic/index.ts +++ b/packages/bridge/src/nomic/index.ts @@ -12,14 +12,17 @@ import { deriveCosmosAddress, getAllBtcMinimalDenom, getnBTCMinimalDenom, + getNomicRelayerUrl, isCosmosAddressValid, timeout, } from "@osmosis-labs/utils"; import { BaseDepositOptions, buildDestination, + Checkpoint, DepositInfo, generateDepositAddressIbc, + getCheckpoint, getPendingDeposits, IbcDepositOptions, } from "nomic-bitcoin"; @@ -43,9 +46,10 @@ import { } from "../interface"; import { getGasAsset } from "../utils/gas"; import { getLaunchDarklyFlagValue } from "../utils/launchdarkly"; +import { NomicProviderId } from "./utils"; export class NomicBridgeProvider implements BridgeProvider { - static readonly ID = "Nomic"; + static readonly ID = NomicProviderId; readonly providerName = NomicBridgeProvider.ID; readonly relayers: string[]; @@ -54,15 +58,11 @@ export class NomicBridgeProvider implements BridgeProvider { protected protoRegistry: Registry | null = null; constructor(protected readonly ctx: BridgeProviderContext) { + this.relayers = getNomicRelayerUrl({ env: this.ctx.env }); 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"] - : ["https://relayer.nomic.mappum.io:8443"]; } async getQuote(params: GetBridgeQuoteParams): Promise { @@ -182,16 +182,29 @@ export class NomicBridgeProvider implements BridgeProvider { }), }; - const [ibcTxMessages, estimatedTime] = await Promise.all([ - ibcProvider.getTxMessages({ - ...transactionDataParams, - memo: destMemo, - }), - ibcProvider.estimateTransferTime( - transactionDataParams.fromChain.chainId.toString(), - transactionDataParams.toChain.chainId.toString() - ), - ]); + const [ibcTxMessages, ibcEstimatedTimeSeconds, nomicCheckpoint] = + await Promise.all([ + ibcProvider.getTxMessages({ + ...transactionDataParams, + memo: destMemo, + }), + ibcProvider.estimateTransferTime( + transactionDataParams.fromChain.chainId.toString(), + transactionDataParams.toChain.chainId.toString() + ), + getCheckpoint({ + relayers: this.relayers, + bitcoinNetwork: this.ctx.env === "mainnet" ? "bitcoin" : "testnet", + }), + ]); + + // 4 hours + const nomicEstimatedTimeSeconds = 4 * 60 * 60; + + const transferFeeInSats = Math.ceil( + (nomicCheckpoint as Checkpoint & { minerFee: number }).minerFee * 64 + 546 + ); + const transferFeeInMicroSats = transferFeeInSats * 1e6; const msgs = [...swapMessages, ...ibcTxMessages]; @@ -238,22 +251,29 @@ export class NomicBridgeProvider implements BridgeProvider { ...params.fromAsset, }, expectedOutput: { - amount: !!swapRoute - ? swapRoute.amount.toCoin().amount - : params.fromAmount, + amount: (!!swapRoute + ? new Dec(swapRoute.amount.toCoin().amount) + : new Dec(params.fromAmount) + ) + // Use micro sats because the amount will always be nomic btc which has 14 decimals (micro sats) + .sub(new Dec(transferFeeInMicroSats)) + .toString(), ...nomicBridgeAsset, denom: "BTC", priceImpact: swapRoute?.priceImpactTokenOut?.toDec().toString() ?? "0", }, fromChain: params.fromChain, toChain: params.toChain, - // currently subsidized by relayers, but could be paid by user in future by charging the user the gas cost of transferFee: { ...params.fromAsset, + denom: "BTC", chainId: params.fromChain.chainId, - amount: "0", + amount: (params.fromAsset.decimals === 14 + ? transferFeeInMicroSats + : transferFeeInSats + ).toString(), }, - estimatedTime, + estimatedTime: ibcEstimatedTimeSeconds + nomicEstimatedTimeSeconds, estimatedGasFee: gasFee ? { address: gasAsset?.address ?? gasFee.denom, diff --git a/packages/bridge/src/nomic/transfer-status.ts b/packages/bridge/src/nomic/transfer-status.ts new file mode 100644 index 0000000000..250b7e75ab --- /dev/null +++ b/packages/bridge/src/nomic/transfer-status.ts @@ -0,0 +1,97 @@ +import { Chain } from "@osmosis-labs/types"; +import { getNomicRelayerUrl, isNil, poll } from "@osmosis-labs/utils"; +import { getCheckpoint } from "nomic-bitcoin"; + +import type { + BridgeEnvironment, + BridgeTransferStatus, + TransferStatusProvider, + TransferStatusReceiver, + TxSnapshot, +} from "../interface"; +import { NomicProviderId } from "./utils"; + +export class NomicTransferStatusProvider implements TransferStatusProvider { + readonly providerId = NomicProviderId; + readonly sourceDisplayName = "Nomic Bridge"; + public statusReceiverDelegate?: TransferStatusReceiver; + + constructor( + protected readonly chainList: Chain[], + readonly env: BridgeEnvironment + ) {} + + /** Request to start polling a new transaction. */ + async trackTxStatus(snapshot: TxSnapshot): Promise { + const { sendTxHash } = snapshot; + + if (!snapshot.nomicCheckpointIndex) { + throw new Error("Nomic checkpoint index is required. Skipping tracking."); + } + + await poll({ + fn: async () => { + const checkpoint = await getCheckpoint( + { + relayers: getNomicRelayerUrl({ env: this.env }), + bitcoinNetwork: this.env === "mainnet" ? "bitcoin" : "testnet", + }, + snapshot.nomicCheckpointIndex! + ); + + if (isNil(checkpoint.txid)) { + return; + } + + return { + id: snapshot.sendTxHash, + status: "success", + } as BridgeTransferStatus; + }, + validate: (incomingStatus) => incomingStatus !== undefined, + interval: 30_000, + maxAttempts: undefined, // unlimited attempts while tab is open or until success/fail + }) + .then((s) => { + if (s) this.receiveConclusiveStatus(sendTxHash, s); + }) + .catch((e) => console.error(`Polling Nomic has failed`, e)); + } + + receiveConclusiveStatus( + sendTxHash: string, + txStatus: BridgeTransferStatus | undefined + ): void { + if (txStatus && txStatus.id) { + const { status, reason } = txStatus; + this.statusReceiverDelegate?.receiveNewTxStatus( + sendTxHash, + status, + reason + ); + } else { + console.error( + "Nomic transfer finished poll but neither succeeded or failed" + ); + } + } + + makeExplorerUrl(snapshot: TxSnapshot): string { + const { + sendTxHash, + fromChain: { chainId: fromChainId }, + } = snapshot; + + const chain = this.chainList.find( + (chain) => chain.chain_id === fromChainId + ); + + if (!chain) throw new Error("Chain not found: " + fromChainId); + if (chain.explorers.length === 0) { + // attempt to link to mintscan since this is an IBC transfer + return `https://www.mintscan.io/${chain.chain_name}/txs/${sendTxHash}`; + } + + return chain.explorers[0].tx_page.replace("{txHash}", sendTxHash); + } +} diff --git a/packages/bridge/src/nomic/utils.ts b/packages/bridge/src/nomic/utils.ts new file mode 100644 index 0000000000..7f2908ffda --- /dev/null +++ b/packages/bridge/src/nomic/utils.ts @@ -0,0 +1 @@ +export const NomicProviderId = "Nomic"; diff --git a/packages/bridge/src/penumbra/index.ts b/packages/bridge/src/penumbra/index.ts new file mode 100644 index 0000000000..4ad5078da8 --- /dev/null +++ b/packages/bridge/src/penumbra/index.ts @@ -0,0 +1,65 @@ +import { + BridgeChain, + BridgeExternalUrl, + BridgeProvider, + BridgeProviderContext, + BridgeQuote, + BridgeSupportedAsset, + BridgeTransactionRequest, + GetBridgeSupportedAssetsParams, +} from "../interface"; + +export class PenumbraBridgeProvider implements BridgeProvider { + static readonly ID = "Penumbra"; + readonly providerName = PenumbraBridgeProvider.ID; + + constructor(protected readonly ctx: BridgeProviderContext) {} + + async getQuote(): Promise { + throw new Error("Penumbra quotes are currently not supported."); + } + + async getSupportedAssets({ + asset, + }: GetBridgeSupportedAssetsParams): Promise< + (BridgeChain & BridgeSupportedAsset)[] + > { + // just supports SOL via Penumbra + + const assetListAsset = this.ctx.assetLists + .flatMap(({ assets }) => assets) + .find( + (a) => a.coinMinimalDenom.toLowerCase() === asset.address.toLowerCase() + ); + + if (assetListAsset) { + const penumbraCounterparty = assetListAsset.counterparty.find( + (c) => c.chainName === "penumbra" + ); + + if (penumbraCounterparty) { + return [ + { + transferTypes: ["external-url"], + chainId: "penumbra", + chainName: "Penumbra", + chainType: "penumbra", + denom: penumbraCounterparty.symbol, + address: penumbraCounterparty.sourceDenom, + decimals: penumbraCounterparty.decimals, + }, + ]; + } + } + + return []; + } + + async getTransactionData(): Promise { + throw new Error("Penumbra transactions are currently not supported."); + } + + async getExternalUrl(): Promise { + throw new Error("Penumbra external urls are currently not supported."); + } +} diff --git a/packages/stores/src/account/base.ts b/packages/stores/src/account/base.ts index b09fc7b4ac..48ac2c410f 100644 --- a/packages/stores/src/account/base.ts +++ b/packages/stores/src/account/base.ts @@ -216,7 +216,7 @@ export class AccountStore[] = []> { makeObservable(this); autorun(async () => { - const isOneClickTradingEnabled = await this.getShouldUseOneClickTrading(); + const isOneClickTradingEnabled = await this.isOneClickTradingEnabled(); const oneClickTradingInfo = await this.getOneClickTradingInfo(); const hasUsedOneClickTrading = await this.getHasUsedOneClickTrading(); runInAction(() => { @@ -515,11 +515,12 @@ export class AccountStore[] = []> { fee?: StdFee, signOptions?: SignOptions, onTxEvents?: - | ((tx: DeliverTxResponse) => void) + | ((tx: DeliverTxResponse) => void | Promise) | { onBroadcastFailed?: (e?: Error) => void; onBroadcasted?: (txHash: Uint8Array) => void; onFulfill?: (tx: DeliverTxResponse) => void; + onSign?: () => Promise | void; } ) { runInAction(() => { @@ -547,6 +548,7 @@ export class AccountStore[] = []> { let onBroadcasted: ((txHash: Uint8Array) => void) | undefined; let onFulfill: ((tx: DeliverTxResponse) => void) | undefined; + let onSign: (() => Promise | void) | undefined; if (onTxEvents) { if (typeof onTxEvents === "function") { @@ -554,6 +556,7 @@ export class AccountStore[] = []> { } else { onBroadcasted = onTxEvents?.onBroadcasted; onFulfill = onTxEvents?.onFulfill; + onSign = onTxEvents?.onSign; } } @@ -598,6 +601,14 @@ export class AccountStore[] = []> { const { TxRaw } = await import("cosmjs-types/cosmos/tx/v1beta1/tx"); const encodedTx = TxRaw.encode(txRaw).finish(); + if (this.options.preTxEvents?.onSign) { + await this.options.preTxEvents.onSign(); + } + + if (onSign) { + await onSign(); + } + const restEndpoint = getEndpointString( await wallet.getRestEndpoint(true) ); @@ -709,7 +720,7 @@ export class AccountStore[] = []> { } if (onFulfill) { - onFulfill(tx); + await onFulfill(tx); } } catch (e) { const error = e as Error | AccountStoreNoBroadcastErrorEvent; @@ -1472,7 +1483,7 @@ export class AccountStore[] = []> { }: { messages: readonly EncodeObject[]; }): Promise { - const isOneClickTradingEnabled = await this.isOneCLickTradingEnabled(); + const isOneClickTradingEnabled = await this.isOneClickTradingEnabled(); const oneClickTradingInfo = await this.getOneClickTradingInfo(); if (!oneClickTradingInfo || !isOneClickTradingEnabled) { @@ -1535,7 +1546,7 @@ export class AccountStore[] = []> { }); } - async isOneCLickTradingEnabled(): Promise { + async isOneClickTradingEnabled(): Promise { const oneClickTradingInfo = await this.getOneClickTradingInfo(); if (isNil(oneClickTradingInfo)) return false; diff --git a/packages/stores/src/account/osmosis/index.ts b/packages/stores/src/account/osmosis/index.ts index c83a0c7784..31afc81845 100644 --- a/packages/stores/src/account/osmosis/index.ts +++ b/packages/stores/src/account/osmosis/index.ts @@ -10,7 +10,6 @@ import { import * as OsmosisMath from "@osmosis-labs/math"; import { maxTick, minTick } from "@osmosis-labs/math"; import { - makeAddAuthenticatorMsg, makeAddToConcentratedLiquiditySuperfluidPositionMsg, makeAddToPositionMsg, makeBeginUnlockingMsg, @@ -27,7 +26,6 @@ import { makeJoinSwapExternAmountInMsg, makeLockAndSuperfluidDelegateMsg, makeLockTokensMsg, - makeRemoveAuthenticatorMsg, makeSetValidatorSetPreferenceMsg, makeSplitRoutesSwapExactAmountInMsg, makeSplitRoutesSwapExactAmountOutMsg, @@ -2203,154 +2201,6 @@ export class OsmosisAccountImpl { ); } - async sendAddOrRemoveAuthenticatorsMsg({ - addAuthenticators, - removeAuthenticators, - memo = "", - onFulfill, - onBroadcasted, - signOptions, - }: { - addAuthenticators: { authenticatorType: string; data: Uint8Array }[]; - removeAuthenticators: bigint[]; - memo?: string; - onFulfill?: (tx: DeliverTxResponse) => void; - onBroadcasted?: () => void; - signOptions?: SignOptions; - }) { - const addAuthenticatorMsgs = addAuthenticators.map((authenticator) => - makeAddAuthenticatorMsg({ - authenticatorType: authenticator.authenticatorType, - data: authenticator.data, - sender: this.address, - }) - ); - const removeAuthenticatorMsgs = removeAuthenticators.map((id) => - makeRemoveAuthenticatorMsg({ - id, - sender: this.address, - }) - ); - const msgs = await Promise.all([ - ...removeAuthenticatorMsgs, - ...addAuthenticatorMsgs, - ]); - - await this.base.signAndBroadcast( - this.chainId, - "addOrRemoveAuthenticators", - msgs, - memo, - undefined, - signOptions, - { - onBroadcasted, - onFulfill: (tx) => { - if (!tx.code) { - // Refresh the balances - const queries = this.queriesStore.get(this.chainId); - - queries.queryBalances - .getQueryBech32Address(this.address) - .balances.forEach((balance) => balance.waitFreshResponse()); - - queries.cosmos.queryDelegations - .getQueryBech32Address(this.address) - .waitFreshResponse(); - - queries.cosmos.queryRewards - .getQueryBech32Address(this.address) - .waitFreshResponse(); - } - onFulfill?.(tx); - }, - } - ); - } - - async sendAddAuthenticatorsMsg( - authenticators: { authenticatorType: string; data: any }[], - memo: string = "", - onFulfill?: (tx: DeliverTxResponse) => void - ) { - const addAuthenticatorMsgs = await Promise.all( - authenticators.map((authenticator) => - makeAddAuthenticatorMsg({ - authenticatorType: authenticator.authenticatorType, - data: authenticator.data, - sender: this.address, - }) - ) - ); - - await this.base.signAndBroadcast( - this.chainId, - "addAuthenticator", - addAuthenticatorMsgs, - memo, - undefined, - undefined, - (tx) => { - if (!tx.code) { - // Refresh the balances - const queries = this.queriesStore.get(this.chainId); - - queries.queryBalances - .getQueryBech32Address(this.address) - .balances.forEach((balance) => balance.waitFreshResponse()); - - queries.cosmos.queryDelegations - .getQueryBech32Address(this.address) - .waitFreshResponse(); - - queries.cosmos.queryRewards - .getQueryBech32Address(this.address) - .waitFreshResponse(); - } - onFulfill?.(tx); - } - ); - } - - async sendRemoveAuthenticatorMsg( - id: bigint, - memo: string = "", - onFulfill?: (tx: DeliverTxResponse) => void - ) { - const removeAuthenticatorMsg = await makeRemoveAuthenticatorMsg({ - id: id, - sender: this.address, - }); - - await this.base.signAndBroadcast( - this.chainId, - "removeAuthenticator", - [removeAuthenticatorMsg], - memo, - undefined, - undefined, - (tx) => { - if (!tx.code) { - // Refresh the balances - const queries = this.queriesStore.get(this.chainId); - - queries.queryBalances - .getQueryBech32Address(this.address) - .balances.forEach((balance) => balance.waitFreshResponse()); - - queries.cosmos.queryDelegations - .getQueryBech32Address(this.address) - .waitFreshResponse(); - - queries.cosmos.queryRewards - .getQueryBech32Address(this.address) - .waitFreshResponse(); - } - onFulfill?.(tx); - } - ); - } - protected get queries() { return this.queriesStore.get(this.chainId).osmosis!; } diff --git a/packages/stores/src/account/types.ts b/packages/stores/src/account/types.ts index fe8ea8faee..2160735f11 100644 --- a/packages/stores/src/account/types.ts +++ b/packages/stores/src/account/types.ts @@ -87,9 +87,10 @@ export type AccountStoreWallet[] = []> = }; export interface TxEvents { - onBroadcastFailed?: (string: string, e?: Error) => void; - onBroadcasted?: (string: string, txHash: Uint8Array) => void; - onFulfill?: (string: string, tx: any) => void; + onSign?: () => Promise | void; + onBroadcastFailed?: (chainNameOrId: string, e?: Error) => void; + onBroadcasted?: (chainNameOrId: string, txHash: Uint8Array) => void; + onFulfill?: (chainNameOrId: string, tx: any) => void; onExceeds1CTNetworkFeeLimit?: (params: { // Continue with a wallet like Keplr. continueTx: () => void; diff --git a/packages/utils/src/__tests__/string.spec.ts b/packages/utils/src/__tests__/string.spec.ts index 033b0a1f46..a4e2d208ef 100644 --- a/packages/utils/src/__tests__/string.spec.ts +++ b/packages/utils/src/__tests__/string.spec.ts @@ -61,39 +61,65 @@ describe("shorten", () => { describe("isBitcoinAddressValid", () => { it("should return true for a valid Bitcoin address (P2PKH)", () => { const validAddress = "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"; - expect(isBitcoinAddressValid({ address: validAddress })).toBe(true); + expect( + isBitcoinAddressValid({ address: validAddress, env: "mainnet" }) + ).toBe(true); }); it("should return true for a valid Bitcoin address (P2SH)", () => { const validAddress = "3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy"; - expect(isBitcoinAddressValid({ address: validAddress })).toBe(true); + expect( + isBitcoinAddressValid({ address: validAddress, env: "mainnet" }) + ).toBe(true); }); it("should return true for a valid Bitcoin address (P2WPKH)", () => { const validAddress = "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq"; - expect(isBitcoinAddressValid({ address: validAddress })).toBe(true); + expect( + isBitcoinAddressValid({ address: validAddress, env: "mainnet" }) + ).toBe(true); }); it("should return true for a valid testnet Bitcoin address ", () => { const validAddress = "tb1qq9epaj33z79vwz5zu9gw40j00yma7cm7g2ympl"; expect( - isBitcoinAddressValid({ address: validAddress, isTestnet: true }) + isBitcoinAddressValid({ address: validAddress, env: "testnet" }) ).toBe(true); }); it("should return false for an invalid Bitcoin address", () => { const invalidAddress = "invalidBitcoinAddress"; - expect(isBitcoinAddressValid({ address: invalidAddress })).toBe(false); + expect( + isBitcoinAddressValid({ address: invalidAddress, env: "mainnet" }) + ).toBe(false); }); it("should return false for an empty address", () => { const emptyAddress = ""; - expect(isBitcoinAddressValid({ address: emptyAddress })).toBe(false); + expect( + isBitcoinAddressValid({ address: emptyAddress, env: "mainnet" }) + ).toBe(false); }); it("should return false for a malformed address", () => { const malformedAddress = "12345"; - expect(isBitcoinAddressValid({ address: malformedAddress })).toBe(false); + expect( + isBitcoinAddressValid({ address: malformedAddress, env: "mainnet" }) + ).toBe(false); + }); + + it("should return false for a testnet address in mainnet", () => { + const testnetAddress = "tb1qq9epaj33z79vwz5zu9gw40j00yma7cm7g2ympl"; + expect( + isBitcoinAddressValid({ address: testnetAddress, env: "mainnet" }) + ).toBe(false); + }); + + it("should return true for a mainnet address in mainnet", () => { + const mainnetAddress = "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"; + expect( + isBitcoinAddressValid({ address: mainnetAddress, env: "mainnet" }) + ).toBe(true); }); }); diff --git a/packages/utils/src/bitcoin.ts b/packages/utils/src/bitcoin.ts index b72303af94..68035ab0eb 100644 --- a/packages/utils/src/bitcoin.ts +++ b/packages/utils/src/bitcoin.ts @@ -3,6 +3,7 @@ export const BitcoinChainInfo = { chainId: "bitcoin", chainName: "Bitcoin", color: "#F7931A", + logoUri: "/networks/bitcoin.svg", }; export const BitcoinMainnetExplorerUrl = @@ -39,5 +40,11 @@ export const getnBTCMinimalDenom = ({ }) => { return env === "mainnet" ? "ibc/75345531D87BD90BF108BE7240BD721CB2CB0A1F16D4EBA71B09EC3C43E15C8F" // nBTC - : "ibc/72D483F0FD4229DBF3ACC78E648F0399C4ACADDFDBCDD9FE791FEE4443343422"; // Testnet nBTC + : "ibc/8D294CE85345F171AAF6B1FF6E64B5A9EE197C99CDAD64D79EA4ACAB270AC95C"; // Testnet nBTC }; + +export function getNomicRelayerUrl({ env }: { env: "mainnet" | "testnet" }) { + return env === "testnet" + ? ["https://testnet-relayer.nomic.io:8443"] + : ["https://relayer.nomic.mappum.io:8443"]; +} diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index 07493feca3..ca00506a85 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -15,6 +15,7 @@ export * from "./gas-utils"; export * from "./ibc-utils"; export * from "./math"; export * from "./object"; +export * from "./penumbra"; export * from "./poll"; export * from "./solana"; export * from "./sort"; diff --git a/packages/utils/src/penumbra.ts b/packages/utils/src/penumbra.ts new file mode 100644 index 0000000000..0d19c06f42 --- /dev/null +++ b/packages/utils/src/penumbra.ts @@ -0,0 +1,6 @@ +export const PenumbraChainInfo = { + prettyName: "Penumbra", + chainId: "penumbra", + chainName: "Penumbra", + color: "#ff902f", +}; diff --git a/packages/utils/src/string.ts b/packages/utils/src/string.ts index ac7acdceb1..8c208f17e0 100644 --- a/packages/utils/src/string.ts +++ b/packages/utils/src/string.ts @@ -76,19 +76,42 @@ export function isEvmAddressValid({ address }: { address: string }): boolean { export function isBitcoinAddressValid({ address, - isTestnet = false, + env, }: { address: string; - isTestnet?: boolean; + env: "mainnet" | "testnet"; }): boolean { try { - bitcoin.address.toOutputScript( - address, - isTestnet ? bitcoin.networks.testnet : bitcoin.networks.bitcoin - ); - return true; + // Attempt to decode the address + const decoded = bitcoin.address.fromBase58Check(address); + const isTestnet = + decoded.version === bitcoin.networks.testnet.pubKeyHash || + decoded.version === bitcoin.networks.testnet.scriptHash; + const isMainnet = + decoded.version === bitcoin.networks.bitcoin.pubKeyHash || + decoded.version === bitcoin.networks.bitcoin.scriptHash; + + if ((env === "mainnet" && isMainnet) || (env === "testnet" && isTestnet)) { + return true; // Address is valid for the given environment + } + return false; // Address is invalid for the given environment } catch (e) { - return false; + try { + // If Base58 decoding fails, try Bech32 decoding + const decoded = bitcoin.address.fromBech32(address); + const isTestnet = decoded.prefix === "tb" || decoded.prefix === "bcrt"; + const isMainnet = decoded.prefix === "bc"; + + if ( + (env === "mainnet" && isMainnet) || + (env === "testnet" && isTestnet) + ) { + return true; // Address is valid for the given environment + } + return false; // Address is invalid for the given environment + } catch (e) { + return false; // Address is invalid + } } } diff --git a/packages/web/.env b/packages/web/.env index d0f78286ff..cf30c16291 100644 --- a/packages/web/.env +++ b/packages/web/.env @@ -48,4 +48,7 @@ TWITTER_API_URL=https://api.twitter.com/ BLOCKAID_BASE_URL=http://api.blockaid.io:80 # BLOCKAID_API_KEY= -NEXT_PUBLIC_SPEND_LIMIT_CONTRACT_ADDRESS=osmo10xqv8rlpkflywm92k5wdmplzy7khtasl9c2c08psmvlu543k724sy94k74 \ No newline at end of file +NEXT_PUBLIC_SPEND_LIMIT_CONTRACT_ADDRESS=osmo10xqv8rlpkflywm92k5wdmplzy7khtasl9c2c08psmvlu543k724sy94k74 + +# Disable TRPC logs in development +# NEXT_PUBLIC_TRPC_LOGS=off diff --git a/packages/web/components/alert/__tests__/prettify.spec.ts b/packages/web/components/alert/__tests__/prettify.spec.ts index 68a35e5f09..90e0bb343c 100644 --- a/packages/web/components/alert/__tests__/prettify.spec.ts +++ b/packages/web/components/alert/__tests__/prettify.spec.ts @@ -1,6 +1,11 @@ +import { Dec } from "@keplr-wallet/unit"; import cases from "jest-in-case"; -import { isOverspendErrorMessage, isRejectedTxErrorMessage } from "../prettify"; +import { + getParametersFromOverspendErrorMessage, + isOverspendErrorMessage, + isRejectedTxErrorMessage, +} from "../prettify"; cases( "isOverspendErrorMessage", @@ -45,6 +50,53 @@ cases( ] ); +cases( + "getParametersFromOverspendErrorMessage", + ({ message, result }) => { + expect(getParametersFromOverspendErrorMessage(message)).toEqual(result); + }, + [ + { + name: "should extract parameters from valid overspend error message", + message: + "Fetch error. Spend limit error: Overspend: 2000000 has been spent but limit is 1000000.", + result: { + wouldSpendTotal: new Dec("2000000", 6), + limit: new Dec("1000000", 6), + }, + }, + { + name: "should extract parameters from complex overspend error message", + message: + "Fetch error. execution blocked by authenticator (account = osmo1sh8lreekwcytxpqr6lxmw5cl7kdrfsdfat2ujlvz, authenticator id = 208, msg index = 0, msg type url = /osmosis.poolmanager.v1beta1.MsgSwapExactAmountIn): Spend limit error: Overspend: 50065777 has been spent but limit is 1000000: execute wasm contract failed", + result: { + wouldSpendTotal: new Dec("50065777", 6), + limit: new Dec("1000000", 6), + }, + }, + { + name: "should handle empty message", + message: "", + result: undefined, + }, + { + name: "should handle undefined message", + message: undefined, + result: undefined, + }, + { + name: "should return undefined for non-overspend error message", + message: "execution succeeded", + result: undefined, + }, + { + name: "should return undefined for invalid overspend error format", + message: "Spend limit error: Invalid format", + result: undefined, + }, + ] +); + cases( "isRejectedTxErrorMessage", ({ message, result }) => { diff --git a/packages/web/components/alert/prettify.ts b/packages/web/components/alert/prettify.ts index 7b53c3ea30..7f4038cdfd 100644 --- a/packages/web/components/alert/prettify.ts +++ b/packages/web/components/alert/prettify.ts @@ -1,5 +1,5 @@ import { AppCurrency } from "@keplr-wallet/types"; -import { CoinPretty, Int } from "@keplr-wallet/unit"; +import { CoinPretty, Dec, Int } from "@keplr-wallet/unit"; import { isInsufficientFeeError, isSlippageErrorMessage, @@ -27,6 +27,34 @@ const regexRejectedTx = /Request rejected/; const regexOverspendError = /Spend limit error: Overspend: (\d+) has been spent but limit is (\d+)/; +export function getParametersFromOverspendErrorMessage( + message: string | undefined +): { wouldSpendTotal: Dec; limit: Dec } | undefined { + if (!message) return; + + const match = message.match(regexOverspendError); + if (!match) return; + + const [, wouldSpendTotal, limit] = match; + + if (!wouldSpendTotal || !limit) return; + + try { + // Validate that extracted values are valid numbers + if (isNaN(Number(wouldSpendTotal)) || isNaN(Number(limit))) { + return; + } + + return { + wouldSpendTotal: new Dec(wouldSpendTotal, 6), + limit: new Dec(limit, 6), + }; + } catch (error) { + console.error("Failed to parse overspend error parameters:", error); + return; + } +} + export function isOverspendErrorMessage({ message, }: { diff --git a/packages/web/components/bridge/amount-screen.tsx b/packages/web/components/bridge/amount-screen.tsx index 783aef9911..a620ad2e5c 100644 --- a/packages/web/components/bridge/amount-screen.tsx +++ b/packages/web/components/bridge/amount-screen.tsx @@ -424,6 +424,14 @@ export const AmountScreen = observer( type: fromChain.chainType, assets: assets as Extract[], }; + case "penumbra": + return { + type: fromChain.chainType, + assets: assets as Extract< + SupportedAsset, + { chainType: "penumbra" } + >[], + }; default: return { type: fromChain.chainType, @@ -783,8 +791,6 @@ export const AmountScreen = observer( /> ); - console.log(fromAsset); - if ( featureFlags.bridgeDepositAddress && !quote.enabled && diff --git a/packages/web/components/bridge/bridge-wallet-select-modal.tsx b/packages/web/components/bridge/bridge-wallet-select-modal.tsx index 1db0cc8f8f..1067b7de50 100644 --- a/packages/web/components/bridge/bridge-wallet-select-modal.tsx +++ b/packages/web/components/bridge/bridge-wallet-select-modal.tsx @@ -532,7 +532,7 @@ const SendToAnotherAddressForm: FunctionComponent< } else if (toChain.chainType === "bitcoin") { isValid = isBitcoinAddressValid({ address: nextValue, - isTestnet: IS_TESTNET, + env: IS_TESTNET ? "testnet" : "mainnet", }); } @@ -570,7 +570,9 @@ const SendToAnotherAddressForm: FunctionComponent< className="body1 cursor-pointer select-none text-osmoverse-300" onClick={() => setIsAcknowledged(!isAcknowledged)} > - {t("transfer.acknowledgement")} + {toChain.chainType === "bitcoin" + ? t("transfer.acknowledgementWithoutExchange") + : t("transfer.acknowledgement")}

- - ); - } - const isLoadingMaxButton = featureFlags.swapToolSimulateFee && !isNil(account?.address) && @@ -341,7 +327,16 @@ export const SwapTool: FunctionComponent = observer( !Boolean(swapState.quote) || isSwapToolLoading || Boolean(swapState.error) || - Boolean(swapState.networkFeeError))); + Boolean( + swapState.networkFeeError && + /** + * We can increase spend limit from the review order modal + * so the decision to disable the button should be made there + */ + !isOverspendErrorMessage({ + message: swapState.networkFeeError.message, + }) + ))); const showTokenSelectRecommendedTokens = isNil(forceSwapInPoolId); @@ -626,16 +621,6 @@ export const SwapTool: FunctionComponent = observer( - {!isNil(warningText) && ( -
- {warningText} -
- )} {swapButton ?? (
+ {show1CT && showOneClickTradingSettings && ( +
+ setShowOneClickTradingSettings(false)} + onClose={() => setShowOneClickTradingSettings(false)} + transaction1CTParams={transaction1CTParams} + setTransaction1CTParams={setTransaction1CTParams} + standalone={false} + />
-
- {orderType === "limit" && tab !== "swap" && ( -
-
-
- {(tab === "buy" && !isBeyondOppositePrice) || - (tab === "sell" && isBeyondOppositePrice) ? ( - - +
+
{title}
+ +
+
+ {orderType === "limit" && tab !== "swap" && ( +
+
+
+ {(tab === "buy" && !isBeyondOppositePrice) || + (tab === "sell" && isBeyondOppositePrice) ? ( + + + + ) : ( + - - ) : ( - + )} +
+ + {t("limitOrders.priceReaches", { + denom: baseDenom ?? "", + price: limitPriceFiat + ? formatPretty( + limitPriceFiat, + getPriceExtendedFormatOptions( + limitPriceFiat.toDec() + ) + ) + : "", + })} + + {percentAdjusted && ( +
+
+ {!percentAdjusted.isZero() && ( + + )} +
+ + {formatPretty(percentAdjusted.mul(new Dec(100)).abs(), { + maxDecimals: 3, + })} + % + +
)}
- - {t("limitOrders.priceReaches", { - denom: baseDenom ?? "", - price: limitPriceFiat - ? formatPretty( - limitPriceFiat, - getPriceExtendedFormatOptions(limitPriceFiat.toDec()) - ) - : "", - })} - - {percentAdjusted && ( -
-
- {!percentAdjusted.isZero() && ( - - )} -
- - {formatPretty(percentAdjusted.mul(new Dec(100)).abs(), { - maxDecimals: 3, - })} - % - -
- )}
-
- )} -
-
-
- {fromAsset && ( - {`${fromAsset.coinDenom} - )} -
-

- {tab === "buy" - ? t("limitOrders.pay") - : t("limitOrders.sell")} -

- {inAmountToken && ( - - {formatPretty(inAmountToken)} - +
+
+
+ {fromAsset && ( + {`${fromAsset.coinDenom} )} +
+

+ {tab === "buy" + ? t("limitOrders.pay") + : t("limitOrders.sell")} +

+ {inAmountToken && ( + + {formatPretty(inAmountToken)} + + )} +
-
-
- {formatFiatPrice( - inAmountFiat ?? new PricePretty(DEFAULT_VS_CURRENCY, 0) - )} -
-
-
-
-
- +
+ {formatFiatPrice( + inAmountFiat ?? new PricePretty(DEFAULT_VS_CURRENCY, 0) + )}
-
-
-
- {toAsset && ( - {`${toAsset.coinDenom} - )} -
-

- {tab === "sell" - ? t("limitOrders.receive") - : t("portfolio.buy")} -

- - {expectedOutput && ( - <> - {formatPretty(expectedOutput.toDec(), { - minimumSignificantDigits: 6, - maximumSignificantDigits: 6, - maxDecimals: 10, - notation: "standard", - })}{" "} - {toAsset?.coinDenom} - - )} - +
+
+
+ +
-
-

- {outputDifference && ( - {`${ - outputDifference.toDec().isPositive() ? "-" : "+" - }${new RatePretty(outputDifference.toDec().abs())}`} +

+
+ {toAsset && ( + {`${toAsset.coinDenom} )} - - {formatFiatPrice( - expectedOutputFiat ?? - new PricePretty(DEFAULT_VS_CURRENCY, 0) +
+

+ {tab === "sell" + ? t("limitOrders.receive") + : t("portfolio.buy")} +

+ + {expectedOutput && ( + <> + {formatPretty(expectedOutput.toDec(), { + minimumSignificantDigits: 6, + maximumSignificantDigits: 6, + maxDecimals: 10, + notation: "standard", + })}{" "} + {toAsset?.coinDenom} + + )} + +
+
+
+

+ {outputDifference && ( + {`${ + outputDifference.toDec().isPositive() ? "-" : "+" + }${new RatePretty( + outputDifference.toDec().abs() + )}`} )} - -

+ + {formatFiatPrice( + expectedOutputFiat ?? + new PricePretty(DEFAULT_VS_CURRENCY, 0) + )} + +

+
-
-
-
- - {tab === "buy" - ? t("limitOrders.aboveMarket.title") - : t("limitOrders.belowMarket.title")} - - } - body={ - - {tab === "buy" - ? t("limitOrders.aboveMarket.description") - : t("limitOrders.belowMarket.description")} - - } - > -
- {isBeyondOppositePrice && ( - - )} - {orderType === "limit" - ? t("limitOrders.limit") - : t("limitOrders.market")} -
- - } - /> - {slippageConfig && orderType === "market" && ( -
+
+
+ + {tab === "buy" + ? t("limitOrders.aboveMarket.title") + : t("limitOrders.belowMarket.title")} + + } + body={ + + {tab === "buy" + ? t("limitOrders.aboveMarket.description") + : t("limitOrders.belowMarket.description")} + + } + > +
+ {isBeyondOppositePrice && ( + + )} + {orderType === "limit" + ? t("limitOrders.limit") + : t("limitOrders.market")} +
+ + } + /> + + {show1CT && is1CTEnabled && ( -
- setShowOneClickTradingSettings(true)} + remainingSpendLimit={remaining1CTSpendLimit} + wouldExceedSpendLimit={wouldExceedSpendLimit} + /> + } + /> + )} + {slippageConfig && orderType === "market" && ( +
+ +
{ - slippageConfig?.setIsManualSlippage(true); - setIsEditingSlippage(true); - }} - onBlur={() => { - if ( - isManualSlippageTooHigh && - +manualSlippage > 50 - ) { - handleManualSlippageChange( - (+manualSlippage).toString().split("")[0] - ); + > + { - handleManualSlippageChange(e.target.value); - - logEvent([ - EventName.Swap.slippageToleranceSet, + className="sm:caption w-fit bg-transparent px-0" + inputClassName={classNames( + "!bg-transparent focus:text-center text-right placeholder:text-wosmongton-300 transition-all focus-visible:outline-none", { - fromToken: fromAsset?.coinDenom, - toToken: toAsset?.coinDenom, - isOnHome: true, - percentage: - slippageConfig?.slippage.toString(), - page, - }, - ]); - }} + "text-rust-400 placeholder:text-rust-400": + isManualSlippageTooHigh, + } + )} + value={manualSlippage} + onFocus={() => { + slippageConfig?.setIsManualSlippage(true); + setIsEditingSlippage(true); + }} + onBlur={() => { + if ( + isManualSlippageTooHigh && + +manualSlippage > 50 + ) { + handleManualSlippageChange( + (+manualSlippage).toString().split("")[0] + ); + } + setIsEditingSlippage(false); + }} + onChange={(e) => { + handleManualSlippageChange(e.target.value); + + logEvent([ + EventName.Swap.slippageToleranceSet, + { + fromToken: fromAsset?.coinDenom, + toToken: toAsset?.coinDenom, + isOnHome: true, + percentage: + slippageConfig?.slippage.toString(), + page, + }, + ]); + }} + /> + {manualSlippage !== "" && ( + + % + + )} +
+
+ } + /> + {isManualSlippageTooHigh && ( +
+ +
+ + {t( + "limitOrders.errors.tradeMayResultInLossOfValue" + )} + + + {t("limitOrders.lowerSlippageToleranceRecommended")} + +
+
+ )} + {isManualSlippageTooLow && ( +
+ + - {manualSlippage !== "" && ( - - % - - )} + +
+ + {t("limitOrders.errors.tradeMayNotExecuted")} + + + {t("limitOrders.tryHigherSlippage")} +
+ )} +
+ )} + {orderType === "market" && ( +
+ )} + {orderType === "market" ? ( + + {amountWithSlippage && + fiatAmountWithSlippage && + toAsset && ( + + {formatPretty(amountWithSlippage, { + maxDecimals: 6, + })}{" "} + {quoteType === "out-given-in" + ? toAsset.coinDenom + : fromAsset?.coinDenom} + + )}{" "} + {fiatAmountWithSlippage && ( + + (~ + {formatPretty(fiatAmountWithSlippage, { + ...getPriceExtendedFormatOptions( + fiatAmountWithSlippage.toDec() + ), + })} + ) + + )} + } /> - {isManualSlippageTooHigh && ( -
- -
- - {t("limitOrders.errors.tradeMayResultInLossOfValue")} - - - {t("limitOrders.lowerSlippageToleranceRecommended")} - -
-
- )} - {isManualSlippageTooLow && ( -
- - - -
- - {t("limitOrders.errors.tradeMayNotExecuted")} - - - {t("limitOrders.tryHigherSlippage")} - -
-
- )} -
- )} - {orderType === "market" && ( -
- )} - {orderType === "market" ? ( + ) : ( + + {t("transfer.free")} + + } + /> + )} - {amountWithSlippage && - fiatAmountWithSlippage && - toAsset && ( - - {formatPretty(amountWithSlippage, { - maxDecimals: 6, - })}{" "} - {quoteType === "out-given-in" - ? toAsset.coinDenom - : fromAsset?.coinDenom} - - )}{" "} - {fiatAmountWithSlippage && ( - - (~ - {formatPretty(fiatAmountWithSlippage, { - ...getPriceExtendedFormatOptions( - fiatAmountWithSlippage.toDec() - ), - })} - ) - - )} - + // Do not show skeleton unless there has been no estimation/error yet + !!gasAmount || !!gasError ? ( + GasEstimation + ) : ( + + ) } /> - ) : ( - - {t("transfer.free")} +
+ {isBeyondOppositePrice && orderType === "limit" && ( +
+ +
+ + {tab === "buy" + ? t("limitOrders.aboveMarket.title") + : t("limitOrders.belowMarket.title")} + + + {tab === "buy" + ? t("limitOrders.aboveMarket.description") + : t("limitOrders.belowMarket.description")} +
+
+ )} + {show1CT && !is1CTEnabled && ( + + setTransaction1CTParams((prev) => { + if (!prev) return; + + return { + ...prev, + isOneClickEnabled: !prev.isOneClickEnabled, + }; + }) } + onParamsChange={() => setShowOneClickTradingSettings(true)} /> )} - - ) - } - /> + {!diffGteSlippage && ( +
+ +
+ )}
- {isBeyondOppositePrice && orderType === "limit" && ( -
- -
- - {tab === "buy" - ? t("limitOrders.aboveMarket.title") - : t("limitOrders.belowMarket.title")} - - - {tab === "buy" - ? t("limitOrders.aboveMarket.description") - : t("limitOrders.belowMarket.description")} - +
+ {diffGteSlippage && ( +
+
+
+
-
- )} - {!diffGteSlippage && ( -
+ + {t("limitOrders.quoteUpdated")} +
- )} -
+
+ )}
- {diffGteSlippage && ( -
-
-
- -
- - {t("limitOrders.quoteUpdated")} - - + )} + + ); +} + +const OneClickTradingPanel = ({ + t, + transactionParams, + onClick, + onParamsChange, +}: { + t: MultiLanguageT; + transactionParams: OneClickTradingTransactionParams | undefined; + onClick: () => void; + onParamsChange: () => void; +}) => { + return ( + <> +
+
+ 1ct rounded rectangle icon +
+
+

+ {t("oneClickTrading.reviewOrder.enableTitle")} +

+ +

+ {t("oneClickTrading.reviewOrder.enableDescription")} +

+
+ +
+
+ {transactionParams?.isOneClickEnabled && ( +

+ {t("oneClickTrading.reviewOrder.paramsDescription", { + sessionLength: t( + `oneClickTrading.sessionPeriods.${ + transactionParams?.sessionPeriod.end ?? "1hour" + }` + ), + spendLimit: + transactionParams?.spendLimit.toString() ?? + t("oneClickTrading.reviewOrder.defaultSpendLimit"), + })} + {" · "} + + {t("oneClickTrading.reviewOrder.change")} + +

)}
- + ); -} +}; + +const OneClickTradingActiveSessionParamsEdit = ({ + onClick, + changes = [], + transactionParams, + remainingSpendLimit, + wouldExceedSpendLimit, +}: { + remainingSpendLimit?: string; + changes: OneClickTradingParamsChanges; + transactionParams: OneClickTradingTransactionParams | undefined; + onClick: () => void; + wouldExceedSpendLimit?: boolean; +}) => { + const { t } = useTranslation(); + + return wouldExceedSpendLimit ? ( + <> +
+ + {t("oneClickTrading.reviewOrder.edit")} + + + + {t("oneClickTrading.reviewOrder.exceeded")} + + +
+ + ) : ( + + + {changes.includes("spendLimit") ? ( + + {transactionParams?.spendLimit.toString()} + + ) : ( + {remainingSpendLimit} + )} + {" / "} + {changes.includes("sessionPeriod") ? ( + + {transactionParams?.sessionPeriod.end} + + ) : ( + + )} + + + ); +}; diff --git a/packages/web/modals/wallet-select/index.tsx b/packages/web/modals/wallet-select/index.tsx index f755c04c2b..0759630dba 100644 --- a/packages/web/modals/wallet-select/index.tsx +++ b/packages/web/modals/wallet-select/index.tsx @@ -1,4 +1,4 @@ -import { State, WalletRepo, WalletStatus } from "@cosmos-kit/core"; +import { State, WalletStatus } from "@cosmos-kit/core"; import { OneClickTradingTransactionParams } from "@osmosis-labs/types"; import { isNil } from "@osmosis-labs/utils"; import classNames from "classnames"; @@ -93,8 +93,7 @@ export const WalletSelectModal: FunctionComponent = onConnect: onConnectProp, isOneClickEnabled: transaction1CTParams?.isOneClickEnabled, - onCreate1CTSession: ({ walletRepo }) => - onCreate1CTSession({ walletRepo, transaction1CTParams }), + onCreate1CTSession: () => onCreate1CTSession({ transaction1CTParams }), }); const currentCosmosWallet = rootWalletRepo?.current; @@ -152,16 +151,13 @@ export const WalletSelectModal: FunctionComponent = }; const onCreate1CTSession = async ({ - walletRepo, transaction1CTParams, }: { - walletRepo: WalletRepo; transaction1CTParams: OneClickTradingTransactionParams | undefined; }) => { create1CTSession.reset(); setIsInitializingOneClickTrading(true); return create1CTSession.mutate({ - walletRepo, transaction1CTParams, spendLimitTokenDecimals: spendLimitTokenDecimals, }); @@ -290,7 +286,6 @@ export const WalletSelectModal: FunctionComponent = walletRepo={rootWalletRepo} onCreate1CTSession={() => onCreate1CTSession({ - walletRepo: rootWalletRepo!, transaction1CTParams, }) } diff --git a/packages/web/modals/wallet-select/use-connect-wallet.ts b/packages/web/modals/wallet-select/use-connect-wallet.ts index 673e94162e..fb18cdfdaf 100644 --- a/packages/web/modals/wallet-select/use-connect-wallet.ts +++ b/packages/web/modals/wallet-select/use-connect-wallet.ts @@ -43,7 +43,7 @@ export const useConnectWallet = ({ onRequestClose?: () => void; isOneClickEnabled?: boolean; - onCreate1CTSession?: (params: { walletRepo: WalletRepo }) => Promise; + onCreate1CTSession?: () => Promise; }) => { const { accountStore, chainStore } = useStore(); @@ -144,7 +144,7 @@ export const useConnectWallet = ({ if (isOneClickEnabled && onCreate1CTSession) { try { - await onCreate1CTSession({ walletRepo }); + await onCreate1CTSession(); } catch (e) { const error = e as CreateOneClickSessionError | Error; diff --git a/packages/web/package.json b/packages/web/package.json index eaf7aea808..57cc219d90 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -113,6 +113,7 @@ "next": "^14.2.5", "next-seo": "^6.6.0", "next-sitemap": "^4.2.3", + "nomic-bitcoin": "^5.0.0-pre.0", "nuqs": "^1.15.4", "polished": "^4.3.1", "qrcode": "^1.5.3", diff --git a/packages/web/public/images/1ct-rounded-rectangle.svg b/packages/web/public/images/1ct-rounded-rectangle.svg new file mode 100644 index 0000000000..475eb0481d --- /dev/null +++ b/packages/web/public/images/1ct-rounded-rectangle.svg @@ -0,0 +1,5 @@ + + + + diff --git a/packages/web/public/networks/penumbra.svg b/packages/web/public/networks/penumbra.svg new file mode 100644 index 0000000000..b1fce4afa6 --- /dev/null +++ b/packages/web/public/networks/penumbra.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/web/server/api/routers/bridge-transfer.ts b/packages/web/server/api/routers/bridge-transfer.ts index daa3fedb77..4760009224 100644 --- a/packages/web/server/api/routers/bridge-transfer.ts +++ b/packages/web/server/api/routers/bridge-transfer.ts @@ -24,8 +24,10 @@ import { ExternalInterfaceBridgeTransferMethod } from "@osmosis-labs/types"; import { BitcoinChainInfo, EthereumChainInfo, + getnBTCMinimalDenom, isNil, isSameVariant, + PenumbraChainInfo, SolanaChainInfo, timeout, TronChainInfo, @@ -41,6 +43,7 @@ export type BridgeChainWithDisplayInfo = ( | Extract | Extract | Extract + | Extract | (Extract & { bech32Prefix: string }) | Extract ) & { @@ -115,6 +118,7 @@ export const bridgeTransferRouter = createTRPCRouter({ ? { ...quote.transferFee, ...input.fromAsset, + denom: quote.transferFee.denom ?? input.fromAsset.denom, chainId: input.fromChain.chainId, } : quote.transferFee; @@ -419,6 +423,12 @@ export const bridgeTransferRouter = createTRPCRouter({ chainType, logoUri: "/networks/tron.svg", }; + } else if (chainType === "penumbra") { + return { + ...PenumbraChainInfo, + chainType, + logoUri: "/networks/penumbra.svg", + }; } return undefined; @@ -709,7 +719,7 @@ export const bridgeTransferRouter = createTRPCRouter({ }); const btcMinimalDenom = IS_TESTNET - ? "ibc/72D483F0FD4229DBF3ACC78E648F0399C4ACADDFDBCDD9FE791FEE4443343422" + ? getnBTCMinimalDenom({ env: "testnet" }) : "factory/osmo1z6r6qdknhgsc0zeracktgpcxf43j6sekq07nw8sxduc9lg0qjjlqfu25e3/alloyed/allBTC"; const btcPrice = await getAssetPrice({ diff --git a/packages/web/server/api/routers/local-bridge-transfer.ts b/packages/web/server/api/routers/local-bridge-transfer.ts index 356362bec4..f87a5b91d9 100644 --- a/packages/web/server/api/routers/local-bridge-transfer.ts +++ b/packages/web/server/api/routers/local-bridge-transfer.ts @@ -63,6 +63,7 @@ export const localBridgeTransferRouter = createTRPCRouter({ createAssetObject("bitcoin", z.object({})), createAssetObject("solana", z.object({})), createAssetObject("tron", z.object({})), + createAssetObject("penumbra", z.object({})), ]), }) ) @@ -236,7 +237,7 @@ export const localBridgeTransferRouter = createTRPCRouter({ return assetsWithBalance; } else { - // For Bitcoin, Tron or Solana, return 0 assets as it's not supported for now + // For Bitcoin, Tron, Penumbra or Solana, return 0 assets as it's not supported for now // TODO: add 2 more else statements and send balance queries to Bitcoin, Tron or Solana as needed return input.source.assets.map((asset) => ({ diff --git a/packages/web/stores/root.ts b/packages/web/stores/root.ts index d4283e2a53..1a5c6ffe76 100644 --- a/packages/web/stores/root.ts +++ b/packages/web/stores/root.ts @@ -1,5 +1,6 @@ import { AxelarTransferStatusProvider } from "@osmosis-labs/bridge/build/axelar/transfer-status"; import { IbcTransferStatusProvider } from "@osmosis-labs/bridge/build/ibc/transfer-status"; +import { NomicTransferStatusProvider } from "@osmosis-labs/bridge/build/nomic/transfer-status"; import { SkipTransferStatusProvider } from "@osmosis-labs/bridge/build/skip/transfer-status"; import { SquidTransferStatusProvider } from "@osmosis-labs/bridge/build/squid/transfer-status"; import { @@ -257,6 +258,10 @@ export class RootStore { } ), new IbcTransferStatusProvider(ChainList, AssetLists), + new NomicTransferStatusProvider( + ChainList, + IS_TESTNET ? "testnet" : "mainnet" + ), ]; this.transferHistoryStore = new TransferHistoryStore( diff --git a/packages/web/utils/bridge.ts b/packages/web/utils/bridge.ts index f29ac91b2c..7d71ead195 100644 --- a/packages/web/utils/bridge.ts +++ b/packages/web/utils/bridge.ts @@ -9,6 +9,7 @@ export const BridgeLogoUrls: Record = { Wormhole: "/bridges/wormhole.svg", Nitro: "/bridges/nitro.svg", Picasso: "/bridges/picasso.svg", + Penumbra: "/networks/penumbra.svg", }; export const ExternalBridgeLogoUrls: Record = { @@ -21,4 +22,5 @@ export const ExternalBridgeLogoUrls: Record = { Generic: "/external-bridges/generic.svg", Nitro: "/bridges/nitro.svg", Picasso: "/bridges/picasso.svg", + Penumbra: "/networks/penumbra.svg", }; diff --git a/packages/web/utils/date.ts b/packages/web/utils/date.ts index 92e3542b0d..c0885f39ac 100644 --- a/packages/web/utils/date.ts +++ b/packages/web/utils/date.ts @@ -1,6 +1,9 @@ import dayjs from "dayjs"; -export function humanizeTime(date: dayjs.Dayjs): { +export function humanizeTime( + date: dayjs.Dayjs, + useShortTimeUnits = false +): { value: number | string; unitTranslationKey: string; } { @@ -10,7 +13,13 @@ export function humanizeTime(date: dayjs.Dayjs): { return { value: Math.max(secondsDiff, 0), unitTranslationKey: - secondsDiff === 1 ? "timeUnits.second" : "timeUnits.seconds", + secondsDiff === 1 + ? useShortTimeUnits + ? "timeUnitsShort.second" + : "timeUnits.second" + : useShortTimeUnits + ? "timeUnitsShort.seconds" + : "timeUnits.seconds", }; } @@ -19,7 +28,13 @@ export function humanizeTime(date: dayjs.Dayjs): { return { value: minutesDiff, unitTranslationKey: - minutesDiff === 1 ? "timeUnits.minute" : "timeUnits.minutes", + minutesDiff === 1 + ? useShortTimeUnits + ? "timeUnitsShort.minute" + : "timeUnits.minute" + : useShortTimeUnits + ? "timeUnitsShort.minutes" + : "timeUnits.minutes", }; } @@ -28,7 +43,13 @@ export function humanizeTime(date: dayjs.Dayjs): { return { value: hoursDiff, unitTranslationKey: - hoursDiff === 1 ? "timeUnits.hour" : "timeUnits.hours", + hoursDiff === 1 + ? useShortTimeUnits + ? "timeUnitsShort.hour" + : "timeUnits.hour" + : useShortTimeUnits + ? "timeUnitsShort.hours" + : "timeUnits.hours", }; } @@ -36,7 +57,14 @@ export function humanizeTime(date: dayjs.Dayjs): { if (daysDiff < 30) { return { value: daysDiff, - unitTranslationKey: daysDiff === 1 ? "timeUnits.day" : "timeUnits.days", + unitTranslationKey: + daysDiff === 1 + ? useShortTimeUnits + ? "timeUnitsShort.day" + : "timeUnits.day" + : useShortTimeUnits + ? "timeUnitsShort.days" + : "timeUnits.days", }; } diff --git a/packages/web/utils/trpc.ts b/packages/web/utils/trpc.ts index 0262453604..77b1146a0e 100644 --- a/packages/web/utils/trpc.ts +++ b/packages/web/utils/trpc.ts @@ -121,8 +121,9 @@ export const api = createTRPCNext({ links: [ loggerLink({ enabled: (opts) => - process.env.NODE_ENV === "development" || - (opts.direction === "down" && opts.result instanceof Error), + process.env.NEXT_PUBLIC_TRPC_LOGS !== "off" && + (process.env.NODE_ENV === "development" || + (opts.direction === "down" && opts.result instanceof Error)), }), /** * Split calls to the node server and the edge server. diff --git a/yarn.lock b/yarn.lock index 706aaff503..9b991c599f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4409,29 +4409,7 @@ buffer "^6.0.3" delay "^4.4.0" -"@keplr-wallet/common@0.12.12": - version "0.12.12" - resolved "https://registry.yarnpkg.com/@keplr-wallet/common/-/common-0.12.12.tgz#55030d985b729eac582c0d7203190e25ea2cb3ec" - integrity sha512-AxpwmXdqs083lMvA8j0/V30oTGyobsefNaCou+lP4rCyDdYuXSEux+x2+1AGL9xB3yZfN+4jvEEKJdMwHYEHcQ== - dependencies: - "@keplr-wallet/crypto" "0.12.12" - "@keplr-wallet/types" "0.12.12" - buffer "^6.0.3" - delay "^4.4.0" - mobx "^6.1.7" - -"@keplr-wallet/common@0.12.28": - version "0.12.28" - resolved "https://registry.yarnpkg.com/@keplr-wallet/common/-/common-0.12.28.tgz#1d5d985070aced31a34a6426c9ac4b775081acca" - integrity sha512-ESQorPZw8PRiUXhsrxED+E1FEWkAdc6Kwi3Az7ce204gMBQDI2j0XJtTd4uCUp+C24Em9fk0samdHzdoB4caIg== - dependencies: - "@keplr-wallet/crypto" "0.12.28" - "@keplr-wallet/types" "0.12.28" - buffer "^6.0.3" - delay "^4.4.0" - mobx "^6.1.7" - -"@keplr-wallet/cosmos@0.10.24-ibc.go.v7.hot.fix": +"@keplr-wallet/cosmos@0.10.24-ibc.go.v7.hot.fix", "@keplr-wallet/cosmos@0.12.12", "@keplr-wallet/cosmos@0.12.28": version "0.10.24-ibc.go.v7.hot.fix" resolved "https://registry.npmjs.org/@keplr-wallet/cosmos/-/cosmos-0.10.24-ibc.go.v7.hot.fix.tgz" integrity sha512-/A/wHyYo5gQIW5YkAQYZadEv/12EcAuDclO0KboIb9ti4XFJW6S4VY8LnA16R7DZyBx1cnQknyDm101fUrJfJQ== @@ -4448,40 +4426,6 @@ long "^4.0.0" protobufjs "^6.11.2" -"@keplr-wallet/cosmos@0.12.12": - version "0.12.12" - resolved "https://registry.yarnpkg.com/@keplr-wallet/cosmos/-/cosmos-0.12.12.tgz#72c0505d2327bbf2f5cb51502acaf399b88b4ae3" - integrity sha512-9TLsefUIAuDqqf1WHBt9Bk29rPlkezmLM8P1eEsXGUaHBfuqUrO+RwL3eLA3HGcgNvdy9s8e0p/4CMInH/LLLQ== - dependencies: - "@ethersproject/address" "^5.6.0" - "@keplr-wallet/common" "0.12.12" - "@keplr-wallet/crypto" "0.12.12" - "@keplr-wallet/proto-types" "0.12.12" - "@keplr-wallet/simple-fetch" "0.12.12" - "@keplr-wallet/types" "0.12.12" - "@keplr-wallet/unit" "0.12.12" - bech32 "^1.1.4" - buffer "^6.0.3" - long "^4.0.0" - protobufjs "^6.11.2" - -"@keplr-wallet/cosmos@0.12.28": - version "0.12.28" - resolved "https://registry.yarnpkg.com/@keplr-wallet/cosmos/-/cosmos-0.12.28.tgz#d56e73468256e7276a66bb41f145449dbf11efa1" - integrity sha512-IuqmSBgKgIeWBA0XGQKKs28IXFeFMCrfadCbtiZccNc7qnNr5Y/Cyyk01BPC8Dd1ZyEyAByoICgrxvtGN0GGvA== - dependencies: - "@ethersproject/address" "^5.6.0" - "@keplr-wallet/common" "0.12.28" - "@keplr-wallet/crypto" "0.12.28" - "@keplr-wallet/proto-types" "0.12.28" - "@keplr-wallet/simple-fetch" "0.12.28" - "@keplr-wallet/types" "0.12.28" - "@keplr-wallet/unit" "0.12.28" - bech32 "^1.1.4" - buffer "^6.0.3" - long "^4.0.0" - protobufjs "^6.11.2" - "@keplr-wallet/crypto@0.10.24-ibc.go.v7.hot.fix": version "0.10.24-ibc.go.v7.hot.fix" resolved "https://registry.npmjs.org/@keplr-wallet/crypto/-/crypto-0.10.24-ibc.go.v7.hot.fix.tgz" @@ -4554,7 +4498,7 @@ resolved "https://registry.npmjs.org/@keplr-wallet/popup/-/popup-0.10.24-ibc.go.v7.hot.fix.tgz" integrity sha512-Q/teyV6vdmpH3SySGd1xrNc/mVGK/tCP5vFEG2I3Y4FDCSV1yD7vcVgUy+tN19Z8EM3goR57V2QlarSOidtdjQ== -"@keplr-wallet/proto-types@0.10.24-ibc.go.v7.hot.fix": +"@keplr-wallet/proto-types@0.10.24-ibc.go.v7.hot.fix", "@keplr-wallet/proto-types@0.12.12": version "0.10.24-ibc.go.v7.hot.fix" resolved "https://registry.npmjs.org/@keplr-wallet/proto-types/-/proto-types-0.10.24-ibc.go.v7.hot.fix.tgz" integrity sha512-fLUJEtDadYJIMBzhMSZpEDTvXqk8wW68TwnUCRAcAooEQEtXPwY5gfo3hcekQEiCYtIu8XqzJ9fg01rp2Z4d3w== @@ -4562,22 +4506,6 @@ long "^4.0.0" protobufjs "^6.11.2" -"@keplr-wallet/proto-types@0.12.12": - version "0.12.12" - resolved "https://registry.yarnpkg.com/@keplr-wallet/proto-types/-/proto-types-0.12.12.tgz#24e0530af7604a90f33a397a82fe500865c76154" - integrity sha512-iAqqNlJpxu/8j+SwOXEH2ymM4W0anfxn+eNeWuqz2c/0JxGTWeLURioxQmCtewtllfHdDHHcoQ7/S+NmXiaEgQ== - dependencies: - long "^4.0.0" - protobufjs "^6.11.2" - -"@keplr-wallet/proto-types@0.12.28": - version "0.12.28" - resolved "https://registry.yarnpkg.com/@keplr-wallet/proto-types/-/proto-types-0.12.28.tgz#2fb2c37749ce7db974f01d07387e966c9b99027d" - integrity sha512-ukti/eCTltPUP64jxtk5TjtwJogyfKPqlBIT3KGUCGzBLIPeYMsffL5w5aoHsMjINzOITjYqzXyEF8LTIK/fmw== - dependencies: - long "^4.0.0" - protobufjs "^6.11.2" - "@keplr-wallet/provider-extension@^0.12.95": version "0.12.107" resolved "https://registry.yarnpkg.com/@keplr-wallet/provider-extension/-/provider-extension-0.12.107.tgz#98a0fb42cb0c54d4e681e60e6b1145429a6e3e23" @@ -4653,32 +4581,12 @@ deepmerge "^4.2.2" long "^4.0.0" -"@keplr-wallet/router@0.10.24-ibc.go.v7.hot.fix": +"@keplr-wallet/router@0.10.24-ibc.go.v7.hot.fix", "@keplr-wallet/router@0.12.12", "@keplr-wallet/router@0.12.96": version "0.10.24-ibc.go.v7.hot.fix" resolved "https://registry.npmjs.org/@keplr-wallet/router/-/router-0.10.24-ibc.go.v7.hot.fix.tgz" integrity sha512-bt9weexlbhlh8KsOvbDrvHJ8jtUXrXgB2LX+hEAwjclHQt7PMUhx9a5z0Obd19/ive5G/1M7/ccdPIWxRBpKQw== -"@keplr-wallet/router@0.12.12": - version "0.12.12" - resolved "https://registry.yarnpkg.com/@keplr-wallet/router/-/router-0.12.12.tgz#92a2c006aec6945ed313575af6b0801f8e84e315" - integrity sha512-Aa1TiVRIEPaqs1t27nCNs5Kz6Ty4CLarVdfqcRWlFQL6zFq33GT46s6K9U4Lz2swVCwdmerSXaq308K/GJHTlw== - -"@keplr-wallet/router@0.12.96": - version "0.12.96" - resolved "https://registry.yarnpkg.com/@keplr-wallet/router/-/router-0.12.96.tgz#6a20ed2c90ba3ed4f3fc43ed7513f72d7055482d" - integrity sha512-O8izj032ZKQIoTus96BFqem+w6NpYHU3j6NEnSaQBh6Zncj9fgjoOVs0CKK+jsuLYUsOHx2t86BxMSKESsR0Ug== - -"@keplr-wallet/simple-fetch@0.12.12": - version "0.12.12" - resolved "https://registry.yarnpkg.com/@keplr-wallet/simple-fetch/-/simple-fetch-0.12.12.tgz#aacc5c3f22b7ab2804b39e864725294a32f858fd" - integrity sha512-lCOsaI8upMpbusfwJqEK8VIEX77+QE8+8MJVRqoCYwjOTqKGdUH7D1ieZWh+pzvzOnVgedM3lxqdmCvdgU91qw== - -"@keplr-wallet/simple-fetch@0.12.28": - version "0.12.28" - resolved "https://registry.yarnpkg.com/@keplr-wallet/simple-fetch/-/simple-fetch-0.12.28.tgz#44225df5b329c823076280df1ec9930a21b1373e" - integrity sha512-T2CiKS2B5n0ZA7CWw0CA6qIAH0XYI1siE50MP+i+V0ZniCGBeL+BMcDw64vFJUcEH+1L5X4sDAzV37fQxGwllA== - -"@keplr-wallet/types@0.10.24-ibc.go.v7.hot.fix": +"@keplr-wallet/types@0.10.24-ibc.go.v7.hot.fix", "@keplr-wallet/types@0.12.107", "@keplr-wallet/types@0.12.12", "@keplr-wallet/types@0.12.96", "@keplr-wallet/types@^0.12.95": version "0.10.24-ibc.go.v7.hot.fix" resolved "https://registry.npmjs.org/@keplr-wallet/types/-/types-0.10.24-ibc.go.v7.hot.fix.tgz" integrity sha512-3KUjDMUCscYkvKnC+JsJh9+X0NHlsvBgAghP/uy2p5OGtiULqPBAjWiO+hnBbhis3ZEkzGcCROnnBOoccKd3CQ== @@ -4689,41 +4597,6 @@ long "^4.0.0" secretjs "^0.17.0" -"@keplr-wallet/types@0.12.107": - version "0.12.107" - resolved "https://registry.yarnpkg.com/@keplr-wallet/types/-/types-0.12.107.tgz#8d6726d86e17a79131b4b6f4f114052d6384aa58" - integrity sha512-jBpjJO+nNL8cgsJLjZYoq84n+7nXHDdztTgRMVnnomFb+Vy0FVIEI8VUl89ImmHDUImDd0562ywsvA496/0yCA== - dependencies: - long "^4.0.0" - -"@keplr-wallet/types@0.12.12": - version "0.12.12" - resolved "https://registry.yarnpkg.com/@keplr-wallet/types/-/types-0.12.12.tgz#f4bd9e710d5e53504f6b53330abb45bedd9c20ae" - integrity sha512-fo6b8j9EXnJukGvZorifJWEm1BPIrvaTLuu5PqaU5k1ANDasm/FL1NaUuaTBVvhRjINtvVXqYpW/rVUinA9MBA== - dependencies: - long "^4.0.0" - -"@keplr-wallet/types@0.12.28": - version "0.12.28" - resolved "https://registry.yarnpkg.com/@keplr-wallet/types/-/types-0.12.28.tgz#eac3c2c9d4560856c5c403a87e67925992a04fbf" - integrity sha512-EcM9d46hYDm3AO4lf4GUbTSLRySONtTmhKb7p88q56OQOgJN3MMjRacEo2p9jX9gpPe7gRIjMUalhAfUiFpZoQ== - dependencies: - long "^4.0.0" - -"@keplr-wallet/types@0.12.96": - version "0.12.96" - resolved "https://registry.yarnpkg.com/@keplr-wallet/types/-/types-0.12.96.tgz#a7735051b1f7cbcdf9b8c29010b1c3c45d195c19" - integrity sha512-tr4tPjMrJCsfRXXhhmqnpb9DqH9auJp3uuj8SvDB3pQTTaYJNxkdonLv1tYmXZZ6J9oWtk9WVEDTVgBQN/wisw== - dependencies: - long "^4.0.0" - -"@keplr-wallet/types@^0.12.95": - version "0.12.156" - resolved "https://registry.yarnpkg.com/@keplr-wallet/types/-/types-0.12.156.tgz#5e9346c12065a21394fa45112ad1b7a072e0f3f3" - integrity sha512-Z/Lf6VEsl/Am3birKE8ZEVZj/x5YGSoTdFMDtq/EfcB+hcJ/ogoiZTVEBweAig/2zcu7MsZvFTVMEXu5+y3e4A== - dependencies: - long "^4.0.0" - "@keplr-wallet/unit@0.10.24-ibc.go.v7.hot.fix": version "0.10.24-ibc.go.v7.hot.fix" resolved "https://registry.npmjs.org/@keplr-wallet/unit/-/unit-0.10.24-ibc.go.v7.hot.fix.tgz" @@ -4733,24 +4606,6 @@ big-integer "^1.6.48" utility-types "^3.10.0" -"@keplr-wallet/unit@0.12.12": - version "0.12.12" - resolved "https://registry.yarnpkg.com/@keplr-wallet/unit/-/unit-0.12.12.tgz#2d7f2e38df4e09c8123dcc0784ffc4b5f4166217" - integrity sha512-fayJcfXWKUnbDZiRJHyuA9GMVS9DymjRlCzlpAJ0+xV0c4Kun/f+9FajL9OQAdPPhnJ7A3KevMI4VHZsd9Yw+A== - dependencies: - "@keplr-wallet/types" "0.12.12" - big-integer "^1.6.48" - utility-types "^3.10.0" - -"@keplr-wallet/unit@0.12.28": - version "0.12.28" - resolved "https://registry.yarnpkg.com/@keplr-wallet/unit/-/unit-0.12.28.tgz#907c7fa0b49a729cda207fca14fc0a38871cc6c4" - integrity sha512-kpXigHDBJGOmhtPkv9hqsQid9zkFo7OQPeKgO2n8GUlOINIXW6kWG5LXYTi/Yg9Uiw1CQF69gFMuZCJ8IzVHlA== - dependencies: - "@keplr-wallet/types" "0.12.28" - big-integer "^1.6.48" - utility-types "^3.10.0" - "@keplr-wallet/wc-client@^0.12.95": version "0.12.96" resolved "https://registry.yarnpkg.com/@keplr-wallet/wc-client/-/wc-client-0.12.96.tgz#a56995172dcdc73d32b24d5a704a954062befc2a"