diff --git a/wallets/core/src/legacy/wallet.ts b/wallets/core/src/legacy/wallet.ts index 62a11e72a5..0a8ddf89a6 100644 --- a/wallets/core/src/legacy/wallet.ts +++ b/wallets/core/src/legacy/wallet.ts @@ -26,11 +26,16 @@ export type EventHandler = ( export type EventInfo = { supportedBlockchains: BlockchainMeta[]; isContractWallet: boolean; + // This is for Hub and be able to make it compatible with legacy behavior. + isHub: boolean; }; export interface State { connected: boolean; connecting: boolean; + /** + * @depreacted it always returns `false`. don't use it. + */ reachable: boolean; installed: boolean; accounts: string[] | null; @@ -57,6 +62,7 @@ class Wallet { this.info = { supportedBlockchains: [], isContractWallet: false, + isHub: false, }; this.state = { connected: false, @@ -408,6 +414,7 @@ class Wallet { const eventInfo: EventInfo = { supportedBlockchains: this.info.supportedBlockchains, isContractWallet: this.info.isContractWallet, + isHub: false, }; this.options.handler( this.options.config.type, diff --git a/wallets/provider-all/src/index.ts b/wallets/provider-all/src/index.ts index 4e4e1ea99e..cf4f73ea45 100644 --- a/wallets/provider-all/src/index.ts +++ b/wallets/provider-all/src/index.ts @@ -35,6 +35,10 @@ import * as tronLink from '@rango-dev/provider-tron-link'; import * as trustwallet from '@rango-dev/provider-trustwallet'; import * as walletconnect2 from '@rango-dev/provider-walletconnect-2'; import * as xdefi from '@rango-dev/provider-xdefi'; +import { + legacyProviderImportsToVersionsInterface, + type Versions, +} from '@rango-dev/wallets-core/utils'; import { type WalletType, WalletTypes } from '@rango-dev/wallets-shared'; import { isWalletExcluded } from './helpers.js'; @@ -45,7 +49,7 @@ interface Options { trezor?: TrezorEnvironments; } -export const allProviders = (options?: Options) => { +export const allProviders = (options?: Options): Versions[] => { const providers = options?.selectedProviders || []; if ( @@ -75,38 +79,38 @@ export const allProviders = (options?: Options) => { } return [ - safe, - defaultInjected, - metamask, - solflareSnap, - walletconnect2, - keplr, - phantom, - argentx, - tronLink, - trustwallet, - bitget, - enkrypt, - xdefi, - clover, - safepal, - brave, - coin98, - coinbase, - cosmostation, - exodus, - mathwallet, - okx, - tokenpocket, - tomo, - halo, - leapCosmos, - frontier, - taho, - braavos, - ledger, - rabby, - trezor, - solflare, + legacyProviderImportsToVersionsInterface(safe), + legacyProviderImportsToVersionsInterface(defaultInjected), + legacyProviderImportsToVersionsInterface(metamask), + legacyProviderImportsToVersionsInterface(solflareSnap), + legacyProviderImportsToVersionsInterface(walletconnect2), + legacyProviderImportsToVersionsInterface(keplr), + legacyProviderImportsToVersionsInterface(phantom), + legacyProviderImportsToVersionsInterface(argentx), + legacyProviderImportsToVersionsInterface(tronLink), + legacyProviderImportsToVersionsInterface(trustwallet), + legacyProviderImportsToVersionsInterface(bitget), + legacyProviderImportsToVersionsInterface(enkrypt), + legacyProviderImportsToVersionsInterface(xdefi), + legacyProviderImportsToVersionsInterface(clover), + legacyProviderImportsToVersionsInterface(safepal), + legacyProviderImportsToVersionsInterface(brave), + legacyProviderImportsToVersionsInterface(coin98), + legacyProviderImportsToVersionsInterface(coinbase), + legacyProviderImportsToVersionsInterface(cosmostation), + legacyProviderImportsToVersionsInterface(exodus), + legacyProviderImportsToVersionsInterface(mathwallet), + legacyProviderImportsToVersionsInterface(okx), + legacyProviderImportsToVersionsInterface(tokenpocket), + legacyProviderImportsToVersionsInterface(tomo), + legacyProviderImportsToVersionsInterface(halo), + legacyProviderImportsToVersionsInterface(leapCosmos), + legacyProviderImportsToVersionsInterface(frontier), + legacyProviderImportsToVersionsInterface(taho), + legacyProviderImportsToVersionsInterface(braavos), + legacyProviderImportsToVersionsInterface(ledger), + legacyProviderImportsToVersionsInterface(rabby), + legacyProviderImportsToVersionsInterface(trezor), + legacyProviderImportsToVersionsInterface(solflare), ]; }; diff --git a/widget/embedded/src/QueueManager.tsx b/widget/embedded/src/QueueManager.tsx index fd3e1f7d87..7ca0ff2ac7 100644 --- a/widget/embedded/src/QueueManager.tsx +++ b/widget/embedded/src/QueueManager.tsx @@ -53,7 +53,12 @@ function QueueManager(props: PropsWithChildren<{ apiKey?: string }>) { if (!canSwitchNetworkTo(wallet, network)) { return undefined; } - return connect(wallet, network); + return connect(wallet, [ + { + namespace: 'DISCOVER_MODE', + network, + }, + ]); }; const isMobileWallet = (walletType: WalletType): boolean => diff --git a/widget/embedded/src/components/SwapDetails/SwapDetails.tsx b/widget/embedded/src/components/SwapDetails/SwapDetails.tsx index 8241e899b9..d4be5f0469 100644 --- a/widget/embedded/src/components/SwapDetails/SwapDetails.tsx +++ b/widget/embedded/src/components/SwapDetails/SwapDetails.tsx @@ -160,7 +160,12 @@ export function SwapDetails(props: SwapDetailsProps) { canSwitchNetworkTo(currentStepWallet.walletType, currentStepBlockchain)); const switchNetwork = showSwitchNetwork - ? connect.bind(null, currentStepWallet.walletType, currentStepBlockchain) + ? connect.bind(null, currentStepWallet.walletType, [ + { + namespace: 'DISCOVER_MODE', + network: currentStepBlockchain, + }, + ]) : undefined; const stepMessage = getSwapMessages(swap, currentStep); diff --git a/widget/embedded/src/components/SwapDetailsAlerts/SwapDetailsAlerts.types.ts b/widget/embedded/src/components/SwapDetailsAlerts/SwapDetailsAlerts.types.ts index 2aba3044ca..32f3b3cc8e 100644 --- a/widget/embedded/src/components/SwapDetailsAlerts/SwapDetailsAlerts.types.ts +++ b/widget/embedded/src/components/SwapDetailsAlerts/SwapDetailsAlerts.types.ts @@ -15,7 +15,7 @@ export interface SwapAlertsProps extends WaningAlertsProps { } export interface WaningAlertsProps extends FailedAlertsProps { - switchNetwork: (() => Promise) | undefined; + switchNetwork: (() => Promise) | undefined; showNetworkModal: PendingSwapNetworkStatus | null | undefined; setNetworkModal: (network: ModalState) => void; } diff --git a/widget/embedded/src/containers/Wallets/Wallets.tsx b/widget/embedded/src/containers/Wallets/Wallets.tsx index bbf19aa42b..f00f345fad 100644 --- a/widget/embedded/src/containers/Wallets/Wallets.tsx +++ b/widget/embedded/src/containers/Wallets/Wallets.tsx @@ -47,6 +47,7 @@ function Main(props: PropsWithChildren) { walletConnectListedDesktopWalletLink: props.config.__UNSTABLE_OR_INTERNAL__ ?.walletConnectListedDesktopWalletLink, + experimentalWallet: props.config.features?.experimentalWallet, }; const { providers } = useWalletProviders(config.wallets, walletOptions); const { connectWallet, disconnectWallet } = useWalletsStore(); @@ -85,6 +86,7 @@ function Main(props: PropsWithChildren) { meta.isContractWallet ); if (data.length) { + console.log('EventHandler', { data }); connectWallet(data, findToken); } } else { @@ -98,17 +100,17 @@ function Main(props: PropsWithChildren) { } } } - if (event === Events.ACCOUNTS && state.connected) { + if ( + (event === Events.ACCOUNTS && state.connected) || + // Hub works differently, and this check should be enough. + (event === Events.ACCOUNTS && meta.isHub) + ) { const key = `${type}-${state.network}-${value}`; - if (state.connected) { - if (!!onConnectWalletHandler.current) { - onConnectWalletHandler.current(key); - } else { - console.warn( - `onConnectWallet handler hasn't been set. Are you sure?` - ); - } + if (!!onConnectWalletHandler.current) { + onConnectWalletHandler.current(key); + } else { + console.warn(`onConnectWallet handler hasn't been set. Are you sure?`); } } @@ -146,7 +148,13 @@ function Main(props: PropsWithChildren) { allBlockChains={blockchains} providers={providers} onUpdateState={onUpdateState} - autoConnect={!!isActiveTab}> + autoConnect={!!isActiveTab} + configs={{ + isExperimentalEnabled: + props.config.features?.experimentalWallet === 'enabled' + ? true + : false, + }}> {props.children} diff --git a/widget/embedded/src/hooks/useStatefulConnect/useStatefulConnect.ts b/widget/embedded/src/hooks/useStatefulConnect/useStatefulConnect.ts index e81e54db7d..84a4494317 100644 --- a/widget/embedded/src/hooks/useStatefulConnect/useStatefulConnect.ts +++ b/widget/embedded/src/hooks/useStatefulConnect/useStatefulConnect.ts @@ -1,5 +1,6 @@ import type { HandleConnectOptions, Result } from './useStatefulConnect.types'; import type { WalletInfoWithExtra } from '../../types'; +import type { ExtendedModalWalletInfo } from '../../utils/wallets'; import type { Namespace, NamespaceData, @@ -75,7 +76,7 @@ export function useStatefulConnect(): UseStatefulConnect { }; const handleConnect = async ( - wallet: WalletInfoWithExtra, + wallet: ExtendedModalWalletInfo, options?: HandleConnectOptions ): Promise<{ status: ResultStatus; @@ -83,6 +84,23 @@ export function useStatefulConnect(): UseStatefulConnect { const isDisconnected = wallet.state === WalletState.DISCONNECTED; if (isDisconnected) { + const detachedInstances = wallet.properties?.find( + (item) => item.name === 'detached' + ); + const hubCondition = detachedInstances && wallet.state !== 'connected'; + if (hubCondition) { + dispatch({ + type: 'needsNamespace', + payload: { + providerType: wallet.type, + providerImage: wallet.image, + availableNamespaces: wallet.namespaces, + singleNamespace: wallet.singleNamespace, + }, + }); + return { status: ResultStatus.Namespace }; + } + if (!wallet.namespaces) { return await runConnect(wallet.type, undefined, options); } diff --git a/widget/embedded/src/types/config.ts b/widget/embedded/src/types/config.ts index 202e9b83f3..1e86ab4955 100644 --- a/widget/embedded/src/types/config.ts +++ b/widget/embedded/src/types/config.ts @@ -1,5 +1,5 @@ import type { Language, theme } from '@rango-dev/ui'; -import type { ProviderInterface } from '@rango-dev/wallets-react'; +import type { LegacyProviderInterface } from '@rango-dev/wallets-core/dist/legacy/mod'; import type { WalletType } from '@rango-dev/wallets-shared'; import type { Asset } from 'rango-sdk'; @@ -132,6 +132,9 @@ export type SignersConfig = { * * @property {'visible' | 'hidden'} [liquiditySource] * - The visibility state for the liquiditySource feature. Optional property. + * + * @property {'disabled' | 'enabled'} [experimentalWallet] + * - Enable our experimental version of wallets. Default: disable on production, enabled on dev. */ export type Features = Partial< Record< @@ -143,7 +146,8 @@ export type Features = Partial< 'visible' | 'hidden' > > & - Partial>; + Partial> & + Partial>; export type TrezorManifest = { appUrl: string; @@ -218,7 +222,7 @@ export type WidgetConfig = { from?: BlockchainAndTokenConfig; to?: BlockchainAndTokenConfig; liquiditySources?: string[]; - wallets?: (WalletType | ProviderInterface)[]; + wallets?: (WalletType | LegacyProviderInterface)[]; multiWallets?: boolean; customDestination?: boolean; defaultCustomDestinations?: { [blockchain: string]: string }; diff --git a/widget/embedded/src/types/wallets.ts b/widget/embedded/src/types/wallets.ts index b15dedcb2b..dfce07acdb 100644 --- a/widget/embedded/src/types/wallets.ts +++ b/widget/embedded/src/types/wallets.ts @@ -29,3 +29,8 @@ export type WalletInfoWithExtra = WalletInfo & { singleNamespace?: boolean; needsDerivationPath?: boolean; }; + +export type WithNamespacesInfo = { + namespaces?: Namespace[]; + singleNamespace?: boolean; +}; diff --git a/widget/embedded/src/utils/providers.ts b/widget/embedded/src/utils/providers.ts index 03484a1ff4..6ab6437167 100644 --- a/widget/embedded/src/utils/providers.ts +++ b/widget/embedded/src/utils/providers.ts @@ -1,7 +1,13 @@ import type { WidgetConfig } from '../types'; -import type { ProviderInterface } from '@rango-dev/wallets-react'; +import type { LegacyProviderInterface } from '@rango-dev/wallets-core/dist/legacy/mod'; import { allProviders } from '@rango-dev/provider-all'; +import { + defineVersions, + pickVersion, + Provider, + type Versions, +} from '@rango-dev/wallets-core'; export interface ProvidersOptions { walletConnectProjectId?: WidgetConfig['walletConnectProjectId']; @@ -9,32 +15,33 @@ export interface ProvidersOptions { WidgetConfig['__UNSTABLE_OR_INTERNAL__'] >['walletConnectListedDesktopWalletLink']; trezorManifest: WidgetConfig['trezorManifest']; + experimentalWallet?: 'enabled' | 'disabled'; } /** * * Generate a list of providers by passing a provider name (e.g. metamask) or a custom provider which implemented ProviderInterface. - * @returns ProviderInterface[] a list of ProviderInterface + * @returns BothProvidersInterface[] a list of BothProvidersInterface * */ +type BothProvidersInterface = LegacyProviderInterface | Provider; export function matchAndGenerateProviders( providers: WidgetConfig['wallets'], options?: ProvidersOptions -): ProviderInterface[] { - const all = allProviders({ +): Versions[] { + const envs = { walletconnect2: { WC_PROJECT_ID: options?.walletConnectProjectId || '', DISABLE_MODAL_AND_OPEN_LINK: options?.walletConnectListedDesktopWalletLink, }, selectedProviders: providers, - trezor: options?.trezorManifest - ? { manifest: options.trezorManifest } - : undefined, - }); + }; + + const all = allProviders(envs); if (providers) { - const selectedProviders: ProviderInterface[] = []; + const selectedProviders: Versions[] = []; providers.forEach((requestedProvider) => { /* @@ -43,11 +50,25 @@ export function matchAndGenerateProviders( * The second way is passing a custom provider which implemented ProviderInterface. */ if (typeof requestedProvider === 'string') { - const result: ProviderInterface | undefined = all.find((provider) => { - return provider.config.type === requestedProvider; - }); + const result: BothProvidersInterface | undefined = + pickVersionWithFallbackToLegacy(all, options).find((provider) => { + if (provider instanceof Provider) { + return provider.id === requestedProvider; + } + return provider.config.type === requestedProvider; + }); + + // TODO: refactor these conditions. if (result) { - selectedProviders.push(result); + if (result instanceof Provider) { + selectedProviders.push( + defineVersions().version('1.0.0', result).build() + ); + } else { + selectedProviders.push( + defineVersions().version('0.0.0', result).build() + ); + } } else { console.warn( `Couldn't find ${requestedProvider} provider. Please make sure you are passing the correct name.` @@ -55,21 +76,54 @@ export function matchAndGenerateProviders( } } else { // It's a custom provider so we directly push it to the list. - selectedProviders.push(requestedProvider); + if (requestedProvider instanceof Provider) { + selectedProviders.push( + defineVersions().version('1.0.0', requestedProvider).build() + ); + } else { + selectedProviders.push( + defineVersions().version('0.0.0', requestedProvider).build() + ); + } } }); + return selectedProviders; } return all; } +// TODO: this is a duplication with what we do in core. +function pickVersionWithFallbackToLegacy( + p: Versions[], + options?: ProvidersOptions +): BothProvidersInterface[] { + const { experimentalWallet = 'disabled' } = options || {}; + + return p.map((provider) => { + const version = experimentalWallet == 'disabled' ? '0.0.0' : '1.0.0'; + try { + return pickVersion(provider, version)[1]; + } catch { + // Fallback to legacy version, if target version doesn't exists. + return pickVersion(provider, '0.0.0')[1]; + } + }); +} + export function configWalletsToWalletName( config: WidgetConfig['wallets'], options?: ProvidersOptions ): string[] { - const providers = matchAndGenerateProviders(config, options); + const providers = pickVersionWithFallbackToLegacy( + matchAndGenerateProviders(config, options), + options + ); const names = providers.map((provider) => { + if (provider instanceof Provider) { + return provider.id; + } return provider.config.type; }); return names; diff --git a/widget/embedded/src/utils/wallets.ts b/widget/embedded/src/utils/wallets.ts index adf4497e7d..67e64a3bea 100644 --- a/widget/embedded/src/utils/wallets.ts +++ b/widget/embedded/src/utils/wallets.ts @@ -5,12 +5,13 @@ import type { SelectedQuote, TokensBalance, Wallet, - WalletInfoWithExtra, + WithNamespacesInfo, } from '../types'; import type { WalletInfo as ModalWalletInfo } from '@rango-dev/ui'; +import type { ProviderInfo } from '@rango-dev/wallets-core'; +import type { ExtendedWalletInfo } from '@rango-dev/wallets-react'; import type { Network, - WalletInfo, WalletState, WalletType, WalletTypes, @@ -50,6 +51,11 @@ import { isBlockchainTypeInCategory, removeDuplicateFrom } from './common'; import { createTokenHash } from './meta'; import { numberToString } from './numbers'; +export type ExtendedModalWalletInfo = ModalWalletInfo & + WithNamespacesInfo & { + properties?: ProviderInfo['properties']; + }; + export function mapStatusToWalletState(state: WalletState): WalletStatus { switch (true) { case state.connected: @@ -65,10 +71,10 @@ export function mapStatusToWalletState(state: WalletState): WalletStatus { export function mapWalletTypesToWalletInfo( getState: (type: WalletType) => WalletState, - getWalletInfo: (type: WalletType) => WalletInfo, + getWalletInfo: (type: WalletType) => ExtendedWalletInfo, list: WalletType[], chain?: string -): WalletInfoWithExtra[] { +): ExtendedModalWalletInfo[] { return list .filter((wallet) => !EXCLUDED_WALLETS.includes(wallet as WalletTypes)) .filter((wallet) => { @@ -97,6 +103,7 @@ export function mapWalletTypesToWalletInfo( singleNamespace, supportedChains, needsDerivationPath, + properties, } = getWalletInfo(type); const blockchainTypes = removeDuplicateFrom( supportedChains.map((item) => item.type) @@ -114,6 +121,7 @@ export function mapWalletTypesToWalletInfo( singleNamespace, blockchainTypes, needsDerivationPath, + properties, }; }); } @@ -436,8 +444,8 @@ export function areTokensEqual( } export function sortWalletsBasedOnConnectionState( - wallets: WalletInfoWithExtra[] -): WalletInfoWithExtra[] { + wallets: ExtendedModalWalletInfo[] +): ExtendedModalWalletInfo[] { return wallets.sort( (a, b) => Number(b.state === WalletStatus.CONNECTED) - @@ -551,7 +559,7 @@ export function filterBlockchainsByWalletTypes( } export function filterWalletsByCategory( - wallets: WalletInfoWithExtra[], + wallets: ExtendedModalWalletInfo[], category: string ) { if (category === BlockchainCategories.ALL) {