Compare commits
14
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
710593fc30 | ||
|
|
fc90fa49bb | ||
|
|
1124805bb7 | ||
|
|
053d7f5618 | ||
|
|
b06c56c41c | ||
|
|
acf0d1fc17 | ||
|
|
08af9ba20d | ||
|
|
43e61b7603 | ||
|
|
79eda18185 | ||
|
|
6515a22abc | ||
|
|
17669b834d | ||
|
|
49cada4fc8 | ||
|
|
f2e5ba0a56 | ||
|
|
b1cc35d4e0 |
@@ -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
|
||||
|
||||
Vendored
+9
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user