diff --git a/.changeset/silver-rockets-brush.md b/.changeset/silver-rockets-brush.md new file mode 100644 index 0000000000..aef1633621 --- /dev/null +++ b/.changeset/silver-rockets-brush.md @@ -0,0 +1,22 @@ +--- +'@reown/appkit-scaffold-ui': patch +'@reown/appkit': patch +'@reown/appkit-core': patch +'@reown/appkit-adapter-ethers': patch +'@reown/appkit-adapter-ethers5': patch +'@reown/appkit-adapter-solana': patch +'@reown/appkit-adapter-wagmi': patch +'@reown/appkit-utils': patch +'@reown/appkit-cdn': patch +'@reown/appkit-cli': patch +'@reown/appkit-common': patch +'@reown/appkit-experimental': patch +'@reown/appkit-polyfills': patch +'@reown/appkit-siwe': patch +'@reown/appkit-siwx': patch +'@reown/appkit-ui': patch +'@reown/appkit-wallet': patch +'@reown/appkit-wallet-button': patch +--- + +Expose a public function to get connect method order with AppKit instance diff --git a/apps/builder/components/configuration-sections/section-connect-options.tsx b/apps/builder/components/configuration-sections/section-connect-options.tsx index 3ca813e536..ef75048d03 100644 --- a/apps/builder/components/configuration-sections/section-connect-options.tsx +++ b/apps/builder/components/configuration-sections/section-connect-options.tsx @@ -27,8 +27,7 @@ const SortableConnectMethodList = dynamic( export function SectionConnectOptions() { const { config, updateFeatures, updateSocials, updateEnableWallets } = useAppKitContext() const collapseWallets = config.features.collapseWallets - const connectMethodsOrder = - config.features.connectMethodsOrder || ConstantsUtil.DEFAULT_FEATURES.connectMethodsOrder + const connectMethodsOrder = config.features.connectMethodsOrder function toggleCollapseWallets() { updateFeatures({ collapseWallets: !collapseWallets }) diff --git a/apps/builder/components/configuration-sections/section-wallet-features.tsx b/apps/builder/components/configuration-sections/section-wallet-features.tsx index ef7f149dfe..7d820e7277 100644 --- a/apps/builder/components/configuration-sections/section-wallet-features.tsx +++ b/apps/builder/components/configuration-sections/section-wallet-features.tsx @@ -9,7 +9,7 @@ const defaultWalletFeaturesOrder = ['onramp', 'swaps', 'receive', 'send'] export function SectionWalletFeatures() { const { config, updateFeatures } = useAppKitContext() - const connectMethodsOrder = config.features.walletFeaturesOrder || defaultWalletFeaturesOrder + const walletFeaturesOrder = config.features.walletFeaturesOrder || defaultWalletFeaturesOrder function handleNewOrder(items: UniqueIdentifier[]) { const titleValueMap = { @@ -33,15 +33,12 @@ export function SectionWalletFeatures() { case 'Swap': updateFeatures({ swaps: !config.features.swaps }) return - case 'Receive': - case 'Send': - return default: return } } - const featureNameMap = connectMethodsOrder.map(name => { + const featureNameMap = walletFeaturesOrder.map(name => { switch (name) { case 'onramp': return 'Buy' diff --git a/apps/builder/components/preview-content.tsx b/apps/builder/components/preview-content.tsx index aa71aa0ea8..cafe26cc5c 100644 --- a/apps/builder/components/preview-content.tsx +++ b/apps/builder/components/preview-content.tsx @@ -4,9 +4,14 @@ import { Button } from '@/components/ui/button' import { toast } from 'sonner' import { Link1Icon, ResetIcon } from '@radix-ui/react-icons' import { useAppKitContext } from '@/hooks/use-appkit' +import { useAppKitState } from '@reown/appkit/react' +import { useEffect } from 'react' +import { useState } from 'react' export function PreviewContent() { - const { isInitialized, resetConfigs } = useAppKitContext() + const [shouldRender, setShouldRender] = useState(false) + const { initialized } = useAppKitState() + const { resetConfigs } = useAppKitContext() async function handleShare() { try { @@ -17,10 +22,18 @@ export function PreviewContent() { } } + useEffect(() => { + setShouldRender(initialized) + }, [initialized]) + + if (!shouldRender) { + return null + } + return ( <> -
- {isInitialized ? ( +
+ {shouldRender ? ( <> {/* @ts-ignore */} ({}), getNewIndex, handle = false, - itemCount = 3, items: initialItems, measuring, modifiers, @@ -109,9 +105,7 @@ export function SortableConnectMethodList({ onToggleOption, handleNewOrder }: Props) { - const [items, setItems] = useState( - () => initialItems ?? createRange(itemCount, index => index) - ) + const [items, setItems] = useState(() => initialItems ?? []) const [activeId, setActiveId] = useState(null) const sensors = useSensors( useSensor(MouseSensor, { @@ -136,13 +130,18 @@ export function SortableConnectMethodList({ }, [activeId]) const orderString = useMemo(() => items.join(','), [items]) + const orderFromPropsString = useMemo(() => initialItems?.join(','), [initialItems]) useEffect(() => { - if (handleNewOrder) { + if (handleNewOrder && orderString) { handleNewOrder(orderString.split(',')) } }, [orderString]) + useEffect(() => { + setItems(orderFromPropsString ? orderFromPropsString.split(',') : []) + }, [orderFromPropsString]) + return ( items.join(','), [items]) + const orderStringFromProps = useMemo(() => initialItems?.join(','), [initialItems]) useEffect(() => { if (handleNewOrder) { @@ -144,6 +145,12 @@ export function SortableWalletFeatureList({ } }, [orderString]) + useEffect(() => { + if (orderStringFromProps) { + setItems(orderStringFromProps.split(',')) + } + }, [orderStringFromProps]) + return ( - isInitialized: boolean updateThemeMode: (mode: ThemeMode) => void updateFeatures: (features: Partial) => void updateSocials: (enabled: boolean) => void diff --git a/apps/builder/providers/appkit-context-provider.tsx b/apps/builder/providers/appkit-context-provider.tsx index 559490608e..931ce74535 100644 --- a/apps/builder/providers/appkit-context-provider.tsx +++ b/apps/builder/providers/appkit-context-provider.tsx @@ -1,7 +1,7 @@ 'use client' import { ReactNode, useEffect, useState } from 'react' -import { Features, ThemeMode, ThemeVariables, type AppKit } from '@reown/appkit/react' +import { Features, ThemeMode, ThemeVariables, useAppKitState } from '@reown/appkit/react' import { ConnectMethod, ConstantsUtil } from '@reown/appkit-core' import { ThemeStore } from '../lib/theme-store' import { URLState, urlStateUtils } from '@/lib/url-state' @@ -24,7 +24,8 @@ interface AppKitProviderProps { const initialConfig = urlStateUtils.getStateFromURL() export const ContextProvider: React.FC = ({ children }) => { - const [isInitialized, setIsInitialized] = useState(false) + const { initialized } = useAppKitState() + const [features, setFeatures] = useState( initialConfig?.features || ConstantsUtil.DEFAULT_FEATURES ) @@ -53,10 +54,21 @@ export const ContextProvider: React.FC = ({ children }) => function updateFeatures(newFeatures: Partial) { setFeatures(prev => { - const newValue = { ...prev, ...newFeatures } - appKit?.updateFeatures(newValue) - urlStateUtils.updateURLWithState({ features: newValue }) - return newValue + // Update the AppKit state first + const newAppKitValue = { ...prev, ...newFeatures } + appKit?.updateFeatures(newAppKitValue) + + // Get the connection methods order since it's calculated based on injected connectors dynamically + const order = + newFeatures?.connectMethodsOrder === undefined + ? appKit?.getConnectMethodsOrder() + : newFeatures.connectMethodsOrder + + // Define and set new internal value with the order + const newInternalValue = { ...newAppKitValue, connectMethodsOrder: order } + urlStateUtils.updateURLWithState({ features: newInternalValue }) + + return newInternalValue }) } @@ -118,9 +130,16 @@ export const ContextProvider: React.FC = ({ children }) => useEffect(() => { setTheme(theme as ThemeMode) - setIsInitialized(true) }, []) + useEffect(() => { + if (initialized) { + const connectMethodsOrder = appKit?.getConnectMethodsOrder() + const order = connectMethodsOrder + updateFeatures({ connectMethodsOrder: order }) + } + }, [initialized]) + const socialsEnabled = Array.isArray(features.socials) return ( @@ -143,7 +162,6 @@ export const ContextProvider: React.FC = ({ children }) => socialsEnabled, enableWallets, isDraggingByKey, - isInitialized, updateFeatures, updateThemeMode, updateSocials, diff --git a/packages/appkit/src/client.ts b/packages/appkit/src/client.ts index fc62a7a222..5ea9f56203 100644 --- a/packages/appkit/src/client.ts +++ b/packages/appkit/src/client.ts @@ -83,6 +83,7 @@ import type { SessionTypes } from '@walletconnect/types' import type { UniversalProviderOpts } from '@walletconnect/universal-provider' import { W3mFrameProviderSingleton } from './auth-provider/W3MFrameProviderSingleton.js' import { WcHelpersUtil } from './utils/HelpersUtil.js' +import { WalletUtil } from '@reown/appkit-scaffold-ui/utils' declare global { interface Window { @@ -234,6 +235,7 @@ export class AppKit { } } }) + PublicStateController.set({ initialized: true }) } // -- Public ------------------------------------------------------------------- @@ -642,6 +644,13 @@ export class AppKit { await this.connectionControllerClient?.disconnect() } + public getConnectMethodsOrder() { + return WalletUtil.getConnectOrderMethod( + OptionsController.state.features, + ConnectorController.getConnectors() + ) + } + // -- Private ------------------------------------------------------------------ private async initControllers( options: AppKitOptions & { diff --git a/packages/core/src/controllers/PublicStateController.ts b/packages/core/src/controllers/PublicStateController.ts index 8139556a8a..8b42bf6ecf 100644 --- a/packages/core/src/controllers/PublicStateController.ts +++ b/packages/core/src/controllers/PublicStateController.ts @@ -1,12 +1,33 @@ import { proxy, subscribe as sub } from 'valtio/vanilla' -import type { CaipNetworkId } from '@reown/appkit-common' +import type { CaipNetworkId, ChainNamespace } from '@reown/appkit-common' // -- Types --------------------------------------------- // export interface PublicStateControllerState { + /** + * @description Indicates if the AppKit is loading. + * @type {boolean} + */ loading: boolean + /** + * @description Indicates if the AppKit modal is open. + * @type {boolean} + */ open: boolean - selectedNetworkId?: CaipNetworkId - activeChain?: string + /** + * @description Indicates the selected network id in CAIP-2 format. + * @type {CaipNetworkId | undefined} + */ + selectedNetworkId?: CaipNetworkId | undefined + /** + * @description Indicates the active chain namespace. + * @type {ChainNamespace | undefined} + */ + activeChain?: ChainNamespace | undefined + /** + * @description Indicates if the AppKit has been initialized. This sets to true when all controllers, adapters and internal state is ready. + * @type {boolean} + */ + initialized: boolean } // -- State --------------------------------------------- // @@ -14,7 +35,8 @@ const state = proxy({ loading: false, open: false, selectedNetworkId: undefined, - activeChain: undefined + activeChain: undefined, + initialized: false }) // -- Controller ---------------------------------------- // diff --git a/packages/core/tests/controllers/PublicStateController.test.ts b/packages/core/tests/controllers/PublicStateController.test.ts index 94b79ee91c..dc052aeb65 100644 --- a/packages/core/tests/controllers/PublicStateController.test.ts +++ b/packages/core/tests/controllers/PublicStateController.test.ts @@ -7,7 +7,9 @@ describe('PublicStateController', () => { expect(PublicStateController.state).toEqual({ loading: false, open: false, - selectedNetworkId: undefined + selectedNetworkId: undefined, + activeChain: undefined, + initialized: false }) }) @@ -15,14 +17,18 @@ describe('PublicStateController', () => { PublicStateController.set({ open: true }) expect(PublicStateController.state).toEqual({ loading: false, - open: true, - selectedNetworkId: undefined + selectedNetworkId: undefined, + activeChain: undefined, + initialized: false, + open: true }) PublicStateController.set({ selectedNetworkId: 'eip155:1' }) expect(PublicStateController.state).toEqual({ loading: false, open: true, - selectedNetworkId: 'eip155:1' + selectedNetworkId: 'eip155:1', + activeChain: undefined, + initialized: false }) }) }) diff --git a/packages/scaffold-ui/exports/utils.tsx b/packages/scaffold-ui/exports/utils.tsx new file mode 100644 index 0000000000..ab17c4c1ea --- /dev/null +++ b/packages/scaffold-ui/exports/utils.tsx @@ -0,0 +1,3 @@ +export * from '../src/utils/ConnectorUtil.js' +export * from '../src/utils/ConstantsUtil.js' +export * from '../src/utils/WalletUtil.js' diff --git a/packages/scaffold-ui/package.json b/packages/scaffold-ui/package.json index 23406b04b2..e5e2a1b612 100644 --- a/packages/scaffold-ui/package.json +++ b/packages/scaffold-ui/package.json @@ -18,6 +18,11 @@ "types": "./dist/types/exports/w3m-modal.d.ts", "import": "./dist/esm/exports/w3m-modal.js", "default": "./dist/esm/exports/w3m-modal.js" + }, + "./utils": { + "types": "./dist/types/exports/utils.d.ts", + "import": "./dist/esm/exports/utils.js", + "default": "./dist/esm/exports/utils.js" } }, "scripts": { diff --git a/packages/scaffold-ui/src/utils/WalletUtil.ts b/packages/scaffold-ui/src/utils/WalletUtil.ts index f238679422..21b63a0eba 100644 --- a/packages/scaffold-ui/src/utils/WalletUtil.ts +++ b/packages/scaffold-ui/src/utils/WalletUtil.ts @@ -4,7 +4,9 @@ import { OptionsController, StorageUtil } from '@reown/appkit-core' -import type { WcWallet } from '@reown/appkit-core' +import type { ConnectMethod, Connector, Features, WcWallet } from '@reown/appkit-core' +import { ConnectorUtil } from './ConnectorUtil.js' +import { ConstantsUtil } from './ConstantsUtil.js' interface AppKitWallet extends WcWallet { installed: boolean @@ -79,5 +81,26 @@ export const WalletUtil = { ) return sortedWallets + }, + + getConnectOrderMethod(_features: Features | undefined, _connectors: Connector[]) { + const connectMethodOrder = + _features?.connectMethodsOrder || OptionsController.state.features?.connectMethodsOrder + const connectors = _connectors || ConnectorController.state.connectors + + if (connectMethodOrder) { + return connectMethodOrder + } + + const { injected, announced } = ConnectorUtil.getConnectorsByType(connectors) + + const shownInjected = injected.filter(ConnectorUtil.showConnector) + const shownAnnounced = announced.filter(ConnectorUtil.showConnector) + + if (shownInjected.length || shownAnnounced.length) { + return ['wallet', 'email', 'social'] as ConnectMethod[] + } + + return ConstantsUtil.DEFAULT_CONNECT_METHOD_ORDER } } diff --git a/packages/scaffold-ui/src/views/w3m-connect-view/index.ts b/packages/scaffold-ui/src/views/w3m-connect-view/index.ts index 5868f41b91..055301e7e0 100644 --- a/packages/scaffold-ui/src/views/w3m-connect-view/index.ts +++ b/packages/scaffold-ui/src/views/w3m-connect-view/index.ts @@ -7,15 +7,13 @@ import { CoreHelperUtil, OptionsController, RouterController, - type ConnectMethod, type WalletGuideType } from '@reown/appkit-core' import { state } from 'lit/decorators/state.js' import { property } from 'lit/decorators.js' import { classMap } from 'lit/directives/class-map.js' import { ifDefined } from 'lit/directives/if-defined.js' -import { ConnectorUtil } from '../../utils/ConnectorUtil.js' -import { ConstantsUtil } from '../../utils/ConstantsUtil.js' +import { WalletUtil } from '../../utils/WalletUtil.js' @customElement('w3m-connect-view') export class W3mConnectView extends LitElement { @@ -119,7 +117,7 @@ export class W3mConnectView extends LitElement { // -- Private ------------------------------------------- // private renderConnectMethod(tabIndex?: number) { - const connectMethodsOrder = this.getConnectOrderMethod() + const connectMethodsOrder = WalletUtil.getConnectOrderMethod(this.features, this.connectors) return html`${connectMethodsOrder.map((method, index) => { switch (method) { @@ -151,7 +149,7 @@ export class W3mConnectView extends LitElement { } private checkIsThereNextMethod(currentIndex: number): string | undefined { - const connectMethodsOrder = this.getConnectOrderMethod() + const connectMethodsOrder = WalletUtil.getConnectOrderMethod(this.features, this.connectors) const nextMethod = connectMethodsOrder[currentIndex + 1] as | 'wallet' @@ -345,25 +343,6 @@ export class W3mConnectView extends LitElement { ) } - private getConnectOrderMethod() { - const connectMethodOrder = this.features?.connectMethodsOrder - - if (connectMethodOrder) { - return connectMethodOrder - } - - const { injected, announced } = ConnectorUtil.getConnectorsByType(this.connectors) - - const shownInjected = injected.filter(ConnectorUtil.showConnector) - const shownAnnounced = announced.filter(ConnectorUtil.showConnector) - - if (shownInjected.length || shownAnnounced.length) { - return ['wallet', 'email', 'social'] as ConnectMethod[] - } - - return ConstantsUtil.DEFAULT_CONNECT_METHOD_ORDER - } - // -- Private Methods ----------------------------------- // private onContinueWalletClick() { RouterController.push('ConnectWallets')