Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
17 changes: 17 additions & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ module.exports = {
},
],
"import/no-named-as-default": "error",
"import/namespace": [
"error",
{
allowComputed: true,
},
],
"prefer-const": "error",
"no-constant-condition": "error",
"no-empty": "error",
Expand Down Expand Up @@ -98,5 +104,16 @@ module.exports = {
"dist/**/*", // Ignore build output
"node_modules/**/*", // Ignore node_modules
],
overrides: [
{
files: ["**/react-native/**/*.{ts,tsx}"],
rules: {
// React Native is a CommonJS package that doesn't play well with ESLint's namespace checking
// The package structure confuses the parser when it tries to validate imports
// This is a known limitation when using TypeScript with React Native
"import/namespace": "off",
},
},
],
root: true,
};
14 changes: 14 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@
"type-check": "tsc --noEmit"
},
"dependencies": {
"@dynamic-labs/client": "4.29.4",
"@dynamic-labs/ethereum": "4.29.4",
"@dynamic-labs/ethers-v6": "4.29.4",
"@dynamic-labs/global-wallet-client": "4.15.0",
"@dynamic-labs/react-native-extension": "4.29.4",
"@dynamic-labs/sdk-react-core": "4.29.4",
"@dynamic-labs/solana": "4.29.4",
"@react-native-async-storage/async-storage": "^2.2.0",
Expand Down Expand Up @@ -65,6 +67,7 @@
"@wallet-standard/wallet": "^1.1.0",
"react": "^18.0.0 || ^19.0.0",
"react-dom": "^18.0.0 || ^19.0.0",
"react-native": "^0.72.0 || ^0.73.0 || ^0.74.0",
"viem": "^2.7.12"
},
"peerDependenciesMeta": {
Expand All @@ -91,13 +94,19 @@
},
"react-dom": {
"optional": false
},
"react-native": {
"optional": true
}
},
"typesVersions": {
"*": {
"react": [
"./dist/types/react/index.d.ts"
],
"react-native": [
"./dist/types/react-native/index.d.ts"
],
"ethereum": [
"./dist/types/ethereum.d.ts"
],
Expand All @@ -123,6 +132,11 @@
"import": "./dist/esm/react/index.js",
"default": "./dist/cjs/react/index.js"
},
"./react-native": {
"types": "./dist/types/react-native/index.d.ts",
"import": "./dist/esm/react-native/index.js",
"default": "./dist/cjs/react-native/index.js"
},
"./ethereum": {
"types": "./dist/types/ethereum.d.ts",
"import": "./dist/esm/ethereum.js",
Expand Down
3 changes: 3 additions & 0 deletions src/react-native/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { useAutoNetworkSwitchOnConnection } from "./useAutoNetworkSwitchOnConnection";
export { useConnectUniversalSignIn } from "./useConnectUniversalSignIn";
export { useUniversalSignInContext } from "./useUniversalSignInContext";
54 changes: 54 additions & 0 deletions src/react-native/hooks/useAutoNetworkSwitchOnConnection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { useCallback, useEffect, useRef } from "react";

import { REQUIRED_NETWORKS_CHAIN_IDS } from "../../constants";
import type { PrimaryWallet } from "../../types";
import { useUniversalSignInContextValue } from "../providers/UniversalSignInContextProvider";
import { isDynamicClient } from "../types";

/**
* Custom hook that automatically switches the network to a specified chain
* when a wallet is first connected (not when switching between existing wallets).
*
* @param chainId - The chain ID to switch to (defaults to ZetaChain mainnet)
*/
export const useAutoNetworkSwitchOnConnection = (
chainId: number = REQUIRED_NETWORKS_CHAIN_IDS.ZETACHAIN_MAINNET
) => {
const { dynamicClient } = useUniversalSignInContextValue();

if (!isDynamicClient(dynamicClient)) {
return;
}

const previousPrimaryWalletRef = useRef<PrimaryWallet | null>(null);

const handlePrimaryWalletChanged = useCallback(
(newPrimaryWallet: PrimaryWallet) => {
const previousWallet = previousPrimaryWalletRef.current;

// Only trigger network switch on initial connection (null -> wallet)
// Skip when switching between existing wallets (wallet A -> wallet B)
if (newPrimaryWallet && !previousWallet) {
if (newPrimaryWallet.connector?.supportsNetworkSwitching()) {
void newPrimaryWallet.switchNetwork(chainId);
}
}

// Update the ref to track the current wallet for next comparison
previousPrimaryWalletRef.current = newPrimaryWallet;
},
[chainId]
);

useEffect(() => {
const callback = (newWallet: unknown) => {
handlePrimaryWalletChanged(newWallet as PrimaryWallet);
};

dynamicClient.on("primaryWalletChanged", callback);

return () => {
dynamicClient.off("primaryWalletChanged", callback);
};
}, [dynamicClient, handlePrimaryWalletChanged]);
};
18 changes: 18 additions & 0 deletions src/react-native/hooks/useConnectUniversalSignIn.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { GLOBAL_WALLET_KEY_ID } from "../../constants";
import { useUniversalSignInContextValue } from "../providers/UniversalSignInContextProvider";
import { isDynamicClient } from "../types";

/**
* Hook that provides a handler to connect to Universal Sign-In EVM
*/
export const useConnectUniversalSignIn = () => {
const { dynamicClient } = useUniversalSignInContextValue();

const connectUniversalSignIn = (): void => {
if (isDynamicClient(dynamicClient)) {
void dynamicClient.selectWalletOption(GLOBAL_WALLET_KEY_ID);
}
};

return { connectUniversalSignIn };
};
68 changes: 68 additions & 0 deletions src/react-native/hooks/useUniversalSignInContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { useEffect, useState } from "react";

import type { PrimaryWallet } from "../../types";
import { useUniversalSignInContextValue } from "../providers/UniversalSignInContextProvider";
import { isDynamicClient } from "../types";

/**
* Hook that provides access to the Universal Sign-In context.
* This mirrors the React implementation but uses the client-based approach.
*/
interface DynamicUser {
[key: string]: unknown;
email?: string;
}

export const useUniversalSignInContext = () => {
const { dynamicClient } = useUniversalSignInContextValue();

if (!isDynamicClient(dynamicClient)) {
throw new Error("Dynamic client is not properly initialized");
}

const initialUser: DynamicUser | null =
(dynamicClient.user as DynamicUser | null) || null;

const initialPrimaryWallet: PrimaryWallet | null =
dynamicClient.primaryWallet;

const initialIsAuthenticated: boolean = dynamicClient.isAuthenticated;

const [user, setUser] = useState<DynamicUser | null>(initialUser);
const [primaryWallet, setPrimaryWallet] = useState<PrimaryWallet | null>(
initialPrimaryWallet
);
const [isAuthenticated, setIsAuthenticated] = useState<boolean>(
initialIsAuthenticated
);

useEffect(() => {
const unsubscribeUser = dynamicClient.on("user", (newUser: unknown) => {
setUser((newUser as DynamicUser | null) || null);
});

const unsubscribePrimaryWallet = dynamicClient.on(
"primaryWalletChanged",
(newWallet: unknown) => {
setPrimaryWallet((newWallet as PrimaryWallet | null) || null);
}
);

const unsubscribeAuth = dynamicClient.on("authFlowComplete", () => {
setIsAuthenticated(dynamicClient.isAuthenticated);
});

return () => {
unsubscribeUser();
unsubscribePrimaryWallet();
unsubscribeAuth();
};
}, [dynamicClient]);

return {
dynamicClient,
isAuthenticated,
primaryWallet,
user,
};
};
13 changes: 13 additions & 0 deletions src/react-native/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Export providers
export {
UniversalSignInContextProvider,
useUniversalSignInContextValue,
} from "./providers";

// Export hooks
export { useAutoNetworkSwitchOnConnection } from "./hooks/useAutoNetworkSwitchOnConnection";
export { useConnectUniversalSignIn } from "./hooks/useConnectUniversalSignIn";
export { useUniversalSignInContext } from "./hooks/useUniversalSignInContext";

// Export types that React Native users might need
export { type DynamicEnvironment } from "../constants";
Loading
Loading