Compare commits

..
14 Commits
6 changed files with 100 additions and 82 deletions
-1
View File
@@ -1,6 +1,5 @@
REACT_APP_WALLET_CONNECT_PROJECT_ID=
REACT_APP_DEFAULT_GAS_PRICE=0.025
# Reference: https://github.com/cosmos/cosmos-sdk/issues/16020
REACT_APP_GAS_ADJUSTMENT=2
REACT_APP_LACONICD_RPC_URL=https://laconicd-sapo.laconic.com
+9
View File
@@ -23,6 +23,12 @@ declare global {
// Called when transfer is cancelled
onTransferCancelled?: () => void;
// Called when account is created
onAccountCreated?: (account: string) => void;
// Called when account creation fails
onAccountError?: (error: string) => void;
};
// Handles incoming signature requests from Android
@@ -30,6 +36,9 @@ declare global {
// Handles incoming transfer requests from Android
receiveTransferRequestFromAndroid?: (to: string, amount: string, namespace: String, chainId: string, memo: string) => void;
// Handles account creation requests from Android
receiveGetOrCreateAccountFromAndroid?: (namespace: string, chainId: string) => void;
}
}
+2 -23
View File
@@ -58,35 +58,14 @@ const useGetOrCreateAccounts = () => {
);
};
const autoCreateAccounts = async () => {
const defaultChainId = networksData[0]?.chainId;
if (!defaultChainId) {
console.log('useGetOrCreateAccounts: No default chainId found');
return;
}
const accounts = await getOrCreateAccountsForChain(defaultChainId);
// Only notify Android when we actually have accounts
if (accounts.length > 0 && window.Android?.onAccountsReady) {
window.Android.onAccountsReady();
} else {
console.log('No accounts created or Android bridge not available');
}
};
window.addEventListener('message', handleCreateAccounts);
const isAndroidWebView = !!(window.Android);
if (isAndroidWebView) {
autoCreateAccounts();
}
return () => {
window.removeEventListener('message', handleCreateAccounts);
};
}, [networksData, getAccountsData, getOrCreateAccountsForChain]);
return { getOrCreateAccountsForChain };
};
export default useGetOrCreateAccounts;
+36 -23
View File
@@ -4,6 +4,8 @@ import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { useAccounts } from '../context/AccountsContext';
import { useNetworks } from '../context/NetworksContext';
import useAccountsData from "../hooks/useAccountsData";
import { StackParamsList } from '../types';
import useGetOrCreateAccounts from './useGetOrCreateAccounts';
import { retrieveAccountsForNetwork } from '../utils/accounts';
@@ -13,9 +15,27 @@ export const useWebViewHandler = () => {
const navigation = useNavigation<NativeStackNavigationProp<StackParamsList>>();
const { selectedNetwork } = useNetworks();
const { accounts, currentIndex } = useAccounts();
const { getAccountsData } = useAccountsData();
const { getOrCreateAccountsForChain } = useGetOrCreateAccounts();
// Initialize accounts
useGetOrCreateAccounts();
const handleGetOrCreateAccount = useCallback(async (chainId: string) => {
try {
const accountsData = await getOrCreateAccountsForChain(chainId);
if (!accountsData || accountsData.length === 0) {
window.Android?.onAccountError?.('Failed to create/retrieve account');
return;
}
window.Android?.onAccountCreated?.(JSON.stringify(accountsData[0]));
} catch (error) {
console.error('Account operation error:', error);
window.Android?.onAccountError?.(`Operation failed: ${error}`);
}
}, [getOrCreateAccountsForChain, getAccountsData]);
// Core navigation handler
const navigateToSignRequest = useCallback((message: string) => {
@@ -76,23 +96,16 @@ export const useWebViewHandler = () => {
// Handle incoming transfer requests
const navigateToTransfer = useCallback(async (to: string, amount: string, namespace: String, chainId: string, memo: string) => {
const chainSpecs = {
namespace,
chainId,
};
try {
const unymAmount = chainSpecs.chainId === 'nyx'
? Math.round(parseFloat(amount) * 1_000_000).toString()
: amount;
// Get all accounts
const chainAccounts = await retrieveAccountsForNetwork(
`${chainSpecs.namespace}:${chainSpecs.chainId}`,
'0' // Use the first account
try {
// TODO: Pass the account info for transferring tokens
// Get first account
const [chainAccount] = await retrieveAccountsForNetwork(
`${namespace}:${chainId}`,
'0'
);
if (!chainAccounts || chainAccounts.length === 0) {
if (!chainAccount) {
console.error('Accounts not found');
if (window.Android?.onTransferError) {
window.Android.onTransferError('Accounts not found');
@@ -100,9 +113,8 @@ export const useWebViewHandler = () => {
return;
}
const chainAccount = chainAccounts[0]; // Use the first account
const path = `/transfer/${chainSpecs.namespace}/${chainSpecs.chainId}/${chainAccount.address}/${to}/${unymAmount}`;
const path = `/transfer/${namespace}/${chainId}/${chainAccount.address}/${to}/${amount}`;
navigation.reset({
index: 0,
@@ -111,13 +123,12 @@ export const useWebViewHandler = () => {
name: 'ApproveTransfer',
path: path,
params: {
namespace: chainSpecs.namespace,
chainId: `${chainSpecs.namespace}:${chainSpecs.chainId}`,
namespace: namespace,
chainId: `${namespace}:${chainId}`,
transaction: {
from: chainAccount.address,
to: to,
value: unymAmount,
data: ''
value: amount
},
accountInfo: chainAccount,
memo: memo
@@ -126,7 +137,6 @@ export const useWebViewHandler = () => {
],
});
} catch (error) {
console.error('Navigation error:', error);
if (window.Android?.onTransferError) {
window.Android.onTransferError(`Navigation error: ${error}`);
}
@@ -139,9 +149,12 @@ export const useWebViewHandler = () => {
window.receiveTransferRequestFromAndroid = navigateToTransfer;
window.receiveGetOrCreateAccountFromAndroid = handleGetOrCreateAccount;
return () => {
window.receiveSignRequestFromAndroid = undefined;
window.receiveTransferRequestFromAndroid = undefined;
window.receiveGetOrCreateAccountFromAndroid = undefined;
};
}, [navigateToSignRequest, navigateToTransfer]); // Only the function reference as dependency
}, [navigateToSignRequest, navigateToTransfer, handleGetOrCreateAccount]); // Only the function reference as dependency
};
+49 -35
View File
@@ -2,11 +2,11 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { Image, ScrollView, View } from 'react-native';
import {
ActivityIndicator,
Button,
Text,
Appbar,
TextInput,
} from 'react-native-paper';
import JSONbig from 'json-bigint';
import { providers, BigNumber } from 'ethers';
import { Deferrable } from 'ethers/lib/utils';
@@ -41,6 +41,8 @@ import { COSMOS, EIP155, IS_NUMBER_REGEX } from '../utils/constants';
import TxErrorDialog from '../components/TxErrorDialog';
import { EIP155_SIGNING_METHODS } from '../utils/wallet-connect/EIP155Data';
import { COSMOS_METHODS } from '../utils/wallet-connect/COSMOSData';
import { Button } from '@mui/material';
import { LoadingButton } from '@mui/lab';
export const MEMO = 'Sending signed tx from Laconic Wallet';
// Reference: https://ethereum.org/en/developers/docs/gas/#what-is-gas-limit
@@ -55,14 +57,13 @@ const ApproveTransfer = ({ route }: ApproveTransferProps) => {
// Extract data from route params or path
const requestSession = route.params.requestSessionData;
const requestName = requestSession?.peer.metadata.name;
const requestIcon = requestSession?.peer.metadata.icons?.[0];
const requestIcon = requestSession?.peer.metadata.icons[0];
const requestURL = requestSession?.peer.metadata.url;
const transaction = route.params.transaction;
const requestEvent = route.params.requestEvent;
const chainId = requestEvent?.params.chainId || route.params.chainId;
const requestMethod = requestEvent?.params.request.method;
const customMemo = route.params.memo;
const finalMemo = customMemo || MEMO;
const txMemo = route.params.memo || MEMO;
const [account, setAccount] = useState<Account>();
const [isLoading, setIsLoading] = useState(true);
@@ -205,6 +206,7 @@ const ApproveTransfer = ({ route }: ApproveTransferProps) => {
[navigation, requestedNetwork],
);
//TODO: Handle ETH transactions
const handleIntent = async () => {
if (!account) {
throw new Error('Account is not valid');
@@ -214,8 +216,8 @@ const ApproveTransfer = ({ route }: ApproveTransferProps) => {
from: account.address,
to: transaction.to,
amount: transaction.value,
denom: requestedNetwork?.nativeDenom,
memo: finalMemo,
denom: requestedNetwork!.nativeDenom,
memo: txMemo,
gas: cosmosGasLimit,
fees: fees
});
@@ -240,15 +242,13 @@ const ApproveTransfer = ({ route }: ApproveTransferProps) => {
],
gas: cosmosGasLimit,
},
finalMemo,
txMemo,
);
console.log('Transaction result:', result);
// Convert BigInt values to strings before sending to Android
const serializedResult = JSON.stringify(result, (key, value) =>
typeof value === 'bigint' ? value.toString() : value
);
const serializedResult = JSONbig.stringify(result);
// Send the result back to Android and close dialog
if (window.Android?.onTransferComplete) {
@@ -372,7 +372,7 @@ const ApproveTransfer = ({ route }: ApproveTransferProps) => {
gas: cosmosGasLimit,
},
sendMsg,
memo: MEMO,
memo: txMemo,
};
break;
default:
@@ -392,9 +392,11 @@ const ApproveTransfer = ({ route }: ApproveTransferProps) => {
navigation.navigate('Home');
} else {
await handleIntent();
navigation.navigate('Home');
}
} catch (error) {
if (window.Android?.onTransferError) {
window.Android.onTransferError(`Transaction Failed: ${error}`);
}
if (!(error instanceof Error)) {
throw error;
}
@@ -406,20 +408,31 @@ const ApproveTransfer = ({ route }: ApproveTransferProps) => {
};
const rejectRequestHandler = async () => {
if (requestEvent) {
const response = rejectWalletConnectRequest(requestEvent);
const { topic } = requestEvent;
await web3wallet!.respondSessionRequest({
topic,
response,
});
}
setIsTxLoading(true);
try {
if (requestEvent) {
const response = rejectWalletConnectRequest(requestEvent);
const { topic } = requestEvent;
await web3wallet!.respondSessionRequest({
topic,
response,
});
}
if (window.Android?.onTransferCancelled) {
window.Android.onTransferCancelled();
} else {
navigation.navigate('Home');
if (window.Android?.onTransferCancelled) {
window.Android.onTransferCancelled();
} else {
navigation.navigate('Home');
}
} catch (error) {
if (!(error instanceof Error)) {
throw error;
}
setTxError(error.message);
setIsTxErrorDialogOpen(true);
}
setIsTxLoading(false);
};
useEffect(() => {
@@ -534,7 +547,7 @@ const ApproveTransfer = ({ route }: ApproveTransferProps) => {
const gasEstimation = await cosmosStargateClient.simulate(
transaction.from!,
[sendMsg],
finalMemo,
txMemo,
);
setCosmosGasLimit(
@@ -552,7 +565,7 @@ const ApproveTransfer = ({ route }: ApproveTransferProps) => {
}
};
getCosmosGas();
}, [cosmosStargateClient, isSufficientFunds, sendMsg, transaction,finalMemo]);
}, [cosmosStargateClient, isSufficientFunds, sendMsg, transaction,txMemo]);
useEffect(() => {
if (balance && !isSufficientFunds) {
@@ -612,7 +625,7 @@ const ApproveTransfer = ({ route }: ApproveTransferProps) => {
{namespace === COSMOS && (
<DataBox
label="Memo"
data={finalMemo}
data={txMemo}
/>
)}
@@ -708,17 +721,18 @@ const ApproveTransfer = ({ route }: ApproveTransferProps) => {
)}
</ScrollView>
<View style={styles.buttonContainer}>
<Button
mode="contained"
onPress={acceptRequestHandler}
<LoadingButton
variant="contained"
onClick={acceptRequestHandler}
loading={isTxLoading}
disabled={!balance || !fees}>
disabled={!balance || !fees}
id="approve-transaction-button">
{isTxLoading ? 'Processing' : 'Yes'}
</Button>
</LoadingButton>
<Button
mode="contained"
onPress={rejectRequestHandler}
buttonColor="#B82B0D">
variant="contained"
onClick={rejectRequestHandler}
color="error">
No
</Button>
</View>
+4
View File
@@ -52,6 +52,8 @@ export const DEFAULT_NETWORKS: NetworksFormData[] = [
gasPrice: '0.025',
isDefault: true,
},
//TODO: Add network from android app
{
chainId: 'nyx',
networkName: 'Nym',
@@ -61,6 +63,8 @@ export const DEFAULT_NETWORKS: NetworksFormData[] = [
nativeDenom: 'unym',
addressPrefix: 'n',
coinType: '118',
// Ref: https://nym.com/docs/operators/nodes/validator-setup#apptoml-configuration
gasPrice: '0.025',
isDefault: true,
},