Use SignRequest

This commit is contained in:
AdityaSalunkhe21 2025-04-09 20:16:55 +05:30
parent 2e84a41aaf
commit 20dee55dca
7 changed files with 192 additions and 78 deletions

View File

@ -40,6 +40,7 @@ import { WalletEmbed } from "./screens/WalletEmbed";
import { AutoSignIn } from "./screens/AutoSignIn"; import { AutoSignIn } from "./screens/AutoSignIn";
import { checkSufficientFunds, getPathKey, sendMessage } from "./utils/misc"; import { checkSufficientFunds, getPathKey, sendMessage } from "./utils/misc";
import useAccountsData from "./hooks/useAccountsData"; import useAccountsData from "./hooks/useAccountsData";
import { SignRequestAndroid } from "./components/SignRequestHandler";
const Stack = createStackNavigator<StackParamsList>(); const Stack = createStackNavigator<StackParamsList>();
@ -132,16 +133,28 @@ const App = (): React.JSX.Element => {
break; break;
case EIP155_SIGNING_METHODS.PERSONAL_SIGN: case EIP155_SIGNING_METHODS.PERSONAL_SIGN:
const chainId = request.params.chainId?.split(':')[1];
const account = accounts.find(acc => acc.address === request.params[1]);
if (!account) {
throw new Error('Account not found');
}
navigation.navigate("SignRequest", { navigation.navigate("SignRequest", {
namespace: EIP155, namespace: EIP155,
chainId,
address: request.params[1], address: request.params[1],
message: getSignParamsMessage(request.params), message: getSignParamsMessage(request.params),
accountInfo: account,
requestEvent, requestEvent,
requestSessionData, requestSessionData,
}); });
break; break;
case COSMOS_METHODS.COSMOS_SIGN_DIRECT: case COSMOS_METHODS.COSMOS_SIGN_DIRECT:
const cosmosChainId = request.params.chainId?.split(':')[1];
const cosmosAccount = accounts.find(acc => acc.address === request.params.signerAddress);
if (!cosmosAccount) {
throw new Error('Account not found');
}
const message = { const message = {
txbody: TxBody.toJSON( txbody: TxBody.toJSON(
TxBody.decode( TxBody.decode(
@ -160,18 +173,27 @@ const App = (): React.JSX.Element => {
}; };
navigation.navigate("SignRequest", { navigation.navigate("SignRequest", {
namespace: COSMOS, namespace: COSMOS,
chainId: cosmosChainId,
address: request.params.signerAddress, address: request.params.signerAddress,
message: JSON.stringify(message, undefined, 2), message: JSON.stringify(message, undefined, 2),
accountInfo: cosmosAccount,
requestEvent, requestEvent,
requestSessionData, requestSessionData,
}); });
break; break;
case COSMOS_METHODS.COSMOS_SIGN_AMINO: case COSMOS_METHODS.COSMOS_SIGN_AMINO:
const aminoChainId = request.params.chainId?.split(':')[1];
const aminoAccount = accounts.find(acc => acc.address === request.params.signerAddress);
if (!aminoAccount) {
throw new Error('Account not found');
}
navigation.navigate("SignRequest", { navigation.navigate("SignRequest", {
namespace: COSMOS, namespace: COSMOS,
chainId: aminoChainId,
address: request.params.signerAddress, address: request.params.signerAddress,
message: request.params.signDoc.memo, message: request.params.signDoc.memo,
accountInfo: aminoAccount,
requestEvent, requestEvent,
requestSessionData, requestSessionData,
}); });
@ -206,6 +228,7 @@ const App = (): React.JSX.Element => {
setCurrentIndex, setCurrentIndex,
selectedNetwork, selectedNetwork,
web3wallet, web3wallet,
accounts,
], ],
); );
@ -254,7 +277,7 @@ const App = (): React.JSX.Element => {
).privKey; ).privKey;
const sender = await DirectSecp256k1Wallet.fromKey( const sender = await DirectSecp256k1Wallet.fromKey(
Buffer.from(cosmosPrivKey.split('0x')[1], 'hex'), Uint8Array.from(Buffer.from(cosmosPrivKey.split('0x')[1], 'hex')),
network.addressPrefix network.addressPrefix
); );
@ -402,6 +425,7 @@ const App = (): React.JSX.Element => {
> >
Session approved Session approved
</Snackbar> </Snackbar>
<SignRequestAndroid />
</Surface> </Surface>
); );
}; };

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect, useRef } from 'react'; import React, { useState, useEffect, useRef, useCallback } from 'react';
import { useNavigation } from '@react-navigation/native'; import { useNavigation } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { useAccounts } from '../context/AccountsContext'; import { useAccounts } from '../context/AccountsContext';
@ -12,62 +12,158 @@ export const SignRequestAndroid: React.FC = () => {
const { selectedNetwork } = useNetworks(); const { selectedNetwork } = useNetworks();
const pendingMessageRef = useRef<string | null>(null); const pendingMessageRef = useRef<string | null>(null);
const [isDataReady, setIsDataReady] = useState(false);
// Run on mount // Run on mount
useGetOrCreateAccounts(); useGetOrCreateAccounts();
// Retry navigation if message is pending and accounts are ready // Check if data is ready
useEffect(() => { useEffect(() => {
if ( const logData = {
pendingMessageRef.current && selectedNetwork: selectedNetwork ? {
selectedNetwork && namespace: selectedNetwork.namespace,
accounts && chainId: selectedNetwork.chainId,
accounts.length > 0 } : 'undefined',
) { accounts: accounts ? `Array of length ${accounts.length}` : 'undefined',
const message = pendingMessageRef.current; currentIndex
pendingMessageRef.current = null; };
console.log('Checking data readiness:', JSON.stringify(logData, null, 2));
navigation.reset({ if (selectedNetwork && accounts && accounts.length > 0) {
index: 0, setIsDataReady(true);
routes: [
{
name: 'SignMessage',
params: {
selectedNamespace: selectedNetwork.namespace,
selectedChainId: selectedNetwork.chainId,
accountInfo: accounts[currentIndex],
prefillMessage: message,
},
},
],
});
} }
}, [accounts, selectedNetwork]); }, [selectedNetwork, accounts, currentIndex]);
useEffect(() => { // Handle navigation to SignRequest screen
window.receiveSignRequestFromAndroid = (message: string) => { const navigateToSignRequest = useCallback(async (message: string) => {
console.log('Sign request received with message:', message); const logData = {
isDataReady,
selectedNetwork: selectedNetwork ? {
namespace: selectedNetwork.namespace,
chainId: selectedNetwork.chainId,
} : 'undefined',
accounts: accounts ? `Array of length ${accounts.length}` : 'undefined',
currentIndex,
message
};
console.log('Attempting to navigate with data:', JSON.stringify(logData, null, 2));
if (!selectedNetwork || !accounts || accounts.length === 0) { if (!isDataReady) {
console.warn('Delaying sign request until data is ready...'); console.log('Delaying sign request until data is ready...');
pendingMessageRef.current = message; pendingMessageRef.current = message;
return; return;
} }
if (!selectedNetwork) {
console.error('No network selected');
if (window.Android?.onSignatureError) {
window.Android.onSignatureError('No network selected');
}
return;
}
if (!accounts || accounts.length === 0) {
console.error('No accounts available');
if (window.Android?.onSignatureError) {
window.Android.onSignatureError('No accounts available');
}
return;
}
const currentAccount = accounts[currentIndex];
if (!currentAccount) {
console.error('Current account not found');
if (window.Android?.onSignatureError) {
window.Android.onSignatureError('Current account not found');
}
return;
}
// Verify network properties exist before using them
if (!selectedNetwork.namespace || !selectedNetwork.chainId) {
const errorData = {
namespace: selectedNetwork.namespace,
chainId: selectedNetwork.chainId
};
console.error('Network missing required properties:', JSON.stringify(errorData, null, 2));
if (window.Android?.onSignatureError) {
window.Android.onSignatureError('Network missing required properties');
}
return;
}
const navigationData = {
namespace: selectedNetwork.namespace,
chainId: selectedNetwork.chainId,
address: currentAccount.address,
message
};
console.log('Navigating to SignRequest with:', JSON.stringify(navigationData, null, 2));
try {
// Ensure the path matches the expected regex pattern: /sign/(eip155|cosmos)/(.+)/(.+)/(.+)
const path = `/sign/${selectedNetwork.namespace}/${selectedNetwork.chainId}/${currentAccount.address}/${encodeURIComponent(message)}`;
// Verify the path matches the expected pattern
const pathRegex = /^\/sign\/(eip155|cosmos)\/(.+)\/(.+)\/(.+)$/;
if (!pathRegex.test(path)) {
console.error('Path does not match expected pattern:', path);
if (window.Android?.onSignatureError) {
window.Android.onSignatureError('Invalid path format');
}
return;
}
// Parse the path to get the components
const match = path.match(pathRegex);
if (!match) {
console.error('Failed to parse path:', path);
if (window.Android?.onSignatureError) {
window.Android.onSignatureError('Failed to parse path');
}
return;
}
const [, pathNamespace, pathChainId, pathAddress, pathMessage] = match;
navigation.reset({ navigation.reset({
index: 0, index: 0,
routes: [ routes: [
{ {
name: 'SignMessage', name: 'SignRequest',
path: `/sign/${selectedNetwork.namespace}/${selectedNetwork.chainId}/${currentAccount.address}/${encodeURIComponent(message)}`,
params: { params: {
selectedNamespace: selectedNetwork.namespace, namespace: pathNamespace,
selectedChainId: selectedNetwork.chainId, chainId: pathChainId,
accountInfo: accounts[currentIndex], address: pathAddress,
prefillMessage: message, message: decodeURIComponent(pathMessage),
accountInfo: currentAccount,
}, },
}, },
], ],
}); });
} catch (error) {
console.error('Navigation error:', error);
if (window.Android?.onSignatureError) {
window.Android.onSignatureError(`Navigation error: ${error}`);
}
}
}, [isDataReady, selectedNetwork, accounts, currentIndex, navigation]);
// Handle pending message when data becomes ready
useEffect(() => {
if (pendingMessageRef.current && isDataReady) {
const message = pendingMessageRef.current;
pendingMessageRef.current = null;
navigateToSignRequest(message);
}
}, [isDataReady, navigateToSignRequest]);
// Setup Android bridge and message handler
useEffect(() => {
window.receiveSignRequestFromAndroid = (message: string) => {
console.log('Sign request received with message:', message);
navigateToSignRequest(message);
}; };
if (window.Android) { if (window.Android) {
@ -80,29 +176,7 @@ export const SignRequestAndroid: React.FC = () => {
return () => { return () => {
window.receiveSignRequestFromAndroid = undefined; window.receiveSignRequestFromAndroid = undefined;
}; };
}, []); }, [navigateToSignRequest]);
return null; return null;
}; };

4
src/global.d.ts vendored
View File

@ -6,9 +6,13 @@ declare global {
onSignatureCancelled?: () => void; onSignatureCancelled?: () => void;
onJsBridgeReady?: () => void; onJsBridgeReady?: () => void;
}; };
// Android WebView bridge methods (from TimeDisplay)
receiveTimeFromAndroid?: (timestamp: number) => void; receiveTimeFromAndroid?: (timestamp: number) => void;
receiveDataFromAndroid?: (data: string) => void; receiveDataFromAndroid?: (data: string) => void;
receiveSignRequestFromAndroid?: (message: string) => void; receiveSignRequestFromAndroid?: (message: string) => void;
// Android native bridge object (from useGetOrCreateAccount)
} }
} }

View File

@ -20,7 +20,6 @@ const useGetOrCreateAccounts = (onWalletCreated?: () => void) => {
// Re-fetch newly created accounts // Re-fetch newly created accounts
accountsData = await getAccountsData(event.data.chainId); accountsData = await getAccountsData(event.data.chainId);
onWalletCreated?.(); onWalletCreated?.();
} }
@ -41,7 +40,6 @@ const useGetOrCreateAccounts = (onWalletCreated?: () => void) => {
console.log("Auto-creating wallet..."); console.log("Auto-creating wallet...");
await createWallet(networksData); await createWallet(networksData);
accountsData = await getAccountsData(defaultChainId); accountsData = await getAccountsData(defaultChainId);
onWalletCreated?.(); onWalletCreated?.();
} }
}; };
@ -49,7 +47,6 @@ const useGetOrCreateAccounts = (onWalletCreated?: () => void) => {
window.addEventListener('message', handleCreateAccounts); window.addEventListener('message', handleCreateAccounts);
const isAndroidWebView = !!(window.Android); const isAndroidWebView = !!(window.Android);
if (isAndroidWebView) { if (isAndroidWebView) {
autoCreateAccounts(); autoCreateAccounts();
} }

View File

@ -23,8 +23,10 @@ const SignMessage = ({ route, navigation }: SignProps) => {
useEffect(() => { useEffect(() => {
if (prefillMessage) { if (prefillMessage) {
setMessage(prefillMessage); setMessage(prefillMessage);
// Show confirmation dialog automatically when prefilled from Android // Show confirmation dialog immediately
setTimeout(() => {
setShowConfirmDialog(true); setShowConfirmDialog(true);
}, 0);
} }
}, [prefillMessage]); }, [prefillMessage]);

View File

@ -202,7 +202,13 @@ const SignRequest = ({ route }: SignRequestProps) => {
chainId, chainId,
accountId: account.index, accountId: account.index,
}); });
alert(`Signature ${signedMessage}`);
// Send the result back to Android and close dialog
if (window.Android?.onSignatureComplete) {
window.Android.onSignatureComplete(signedMessage || "");
} else {
alert(`Signature: ${signedMessage}`);
}
} }
}; };
@ -230,7 +236,11 @@ const SignRequest = ({ route }: SignRequestProps) => {
} }
setIsRejecting(false); setIsRejecting(false);
if (window.Android?.onSignatureCancelled) {
window.Android.onSignatureCancelled();
} else {
navigation.navigate('Home'); navigation.navigate('Home');
}
}; };
useEffect(() => { useEffect(() => {

View File

@ -13,9 +13,12 @@ export type StackParamsList = {
prefillMessage?: string; prefillMessage?: string;
}; };
SignRequest: { SignRequest: {
path?: string;
namespace: string; namespace: string;
chainId?: string;
address: string; address: string;
message: string; message: string;
accountInfo?: Account;
requestEvent?: Web3WalletTypes.SessionRequest; requestEvent?: Web3WalletTypes.SessionRequest;
requestSessionData?: SessionTypes.Struct; requestSessionData?: SessionTypes.Struct;
}; };