Skip to content

Fix unsupported network detection and UI handling #351

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 17 commits into
base: develop
Choose a base branch
from
Open
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
043efec
Fix unsupported network detection and UI handling
devin-ai-integration[bot] May 22, 2025
d76da53
Fix lint errors and improve network validation
devin-ai-integration[bot] May 22, 2025
78499a8
Fix lint issues and improve network validation
devin-ai-integration[bot] May 22, 2025
4297e21
Update AccountDefaultView and AccountView to handle unsupported networks
devin-ai-integration[bot] May 22, 2025
d119242
Fix formatting in AccountView component
devin-ai-integration[bot] May 22, 2025
f893487
Update AccountDefaultView to show Switch Network when network is not …
devin-ai-integration[bot] May 22, 2025
d3f4859
Fix formatting in AccountDefaultView component
devin-ai-integration[bot] May 22, 2025
75516c9
chore: improvements
ignaciosantise May 22, 2025
634bd4f
fix: handle unsupported network gracefully, also in siwe
ignaciosantise May 23, 2025
2ff7c81
chore: set unsupported network on ethers
ignaciosantise May 26, 2025
89bec04
chore: check namespaces approved networks to set unsupported flags
ignaciosantise May 26, 2025
8d8b931
chore: ethers improvements
ignaciosantise May 27, 2025
23c7822
chore: moved siwe logic to clients
ignaciosantise May 27, 2025
1013fc6
chore: removed old tests
ignaciosantise May 27, 2025
41b2b25
chore: fixed 1CA issue in wagmi connector + solved test issues
ignaciosantise May 28, 2025
e135b8e
fix: compare walletchoice with authtype in approved namespace getter
ignaciosantise May 28, 2025
b76f26d
chore: check supported network only if needed, added timeout for the …
ignaciosantise May 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions apps/native/tests/shared/pages/ModalPage.ts
Original file line number Diff line number Diff line change
@@ -32,7 +32,7 @@ export class ModalPage {
const qrLoadInitiatedTime = new Date();

const qrCode = this.page.getByTestId('qr-code');
await expect(qrCode).toBeVisible();
await expect(qrCode).toBeVisible({ timeout: 20000 });
const uri = await this.clickCopyLink();

const qrLoadedTime = new Date();
@@ -51,7 +51,7 @@ export class ModalPage {
const qrLoadInitiatedTime = new Date();

const qrCode = this.page.getByTestId('qr-code');
await expect(qrCode).toBeVisible();
await expect(qrCode).toBeVisible({ timeout: 20000 });
const uri = await this.clickCopyLink();
const qrLoadedTime = new Date();
if (timingRecords) {
4 changes: 2 additions & 2 deletions apps/native/tests/shared/pages/WalletPage.ts
Original file line number Diff line number Diff line change
@@ -81,8 +81,8 @@ export class WalletPage {
timeout: 30000
});
await expect(btn).toBeEnabled();
await btn.focus();
await this.page.keyboard.press('Space');
await this.page.waitForTimeout(1000);
await btn.click();
}

/**
6 changes: 3 additions & 3 deletions apps/native/tests/shared/validators/WalletValidator.ts
Original file line number Diff line number Diff line change
@@ -28,14 +28,14 @@ export class WalletValidator {
async expectSessionCard({ visible = true }: { visible?: boolean }) {
if (visible) {
await expect(
this.page.getByTestId('session-card'),
this.page.getByTestId('session-card').first(),
'Session card should be visible'
).toBeVisible({
timeout: MAX_WAIT
});
} else {
await expect(
this.page.getByTestId('session-card'),
this.page.getByTestId('session-card').first(),
'Session card should not be visible'
).not.toBeVisible({
timeout: MAX_WAIT
@@ -46,7 +46,7 @@ export class WalletValidator {
async expectDisconnected() {
await this.gotoSessions.click();
await expect(
this.page.getByTestId('session-card'),
this.page.getByTestId('session-card').first(),
'Session card should not be visible'
).not.toBeVisible({
timeout: MAX_WAIT
2 changes: 1 addition & 1 deletion apps/native/tests/wallet.spec.ts
Original file line number Diff line number Diff line change
@@ -137,7 +137,7 @@ sampleWalletTest('it should disconnect using hook', async () => {
await modalValidator.expectDisconnected();
});

sampleWalletTest('it should disconnect and close modal when connecting from wallet', async () => {
sampleWalletTest('it should disconnect and close modal when disconnecting from wallet', async () => {
await modalValidator.expectDisconnected();
await modalPage.qrCodeFlow(modalPage, walletPage);
await modalValidator.expectConnected();
Original file line number Diff line number Diff line change
@@ -19,7 +19,6 @@ const client: NetworkControllerClient = {
const initialState = {
_client: client,
supportsAllNetworks: true,
isDefaultCaipNetwork: false,
smartAccountEnabledNetworks: []
};

@@ -65,7 +64,7 @@ describe('NetworkController', () => {
it('should update state correctly on setDefaultCaipNetwork()', () => {
NetworkController.setDefaultCaipNetwork(caipNetwork);
expect(NetworkController.state.caipNetwork).toEqual(caipNetwork);
expect(NetworkController.state.isDefaultCaipNetwork).toEqual(true);
expect(NetworkController.state.defaultCaipNetwork).toEqual(caipNetwork);
});

it('should reset state correctly when default caip network is true', () => {
2 changes: 1 addition & 1 deletion packages/core/src/controllers/ConnectionController.ts
Original file line number Diff line number Diff line change
@@ -93,7 +93,7 @@ export const ConnectionController = {

async connectExternal(options: ConnectExternalOptions) {
await this._getClient().connectExternal?.(options);
ConnectorController.setConnectedConnector(options.type);
await ConnectorController.setConnectedConnector(options.type);
},

async signMessage(message: string) {
6 changes: 3 additions & 3 deletions packages/core/src/controllers/ConnectorController.ts
Original file line number Diff line number Diff line change
@@ -42,17 +42,17 @@ export const ConnectorController = {
return state.connectors.find(c => c.type === 'AUTH');
},

setConnectedConnector(
async setConnectedConnector(
connectorType: ConnectorControllerState['connectedConnector'],
saveStorage = true
) {
state.connectedConnector = connectorType;

if (saveStorage) {
if (connectorType) {
StorageUtil.setConnectedConnector(connectorType);
await StorageUtil.setConnectedConnector(connectorType);
} else {
StorageUtil.removeConnectedConnector();
await StorageUtil.removeConnectedConnector();
}
}
},
14 changes: 7 additions & 7 deletions packages/core/src/controllers/NetworkController.ts
Original file line number Diff line number Diff line change
@@ -15,9 +15,9 @@ export interface NetworkControllerClient {

export interface NetworkControllerState {
supportsAllNetworks: boolean;
isDefaultCaipNetwork: boolean;
_client?: NetworkControllerClient;
caipNetwork?: CaipNetwork;
defaultCaipNetwork?: CaipNetwork;
requestedCaipNetworks?: CaipNetwork[];
approvedCaipNetworkIds?: CaipNetworkId[];
smartAccountEnabledNetworks: number[];
@@ -26,7 +26,7 @@ export interface NetworkControllerState {
// -- State --------------------------------------------- //
const state = proxy<NetworkControllerState>({
supportsAllNetworks: true,
isDefaultCaipNetwork: false,
defaultCaipNetwork: undefined,
smartAccountEnabledNetworks: []
});

@@ -53,7 +53,7 @@ export const NetworkController = {

setDefaultCaipNetwork(caipNetwork: NetworkControllerState['caipNetwork']) {
state.caipNetwork = caipNetwork;
state.isDefaultCaipNetwork = true;
state.defaultCaipNetwork = caipNetwork;
PublicStateController.set({ selectedNetworkId: caipNetwork?.id });
},

@@ -84,9 +84,11 @@ export const NetworkController = {
},

getApprovedCaipNetworks() {
return state.approvedCaipNetworkIds
const networks = state.approvedCaipNetworkIds
?.map(id => state.requestedCaipNetworks?.find(network => network.id === id))
.filter(Boolean) as CaipNetwork[];

return networks ?? [];
},

getSmartAccountEnabledNetworks() {
@@ -110,9 +112,7 @@ export const NetworkController = {
},

resetNetwork() {
if (!state.isDefaultCaipNetwork) {
state.caipNetwork = undefined;
}
state.caipNetwork = state.defaultCaipNetwork || undefined;
state.approvedCaipNetworkIds = undefined;
state.supportsAllNetworks = true;
state.smartAccountEnabledNetworks = [];
12 changes: 7 additions & 5 deletions packages/core/src/utils/NetworkUtil.ts
Original file line number Diff line number Diff line change
@@ -7,18 +7,20 @@ import { SwapController } from '../controllers/SwapController';
import type { CaipNetwork } from '../utils/TypeUtil';

export const NetworkUtil = {
async handleNetworkSwitch(network: CaipNetwork) {
async handleNetworkSwitch(network: CaipNetwork, navigate: boolean = true) {
const { isConnected } = AccountController.state;
const { caipNetwork, approvedCaipNetworkIds, supportsAllNetworks } = NetworkController.state;
const isAuthConnected = ConnectorController.state.connectedConnector === 'AUTH';
const isAuthConnector = ConnectorController.state.connectedConnector === 'AUTH';
let eventData = null;

if (isConnected && caipNetwork?.id !== network.id) {
if (approvedCaipNetworkIds?.includes(network.id) && !isAuthConnected) {
if (approvedCaipNetworkIds?.includes(network.id) && !isAuthConnector) {
await NetworkController.switchActiveNetwork(network);
RouterUtil.navigateAfterNetworkSwitch(['ConnectingSiwe']);
if (navigate) {
RouterUtil.goBackOrCloseModal();
}
eventData = { type: 'SWITCH_NETWORK', networkId: network.id };
} else if (supportsAllNetworks || isAuthConnected) {
} else if (supportsAllNetworks || isAuthConnector) {
RouterController.push('SwitchNetwork', { network });
}
} else if (!isConnected) {
17 changes: 4 additions & 13 deletions packages/core/src/utils/RouterUtil.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,10 @@
import { RouterController, type RouterControllerState } from '../controllers/RouterController';
import { RouterController } from '../controllers/RouterController';
import { ModalController } from '../controllers/ModalController';

export const RouterUtil = {
navigateAfterNetworkSwitch(excludeViews: RouterControllerState['view'][] = []) {
if (excludeViews.includes(RouterController.state.view)) {
return;
}

const { history } = RouterController.state;
const networkSelectIndex = history.findIndex(
name => name === 'Networks' || name === 'UnsupportedChain'
);

if (networkSelectIndex >= 1) {
RouterController.goBackToIndex(networkSelectIndex - 1);
goBackOrCloseModal() {
if (RouterController.state.history.length > 1) {
RouterController.goBack();
} else {
ModalController.close();
}
205 changes: 113 additions & 92 deletions packages/ethers/src/client.ts
Original file line number Diff line number Diff line change
@@ -103,6 +103,12 @@ export class AppKit extends AppKitScaffold {

private authProvider?: AppKitFrameProvider;

private providerHandlers: {
disconnect: () => void;
accountsChanged: (accounts: string[]) => void;
chainChanged: (chainId: string) => void;
} | null = null;

public constructor(options: AppKitClientOptions) {
const {
config,
@@ -144,7 +150,7 @@ export class AppKit extends AppKitScaffold {
const authType = PresetsUtil.ConnectorTypesMap[ConstantsUtil.AUTH_CONNECTOR_ID]!;
if (walletChoice?.includes(walletConnectType)) {
const provider = await this.getWalletConnectProvider();
const result = getWalletConnectCaipNetworks(provider);
const result = await getWalletConnectCaipNetworks(provider);

resolve(result);
} else if (walletChoice?.includes(authType)) {
@@ -248,6 +254,7 @@ export class AppKit extends AppKitScaffold {
await this.setCoinbaseProvider(coinbaseProvider as Provider);
} catch (error) {
EthersStoreUtil.setError(error);
throw error;
}
} else if (id === ConstantsUtil.AUTH_CONNECTOR_ID) {
await this.setAuthProvider();
@@ -452,7 +459,7 @@ export class AppKit extends AppKitScaffold {
});

EthersStoreUtil.subscribeKey('chainId', () => {
this.syncNetwork(chainImages);
this.syncNetwork();
});

EthersStoreUtil.subscribeKey('provider', provider => {
@@ -609,7 +616,6 @@ export class AppKit extends AppKitScaffold {
if (walletId === ConstantsUtil.COINBASE_CONNECTOR_ID) {
if (CoinbaseProvider.address) {
await this.setCoinbaseProvider(provider);
await this.watchCoinbase(provider);
} else {
await StorageUtil.removeItem(EthersConstantsUtil.WALLET_ID);
EthersStoreUtil.reset();
@@ -623,12 +629,12 @@ export class AppKit extends AppKitScaffold {
const WalletConnectProvider = await this.getWalletConnectProvider();
if (WalletConnectProvider) {
const providerType = PresetsUtil.ConnectorTypesMap[ConstantsUtil.WALLET_CONNECT_CONNECTOR_ID];
EthersStoreUtil.setChainId(WalletConnectProvider.chainId);
EthersStoreUtil.setProviderType(providerType);
EthersStoreUtil.setProvider(WalletConnectProvider as unknown as Provider);
EthersStoreUtil.setIsConnected(true);
EthersStoreUtil.setChainId(WalletConnectProvider.chainId);
this.setAddress(WalletConnectProvider.accounts?.[0]);
await this.watchWalletConnect();
this.listenProviderEvents(WalletConnectProvider as unknown as Provider);
}
}

@@ -639,12 +645,12 @@ export class AppKit extends AppKitScaffold {
const { address, chainId } = await EthersHelpersUtil.getUserInfo(provider);
if (address && chainId) {
const providerType = PresetsUtil.ConnectorTypesMap[ConstantsUtil.COINBASE_CONNECTOR_ID];
EthersStoreUtil.setChainId(chainId);
EthersStoreUtil.setProviderType(providerType);
EthersStoreUtil.setProvider(provider);
EthersStoreUtil.setIsConnected(true);
this.setAddress(address);
await this.watchCoinbase(provider);
EthersStoreUtil.setChainId(chainId);
this.listenProviderEvents(provider);
}
}
}
@@ -656,140 +662,155 @@ export class AppKit extends AppKitScaffold {
const { address, chainId } = await this.authProvider.connect();
super.setLoading(false);
if (address && chainId) {
EthersStoreUtil.setChainId(chainId);
EthersStoreUtil.setProviderType(
PresetsUtil.ConnectorTypesMap[ConstantsUtil.AUTH_CONNECTOR_ID]
);
EthersStoreUtil.setProvider(this.authProvider as CombinedProviderType);
EthersStoreUtil.setIsConnected(true);
EthersStoreUtil.setAddress(address as Address);
EthersStoreUtil.setChainId(chainId);
}
}
}

private async watchWalletConnect() {
const WalletConnectProvider = await this.getWalletConnectProvider();

function disconnectHandler() {
private listenProviderEvents(provider: Provider) {
const disconnectHandler = () => {
this.removeProviderListeners(provider);
StorageUtil.removeItem(EthersConstantsUtil.WALLET_ID);
EthersStoreUtil.reset();

WalletConnectProvider?.removeListener('disconnect', disconnectHandler);
WalletConnectProvider?.removeListener('accountsChanged', accountsChangedHandler);
WalletConnectProvider?.removeListener('chainChanged', chainChangedHandler);
}

function chainChangedHandler(chainId: string) {
if (chainId) {
const chain = EthersHelpersUtil.hexStringToNumber(chainId);
EthersStoreUtil.setChainId(chain);
}
}

const accountsChangedHandler = async (accounts: string[]) => {
if (accounts.length > 0) {
await this.setWalletConnectProvider();
}
this.setClientId(null);
};

if (WalletConnectProvider) {
WalletConnectProvider.on('disconnect', disconnectHandler);
WalletConnectProvider.on('accountsChanged', accountsChangedHandler);
WalletConnectProvider.on('chainChanged', chainChangedHandler);
}
}

private async watchCoinbase(provider: Provider) {
const walletId = await StorageUtil.getItem(EthersConstantsUtil.WALLET_ID);

function disconnectHandler() {
StorageUtil.removeItem(EthersConstantsUtil.WALLET_ID);
EthersStoreUtil.reset();

provider?.removeListener('disconnect', disconnectHandler);
provider?.removeListener('accountsChanged', accountsChangedHandler);
provider?.removeListener('chainChanged', chainChangedHandler);
}

function accountsChangedHandler(accounts: string[]) {
const accountsChangedHandler = (accounts: string[]) => {
if (accounts.length === 0) {
StorageUtil.removeItem(EthersConstantsUtil.WALLET_ID);
EthersStoreUtil.reset();
disconnectHandler();
} else {
EthersStoreUtil.setAddress(accounts[0] as Address);
}
}
};

function chainChangedHandler(chainId: string) {
if (chainId && walletId === ConstantsUtil.COINBASE_CONNECTOR_ID) {
const chain = Number(chainId);
const chainChangedHandler = (chainId: string) => {
if (chainId) {
const chain = EthersHelpersUtil.hexStringToNumber(chainId);
EthersStoreUtil.setChainId(chain);
}
}
};

if (provider) {
provider.on('disconnect', disconnectHandler);
provider.on('accountsChanged', accountsChangedHandler);
provider.on('chainChanged', chainChangedHandler);
provider.on('disconnect', disconnectHandler);
provider.on('accountsChanged', accountsChangedHandler);
provider.on('chainChanged', chainChangedHandler);

this.providerHandlers = {
disconnect: disconnectHandler,
accountsChanged: accountsChangedHandler,
chainChanged: chainChangedHandler
};
}

private removeProviderListeners(provider: Provider) {
if (this.providerHandlers) {
provider.removeListener('disconnect', this.providerHandlers.disconnect);
provider.removeListener('accountsChanged', this.providerHandlers.accountsChanged);
provider.removeListener('chainChanged', this.providerHandlers.chainChanged);
this.providerHandlers = null;
}
}

private async syncAccount({ address }: { address?: Address }) {
const chainId = EthersStoreUtil.state.chainId;
const isConnected = EthersStoreUtil.state.isConnected;
const isSiweEnabled = this.options?.siweConfig?.options?.enabled;

if (isConnected && address && chainId) {
if (address && chainId) {
const caipAddress: CaipAddress = `${ConstantsUtil.EIP155}:${chainId}:${address}`;

this.setIsConnected(isConnected);
EthersStoreUtil.setIsConnected(true);
this.setIsConnected(true);

this.setCaipAddress(caipAddress);
this.resetTransactions();

await Promise.all([
this.syncProfile(address),
this.syncBalance(address),
this.getApprovedCaipNetworksData()
]);
await Promise.all([this.syncProfile(address), this.syncBalance(address)]);
this.hasSyncedConnectedAccount = true;
} else if (!isConnected && this.hasSyncedConnectedAccount) {

if (isSiweEnabled) {
this.handleSiweChange({ isNetworkChange: false, isAccountChange: true });
}
} else if (!address && this.hasSyncedConnectedAccount) {
this.close();
this.resetAccount();
this.resetWcConnection();
this.resetNetwork();
}
}

private async syncNetwork(chainImages?: AppKitClientOptions['chainImages']) {
private async checkNetworkSupport(params: { chainId?: number; isConnected?: boolean }) {
// wait until the session is set
await new Promise(resolve => setTimeout(resolve, 500));

const { isConnected = false, chainId } = params;
const chain = this.chains.find((c: Chain) => c.chainId === chainId);

if (chain) {
const caipChainId: CaipNetworkId = `${ConstantsUtil.EIP155}:${chain.chainId}`;
this.setCaipNetwork({
id: caipChainId,
name: chain.name,
imageId: PresetsUtil.EIP155NetworkImageIds[chain.chainId],
imageUrl: this.options?.chainImages?.[chain.chainId]
});
} else if (params.chainId) {
this.setCaipNetwork({
id: `${ConstantsUtil.EIP155}:${params.chainId}`,
name: 'Unsupported Network'
});
this.setAddressExplorerUrl(undefined);
this.setBalance(undefined, undefined);
}

if (isConnected) {
await this.getApprovedCaipNetworksData();
const isApproved = this.getApprovedCaipNetworks().some(
network => network.id === `${ConstantsUtil.EIP155}:${params.chainId}`
);

const isSupported = !!chain && isApproved;

this.openUnsupportedNetworkView(isSupported);

return isSupported;
}

return false;
}

private async syncNetwork() {
const address = EthersStoreUtil.state.address;
const chainId = EthersStoreUtil.state.chainId;
const isConnected = EthersStoreUtil.state.isConnected;
if (this.chains) {
const chain = this.chains.find(c => c.chainId === chainId);
const isSupported = await this.checkNetworkSupport({ chainId, isConnected });
const isSiweEnabled = this.options?.siweConfig?.options?.enabled;

if (chain && isConnected && address) {
const caipAddress: CaipAddress = `${ConstantsUtil.EIP155}:${chainId}:${address}`;
this.setCaipAddress(caipAddress);
this.resetTransactions();
if (chain.explorerUrl) {
const url = `${chain.explorerUrl}/address/${address}`;
this.setAddressExplorerUrl(url);
} else {
this.setAddressExplorerUrl(undefined);
}

if (chain) {
const caipChainId: CaipNetworkId = `${ConstantsUtil.EIP155}:${chain.chainId}`;

this.setCaipNetwork({
id: caipChainId,
name: chain.name,
imageId: PresetsUtil.EIP155NetworkImageIds[chain.chainId],
imageUrl: chainImages?.[chain.chainId]
});
if (isConnected && address) {
const caipAddress: CaipAddress = `${ConstantsUtil.EIP155}:${chainId}:${address}`;
this.setCaipAddress(caipAddress);
if (chain.explorerUrl) {
const url = `${chain.explorerUrl}/address/${address}`;
this.setAddressExplorerUrl(url);
} else {
this.setAddressExplorerUrl(undefined);
}

if (this.hasSyncedConnectedAccount) {
await this.syncBalance(address);
}
if (this.hasSyncedConnectedAccount) {
await this.syncBalance(address);
}
}

if (isConnected && isSupported && isSiweEnabled) {
this.handleSiweChange({ isNetworkChange: true, isAccountChange: false });
}
}
}

205 changes: 113 additions & 92 deletions packages/ethers5/src/client.ts
Original file line number Diff line number Diff line change
@@ -90,6 +90,12 @@ export class AppKit extends AppKitScaffold {

private authProvider?: AppKitFrameProvider;

private providerHandlers: {
disconnect: () => void;
accountsChanged: (accounts: string[]) => void;
chainChanged: (chainId: string) => void;
} | null = null;

public constructor(options: AppKitClientOptions) {
const {
config,
@@ -131,7 +137,7 @@ export class AppKit extends AppKitScaffold {
const authType = PresetsUtil.ConnectorTypesMap[ConstantsUtil.AUTH_CONNECTOR_ID]!;
if (walletChoice?.includes(walletConnectType)) {
const provider = await this.getWalletConnectProvider();
const result = getWalletConnectCaipNetworks(provider);
const result = await getWalletConnectCaipNetworks(provider);

resolve(result);
} else if (walletChoice?.includes(authType)) {
@@ -235,6 +241,7 @@ export class AppKit extends AppKitScaffold {
await this.setCoinbaseProvider(coinbaseProvider as Provider);
} catch (error) {
EthersStoreUtil.setError(error);
throw error;
}
} else if (id === ConstantsUtil.AUTH_CONNECTOR_ID) {
await this.setAuthProvider();
@@ -434,7 +441,7 @@ export class AppKit extends AppKitScaffold {
});

EthersStoreUtil.subscribeKey('chainId', () => {
this.syncNetwork(chainImages);
this.syncNetwork();
});

EthersStoreUtil.subscribeKey('provider', provider => {
@@ -509,7 +516,7 @@ export class AppKit extends AppKitScaffold {
EthersStoreUtil.reset();
this.setClientId(null);

await (provider as unknown as EthereumProvider).disconnect();
await (provider as unknown as EthereumProvider)?.disconnect?.();
}

// -- Private -----------------------------------------------------------------
@@ -589,7 +596,6 @@ export class AppKit extends AppKitScaffold {
if (walletId === ConstantsUtil.COINBASE_CONNECTOR_ID) {
if (CoinbaseProvider.address) {
await this.setCoinbaseProvider(provider);
await this.watchCoinbase(provider);
} else {
await StorageUtil.removeItem(EthersConstantsUtil.WALLET_ID);
EthersStoreUtil.reset();
@@ -603,12 +609,12 @@ export class AppKit extends AppKitScaffold {
const WalletConnectProvider = await this.getWalletConnectProvider();
if (WalletConnectProvider) {
const providerType = PresetsUtil.ConnectorTypesMap[ConstantsUtil.WALLET_CONNECT_CONNECTOR_ID];
EthersStoreUtil.setChainId(WalletConnectProvider.chainId);
EthersStoreUtil.setProviderType(providerType);
EthersStoreUtil.setProvider(WalletConnectProvider as unknown as Provider);
EthersStoreUtil.setIsConnected(true);
this.setAddress(WalletConnectProvider.accounts?.[0]);
await this.watchWalletConnect();
EthersStoreUtil.setChainId(WalletConnectProvider.chainId);
this.listenProviderEvents(WalletConnectProvider as unknown as Provider);
}
}

@@ -619,12 +625,12 @@ export class AppKit extends AppKitScaffold {
const { address, chainId } = await EthersHelpersUtil.getUserInfo(provider);
if (address && chainId) {
const providerType = PresetsUtil.ConnectorTypesMap[ConstantsUtil.COINBASE_CONNECTOR_ID];
EthersStoreUtil.setChainId(chainId);
EthersStoreUtil.setProviderType(providerType);
EthersStoreUtil.setProvider(provider);
EthersStoreUtil.setIsConnected(true);
this.setAddress(address);
await this.watchCoinbase(provider);
EthersStoreUtil.setChainId(chainId);
this.listenProviderEvents(provider);
}
}
}
@@ -647,129 +653,144 @@ export class AppKit extends AppKitScaffold {
}
}

private async watchWalletConnect() {
const WalletConnectProvider = await this.getWalletConnectProvider();

function disconnectHandler() {
private listenProviderEvents(provider: Provider) {
const disconnectHandler = () => {
this.removeProviderListeners(provider);
StorageUtil.removeItem(EthersConstantsUtil.WALLET_ID);
EthersStoreUtil.reset();

WalletConnectProvider?.removeListener('disconnect', disconnectHandler);
WalletConnectProvider?.removeListener('accountsChanged', accountsChangedHandler);
WalletConnectProvider?.removeListener('chainChanged', chainChangedHandler);
}

function chainChangedHandler(chainId: string) {
if (chainId) {
const chain = EthersHelpersUtil.hexStringToNumber(chainId);
EthersStoreUtil.setChainId(chain);
}
}

const accountsChangedHandler = async (accounts: string[]) => {
if (accounts.length > 0) {
await this.setWalletConnectProvider();
}
this.setClientId(null);
};

if (WalletConnectProvider) {
WalletConnectProvider.on('disconnect', disconnectHandler);
WalletConnectProvider.on('accountsChanged', accountsChangedHandler);
WalletConnectProvider.on('chainChanged', chainChangedHandler);
}
}

private async watchCoinbase(provider: Provider) {
const walletId = await StorageUtil.getItem(EthersConstantsUtil.WALLET_ID);

function disconnectHandler() {
StorageUtil.removeItem(EthersConstantsUtil.WALLET_ID);
EthersStoreUtil.reset();

provider?.removeListener('disconnect', disconnectHandler);
provider?.removeListener('accountsChanged', accountsChangedHandler);
provider?.removeListener('chainChanged', chainChangedHandler);
}

function accountsChangedHandler(accounts: string[]) {
const accountsChangedHandler = (accounts: string[]) => {
if (accounts.length === 0) {
StorageUtil.removeItem(EthersConstantsUtil.WALLET_ID);
EthersStoreUtil.reset();
disconnectHandler();
} else {
EthersStoreUtil.setAddress(accounts[0] as Address);
}
}
};

function chainChangedHandler(chainId: string) {
if (chainId && walletId === ConstantsUtil.COINBASE_CONNECTOR_ID) {
const chain = Number(chainId);
const chainChangedHandler = (chainId: string) => {
if (chainId) {
const chain = EthersHelpersUtil.hexStringToNumber(chainId);
EthersStoreUtil.setChainId(chain);
}
}
};

if (provider) {
provider.on('disconnect', disconnectHandler);
provider.on('accountsChanged', accountsChangedHandler);
provider.on('chainChanged', chainChangedHandler);
provider.on('disconnect', disconnectHandler);
provider.on('accountsChanged', accountsChangedHandler);
provider.on('chainChanged', chainChangedHandler);

this.providerHandlers = {
disconnect: disconnectHandler,
accountsChanged: accountsChangedHandler,
chainChanged: chainChangedHandler
};
}

private removeProviderListeners(provider: Provider) {
if (this.providerHandlers) {
provider.removeListener('disconnect', this.providerHandlers.disconnect);
provider.removeListener('accountsChanged', this.providerHandlers.accountsChanged);
provider.removeListener('chainChanged', this.providerHandlers.chainChanged);
this.providerHandlers = null;
}
}

private async syncAccount({ address }: { address?: Address }) {
const chainId = EthersStoreUtil.state.chainId;
const isConnected = EthersStoreUtil.state.isConnected;
const isSiweEnabled = this.options?.siweConfig?.options?.enabled;

if (isConnected && address && chainId) {
if (address && chainId) {
const caipAddress: CaipAddress = `${ConstantsUtil.EIP155}:${chainId}:${address}`;

this.setIsConnected(isConnected);
EthersStoreUtil.setIsConnected(true);
this.setIsConnected(true);

this.setCaipAddress(caipAddress);
this.resetTransactions();

await Promise.all([
this.syncProfile(address),
this.syncBalance(address),
this.getApprovedCaipNetworksData()
]);
await Promise.all([this.syncProfile(address), this.syncBalance(address)]);
this.hasSyncedConnectedAccount = true;
} else if (!isConnected && this.hasSyncedConnectedAccount) {

if (isSiweEnabled) {
this.handleSiweChange({ isNetworkChange: false, isAccountChange: true });
}
} else if (!address && this.hasSyncedConnectedAccount) {
this.close();
this.resetAccount();
this.resetWcConnection();
this.resetNetwork();
}
}

private async syncNetwork(chainImages?: AppKitClientOptions['chainImages']) {
private async checkNetworkSupport(params: { chainId?: number; isConnected?: boolean }) {
// wait until the session is set
await new Promise(resolve => setTimeout(resolve, 500));

const { isConnected = false, chainId } = params;
const chain = this.chains.find((c: Chain) => c.chainId === chainId);

if (chain) {
const caipChainId: CaipNetworkId = `${ConstantsUtil.EIP155}:${chain.chainId}`;
this.setCaipNetwork({
id: caipChainId,
name: chain.name,
imageId: PresetsUtil.EIP155NetworkImageIds[chain.chainId],
imageUrl: this.options?.chainImages?.[chain.chainId]
});
} else if (params.chainId) {
this.setCaipNetwork({
id: `${ConstantsUtil.EIP155}:${params.chainId}`,
name: 'Unsupported Network'
});
this.setAddressExplorerUrl(undefined);
this.setBalance(undefined, undefined);
}

if (isConnected) {
await this.getApprovedCaipNetworksData();
const isApproved = this.getApprovedCaipNetworks().some(
network => network.id === `${ConstantsUtil.EIP155}:${params.chainId}`
);

const isSupported = !!chain && isApproved;

this.openUnsupportedNetworkView(isSupported);

return isSupported;
}

return false;
}

private async syncNetwork() {
const address = EthersStoreUtil.state.address;
const chainId = EthersStoreUtil.state.chainId;
const isConnected = EthersStoreUtil.state.isConnected;
if (this.chains) {
const chain = this.chains.find(c => c.chainId === chainId);
const isSupported = await this.checkNetworkSupport({ chainId, isConnected });
const isSiweEnabled = this.options?.siweConfig?.options?.enabled;

if (chain && isConnected && address) {
const caipAddress: CaipAddress = `${ConstantsUtil.EIP155}:${chainId}:${address}`;
this.setCaipAddress(caipAddress);
this.resetTransactions();
if (chain.explorerUrl) {
const url = `${chain.explorerUrl}/address/${address}`;
this.setAddressExplorerUrl(url);
} else {
this.setAddressExplorerUrl(undefined);
}

if (chain) {
const caipChainId: CaipNetworkId = `${ConstantsUtil.EIP155}:${chain.chainId}`;

this.setCaipNetwork({
id: caipChainId,
name: chain.name,
imageId: PresetsUtil.EIP155NetworkImageIds[chain.chainId],
imageUrl: chainImages?.[chain.chainId]
});
if (isConnected && address) {
const caipAddress: CaipAddress = `${ConstantsUtil.EIP155}:${chainId}:${address}`;
this.setCaipAddress(caipAddress);
if (chain.explorerUrl) {
const url = `${chain.explorerUrl}/address/${address}`;
this.setAddressExplorerUrl(url);
} else {
this.setAddressExplorerUrl(undefined);
}

if (this.hasSyncedConnectedAccount) {
await this.syncBalance(address);
}
if (this.hasSyncedConnectedAccount) {
await this.syncBalance(address);
}
}

if (isConnected && isSupported && isSiweEnabled) {
this.handleSiweChange({ isNetworkChange: true, isAccountChange: false });
}
}
}

56 changes: 55 additions & 1 deletion packages/scaffold/src/client.ts
Original file line number Diff line number Diff line change
@@ -27,6 +27,8 @@ import {
NetworkController,
OptionsController,
PublicStateController,
RouterController,
RouterUtil,
SnackController,
StorageUtil,
ThemeController,
@@ -200,6 +202,9 @@ export class AppKitScaffold {
protected getApprovedCaipNetworksData: (typeof NetworkController)['getApprovedCaipNetworksData'] =
() => NetworkController.getApprovedCaipNetworksData();

protected getApprovedCaipNetworks: (typeof NetworkController)['getApprovedCaipNetworks'] = () =>
NetworkController.getApprovedCaipNetworks();

protected resetNetwork: (typeof NetworkController)['resetNetwork'] = () => {
NetworkController.resetNetwork();
};
@@ -274,6 +279,55 @@ export class AppKitScaffold {
}
}

protected onSiweNavigation = () => {
if (ModalController.state.open) {
RouterController.push('ConnectingSiwe');
} else {
ModalController.open({ view: 'ConnectingSiwe' });
}
};

protected openUnsupportedNetworkView(isSupported: boolean) {
if (isSupported && RouterController.state.view === 'UnsupportedChain') {
return RouterUtil.goBackOrCloseModal();
} else if (!isSupported) {
if (ModalController.state.open) {
RouterController.push('UnsupportedChain');
} else {
ModalController.open({ view: 'UnsupportedChain' });
}
}
}

protected async handleSiweChange(params: {
isNetworkChange?: boolean;
isAccountChange?: boolean;
}) {
const { isNetworkChange, isAccountChange } = params;
const { enabled, signOutOnAccountChange, signOutOnNetworkChange } =
SIWEController.state._client?.options ?? {};

if (enabled) {
const session = await SIWEController.getSession();
if (session && isAccountChange && signOutOnAccountChange) {
// If the address has changed and signOnAccountChange is enabled, sign out
await SIWEController.signOut();
this.onSiweNavigation();
} else if (isNetworkChange && signOutOnNetworkChange) {
// If the network has changed and signOnNetworkChange is enabled, sign out
await SIWEController.signOut();
this.onSiweNavigation();
} else if (!session) {
// If it's connected but there's no session, show sign view
this.onSiweNavigation();
}
}
}

protected resetTransactions() {
TransactionsController.resetTransactions();
}

// -- Private ------------------------------------------------------------------
private async initControllers(options: ScaffoldOptions) {
this.initAsyncValues(options);
@@ -356,7 +410,7 @@ export class AppKitScaffold {
private async initConnectedConnector() {
const connectedConnector = await StorageUtil.getConnectedConnector();
if (connectedConnector) {
ConnectorController.setConnectedConnector(connectedConnector, false);
await ConnectorController.setConnectedConnector(connectedConnector, false);
}
}

74 changes: 18 additions & 56 deletions packages/scaffold/src/modal/w3m-modal/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useSnapshot } from 'valtio';
import { useCallback, useEffect } from 'react';
import { useEffect } from 'react';
import { useWindowDimensions, StatusBar } from 'react-native';
import Modal from 'react-native-modal';
import { Card, ThemeProvider } from '@reown/appkit-ui-react-native';
@@ -8,13 +8,10 @@ import {
ApiController,
ConnectionController,
ConnectorController,
CoreHelperUtil,
EventsController,
ModalController,
OptionsController,
RouterController,
TransactionsController,
type CaipAddress,
type AppKitFrameProvider,
ThemeController
} from '@reown/appkit-core-react-native';
@@ -26,10 +23,12 @@ import { Snackbar } from '../../partials/w3m-snackbar';
import { useCustomDimensions } from '../../hooks/useCustomDimensions';
import styles from './styles';

const disableCloseViews = ['UnsupportedChain', 'ConnectingSiwe'];

export function AppKit() {
const { open, loading } = useSnapshot(ModalController.state);
const { open } = useSnapshot(ModalController.state);
const { connectors, connectedConnector } = useSnapshot(ConnectorController.state);
const { caipAddress, isConnected } = useSnapshot(AccountController.state);

const { themeMode, themeVariables } = useSnapshot(ThemeController.state);
const { height } = useWindowDimensions();
const { isLandscape } = useCustomDimensions();
@@ -40,7 +39,19 @@ export function AppKit() {
const SocialView = authProvider?.Webview;
const showAuth = !connectedConnector || connectedConnector === 'AUTH';

const onBackdropPress = () => {
if (disableCloseViews.includes(RouterController.state.view)) {
return;
}

return ModalController.close();
};

const onBackButtonPress = () => {
if (disableCloseViews.includes(RouterController.state.view)) {
return;
}

if (RouterController.state.history.length > 1) {
return RouterController.goBack();
}
@@ -61,59 +72,10 @@ export function AppKit() {
}
};

const onNewAddress = useCallback(
async (address?: CaipAddress) => {
if (!isConnected || loading) {
return;
}

const newAddress = CoreHelperUtil.getPlainAddress(address);
TransactionsController.resetTransactions();

if (OptionsController.state.isSiweEnabled) {
const newNetworkId = CoreHelperUtil.getNetworkId(address);

const { signOutOnAccountChange, signOutOnNetworkChange } =
SIWEController.state._client?.options ?? {};
const session = await SIWEController.getSession();

if (session && newAddress && signOutOnAccountChange) {
// If the address has changed and signOnAccountChange is enabled, sign out
await SIWEController.signOut();
onSiweNavigation();
} else if (
newNetworkId &&
session?.chainId.toString() !== newNetworkId &&
signOutOnNetworkChange
) {
// If the network has changed and signOnNetworkChange is enabled, sign out
await SIWEController.signOut();
onSiweNavigation();
} else if (!session) {
// If it's connected but there's no session, show sign view
onSiweNavigation();
}
}
},
[isConnected, loading]
);

const onSiweNavigation = () => {
if (ModalController.state.open) {
RouterController.push('ConnectingSiwe');
} else {
ModalController.open({ view: 'ConnectingSiwe' });
}
};

useEffect(() => {
prefetch();
}, []);

useEffect(() => {
onNewAddress(caipAddress);
}, [caipAddress, onNewAddress]);

return (
<>
<ThemeProvider themeMode={themeMode} themeVariables={themeVariables}>
@@ -127,7 +89,7 @@ export function AppKit() {
hideModalContentWhileAnimating
propagateSwipe
onModalHide={handleClose}
onBackdropPress={ModalController.close}
onBackdropPress={onBackdropPress}
onBackButtonPress={onBackButtonPress}
testID="w3m-modal"
>
11 changes: 7 additions & 4 deletions packages/scaffold/src/modal/w3m-network-button/index.tsx
Original file line number Diff line number Diff line change
@@ -10,16 +10,17 @@ import {
ThemeController
} from '@reown/appkit-core-react-native';
import { NetworkButton as NetworkButtonUI, ThemeProvider } from '@reown/appkit-ui-react-native';
import { UiUtil } from '../../utils/UiUtil';

export interface NetworkButtonProps {
disabled?: boolean;
style?: StyleProp<ViewStyle>;
}

export function NetworkButton({ disabled, style }: NetworkButtonProps) {
const { isConnected } = useSnapshot(AccountController.state);
const { caipNetwork } = useSnapshot(NetworkController.state);
const { loading } = useSnapshot(ModalController.state);
const { caipNetwork } = useSnapshot(NetworkController.state);
const { isConnected } = useSnapshot(AccountController.state);
const { themeMode, themeVariables } = useSnapshot(ThemeController.state);

const onNetworkPress = () => {
@@ -30,18 +31,20 @@ export function NetworkButton({ disabled, style }: NetworkButtonProps) {
});
};

const buttonText = UiUtil.getNetworkButtonText(isConnected, caipNetwork);

return (
<ThemeProvider themeMode={themeMode} themeVariables={themeVariables}>
<NetworkButtonUI
imageSrc={AssetUtil.getNetworkImage(caipNetwork)}
imageSrc={AssetUtil.getNetworkImage(NetworkController.state.caipNetwork)}
imageHeaders={ApiController._getApiHeaders()}
disabled={disabled || loading}
style={style}
onPress={onNetworkPress}
loading={loading}
testID="network-button"
>
{caipNetwork?.name ?? (isConnected ? 'Unknown Network' : 'Select Network')}
{buttonText}
</NetworkButtonUI>
</ThemeProvider>
);
11 changes: 7 additions & 4 deletions packages/scaffold/src/partials/w3m-header/index.tsx
Original file line number Diff line number Diff line change
@@ -19,6 +19,7 @@ export function Header() {
RouterController.push('WhatIsAWallet');
EventsController.sendEvent({ type: 'track', event: 'CLICK_WALLET_HELP' });
};
const showButtons = !['ConnectingSiwe', 'UnsupportedChain'].includes(view);

const headings = (_data: RouterControllerState['data'], _view: RouterControllerState['view']) => {
const connectorName = _data?.connector?.name;
@@ -100,9 +101,7 @@ export function Header() {
};

const dynamicButtonTemplate = () => {
const noButtonViews = ['ConnectingSiwe'];

if (noButtonViews.includes(RouterController.state.view)) {
if (!showButtons) {
return <FlexView style={styles.iconPlaceholder} />;
}

@@ -130,7 +129,11 @@ export function Header() {
<Text variant="paragraph-600" numberOfLines={1} testID="header-text">
{header}
</Text>
<IconLink icon="close" size="md" onPress={handleClose} testID="header-close" />
{showButtons ? (
<IconLink icon="close" size="md" onPress={handleClose} testID="header-close" />
) : (
<FlexView style={styles.iconPlaceholder} />
)}
</FlexView>
);
}
23 changes: 22 additions & 1 deletion packages/scaffold/src/utils/UiUtil.ts
Original file line number Diff line number Diff line change
@@ -2,7 +2,8 @@ import {
AssetUtil,
ConnectionController,
StorageUtil,
type WcWallet
type WcWallet,
type CaipNetwork
} from '@reown/appkit-core-react-native';
import { LayoutAnimation } from 'react-native';

@@ -27,5 +28,25 @@ export const UiUtil = {
const url = AssetUtil.getWalletImage(pressedWallet);
ConnectionController.setConnectedWalletImageUrl(url);
}
},

getNetworkButtonText(isConnected: boolean, caipNetwork: CaipNetwork | undefined): string {
let buttonText: string;

if (!isConnected) {
if (caipNetwork) {
buttonText = caipNetwork.name ?? 'Unknown Network';
} else {
buttonText = 'Select Network';
}
} else {
if (caipNetwork) {
buttonText = caipNetwork.name ?? 'Unknown Network';
} else {
buttonText = 'Select Network';
}
}

return buttonText;
}
};
14 changes: 8 additions & 6 deletions packages/scaffold/src/views/w3m-account-default-view/index.tsx
Original file line number Diff line number Diff line change
@@ -30,6 +30,7 @@ import {
ListItem
} from '@reown/appkit-ui-react-native';
import { useCustomDimensions } from '../../hooks/useCustomDimensions';
import { UiUtil as ScaffoldUiUtil } from '../../utils/UiUtil';

import styles from './styles';
import { AuthButtons } from './components/auth-buttons';
@@ -42,7 +43,8 @@ export function AccountDefaultView() {
balance,
balanceSymbol,
addressExplorerUrl,
preferredAccountType
preferredAccountType,
isConnected
} = useSnapshot(AccountController.state);
const { loading } = useSnapshot(ModalController.state);
const [disconnecting, setDisconnecting] = useState(false);
@@ -238,17 +240,17 @@ export function AccountDefaultView() {
)}
<ListItem
chevron
icon="networkPlaceholder"
iconColor="accent-100"
iconBackgroundColor="accent-glass-015"
icon={'networkPlaceholder'}
iconColor={'accent-100'}
iconBackgroundColor={'accent-glass-015'}
imageSrc={networkImage}
imageHeaders={ApiController._getApiHeaders()}
onPress={onNetworkPress}
testID="button-network"
style={styles.actionButton}
>
<Text numberOfLines={1} color="fg-100" testID="account-select-network-text">
{caipNetwork?.name}
<Text numberOfLines={1} color={'fg-100'} testID="account-select-network-text">
{ScaffoldUiUtil.getNetworkButtonText(isConnected, caipNetwork)}
</Text>
</ListItem>

2 changes: 1 addition & 1 deletion packages/scaffold/src/views/w3m-connecting-view/index.tsx
Original file line number Diff line number Diff line change
@@ -50,7 +50,7 @@ export function ConnectingView() {
ConnectionController.setWcError(false);
ConnectionController.connectWalletConnect(routeData?.wallet?.link_mode ?? undefined);
await ConnectionController.state.wcPromise;
ConnectorController.setConnectedConnector('WALLET_CONNECT');
await ConnectorController.setConnectedConnector('WALLET_CONNECT');
AccountController.setIsConnected(true);

if (OptionsController.state.isSiweEnabled) {
Original file line number Diff line number Diff line change
@@ -56,7 +56,7 @@ export function NetworkSwitchView() {
useEffect(() => {
// Go back if network is already switched
if (caipNetwork?.id === network?.id) {
RouterUtil.navigateAfterNetworkSwitch();
RouterUtil.goBackOrCloseModal();
}
}, [caipNetwork?.id, network?.id]);

13 changes: 9 additions & 4 deletions packages/scaffold/src/views/w3m-unsupported-chain-view/index.tsx
Original file line number Diff line number Diff line change
@@ -11,7 +11,8 @@ import {
NetworkController,
NetworkUtil,
type CaipNetwork,
type NetworkControllerState
type NetworkControllerState,
ModalController
} from '@reown/appkit-core-react-native';
import styles from './styles';

@@ -24,7 +25,11 @@ export function UnsupportedChainView() {
const imageHeaders = ApiController._getApiHeaders();

const onNetworkPress = async (network: CaipNetwork) => {
const result = await NetworkUtil.handleNetworkSwitch(network);
if (NetworkController.state.caipNetwork?.id === network.id) {
return ModalController.close();
}

const result = await NetworkUtil.handleNetworkSwitch(network, false);
if (result?.type === 'SWITCH_NETWORK') {
EventsController.sendEvent({
type: 'track',
@@ -49,8 +54,8 @@ export function UnsupportedChainView() {
ListHeaderComponentStyle={styles.header}
ListHeaderComponent={
<Text variant="small-400" color="fg-200" center>
The swap feature doesn't support your current network. Switch to an available option to
continue.
The current network is not supported by this application. Please switch to an available
option to continue.
</Text>
}
contentContainerStyle={styles.contentContainer}
4 changes: 2 additions & 2 deletions packages/siwe/src/client.ts
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@ import {
AccountController,
NetworkController,
ConnectionController,
RouterUtil
ModalController
} from '@reown/appkit-core-react-native';
import { NetworkUtil } from '@reown/appkit-common-react-native';

@@ -120,7 +120,7 @@ export class AppKitSIWEClient {
this.methods.onSignIn(session);
}

RouterUtil.navigateAfterNetworkSwitch();
ModalController.close();

return session;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useSnapshot } from 'valtio';
import { Button, FlexView, IconLink, Text } from '@reown/appkit-ui-react-native';
import { Button, FlexView, Text } from '@reown/appkit-ui-react-native';
import {
AccountController,
ConnectionController,
@@ -86,13 +86,6 @@ export function ConnectingSiweView() {

return (
<FlexView padding={['2xl', 's', '3xl', 's']}>
<IconLink
icon="close"
size="md"
onPress={onCancel}
testID="header-close"
style={styles.closeButton}
/>
<Text variant="paragraph-600" numberOfLines={1} center>
Sign in
</Text>
4 changes: 4 additions & 0 deletions packages/ui/src/utils/TypesUtil.ts
Original file line number Diff line number Diff line change
@@ -113,6 +113,10 @@ export type ColorType =
| 'gray-glass-010'
| 'gray-glass-005'
| 'gray-glass-002'
| 'error-glass-020'
| 'error-glass-015'
| 'error-glass-010'
| 'error-glass-005'
| 'inverse-000'
| 'inverse-100'
| 'success-100'
97 changes: 76 additions & 21 deletions packages/wagmi/src/client.ts
Original file line number Diff line number Diff line change
@@ -124,7 +124,7 @@ export class AppKit extends AppKitScaffold {
);

return getWalletConnectCaipNetworks(connector);
} else if (authType) {
} else if (walletChoice?.includes(authType)) {
return getAuthCaipNetworks();
}

@@ -157,7 +157,8 @@ export class AppKit extends AppKitScaffold {
this.setClientId(clientId);
}

const chainId = NetworkUtil.caipNetworkIdToNumber(this.getCaipNetwork()?.id);
const caipNetwork = this.getCaipNetwork();
const chainId = NetworkUtil.caipNetworkIdToNumber(caipNetwork?.id);

// SIWE
const siweParams = await siweConfig?.getMessageParams?.();
@@ -198,7 +199,7 @@ export class AppKit extends AppKitScaffold {
cacao: signedCacao
});

if (address && chainId) {
if (address && cacaoChainId) {
const session = {
address,
chainId: parseInt(cacaoChainId, 10)
@@ -246,10 +247,6 @@ export class AppKit extends AppKitScaffold {
disconnect: async () => {
await disconnect(this.wagmiConfig);
this.setClientId(null);

if (siweConfig?.options?.signOutOnDisconnect) {
await SIWEController.signOut();
}
},

sendTransaction: async (data: SendTransactionArgs) => {
@@ -322,9 +319,7 @@ export class AppKit extends AppKitScaffold {
getEnsAddress: async (value: string) => {
try {
if (!this.wagmiConfig) {
throw new Error(
'networkControllerClient:getApprovedCaipNetworksData - wagmiConfig is undefined'
);
throw new Error('WagmiAdapter:getEnsAddress - wagmiConfig is undefined');
}
const chainId = Number(NetworkUtil.caipNetworkIdToNumber(this.getCaipNetwork()?.id));
let ensName: boolean | GetEnsAddressReturnType = false;
@@ -380,9 +375,14 @@ export class AppKit extends AppKitScaffold {

watchAccount(wagmiConfig, {
onChange: (accountData, prevAccountData) => {
this.syncAccount({ ...accountData });
this.syncAccount({
...accountData,
isNetworkChange: accountData.chainId !== prevAccountData.chainId,
isAccountChange: accountData.address !== prevAccountData.address
});

if (accountData.status === 'disconnected' && prevAccountData.status === 'connected') {
this.onSiweDisconnect();
this.close();
}
}
@@ -431,23 +431,25 @@ export class AppKit extends AppKitScaffold {
chainId,
connector,
isConnecting,
isReconnecting
isReconnecting,
isNetworkChange,
isAccountChange
}: Pick<
GetAccountReturnType,
'address' | 'isConnected' | 'chainId' | 'connector' | 'isConnecting' | 'isReconnecting'
>) {
this.syncNetwork(address, chainId, isConnected);
> & { isNetworkChange?: boolean; isAccountChange?: boolean }) {
this.syncNetwork(address, chainId, isConnected, isNetworkChange, isAccountChange);
this.setLoading(!!connector && (isConnecting || isReconnecting));

if (isConnected && address && chainId) {
const caipAddress: CaipAddress = `${ConstantsUtil.EIP155}:${chainId}:${address}`;
this.setIsConnected(isConnected);
this.setCaipAddress(caipAddress);
this.resetTransactions();
await Promise.all([
this.syncProfile(address, chainId),
this.syncBalance(address, chainId),
this.syncConnectedWalletInfo(connector),
this.getApprovedCaipNetworksData()
this.syncConnectedWalletInfo(connector)
]);
this.hasSyncedConnectedAccount = true;
} else if (!isConnected && !isConnecting && !isReconnecting && this.hasSyncedConnectedAccount) {
@@ -457,22 +459,60 @@ export class AppKit extends AppKitScaffold {
}
}

private async syncNetwork(address?: Hex, chainId?: number, isConnected?: boolean) {
private async checkNetworkSupport(params: { chainId?: number; isConnected?: boolean }) {
// wait until the session is set
await new Promise(resolve => setTimeout(resolve, 500));

const { isConnected = false, chainId } = params;
const chain = this.wagmiConfig.chains.find((c: Chain) => c.id === chainId);

if (chain || chainId) {
const name = chain?.name ?? chainId?.toString();
const id = Number(chain?.id ?? chainId);
if (chain) {
const id = Number(chain.id);
const caipChainId: CaipNetworkId = `${ConstantsUtil.EIP155}:${id}`;
this.setCaipNetwork({
id: caipChainId,
name,
name: chain.name,
imageId: PresetsUtil.EIP155NetworkImageIds[id],
imageUrl: this.options?.chainImages?.[id]
});
} else if (params.chainId) {
this.setCaipNetwork({
id: `${ConstantsUtil.EIP155}:${params.chainId}`,
name: 'Unsupported Network'
});
}

if (isConnected) {
await this.getApprovedCaipNetworksData();
const isApproved = this.getApprovedCaipNetworks().some(
network => network.id === `${ConstantsUtil.EIP155}:${params.chainId}`
);

const isSupported = !!chain && isApproved;
this.openUnsupportedNetworkView(isSupported);

return isSupported;
}

return false;
}

private async syncNetwork(
address?: Hex,
chainId?: number,
isConnected?: boolean,
isNetworkChange?: boolean,
isAccountChange?: boolean
) {
const chain = this.wagmiConfig.chains.find((c: Chain) => c.id === chainId);
const isSiweEnabled = this.options?.siweConfig?.options?.enabled;

if (chain || chainId) {
const id = Number(chain?.id ?? chainId);
if (isConnected && address && chainId) {
const caipAddress: CaipAddress = `${ConstantsUtil.EIP155}:${id}:${address}`;
this.setCaipAddress(caipAddress);
this.resetTransactions();
if (chain?.blockExplorers?.default?.url) {
const url = `${chain.blockExplorers.default.url}/address/${address}`;
this.setAddressExplorerUrl(url);
@@ -485,6 +525,15 @@ export class AppKit extends AppKitScaffold {
}
}
}

let isSupported = true;
if (isNetworkChange) {
isSupported = await this.checkNetworkSupport({ chainId, isConnected });
}

if (isConnected && isSupported && isSiweEnabled) {
this.handleSiweChange({ isNetworkChange, isAccountChange });
}
}

private async syncProfile(address: Hex, chainId: number) {
@@ -641,4 +690,10 @@ export class AppKit extends AppKitScaffold {
this.setLoading(false);
});
}

private async onSiweDisconnect() {
if (this.options?.siweConfig?.options?.signOutOnDisconnect) {
await SIWEController.signOut();
}
}
}