From 1614ff603d09fbfc9c2d70fc9a7c8cff33b98b46 Mon Sep 17 00:00:00 2001 From: tomiir Date: Fri, 17 Jan 2025 13:21:09 +0100 Subject: [PATCH 1/9] fix: remove duplicated basic button (#3678) --- .changeset/sweet-sloths-sneeze.md | 23 +++++++++++++++++++ .../w3m-connecting-wc-qrcode/index.ts | 16 ------------- .../partials/w3m-connecting-wc-qrcode.test.ts | 21 ----------------- 3 files changed, 23 insertions(+), 37 deletions(-) create mode 100644 .changeset/sweet-sloths-sneeze.md diff --git a/.changeset/sweet-sloths-sneeze.md b/.changeset/sweet-sloths-sneeze.md new file mode 100644 index 0000000000..550c1983ad --- /dev/null +++ b/.changeset/sweet-sloths-sneeze.md @@ -0,0 +1,23 @@ +--- +'@reown/appkit-scaffold-ui': patch +'@reown/appkit-adapter-bitcoin': patch +'@reown/appkit-adapter-ethers': patch +'@reown/appkit-adapter-ethers5': patch +'@reown/appkit-adapter-solana': patch +'@reown/appkit-adapter-wagmi': patch +'@reown/appkit': patch +'@reown/appkit-utils': patch +'@reown/appkit-cdn': patch +'@reown/appkit-cli': patch +'@reown/appkit-common': patch +'@reown/appkit-core': 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 +--- + +Removes duplicated all wallets button on AppKit Basic diff --git a/packages/scaffold-ui/src/partials/w3m-connecting-wc-qrcode/index.ts b/packages/scaffold-ui/src/partials/w3m-connecting-wc-qrcode/index.ts index e964bcc9f3..fe1cdd6704 100644 --- a/packages/scaffold-ui/src/partials/w3m-connecting-wc-qrcode/index.ts +++ b/packages/scaffold-ui/src/partials/w3m-connecting-wc-qrcode/index.ts @@ -1,12 +1,10 @@ import { html } from 'lit' -import { state } from 'lit/decorators.js' import { ifDefined } from 'lit/directives/if-defined.js' import { AssetUtil, ConnectionController, EventsController, - OptionsController, ThemeController } from '@reown/appkit-core' import { customElement } from '@reown/appkit-ui' @@ -18,18 +16,9 @@ import styles from './styles.js' export class W3mConnectingWcQrcode extends W3mConnectingWidget { public static override styles = styles - // -- State & Properties -------------------------------- // - @state() private useInjectedUniversalProvider = - OptionsController.state.useInjectedUniversalProvider - public constructor() { super() window.addEventListener('resize', this.forceUpdate) - this.unsubscribe.push( - OptionsController.subscribeKey('useInjectedUniversalProvider', () => { - this.useInjectedUniversalProvider = OptionsController.state.useInjectedUniversalProvider - }) - ) EventsController.sendEvent({ type: 'track', @@ -63,11 +52,6 @@ export class W3mConnectingWcQrcode extends W3mConnectingWidget { ${this.copyTemplate()} - ${this.useInjectedUniversalProvider - ? html` - - ` - : null} ` } diff --git a/packages/scaffold-ui/test/partials/w3m-connecting-wc-qrcode.test.ts b/packages/scaffold-ui/test/partials/w3m-connecting-wc-qrcode.test.ts index 41949ca3f3..1841e12ef8 100644 --- a/packages/scaffold-ui/test/partials/w3m-connecting-wc-qrcode.test.ts +++ b/packages/scaffold-ui/test/partials/w3m-connecting-wc-qrcode.test.ts @@ -7,7 +7,6 @@ import { ConnectionController, CoreHelperUtil, EventsController, - OptionsController, RouterController, type WcWallet } from '@reown/appkit-core' @@ -16,7 +15,6 @@ import type { WuiQrCode } from '@reown/appkit-ui' import { HelpersUtil } from '../utils/HelpersUtil' // -- Constants ------------------------------------------- // -const ALL_WALLETS_WIDGET = 'w3m-all-wallets-widget' const QR_CODE = 'wui-qr-code' const WALLET = { name: 'WalletConnect' @@ -64,23 +62,4 @@ describe('W3mConnectingWcQrcode', () => { properties: { name: WALLET.name, platform: 'qrcode' } }) }) - - it('it should use the injected universal provider when "OptionsController.useInjectedUniversalProvider" is true', async () => { - vi.spyOn(OptionsController, 'state', 'get').mockReturnValue({ - ...OptionsController.state, - useInjectedUniversalProvider: true - }) - - const connectingQrCode = await fixture( - html`` - ) - - // We display all wallets widget if we use injected universal provider - expect(HelpersUtil.querySelect(connectingQrCode, ALL_WALLETS_WIDGET)).not.toBeNull() - expect(EventsController.sendEvent).toHaveBeenCalledWith({ - type: 'track', - event: 'SELECT_WALLET', - properties: { name: WALLET.name, platform: 'qrcode' } - }) - }) }) From d203a1afa834222d584785883add3a1ab73171bd Mon Sep 17 00:00:00 2001 From: MK <53529533+magiziz@users.noreply.github.com> Date: Fri, 17 Jan 2025 13:04:17 +0000 Subject: [PATCH 2/9] chore: add test for `w3m-alertbar` (#3654) Co-authored-by: Enes --- .../src/partials/w3m-alertbar/index.ts | 2 +- .../test/partials/w3m-alertbar.test.ts | 108 ++++++++++++++++++ 2 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 packages/scaffold-ui/test/partials/w3m-alertbar.test.ts diff --git a/packages/scaffold-ui/src/partials/w3m-alertbar/index.ts b/packages/scaffold-ui/src/partials/w3m-alertbar/index.ts index d979906df0..91c6c062cc 100644 --- a/packages/scaffold-ui/src/partials/w3m-alertbar/index.ts +++ b/packages/scaffold-ui/src/partials/w3m-alertbar/index.ts @@ -7,7 +7,7 @@ import { customElement } from '@reown/appkit-ui' import styles from './styles.js' // -- Helpers ------------------------------------------- // -const presets = { +export const presets = { info: { backgroundColor: 'fg-350', iconColor: 'fg-325', diff --git a/packages/scaffold-ui/test/partials/w3m-alertbar.test.ts b/packages/scaffold-ui/test/partials/w3m-alertbar.test.ts new file mode 100644 index 0000000000..dec936cf05 --- /dev/null +++ b/packages/scaffold-ui/test/partials/w3m-alertbar.test.ts @@ -0,0 +1,108 @@ +import { elementUpdated, fixture } from '@open-wc/testing' +import { afterEach, beforeAll, describe, expect, it, vi } from 'vitest' + +import { html } from 'lit' + +import { AlertController } from '@reown/appkit-core' +import type { WuiAlertBar } from '@reown/appkit-ui' + +import { W3mAlertBar, presets } from '../../src/partials/w3m-alertbar' +import { HelpersUtil } from '../utils/HelpersUtil' + +// --- Constants ---------------------------------------------------- // +const ALERBAR = 'wui-alertbar' +const ALERT_VARIANTS = [ + { + variant: 'info', + message: 'info message', + preset: presets.info + }, + { + variant: 'success', + message: 'success message', + preset: presets.success + }, + { + variant: 'warning', + message: 'warning message', + preset: presets.warning + }, + { + variant: 'error', + message: 'error message', + preset: presets.error + } +] as const + +describe('W3mAlertBar', () => { + beforeAll(() => { + Element.prototype.animate = vi.fn() + }) + + afterEach(() => { + vi.clearAllMocks() + }) + + it('it should display the correct alert based on the variant', async () => { + for (const { variant, message, preset } of ALERT_VARIANTS) { + AlertController.state.message = message + AlertController.state.variant = variant + + const alertBar: W3mAlertBar = await fixture(html``) + const { + message: alertMessage, + backgroundColor, + icon, + iconColor + } = HelpersUtil.querySelect(alertBar, ALERBAR) as WuiAlertBar + + expect(alertMessage).toBe(message) + expect(backgroundColor).toBe(preset.backgroundColor) + expect(icon).toBe(preset.icon) + expect(iconColor).toBe(preset.iconColor) + } + }) + + it('it should apply correct animations and styles based on open state', async () => { + AlertController.state.open = false + + const alertBar: W3mAlertBar = await fixture(html``) + const animateSpy = vi.spyOn(alertBar, 'animate') + + AlertController.state.open = true + + await alertBar.requestUpdate() + await elementUpdated(alertBar) + + expect(animateSpy).toHaveBeenCalledWith( + [ + { opacity: 0, transform: 'scale(0.85)' }, + { opacity: 1, transform: 'scale(1)' } + ], + { + duration: 150, + fill: 'forwards', + easing: 'ease' + } + ) + expect(alertBar.style.cssText).toContain('pointer-events: auto') + + AlertController.state.open = false + + await alertBar.requestUpdate() + await elementUpdated(alertBar) + + expect(animateSpy).toHaveBeenCalledWith( + [ + { opacity: 1, transform: 'scale(1)' }, + { opacity: 0, transform: 'scale(0.85)' } + ], + { + duration: 150, + fill: 'forwards', + easing: 'ease' + } + ) + expect(alertBar.style.cssText).toContain('pointer-events: none') + }) +}) From 3976105b1de1a20cf478f70664f1ec81f22e07ce Mon Sep 17 00:00:00 2001 From: Sven <38101365+svenvoskamp@users.noreply.github.com> Date: Fri, 17 Jan 2025 14:45:07 +0100 Subject: [PATCH 3/9] test: add test for `w3m-all-wallets-widget` (#3634) --- .../partials/w3m-all-wallets-widget.test.ts | 158 ++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 packages/scaffold-ui/test/partials/w3m-all-wallets-widget.test.ts diff --git a/packages/scaffold-ui/test/partials/w3m-all-wallets-widget.test.ts b/packages/scaffold-ui/test/partials/w3m-all-wallets-widget.test.ts new file mode 100644 index 0000000000..0a2e88fb94 --- /dev/null +++ b/packages/scaffold-ui/test/partials/w3m-all-wallets-widget.test.ts @@ -0,0 +1,158 @@ +import { fixture } from '@open-wc/testing' +import { afterEach, beforeAll, describe, expect, it, vi } from 'vitest' + +import { html } from 'lit' + +import { + ApiController, + type ApiControllerState, + ConnectorController, + type ConnectorControllerState, + type ConnectorWithProviders, + CoreHelperUtil, + EventsController, + OptionsController, + type OptionsControllerState, + RouterController, + type SdkVersion +} from '@reown/appkit-core' + +import type { OptionsControllerStateInternal } from '../../../core/dist/types/src/controllers/OptionsController' +import { W3mAllWalletsWidget } from '../../src/partials/w3m-all-wallets-widget' +import { HelpersUtil } from '../utils/HelpersUtil' + +// --- Constants ---------------------------------------------------- // +const ALL_WALLETS_TEST_ID = 'all-wallets' +const WALLET_CONNECT_ID = 'walletConnect' + +const mockConnectorState: ConnectorControllerState = { + connectors: [], + activeConnector: undefined, + allConnectors: [] +} + +const mockOptionsState: OptionsControllerState & OptionsControllerStateInternal = { + allWallets: 'SHOW' as const, + projectId: 'test-project-id', + sdkVersion: '1.0.0' as SdkVersion, + sdkType: 'appkit' as const, + defaultAccountTypes: [] +} as unknown as OptionsControllerState & OptionsControllerStateInternal + +const mockConnector: ConnectorWithProviders = { + id: WALLET_CONNECT_ID, + type: 'WALLET_CONNECT', + name: 'WalletConnect', + chain: 'eip155' +} + +const mockApiState: ApiControllerState = { + page: 1, + count: 8, + featured: [ + { + id: '1', + name: 'Test Wallet', + rdns: 'io.test', + homepage: 'https://test.com', + image_url: 'https://test.com/image.png' + } + ], + recommended: [], + wallets: [], + search: [], + isAnalyticsEnabled: false, + excludedRDNS: [] +} + +describe('W3mAllWalletsWidget', () => { + beforeAll(() => { + vi.spyOn(CoreHelperUtil, 'isMobile').mockReturnValue(false) + }) + + afterEach(() => { + vi.resetAllMocks() + }) + + it('should not render if WalletConnect connector is not found', async () => { + vi.spyOn(ConnectorController, 'state', 'get').mockReturnValue(mockConnectorState) + + const element: W3mAllWalletsWidget = await fixture( + html`` + ) + + expect(HelpersUtil.getByTestId(element, ALL_WALLETS_TEST_ID)).toBeNull() + }) + + it('should not render if allWallets option is HIDE', async () => { + vi.spyOn(ConnectorController, 'state', 'get').mockReturnValue({ + ...mockConnectorState, + connectors: [mockConnector] + }) + vi.spyOn(OptionsController, 'state', 'get').mockReturnValue({ + allWallets: 'HIDE' as const + } as unknown as OptionsControllerState & OptionsControllerStateInternal) + + const element: W3mAllWalletsWidget = await fixture( + html`` + ) + + expect(HelpersUtil.getByTestId(element, ALL_WALLETS_TEST_ID)).toBeNull() + }) + + it('should not render if allWallets is ONLY_MOBILE and not on mobile', async () => { + vi.spyOn(ConnectorController, 'state', 'get').mockReturnValue({ + ...mockConnectorState, + connectors: [mockConnector] + }) + vi.spyOn(OptionsController, 'state', 'get').mockReturnValue({ + allWallets: 'ONLY_MOBILE' as const + } as unknown as OptionsControllerState & OptionsControllerStateInternal) + vi.spyOn(CoreHelperUtil, 'isMobile').mockReturnValue(false) + + const element: W3mAllWalletsWidget = await fixture( + html`` + ) + + expect(HelpersUtil.getByTestId(element, ALL_WALLETS_TEST_ID)).toBeNull() + }) + + it('should render with correct wallet count tag', async () => { + vi.spyOn(ConnectorController, 'state', 'get').mockReturnValue({ + ...mockConnectorState, + connectors: [mockConnector] + }) + vi.spyOn(OptionsController, 'state', 'get').mockReturnValue(mockOptionsState) + vi.spyOn(ApiController, 'state', 'get').mockReturnValue(mockApiState) + + const element: W3mAllWalletsWidget = await fixture( + html`` + ) + + const walletList = HelpersUtil.getByTestId(element, ALL_WALLETS_TEST_ID) + expect(walletList).not.toBeNull() + expect(walletList.getAttribute('tagLabel')).toBe('9') + }) + + it('should navigate to AllWallets view and track event on click', async () => { + vi.spyOn(ConnectorController, 'state', 'get').mockReturnValue({ + ...mockConnectorState, + connectors: [mockConnector] + }) + vi.spyOn(OptionsController, 'state', 'get').mockReturnValue(mockOptionsState) + vi.spyOn(ApiController, 'state', 'get').mockReturnValue(mockApiState) + + const routerPushSpy = vi.spyOn(RouterController, 'push') + const sendEventSpy = vi.spyOn(EventsController, 'sendEvent') + + const element: W3mAllWalletsWidget = await fixture( + html`` + ) + + const walletList = HelpersUtil.getByTestId(element, ALL_WALLETS_TEST_ID) + walletList.click() + + expect(sendEventSpy).toHaveBeenCalledWith({ type: 'track', event: 'CLICK_ALL_WALLETS' }) + expect(routerPushSpy).toHaveBeenCalledWith('AllWallets') + }) +}) From 62b4369ade281bdd5bcb90791817283e20c678cc Mon Sep 17 00:00:00 2001 From: tomiir Date: Fri, 17 Jan 2025 15:14:39 +0100 Subject: [PATCH 4/9] Fix/account buttons (#3680) --- .changeset/thin-bananas-try.md | 23 ++ packages/core/src/utils/ConstantsUtil.ts | 3 +- .../w3m-account-default-widget/index.ts | 48 ++- .../w3m-default-account-widget.test.ts | 301 ++++++++++++++++++ 4 files changed, 358 insertions(+), 17 deletions(-) create mode 100644 .changeset/thin-bananas-try.md create mode 100644 packages/scaffold-ui/test/partials/w3m-default-account-widget.test.ts diff --git a/.changeset/thin-bananas-try.md b/.changeset/thin-bananas-try.md new file mode 100644 index 0000000000..da5a7e8b02 --- /dev/null +++ b/.changeset/thin-bananas-try.md @@ -0,0 +1,23 @@ +--- +'@reown/appkit-scaffold-ui': patch +'@reown/appkit-core': patch +'@reown/appkit-adapter-bitcoin': patch +'@reown/appkit-adapter-ethers': patch +'@reown/appkit-adapter-ethers5': patch +'@reown/appkit-adapter-solana': patch +'@reown/appkit-adapter-wagmi': patch +'@reown/appkit': 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 +--- + +Fixes issue where onramp and activity were enabled in non-supported networks' diff --git a/packages/core/src/utils/ConstantsUtil.ts b/packages/core/src/utils/ConstantsUtil.ts index 9d7aa88128..4bcfe2358c 100644 --- a/packages/core/src/utils/ConstantsUtil.ts +++ b/packages/core/src/utils/ConstantsUtil.ts @@ -201,7 +201,8 @@ export const ConstantsUtil = { ], NAMES_SUPPORTED_CHAIN_NAMESPACES: ['eip155'] as ChainNamespace[], - + ONRAMP_SUPPORTED_CHAIN_NAMESPACES: ['eip155', 'solana'] as ChainNamespace[], + ACTIVITY_ENABLED_CHAIN_NAMESPACES: ['eip155', 'solana'] as ChainNamespace[], NATIVE_TOKEN_ADDRESS: { eip155: '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', solana: 'So11111111111111111111111111111111111111111', diff --git a/packages/scaffold-ui/src/partials/w3m-account-default-widget/index.ts b/packages/scaffold-ui/src/partials/w3m-account-default-widget/index.ts index bcbb0a526d..4c000b1da2 100644 --- a/packages/scaffold-ui/src/partials/w3m-account-default-widget/index.ts +++ b/packages/scaffold-ui/src/partials/w3m-account-default-widget/index.ts @@ -7,7 +7,6 @@ import { AccountController, type AccountType, ChainController, - ConstantsUtil as CommonConstantsUtil, ConnectionController, ConnectorController, ConstantsUtil as CoreConstantsUtil, @@ -132,10 +131,16 @@ export class W3mAccountDefaultWidget extends LitElement { // -- Private ------------------------------------------- // private onrampTemplate() { + if (!this.namespace) { + return null + } + const onramp = this.features?.onramp - const isBitcoin = ChainController.state.activeChain === 'bip122' + const hasNetworkSupport = CoreConstantsUtil.ONRAMP_SUPPORTED_CHAIN_NAMESPACES.includes( + this.namespace + ) - if (!onramp || isBitcoin) { + if (!onramp || !hasNetworkSupport) { return null } @@ -171,19 +176,30 @@ export class W3mAccountDefaultWidget extends LitElement { } private activityTemplate() { - const isSolana = ChainController.state.activeChain === ConstantsUtil.CHAIN.SOLANA + if (!this.namespace) { + return null + } - return html` - Activity - ${isSolana ? html`Coming soon` : ''} - ` + const isSolana = ChainController.state.activeChain === ConstantsUtil.CHAIN.SOLANA + const isEnabled = + this.features?.history && + CoreConstantsUtil.ACTIVITY_ENABLED_CHAIN_NAMESPACES.includes(this.namespace) + + return isEnabled + ? html` + + Activity + + ${isSolana ? html`Coming soon` : ''} + ` + : null } private swapsTemplate() { @@ -234,7 +250,7 @@ export class W3mAccountDefaultWidget extends LitElement { if ( !authConnector || connectorId !== ConstantsUtil.CONNECTOR_ID.AUTH || - origin.includes(CommonConstantsUtil.SECURE_SITE) + origin.includes(CoreConstantsUtil.SECURE_SITE) ) { return null } diff --git a/packages/scaffold-ui/test/partials/w3m-default-account-widget.test.ts b/packages/scaffold-ui/test/partials/w3m-default-account-widget.test.ts new file mode 100644 index 0000000000..bc8d4ae296 --- /dev/null +++ b/packages/scaffold-ui/test/partials/w3m-default-account-widget.test.ts @@ -0,0 +1,301 @@ +import { fixture } from '@open-wc/testing' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' + +import { html } from 'lit' + +import { ConstantsUtil } from '@reown/appkit-common' +import { + AccountController, + ChainController, + ConnectionController, + ConnectorController, + CoreHelperUtil, + EventsController, + ModalController, + OptionsController, + RouterController, + SnackController, + StorageUtil +} from '@reown/appkit-core' +import type { + AccountControllerState, + AuthConnector, + ChainControllerState +} from '@reown/appkit-core' + +import type { W3mAccountDefaultWidget } from '../../exports' +import { HelpersUtil } from '../utils/HelpersUtil' + +describe('W3mAccountDefaultWidget', () => { + const mockCaipAddress = 'eip155:1:0x123' + const mockAddress = '0x123' + const mockProfileName = 'Test Account' + const mockProfileImage = 'profile.jpg' + + beforeEach(() => { + // Mock AccountController state + vi.spyOn(AccountController, 'state', 'get').mockReturnValue({ + caipAddress: mockCaipAddress, + address: mockAddress, + profileName: mockProfileName, + profileImage: mockProfileImage, + balance: '100', + balanceSymbol: 'ETH', + allAccounts: [{ address: mockAddress, type: 'eoa' }], + addressExplorerUrl: 'https://etherscan.io', + addressLabels: new Map(), + preferredAccountType: 'eoa' + } as unknown as AccountControllerState) + + // Mock ChainController state + vi.spyOn(ChainController, 'state', 'get').mockReturnValue({ + activeChain: ConstantsUtil.CHAIN.EVM, + activeCaipNetwork: { + id: '1', + caipNetworkId: 'eip155:1' + } + } as unknown as ChainControllerState) + + // Mock OptionsController state + vi.spyOn(OptionsController, 'state', 'get').mockReturnValue({ + features: { + onramp: true, + swaps: true, + send: true, + walletFeaturesOrder: ['onramp', 'swaps', 'send'] + } + } as any) + + // Mock other controllers + vi.spyOn(StorageUtil, 'getConnectedConnectorId').mockReturnValue('test') + vi.spyOn(ConnectorController, 'getAuthConnector').mockReturnValue(undefined) + vi.spyOn(CoreHelperUtil, 'copyToClopboard').mockImplementation(vi.fn()) + vi.spyOn(CoreHelperUtil, 'openHref').mockImplementation(vi.fn()) + vi.spyOn(RouterController, 'push').mockImplementation(vi.fn()) + vi.spyOn(ModalController, 'close').mockImplementation(vi.fn()) + vi.spyOn(EventsController, 'sendEvent').mockImplementation(vi.fn()) + vi.spyOn(SnackController, 'showSuccess').mockImplementation(vi.fn()) + vi.spyOn(SnackController, 'showError').mockImplementation(vi.fn()) + }) + + afterEach(() => { + vi.clearAllMocks() + }) + + describe('Rendering', () => { + it('renders nothing when no caipAddress', async () => { + vi.spyOn(AccountController, 'state', 'get').mockReturnValue({ + caipAddress: null + } as unknown as AccountControllerState) + + const element: W3mAccountDefaultWidget = await fixture( + html`` + ) + // Should only have styles tag + expect(element.shadowRoot?.children.length).toBe(1) + }) + + it('renders single account view for Solana chain', async () => { + vi.spyOn(ChainController, 'state', 'get').mockReturnValue({ + activeChain: ConstantsUtil.CHAIN.SOLANA + } as unknown as ChainControllerState) + + const element: W3mAccountDefaultWidget = await fixture( + html`` + ) + expect(HelpersUtil.querySelect(element, '[data-testid="single-account-avatar"]')).toBeTruthy() + }) + + it('renders multi account view for EVM with multiple accounts', async () => { + vi.spyOn(AccountController, 'state', 'get').mockReturnValue({ + ...AccountController.state, + allAccounts: [ + { address: '0x123', type: 'eoa' }, + { address: '0x456', type: 'eoa' } + ] + } as AccountControllerState) + + const element: W3mAccountDefaultWidget = await fixture( + html`` + ) + expect(HelpersUtil.querySelect(element, 'wui-profile-button-v2')).toBeTruthy() + }) + + it('renders BTC accounts template for bip122 namespace with multiple accounts', async () => { + vi.spyOn(AccountController, 'state', 'get').mockReturnValue({ + ...AccountController.state, + allAccounts: [ + { address: '0x123', type: 'eoa' }, + { address: '0x456', type: 'eoa' } + ] + } as AccountControllerState) + + vi.spyOn(ChainController, 'state', 'get').mockReturnValue({ + activeChain: 'bip122', + activeCaipNetwork: { id: '1', caipNetworkId: 'bip122:1' } + } as unknown as ChainControllerState) + + const element: W3mAccountDefaultWidget = await fixture( + html`` + ) + expect(HelpersUtil.querySelect(element, 'wui-tabs')).toBeTruthy() + }) + }) + + describe('Features', () => { + it('shows onramp button when enabled for supported chain', async () => { + const element: W3mAccountDefaultWidget = await fixture( + html`` + ) + expect( + HelpersUtil.querySelect(element, '[data-testid="w3m-account-default-onramp-button"]') + ).toBeTruthy() + }) + + it('should not show onramp button when disabled', async () => { + vi.spyOn(OptionsController, 'state', 'get').mockReturnValue({ + features: { + onramp: false + } + } as any) + + const element: W3mAccountDefaultWidget = await fixture( + html`` + ) + expect(HelpersUtil.getByTestId(element, 'w3m-account-default-onramp-button')).toBeFalsy() + }) + + it('should not show onramp button for non-enabled chain', async () => { + vi.spyOn(ChainController, 'state', 'get').mockReturnValue({ + activeChain: ConstantsUtil.CHAIN.BITCOIN + } as unknown as ChainControllerState) + + const element: W3mAccountDefaultWidget = await fixture( + html`` + ) + expect(HelpersUtil.getByTestId(element, 'w3m-account-default-onramp-button')).toBeFalsy() + }) + + it('shows swap button for EVM chain', async () => { + const element: W3mAccountDefaultWidget = await fixture( + html`` + ) + const text = HelpersUtil.querySelectAll(element, 'wui-text') + expect([...text].some(t => t.textContent?.includes('Swap'))).toBeTruthy() + }) + + it('does not show swap button when disabled', async () => { + vi.spyOn(OptionsController, 'state', 'get').mockReturnValue({ + features: { + swaps: false + } + } as any) + + const element: W3mAccountDefaultWidget = await fixture( + html`` + ) + const texts = HelpersUtil.querySelectAll(element, 'wui-text') + expect([...texts].some(text => text?.textContent?.includes('Swap'))).toBeFalsy() + }) + + it('does not show swap button for non-EVM chain', async () => { + vi.spyOn(ChainController, 'state', 'get').mockReturnValue({ + activeChain: ConstantsUtil.CHAIN.SOLANA + } as unknown as ChainControllerState) + + const element: W3mAccountDefaultWidget = await fixture( + html`` + ) + const texts = HelpersUtil.querySelectAll(element, 'wui-text') + expect([...texts].some(text => text?.textContent?.includes('Swap'))).toBeFalsy() + }) + + it('shows auth card for AUTH connector', async () => { + vi.spyOn(StorageUtil, 'getConnectedConnectorId').mockReturnValue( + ConstantsUtil.CONNECTOR_ID.AUTH + ) + vi.spyOn(ConnectorController, 'getAuthConnector').mockReturnValue({ + id: 'auth', + provider: { + getEmail: () => 'email@email.com' + } + } as AuthConnector) + + const element: W3mAccountDefaultWidget = await fixture( + html`` + ) + expect( + HelpersUtil.querySelect(element, '[data-testid="w3m-wallet-upgrade-card"]') + ).toBeTruthy() + }) + }) + + describe('Interactions', () => { + it('copies address and shows success message', async () => { + const element: W3mAccountDefaultWidget = await fixture( + html`` + ) + const copyButton = HelpersUtil.querySelect(element, 'wui-icon-link') + await copyButton?.click() + + expect(CoreHelperUtil.copyToClopboard).toHaveBeenCalledWith(mockAddress) + expect(SnackController.showSuccess).toHaveBeenCalledWith('Address copied') + }) + + it('disconnects wallet successfully', async () => { + vi.spyOn(ConnectionController, 'disconnect').mockResolvedValue() + + const element: W3mAccountDefaultWidget = await fixture( + html`` + ) + const disconnectButton = HelpersUtil.querySelect(element, '[data-testid="disconnect-button"]') + await disconnectButton?.click() + + expect(ConnectionController.disconnect).toHaveBeenCalled() + expect(EventsController.sendEvent).toHaveBeenCalledWith({ + type: 'track', + event: 'DISCONNECT_SUCCESS' + }) + expect(ModalController.close).toHaveBeenCalled() + }) + + it('handles disconnect failure', async () => { + vi.spyOn(ConnectionController, 'disconnect').mockRejectedValue(new Error()) + + const element: W3mAccountDefaultWidget = await fixture( + html`` + ) + const disconnectButton = HelpersUtil.querySelect(element, '[data-testid="disconnect-button"]') + await disconnectButton?.click() + + expect(EventsController.sendEvent).toHaveBeenCalledWith({ + type: 'track', + event: 'DISCONNECT_ERROR' + }) + expect(SnackController.showError).toHaveBeenCalledWith('Failed to disconnect') + }) + + it('navigates to explorer', async () => { + const element: W3mAccountDefaultWidget = await fixture( + html`` + ) + const explorerButton = HelpersUtil.querySelect(element, 'wui-button') + await explorerButton?.click() + + expect(CoreHelperUtil.openHref).toHaveBeenCalledWith('https://etherscan.io', '_blank') + }) + }) + + describe('State Updates', () => { + it('cleans up subscriptions on disconnect', async () => { + const element: W3mAccountDefaultWidget = await fixture( + html`` + ) + const unsubscribeSpy = vi.fn() + ;(element as any).unsubscribe = [unsubscribeSpy] + + element.disconnectedCallback() + expect(unsubscribeSpy).toHaveBeenCalled() + }) + }) +}) From 3305586614d73fe9a3b71919c2b29c3b1568826b Mon Sep 17 00:00:00 2001 From: Enes Date: Fri, 17 Jan 2025 17:35:24 +0300 Subject: [PATCH 5/9] refactor: handle undefined caipNetwork when calling syncBalance (#3679) --- .changeset/nine-chicken-join.md | 23 ++ packages/appkit/src/client.ts | 11 +- packages/appkit/tests/appkit.test.ts | 313 ++++++++----------------- packages/appkit/tests/mocks/Options.ts | 4 +- packages/core/src/utils/StorageUtil.ts | 19 +- 5 files changed, 144 insertions(+), 226 deletions(-) create mode 100644 .changeset/nine-chicken-join.md diff --git a/.changeset/nine-chicken-join.md b/.changeset/nine-chicken-join.md new file mode 100644 index 0000000000..e34abb16fc --- /dev/null +++ b/.changeset/nine-chicken-join.md @@ -0,0 +1,23 @@ +--- +'@reown/appkit': patch +'@reown/appkit-core': patch +'@reown/appkit-adapter-bitcoin': 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-scaffold-ui': patch +'@reown/appkit-siwe': patch +'@reown/appkit-siwx': patch +'@reown/appkit-ui': patch +'@reown/appkit-wallet': patch +'@reown/appkit-wallet-button': patch +--- + +Refactors AppKit client to handle syncBalance call for unsupported networks as expected diff --git a/packages/appkit/src/client.ts b/packages/appkit/src/client.ts index 53390e674f..5414ee154e 100644 --- a/packages/appkit/src/client.ts +++ b/packages/appkit/src/client.ts @@ -1588,25 +1588,26 @@ export class AppKit { ) } - const network = (caipNetwork || fallbackCaipNetwork) as CaipNetwork - if (network.chainNamespace === ChainController.state.activeChain) { + const network = caipNetwork || fallbackCaipNetwork + + if (network?.chainNamespace === ChainController.state.activeChain) { this.setCaipNetwork(network) } this.syncConnectedWalletInfo(chainNamespace) - await this.syncBalance({ address, chainId: network.id, chainNamespace }) + await this.syncBalance({ address, chainId: network?.id, chainNamespace }) } } private async syncBalance(params: { address: string - chainId: string | number + chainId: string | number | undefined chainNamespace: ChainNamespace }) { const caipNetwork = NetworkUtil.getNetworksByNamespace( this.caipNetworks, params.chainNamespace - ).find(n => n.id.toString() === params.chainId.toString()) + ).find(n => n.id.toString() === params.chainId?.toString()) if (!caipNetwork) { return diff --git a/packages/appkit/tests/appkit.test.ts b/packages/appkit/tests/appkit.test.ts index 4de41d8d0c..824f528bcd 100644 --- a/packages/appkit/tests/appkit.test.ts +++ b/packages/appkit/tests/appkit.test.ts @@ -5,7 +5,6 @@ import { type AppKitNetwork, type Balance, type CaipNetwork, - type CaipNetworkId, type ChainNamespace, Emitter, NetworkUtil, @@ -40,13 +39,25 @@ import { CaipNetworksUtil, ErrorUtil } from '@reown/appkit-utils' import type { AdapterBlueprint } from '../src/adapters/ChainAdapterBlueprint' import { AppKit } from '../src/client' -import { base, mainnet, polygon, sepolia, solana } from '../src/networks/index.js' +import { + base as baseNetwork, + mainnet as mainnetNetwork, + polygon as polygonNetwork, + sepolia as sepoliaNetwork, + solana as solanaNetwork +} from '../src/networks/index.js' import { ProviderUtil } from '../src/store' import { UniversalAdapter } from '../src/universal-adapter/client' import mockUniversalAdapter from './mocks/Adapter' import { mockOptions } from './mocks/Options' import mockProvider from './mocks/UniversalProvider' +// Extend networks as CAIP networks +const [base, mainnet, polygon, sepolia, solana] = CaipNetworksUtil.extendCaipNetworks( + [baseNetwork, mainnetNetwork, polygonNetwork, sepoliaNetwork, solanaNetwork], + { customNetworkImageUrls: {}, projectId: 'test-project-id' } +) as [CaipNetwork, CaipNetwork, CaipNetwork, CaipNetwork, CaipNetwork] + // Mock all controllers and UniversalAdapterClient vi.mock('@reown/appkit-core') vi.mock('../src/universal-adapter/client') @@ -75,7 +86,6 @@ describe('Base', () => { beforeEach(() => { vi.mocked(ConnectorController).getConnectors = vi.fn().mockReturnValue([]) - vi.mocked(CaipNetworksUtil).extendCaipNetworks = vi.fn().mockReturnValue([]) appKit = new AppKit(mockOptions) @@ -109,10 +119,14 @@ describe('Base', () => { }) expect(ChainController.initialize).toHaveBeenCalledOnce() - expect(ChainController.initialize).toHaveBeenCalledWith(mockOptions.adapters, [], { - connectionControllerClient: expect.any(Object), - networkControllerClient: expect.any(Object) - }) + expect(ChainController.initialize).toHaveBeenCalledWith( + mockOptions.adapters, + [mainnet, sepolia, solana], + { + connectionControllerClient: expect.any(Object), + networkControllerClient: expect.any(Object) + } + ) }) it('should set EIP6963 enabled by default', () => { @@ -350,14 +364,12 @@ describe('Base', () => { }) it('should set CAIP address', () => { - // First mock AccountController.setCaipAddress to update ChainController state - vi.spyOn(AccountController, 'setCaipAddress').mockImplementation(() => { - vi.spyOn(ChainController, 'state', 'get').mockReturnValueOnce({ - ...ChainController.state, - activeCaipAddress: 'eip155:1:0x123', - chains: new Map([['eip155', { namespace: 'eip155' }]]) - }) as any - }) + vi.spyOn(ChainController, 'state', 'get').mockReturnValue({ + ...ChainController.state, + activeChain: 'eip155', + activeCaipAddress: 'eip155:1:0x123', + chains: new Map([['eip155', { namespace: 'eip155' }]]) + } as unknown as ChainControllerState) appKit.setCaipAddress('eip155:1:0x123', 'eip155') expect(AccountController.setCaipAddress).toHaveBeenCalledWith('eip155:1:0x123', 'eip155') @@ -396,21 +408,21 @@ describe('Base', () => { }) it('should set CAIP network', () => { - const caipNetwork = { id: 'eip155:1', name: 'Ethereum' } as unknown as CaipNetwork + const caipNetwork = mainnet appKit.setCaipNetwork(caipNetwork) expect(ChainController.setActiveCaipNetwork).toHaveBeenCalledWith(caipNetwork) }) it('should get CAIP network', () => { vi.spyOn(ChainController, 'state', 'get').mockReturnValue({ - activeCaipNetwork: { id: 'eip155:1', name: 'Ethereum' }, + activeCaipNetwork: mainnet, chains: new Map([['eip155', { namespace: 'eip155' }]]) } as any) - expect(appKit.getCaipNetwork()).toEqual({ id: 'eip155:1', name: 'Ethereum' }) + expect(appKit.getCaipNetwork()).toEqual(mainnet) }) it('should set requested CAIP networks', () => { - const requestedNetworks = [{ id: 'eip155:1', name: 'Ethereum' }] as unknown as CaipNetwork[] + const requestedNetworks = [mainnet] as unknown as CaipNetwork[] appKit.setRequestedCaipNetworks(requestedNetworks, 'eip155') expect(ChainController.setRequestedCaipNetworks).toHaveBeenCalledWith( requestedNetworks, @@ -647,15 +659,11 @@ describe('Base', () => { }) it('should switch network when requested', async () => { - vi.mocked(CaipNetworksUtil).extendCaipNetworks = vi - .fn() - .mockReturnValue([{ id: mainnet.id, name: mainnet.name }]) - const mockAppKit = new AppKit(mockOptions) vi.mocked(ChainController.switchActiveNetwork).mockResolvedValue(undefined) - await mockAppKit.switchNetwork(mainnet) + mockAppKit.switchNetwork(mainnet) expect(ChainController.switchActiveNetwork).toHaveBeenCalledWith( expect.objectContaining({ @@ -664,15 +672,12 @@ describe('Base', () => { }) ) - await mockAppKit.switchNetwork(polygon) + mockAppKit.switchNetwork(polygon) expect(ChainController.switchActiveNetwork).toHaveBeenCalledTimes(1) }) it('should use the correct network when syncing account if is does not allow all networks and network is not allowed', async () => { - vi.spyOn(NetworkUtil, 'getNetworksByNamespace').mockReturnValue([ - mainnet as unknown as CaipNetwork - ]) vi.spyOn(AccountController, 'fetchTokenBalance').mockResolvedValue([ { quantity: { numeric: '0.00', decimals: '18' }, @@ -680,39 +685,30 @@ describe('Base', () => { symbol: 'ETH' } as Balance ]) - vi.mocked(ChainController.getAllApprovedCaipNetworkIds).mockReturnValue(['eip155:1']) + vi.mocked(ChainController.getAllApprovedCaipNetworkIds).mockReturnValue([ + 'eip155:1', + 'eip155:11155111' + ]) vi.spyOn(ChainController, 'getNetworkProp').mockReturnValue(false) vi.spyOn(ChainController.state, 'activeChain', 'get').mockReturnValueOnce('eip155') - vi.mocked(appKit as any).caipNetworks = [ - { - id: '1', - chainNamespace: 'eip155', - caipNetworkId: 'eip155:1' as CaipNetworkId - } - ] + vi.spyOn(StorageUtil, 'getActiveNetworkProps').mockReturnValueOnce({ + namespace: mainnet.chainNamespace, + chainId: mainnet.id, + caipNetworkId: mainnet.caipNetworkId + }) + vi.spyOn(OptionsController, 'state', 'get').mockReturnValueOnce({ + allowUnsupportedChain: false + } as any) const mockAccountData = { address: '0x123', chainId: '2', chainNamespace: 'eip155' as const } - vi.spyOn(StorageUtil, 'getActiveNetworkProps').mockReturnValueOnce({ - namespace: 'eip155', - chainId: '1', - caipNetworkId: 'eip155:1' - }) - - OptionsController.state.allowUnsupportedChain = undefined - vi.spyOn(OptionsController.state, 'allowUnsupportedChain', 'get').mockReturnValueOnce(false) - await appKit['syncAccount'](mockAccountData) expect(ChainController.getNetworkProp).toHaveBeenCalledWith('supportsAllNetworks', 'eip155') - expect(ChainController.setActiveCaipNetwork).toHaveBeenCalledWith({ - id: '1', - chainNamespace: 'eip155', - caipNetworkId: 'eip155:1' - }) + expect(ChainController.setActiveCaipNetwork).toHaveBeenCalledWith(mainnet) }) it('should set connected wallet info when syncing account', async () => { @@ -731,13 +727,7 @@ describe('Base', () => { } as Balance ]) vi.spyOn(ChainController, 'getAllApprovedCaipNetworkIds').mockReturnValue(['eip155:1']) - vi.mocked(appKit as any).caipNetworks = [ - { - id: '1', - chainNamespace: 'eip155', - caipNetworkId: 'eip155:1' as CaipNetworkId - } - ] + vi.mocked(appKit as any).caipNetworks = [mainnet] // Mock the connector data const mockConnector = { id: 'test-wallet' @@ -745,15 +735,15 @@ describe('Base', () => { vi.mocked(ConnectorController.getConnectors).mockReturnValue([mockConnector]) vi.mocked(StorageUtil.getActiveNetworkProps).mockReturnValue({ - namespace: 'eip155', - chainId: '1', - caipNetworkId: '1' + namespace: mainnet.chainNamespace, + chainId: mainnet.id, + caipNetworkId: mainnet.caipNetworkId }) const mockAccountData = { address: '0x123', - chainId: '1', - chainNamespace: 'eip155' as const + chainId: mainnet.id, + chainNamespace: mainnet.chainNamespace } vi.spyOn(StorageUtil, 'getConnectedConnectorId').mockReturnValue(mockConnector.id) @@ -784,23 +774,17 @@ describe('Base', () => { } as Balance ]) vi.spyOn(ChainController, 'getAllApprovedCaipNetworkIds').mockReturnValue(['eip155:1']) - vi.mocked(appKit as any).caipNetworks = [ - { - id: '1', - chainNamespace: 'eip155', - caipNetworkId: 'eip155:1' as CaipNetworkId - } - ] + vi.mocked(appKit as any).caipNetworks = [mainnet] const mockAccountData = { address: '0x123', - chainId: '1', - chainNamespace: 'eip155' as const + chainId: mainnet.id, + chainNamespace: mainnet.chainNamespace } vi.spyOn(StorageUtil, 'getActiveNetworkProps').mockReturnValue({ - namespace: 'eip155', - chainId: '1', - caipNetworkId: '1' + namespace: mainnet.chainNamespace, + chainId: mainnet.id, + caipNetworkId: mainnet.caipNetworkId }) vi.mocked(BlockchainApiController.fetchIdentity).mockResolvedValue({ @@ -835,22 +819,16 @@ describe('Base', () => { } as Balance ]) vi.spyOn(ChainController, 'getAllApprovedCaipNetworkIds').mockReturnValue(['solana:1']) - vi.mocked(appKit as any).caipNetworks = [ - { - id: '1', - chainNamespace: 'solana', - caipNetworkId: 'solana:1' as CaipNetworkId - } - ] + vi.mocked(appKit as any).caipNetworks = [solana] const mockAccountData = { address: '0x123', - chainId: '1', - chainNamespace: 'solana' as const + chainId: solana.id, + chainNamespace: solana.chainNamespace } vi.spyOn(StorageUtil, 'getActiveNetworkProps').mockReturnValueOnce({ - namespace: 'solana', - chainId: '1', - caipNetworkId: 'solana:1' + namespace: solana.chainNamespace, + chainId: solana.id, + caipNetworkId: solana.caipNetworkId }) await appKit['syncAccount'](mockAccountData) @@ -874,15 +852,7 @@ describe('Base', () => { } as Balance ]) vi.spyOn(ChainController, 'getAllApprovedCaipNetworkIds').mockReturnValue(['eip155:11155111']) - vi.mocked(appKit as any).caipNetworks = [ - { - ...sepolia, - nativeCurrency: { symbol: 'sETH' }, - chainNamespace: 'eip155', - caipNetworkId: 'eip155:11155111', - testnet: true - } as unknown as CaipNetwork - ] + vi.mocked(appKit as any).caipNetworks = [sepolia] const mockAccountData = { address: '0x123', chainId: '11155111', @@ -914,25 +884,18 @@ describe('Base', () => { ]) vi.spyOn(ChainController, 'getAllApprovedCaipNetworkIds').mockReturnValue(['eip155:1']) vi.spyOn(StorageUtil, 'getActiveNetworkProps').mockReturnValueOnce({ - namespace: 'eip155', - chainId: '1', - caipNetworkId: 'eip155:1' + namespace: mainnet.chainNamespace, + chainId: mainnet.id, + caipNetworkId: mainnet.caipNetworkId }) const mockAccountData = { address: '0x123', - chainId: '1', - chainNamespace: 'eip155' as const + chainId: mainnet.id, + chainNamespace: mainnet.chainNamespace } vi.spyOn(AccountController, 'state', 'get').mockReturnValue(mockAccountData as any) - vi.spyOn(CaipNetworksUtil, 'extendCaipNetworks').mockReturnValue([ - { - id: '1', - chainNamespace: 'eip155', - caipNetworkId: 'eip155:1' as CaipNetworkId - } as CaipNetwork - ]) appKit = new AppKit({ ...mockOptions }) @@ -969,16 +932,6 @@ describe('Base', () => { caipNetworkId: 'eip155:11155111' }) - vi.spyOn(CaipNetworksUtil, 'extendCaipNetworks').mockReturnValueOnce([ - { - id: '11155111', - chainNamespace: 'eip155', - caipNetworkId: 'eip155:11155111' as CaipNetworkId, - testnet: true, - nativeCurrency: { symbol: 'sETH' } - } as CaipNetwork - ]) - vi.spyOn(AccountController, 'state', 'get').mockReturnValue(mockAccountData as any) appKit = new AppKit({ ...mockOptions }) @@ -990,10 +943,6 @@ describe('Base', () => { }) it('should disconnect correctly', async () => { - vi.mocked(CaipNetworksUtil.extendCaipNetworks).mockReturnValue([ - { id: 'eip155:1', chainNamespace: 'eip155' } as CaipNetwork - ]) - vi.spyOn(ChainController, 'state', 'get').mockReturnValue({ chains: new Map([['eip155', { namespace: 'eip155' }]]), activeChain: 'eip155' @@ -1006,7 +955,6 @@ describe('Base', () => { const appKit = new AppKit({ ...mockOptions, networks: [base], - projectId: 'YOUR_PROJECT_ID', adapters: [mockUniversalAdapter] }) @@ -1021,7 +969,7 @@ describe('Base', () => { chains: new Map([['eip155', { namespace: 'eip155' }]]), activeChain: 'eip155' } as any) - ;(appKit as any).caipNetworks = [{ id: 'eip155:1', chainNamespace: 'eip155' }] + ;(appKit as any).caipNetworks = [mainnet] OptionsController.state.allowUnsupportedChain = undefined vi.spyOn(OptionsController.state, 'allowUnsupportedChain', 'get').mockResolvedValueOnce(true) @@ -1099,17 +1047,17 @@ describe('Base', () => { activeCaipNetwork: { id: 'eip155:1', chainNamespace: 'eip155' } as CaipNetwork } as ChainControllerState) vi.mocked(StorageUtil.getActiveNetworkProps).mockReturnValue({ - namespace: 'eip155', - chainId: '1', - caipNetworkId: '1' + namespace: mainnet.chainNamespace, + chainId: mainnet.id, + caipNetworkId: mainnet.caipNetworkId }) const mockAdapter = { getAccounts: vi.fn().mockResolvedValue({ accounts: [{ address: '0x123', type: 'eoa' }] }), syncConnection: vi.fn().mockResolvedValue({ address: '0x123', - chainId: '1', - chainNamespace: 'eip155', + chainId: mainnet.id, + chainNamespace: mainnet.chainNamespace, accounts: [{ address: '0x123', type: 'eoa' }] }), on: vi.fn(), @@ -1169,8 +1117,8 @@ describe('Base', () => { vi.spyOn(ProviderUtil, 'setProviderId').mockImplementation(vi.fn()) vi.spyOn(StorageUtil, 'getActiveNetworkProps').mockReturnValue({ namespace: 'eip155', - chainId: '1', - caipNetworkId: '1' + chainId: mainnet.id, + caipNetworkId: mainnet.caipNetworkId }) vi.spyOn(StorageUtil, 'getConnectedNamespaces').mockReturnValueOnce(['eip155', 'solana']) vi.spyOn(StorageUtil, 'getConnectedConnectorId').mockImplementation(namespace => { @@ -1184,8 +1132,8 @@ describe('Base', () => { getAccounts: vi.fn().mockResolvedValue({ accounts: [{ address: '0x123', type: 'eoa' }] }), syncConnection: vi.fn().mockResolvedValue({ address: '0x123', - chainId: '1', - chainNamespace: 'eip155', + chainId: mainnet.id, + chainNamespace: mainnet.chainNamespace, accounts: [{ address: '0x123', type: 'eoa' }], type: 'EXTERNAL', id: 'evm-connector' @@ -1197,8 +1145,8 @@ describe('Base', () => { getAccounts: vi.fn().mockResolvedValue({ accounts: [{ address: 'Hgbsh1', type: 'eoa' }] }), syncConnection: vi.fn().mockResolvedValue({ address: 'Hgbsh1', - chainId: '1', - chainNamespace: 'solana', + chainId: solana.id, + chainNamespace: solana.chainNamespace, accounts: [{ address: 'Hgbsh1', type: 'eoa' }], type: 'EXTERNAL', id: 'solana-connector' @@ -1271,14 +1219,9 @@ describe('Base', () => { }) it('should call syncConnectors when initializing adapters', async () => { - vi.mocked(CaipNetworksUtil.extendCaipNetworks).mockReturnValue([ - { id: 'eip155:1', chainNamespace: 'eip155' } as CaipNetwork - ]) - const appKit = new AppKit({ ...mockOptions, networks: [base], - projectId: 'YOUR_PROJECT_ID', adapters: [mockAdapter] }) @@ -1293,14 +1236,9 @@ describe('Base', () => { }) it('should create UniversalAdapter when no blueprint is provided for namespace', async () => { - vi.mocked(CaipNetworksUtil.extendCaipNetworks).mockReturnValue([ - { id: 'eip155:1', chainNamespace: 'eip155' } as CaipNetwork - ]) - const appKit = new AppKit({ ...mockOptions, - networks: [mainnet], - projectId: 'YOUR_PROJECT_ID', + networks: [mainnetNetwork], adapters: [mockAdapter] }) @@ -1321,14 +1259,11 @@ describe('Base', () => { expect(UniversalAdapter).toHaveBeenCalledWith({ namespace: 'eip155', - networks: [{ id: 'eip155:1', chainNamespace: 'eip155' } as CaipNetwork] + networks: [mainnet] }) }) it('should initialize UniversalProvider when not provided in options', () => { - vi.mocked(CaipNetworksUtil.extendCaipNetworks).mockReturnValue([ - { id: 'eip155:1', chainNamespace: 'eip155' } as CaipNetwork - ]) vi.spyOn(CoreHelperUtil, 'isClient').mockReturnValue(true) const upSpy = vi.spyOn(UniversalProvider, 'init') @@ -1345,9 +1280,6 @@ describe('Base', () => { }) it('should not initialize UniversalProvider when provided in options', async () => { - vi.mocked(CaipNetworksUtil.extendCaipNetworks).mockReturnValue([ - { id: 'eip155:1', chainNamespace: 'eip155' } as CaipNetwork - ]) vi.spyOn(CoreHelperUtil, 'isClient').mockReturnValue(true) const upSpy = vi.spyOn(UniversalProvider, 'init') @@ -1365,11 +1297,6 @@ describe('Base', () => { }) it('should initialize multiple adapters for different namespaces', async () => { - vi.mocked(CaipNetworksUtil.extendCaipNetworks).mockReturnValue([ - { id: '1', chainNamespace: 'eip155' } as CaipNetwork, - { id: 'solana', chainNamespace: 'solana' } as CaipNetwork - ]) - const mockSolanaAdapter = { namespace: 'solana', construct: vi.fn(), @@ -1385,7 +1312,6 @@ describe('Base', () => { const appKit = new AppKit({ ...mockOptions, networks: [mainnet, solana], - projectId: 'YOUR_PROJECT_ID', adapters: [mockSolanaAdapter, mockAdapter] }) @@ -1402,14 +1328,9 @@ describe('Base', () => { }) it('should set universal provider and auth provider for each adapter', async () => { - vi.mocked(CaipNetworksUtil.extendCaipNetworks).mockReturnValue([ - { id: '1', chainNamespace: 'eip155' } as CaipNetwork - ]) - const appKit = new AppKit({ ...mockOptions, networks: [mainnet], - projectId: 'YOUR_PROJECT_ID', adapters: [mockAdapter] }) @@ -1480,19 +1401,16 @@ describe('Listeners', () => { vi.spyOn(AccountController, 'state', 'get').mockReturnValue({ address: '0x' } as unknown as typeof AccountController.state) - vi.spyOn(CaipNetworksUtil, 'extendCaipNetworks').mockReturnValueOnce([ - { id: '1', chainNamespace: 'eip155' } as CaipNetwork - ]) vi.spyOn(StorageUtil, 'getActiveNetworkProps').mockReturnValueOnce({ - namespace: 'eip155', - chainId: '1', - caipNetworkId: '1' + namespace: mainnet.chainNamespace, + chainId: mainnet.id, + caipNetworkId: mainnet.caipNetworkId }) const mockAccount = { address: '0x123', - chainId: '1', - chainNamespace: 'eip155' + chainId: mainnet.id, + chainNamespace: mainnet.chainNamespace } vi.spyOn(ChainController, 'state', 'get').mockReturnValue({ @@ -1519,7 +1437,6 @@ describe('Listeners', () => { const appKit = new AppKit({ ...mockOptions, networks: [mainnet], - projectId: 'YOUR_PROJECT_ID', features: { email: false, socials: [] @@ -1555,7 +1472,6 @@ describe('Listeners', () => { describe('Adapter Management', () => { let appKit: AppKit let mockAdapter: AdapterBlueprint - let mockNetwork: AppKitNetwork beforeEach(() => { vi.spyOn(OptionsController, 'getSnapshot').mockReturnValue({ ...OptionsController.state }) @@ -1574,18 +1490,9 @@ describe('Adapter Management', () => { removeAllEventListeners: vi.fn() } as unknown as AdapterBlueprint - mockNetwork = { - id: 'eip155:1', - name: 'Ethereum' - } as unknown as AppKitNetwork - - vi.mocked(CaipNetworksUtil.extendCaipNetworks).mockReturnValue([ - { id: 'eip155:1', chainNamespace: 'eip155' } as CaipNetwork - ]) - appKit = new AppKit({ ...mockOptions, - networks: [mockNetwork], + networks: [mainnet], adapters: [mockAdapter] }) @@ -1608,16 +1515,7 @@ describe('Adapter Management', () => { emit: vi.fn() } as unknown as ChainAdapter - const newNetwork = { - id: 'solana:1', - name: 'Solana' - } as unknown as AppKitNetwork - - vi.mocked(CaipNetworksUtil.extendCaipNetworks).mockReturnValueOnce([ - { id: 'solana:1', chainNamespace: 'solana' } as CaipNetwork - ]) - - appKit.addAdapter(newAdapter, [newNetwork]) + appKit.addAdapter(newAdapter, [solana]) expect(appKit.chainAdapters?.solana).toBeDefined() expect(appKit.chainNamespaces).toContain('solana') @@ -1636,16 +1534,11 @@ describe('Adapter Management', () => { namespace: 'solana' } as unknown as ChainAdapter - const newNetwork = { - id: 'solana:1', - name: 'Solana' - } as unknown as AppKitNetwork - // Remove clients ;(appKit as any).connectionControllerClient = undefined ;(appKit as any).networkControllerClient = undefined - appKit.addAdapter(newAdapter, [newNetwork]) + appKit.addAdapter(newAdapter, [solana]) expect(appKit.chainAdapters?.solana).toBeUndefined() }) @@ -1659,15 +1552,10 @@ describe('Adapter Management', () => { namespace: 'solana' } as unknown as ChainAdapter - const newNetwork = { - id: 'solana:1', - name: 'Solana' - } as unknown as AppKitNetwork - // Remove chainAdapters ;(appKit as any).chainAdapters = undefined - appKit.addAdapter(newAdapter, [newNetwork]) + appKit.addAdapter(newAdapter, [solana]) expect((appKit as any).createAdapter).not.toHaveBeenCalled() expect((appKit as any).initChainAdapter).not.toHaveBeenCalled() @@ -1737,20 +1625,23 @@ describe('Adapter Management', () => { describe('Balance sync', () => { beforeEach(() => { vi.resetAllMocks() + vi.spyOn(OptionsController, 'getSnapshot').mockReturnValue({ ...OptionsController.state }) + vi.spyOn(ThemeController, 'getSnapshot').mockReturnValue({ ...ThemeController.state }) + vi.spyOn(ChainController, 'state', 'get').mockReturnValue({ ...ChainController.state }) }) it('should not sync balance if theres no matching caipNetwork', async () => { - vi.spyOn(NetworkUtil, 'getNetworksByNamespace').mockReturnValue([]) - const appKit = new AppKit({ ...mockOptions, networks: [mainnet] }) + vi.spyOn(NetworkUtil, 'getNetworksByNamespace').mockReturnValue([]) + await appKit['syncBalance']({ address: '0x123', - chainId: '1', - chainNamespace: 'eip155' as const + chainId: sepolia.id, + chainNamespace: sepolia.chainNamespace }) expect(NetworkUtil.getNetworksByNamespace).toHaveBeenCalled() @@ -1759,9 +1650,7 @@ describe('Balance sync', () => { }) it('should set empty balance on testnet', async () => { - vi.spyOn(NetworkUtil, 'getNetworksByNamespace').mockReturnValue([ - { ...sepolia, caipNetworkId: 'eip155:11155111', chainNamespace: 'eip155' } - ]) + vi.spyOn(NetworkUtil, 'getNetworksByNamespace').mockReturnValue([sepolia]) const appKit = new AppKit({ ...mockOptions, diff --git a/packages/appkit/tests/mocks/Options.ts b/packages/appkit/tests/mocks/Options.ts index 16eddf4d59..ad4fde2276 100644 --- a/packages/appkit/tests/mocks/Options.ts +++ b/packages/appkit/tests/mocks/Options.ts @@ -3,7 +3,7 @@ import { vi } from 'vitest' import type { ChainAdapter } from '@reown/appkit-core' import type { SdkVersion } from '@reown/appkit-core' -import { mainnet, solana } from '../../src/networks/index.js' +import { mainnet, sepolia, solana } from '../../src/networks/index.js' import type { AppKitOptions } from '../../src/utils/index.js' export const mockOptions: AppKitOptions & { @@ -29,7 +29,7 @@ export const mockOptions: AppKitOptions & { emit: vi.fn() } as unknown as ChainAdapter ], - networks: [mainnet, solana], + networks: [mainnet, sepolia, solana], metadata: { name: 'Test App', description: 'Test App Description', diff --git a/packages/core/src/utils/StorageUtil.ts b/packages/core/src/utils/StorageUtil.ts index 47872f9a28..ecdc351188 100644 --- a/packages/core/src/utils/StorageUtil.ts +++ b/packages/core/src/utils/StorageUtil.ts @@ -12,16 +12,21 @@ import type { ConnectionStatus, SocialProvider, WcWallet } from './TypeUtil.js' // -- Utility ----------------------------------------------------------------- export const StorageUtil = { getActiveNetworkProps() { - const activeNamespace = StorageUtil.getActiveNamespace() - const activeCaipNetworkId = StorageUtil.getActiveCaipNetworkId() - const caipNetworkIdFromStorage = activeCaipNetworkId - ? activeCaipNetworkId.split(':')[1] + const namespace = StorageUtil.getActiveNamespace() + const caipNetworkId = StorageUtil.getActiveCaipNetworkId() as CaipNetworkId | undefined + const stringChainId = caipNetworkId ? caipNetworkId.split(':')[1] : undefined + + // eslint-disable-next-line no-nested-ternary + const chainId = stringChainId + ? isNaN(Number(stringChainId)) + ? stringChainId + : Number(stringChainId) : undefined return { - namespace: activeNamespace, - caipNetworkId: activeCaipNetworkId, - chainId: caipNetworkIdFromStorage + namespace, + caipNetworkId, + chainId } }, From 4211cfc79ef0dcd9f0e59329a8201d7aa69b6034 Mon Sep 17 00:00:00 2001 From: Karandeep Singh <90941366+KannuSingh@users.noreply.github.com> Date: Mon, 20 Jan 2025 02:54:11 -0500 Subject: [PATCH 6/9] update donut contract address (#3645) Co-authored-by: Enes --- .../Ethers/Ethers5WriteContractTest.tsx | 2 +- .../Ethers/EthersWriteContractTest.tsx | 2 +- ...WagmiPurchaseDonutAsyncPermissionsTest.tsx | 2 +- .../WagmiPurchaseDonutSyncPermissionsTest.tsx | 2 +- ...WagmiSendCallsWithPaymasterServiceTest.tsx | 44 ++++++++++++++----- .../Wagmi/WagmiWriteContractTest.tsx | 2 +- apps/laboratory/src/utils/DonutContract.ts | 10 ++++- 7 files changed, 47 insertions(+), 17 deletions(-) diff --git a/apps/laboratory/src/components/Ethers/Ethers5WriteContractTest.tsx b/apps/laboratory/src/components/Ethers/Ethers5WriteContractTest.tsx index fb0064155c..3853577053 100644 --- a/apps/laboratory/src/components/Ethers/Ethers5WriteContractTest.tsx +++ b/apps/laboratory/src/components/Ethers/Ethers5WriteContractTest.tsx @@ -32,7 +32,7 @@ export function Ethers5WriteContractTest() { const signer = provider.getSigner(address) const contract = new ethers.Contract(donutAddress, abi, signer) // @ts-expect-error ethers types are correct - const tx = await contract.purchase(1, { value: ethers.parseEther('0.0001') }) + const tx = await contract.purchase(1, { value: ethers.parseEther('0.00001') }) toast({ title: 'Success', description: tx.hash, diff --git a/apps/laboratory/src/components/Ethers/EthersWriteContractTest.tsx b/apps/laboratory/src/components/Ethers/EthersWriteContractTest.tsx index 70f34a22ea..35dd221aa5 100644 --- a/apps/laboratory/src/components/Ethers/EthersWriteContractTest.tsx +++ b/apps/laboratory/src/components/Ethers/EthersWriteContractTest.tsx @@ -31,7 +31,7 @@ export function EthersWriteContractTest() { const signer = new JsonRpcSigner(provider, address) const contract = new ethers.Contract(donutAddress, abi, signer) // @ts-expect-error ethers types are correct - const tx = await contract.purchase(1, { value: ethers.parseEther('0.0001') }) + const tx = await contract.purchase(1, { value: ethers.parseEther('0.00001') }) toast({ title: 'Success', description: tx.hash, diff --git a/apps/laboratory/src/components/Wagmi/WagmiPurchaseDonutAsyncPermissionsTest.tsx b/apps/laboratory/src/components/Wagmi/WagmiPurchaseDonutAsyncPermissionsTest.tsx index a0a42549ab..4ddaf07f65 100644 --- a/apps/laboratory/src/components/Wagmi/WagmiPurchaseDonutAsyncPermissionsTest.tsx +++ b/apps/laboratory/src/components/Wagmi/WagmiPurchaseDonutAsyncPermissionsTest.tsx @@ -71,7 +71,7 @@ function ConnectedTestContent({ const purchaseDonutCallDataExecution = [ { to: donutContractaddress as `0x${string}`, - value: parseEther('0.0001'), + value: parseEther('0.00001'), data: purchaseDonutCallData } ] diff --git a/apps/laboratory/src/components/Wagmi/WagmiPurchaseDonutSyncPermissionsTest.tsx b/apps/laboratory/src/components/Wagmi/WagmiPurchaseDonutSyncPermissionsTest.tsx index c200920a0e..15e1e1a2ee 100644 --- a/apps/laboratory/src/components/Wagmi/WagmiPurchaseDonutSyncPermissionsTest.tsx +++ b/apps/laboratory/src/components/Wagmi/WagmiPurchaseDonutSyncPermissionsTest.tsx @@ -69,7 +69,7 @@ function ConnectedTestContent({ const purchaseDonutCallDataExecution = [ { to: donutContractaddress as `0x${string}`, - value: parseEther('0.0001'), + value: parseEther('0.00001'), data: purchaseDonutCallData } ] diff --git a/apps/laboratory/src/components/Wagmi/WagmiSendCallsWithPaymasterServiceTest.tsx b/apps/laboratory/src/components/Wagmi/WagmiSendCallsWithPaymasterServiceTest.tsx index 64c3b80fb5..278a24fd46 100644 --- a/apps/laboratory/src/components/Wagmi/WagmiSendCallsWithPaymasterServiceTest.tsx +++ b/apps/laboratory/src/components/Wagmi/WagmiSendCallsWithPaymasterServiceTest.tsx @@ -8,7 +8,12 @@ import { useSendCalls } from 'wagmi/experimental' import { useAppKitAccount } from '@reown/appkit/react' import { useWagmiAvailableCapabilities } from '../../hooks/useWagmiActiveCapabilities' -import { abi as donutContractAbi, address as donutContractaddress } from '../../utils/DonutContract' +import { + abi as donutContractAbi, + donutContractSupportedChains, + donutContractSupportedChainsName, + address as donutContractaddress +} from '../../utils/DonutContract' import { EIP_5792_RPC_METHODS, WALLET_CAPABILITIES } from '../../utils/EIP5792Utils' import { useChakraToast } from '../Toast' @@ -20,7 +25,7 @@ const purchaseDonutCallData = encodeFunctionData({ const TEST_TX = { to: donutContractaddress as `0x${string}`, - value: parseEther('0.0001'), + value: parseEther('0.00001'), data: purchaseDonutCallData } @@ -38,16 +43,33 @@ const BICONOMY_PAYMASTER_CONTEXT = { } export function WagmiSendCallsWithPaymasterServiceTest() { - const { provider, supportedChains, supportedChainsName, currentChainsInfo, supported } = - useWagmiAvailableCapabilities({ - capability: WALLET_CAPABILITIES.PAYMASTER_SERVICE, - method: EIP_5792_RPC_METHODS.WALLET_SEND_CALLS - }) + const { + provider, + supportedChains: capabilitySupportedChains, + currentChainsInfo, + supported + } = useWagmiAvailableCapabilities({ + capability: WALLET_CAPABILITIES.PAYMASTER_SERVICE, + method: EIP_5792_RPC_METHODS.WALLET_SEND_CALLS + }) const { address } = useAppKitAccount() const { status } = useAccount() const isConnected = status === 'connected' + const isFeatureSupported = useMemo( + () => + currentChainsInfo && + donutContractSupportedChains.some(chain => chain.id === currentChainsInfo.chainId), + [currentChainsInfo] + ) + + const doWalletSupportCapability = useMemo( + () => + currentChainsInfo && + capabilitySupportedChains.some(chain => chain.chainId === currentChainsInfo.chainId), + [capabilitySupportedChains] + ) if (!isConnected || !provider || !address) { return ( @@ -65,18 +87,18 @@ export function WagmiSendCallsWithPaymasterServiceTest() { ) } - if (supportedChains.length === 0) { + if (!isFeatureSupported) { return ( - Account does not support paymaster service feature + Switch to {donutContractSupportedChainsName} to test this feature ) } - if (!currentChainsInfo) { + if (!doWalletSupportCapability) { return ( - Switch to {supportedChainsName} to test this feature + Account does not support paymaster service feature on {currentChainsInfo?.chainName} ) } diff --git a/apps/laboratory/src/components/Wagmi/WagmiWriteContractTest.tsx b/apps/laboratory/src/components/Wagmi/WagmiWriteContractTest.tsx index 1d5c8377ff..3127b16378 100644 --- a/apps/laboratory/src/components/Wagmi/WagmiWriteContractTest.tsx +++ b/apps/laboratory/src/components/Wagmi/WagmiWriteContractTest.tsx @@ -49,7 +49,7 @@ function AvailableTestContent({ accountAddress }: { accountAddress: string | und abi, address, functionName: 'purchase', - value: parseEther('0.0001'), + value: parseEther('0.00001'), args: [1], query: { enabled: false diff --git a/apps/laboratory/src/utils/DonutContract.ts b/apps/laboratory/src/utils/DonutContract.ts index 70d7f17faa..029fa344b5 100644 --- a/apps/laboratory/src/utils/DonutContract.ts +++ b/apps/laboratory/src/utils/DonutContract.ts @@ -1,3 +1,5 @@ +import { base, baseSepolia, optimism, sepolia } from 'viem/chains' + export const abi = [ { inputs: [ @@ -70,4 +72,10 @@ export const abi = [ } ] -export const address = '0x2E65BAfA07238666c3b239E94F32DaD3cDD6498D' +export const address = '0x59d6578C1D8FEb2f892f68D4f76f98511F831c6F' + +// Deployed on sepolia, base-seoplia, base-mainnet, optimism-mainnet. +export const donutContractSupportedChains = [sepolia, baseSepolia, base, optimism] +export const donutContractSupportedChainsName = donutContractSupportedChains + .map(chain => chain.name) + .join(', ') From 25a97c66fe47c2c1d19cf8bbf5c5474612cd6e7b Mon Sep 17 00:00:00 2001 From: tomiir Date: Mon, 20 Jan 2025 10:30:42 +0100 Subject: [PATCH 7/9] fix: cloudauth siwx address match (#3670) --- .changeset/serious-ravens-appear.md | 23 ++++++++++++++++ packages/siwx/src/configs/CloudAuthSIWX.ts | 5 +++- .../siwx/tests/configs/CloudAuthSIWX.test.ts | 27 +++++++++++++++++++ 3 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 .changeset/serious-ravens-appear.md diff --git a/.changeset/serious-ravens-appear.md b/.changeset/serious-ravens-appear.md new file mode 100644 index 0000000000..bc77407a11 --- /dev/null +++ b/.changeset/serious-ravens-appear.md @@ -0,0 +1,23 @@ +--- +'@reown/appkit-siwx': patch +'@reown/appkit-adapter-bitcoin': patch +'@reown/appkit-adapter-ethers': patch +'@reown/appkit-adapter-ethers5': patch +'@reown/appkit-adapter-solana': patch +'@reown/appkit-adapter-wagmi': patch +'@reown/appkit': patch +'@reown/appkit-utils': patch +'@reown/appkit-cdn': patch +'@reown/appkit-cli': patch +'@reown/appkit-common': patch +'@reown/appkit-core': patch +'@reown/appkit-experimental': patch +'@reown/appkit-polyfills': patch +'@reown/appkit-scaffold-ui': patch +'@reown/appkit-siwe': patch +'@reown/appkit-ui': patch +'@reown/appkit-wallet': patch +'@reown/appkit-wallet-button': patch +--- + +Fixes issue where 1CA session would not be found because of non-cased addresses mismatching.' diff --git a/packages/siwx/src/configs/CloudAuthSIWX.ts b/packages/siwx/src/configs/CloudAuthSIWX.ts index 123eff245f..d1f341a48a 100644 --- a/packages/siwx/src/configs/CloudAuthSIWX.ts +++ b/packages/siwx/src/configs/CloudAuthSIWX.ts @@ -72,7 +72,10 @@ export class CloudAuthSIWX implements SIWXConfig { const siweCaipNetworkId = `eip155:${siweSession?.chainId}` - if (!siweSession || siweCaipNetworkId !== chainId || siweSession.address !== address) { + const isSameAddress = siweSession?.address.toLowerCase() === address.toLowerCase() + const isSameNetwork = siweCaipNetworkId === chainId + + if (!isSameAddress || !isSameNetwork) { return [] } diff --git a/packages/siwx/tests/configs/CloudAuthSIWX.test.ts b/packages/siwx/tests/configs/CloudAuthSIWX.test.ts index c33487615b..bd4476657b 100644 --- a/packages/siwx/tests/configs/CloudAuthSIWX.test.ts +++ b/packages/siwx/tests/configs/CloudAuthSIWX.test.ts @@ -241,6 +241,33 @@ Issued At: 2024-12-05T16:02:32.905Z`) ) }) + it('gets sessions when address is not lowercased', async () => { + const fetchSpy = vi.spyOn(global, 'fetch') + + fetchSpy.mockResolvedValueOnce( + mocks.mockFetchResponse({ + address: '0x1234567890abcdef1234567890abcdef12345678', + chainId: 1 + }) + ) + + const sessions = await siwx.getSessions( + 'eip155:1', + '0x1234567890ABCDEF1234567890abcdef12345678' + ) + + expect(sessions).toEqual([ + { + data: { + accountAddress: '0x1234567890abcdef1234567890abcdef12345678', + chainId: 'eip155:1' + }, + message: '', + signature: '' + } + ]) + }) + it('returns empty array if session is not found', async () => { const fetchSpy = vi.spyOn(global, 'fetch') From 98ad777c5de798ae549ad4bac10b6ced7cda18b1 Mon Sep 17 00:00:00 2001 From: MK <53529533+magiziz@users.noreply.github.com> Date: Mon, 20 Jan 2025 10:29:58 +0000 Subject: [PATCH 8/9] fix: `ProviderUtil.getProvider('eip155')` returns `undefined` when using wagmi adapter (#3672) Co-authored-by: tomiir --- .changeset/selfish-rivers-shout.md | 23 +++++++++ packages/adapters/wagmi/src/client.ts | 11 +++-- .../adapters/wagmi/src/tests/client.test.ts | 47 +++++++++++++++++-- 3 files changed, 75 insertions(+), 6 deletions(-) create mode 100644 .changeset/selfish-rivers-shout.md diff --git a/.changeset/selfish-rivers-shout.md b/.changeset/selfish-rivers-shout.md new file mode 100644 index 0000000000..29764b5c42 --- /dev/null +++ b/.changeset/selfish-rivers-shout.md @@ -0,0 +1,23 @@ +--- +'@reown/appkit-adapter-wagmi': patch +'@reown/appkit-adapter-bitcoin': patch +'@reown/appkit-adapter-ethers': patch +'@reown/appkit-adapter-ethers5': patch +'@reown/appkit-adapter-solana': patch +'@reown/appkit': patch +'@reown/appkit-utils': patch +'@reown/appkit-cdn': patch +'@reown/appkit-cli': patch +'@reown/appkit-common': patch +'@reown/appkit-core': patch +'@reown/appkit-experimental': patch +'@reown/appkit-polyfills': patch +'@reown/appkit-scaffold-ui': patch +'@reown/appkit-siwe': patch +'@reown/appkit-siwx': patch +'@reown/appkit-ui': patch +'@reown/appkit-wallet': patch +'@reown/appkit-wallet-button': patch +--- + +Fixed an issue where `walletProvider` from the `useAppKitProvider` hook was `undefined` when the wallet was connected. This issue occurred only when using wagmi adapter. diff --git a/packages/adapters/wagmi/src/client.ts b/packages/adapters/wagmi/src/client.ts index b5855dc6d1..037db3d43d 100644 --- a/packages/adapters/wagmi/src/client.ts +++ b/packages/adapters/wagmi/src/client.ts @@ -410,7 +410,7 @@ export class WagmiAdapter extends AdapterBlueprint { return formatUnits(params.value, params.decimals) } - private addWagmiConnector(connector: Connector, options: AppKitOptions) { + private async addWagmiConnector(connector: Connector, options: AppKitOptions) { /* * We don't need to set auth connector or walletConnect connector * from wagmi since we already set it in chain adapter blueprint @@ -422,6 +422,8 @@ export class WagmiAdapter extends AdapterBlueprint { return } + const provider = (await connector.getProvider().catch(() => undefined)) as Provider | undefined + this.addConnector({ id: connector.id, explorerId: PresetsUtil.ConnectorExplorerIds[connector.id], @@ -433,17 +435,20 @@ export class WagmiAdapter extends AdapterBlueprint { connector.id === CommonConstantsUtil.CONNECTOR_ID.INJECTED ? undefined : { rdns: connector.id }, + provider, chain: this.namespace as ChainNamespace, chains: [] }) } - public syncConnectors(options: AppKitOptions, appKit: AppKit) { + public async syncConnectors(options: AppKitOptions, appKit: AppKit) { // Add wagmi connectors this.addWagmiConnectors(options, appKit) // Add current wagmi connectors to chain adapter blueprint - this.wagmiConfig.connectors.forEach(connector => this.addWagmiConnector(connector, options)) + await Promise.all( + this.wagmiConfig.connectors.map(connector => this.addWagmiConnector(connector, options)) + ) /* * Watch for new connectors. This is needed because some EIP6963 diff --git a/packages/adapters/wagmi/src/tests/client.test.ts b/packages/adapters/wagmi/src/tests/client.test.ts index 8cee2c208d..b70c70e415 100644 --- a/packages/adapters/wagmi/src/tests/client.test.ts +++ b/packages/adapters/wagmi/src/tests/client.test.ts @@ -66,7 +66,10 @@ const mockCaipNetworks = CaipNetworksUtil.extendCaipNetworks(mockNetworks, { const mockWagmiConfig = { connectors: [ { - id: 'test-connector' + id: 'test-connector', + getProvider() { + return Promise.resolve({ connect: vi.fn(), request: vi.fn() }) + } } ], _internal: { @@ -101,10 +104,13 @@ describe('WagmiAdapter', () => { expect(adapter.namespace).toBe('eip155') }) - it('should set wagmi connectors', () => { + it('should set wagmi connectors', async () => { vi.spyOn(wagmiCore, 'watchConnectors').mockImplementation(vi.fn()) - adapter.syncConnectors({ networks: [mainnet], projectId: 'YOUR_PROJECT_ID' }, mockAppKit) + await adapter.syncConnectors( + { networks: [mainnet], projectId: 'YOUR_PROJECT_ID' }, + mockAppKit + ) expect(adapter.connectors).toStrictEqual([ { @@ -115,6 +121,10 @@ describe('WagmiAdapter', () => { imageId: undefined, imageUrl: undefined, info: { rdns: 'test-connector' }, + provider: { + connect: expect.any(Function), + request: expect.any(Function) + }, name: undefined, type: 'EXTERNAL' } @@ -156,6 +166,37 @@ describe('WagmiAdapter', () => { `https://cloudflare-eth.com` ) }) + + it('should add connector with provider', async () => { + const mockConnector = { + id: 'injected', + name: 'Injected Wallet', + type: 'injected', + getProvider() { + return Promise.resolve({ connect: vi.fn(), request: vi.fn() }) + } + } as unknown as wagmiCore.Connector + + await (adapter as any).addWagmiConnector(mockConnector) + + expect(adapter.connectors).toStrictEqual([ + { + chain: 'eip155', + chains: [], + explorerId: undefined, + id: 'injected', + imageId: '07ba87ed-43aa-4adf-4540-9e6a2b9cae00', + imageUrl: undefined, + info: undefined, + name: 'Browser Wallet', + provider: { + connect: expect.any(Function), + request: expect.any(Function) + }, + type: 'INJECTED' + } + ]) + }) }) describe('WagmiAdapter - signMessage', () => { From 1370e40f9ba2a454379e715b561c0414a5147f6b Mon Sep 17 00:00:00 2001 From: Felipe Mendes Date: Mon, 20 Jan 2025 11:52:59 -0300 Subject: [PATCH 9/9] fix: add safari ios to prefetch wc uri (#3671) --- apps/builder/providers/appkit-provider.tsx | 2 +- .../src/controllers/ConnectionController.ts | 23 ++++------- packages/core/src/utils/CoreHelperUtil.ts | 22 ++++++++++- .../w3m-connecting-wc-mobile/index.ts | 13 +++++-- .../src/utils/w3m-connecting-widget/index.ts | 8 +++- .../src/views/w3m-connect-view/index.ts | 38 +++++++++---------- .../partials/w3m-connecting-wc-mobile.test.ts | 4 +- 7 files changed, 65 insertions(+), 45 deletions(-) diff --git a/apps/builder/providers/appkit-provider.tsx b/apps/builder/providers/appkit-provider.tsx index ff127109d6..836d288407 100644 --- a/apps/builder/providers/appkit-provider.tsx +++ b/apps/builder/providers/appkit-provider.tsx @@ -24,7 +24,7 @@ if (!projectId) { const metadata = { name: 'AppKit Builder', description: 'The full stack toolkit to build onchain app UX', - url: 'https://github.com/0xonerb/next-reown-appkit-ssr', // origin must match your domain & subdomain + url: window?.origin || 'https://demo.reown.com', // origin must match your domain & subdomain icons: ['https://avatars.githubusercontent.com/u/179229932'] } diff --git a/packages/core/src/controllers/ConnectionController.ts b/packages/core/src/controllers/ConnectionController.ts index a708209c09..af2c9f8024 100644 --- a/packages/core/src/controllers/ConnectionController.ts +++ b/packages/core/src/controllers/ConnectionController.ts @@ -106,13 +106,9 @@ export const ConnectionController = { StorageUtil.setConnectedConnectorId(namespace, ConstantsUtil.CONNECTOR_ID.WALLET_CONNECT) }) - if (CoreHelperUtil.isTelegram()) { + if (CoreHelperUtil.isTelegram() || (CoreHelperUtil.isSafari() && CoreHelperUtil.isIos())) { if (wcConnectionPromise) { - try { - await wcConnectionPromise - } catch (error) { - /* Empty */ - } + await wcConnectionPromise wcConnectionPromise = undefined return @@ -124,15 +120,12 @@ export const ConnectionController = { return } - wcConnectionPromise = new Promise(async (resolve, reject) => { - await this._getClient() - ?.connectWalletConnect?.(uri => { - state.wcUri = uri - state.wcPairingExpiry = CoreHelperUtil.getPairingExpiry() - }) - .catch(reject) - resolve() - }) + wcConnectionPromise = this._getClient() + ?.connectWalletConnect?.(uri => { + state.wcUri = uri + state.wcPairingExpiry = CoreHelperUtil.getPairingExpiry() + }) + .catch(() => undefined) this.state.status = 'connecting' await wcConnectionPromise wcConnectionPromise = undefined diff --git a/packages/core/src/utils/CoreHelperUtil.ts b/packages/core/src/utils/CoreHelperUtil.ts index 6fccf5e298..a629fd5c60 100644 --- a/packages/core/src/utils/CoreHelperUtil.ts +++ b/packages/core/src/utils/CoreHelperUtil.ts @@ -10,7 +10,7 @@ type OpenTarget = '_blank' | '_self' | 'popupWindow' | '_top' export const CoreHelperUtil = { isMobile() { - if (typeof window !== 'undefined') { + if (this.isClient()) { return Boolean( window.matchMedia('(pointer:coarse)').matches || /Android|webOS|iPhone|iPad|iPod|BlackBerry|Opera Mini/u.test(navigator.userAgent) @@ -25,15 +25,33 @@ export const CoreHelperUtil = { }, isAndroid() { + if (!this.isMobile()) { + return false + } + const ua = window.navigator.userAgent.toLowerCase() return CoreHelperUtil.isMobile() && ua.includes('android') }, isIos() { + if (!this.isMobile()) { + return false + } + + const ua = window.navigator.userAgent.toLowerCase() + + return ua.includes('iphone') || ua.includes('ipad') + }, + + isSafari() { + if (!this.isClient()) { + return false + } + const ua = window.navigator.userAgent.toLowerCase() - return CoreHelperUtil.isMobile() && (ua.includes('iphone') || ua.includes('ipad')) + return ua.includes('safari') }, isClient() { diff --git a/packages/scaffold-ui/src/partials/w3m-connecting-wc-mobile/index.ts b/packages/scaffold-ui/src/partials/w3m-connecting-wc-mobile/index.ts index 331b9fb214..3e5ee0342d 100644 --- a/packages/scaffold-ui/src/partials/w3m-connecting-wc-mobile/index.ts +++ b/packages/scaffold-ui/src/partials/w3m-connecting-wc-mobile/index.ts @@ -20,8 +20,6 @@ export class W3mConnectingWcMobile extends W3mConnectingWidget { } this.secondaryBtnLabel = undefined this.secondaryLabel = ConstantsUtil.CONNECT_LABELS.MOBILE - this.onConnect = this.onConnectProxy.bind(this) - this.onRender = this.onRenderProxy.bind(this) document.addEventListener('visibilitychange', this.onBuffering.bind(this)) EventsController.sendEvent({ type: 'track', @@ -45,14 +43,14 @@ export class W3mConnectingWcMobile extends W3mConnectingWidget { } // -- Private ------------------------------------------- // - private onRenderProxy() { + protected override onRender = () => { if (!this.ready && this.uri) { this.ready = true this.onConnect?.() } } - private onConnectProxy() { + protected override onConnect = () => { if (this.wallet?.mobile_link && this.uri) { try { this.error = false @@ -89,6 +87,13 @@ export class W3mConnectingWcMobile extends W3mConnectingWidget { }, 5000) } } + + protected override onTryAgain() { + if (!this.buffering) { + ConnectionController.setWcError(false) + this.onConnect() + } + } } declare global { diff --git a/packages/scaffold-ui/src/utils/w3m-connecting-widget/index.ts b/packages/scaffold-ui/src/utils/w3m-connecting-widget/index.ts index 25da04281c..a01ee2fc1b 100644 --- a/packages/scaffold-ui/src/utils/w3m-connecting-widget/index.ts +++ b/packages/scaffold-ui/src/utils/w3m-connecting-widget/index.ts @@ -78,7 +78,11 @@ export class W3mConnectingWidget extends LitElement { ] ) // The uri should be preloaded in the tg ios context so we can safely init as the subscribeKey won't trigger - if (CoreHelperUtil.isTelegram() && CoreHelperUtil.isIos() && ConnectionController.state.wcUri) { + if ( + (CoreHelperUtil.isTelegram() || CoreHelperUtil.isSafari()) && + CoreHelperUtil.isIos() && + ConnectionController.state.wcUri + ) { this.onConnect?.() } } @@ -187,7 +191,7 @@ export class W3mConnectingWidget extends LitElement { } } - private onTryAgain() { + protected onTryAgain() { if (!this.buffering) { ConnectionController.setWcError(false) if (this.onRetry) { 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 b3647dcbf9..ea97d3ac6a 100644 --- a/packages/scaffold-ui/src/views/w3m-connect-view/index.ts +++ b/packages/scaffold-ui/src/views/w3m-connect-view/index.ts @@ -89,26 +89,26 @@ export class W3mConnectView extends LitElement { public override render() { const { termsConditionsUrl, privacyPolicyUrl } = OptionsController.state - const legalCheckbox = OptionsController.state.features?.legalCheckbox + const isLegalCheckbox = OptionsController.state.features?.legalCheckbox const legalUrl = termsConditionsUrl || privacyPolicyUrl - const showLegalCheckbox = - Boolean(legalUrl) && Boolean(legalCheckbox) && this.walletGuide === 'get-started' + const isShowLegalCheckbox = + Boolean(legalUrl) && Boolean(isLegalCheckbox) && this.walletGuide === 'get-started' - const disabled = showLegalCheckbox && !this.checked + const isDisabled = isShowLegalCheckbox && !this.checked const classes = { connect: true, - disabled + disabled: isDisabled } - const enableWalletGuide = OptionsController.state.enableWalletGuide + const isEnableWalletGuide = OptionsController.state.enableWalletGuide - const enableWallets = this.enableWallets + const isEnableWallets = this.enableWallets const socialOrEmailLoginEnabled = this.isSocialEnabled || this.authConnector - const tabIndex = disabled ? -1 : undefined + const tabIndex = isDisabled ? -1 : undefined return html` @@ -123,8 +123,8 @@ export class W3mConnectView extends LitElement { flexDirection="column" gap="s" .padding=${socialOrEmailLoginEnabled && - enableWallets && - enableWalletGuide && + isEnableWallets && + isEnableWalletGuide && this.walletGuide === 'get-started' ? ['3xs', 's', '0', 's'] : ['3xs', 's', 's', 's']} @@ -132,7 +132,7 @@ export class W3mConnectView extends LitElement { ${this.renderConnectMethod(tabIndex)} - ${this.guideTemplate(disabled)} + ${this.guideTemplate(isDisabled)} ` @@ -248,16 +248,16 @@ export class W3mConnectView extends LitElement { } private walletListTemplate(tabIndex?: number) { - const enableWallets = this.enableWallets - const collapseWalletsOldProp = this.features?.emailShowWallets === false - const collapseWallets = this.features?.collapseWallets - const shouldCollapseWallets = collapseWalletsOldProp || collapseWallets + const isEnableWallets = this.enableWallets + const isCollapseWalletsOldProp = this.features?.emailShowWallets === false + const isCollapseWallets = this.features?.collapseWallets + const shouldCollapseWallets = isCollapseWalletsOldProp || isCollapseWallets - if (!enableWallets) { + if (!isEnableWallets) { return null } // In tg ios context, we have to preload the connection uri so we can use it to deeplink on user click - if (CoreHelperUtil.isTelegram() && CoreHelperUtil.isIos()) { + if ((CoreHelperUtil.isTelegram() || CoreHelperUtil.isSafari()) && CoreHelperUtil.isIos()) { ConnectionController.connectWalletConnect().catch(_e => ({})) } @@ -280,9 +280,9 @@ export class W3mConnectView extends LitElement { } private guideTemplate(disabled = false) { - const enableWalletGuide = OptionsController.state.enableWalletGuide + const isEnableWalletGuide = OptionsController.state.enableWalletGuide - if (!enableWalletGuide) { + if (!isEnableWalletGuide) { return null } diff --git a/packages/scaffold-ui/test/partials/w3m-connecting-wc-mobile.test.ts b/packages/scaffold-ui/test/partials/w3m-connecting-wc-mobile.test.ts index 35520b41f5..b9328f26a6 100644 --- a/packages/scaffold-ui/test/partials/w3m-connecting-wc-mobile.test.ts +++ b/packages/scaffold-ui/test/partials/w3m-connecting-wc-mobile.test.ts @@ -31,7 +31,7 @@ describe('W3mConnectingWcMobile', () => { const el: W3mConnectingWcMobile = await fixture( html`` ) - el['onConnectProxy']() + el['onConnect']() expect(openHrefSpy).toHaveBeenCalledWith(`link://wc?uri=${WC_URI}`, '_self') }) @@ -46,7 +46,7 @@ describe('W3mConnectingWcMobile', () => { const el: W3mConnectingWcMobile = await fixture( html`` ) - el['onConnectProxy']() + el['onConnect']() expect(openHrefSpy).toHaveBeenCalledWith(`link://wc?uri=${WC_URI}`, '_top') } finally {