diff --git a/.changeset/empty-mangos-push.md b/.changeset/empty-mangos-push.md new file mode 100644 index 000000000..7f70026ae --- /dev/null +++ b/.changeset/empty-mangos-push.md @@ -0,0 +1,18 @@ +--- +'@reown/appkit-coinbase-ethers-react-native': patch +'@reown/appkit-coinbase-wagmi-react-native': patch +'@reown/appkit-scaffold-utils-react-native': patch +'@reown/appkit-auth-ethers-react-native': patch +'@reown/appkit-auth-wagmi-react-native': patch +'@reown/appkit-scaffold-react-native': patch +'@reown/appkit-ethers5-react-native': patch +'@reown/appkit-common-react-native': patch +'@reown/appkit-ethers-react-native': patch +'@reown/appkit-wagmi-react-native': patch +'@reown/appkit-core-react-native': patch +'@reown/appkit-siwe-react-native': patch +'@reown/appkit-ui-react-native': patch +'@reown/appkit-wallet-react-native': patch +--- + +fix: load transactions when needed diff --git a/.changeset/light-geese-stare.md b/.changeset/light-geese-stare.md new file mode 100644 index 000000000..a55349ef7 --- /dev/null +++ b/.changeset/light-geese-stare.md @@ -0,0 +1,18 @@ +--- +'@reown/appkit-ethers5-react-native': patch +'@reown/appkit-ethers-react-native': patch +'@reown/appkit-auth-ethers-react-native': patch +'@reown/appkit-auth-wagmi-react-native': patch +'@reown/appkit-coinbase-ethers-react-native': patch +'@reown/appkit-coinbase-wagmi-react-native': patch +'@reown/appkit-common-react-native': patch +'@reown/appkit-core-react-native': patch +'@reown/appkit-scaffold-react-native': patch +'@reown/appkit-scaffold-utils-react-native': patch +'@reown/appkit-siwe-react-native': patch +'@reown/appkit-ui-react-native': patch +'@reown/appkit-wagmi-react-native': patch +'@reown/appkit-wallet-react-native': patch +--- + +fix: show wallet info in useWalletInfo hook for ethers and ethers5 diff --git a/.changeset/purple-berries-marry.md b/.changeset/purple-berries-marry.md new file mode 100644 index 000000000..5570df13f --- /dev/null +++ b/.changeset/purple-berries-marry.md @@ -0,0 +1,18 @@ +--- +'@reown/appkit-core-react-native': patch +'@reown/appkit-auth-ethers-react-native': patch +'@reown/appkit-auth-wagmi-react-native': patch +'@reown/appkit-coinbase-ethers-react-native': patch +'@reown/appkit-coinbase-wagmi-react-native': patch +'@reown/appkit-common-react-native': patch +'@reown/appkit-ethers-react-native': patch +'@reown/appkit-ethers5-react-native': patch +'@reown/appkit-scaffold-react-native': patch +'@reown/appkit-scaffold-utils-react-native': patch +'@reown/appkit-siwe-react-native': patch +'@reown/appkit-ui-react-native': patch +'@reown/appkit-wagmi-react-native': patch +'@reown/appkit-wallet-react-native': patch +--- + +fix: improved state array updates in connection and router controllers diff --git a/.changeset/witty-melons-brush.md b/.changeset/witty-melons-brush.md new file mode 100644 index 000000000..343f1f66d --- /dev/null +++ b/.changeset/witty-melons-brush.md @@ -0,0 +1,18 @@ +--- +'@reown/appkit-coinbase-ethers-react-native': patch +'@reown/appkit-coinbase-wagmi-react-native': patch +'@reown/appkit-scaffold-utils-react-native': patch +'@reown/appkit-auth-ethers-react-native': patch +'@reown/appkit-auth-wagmi-react-native': patch +'@reown/appkit-scaffold-react-native': patch +'@reown/appkit-ethers5-react-native': patch +'@reown/appkit-common-react-native': patch +'@reown/appkit-ethers-react-native': patch +'@reown/appkit-wagmi-react-native': patch +'@reown/appkit-core-react-native': patch +'@reown/appkit-siwe-react-native': patch +'@reown/appkit-ui-react-native': patch +'@reown/appkit-wallet-react-native': patch +--- + +chore: send expo info in useragent diff --git a/.maestro/w3m-connect-flow.yaml b/.maestro/w3m-connect-flow.yaml index 155431e5a..ac7a4d80e 100644 --- a/.maestro/w3m-connect-flow.yaml +++ b/.maestro/w3m-connect-flow.yaml @@ -57,7 +57,7 @@ appId: com.walletconnect.web3modal.rnclisdk id: 'account-button' - tapOn: - id: 'w3m-account-select-network' + id: 'button-network' - tapOn: text: 'Polygon' @@ -68,7 +68,7 @@ appId: com.walletconnect.web3modal.rnclisdk text: 'Polygon' - tapOn: - id: 'disconnect-button' + id: 'button-disconnect' - assertVisible: id: 'connect-button' diff --git a/apps/native/tests/shared/constants/index.ts b/apps/native/tests/shared/constants/index.ts index dc2550fef..77f3b75c2 100644 --- a/apps/native/tests/shared/constants/index.ts +++ b/apps/native/tests/shared/constants/index.ts @@ -8,4 +8,20 @@ export const DEFAULT_SESSION_PARAMS: SessionParams = { optAccounts: ['1', '2'], accept: true }; -export const DEFAULT_CHAIN_NAME = process.env.DEFAULT_CHAIN_NAME || 'Ethereum'; + +export const TEST_CHAINS = { + POLYGON: 'Polygon', + ETHEREUM: 'Ethereum', + GNOSIS: 'Gnosis' +} as const; + +export type SupportedChain = (typeof TEST_CHAINS)[keyof typeof TEST_CHAINS]; + +export const TIMEOUTS = { + ANIMATION: 300, + NETWORK_SWITCH: 500, + CONNECTION: 5000, + SESSION_PROPOSAL: 30000 +} as const; + +export const DEFAULT_CHAIN_NAME = process.env.DEFAULT_CHAIN_NAME || TEST_CHAINS.ETHEREUM; diff --git a/apps/native/tests/shared/constants/timeouts.ts b/apps/native/tests/shared/constants/timeouts.ts deleted file mode 100644 index 03ff123ce..000000000 --- a/apps/native/tests/shared/constants/timeouts.ts +++ /dev/null @@ -1 +0,0 @@ -export const MAXIMUM_WAIT_CONNECTIONS = 30 * 1000; diff --git a/apps/native/tests/shared/pages/ModalPage.ts b/apps/native/tests/shared/pages/ModalPage.ts index a8840f21a..b7e6f1e71 100644 --- a/apps/native/tests/shared/pages/ModalPage.ts +++ b/apps/native/tests/shared/pages/ModalPage.ts @@ -1,9 +1,9 @@ import type { Locator, Page } from '@playwright/test'; import { expect } from '@playwright/test'; -import { BASE_URL, DEFAULT_SESSION_PARAMS } from '../constants'; +import { BASE_URL, DEFAULT_SESSION_PARAMS, TIMEOUTS } from '../constants'; import { WalletValidator } from '../validators/WalletValidator'; import { WalletPage } from './WalletPage'; -import { TimingRecords } from '../types'; +import { SupportedChain, TimingRecords } from '../types'; import { ModalValidator } from '../validators/ModalValidator'; export class ModalPage { @@ -92,7 +92,7 @@ export class ModalPage { await expect(accountBtn, 'Account button should be visible').toBeVisible(); await expect(accountBtn, 'Account button should be enabled').toBeEnabled(); await accountBtn.click(); - const disconnectBtn = this.page.getByTestId('disconnect-button'); + const disconnectBtn = this.page.getByTestId('button-disconnect'); await expect(disconnectBtn, 'Disconnect button should be visible').toBeVisible(); await expect(disconnectBtn, 'Disconnect button should be enabled').toBeEnabled(); await disconnectBtn.click(); @@ -125,20 +125,12 @@ export class ModalPage { // await this.page.getByTestId('w3m-connecting-siwe-cancel').click(); // } - async switchNetwork(network: string) { - await this.openAccountModal(); - await this.page.getByTestId('w3m-account-select-network').click(); + async switchNetwork(network: SupportedChain) { await this.page.getByTestId(`w3m-network-switch-${network}`).click(); - // The state is chaing too fast and test runner doesn't wait the loading page. It's fastly checking the network selection button and detect that it's switched already. - await this.page.waitForTimeout(300); + // The state is changing too fast and test runner doesn't wait for the loading page + await this.page.waitForTimeout(TIMEOUTS.NETWORK_SWITCH); } - // async clickWalletDeeplink() { - // await this.connectButton.click(); - // await this.page.getByTestId('wallet-selector-react-wallet-v2').click(); - // await this.page.getByTestId('tab-desktop').click(); - // } - async openAccountModal() { await this.page.getByTestId('account-button').click(); } @@ -154,17 +146,9 @@ export class ModalPage { async closeModal() { await this.page.getByTestId('header-close')?.click?.(); // Wait for the modal fade out animation - await this.page.waitForTimeout(300); + await this.page.waitForTimeout(TIMEOUTS.ANIMATION); } - // async switchNetworkWithNetworkButton(networkName: string) { - // const networkButton = this.page.getByTestId('wui-network-button'); - // await networkButton.click(); - - // const networkToSwitchButton = this.page.getByTestId(`w3m-network-switch-${networkName}`); - // await networkToSwitchButton.click(); - // } - async openAllWallets() { const allWallets = this.page.getByTestId('all-wallets'); await expect(allWallets, 'All wallets should be visible').toBeVisible(); @@ -177,18 +161,6 @@ export class ModalPage { await qrCodeButton.click(); } - // async clickAllWalletsListSearchItem(id: string) { - // const allWalletsListSearchItem = this.page.getByTestId(`wallet-search-item-${id}`); - // await expect(allWalletsListSearchItem).toBeVisible(); - // await allWalletsListSearchItem.click(); - // } - - // async clickTabWebApp() { - // const tabWebApp = this.page.getByTestId('tab-webapp'); - // await expect(tabWebApp).toBeVisible(); - // await tabWebApp.click(); - // } - async clickHookDisconnectButton() { const disconnectHookButton = this.page.getByTestId('disconnect-hook-button'); await expect(disconnectHookButton).toBeVisible(); @@ -216,33 +188,6 @@ export class ModalPage { return this.page.evaluate(() => navigator.clipboard.readText()); } - // async clickOpenWebApp() { - // let url = ''; - - // const openButton = this.page.getByTestId('w3m-connecting-widget-secondary-button'); - // await expect(openButton).toBeVisible(); - // await expect(openButton).toHaveText('Open'); - - // while (!url) { - // await openButton.click(); - // await this.page.waitForTimeout(500); - - // const pages = this.page.context().pages(); - - // // Check if more than 1 tab is open - // if (pages.length > 1) { - // const lastTab = pages[pages.length - 1]; - - // if (lastTab) { - // url = lastTab.url(); - // break; - // } - // } - // } - - // return url; - // } - async search(value: string) { const searchInput = this.page.getByTestId('wui-input-text'); await expect(searchInput, 'Search input should be visible').toBeVisible(); @@ -250,30 +195,30 @@ export class ModalPage { await searchInput.fill(value); } - async openNetworks() { - await this.page.getByTestId('w3m-account-select-network').click(); + async goToNetworks() { + await this.page.getByTestId('button-network').click(); await expect(this.page.getByText('Select network')).toBeVisible(); } - // async openProfileView() { - // await this.page.getByTestId('wui-profile-button').click(); - // } - - // async getAddress(): Promise<`0x${string}`> { - // const address = await this.page.getByTestId('w3m-address').textContent(); - // expect(address, 'Address should be present').toBeTruthy(); - - // return address as `0x${string}`; - // } + async goToActivity() { + await this.page.getByTestId('button-activity').click(); + } - // async getChainId(): Promise { - // const chainId = await this.page.getByTestId('w3m-chain-id').textContent(); - // expect(chainId, 'Chain ID should be present').toBeTruthy(); + async goBack() { + await this.page.getByTestId('button-back').click(); + } - // return Number(chainId); - // } + async expectLoaderVisible() { + await expect( + this.page.getByTestId('loading-spinner'), + 'Loading spinner should be visible' + ).toBeVisible(); + } - // async switchNetworkWithHook() { - // await this.page.getByTestId('switch-network-hook-button').click(); - // } + async expectLoaderHidden() { + await expect( + this.page.getByTestId('loading-spinner'), + 'Loading spinner should be hidden' + ).toBeHidden(); + } } diff --git a/apps/native/tests/shared/pages/WalletPage.ts b/apps/native/tests/shared/pages/WalletPage.ts index 49f5a2038..44a48135d 100644 --- a/apps/native/tests/shared/pages/WalletPage.ts +++ b/apps/native/tests/shared/pages/WalletPage.ts @@ -117,9 +117,23 @@ export class WalletPage { await this.page.waitForLoadState(); const sessionsButton = this.page.getByTestId('sessions'); await sessionsButton.click(); - const sessionCard = this.page.getByTestId(`session-card`); - await sessionCard.click(); - const disconnectButton = this.page.getByText('Delete'); - await disconnectButton.click(); + + // Try to disconnect all visible session cards + while (true) { + const sessionCards = this.page.getByTestId('session-card'); + const count = await sessionCards.count(); + + if (count === 0) { + break; + } + + // Click the first card and disconnect it + await sessionCards.first().click(); + const disconnectButton = this.page.getByText('Delete'); + await disconnectButton.click(); + + // Wait a bit for the disconnection to complete + await this.page.waitForTimeout(500); + } } } diff --git a/apps/native/tests/shared/types/index.ts b/apps/native/tests/shared/types/index.ts index abf6bc54f..05d6a4a01 100644 --- a/apps/native/tests/shared/types/index.ts +++ b/apps/native/tests/shared/types/index.ts @@ -1,3 +1,5 @@ +import { TEST_CHAINS } from '../constants'; + export interface SessionParams { reqAccounts: string[]; optAccounts: string[]; @@ -7,3 +9,5 @@ export interface SessionParams { export type TimingRecords = { item: string; timeMs: number }[]; export type CaipNetworkId = `${string}:${string}`; + +export type SupportedChain = (typeof TEST_CHAINS)[keyof typeof TEST_CHAINS]; diff --git a/apps/native/tests/shared/utils/timeouts.ts b/apps/native/tests/shared/utils/timeouts.ts index a74e5eee4..d1d154cbf 100644 --- a/apps/native/tests/shared/utils/timeouts.ts +++ b/apps/native/tests/shared/utils/timeouts.ts @@ -1,9 +1,9 @@ -import { MAXIMUM_WAIT_CONNECTIONS } from '../constants/timeouts'; +import { TIMEOUTS } from '../constants'; export function getMaximumWaitConnections(): number { if (process.env.CI) { - return MAXIMUM_WAIT_CONNECTIONS; + return TIMEOUTS.SESSION_PROPOSAL; } - return MAXIMUM_WAIT_CONNECTIONS * 2; + return TIMEOUTS.SESSION_PROPOSAL * 2; } diff --git a/apps/native/tests/shared/validators/ModalValidator.ts b/apps/native/tests/shared/validators/ModalValidator.ts index d27de8fbe..113c0c1f5 100644 --- a/apps/native/tests/shared/validators/ModalValidator.ts +++ b/apps/native/tests/shared/validators/ModalValidator.ts @@ -82,16 +82,6 @@ export class ModalValidator { await expect(address, 'Correct address should be present').toHaveText(expectedAddress); } - // async expectNetwork(network: string) { - // const networkButton = this.page.getByTestId('w3m-account-select-network'); - // await expect(networkButton, `Network button should contain text ${network}`).toHaveText( - // network, - // { - // timeout: 5000 - // } - // ); - // } - async expectAcceptedSign() { await expect(this.page.getByText('Signature successful')).toBeVisible({ timeout: 30 * 1000 @@ -103,7 +93,7 @@ export class ModalValidator { } async expectSwitchedNetwork(network: string) { - const switchNetworkButton = this.page.getByTestId(`w3m-account-select-network-text`); + const switchNetworkButton = this.page.getByTestId(`account-select-network-text`); await expect(switchNetworkButton).toContainText(network); } @@ -132,11 +122,6 @@ export class ModalValidator { await expect(getAWalletView).toBeVisible(); } - // async expectHeaderText(text: string) { - // const headerText = this.page.getByTestId('header-text'); - // await expect(headerText).toHaveText(text); - // } - async expectNetworksDisabled(name: string) { const disabledNetworkButton = this.page.getByTestId(`w3m-network-switch-${name}`); disabledNetworkButton.click(); diff --git a/apps/native/tests/wallet.spec.ts b/apps/native/tests/wallet.spec.ts index 6f5eb68ca..c1bc69de9 100644 --- a/apps/native/tests/wallet.spec.ts +++ b/apps/native/tests/wallet.spec.ts @@ -3,7 +3,7 @@ import { WalletPage } from './shared/pages/WalletPage'; import { WalletValidator } from './shared/validators/WalletValidator'; import { ModalPage } from './shared/pages/ModalPage'; import { ModalValidator } from './shared/validators/ModalValidator'; -import { DEFAULT_CHAIN_NAME } from './shared/constants'; +import { DEFAULT_CHAIN_NAME, TEST_CHAINS } from './shared/constants'; let modalPage: ModalPage; let modalValidator: ModalValidator; @@ -37,21 +37,36 @@ sampleWalletTest.afterAll(async () => { }); // -- Tests -------------------------------------------------------------------- +/** + * Connection Tests + * Tests the basic connection functionality including: + * - Page refresh behavior + * - Network switching + * - Disconnection flows + */ + sampleWalletTest('it should be connected instantly after page refresh', async () => { await modalPage.page.reload(); await modalValidator.expectToBeConnectedInstantly(); }); +/** + * Network Tests + * Tests network-related functionality including: + * - Disabled networks visibility + * - Network switching + * - Network persistence after refresh + */ + sampleWalletTest('it should show disabled networks', async () => { - const disabledNetworks = 'Gnosis'; await modalPage.openAccountModal(); - await modalPage.openNetworks(); - await modalValidator.expectNetworksDisabled(disabledNetworks); + await modalPage.goToNetworks(); + await modalValidator.expectNetworksDisabled(TEST_CHAINS.GNOSIS); await modalPage.closeModal(); }); sampleWalletTest('it should switch networks and sign', async () => { - const chains = ['Polygon', 'Ethereum']; + const chains = [TEST_CHAINS.POLYGON, TEST_CHAINS.ETHEREUM]; async function processChain(index: number) { if (index >= chains.length) { @@ -62,6 +77,8 @@ sampleWalletTest('it should switch networks and sign', async () => { // -- Switch network -------------------------------------------------------- const chainNameOnWalletPage = chainName; + await modalPage.openAccountModal(); + await modalPage.goToNetworks(); await modalPage.switchNetwork(chainName); await modalValidator.expectSwitchedNetwork(chainName); await modalPage.closeModal(); @@ -75,32 +92,45 @@ sampleWalletTest('it should switch networks and sign', async () => { await processChain(index + 1); } - // Start processing from the first chain await processChain(0); }); sampleWalletTest('it should show last connected network after refreshing', async () => { - const chainName = 'Polygon'; - - await modalPage.switchNetwork(chainName); - await modalValidator.expectSwitchedNetwork(chainName); + await modalPage.openAccountModal(); + await modalPage.goToNetworks(); + await modalPage.switchNetwork(TEST_CHAINS.POLYGON); + await modalValidator.expectSwitchedNetwork(TEST_CHAINS.POLYGON); await modalPage.closeModal(); await modalPage.page.reload(); await modalPage.openAccountModal(); - await modalValidator.expectSwitchedNetwork(chainName); + await modalValidator.expectSwitchedNetwork(TEST_CHAINS.POLYGON); await modalPage.closeModal(); }); +/** + * Signing Tests + * Tests message signing functionality including: + * - Successful signing flow + * - Rejection flow + */ + sampleWalletTest('it should reject sign', async () => { - const chainName = 'Polygon'; await modalPage.sign(); - await walletValidator.expectReceivedSign({ chainName }); + await walletValidator.expectReceivedSign({ chainName: TEST_CHAINS.POLYGON }); await walletPage.handleRequest({ accept: false }); await modalValidator.expectRejectedSign(); }); +/** + * Disconnection Tests + * Tests various disconnection scenarios including: + * - Hook-based disconnection + * - Wallet-initiated disconnection + * - Manual disconnection + */ + sampleWalletTest('it should disconnect using hook', async () => { await modalValidator.expectConnected(); await modalPage.clickHookDisconnectButton(); @@ -124,3 +154,54 @@ sampleWalletTest('it should disconnect as expected', async () => { await modalPage.disconnect(); await modalValidator.expectDisconnected(); }); + +/** + * Activity Screen Tests + * Tests the Activity screen behavior including: + * - Loader visibility on first visit + * - Loader behavior on subsequent visits + * - Loader behavior after network changes + */ + +sampleWalletTest('shows loader behavior on first visit to Activity screen', async () => { + // Connect to wallet + await modalPage.qrCodeFlow(modalPage, walletPage); + await modalValidator.expectConnected(); + + // First visit to Activity screen + await modalPage.openAccountModal(); + await modalPage.goToActivity(); + await modalPage.expectLoaderVisible(); + await modalPage.expectLoaderHidden(); + + // Second visit to Activity screen + await modalPage.goBack(); + await modalPage.goToActivity(); + await modalPage.expectLoaderHidden(); + + // Third visit after closing the modal + await modalPage.closeModal(); + await modalPage.openAccountModal(); + await modalPage.goToActivity(); + await modalPage.expectLoaderHidden(); + await modalPage.closeModal(); +}); + +sampleWalletTest('shows loader behavior after network change in Activity screen', async () => { + await modalPage.openAccountModal(); + + // Change network + await modalPage.goToNetworks(); + await modalPage.switchNetwork(TEST_CHAINS.POLYGON); + await modalValidator.expectSwitchedNetwork(TEST_CHAINS.POLYGON); + + // Visit Activity screen after network change + await modalPage.goToActivity(); + await modalPage.expectLoaderVisible(); + await modalPage.expectLoaderHidden(); + + // Second visit after network change + await modalPage.goBack(); + await modalPage.goToActivity(); + await modalPage.expectLoaderHidden(); +}); diff --git a/packages/auth-ethers/package.json b/packages/auth-ethers/package.json index 4114a9733..e4741edca 100644 --- a/packages/auth-ethers/package.json +++ b/packages/auth-ethers/package.json @@ -36,6 +36,7 @@ "access": "public" }, "dependencies": { + "@reown/appkit-common-react-native": "1.2.1", "@reown/appkit-wallet-react-native": "1.2.1" }, "peerDependencies": { diff --git a/packages/auth-ethers/src/index.ts b/packages/auth-ethers/src/index.ts index 01ad7822b..123e25348 100644 --- a/packages/auth-ethers/src/index.ts +++ b/packages/auth-ethers/src/index.ts @@ -1,13 +1,13 @@ import { AppKitFrameProvider, type AppKitFrameTypes } from '@reown/appkit-wallet-react-native'; - +import { ConstantsUtil, PresetsUtil } from '@reown/appkit-common-react-native'; interface AuthProviderProps { projectId: string; metadata: AppKitFrameTypes.Metadata; } export class AuthProvider extends AppKitFrameProvider { - readonly id = 'appKitAuth'; - readonly name = 'AppKit Auth'; + readonly id = ConstantsUtil.AUTH_CONNECTOR_ID; + readonly name = PresetsUtil.ConnectorNamesMap[ConstantsUtil.AUTH_CONNECTOR_ID]!; constructor(props: AuthProviderProps) { super(props.projectId, props.metadata); diff --git a/packages/auth-wagmi/package.json b/packages/auth-wagmi/package.json index 7e5eefd0f..7ec86311a 100644 --- a/packages/auth-wagmi/package.json +++ b/packages/auth-wagmi/package.json @@ -36,6 +36,7 @@ "access": "public" }, "dependencies": { + "@reown/appkit-common-react-native": "1.2.1", "@reown/appkit-core-react-native": "1.2.1", "@reown/appkit-wallet-react-native": "1.2.1" }, diff --git a/packages/auth-wagmi/src/index.ts b/packages/auth-wagmi/src/index.ts index b9859d6ab..fd9e3f9d8 100644 --- a/packages/auth-wagmi/src/index.ts +++ b/packages/auth-wagmi/src/index.ts @@ -3,6 +3,7 @@ import { SwitchChainError, getAddress, type Address, type Hex } from 'viem'; import { AppKitFrameProvider } from '@reown/appkit-wallet-react-native'; import { StorageUtil } from '@reown/appkit-core-react-native'; +import { ConstantsUtil, PresetsUtil } from '@reown/appkit-common-react-native'; export type Metadata = { name: string; @@ -26,8 +27,8 @@ type StorageItemMap = { recentConnectorId?: string; }; -authConnector.type = 'appKitAuth' as const; -authConnector.id = 'appKitAuth' as const; +authConnector.type = PresetsUtil.ConnectorTypesMap[ConstantsUtil.AUTH_CONNECTOR_ID]!; +authConnector.id = ConstantsUtil.AUTH_CONNECTOR_ID; export function authConnector(parameters: AuthConnectorOptions) { let _provider: AppKitFrameProvider = {} as AppKitFrameProvider; let _currentAddress: Address | null = null; @@ -35,7 +36,7 @@ export function authConnector(parameters: AuthConnectorOptions) { return createConnector(config => ({ id: authConnector.id, - name: 'AppKit Auth', + name: PresetsUtil.ConnectorNamesMap[ConstantsUtil.AUTH_CONNECTOR_ID]!, type: authConnector.type, async setup() { _provider = new AppKitFrameProvider(parameters.projectId, parameters.metadata); diff --git a/packages/coinbase-ethers/package.json b/packages/coinbase-ethers/package.json index a5c119769..af13e2a26 100644 --- a/packages/coinbase-ethers/package.json +++ b/packages/coinbase-ethers/package.json @@ -36,6 +36,9 @@ "registry": "https://registry.npmjs.org/", "access": "public" }, + "dependencies": { + "@reown/appkit-common-react-native": "1.2.1" + }, "peerDependencies": { "@coinbase/wallet-mobile-sdk": ">=1.0.10", "ethers": ">=5" diff --git a/packages/coinbase-ethers/src/index.ts b/packages/coinbase-ethers/src/index.ts index 51dc3256c..c8f8bfbfd 100644 --- a/packages/coinbase-ethers/src/index.ts +++ b/packages/coinbase-ethers/src/index.ts @@ -1,5 +1,6 @@ import { configure, WalletMobileSDKEVMProvider } from '@coinbase/wallet-mobile-sdk'; import type { WalletMobileSDKProviderOptions } from '@coinbase/wallet-mobile-sdk/build/WalletMobileSDKEVMProvider'; +import { ConstantsUtil, PresetsUtil } from '@reown/appkit-common-react-native'; interface RequestArguments { readonly method: string; @@ -12,8 +13,8 @@ type CoinbaseProviderOptions = WalletMobileSDKProviderOptions & { }; export class CoinbaseProvider { - readonly id = 'coinbaseWallet'; - readonly name = 'Coinbase Wallet'; + readonly id = ConstantsUtil.COINBASE_CONNECTOR_ID; + readonly name = PresetsUtil.ConnectorNamesMap[ConstantsUtil.COINBASE_CONNECTOR_ID]!; private _provider?: WalletMobileSDKEVMProvider; private _initProviderPromise?: Promise; diff --git a/packages/coinbase-wagmi/package.json b/packages/coinbase-wagmi/package.json index 5a7ec05dc..758311c70 100644 --- a/packages/coinbase-wagmi/package.json +++ b/packages/coinbase-wagmi/package.json @@ -36,6 +36,9 @@ "registry": "https://registry.npmjs.org/", "access": "public" }, + "dependencies": { + "@reown/appkit-common-react-native": "1.2.1" + }, "peerDependencies": { "@coinbase/wallet-mobile-sdk": ">=1.0.10", "wagmi": ">=2" diff --git a/packages/coinbase-wagmi/src/index.ts b/packages/coinbase-wagmi/src/index.ts index 8923ad018..0450673df 100644 --- a/packages/coinbase-wagmi/src/index.ts +++ b/packages/coinbase-wagmi/src/index.ts @@ -8,6 +8,7 @@ import { } from 'viem'; import { WalletMobileSDKEVMProvider, configure } from '@coinbase/wallet-mobile-sdk'; import type { WalletMobileSDKProviderOptions } from '@coinbase/wallet-mobile-sdk/build/WalletMobileSDKEVMProvider'; +import { ConstantsUtil, PresetsUtil } from '@reown/appkit-common-react-native'; type CoinbaseConnectorParameters = WalletMobileSDKProviderOptions & { redirect: string; @@ -15,13 +16,13 @@ type CoinbaseConnectorParameters = WalletMobileSDKProviderOptions & { type Provider = WalletMobileSDKEVMProvider; -coinbaseConnector.type = 'coinbaseWallet' as const; +coinbaseConnector.type = PresetsUtil.ConnectorTypesMap[ConstantsUtil.COINBASE_CONNECTOR_ID]!; export function coinbaseConnector(parameters: CoinbaseConnectorParameters) { let _provider: Provider; return createConnector(config => ({ - id: 'coinbaseWallet', - name: 'Coinbase Wallet', + id: ConstantsUtil.COINBASE_CONNECTOR_ID, + name: PresetsUtil.ConnectorNamesMap[ConstantsUtil.COINBASE_CONNECTOR_ID]!, type: coinbaseConnector.type, async connect({ chainId } = {}) { try { diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index 13f28df44..52d0a3bb5 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -4,6 +4,7 @@ export { DateUtil } from './utils/DateUtil'; export { NamesUtil } from './utils/NamesUtil'; export { NetworkUtil } from './utils/NetworkUtil'; export { NumberUtil } from './utils/NumberUtil'; +export { PresetsUtil } from './utils/PresetsUtil'; export { StringUtil } from './utils/StringUtil'; export { ErrorUtil } from './utils/ErrorUtil'; export { erc20ABI } from './contracts/erc20'; diff --git a/packages/common/src/utils/ConstantsUtil.ts b/packages/common/src/utils/ConstantsUtil.ts index 4d21f178c..e5a8ee51e 100644 --- a/packages/common/src/utils/ConstantsUtil.ts +++ b/packages/common/src/utils/ConstantsUtil.ts @@ -1,11 +1,22 @@ export const ConstantsUtil = { + VERSION: '1.2.1', + + EIP155: 'eip155', + ADD_CHAIN_METHOD: 'wallet_addEthereumChain', + WC_NAME_SUFFIX: '.reown.id', WC_NAME_SUFFIX_LEGACY: '.wcn.id', + BLOCKCHAIN_API_RPC_URL: 'https://rpc.walletconnect.org', PULSE_API_URL: 'https://pulse.walletconnect.org', API_URL: 'https://api.web3modal.org', + + WALLET_CONNECT_CONNECTOR_ID: 'walletConnect', COINBASE_CONNECTOR_ID: 'coinbaseWallet', + AUTH_CONNECTOR_ID: 'appKitAuth', + COINBASE_EXPLORER_ID: 'fd20dc426fb37566d803205b19bbc1d4096b248ac04548e3cfb6b3a38bd033aa', + USDT_CONTRACT_ADDRESSES: [ // Mainnet '0xdac17f958d2ee523a2206206994597c13d831ec7', diff --git a/packages/scaffold-utils/src/utils/PresetsUtil.ts b/packages/common/src/utils/PresetsUtil.ts similarity index 94% rename from packages/scaffold-utils/src/utils/PresetsUtil.ts rename to packages/common/src/utils/PresetsUtil.ts index fd510290b..b949dded0 100644 --- a/packages/scaffold-utils/src/utils/PresetsUtil.ts +++ b/packages/common/src/utils/PresetsUtil.ts @@ -1,4 +1,4 @@ -import type { ConnectorType } from '@reown/appkit-scaffold-react-native'; +import type { ConnectorType } from './TypeUtil'; import { ConstantsUtil } from './ConstantsUtil'; export const PresetsUtil = { @@ -48,17 +48,17 @@ export const PresetsUtil = { 1313161554: '3ff73439-a619-4894-9262-4470c773a100' } as Record, + ConnectorNamesMap: { + [ConstantsUtil.WALLET_CONNECT_CONNECTOR_ID]: 'WalletConnect', + [ConstantsUtil.COINBASE_CONNECTOR_ID]: 'Coinbase Wallet', + [ConstantsUtil.AUTH_CONNECTOR_ID]: 'AppKit Universal Wallet' + } as Record, + ConnectorImageIds: { [ConstantsUtil.COINBASE_CONNECTOR_ID]: '0c2840c3-5b04-4c44-9661-fbd4b49e1800', [ConstantsUtil.WALLET_CONNECT_CONNECTOR_ID]: 'ef1a1fcf-7fe8-4d69-bd6d-fda1345b4400' } as Record, - ConnectorNamesMap: { - [ConstantsUtil.WALLET_CONNECT_CONNECTOR_ID]: 'WalletConnect', - [ConstantsUtil.COINBASE_CONNECTOR_ID]: 'Coinbase', - [ConstantsUtil.AUTH_CONNECTOR_ID]: 'Auth' - } as Record, - ConnectorTypesMap: { [ConstantsUtil.WALLET_CONNECT_CONNECTOR_ID]: 'WALLET_CONNECT', [ConstantsUtil.COINBASE_CONNECTOR_ID]: 'COINBASE', diff --git a/packages/common/src/utils/TypeUtil.ts b/packages/common/src/utils/TypeUtil.ts index a25bdcd66..13ea7ede1 100644 --- a/packages/common/src/utils/TypeUtil.ts +++ b/packages/common/src/utils/TypeUtil.ts @@ -93,3 +93,5 @@ export type ThemeMode = 'dark' | 'light'; export interface ThemeVariables { accent?: string; } + +export type ConnectorType = 'WALLET_CONNECT' | 'COINBASE' | 'AUTH' | 'EXTERNAL'; diff --git a/packages/core/src/controllers/BlockchainApiController.ts b/packages/core/src/controllers/BlockchainApiController.ts index 2b982d408..6ee7e65bd 100644 --- a/packages/core/src/controllers/BlockchainApiController.ts +++ b/packages/core/src/controllers/BlockchainApiController.ts @@ -26,6 +26,7 @@ import type { } from '../utils/TypeUtil'; import { OptionsController } from './OptionsController'; import { ConstantsUtil } from '../utils/ConstantsUtil'; +import { ApiUtil } from '../utils/ApiUtil'; // -- Helpers ------------------------------------------- // const baseUrl = CoreHelperUtil.getBlockchainApiUrl(); @@ -36,7 +37,9 @@ const getHeaders = () => { return { 'Content-Type': 'application/json', 'x-sdk-type': sdkType, - 'x-sdk-version': sdkVersion + 'x-sdk-version': sdkVersion, + 'User-Agent': ApiUtil.getUserAgent(), + 'origin': ApiUtil.getOrigin() }; }; diff --git a/packages/core/src/controllers/ConnectorController.ts b/packages/core/src/controllers/ConnectorController.ts index a74a4c1c5..7ff77664c 100644 --- a/packages/core/src/controllers/ConnectorController.ts +++ b/packages/core/src/controllers/ConnectorController.ts @@ -1,6 +1,7 @@ +import type { ConnectorType } from '@reown/appkit-common-react-native'; import { subscribeKey as subKey } from 'valtio/utils'; import { proxy, ref } from 'valtio'; -import type { Connector, ConnectorType } from '../utils/TypeUtil'; +import type { Connector } from '../utils/TypeUtil'; import { StorageUtil } from '../utils/StorageUtil'; // -- Types --------------------------------------------- // @@ -30,7 +31,7 @@ export const ConnectorController = { }, addConnector(connector: Connector) { - state.connectors.push(ref(connector)); + state.connectors = [...state.connectors, ref(connector)]; }, getConnectors() { diff --git a/packages/core/src/controllers/RouterController.ts b/packages/core/src/controllers/RouterController.ts index 4865a8e79..d608e566f 100644 --- a/packages/core/src/controllers/RouterController.ts +++ b/packages/core/src/controllers/RouterController.ts @@ -72,13 +72,13 @@ export const RouterController = { push(view: RouterControllerState['view'], data?: RouterControllerState['data']) { if (view !== state.view) { state.view = view; - state.history.push(view); + state.history = [...state.history, view]; state.data = data; } }, pushTransactionStack(action: TransactionAction) { - state.transactionStack.push(action); + state.transactionStack = [...state.transactionStack, action]; }, popTransactionStack(cancel?: boolean) { diff --git a/packages/core/src/utils/ApiUtil.ts b/packages/core/src/utils/ApiUtil.ts index bcf778f05..413191218 100644 --- a/packages/core/src/utils/ApiUtil.ts +++ b/packages/core/src/utils/ApiUtil.ts @@ -14,6 +14,26 @@ export const ApiUtil = { ].join('.'); }, + getEnvironment() { + try { + // Check if Expo is installed + const hasExpoGlobal = !!(global as any).expo; + const hasExpoConstants = hasExpoGlobal && (global as any).expo?.modules?.ExponentConstants; + const environment: string | undefined = + hasExpoConstants && (global as any).expo?.modules?.ExponentConstants?.executionEnvironment; + + if (environment === 'standalone' || environment === 'storeClient') { + return 'expo-managed'; + } else if (environment === 'bare') { + return 'expo-bare'; + } + + return 'bare'; + } catch { + return 'bare'; + } + }, + getUserAgent() { const rnVersion = Platform.select({ ios: this.getReactNativeVersion(), @@ -21,6 +41,10 @@ export const ApiUtil = { default: 'undefined' }); - return `${Platform.OS}-${Platform.Version}@rn-${rnVersion}`; + const envPrefix = this.getEnvironment(); + + const userAgent = `${Platform.OS}-${Platform.Version}@rn-${rnVersion}@${envPrefix}`; + + return userAgent; } }; diff --git a/packages/core/src/utils/StorageUtil.ts b/packages/core/src/utils/StorageUtil.ts index e340d58b6..b60e0c2a3 100644 --- a/packages/core/src/utils/StorageUtil.ts +++ b/packages/core/src/utils/StorageUtil.ts @@ -1,7 +1,7 @@ /* eslint-disable no-console */ import AsyncStorage from '@react-native-async-storage/async-storage'; -import type { ConnectorType, WcWallet } from './TypeUtil'; -import type { SocialProvider } from '@reown/appkit-common-react-native'; +import type { WcWallet } from './TypeUtil'; +import type { SocialProvider, ConnectorType } from '@reown/appkit-common-react-native'; // -- Helpers ----------------------------------------------------------------- const WC_DEEPLINK = 'WALLETCONNECT_DEEPLINK_CHOICE'; diff --git a/packages/core/src/utils/TypeUtil.ts b/packages/core/src/utils/TypeUtil.ts index 498a18106..07d385ce1 100644 --- a/packages/core/src/utils/TypeUtil.ts +++ b/packages/core/src/utils/TypeUtil.ts @@ -3,7 +3,8 @@ import type { Balance, SocialProvider, ThemeMode, - Transaction + Transaction, + ConnectorType } from '@reown/appkit-common-react-native'; export interface BaseError { @@ -25,6 +26,14 @@ export type ConnectedWalletInfo = | { name?: string; icon?: string; + description?: string; + url?: string; + icons?: string[]; + redirect?: { + native?: string; + universal?: string; + linkMode?: boolean; + }; [key: string]: unknown; } | undefined; @@ -38,8 +47,6 @@ export type ProjectId = string; export type Platform = 'mobile' | 'web' | 'qrcode' | 'email' | 'unsupported'; -export type ConnectorType = 'WALLET_CONNECT' | 'COINBASE' | 'AUTH' | 'EXTERNAL'; - export type Connector = { id: string; type: ConnectorType; diff --git a/packages/ethers/src/client.ts b/packages/ethers/src/client.ts index 846fd8a26..ab0ef7e4c 100644 --- a/packages/ethers/src/client.ts +++ b/packages/ethers/src/client.ts @@ -28,10 +28,15 @@ import { type AppKitFrameAccountType, type EstimateGasTransactionArgs } from '@reown/appkit-scaffold-react-native'; -import { erc20ABI, ErrorUtil, NamesUtil, NetworkUtil } from '@reown/appkit-common-react-native'; import { - ConstantsUtil, + erc20ABI, + ErrorUtil, + NamesUtil, + NetworkUtil, PresetsUtil, + ConstantsUtil +} from '@reown/appkit-common-react-native'; +import { HelpersUtil, StorageUtil, EthersConstantsUtil, @@ -450,6 +455,10 @@ export class AppKit extends AppKitScaffold { this.syncNetwork(chainImages); }); + EthersStoreUtil.subscribeKey('provider', provider => { + this.syncConnectedWalletInfo(provider); + }); + this.syncRequestedNetworks(chains, chainImages); this.syncConnectors(config); this.syncAuthConnector(config); @@ -953,14 +962,15 @@ export class AppKit extends AppKitScaffold { explorerId: PresetsUtil.ConnectorExplorerIds[ConstantsUtil.COINBASE_CONNECTOR_ID], imageId: PresetsUtil.ConnectorImageIds[ConstantsUtil.COINBASE_CONNECTOR_ID], imageUrl: this.options?.connectorImages?.[ConstantsUtil.COINBASE_CONNECTOR_ID], - name: PresetsUtil.ConnectorNamesMap[ConstantsUtil.COINBASE_CONNECTOR_ID], + name: + connector?.name ?? PresetsUtil.ConnectorNamesMap[ConstantsUtil.COINBASE_CONNECTOR_ID], type: PresetsUtil.ConnectorTypesMap[ConstantsUtil.COINBASE_CONNECTOR_ID]! }); this.checkActiveCoinbaseProvider(connector as Provider); } else { _connectors.push({ id: connector.id, - name: connector.name, + name: connector.name ?? PresetsUtil.ConnectorNamesMap[connector.id], type: 'EXTERNAL' }); } @@ -1002,6 +1012,33 @@ export class AppKit extends AppKitScaffold { this.addAuthListeners(this.authProvider); } + private async syncConnectedWalletInfo(provider?: Provider) { + if (!provider) { + this.setConnectedWalletInfo(undefined); + + return; + } + + if ((provider as any)?.session?.peer?.metadata) { + const metadata = (provider as unknown as EthereumProvider)?.session?.peer.metadata; + if (metadata) { + this.setConnectedWalletInfo({ + ...metadata, + name: metadata.name, + icon: metadata.icons?.[0] + }); + } + } else if (provider?.id) { + this.setConnectedWalletInfo({ + id: provider.id, + name: provider?.name ?? PresetsUtil.ConnectorNamesMap[provider.id], + icon: this.options?.connectorImages?.[provider.id] + }); + } else { + this.setConnectedWalletInfo(undefined); + } + } + private async addAuthListeners(authProvider: AppKitFrameProvider) { authProvider.onSetPreferredAccount(async ({ address, type }) => { if (address) { diff --git a/packages/ethers/src/index.tsx b/packages/ethers/src/index.tsx index ccdeddbf9..2bd2a5692 100644 --- a/packages/ethers/src/index.tsx +++ b/packages/ethers/src/index.tsx @@ -1,10 +1,6 @@ import { useEffect, useState, useSyncExternalStore } from 'react'; import { useSnapshot } from 'valtio'; -import { - ConstantsUtil, - EthersStoreUtil, - type Provider -} from '@reown/appkit-scaffold-utils-react-native'; +import { EthersStoreUtil, type Provider } from '@reown/appkit-scaffold-utils-react-native'; export { AccountButton, @@ -14,7 +10,7 @@ export { AppKit } from '@reown/appkit-scaffold-react-native'; import type { EventName, EventsControllerState } from '@reown/appkit-scaffold-react-native'; - +import { ConstantsUtil } from '@reown/appkit-common-react-native'; export { defaultConfig } from './utils/defaultConfig'; import type { AppKitOptions } from './client'; diff --git a/packages/ethers/src/utils/helpers.ts b/packages/ethers/src/utils/helpers.ts index 221b7cf1d..2035ecb43 100644 --- a/packages/ethers/src/utils/helpers.ts +++ b/packages/ethers/src/utils/helpers.ts @@ -1,5 +1,5 @@ import type { CaipNetworkId } from '@reown/appkit-scaffold-react-native'; -import { PresetsUtil, ConstantsUtil } from '@reown/appkit-scaffold-utils-react-native'; +import { PresetsUtil, ConstantsUtil } from '@reown/appkit-common-react-native'; import EthereumProvider from '@walletconnect/ethereum-provider'; export async function getWalletConnectCaipNetworks(provider?: EthereumProvider) { diff --git a/packages/ethers5/src/client.ts b/packages/ethers5/src/client.ts index 083096c2f..a371c9643 100644 --- a/packages/ethers5/src/client.ts +++ b/packages/ethers5/src/client.ts @@ -16,8 +16,6 @@ import { AppKitScaffold } from '@reown/appkit-scaffold-react-native'; import { - ConstantsUtil, - PresetsUtil, StorageUtil, HelpersUtil, EthersConstantsUtil, @@ -38,7 +36,14 @@ import { getDidAddress, type AppKitSIWEClient } from '@reown/appkit-siwe-react-native'; -import { erc20ABI, ErrorUtil, NamesUtil, NetworkUtil } from '@reown/appkit-common-react-native'; +import { + erc20ABI, + ErrorUtil, + NamesUtil, + NetworkUtil, + ConstantsUtil, + PresetsUtil +} from '@reown/appkit-common-react-native'; import EthereumProvider, { OPTIONAL_METHODS } from '@walletconnect/ethereum-provider'; import type { EthereumProviderOptions } from '@walletconnect/ethereum-provider'; import { type JsonRpcError } from '@walletconnect/jsonrpc-types'; @@ -432,6 +437,10 @@ export class AppKit extends AppKitScaffold { this.syncNetwork(chainImages); }); + EthersStoreUtil.subscribeKey('provider', provider => { + this.syncConnectedWalletInfo(provider); + }); + this.syncRequestedNetworks(chains, chainImages); this.syncConnectors(config); this.syncAuthConnector(config); @@ -937,7 +946,7 @@ export class AppKit extends AppKitScaffold { } else { _connectors.push({ id: connector.id, - name: connector.name, + name: connector.name ?? PresetsUtil.ConnectorNamesMap[connector.id], type: 'EXTERNAL' }); } @@ -979,6 +988,33 @@ export class AppKit extends AppKitScaffold { this.addAuthListeners(this.authProvider); } + private async syncConnectedWalletInfo(provider?: Provider) { + if (!provider) { + this.setConnectedWalletInfo(undefined); + + return; + } + + if ((provider as any)?.session?.peer?.metadata) { + const metadata = (provider as unknown as EthereumProvider)?.session?.peer.metadata; + if (metadata) { + this.setConnectedWalletInfo({ + ...metadata, + name: metadata.name, + icon: metadata.icons?.[0] + }); + } + } else if (provider?.id) { + this.setConnectedWalletInfo({ + id: provider.id, + name: provider?.name ?? PresetsUtil.ConnectorNamesMap[provider.id], + icon: this.options?.connectorImages?.[provider.id] + }); + } else { + this.setConnectedWalletInfo(undefined); + } + } + private async addAuthListeners(authProvider: AppKitFrameProvider) { authProvider.onSetPreferredAccount(async ({ address, type }) => { if (address) { diff --git a/packages/ethers5/src/index.tsx b/packages/ethers5/src/index.tsx index 72e55c58a..868e583f8 100644 --- a/packages/ethers5/src/index.tsx +++ b/packages/ethers5/src/index.tsx @@ -7,11 +7,9 @@ export { AppKit } from '@reown/appkit-scaffold-react-native'; import type { EventName, EventsControllerState } from '@reown/appkit-scaffold-react-native'; -import { - ConstantsUtil, - EthersStoreUtil, - type Provider -} from '@reown/appkit-scaffold-utils-react-native'; +import { EthersStoreUtil, type Provider } from '@reown/appkit-scaffold-utils-react-native'; +import { ConstantsUtil } from '@reown/appkit-common-react-native'; + export { defaultConfig } from './utils/defaultConfig'; import { useEffect, useState, useSyncExternalStore } from 'react'; import type { AppKitOptions } from './client'; diff --git a/packages/ethers5/src/utils/helpers.ts b/packages/ethers5/src/utils/helpers.ts index 221b7cf1d..2035ecb43 100644 --- a/packages/ethers5/src/utils/helpers.ts +++ b/packages/ethers5/src/utils/helpers.ts @@ -1,5 +1,5 @@ import type { CaipNetworkId } from '@reown/appkit-scaffold-react-native'; -import { PresetsUtil, ConstantsUtil } from '@reown/appkit-scaffold-utils-react-native'; +import { PresetsUtil, ConstantsUtil } from '@reown/appkit-common-react-native'; import EthereumProvider from '@walletconnect/ethereum-provider'; export async function getWalletConnectCaipNetworks(provider?: EthereumProvider) { diff --git a/packages/scaffold-utils/package.json b/packages/scaffold-utils/package.json index 413b185f1..1d436d047 100644 --- a/packages/scaffold-utils/package.json +++ b/packages/scaffold-utils/package.json @@ -35,6 +35,7 @@ "access": "public" }, "dependencies": { + "@reown/appkit-common-react-native": "1.2.1", "@reown/appkit-scaffold-react-native": "1.2.1" }, "react-native": "src/index.ts", diff --git a/packages/scaffold-utils/src/index.ts b/packages/scaffold-utils/src/index.ts index a8ecb2455..c01545ca5 100644 --- a/packages/scaffold-utils/src/index.ts +++ b/packages/scaffold-utils/src/index.ts @@ -1,5 +1,3 @@ -export { ConstantsUtil } from './utils/ConstantsUtil'; -export { PresetsUtil } from './utils/PresetsUtil'; export { HelpersUtil } from './utils/HelpersUtil'; export { StorageUtil } from './utils/StorageUtil'; diff --git a/packages/scaffold-utils/src/utils/ConstantsUtil.ts b/packages/scaffold-utils/src/utils/ConstantsUtil.ts deleted file mode 100644 index c2cc8e803..000000000 --- a/packages/scaffold-utils/src/utils/ConstantsUtil.ts +++ /dev/null @@ -1,8 +0,0 @@ -export const ConstantsUtil = { - VERSION: '1.2.1', - WALLET_CONNECT_CONNECTOR_ID: 'walletConnect', - COINBASE_CONNECTOR_ID: 'coinbaseWallet', - AUTH_CONNECTOR_ID: 'appKitAuth', - EIP155: 'eip155', - ADD_CHAIN_METHOD: 'wallet_addEthereumChain' -}; diff --git a/packages/scaffold-utils/src/utils/EthersHelpersUtil.ts b/packages/scaffold-utils/src/utils/EthersHelpersUtil.ts index 2ef1bc53d..338fbd479 100644 --- a/packages/scaffold-utils/src/utils/EthersHelpersUtil.ts +++ b/packages/scaffold-utils/src/utils/EthersHelpersUtil.ts @@ -1,6 +1,6 @@ import type { CaipNetwork } from '@reown/appkit-scaffold-react-native'; -import { ConstantsUtil } from './ConstantsUtil'; -import { PresetsUtil } from './PresetsUtil'; +import { ConstantsUtil, PresetsUtil } from '@reown/appkit-common-react-native'; + import type { Chain, Provider } from './EthersTypesUtil'; export const EthersHelpersUtil = { diff --git a/packages/scaffold-utils/src/utils/HelpersUtil.ts b/packages/scaffold-utils/src/utils/HelpersUtil.ts index 7e443fff0..354d3e996 100644 --- a/packages/scaffold-utils/src/utils/HelpersUtil.ts +++ b/packages/scaffold-utils/src/utils/HelpersUtil.ts @@ -1,5 +1,5 @@ import type { Tokens } from '@reown/appkit-scaffold-react-native'; -import { ConstantsUtil } from './ConstantsUtil'; +import { ConstantsUtil } from '@reown/appkit-common-react-native'; export const HelpersUtil = { getCaipTokens(tokens?: Tokens) { diff --git a/packages/scaffold/src/modal/w3m-modal/index.tsx b/packages/scaffold/src/modal/w3m-modal/index.tsx index fe2db865c..678942ec0 100644 --- a/packages/scaffold/src/modal/w3m-modal/index.tsx +++ b/packages/scaffold/src/modal/w3m-modal/index.tsx @@ -71,7 +71,6 @@ export function AppKit() { const newAddress = CoreHelperUtil.getPlainAddress(address); TransactionsController.resetTransactions(); - TransactionsController.fetchTransactions(newAddress, true); if (OptionsController.state.isSiweEnabled) { const newNetworkId = CoreHelperUtil.getNetworkId(address); diff --git a/packages/scaffold/src/partials/w3m-account-activity/index.tsx b/packages/scaffold/src/partials/w3m-account-activity/index.tsx index b08ff2654..3ec7ee05a 100644 --- a/packages/scaffold/src/partials/w3m-account-activity/index.tsx +++ b/packages/scaffold/src/partials/w3m-account-activity/index.tsx @@ -1,5 +1,5 @@ -import { useCallback, useMemo, useState } from 'react'; import { useSnapshot } from 'valtio'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { ScrollView, View, type StyleProp, type ViewStyle, RefreshControl } from 'react-native'; import { FlexView, @@ -30,6 +30,7 @@ interface Props { export function AccountActivity({ style }: Props) { const Theme = useTheme(); const [refreshing, setRefreshing] = useState(false); + const [initialLoad, setInitialLoad] = useState(true); const { loading, transactions, next } = useSnapshot(TransactionsController.state); const { caipNetwork } = useSnapshot(NetworkController.state); const networkImage = AssetUtil.getNetworkImage(caipNetwork); @@ -58,7 +59,18 @@ export function AccountActivity({ style }: Props) { return TransactionsController.getTransactionsByYearAndMonth(transactions as Transaction[]); }, [transactions]); - if (loading && !transactions.length) { + useEffect(() => { + if (!TransactionsController.state.transactions.length) { + TransactionsController.fetchTransactions(AccountController.state.address, true); + } + // Set initial load to false after first fetch + const timer = setTimeout(() => setInitialLoad(false), 100); + + return () => clearTimeout(timer); + }, []); + + // Show loading spinner during initial load or when loading with no transactions + if ((initialLoad || loading) && !transactions.length) { return ( @@ -66,13 +78,14 @@ export function AccountActivity({ style }: Props) { ); } - if (!Object.keys(transactionsByYear).length) { + // Only show placeholder when we're not in initial load or loading state + if (!Object.keys(transactionsByYear).length && !loading && !initialLoad) { return ( ); } @@ -144,14 +157,14 @@ export function AccountActivity({ style }: Props) { ))} ))} - {(next || loading) && ( + {(next || loading) && !refreshing && ( {next && !loading && ( Load more )} - {loading && !refreshing && } + {loading && } )} diff --git a/packages/scaffold/src/partials/w3m-account-activity/styles.ts b/packages/scaffold/src/partials/w3m-account-activity/styles.ts index df9874a45..64c13aaba 100644 --- a/packages/scaffold/src/partials/w3m-account-activity/styles.ts +++ b/packages/scaffold/src/partials/w3m-account-activity/styles.ts @@ -18,7 +18,8 @@ export default StyleSheet.create({ height: 40 }, placeholder: { - minHeight: 200 + minHeight: 200, + flex: 0 }, loadMoreButton: { alignSelf: 'center', diff --git a/packages/scaffold/src/views/w3m-account-default-view/index.tsx b/packages/scaffold/src/views/w3m-account-default-view/index.tsx index dba1584a9..f3982ed92 100644 --- a/packages/scaffold/src/views/w3m-account-default-view/index.tsx +++ b/packages/scaffold/src/views/w3m-account-default-view/index.tsx @@ -244,10 +244,10 @@ export function AccountDefaultView() { imageSrc={networkImage} imageHeaders={ApiController._getApiHeaders()} onPress={onNetworkPress} - testID="w3m-account-select-network" + testID="button-network" style={styles.actionButton} > - + {caipNetwork?.name} @@ -259,7 +259,7 @@ export function AccountDefaultView() { iconColor="accent-100" iconBackgroundColor="accent-glass-015" onPress={onSwapPress} - testID="button-activity" + testID="button-swap" style={styles.actionButton} > Swap @@ -299,7 +299,7 @@ export function AccountDefaultView() { onPress={onDisconnect} loading={disconnecting} iconBackgroundColor="gray-glass-010" - testID="disconnect-button" + testID="button-disconnect" > Disconnect diff --git a/packages/scaffold/src/views/w3m-connect-view/components/connectors-list.tsx b/packages/scaffold/src/views/w3m-connect-view/components/connectors-list.tsx index fd655376d..e1920354f 100644 --- a/packages/scaffold/src/views/w3m-connect-view/components/connectors-list.tsx +++ b/packages/scaffold/src/views/w3m-connect-view/components/connectors-list.tsx @@ -1,13 +1,14 @@ import { useSnapshot } from 'valtio'; +import type { StyleProp, ViewStyle } from 'react-native'; import { ConnectorController, AssetUtil, RouterController, - type ConnectorType, ApiController } from '@reown/appkit-core-react-native'; + import { ListWallet } from '@reown/appkit-ui-react-native'; -import type { StyleProp, ViewStyle } from 'react-native'; +import type { ConnectorType } from '@reown/appkit-common-react-native'; interface Props { itemStyle: StyleProp; diff --git a/packages/ui/src/components/wui-loading-spinner/index.tsx b/packages/ui/src/components/wui-loading-spinner/index.tsx index 4270f571b..0fff6586f 100644 --- a/packages/ui/src/components/wui-loading-spinner/index.tsx +++ b/packages/ui/src/components/wui-loading-spinner/index.tsx @@ -12,9 +12,15 @@ export interface LoadingSpinnerProps { size?: Exclude; color?: Exclude; style?: StyleProp; + testID?: string; } -export function LoadingSpinner({ color, style, size = 'lg' }: LoadingSpinnerProps) { +export function LoadingSpinner({ + color, + style, + size = 'lg', + testID = 'loading-spinner' +}: LoadingSpinnerProps) { const Theme = useTheme(); const spinValue = useRef(new Animated.Value(0)); const stroke = color ? Theme[color] : Theme['accent-100']; @@ -41,7 +47,7 @@ export function LoadingSpinner({ color, style, size = 'lg' }: LoadingSpinnerProp }); return ( - + =5" @@ -6693,6 +6694,7 @@ __metadata: version: 0.0.0-use.local resolution: "@reown/appkit-auth-wagmi-react-native@workspace:packages/auth-wagmi" dependencies: + "@reown/appkit-common-react-native": "npm:1.2.1" "@reown/appkit-core-react-native": "npm:1.2.1" "@reown/appkit-wallet-react-native": "npm:1.2.1" peerDependencies: @@ -6703,6 +6705,8 @@ __metadata: "@reown/appkit-coinbase-ethers-react-native@workspace:packages/coinbase-ethers": version: 0.0.0-use.local resolution: "@reown/appkit-coinbase-ethers-react-native@workspace:packages/coinbase-ethers" + dependencies: + "@reown/appkit-common-react-native": "npm:1.2.1" peerDependencies: "@coinbase/wallet-mobile-sdk": ">=1.0.10" ethers: ">=5" @@ -6712,6 +6716,8 @@ __metadata: "@reown/appkit-coinbase-wagmi-react-native@workspace:packages/coinbase-wagmi": version: 0.0.0-use.local resolution: "@reown/appkit-coinbase-wagmi-react-native@workspace:packages/coinbase-wagmi" + dependencies: + "@reown/appkit-common-react-native": "npm:1.2.1" peerDependencies: "@coinbase/wallet-mobile-sdk": ">=1.0.10" wagmi: ">=2" @@ -6802,6 +6808,7 @@ __metadata: version: 0.0.0-use.local resolution: "@reown/appkit-scaffold-utils-react-native@workspace:packages/scaffold-utils" dependencies: + "@reown/appkit-common-react-native": "npm:1.2.1" "@reown/appkit-scaffold-react-native": "npm:1.2.1" languageName: unknown linkType: soft