Compare commits
No commits in common. "main" and "release-v0.1.0" have entirely different histories.
main
...
release-v0
@ -2,4 +2,3 @@ WALLET_CONNECT_PROJECT_ID=
|
|||||||
DEFAULT_GAS_PRICE=0.025
|
DEFAULT_GAS_PRICE=0.025
|
||||||
# Reference: https://github.com/cosmos/cosmos-sdk/issues/16020
|
# Reference: https://github.com/cosmos/cosmos-sdk/issues/16020
|
||||||
DEFAULT_GAS_ADJUSTMENT=2
|
DEFAULT_GAS_ADJUSTMENT=2
|
||||||
LACONICD_RPC_URL=https://laconicd.laconic.com
|
|
||||||
|
15
README.md
15
README.md
@ -25,7 +25,7 @@
|
|||||||
|
|
||||||
- Install Android SDK
|
- Install Android SDK
|
||||||
|
|
||||||
- Open Android Studio -> Configure -> SDK Manager -> SDK Platform Tab.
|
- Open Android Studio -> Configure -> SDK Manager -> SDK PLatfrom Tab.
|
||||||
|
|
||||||
- Check the box next to "Show Package Details" in the bottom right corner. Look for and expand the Android 13 (Tiramisu) entry, then make sure the following items are checked:
|
- Check the box next to "Show Package Details" in the bottom right corner. Look for and expand the Android 13 (Tiramisu) entry, then make sure the following items are checked:
|
||||||
|
|
||||||
@ -94,26 +94,19 @@
|
|||||||
WALLET_CONNECT_PROJECT_ID=39bc93c...
|
WALLET_CONNECT_PROJECT_ID=39bc93c...
|
||||||
```
|
```
|
||||||
|
|
||||||
5. Add SDK directory to project
|
5. Set up the Android device
|
||||||
- Inside the [`android`](./android/) directory, create a file `local.properties` and add your Android SDK path
|
|
||||||
```
|
|
||||||
sdk.dir = /home/USERNAME/Android/Sdk
|
|
||||||
```
|
|
||||||
Where `USERNAME` is your linux username
|
|
||||||
|
|
||||||
6. Set up the Android device
|
|
||||||
|
|
||||||
- For a physical device, refer to the [React Native documentation for running on a physical device](https://reactnative.dev/docs/running-on-device)
|
- For a physical device, refer to the [React Native documentation for running on a physical device](https://reactnative.dev/docs/running-on-device)
|
||||||
|
|
||||||
- For a virtual device, continue with the steps
|
- For a virtual device, continue with the steps
|
||||||
|
|
||||||
7. Start the application
|
6. Start the application
|
||||||
|
|
||||||
```
|
```
|
||||||
yarn start
|
yarn start
|
||||||
```
|
```
|
||||||
|
|
||||||
8. Press `a` to run the application on android
|
7. Press `a` to run the application on android
|
||||||
|
|
||||||
## Flow for the app
|
## Flow for the app
|
||||||
|
|
||||||
|
@ -10,9 +10,6 @@ module.exports = {
|
|||||||
crypto: 'react-native-quick-crypto',
|
crypto: 'react-native-quick-crypto',
|
||||||
stream: 'stream-browserify',
|
stream: 'stream-browserify',
|
||||||
buffer: '@craftzdog/react-native-buffer',
|
buffer: '@craftzdog/react-native-buffer',
|
||||||
http: 'http-browserify',
|
|
||||||
https: 'https-browserify',
|
|
||||||
url: 'react-native-url-polyfill',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
- Function for creating web3 wallet
|
- Function for creating web3 wallet
|
||||||
|
|
||||||
```js
|
```js
|
||||||
|
export let web3wallet: IWeb3Wallet;
|
||||||
export let core: ICore;
|
export let core: ICore;
|
||||||
|
|
||||||
export async function createWeb3Wallet() {
|
export async function createWeb3Wallet() {
|
||||||
@ -25,27 +26,23 @@
|
|||||||
icons: ['https://avatars.githubusercontent.com/u/92608123'],
|
icons: ['https://avatars.githubusercontent.com/u/92608123'],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return web3wallet;
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- Hook used for intializing web3 wallet
|
- Hook used for intializing web3 wallet
|
||||||
|
|
||||||
```js
|
```js
|
||||||
export default function useInitialization(setWeb3wallet) {
|
export default function useInitialization() {
|
||||||
const [initialized, setInitialized] = useState(false);
|
const [initialized, setInitialized] = useState(false);
|
||||||
|
|
||||||
const onInitialize = useCallback(async () => {
|
const onInitialize = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
const web3walletInstance = await createWeb3Wallet();
|
await createWeb3Wallet();
|
||||||
setWeb3wallet(web3walletInstance);
|
|
||||||
|
|
||||||
setInitialized(true);
|
setInitialized(true);
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
console.error('Error for initializing', err);
|
console.log('Error for initializing', err);
|
||||||
}
|
}
|
||||||
}, [setWeb3wallet]);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!initialized) {
|
if (!initialized) {
|
||||||
|
@ -12,7 +12,6 @@
|
|||||||
"prepare": "husky"
|
"prepare": "husky"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cerc-io/registry-sdk": "^0.2.5",
|
|
||||||
"@cosmjs/amino": "^0.32.3",
|
"@cosmjs/amino": "^0.32.3",
|
||||||
"@cosmjs/crypto": "^0.32.3",
|
"@cosmjs/crypto": "^0.32.3",
|
||||||
"@cosmjs/proto-signing": "^0.32.3",
|
"@cosmjs/proto-signing": "^0.32.3",
|
||||||
@ -21,7 +20,6 @@
|
|||||||
"@hookform/resolvers": "^3.3.4",
|
"@hookform/resolvers": "^3.3.4",
|
||||||
"@json-rpc-tools/utils": "^1.7.6",
|
"@json-rpc-tools/utils": "^1.7.6",
|
||||||
"@react-native-async-storage/async-storage": "^1.22.3",
|
"@react-native-async-storage/async-storage": "^1.22.3",
|
||||||
"@react-native-clipboard/clipboard": "^1.14.1",
|
|
||||||
"@react-native-community/netinfo": "^11.3.1",
|
"@react-native-community/netinfo": "^11.3.1",
|
||||||
"@react-navigation/elements": "^1.3.30",
|
"@react-navigation/elements": "^1.3.30",
|
||||||
"@react-navigation/native": "^6.1.10",
|
"@react-navigation/native": "^6.1.10",
|
||||||
@ -30,12 +28,9 @@
|
|||||||
"@walletconnect/react-native-compat": "^2.11.2",
|
"@walletconnect/react-native-compat": "^2.11.2",
|
||||||
"@walletconnect/utils": "^2.12.2",
|
"@walletconnect/utils": "^2.12.2",
|
||||||
"@walletconnect/web3wallet": "^1.10.2",
|
"@walletconnect/web3wallet": "^1.10.2",
|
||||||
"assert": "^2.1.0",
|
|
||||||
"chain-registry": "^1.41.2",
|
"chain-registry": "^1.41.2",
|
||||||
"cosmjs-types": "^0.9.0",
|
"cosmjs-types": "^0.9.0",
|
||||||
"ethers": "5.7.2",
|
"ethers": "5.7.2",
|
||||||
"http-browserify": "^1.7.0",
|
|
||||||
"https-browserify": "^1.0.0",
|
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"patch-package": "^8.0.0",
|
"patch-package": "^8.0.0",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
@ -50,7 +45,6 @@
|
|||||||
"react-native-safe-area-context": "^4.9.0",
|
"react-native-safe-area-context": "^4.9.0",
|
||||||
"react-native-screens": "^3.29.0",
|
"react-native-screens": "^3.29.0",
|
||||||
"react-native-svg": "^15.1.0",
|
"react-native-svg": "^15.1.0",
|
||||||
"react-native-url-polyfill": "^2.0.0",
|
|
||||||
"react-native-vector-icons": "^10.0.3",
|
"react-native-vector-icons": "^10.0.3",
|
||||||
"react-native-vision-camera": "^3.9.0",
|
"react-native-vision-camera": "^3.9.0",
|
||||||
"text-encoding-polyfill": "^0.6.7",
|
"text-encoding-polyfill": "^0.6.7",
|
||||||
|
1
react-native-config.d.ts
vendored
1
react-native-config.d.ts
vendored
@ -4,7 +4,6 @@ declare module 'react-native-config' {
|
|||||||
WALLET_CONNECT_PROJECT_ID: string;
|
WALLET_CONNECT_PROJECT_ID: string;
|
||||||
DEFAULT_GAS_PRICE: string;
|
DEFAULT_GAS_PRICE: string;
|
||||||
DEFAULT_GAS_ADJUSTMENT: string;
|
DEFAULT_GAS_ADJUSTMENT: string;
|
||||||
LACONICD_RPC_URL: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Config: NativeConfig;
|
export const Config: NativeConfig;
|
||||||
|
@ -1,31 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Default value for IS_RELEASE
|
|
||||||
IS_RELEASE=${IS_RELEASE:-false}
|
|
||||||
|
|
||||||
# Install dependencies
|
|
||||||
echo "Installing dependencies..."
|
|
||||||
yarn
|
|
||||||
|
|
||||||
# Create the necessary directory for assets
|
|
||||||
mkdir -p android/app/src/main/assets/
|
|
||||||
|
|
||||||
# Bundle the React Native application
|
|
||||||
yarn react-native bundle \
|
|
||||||
--platform android \
|
|
||||||
--dev false \
|
|
||||||
--entry-file index.js \
|
|
||||||
--bundle-output android/app/src/main/assets/index.android.bundle \
|
|
||||||
--assets-dest android/app/src/main/res
|
|
||||||
|
|
||||||
# Navigate to the android directory
|
|
||||||
cd android
|
|
||||||
|
|
||||||
# Run the Gradle build based on the IS_RELEASE flag
|
|
||||||
if [ "$IS_RELEASE" = "true" ]; then
|
|
||||||
echo "Building release version..."
|
|
||||||
./gradlew assembleRelease
|
|
||||||
else
|
|
||||||
echo "Building debug version..."
|
|
||||||
./gradlew assembleDebug
|
|
||||||
fi
|
|
45
src/App.tsx
45
src/App.tsx
@ -22,17 +22,16 @@ import HomeScreen from './screens/HomeScreen';
|
|||||||
import SignRequest from './screens/SignRequest';
|
import SignRequest from './screens/SignRequest';
|
||||||
import AddSession from './screens/AddSession';
|
import AddSession from './screens/AddSession';
|
||||||
import WalletConnect from './screens/WalletConnect';
|
import WalletConnect from './screens/WalletConnect';
|
||||||
import ApproveTransaction from './screens/ApproveTransaction';
|
|
||||||
import { StackParamsList } from './types';
|
import { StackParamsList } from './types';
|
||||||
|
import { web3wallet } from './utils/wallet-connect/WalletConnectUtils';
|
||||||
import { EIP155_SIGNING_METHODS } from './utils/wallet-connect/EIP155Data';
|
import { EIP155_SIGNING_METHODS } from './utils/wallet-connect/EIP155Data';
|
||||||
import { getSignParamsMessage } from './utils/wallet-connect/helpers';
|
import { getSignParamsMessage } from './utils/wallet-connect/helpers';
|
||||||
import ApproveTransfer from './screens/ApproveTransfer';
|
import ApproveTransaction from './screens/ApproveTransaction';
|
||||||
import AddNetwork from './screens/AddNetwork';
|
import AddNetwork from './screens/AddNetwork';
|
||||||
import EditNetwork from './screens/EditNetwork';
|
import EditNetwork from './screens/EditNetwork';
|
||||||
import { COSMOS, EIP155 } from './utils/constants';
|
import { COSMOS, EIP155 } from './utils/constants';
|
||||||
import { useNetworks } from './context/NetworksContext';
|
import { useNetworks } from './context/NetworksContext';
|
||||||
import { NETWORK_METHODS } from './utils/wallet-connect/common-data';
|
import { NETWORK_METHODS } from './utils/wallet-connect/common-data';
|
||||||
import { COSMOS_METHODS } from './utils/wallet-connect/COSMOSData';
|
|
||||||
|
|
||||||
const Stack = createNativeStackNavigator<StackParamsList>();
|
const Stack = createNativeStackNavigator<StackParamsList>();
|
||||||
|
|
||||||
@ -40,7 +39,7 @@ const App = (): React.JSX.Element => {
|
|||||||
const navigation =
|
const navigation =
|
||||||
useNavigation<NativeStackNavigationProp<StackParamsList>>();
|
useNavigation<NativeStackNavigationProp<StackParamsList>>();
|
||||||
|
|
||||||
const { web3wallet, setActiveSessions } = useWalletConnect();
|
const { setActiveSessions } = useWalletConnect();
|
||||||
const { accounts, setCurrentIndex } = useAccounts();
|
const { accounts, setCurrentIndex } = useAccounts();
|
||||||
const { networksData, selectedNetwork, setSelectedNetwork } = useNetworks();
|
const { networksData, selectedNetwork, setSelectedNetwork } = useNetworks();
|
||||||
const [modalVisible, setModalVisible] = useState(false);
|
const [modalVisible, setModalVisible] = useState(false);
|
||||||
@ -62,7 +61,7 @@ const App = (): React.JSX.Element => {
|
|||||||
setModalVisible(true);
|
setModalVisible(true);
|
||||||
setCurrentProposal(proposal);
|
setCurrentProposal(proposal);
|
||||||
},
|
},
|
||||||
[accounts, web3wallet],
|
[accounts],
|
||||||
);
|
);
|
||||||
|
|
||||||
const onSessionRequest = useCallback(
|
const onSessionRequest = useCallback(
|
||||||
@ -115,7 +114,7 @@ const App = (): React.JSX.Element => {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case EIP155_SIGNING_METHODS.ETH_SEND_TRANSACTION:
|
case EIP155_SIGNING_METHODS.ETH_SEND_TRANSACTION:
|
||||||
navigation.navigate('ApproveTransfer', {
|
navigation.navigate('ApproveTransaction', {
|
||||||
transaction: request.params[0],
|
transaction: request.params[0],
|
||||||
requestEvent,
|
requestEvent,
|
||||||
requestSessionData,
|
requestSessionData,
|
||||||
@ -132,7 +131,7 @@ const App = (): React.JSX.Element => {
|
|||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case COSMOS_METHODS.COSMOS_SIGN_DIRECT:
|
case 'cosmos_signDirect':
|
||||||
const message = {
|
const message = {
|
||||||
txbody: TxBody.toJSON(
|
txbody: TxBody.toJSON(
|
||||||
TxBody.decode(
|
TxBody.decode(
|
||||||
@ -158,7 +157,7 @@ const App = (): React.JSX.Element => {
|
|||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case COSMOS_METHODS.COSMOS_SIGN_AMINO:
|
case 'cosmos_signAmino':
|
||||||
navigation.navigate('SignRequest', {
|
navigation.navigate('SignRequest', {
|
||||||
namespace: COSMOS,
|
namespace: COSMOS,
|
||||||
address: request.params.signerAddress,
|
address: request.params.signerAddress,
|
||||||
@ -168,19 +167,9 @@ const App = (): React.JSX.Element => {
|
|||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case COSMOS_METHODS.COSMOS_SEND_TOKENS:
|
case 'cosmos_sendTokens':
|
||||||
navigation.navigate('ApproveTransfer', {
|
|
||||||
transaction: request.params[0],
|
|
||||||
requestEvent,
|
|
||||||
requestSessionData,
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
|
|
||||||
case COSMOS_METHODS.COSMOS_SEND_TRANSACTION:
|
|
||||||
const { transactionMessage, signer } = request.params;
|
|
||||||
navigation.navigate('ApproveTransaction', {
|
navigation.navigate('ApproveTransaction', {
|
||||||
transactionMessage,
|
transaction: request.params[0],
|
||||||
signer,
|
|
||||||
requestEvent,
|
requestEvent,
|
||||||
requestSessionData,
|
requestSessionData,
|
||||||
});
|
});
|
||||||
@ -196,14 +185,13 @@ const App = (): React.JSX.Element => {
|
|||||||
setSelectedNetwork,
|
setSelectedNetwork,
|
||||||
setCurrentIndex,
|
setCurrentIndex,
|
||||||
selectedNetwork,
|
selectedNetwork,
|
||||||
web3wallet,
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
const onSessionDelete = useCallback(() => {
|
const onSessionDelete = useCallback(() => {
|
||||||
const sessions = web3wallet!.getActiveSessions();
|
const sessions = web3wallet!.getActiveSessions();
|
||||||
setActiveSessions(sessions);
|
setActiveSessions(sessions);
|
||||||
}, [setActiveSessions, web3wallet]);
|
}, [setActiveSessions]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
web3wallet?.on('session_proposal', onSessionProposal);
|
web3wallet?.on('session_proposal', onSessionProposal);
|
||||||
@ -280,10 +268,10 @@ const App = (): React.JSX.Element => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
name="ApproveTransfer"
|
name="ApproveTransaction"
|
||||||
component={ApproveTransfer}
|
component={ApproveTransaction}
|
||||||
options={{
|
options={{
|
||||||
title: 'Approve transfer',
|
title: 'Approve transaction',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
@ -300,13 +288,6 @@ const App = (): React.JSX.Element => {
|
|||||||
title: 'Edit Network',
|
title: 'Edit Network',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Stack.Screen
|
|
||||||
name="ApproveTransaction"
|
|
||||||
component={ApproveTransaction}
|
|
||||||
options={{
|
|
||||||
title: 'Approve Transaction',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Stack.Navigator>
|
</Stack.Navigator>
|
||||||
<PairingModal
|
<PairingModal
|
||||||
visible={modalVisible}
|
visible={modalVisible}
|
||||||
|
@ -6,28 +6,24 @@ import { setInternetCredentials } from 'react-native-keychain';
|
|||||||
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 { StackParamsList, Account } from '../types';
|
import { AccountsProps, StackParamsList, Account } from '../types';
|
||||||
import { addAccount } from '../utils/accounts';
|
import { addAccount } from '../utils/accounts';
|
||||||
import styles from '../styles/stylesheet';
|
import styles from '../styles/stylesheet';
|
||||||
import HDPathDialog from './HDPathDialog';
|
import HDPathDialog from './HDPathDialog';
|
||||||
import AccountDetails from './AccountDetails';
|
import AccountDetails from './AccountDetails';
|
||||||
import { useAccounts } from '../context/AccountsContext';
|
import { useAccounts } from '../context/AccountsContext';
|
||||||
|
import { web3wallet } from '../utils/wallet-connect/WalletConnectUtils';
|
||||||
import { useNetworks } from '../context/NetworksContext';
|
import { useNetworks } from '../context/NetworksContext';
|
||||||
import ConfirmDialog from './ConfirmDialog';
|
import ConfirmDialog from './ConfirmDialog';
|
||||||
import { getNamespaces } from '../utils/wallet-connect/helpers';
|
import { getNamespaces } from '../utils/wallet-connect/helpers';
|
||||||
import ShowPKDialog from './ShowPKDialog';
|
|
||||||
import { useWalletConnect } from '../context/WalletConnectContext';
|
|
||||||
|
|
||||||
const Accounts = () => {
|
const Accounts = ({ currentIndex, updateIndex }: AccountsProps) => {
|
||||||
const navigation =
|
const navigation =
|
||||||
useNavigation<NativeStackNavigationProp<StackParamsList>>();
|
useNavigation<NativeStackNavigationProp<StackParamsList>>();
|
||||||
|
|
||||||
const { accounts, setAccounts, setCurrentIndex, currentIndex } =
|
const { accounts, setAccounts, setCurrentIndex } = useAccounts();
|
||||||
useAccounts();
|
|
||||||
const { networksData, selectedNetwork, setNetworksData, setSelectedNetwork } =
|
const { networksData, selectedNetwork, setNetworksData, setSelectedNetwork } =
|
||||||
useNetworks();
|
useNetworks();
|
||||||
|
|
||||||
const { web3wallet } = useWalletConnect();
|
|
||||||
const [expanded, setExpanded] = useState(false);
|
const [expanded, setExpanded] = useState(false);
|
||||||
const [isAccountCreating, setIsAccountCreating] = useState(false);
|
const [isAccountCreating, setIsAccountCreating] = useState(false);
|
||||||
const [hdDialog, setHdDialog] = useState(false);
|
const [hdDialog, setHdDialog] = useState(false);
|
||||||
@ -60,6 +56,7 @@ const Accounts = () => {
|
|||||||
networksData,
|
networksData,
|
||||||
selectedNetwork!,
|
selectedNetwork!,
|
||||||
accounts,
|
accounts,
|
||||||
|
currentIndex,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!updatedNamespaces) {
|
if (!updatedNamespaces) {
|
||||||
@ -75,7 +72,7 @@ const Accounts = () => {
|
|||||||
// Call the updateSessions function when the 'accounts' dependency changes
|
// Call the updateSessions function when the 'accounts' dependency changes
|
||||||
updateSessions();
|
updateSessions();
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [accounts, web3wallet]);
|
}, [accounts]);
|
||||||
|
|
||||||
const addAccountHandler = async () => {
|
const addAccountHandler = async () => {
|
||||||
setIsAccountCreating(true);
|
setIsAccountCreating(true);
|
||||||
@ -83,7 +80,7 @@ const Accounts = () => {
|
|||||||
setIsAccountCreating(false);
|
setIsAccountCreating(false);
|
||||||
if (newAccount) {
|
if (newAccount) {
|
||||||
updateAccounts(newAccount);
|
updateAccounts(newAccount);
|
||||||
setCurrentIndex(newAccount.index);
|
updateIndex(newAccount.index);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -93,7 +90,7 @@ const Accounts = () => {
|
|||||||
key={account.index}
|
key={account.index}
|
||||||
title={`Account ${account.index + 1}`}
|
title={`Account ${account.index + 1}`}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
setCurrentIndex(account.index);
|
updateIndex(account.index);
|
||||||
setExpanded(false);
|
setExpanded(false);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -123,6 +120,7 @@ const Accounts = () => {
|
|||||||
visible={hdDialog}
|
visible={hdDialog}
|
||||||
hideDialog={() => setHdDialog(false)}
|
hideDialog={() => setHdDialog(false)}
|
||||||
updateAccounts={updateAccounts}
|
updateAccounts={updateAccounts}
|
||||||
|
updateIndex={updateIndex}
|
||||||
pathCode={pathCode}
|
pathCode={pathCode}
|
||||||
/>
|
/>
|
||||||
<List.Accordion
|
<List.Accordion
|
||||||
@ -220,8 +218,6 @@ const Accounts = () => {
|
|||||||
hideDialog={hideDeleteNetworkDialog}
|
hideDialog={hideDeleteNetworkDialog}
|
||||||
onConfirm={handleRemove}
|
onConfirm={handleRemove}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ShowPKDialog />
|
|
||||||
</View>
|
</View>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
import React, { useState } from 'react';
|
import React from 'react';
|
||||||
import { View } from 'react-native';
|
import { View } from 'react-native';
|
||||||
import { Button, Dialog, Portal, Snackbar, Text } from 'react-native-paper';
|
import { Button, Dialog, Portal, Text } from 'react-native-paper';
|
||||||
|
|
||||||
import Clipboard from '@react-native-clipboard/clipboard';
|
|
||||||
|
|
||||||
import styles from '../styles/stylesheet';
|
import styles from '../styles/stylesheet';
|
||||||
import GridView from './Grid';
|
import GridView from './Grid';
|
||||||
@ -13,45 +11,29 @@ const DialogComponent = ({
|
|||||||
hideDialog,
|
hideDialog,
|
||||||
contentText,
|
contentText,
|
||||||
}: CustomDialogProps) => {
|
}: CustomDialogProps) => {
|
||||||
const [toastVisible, setToastVisible] = useState(false);
|
|
||||||
|
|
||||||
const words = contentText.split(' ');
|
const words = contentText.split(' ');
|
||||||
|
|
||||||
const handleCopy = () => {
|
|
||||||
Clipboard.setString(contentText);
|
|
||||||
setToastVisible(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Portal>
|
||||||
<Portal>
|
<Dialog visible={visible} onDismiss={hideDialog}>
|
||||||
<Dialog visible={visible} onDismiss={hideDialog}>
|
<Dialog.Content>
|
||||||
<Dialog.Content>
|
<Text variant="titleLarge">Mnemonic</Text>
|
||||||
<Text variant="titleLarge">Mnemonic</Text>
|
<View style={styles.dialogTitle}>
|
||||||
<View style={styles.dialogTitle}>
|
<Text variant="titleMedium">
|
||||||
<Text variant="titleMedium">
|
Your mnemonic provides full access to your wallet and funds. Make
|
||||||
Your mnemonic provides full access to your wallet and funds.
|
sure to note it down.{' '}
|
||||||
Make sure to note it down.{' '}
|
</Text>
|
||||||
</Text>
|
<Text variant="titleMedium" style={styles.dialogWarning}>
|
||||||
<Text variant="titleMedium" style={styles.dialogWarning}>
|
Do not share your mnemonic with anyone
|
||||||
Do not share your mnemonic with anyone
|
</Text>
|
||||||
</Text>
|
<GridView words={words} />
|
||||||
<GridView words={words} />
|
</View>
|
||||||
</View>
|
</Dialog.Content>
|
||||||
</Dialog.Content>
|
<Dialog.Actions>
|
||||||
<Dialog.Actions>
|
<Button onPress={hideDialog}>Done</Button>
|
||||||
<Button onPress={handleCopy}>Copy</Button>
|
</Dialog.Actions>
|
||||||
<Button onPress={hideDialog}>Done</Button>
|
</Dialog>
|
||||||
</Dialog.Actions>
|
</Portal>
|
||||||
</Dialog>
|
|
||||||
</Portal>
|
|
||||||
<Snackbar
|
|
||||||
visible={toastVisible}
|
|
||||||
onDismiss={() => setToastVisible(false)}
|
|
||||||
duration={1000}>
|
|
||||||
Mnemonic copied to clipboard
|
|
||||||
</Snackbar>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -5,20 +5,20 @@ import { Button, TextInput } from 'react-native-paper';
|
|||||||
import { addAccountFromHDPath } from '../utils/accounts';
|
import { addAccountFromHDPath } from '../utils/accounts';
|
||||||
import { Account, NetworksDataState, PathState } from '../types';
|
import { Account, NetworksDataState, PathState } from '../types';
|
||||||
import styles from '../styles/stylesheet';
|
import styles from '../styles/stylesheet';
|
||||||
import { useAccounts } from '../context/AccountsContext';
|
|
||||||
|
|
||||||
const HDPath = ({
|
const HDPath = ({
|
||||||
pathCode,
|
pathCode,
|
||||||
updateAccounts,
|
updateAccounts,
|
||||||
|
updateIndex,
|
||||||
hideDialog,
|
hideDialog,
|
||||||
selectedNetwork,
|
selectedNetwork,
|
||||||
}: {
|
}: {
|
||||||
pathCode: string;
|
pathCode: string;
|
||||||
|
updateIndex: (index: number) => void;
|
||||||
updateAccounts: (account: Account) => void;
|
updateAccounts: (account: Account) => void;
|
||||||
hideDialog: () => void;
|
hideDialog: () => void;
|
||||||
selectedNetwork: NetworksDataState;
|
selectedNetwork: NetworksDataState;
|
||||||
}) => {
|
}) => {
|
||||||
const { setCurrentIndex } = useAccounts();
|
|
||||||
const [isAccountCreating, setIsAccountCreating] = useState(false);
|
const [isAccountCreating, setIsAccountCreating] = useState(false);
|
||||||
const [path, setPath] = useState<PathState>({
|
const [path, setPath] = useState<PathState>({
|
||||||
firstNumber: '',
|
firstNumber: '',
|
||||||
@ -46,7 +46,7 @@ const HDPath = ({
|
|||||||
const newAccount = await addAccountFromHDPath(hdPath, selectedNetwork);
|
const newAccount = await addAccountFromHDPath(hdPath, selectedNetwork);
|
||||||
if (newAccount) {
|
if (newAccount) {
|
||||||
updateAccounts(newAccount);
|
updateAccounts(newAccount);
|
||||||
setCurrentIndex(newAccount.index);
|
updateIndex(newAccount.index);
|
||||||
hideDialog();
|
hideDialog();
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -8,6 +8,7 @@ import { useNetworks } from '../context/NetworksContext';
|
|||||||
const HDPathDialog = ({
|
const HDPathDialog = ({
|
||||||
visible,
|
visible,
|
||||||
hideDialog,
|
hideDialog,
|
||||||
|
updateIndex,
|
||||||
updateAccounts,
|
updateAccounts,
|
||||||
pathCode,
|
pathCode,
|
||||||
}: HDPathDialogProps) => {
|
}: HDPathDialogProps) => {
|
||||||
@ -21,6 +22,7 @@ const HDPathDialog = ({
|
|||||||
<HDPath
|
<HDPath
|
||||||
selectedNetwork={selectedNetwork!}
|
selectedNetwork={selectedNetwork!}
|
||||||
pathCode={pathCode}
|
pathCode={pathCode}
|
||||||
|
updateIndex={updateIndex}
|
||||||
updateAccounts={updateAccounts}
|
updateAccounts={updateAccounts}
|
||||||
hideDialog={hideDialog}
|
hideDialog={hideDialog}
|
||||||
/>
|
/>
|
||||||
|
@ -8,6 +8,7 @@ import { buildApprovedNamespaces, getSdkError } from '@walletconnect/utils';
|
|||||||
|
|
||||||
import { PairingModalProps } from '../types';
|
import { PairingModalProps } from '../types';
|
||||||
import styles from '../styles/stylesheet';
|
import styles from '../styles/stylesheet';
|
||||||
|
import { web3wallet } from '../utils/wallet-connect/WalletConnectUtils';
|
||||||
import { useAccounts } from '../context/AccountsContext';
|
import { useAccounts } from '../context/AccountsContext';
|
||||||
import { useWalletConnect } from '../context/WalletConnectContext';
|
import { useWalletConnect } from '../context/WalletConnectContext';
|
||||||
import { useNetworks } from '../context/NetworksContext';
|
import { useNetworks } from '../context/NetworksContext';
|
||||||
@ -20,7 +21,7 @@ const PairingModal = ({
|
|||||||
setModalVisible,
|
setModalVisible,
|
||||||
setToastVisible,
|
setToastVisible,
|
||||||
}: PairingModalProps) => {
|
}: PairingModalProps) => {
|
||||||
const { accounts } = useAccounts();
|
const { accounts, currentIndex } = useAccounts();
|
||||||
const { selectedNetwork, networksData } = useNetworks();
|
const { selectedNetwork, networksData } = useNetworks();
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [chainError, setChainError] = useState('');
|
const [chainError, setChainError] = useState('');
|
||||||
@ -86,7 +87,7 @@ const PairingModal = ({
|
|||||||
});
|
});
|
||||||
}, [currentProposal]);
|
}, [currentProposal]);
|
||||||
|
|
||||||
const { setActiveSessions, web3wallet } = useWalletConnect();
|
const { setActiveSessions } = useWalletConnect();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const getSupportedNamespaces = async () => {
|
const getSupportedNamespaces = async () => {
|
||||||
@ -103,6 +104,7 @@ const PairingModal = ({
|
|||||||
networksData,
|
networksData,
|
||||||
selectedNetwork!,
|
selectedNetwork!,
|
||||||
accounts,
|
accounts,
|
||||||
|
currentIndex,
|
||||||
);
|
);
|
||||||
setSupportedNamespaces(nameSpaces);
|
setSupportedNamespaces(nameSpaces);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -128,7 +130,7 @@ const PairingModal = ({
|
|||||||
networksData,
|
networksData,
|
||||||
selectedNetwork,
|
selectedNetwork,
|
||||||
accounts,
|
accounts,
|
||||||
web3wallet,
|
currentIndex,
|
||||||
setCurrentProposal,
|
setCurrentProposal,
|
||||||
setModalVisible,
|
setModalVisible,
|
||||||
]);
|
]);
|
||||||
|
@ -1,97 +0,0 @@
|
|||||||
import { TouchableOpacity, View } from 'react-native';
|
|
||||||
import React, { useState } from 'react';
|
|
||||||
import { Button, Text, Portal, Dialog, useTheme } from 'react-native-paper';
|
|
||||||
|
|
||||||
import styles from '../styles/stylesheet';
|
|
||||||
import { getPathKey } from '../utils/misc';
|
|
||||||
import { useNetworks } from '../context/NetworksContext';
|
|
||||||
import { useAccounts } from '../context/AccountsContext';
|
|
||||||
|
|
||||||
const ShowPKDialog = () => {
|
|
||||||
const { currentIndex } = useAccounts();
|
|
||||||
const { selectedNetwork } = useNetworks();
|
|
||||||
|
|
||||||
const [privateKey, setprivateKey] = useState<string>();
|
|
||||||
const [showPKDialog, setShowPKDialog] = useState<boolean>(false);
|
|
||||||
|
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
const handleShowPrivateKey = async () => {
|
|
||||||
const pathKey = await getPathKey(
|
|
||||||
`${selectedNetwork!.namespace}:${selectedNetwork!.chainId}`,
|
|
||||||
currentIndex,
|
|
||||||
);
|
|
||||||
|
|
||||||
setprivateKey(pathKey.privKey);
|
|
||||||
};
|
|
||||||
|
|
||||||
const hideShowPKDialog = () => {
|
|
||||||
setShowPKDialog(false);
|
|
||||||
setprivateKey(undefined);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<View style={styles.signLink}>
|
|
||||||
<TouchableOpacity
|
|
||||||
onPress={() => {
|
|
||||||
setShowPKDialog(true);
|
|
||||||
}}>
|
|
||||||
<Text
|
|
||||||
variant="titleSmall"
|
|
||||||
style={[styles.hyperlink, { color: theme.colors.primary }]}>
|
|
||||||
Show Private Key
|
|
||||||
</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
|
||||||
<View>
|
|
||||||
<Portal>
|
|
||||||
<Dialog visible={showPKDialog} onDismiss={hideShowPKDialog}>
|
|
||||||
<Dialog.Title>
|
|
||||||
{!privateKey ? (
|
|
||||||
<Text>Show Private Key?</Text>
|
|
||||||
) : (
|
|
||||||
<Text>Private Key</Text>
|
|
||||||
)}
|
|
||||||
</Dialog.Title>
|
|
||||||
<Dialog.Content>
|
|
||||||
{privateKey && (
|
|
||||||
<View style={[styles.dataBox, styles.dataBoxContainer]}>
|
|
||||||
<Text
|
|
||||||
selectable
|
|
||||||
variant="bodyMedium"
|
|
||||||
style={styles.dataBoxData}>
|
|
||||||
{privateKey}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
<View>
|
|
||||||
<Text variant="bodyMedium" style={styles.dialogWarning}>
|
|
||||||
<Text style={[styles.highlight, styles.dialogWarning]}>
|
|
||||||
Warning:
|
|
||||||
</Text>
|
|
||||||
Never disclose this key. Anyone with your private keys can
|
|
||||||
steal any assets held in your account.
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
</Dialog.Content>
|
|
||||||
<Dialog.Actions>
|
|
||||||
{!privateKey ? (
|
|
||||||
<>
|
|
||||||
<Button onPress={handleShowPrivateKey} textColor="red">
|
|
||||||
Yes
|
|
||||||
</Button>
|
|
||||||
<Button onPress={hideShowPKDialog}>No</Button>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<Button onPress={hideShowPKDialog}>Ok</Button>
|
|
||||||
)}
|
|
||||||
</Dialog.Actions>
|
|
||||||
</Dialog>
|
|
||||||
</Portal>
|
|
||||||
</View>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ShowPKDialog;
|
|
@ -1,16 +1,14 @@
|
|||||||
import React, { createContext, useContext, useEffect, useState } from 'react';
|
import React, { createContext, useContext, useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { SessionTypes } from '@walletconnect/types';
|
import { SessionTypes } from '@walletconnect/types';
|
||||||
import { IWeb3Wallet } from '@walletconnect/web3wallet';
|
|
||||||
|
|
||||||
import { WalletConnectContextProps } from '../types';
|
import { WalletConnectContextProps } from '../types';
|
||||||
|
import { web3wallet } from '../utils/wallet-connect/WalletConnectUtils';
|
||||||
import useInitialization from '../hooks/useInitialization';
|
import useInitialization from '../hooks/useInitialization';
|
||||||
|
|
||||||
const WalletConnectContext = createContext<WalletConnectContextProps>({
|
const WalletConnectContext = createContext<WalletConnectContextProps>({
|
||||||
activeSessions: {},
|
activeSessions: {},
|
||||||
setActiveSessions: () => {},
|
setActiveSessions: () => {},
|
||||||
web3wallet: {} as IWeb3Wallet,
|
|
||||||
setWeb3wallet: () => {},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const useWalletConnect = () => {
|
const useWalletConnect = () => {
|
||||||
@ -19,14 +17,12 @@ const useWalletConnect = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const WalletConnectProvider = ({ children }: { children: React.ReactNode }) => {
|
const WalletConnectProvider = ({ children }: { children: React.ReactNode }) => {
|
||||||
const [web3wallet, setWeb3wallet] = useState<IWeb3Wallet | undefined>();
|
useInitialization();
|
||||||
|
|
||||||
useInitialization(setWeb3wallet);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const sessions = (web3wallet && web3wallet.getActiveSessions()) || {};
|
const sessions = (web3wallet && web3wallet.getActiveSessions()) || {};
|
||||||
setActiveSessions(sessions);
|
setActiveSessions(sessions);
|
||||||
}, [web3wallet]);
|
}, []);
|
||||||
|
|
||||||
const [activeSessions, setActiveSessions] = useState<
|
const [activeSessions, setActiveSessions] = useState<
|
||||||
Record<string, SessionTypes.Struct>
|
Record<string, SessionTypes.Struct>
|
||||||
@ -37,8 +33,6 @@ const WalletConnectProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
value={{
|
value={{
|
||||||
activeSessions,
|
activeSessions,
|
||||||
setActiveSessions,
|
setActiveSessions,
|
||||||
web3wallet,
|
|
||||||
setWeb3wallet,
|
|
||||||
}}>
|
}}>
|
||||||
{children}
|
{children}
|
||||||
</WalletConnectContext.Provider>
|
</WalletConnectContext.Provider>
|
||||||
|
@ -1,24 +1,17 @@
|
|||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { IWeb3Wallet } from '@walletconnect/web3wallet';
|
|
||||||
|
|
||||||
import { createWeb3Wallet } from '../utils/wallet-connect/WalletConnectUtils';
|
import { createWeb3Wallet } from '../utils/wallet-connect/WalletConnectUtils';
|
||||||
|
|
||||||
export default function useInitialization(
|
export default function useInitialization() {
|
||||||
setWeb3wallet: React.Dispatch<React.SetStateAction<IWeb3Wallet | undefined>>,
|
|
||||||
) {
|
|
||||||
const [initialized, setInitialized] = useState(false);
|
const [initialized, setInitialized] = useState(false);
|
||||||
|
|
||||||
const onInitialize = useCallback(async () => {
|
const onInitialize = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
const web3walletInstance = await createWeb3Wallet();
|
await createWeb3Wallet();
|
||||||
setWeb3wallet(web3walletInstance);
|
|
||||||
|
|
||||||
setInitialized(true);
|
setInitialized(true);
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
console.error('Error for initializing', err);
|
console.error('Error for initializing', err);
|
||||||
}
|
}
|
||||||
}, [setWeb3wallet]);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!initialized) {
|
if (!initialized) {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useCallback, useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { AppState, TouchableOpacity, View } from 'react-native';
|
import { AppState, TouchableOpacity, View } from 'react-native';
|
||||||
import { Button, Text, TextInput } from 'react-native-paper';
|
import { Button, Text, TextInput } from 'react-native-paper';
|
||||||
import {
|
import {
|
||||||
@ -15,7 +15,6 @@ import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
|||||||
import { web3WalletPair } from '../utils/wallet-connect/WalletConnectUtils';
|
import { web3WalletPair } from '../utils/wallet-connect/WalletConnectUtils';
|
||||||
import styles from '../styles/stylesheet';
|
import styles from '../styles/stylesheet';
|
||||||
import { StackParamsList } from '../types';
|
import { StackParamsList } from '../types';
|
||||||
import { useWalletConnect } from '../context/WalletConnectContext';
|
|
||||||
|
|
||||||
const AddSession = () => {
|
const AddSession = () => {
|
||||||
const navigation =
|
const navigation =
|
||||||
@ -24,8 +23,6 @@ const AddSession = () => {
|
|||||||
const { hasPermission, requestPermission } = useCameraPermission();
|
const { hasPermission, requestPermission } = useCameraPermission();
|
||||||
const device = useCameraDevice('back');
|
const device = useCameraDevice('back');
|
||||||
|
|
||||||
const { web3wallet } = useWalletConnect();
|
|
||||||
|
|
||||||
const [currentWCURI, setCurrentWCURI] = useState<string>('');
|
const [currentWCURI, setCurrentWCURI] = useState<string>('');
|
||||||
const [isActive, setIsActive] = useState(AppState.currentState === 'active');
|
const [isActive, setIsActive] = useState(AppState.currentState === 'active');
|
||||||
const [isScanning, setScanning] = useState(true);
|
const [isScanning, setScanning] = useState(true);
|
||||||
@ -48,15 +45,11 @@ const AddSession = () => {
|
|||||||
await Linking.openSettings();
|
await Linking.openSettings();
|
||||||
};
|
};
|
||||||
|
|
||||||
const pair = useCallback(async () => {
|
const pair = async () => {
|
||||||
if (!web3wallet) {
|
const pairing = await web3WalletPair({ uri: currentWCURI });
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const pairing = await web3WalletPair(web3wallet, { uri: currentWCURI });
|
|
||||||
navigation.navigate('WalletConnect');
|
navigation.navigate('WalletConnect');
|
||||||
return pairing;
|
return pairing;
|
||||||
}, [web3wallet, currentWCURI, navigation]);
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleAppStateChange = (newState: string) => {
|
const handleAppStateChange = (newState: string) => {
|
||||||
|
@ -1,67 +1,103 @@
|
|||||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { Image, ScrollView, View } from 'react-native';
|
import { Image, ScrollView, View } from 'react-native';
|
||||||
import { Button, Text, TextInput } from 'react-native-paper';
|
import {
|
||||||
import { SvgUri } from 'react-native-svg';
|
ActivityIndicator,
|
||||||
|
Button,
|
||||||
|
Text,
|
||||||
|
Appbar,
|
||||||
|
TextInput,
|
||||||
|
} from 'react-native-paper';
|
||||||
|
import { providers, BigNumber } from 'ethers';
|
||||||
import Config from 'react-native-config';
|
import Config from 'react-native-config';
|
||||||
import { MsgCreateValidator } from 'cosmjs-types/cosmos/staking/v1beta1/tx';
|
import { Deferrable } from 'ethers/lib/utils';
|
||||||
|
|
||||||
|
import { useNavigation } from '@react-navigation/native';
|
||||||
import {
|
import {
|
||||||
NativeStackNavigationProp,
|
NativeStackNavigationProp,
|
||||||
NativeStackScreenProps,
|
NativeStackScreenProps,
|
||||||
} from '@react-navigation/native-stack';
|
} from '@react-navigation/native-stack';
|
||||||
import { useNavigation } from '@react-navigation/native';
|
import { getHeaderTitle } from '@react-navigation/elements';
|
||||||
import { DirectSecp256k1Wallet, EncodeObject } from '@cosmjs/proto-signing';
|
import { DirectSecp256k1Wallet } from '@cosmjs/proto-signing';
|
||||||
import { LaconicClient } from '@cerc-io/registry-sdk';
|
import {
|
||||||
import { GasPrice, calculateFee } from '@cosmjs/stargate';
|
calculateFee,
|
||||||
import { formatJsonRpcError } from '@json-rpc-tools/utils';
|
GasPrice,
|
||||||
|
MsgSendEncodeObject,
|
||||||
import { useNetworks } from '../context/NetworksContext';
|
SigningStargateClient,
|
||||||
import { Account, StackParamsList } from '../types';
|
} from '@cosmjs/stargate';
|
||||||
import styles from '../styles/stylesheet';
|
|
||||||
import { COSMOS, IS_NUMBER_REGEX } from '../utils/constants';
|
import { Account, StackParamsList } from '../types';
|
||||||
import { retrieveSingleAccount } from '../utils/accounts';
|
import AccountDetails from '../components/AccountDetails';
|
||||||
import { getPathKey } from '../utils/misc';
|
import styles from '../styles/stylesheet';
|
||||||
|
import { retrieveSingleAccount } from '../utils/accounts';
|
||||||
import {
|
import {
|
||||||
WalletConnectRequests,
|
|
||||||
approveWalletConnectRequest,
|
approveWalletConnectRequest,
|
||||||
rejectWalletConnectRequest,
|
rejectWalletConnectRequest,
|
||||||
|
WalletConnectRequests,
|
||||||
} from '../utils/wallet-connect/wallet-connect-requests';
|
} from '../utils/wallet-connect/wallet-connect-requests';
|
||||||
import { MEMO } from './ApproveTransfer';
|
import { web3wallet } from '../utils/wallet-connect/WalletConnectUtils';
|
||||||
|
import DataBox from '../components/DataBox';
|
||||||
|
import { getPathKey } from '../utils/misc';
|
||||||
|
import { useNetworks } from '../context/NetworksContext';
|
||||||
|
import { COSMOS, EIP155, IS_NUMBER_REGEX } from '../utils/constants';
|
||||||
import TxErrorDialog from '../components/TxErrorDialog';
|
import TxErrorDialog from '../components/TxErrorDialog';
|
||||||
import AccountDetails from '../components/AccountDetails';
|
import { EIP155_SIGNING_METHODS } from '../utils/wallet-connect/EIP155Data';
|
||||||
import { useWalletConnect } from '../context/WalletConnectContext';
|
import { COSMOS_METHODS } from '../utils/wallet-connect/COSMOSData';
|
||||||
|
|
||||||
type ApproveTransactionProps = NativeStackScreenProps<
|
const MEMO = 'Sending signed tx from Laconic Wallet';
|
||||||
|
// Reference: https://ethereum.org/en/developers/docs/gas/#what-is-gas-limit
|
||||||
|
const ETH_MINIMUM_GAS = 21000;
|
||||||
|
|
||||||
|
type SignRequestProps = NativeStackScreenProps<
|
||||||
StackParamsList,
|
StackParamsList,
|
||||||
'ApproveTransaction'
|
'ApproveTransaction'
|
||||||
>;
|
>;
|
||||||
|
|
||||||
const ApproveTransaction = ({ route }: ApproveTransactionProps) => {
|
const ApproveTransaction = ({ route }: SignRequestProps) => {
|
||||||
const { networksData } = useNetworks();
|
const { networksData } = useNetworks();
|
||||||
|
|
||||||
const requestSession = route.params.requestSessionData;
|
const requestSession = route.params.requestSessionData;
|
||||||
const requestName = requestSession.peer.metadata.name;
|
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 requestURL = requestSession.peer.metadata.url;
|
||||||
const signer = route.params.signer;
|
const transaction = route.params.transaction;
|
||||||
const requestEvent = route.params.requestEvent;
|
const requestEvent = route.params.requestEvent;
|
||||||
const chainId = requestEvent.params.chainId;
|
const chainId = requestEvent.params.chainId;
|
||||||
const requestEventId = requestEvent.id;
|
const requestMethod = requestEvent.params.request.method;
|
||||||
const topic = requestEvent.topic;
|
|
||||||
|
|
||||||
const { web3wallet } = useWalletConnect();
|
|
||||||
|
|
||||||
const [account, setAccount] = useState<Account>();
|
const [account, setAccount] = useState<Account>();
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
const [balance, setBalance] = useState<string>('');
|
||||||
|
const [isTxLoading, setIsTxLoading] = useState(false);
|
||||||
const [cosmosStargateClient, setCosmosStargateClient] =
|
const [cosmosStargateClient, setCosmosStargateClient] =
|
||||||
useState<LaconicClient>();
|
useState<SigningStargateClient>();
|
||||||
const [cosmosGasLimit, setCosmosGasLimit] = useState<string>();
|
|
||||||
const [fees, setFees] = useState<string>();
|
const [fees, setFees] = useState<string>();
|
||||||
|
const [cosmosGasLimit, setCosmosGasLimit] = useState<string>();
|
||||||
const [txError, setTxError] = useState<string>();
|
const [txError, setTxError] = useState<string>();
|
||||||
const [isTxErrorDialogOpen, setIsTxErrorDialogOpen] = useState(false);
|
const [isTxErrorDialogOpen, setIsTxErrorDialogOpen] = useState(false);
|
||||||
const [isRequestAccepted, setIsRequestAccepted] = useState(false);
|
const [ethGasPrice, setEthGasPrice] = useState<BigNumber | null>();
|
||||||
|
const [ethGasLimit, setEthGasLimit] = useState<BigNumber>();
|
||||||
|
const [ethMaxFee, setEthMaxFee] = useState<BigNumber | null>();
|
||||||
|
const [ethMaxPriorityFee, setEthMaxPriorityFee] =
|
||||||
|
useState<BigNumber | null>();
|
||||||
|
|
||||||
const navigation =
|
const isSufficientFunds = useMemo(() => {
|
||||||
useNavigation<NativeStackNavigationProp<StackParamsList>>();
|
if (!transaction.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!balance) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const amountBigNum = BigNumber.from(String(transaction.value));
|
||||||
|
const balanceBigNum = BigNumber.from(balance);
|
||||||
|
|
||||||
|
if (amountBigNum.gte(balanceBigNum)) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}, [balance, transaction]);
|
||||||
|
|
||||||
const requestedNetwork = networksData.find(
|
const requestedNetwork = networksData.find(
|
||||||
networkData =>
|
networkData =>
|
||||||
@ -69,19 +105,21 @@ const ApproveTransaction = ({ route }: ApproveTransactionProps) => {
|
|||||||
);
|
);
|
||||||
const namespace = requestedNetwork!.namespace;
|
const namespace = requestedNetwork!.namespace;
|
||||||
|
|
||||||
const transactionMessage = useMemo((): EncodeObject => {
|
const sendMsg: MsgSendEncodeObject = useMemo(() => {
|
||||||
const inputTxMsg = route.params.transactionMessage;
|
return {
|
||||||
|
typeUrl: '/cosmos.bank.v1beta1.MsgSend',
|
||||||
// If it's a MsgCreateValidator, decode the tx msg value using MsgCreateValidator type
|
value: {
|
||||||
if (inputTxMsg.typeUrl.includes('MsgCreateValidator')) {
|
fromAddress: transaction.from,
|
||||||
return {
|
toAddress: transaction.to,
|
||||||
typeUrl: inputTxMsg.typeUrl,
|
amount: [
|
||||||
value: MsgCreateValidator.fromJSON(inputTxMsg.value),
|
{
|
||||||
};
|
amount: String(transaction.value),
|
||||||
}
|
denom: requestedNetwork!.nativeDenom!,
|
||||||
|
},
|
||||||
return inputTxMsg;
|
],
|
||||||
}, [route.params.transactionMessage]);
|
},
|
||||||
|
};
|
||||||
|
}, [requestedNetwork, transaction]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (namespace !== COSMOS) {
|
if (namespace !== COSMOS) {
|
||||||
@ -106,29 +144,41 @@ const ApproveTransaction = ({ route }: ApproveTransactionProps) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const client = await LaconicClient.connectWithSigner(
|
const client = await SigningStargateClient.connectWithSigner(
|
||||||
requestedNetwork?.rpcUrl!,
|
requestedNetwork?.rpcUrl!,
|
||||||
sender,
|
sender,
|
||||||
);
|
);
|
||||||
|
|
||||||
setCosmosStargateClient(client);
|
setCosmosStargateClient(client);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
setTxError(error.message);
|
setTxError(error.message);
|
||||||
setIsTxErrorDialogOpen(true);
|
setIsTxErrorDialogOpen(true);
|
||||||
const response = formatJsonRpcError(requestEventId, error.message);
|
|
||||||
await web3wallet!.respondSessionRequest({ topic, response });
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
setClient();
|
setClient();
|
||||||
}, [
|
}, [account, requestedNetwork, chainId, namespace]);
|
||||||
account,
|
|
||||||
requestedNetwork,
|
const provider = useMemo(() => {
|
||||||
chainId,
|
if (namespace === EIP155) {
|
||||||
namespace,
|
if (!requestedNetwork) {
|
||||||
requestEventId,
|
throw new Error('Requested chain not supported');
|
||||||
topic,
|
}
|
||||||
web3wallet,
|
try {
|
||||||
]);
|
const ethProvider = new providers.JsonRpcProvider(
|
||||||
|
requestedNetwork.rpcUrl,
|
||||||
|
);
|
||||||
|
|
||||||
|
return ethProvider;
|
||||||
|
} catch (error: any) {
|
||||||
|
setTxError(error.message);
|
||||||
|
setIsTxErrorDialogOpen(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [requestedNetwork, namespace]);
|
||||||
|
|
||||||
|
const navigation =
|
||||||
|
useNavigation<NativeStackNavigationProp<StackParamsList>>();
|
||||||
|
|
||||||
const retrieveData = useCallback(
|
const retrieveData = useCallback(
|
||||||
async (requestAddress: string) => {
|
async (requestAddress: string) => {
|
||||||
@ -148,8 +198,245 @@ const ApproveTransaction = ({ route }: ApproveTransactionProps) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
retrieveData(signer);
|
// Set loading to false when gas values for requested chain are fetched
|
||||||
}, [retrieveData, signer]);
|
// If requested chain is EVM compatible, the cosmos gas values will be undefined and vice-versa, hence the condition checks only one of them at the same time
|
||||||
|
if (
|
||||||
|
// If requested chain is EVM compatible, set loading to false when ethMaxFee and ethPriorityFee have been populated
|
||||||
|
(ethMaxFee !== undefined && ethMaxPriorityFee !== undefined) ||
|
||||||
|
// Or if requested chain is a cosmos chain, set loading to false when cosmosGasLimit has been populated
|
||||||
|
cosmosGasLimit !== undefined
|
||||||
|
) {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
}, [ethMaxFee, ethMaxPriorityFee, cosmosGasLimit]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (namespace === EIP155) {
|
||||||
|
const ethFees = BigNumber.from(ethGasLimit ?? 0)
|
||||||
|
.mul(BigNumber.from(ethMaxFee ?? ethGasPrice ?? 0))
|
||||||
|
.toString();
|
||||||
|
setFees(ethFees);
|
||||||
|
} else {
|
||||||
|
const gasPrice = GasPrice.fromString(
|
||||||
|
requestedNetwork?.gasPrice! + requestedNetwork?.nativeDenom,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!cosmosGasLimit) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cosmosFees = calculateFee(Number(cosmosGasLimit), gasPrice);
|
||||||
|
|
||||||
|
setFees(cosmosFees.amount[0].amount);
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
transaction,
|
||||||
|
namespace,
|
||||||
|
ethGasLimit,
|
||||||
|
ethGasPrice,
|
||||||
|
cosmosGasLimit,
|
||||||
|
requestedNetwork,
|
||||||
|
ethMaxFee,
|
||||||
|
]);
|
||||||
|
useEffect(() => {
|
||||||
|
retrieveData(transaction.from!);
|
||||||
|
}, [retrieveData, transaction]);
|
||||||
|
|
||||||
|
const isEIP1559 = useMemo(() => {
|
||||||
|
if (cosmosGasLimit) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (ethMaxFee !== null && ethMaxPriorityFee !== null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}, [cosmosGasLimit, ethMaxFee, ethMaxPriorityFee]);
|
||||||
|
|
||||||
|
const acceptRequestHandler = async () => {
|
||||||
|
setIsTxLoading(true);
|
||||||
|
try {
|
||||||
|
if (!account) {
|
||||||
|
throw new Error('account not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ethGasLimit && ethGasLimit.lt(ETH_MINIMUM_GAS)) {
|
||||||
|
throw new Error(`Atleast ${ETH_MINIMUM_GAS} gas limit is required`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ethMaxFee && ethMaxPriorityFee && ethMaxFee.lte(ethMaxPriorityFee)) {
|
||||||
|
throw new Error(
|
||||||
|
`Max fee per gas (${ethMaxFee.toNumber()}) cannot be lower than or equal to max priority fee per gas (${ethMaxPriorityFee.toNumber()})`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let options: WalletConnectRequests;
|
||||||
|
|
||||||
|
switch (requestMethod) {
|
||||||
|
case EIP155_SIGNING_METHODS.ETH_SEND_TRANSACTION:
|
||||||
|
if (
|
||||||
|
ethMaxFee === undefined ||
|
||||||
|
ethMaxPriorityFee === undefined ||
|
||||||
|
ethGasPrice === undefined
|
||||||
|
) {
|
||||||
|
throw new Error('Gas values not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
options = {
|
||||||
|
type: 'eth_sendTransaction',
|
||||||
|
provider: provider!,
|
||||||
|
ethGasLimit: BigNumber.from(ethGasLimit),
|
||||||
|
ethGasPrice: ethGasPrice ? ethGasPrice.toHexString() : null,
|
||||||
|
maxFeePerGas: ethMaxFee,
|
||||||
|
maxPriorityFeePerGas: ethMaxPriorityFee,
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case COSMOS_METHODS.COSMOS_SEND_TOKENS:
|
||||||
|
if (!cosmosStargateClient) {
|
||||||
|
throw new Error('Cosmos stargate client not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
options = {
|
||||||
|
type: 'cosmos_sendTokens',
|
||||||
|
signingStargateClient: cosmosStargateClient,
|
||||||
|
// StdFee object
|
||||||
|
cosmosFee: {
|
||||||
|
// This amount is total fees required for transaction
|
||||||
|
amount: [
|
||||||
|
{
|
||||||
|
amount: fees!,
|
||||||
|
denom: requestedNetwork!.nativeDenom!,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
gas: cosmosGasLimit!,
|
||||||
|
},
|
||||||
|
sendMsg,
|
||||||
|
memo: MEMO,
|
||||||
|
};
|
||||||
|
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error('Invalid method');
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await approveWalletConnectRequest(
|
||||||
|
requestEvent,
|
||||||
|
account,
|
||||||
|
namespace,
|
||||||
|
requestedNetwork!.chainId,
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
|
||||||
|
const { topic } = requestEvent;
|
||||||
|
await web3wallet!.respondSessionRequest({ topic, response });
|
||||||
|
navigation.navigate('Laconic');
|
||||||
|
} catch (error: any) {
|
||||||
|
setTxError(error.message);
|
||||||
|
setIsTxErrorDialogOpen(true);
|
||||||
|
}
|
||||||
|
setIsTxLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const rejectRequestHandler = async () => {
|
||||||
|
const response = rejectWalletConnectRequest(requestEvent);
|
||||||
|
const { topic } = requestEvent;
|
||||||
|
await web3wallet!.respondSessionRequest({
|
||||||
|
topic,
|
||||||
|
response,
|
||||||
|
});
|
||||||
|
|
||||||
|
navigation.navigate('Laconic');
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const getAccountBalance = async () => {
|
||||||
|
try {
|
||||||
|
if (!account) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (namespace === EIP155) {
|
||||||
|
if (!provider) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const fetchedBalance = await provider.getBalance(account.address);
|
||||||
|
setBalance(fetchedBalance ? fetchedBalance.toString() : '0');
|
||||||
|
} else {
|
||||||
|
const cosmosBalance = await cosmosStargateClient?.getBalance(
|
||||||
|
account.address,
|
||||||
|
requestedNetwork!.nativeDenom!.toLowerCase(),
|
||||||
|
);
|
||||||
|
|
||||||
|
setBalance(cosmosBalance?.amount!);
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
setTxError(error.message);
|
||||||
|
setIsTxErrorDialogOpen(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
getAccountBalance();
|
||||||
|
}, [account, provider, namespace, cosmosStargateClient, requestedNetwork]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
navigation.setOptions({
|
||||||
|
// eslint-disable-next-line react/no-unstable-nested-components
|
||||||
|
header: ({ options, back }) => {
|
||||||
|
const title = getHeaderTitle(options, 'Approve Transaction');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Appbar.Header>
|
||||||
|
{back && (
|
||||||
|
<Appbar.BackAction
|
||||||
|
onPress={async () => {
|
||||||
|
await rejectRequestHandler();
|
||||||
|
navigation.navigate('Laconic');
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Appbar.Content title={title} />
|
||||||
|
</Appbar.Header>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [navigation, route.name]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const getEthGas = async () => {
|
||||||
|
try {
|
||||||
|
if (!isSufficientFunds || !provider) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await provider.getFeeData();
|
||||||
|
|
||||||
|
setEthMaxFee(data.maxFeePerGas);
|
||||||
|
setEthMaxPriorityFee(data.maxPriorityFeePerGas);
|
||||||
|
setEthGasPrice(data.gasPrice);
|
||||||
|
|
||||||
|
if (transaction.gasLimit) {
|
||||||
|
setEthGasLimit(BigNumber.from(transaction.gasLimit));
|
||||||
|
} else {
|
||||||
|
const transactionObject: Deferrable<providers.TransactionRequest> = {
|
||||||
|
from: transaction.from!,
|
||||||
|
to: transaction.to!,
|
||||||
|
data: transaction.data!,
|
||||||
|
value: transaction.value!,
|
||||||
|
maxFeePerGas: data.maxFeePerGas ?? undefined,
|
||||||
|
maxPriorityFeePerGas: data.maxPriorityFeePerGas ?? undefined,
|
||||||
|
gasPrice: data.maxFeePerGas
|
||||||
|
? undefined
|
||||||
|
: data.gasPrice ?? undefined,
|
||||||
|
};
|
||||||
|
const gasLimit = await provider.estimateGas(transactionObject);
|
||||||
|
setEthGasLimit(gasLimit);
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
setTxError(error.message);
|
||||||
|
setIsTxErrorDialogOpen(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
getEthGas();
|
||||||
|
}, [provider, transaction, isSufficientFunds]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const getCosmosGas = async () => {
|
const getCosmosGas = async () => {
|
||||||
@ -157,9 +444,13 @@ const ApproveTransaction = ({ route }: ApproveTransactionProps) => {
|
|||||||
if (!cosmosStargateClient) {
|
if (!cosmosStargateClient) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const gasEstimation = await cosmosStargateClient!.simulate(
|
if (!isSufficientFunds) {
|
||||||
signer,
|
return;
|
||||||
[transactionMessage],
|
}
|
||||||
|
|
||||||
|
const gasEstimation = await cosmosStargateClient.simulate(
|
||||||
|
transaction.from!,
|
||||||
|
[sendMsg],
|
||||||
MEMO,
|
MEMO,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -171,148 +462,163 @@ const ApproveTransaction = ({ route }: ApproveTransactionProps) => {
|
|||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
setTxError(error.message);
|
setTxError(error.message);
|
||||||
setIsTxErrorDialogOpen(true);
|
setIsTxErrorDialogOpen(true);
|
||||||
const response = formatJsonRpcError(requestEventId, error.message);
|
|
||||||
await web3wallet!.respondSessionRequest({ topic, response });
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
getCosmosGas();
|
getCosmosGas();
|
||||||
}, [
|
}, [cosmosStargateClient, isSufficientFunds, sendMsg, transaction]);
|
||||||
cosmosStargateClient,
|
|
||||||
transactionMessage,
|
|
||||||
requestEventId,
|
|
||||||
topic,
|
|
||||||
web3wallet,
|
|
||||||
signer,
|
|
||||||
]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const gasPrice = GasPrice.fromString(
|
if (balance && !isSufficientFunds) {
|
||||||
requestedNetwork?.gasPrice! + requestedNetwork?.nativeDenom,
|
setTxError('Insufficient funds');
|
||||||
);
|
|
||||||
|
|
||||||
if (!cosmosGasLimit) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const cosmosFees = calculateFee(Number(cosmosGasLimit), gasPrice);
|
|
||||||
|
|
||||||
setFees(cosmosFees.amount[0].amount);
|
|
||||||
}, [namespace, cosmosGasLimit, requestedNetwork]);
|
|
||||||
|
|
||||||
const acceptRequestHandler = async () => {
|
|
||||||
try {
|
|
||||||
setIsRequestAccepted(true);
|
|
||||||
if (!account) {
|
|
||||||
throw new Error('account not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
let options: WalletConnectRequests;
|
|
||||||
|
|
||||||
if (!cosmosStargateClient) {
|
|
||||||
throw new Error('Cosmos stargate client not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
options = {
|
|
||||||
type: 'cosmos_sendTransaction',
|
|
||||||
LaconicClient: cosmosStargateClient,
|
|
||||||
// StdFee object
|
|
||||||
cosmosFee: {
|
|
||||||
// This amount is total fees required for transaction
|
|
||||||
amount: [
|
|
||||||
{
|
|
||||||
amount: fees!,
|
|
||||||
denom: requestedNetwork!.nativeDenom!,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
gas: cosmosGasLimit!,
|
|
||||||
},
|
|
||||||
txMsg: transactionMessage,
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await approveWalletConnectRequest(
|
|
||||||
requestEvent,
|
|
||||||
account,
|
|
||||||
namespace,
|
|
||||||
requestedNetwork!.chainId,
|
|
||||||
options,
|
|
||||||
);
|
|
||||||
|
|
||||||
await web3wallet!.respondSessionRequest({ topic, response });
|
|
||||||
setIsRequestAccepted(false);
|
|
||||||
navigation.navigate('Laconic');
|
|
||||||
} catch (error: any) {
|
|
||||||
setTxError(error.message);
|
|
||||||
setIsTxErrorDialogOpen(true);
|
setIsTxErrorDialogOpen(true);
|
||||||
const response = formatJsonRpcError(requestEventId, error.message);
|
|
||||||
await web3wallet!.respondSessionRequest({ topic, response });
|
|
||||||
}
|
}
|
||||||
};
|
}, [isSufficientFunds, balance]);
|
||||||
|
|
||||||
const rejectRequestHandler = async () => {
|
|
||||||
const response = rejectWalletConnectRequest(requestEvent);
|
|
||||||
await web3wallet!.respondSessionRequest({
|
|
||||||
topic,
|
|
||||||
response,
|
|
||||||
});
|
|
||||||
|
|
||||||
navigation.navigate('Laconic');
|
|
||||||
};
|
|
||||||
|
|
||||||
const replacer = (key: string, value: any): any => {
|
|
||||||
if (value instanceof Uint8Array) {
|
|
||||||
return Buffer.from(value).toString('hex');
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ScrollView contentContainerStyle={styles.approveTransaction}>
|
{isLoading ? (
|
||||||
<View style={styles.dappDetails}>
|
<View style={styles.spinnerContainer}>
|
||||||
{requestIcon && (
|
<ActivityIndicator size="large" color="#0000ff" />
|
||||||
<>
|
|
||||||
{requestIcon.endsWith('.svg') ? (
|
|
||||||
<View style={styles.dappLogo}>
|
|
||||||
<SvgUri height="50" width="50" uri={requestIcon} />
|
|
||||||
</View>
|
|
||||||
) : (
|
|
||||||
<Image style={styles.dappLogo} source={{ uri: requestIcon }} />
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<Text>{requestName}</Text>
|
|
||||||
<Text variant="bodySmall">{requestURL}</Text>
|
|
||||||
</View>
|
|
||||||
<AccountDetails account={account} />
|
|
||||||
<Text variant="bodyLarge" style={styles.transactionLabel}>
|
|
||||||
Message:
|
|
||||||
</Text>
|
|
||||||
<View style={styles.messageBody}>
|
|
||||||
<Text variant="bodyLarge">
|
|
||||||
{JSON.stringify(transactionMessage, replacer, 2)}
|
|
||||||
</Text>
|
|
||||||
</View>
|
</View>
|
||||||
|
) : (
|
||||||
<>
|
<>
|
||||||
<Text variant="bodyLarge" style={styles.transactionLabel}>
|
<ScrollView contentContainerStyle={styles.appContainer}>
|
||||||
Gas Limit:
|
<View style={styles.dappDetails}>
|
||||||
</Text>
|
{requestIcon && (
|
||||||
<TextInput
|
<Image
|
||||||
mode="outlined"
|
style={styles.dappLogo}
|
||||||
style={styles.transactionFeesInput}
|
source={requestIcon ? { uri: requestIcon } : undefined}
|
||||||
value={cosmosGasLimit}
|
/>
|
||||||
onChangeText={value => {
|
)}
|
||||||
if (IS_NUMBER_REGEX.test(value)) {
|
<Text>{requestName}</Text>
|
||||||
setCosmosGasLimit(value);
|
<Text variant="bodyMedium">{requestURL}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.dataBoxContainer}>
|
||||||
|
<Text style={styles.dataBoxLabel}>From</Text>
|
||||||
|
<View style={styles.dataBox}>
|
||||||
|
<AccountDetails account={account} />
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
<DataBox
|
||||||
|
label={`Balance (${
|
||||||
|
namespace === EIP155 ? 'wei' : requestedNetwork!.nativeDenom
|
||||||
|
})`}
|
||||||
|
data={
|
||||||
|
balance === '' || balance === undefined
|
||||||
|
? 'Loading balance...'
|
||||||
|
: `${balance}`
|
||||||
}
|
}
|
||||||
}}
|
/>
|
||||||
/>
|
{transaction && (
|
||||||
|
<View style={styles.approveTransaction}>
|
||||||
|
<DataBox label="To" data={transaction.to!} />
|
||||||
|
<DataBox
|
||||||
|
label={`Amount (${
|
||||||
|
namespace === EIP155 ? 'wei' : requestedNetwork!.nativeDenom
|
||||||
|
})`}
|
||||||
|
data={BigNumber.from(
|
||||||
|
transaction.value?.toString(),
|
||||||
|
).toString()}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{namespace === EIP155 ? (
|
||||||
|
<>
|
||||||
|
{isEIP1559 === false ? (
|
||||||
|
<>
|
||||||
|
<Text style={styles.dataBoxLabel}>
|
||||||
|
{'Gas Price (wei)'}
|
||||||
|
</Text>
|
||||||
|
<TextInput
|
||||||
|
mode="outlined"
|
||||||
|
value={ethGasPrice?.toNumber().toString()}
|
||||||
|
onChangeText={value =>
|
||||||
|
setEthGasPrice(BigNumber.from(value))
|
||||||
|
}
|
||||||
|
style={styles.transactionFeesInput}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Text style={styles.dataBoxLabel}>
|
||||||
|
Max Fee Per Gas (wei)
|
||||||
|
</Text>
|
||||||
|
<TextInput
|
||||||
|
mode="outlined"
|
||||||
|
value={ethMaxFee?.toNumber().toString()}
|
||||||
|
onChangeText={value => {
|
||||||
|
if (IS_NUMBER_REGEX.test(value)) {
|
||||||
|
setEthMaxFee(BigNumber.from(value));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
style={styles.transactionFeesInput}
|
||||||
|
/>
|
||||||
|
<Text style={styles.dataBoxLabel}>
|
||||||
|
Max Priority Fee Per Gas (wei)
|
||||||
|
</Text>
|
||||||
|
<TextInput
|
||||||
|
mode="outlined"
|
||||||
|
value={ethMaxPriorityFee?.toNumber().toString()}
|
||||||
|
onChangeText={value => {
|
||||||
|
if (IS_NUMBER_REGEX.test(value)) {
|
||||||
|
setEthMaxPriorityFee(BigNumber.from(value));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
style={styles.transactionFeesInput}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<Text style={styles.dataBoxLabel}>Gas Limit</Text>
|
||||||
|
<TextInput
|
||||||
|
mode="outlined"
|
||||||
|
value={ethGasLimit?.toNumber().toString()}
|
||||||
|
onChangeText={value => {
|
||||||
|
if (IS_NUMBER_REGEX.test(value)) {
|
||||||
|
setEthGasLimit(BigNumber.from(value));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
style={styles.transactionFeesInput}
|
||||||
|
/>
|
||||||
|
<DataBox
|
||||||
|
label={`${
|
||||||
|
isEIP1559 === true ? 'Max Fee' : 'Gas Fee'
|
||||||
|
} (wei)`}
|
||||||
|
data={fees!}
|
||||||
|
/>
|
||||||
|
<DataBox label="Data" data={transaction.data!} />
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Text style={styles.dataBoxLabel}>{`Fee (${
|
||||||
|
requestedNetwork!.nativeDenom
|
||||||
|
})`}</Text>
|
||||||
|
<TextInput
|
||||||
|
mode="outlined"
|
||||||
|
value={fees}
|
||||||
|
onChangeText={value => setFees(value)}
|
||||||
|
style={styles.transactionFeesInput}
|
||||||
|
/>
|
||||||
|
<Text style={styles.dataBoxLabel}>Gas Limit</Text>
|
||||||
|
<TextInput
|
||||||
|
mode="outlined"
|
||||||
|
value={cosmosGasLimit}
|
||||||
|
onChangeText={value => {
|
||||||
|
if (IS_NUMBER_REGEX.test(value)) {
|
||||||
|
setCosmosGasLimit(value);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</ScrollView>
|
||||||
<View style={styles.buttonContainer}>
|
<View style={styles.buttonContainer}>
|
||||||
<Button
|
<Button
|
||||||
mode="contained"
|
mode="contained"
|
||||||
onPress={acceptRequestHandler}
|
onPress={acceptRequestHandler}
|
||||||
loading={isRequestAccepted}
|
loading={isTxLoading}
|
||||||
disabled={isRequestAccepted}>
|
disabled={!balance || !fees}>
|
||||||
Yes
|
{isTxLoading ? 'Processing' : 'Yes'}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
mode="contained"
|
mode="contained"
|
||||||
@ -322,13 +628,16 @@ const ApproveTransaction = ({ route }: ApproveTransactionProps) => {
|
|||||||
</Button>
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
</>
|
</>
|
||||||
</ScrollView>
|
)}
|
||||||
<TxErrorDialog
|
<TxErrorDialog
|
||||||
error={txError!}
|
error={txError!}
|
||||||
visible={isTxErrorDialogOpen}
|
visible={isTxErrorDialogOpen}
|
||||||
hideDialog={async () => {
|
hideDialog={() => {
|
||||||
setIsTxErrorDialogOpen(false);
|
setIsTxErrorDialogOpen(false);
|
||||||
navigation.navigate('Laconic');
|
if (!isSufficientFunds || !balance || !fees) {
|
||||||
|
rejectRequestHandler();
|
||||||
|
navigation.navigate('Laconic');
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
@ -1,650 +0,0 @@
|
|||||||
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 { providers, BigNumber } from 'ethers';
|
|
||||||
import Config from 'react-native-config';
|
|
||||||
import { Deferrable } from 'ethers/lib/utils';
|
|
||||||
|
|
||||||
import { useNavigation } from '@react-navigation/native';
|
|
||||||
import {
|
|
||||||
NativeStackNavigationProp,
|
|
||||||
NativeStackScreenProps,
|
|
||||||
} from '@react-navigation/native-stack';
|
|
||||||
import { getHeaderTitle } from '@react-navigation/elements';
|
|
||||||
import { DirectSecp256k1Wallet } from '@cosmjs/proto-signing';
|
|
||||||
import {
|
|
||||||
calculateFee,
|
|
||||||
GasPrice,
|
|
||||||
MsgSendEncodeObject,
|
|
||||||
SigningStargateClient,
|
|
||||||
} from '@cosmjs/stargate';
|
|
||||||
|
|
||||||
import { Account, StackParamsList } from '../types';
|
|
||||||
import AccountDetails from '../components/AccountDetails';
|
|
||||||
import styles from '../styles/stylesheet';
|
|
||||||
import { retrieveSingleAccount } from '../utils/accounts';
|
|
||||||
import {
|
|
||||||
approveWalletConnectRequest,
|
|
||||||
rejectWalletConnectRequest,
|
|
||||||
WalletConnectRequests,
|
|
||||||
} from '../utils/wallet-connect/wallet-connect-requests';
|
|
||||||
import DataBox from '../components/DataBox';
|
|
||||||
import { getPathKey } from '../utils/misc';
|
|
||||||
import { useNetworks } from '../context/NetworksContext';
|
|
||||||
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 { useWalletConnect } from '../context/WalletConnectContext';
|
|
||||||
|
|
||||||
export const MEMO = 'Sending signed tx from Laconic Wallet';
|
|
||||||
// Reference: https://ethereum.org/en/developers/docs/gas/#what-is-gas-limit
|
|
||||||
const ETH_MINIMUM_GAS = 21000;
|
|
||||||
|
|
||||||
type SignRequestProps = NativeStackScreenProps<
|
|
||||||
StackParamsList,
|
|
||||||
'ApproveTransfer'
|
|
||||||
>;
|
|
||||||
|
|
||||||
const ApproveTransfer = ({ route }: SignRequestProps) => {
|
|
||||||
const { networksData } = useNetworks();
|
|
||||||
|
|
||||||
const requestSession = route.params.requestSessionData;
|
|
||||||
const requestName = requestSession.peer.metadata.name;
|
|
||||||
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;
|
|
||||||
const requestMethod = requestEvent.params.request.method;
|
|
||||||
|
|
||||||
const { web3wallet } = useWalletConnect();
|
|
||||||
|
|
||||||
const [account, setAccount] = useState<Account>();
|
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
|
||||||
const [balance, setBalance] = useState<string>('');
|
|
||||||
const [isTxLoading, setIsTxLoading] = useState(false);
|
|
||||||
const [cosmosStargateClient, setCosmosStargateClient] =
|
|
||||||
useState<SigningStargateClient>();
|
|
||||||
const [fees, setFees] = useState<string>();
|
|
||||||
const [cosmosGasLimit, setCosmosGasLimit] = useState<string>();
|
|
||||||
const [txError, setTxError] = useState<string>();
|
|
||||||
const [isTxErrorDialogOpen, setIsTxErrorDialogOpen] = useState(false);
|
|
||||||
const [ethGasPrice, setEthGasPrice] = useState<BigNumber | null>();
|
|
||||||
const [ethGasLimit, setEthGasLimit] = useState<BigNumber>();
|
|
||||||
const [ethMaxFee, setEthMaxFee] = useState<BigNumber | null>();
|
|
||||||
const [ethMaxPriorityFee, setEthMaxPriorityFee] =
|
|
||||||
useState<BigNumber | null>();
|
|
||||||
|
|
||||||
const isSufficientFunds = useMemo(() => {
|
|
||||||
if (!transaction.value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!balance) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const amountBigNum = BigNumber.from(String(transaction.value));
|
|
||||||
const balanceBigNum = BigNumber.from(balance);
|
|
||||||
|
|
||||||
if (amountBigNum.gte(balanceBigNum)) {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}, [balance, transaction]);
|
|
||||||
|
|
||||||
const requestedNetwork = networksData.find(
|
|
||||||
networkData =>
|
|
||||||
`${networkData.namespace}:${networkData.chainId}` === chainId,
|
|
||||||
);
|
|
||||||
const namespace = requestedNetwork!.namespace;
|
|
||||||
|
|
||||||
const sendMsg: MsgSendEncodeObject = useMemo(() => {
|
|
||||||
return {
|
|
||||||
typeUrl: '/cosmos.bank.v1beta1.MsgSend',
|
|
||||||
value: {
|
|
||||||
fromAddress: transaction.from,
|
|
||||||
toAddress: transaction.to,
|
|
||||||
amount: [
|
|
||||||
{
|
|
||||||
amount: String(transaction.value),
|
|
||||||
denom: requestedNetwork!.nativeDenom!,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}, [requestedNetwork, transaction]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (namespace !== COSMOS) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const setClient = async () => {
|
|
||||||
if (!account) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const cosmosPrivKey = (
|
|
||||||
await getPathKey(
|
|
||||||
`${requestedNetwork?.namespace}:${requestedNetwork?.chainId}`,
|
|
||||||
account.index,
|
|
||||||
)
|
|
||||||
).privKey;
|
|
||||||
|
|
||||||
const sender = await DirectSecp256k1Wallet.fromKey(
|
|
||||||
Buffer.from(cosmosPrivKey.split('0x')[1], 'hex'),
|
|
||||||
requestedNetwork?.addressPrefix,
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const client = await SigningStargateClient.connectWithSigner(
|
|
||||||
requestedNetwork?.rpcUrl!,
|
|
||||||
sender,
|
|
||||||
);
|
|
||||||
|
|
||||||
setCosmosStargateClient(client);
|
|
||||||
} catch (error: any) {
|
|
||||||
setTxError(error.message);
|
|
||||||
setIsTxErrorDialogOpen(true);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
setClient();
|
|
||||||
}, [account, requestedNetwork, chainId, namespace]);
|
|
||||||
|
|
||||||
const provider = useMemo(() => {
|
|
||||||
if (namespace === EIP155) {
|
|
||||||
if (!requestedNetwork) {
|
|
||||||
throw new Error('Requested chain not supported');
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const ethProvider = new providers.JsonRpcProvider(
|
|
||||||
requestedNetwork.rpcUrl,
|
|
||||||
);
|
|
||||||
|
|
||||||
return ethProvider;
|
|
||||||
} catch (error: any) {
|
|
||||||
setTxError(error.message);
|
|
||||||
setIsTxErrorDialogOpen(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [requestedNetwork, namespace]);
|
|
||||||
|
|
||||||
const navigation =
|
|
||||||
useNavigation<NativeStackNavigationProp<StackParamsList>>();
|
|
||||||
|
|
||||||
const retrieveData = useCallback(
|
|
||||||
async (requestAddress: string) => {
|
|
||||||
const requestAccount = await retrieveSingleAccount(
|
|
||||||
requestedNetwork!.namespace,
|
|
||||||
requestedNetwork!.chainId,
|
|
||||||
requestAddress,
|
|
||||||
);
|
|
||||||
if (!requestAccount) {
|
|
||||||
navigation.navigate('InvalidPath');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setAccount(requestAccount);
|
|
||||||
},
|
|
||||||
[navigation, requestedNetwork],
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// Set loading to false when gas values for requested chain are fetched
|
|
||||||
// If requested chain is EVM compatible, the cosmos gas values will be undefined and vice-versa, hence the condition checks only one of them at the same time
|
|
||||||
if (
|
|
||||||
// If requested chain is EVM compatible, set loading to false when ethMaxFee and ethPriorityFee have been populated
|
|
||||||
(ethMaxFee !== undefined && ethMaxPriorityFee !== undefined) ||
|
|
||||||
// Or if requested chain is a cosmos chain, set loading to false when cosmosGasLimit has been populated
|
|
||||||
cosmosGasLimit !== undefined
|
|
||||||
) {
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
|
||||||
}, [ethMaxFee, ethMaxPriorityFee, cosmosGasLimit]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (namespace === EIP155) {
|
|
||||||
const ethFees = BigNumber.from(ethGasLimit ?? 0)
|
|
||||||
.mul(BigNumber.from(ethMaxFee ?? ethGasPrice ?? 0))
|
|
||||||
.toString();
|
|
||||||
setFees(ethFees);
|
|
||||||
} else {
|
|
||||||
const gasPrice = GasPrice.fromString(
|
|
||||||
requestedNetwork?.gasPrice! + requestedNetwork?.nativeDenom,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!cosmosGasLimit) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const cosmosFees = calculateFee(Number(cosmosGasLimit), gasPrice);
|
|
||||||
|
|
||||||
setFees(cosmosFees.amount[0].amount);
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
transaction,
|
|
||||||
namespace,
|
|
||||||
ethGasLimit,
|
|
||||||
ethGasPrice,
|
|
||||||
cosmosGasLimit,
|
|
||||||
requestedNetwork,
|
|
||||||
ethMaxFee,
|
|
||||||
]);
|
|
||||||
useEffect(() => {
|
|
||||||
retrieveData(transaction.from!);
|
|
||||||
}, [retrieveData, transaction]);
|
|
||||||
|
|
||||||
const isEIP1559 = useMemo(() => {
|
|
||||||
if (cosmosGasLimit) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (ethMaxFee !== null && ethMaxPriorityFee !== null) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}, [cosmosGasLimit, ethMaxFee, ethMaxPriorityFee]);
|
|
||||||
|
|
||||||
const acceptRequestHandler = async () => {
|
|
||||||
setIsTxLoading(true);
|
|
||||||
try {
|
|
||||||
if (!account) {
|
|
||||||
throw new Error('account not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ethGasLimit && ethGasLimit.lt(ETH_MINIMUM_GAS)) {
|
|
||||||
throw new Error(`Atleast ${ETH_MINIMUM_GAS} gas limit is required`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ethMaxFee && ethMaxPriorityFee && ethMaxFee.lte(ethMaxPriorityFee)) {
|
|
||||||
throw new Error(
|
|
||||||
`Max fee per gas (${ethMaxFee.toNumber()}) cannot be lower than or equal to max priority fee per gas (${ethMaxPriorityFee.toNumber()})`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let options: WalletConnectRequests;
|
|
||||||
|
|
||||||
switch (requestMethod) {
|
|
||||||
case EIP155_SIGNING_METHODS.ETH_SEND_TRANSACTION:
|
|
||||||
if (
|
|
||||||
ethMaxFee === undefined ||
|
|
||||||
ethMaxPriorityFee === undefined ||
|
|
||||||
ethGasPrice === undefined
|
|
||||||
) {
|
|
||||||
throw new Error('Gas values not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
options = {
|
|
||||||
type: 'eth_sendTransaction',
|
|
||||||
provider: provider!,
|
|
||||||
ethGasLimit: BigNumber.from(ethGasLimit),
|
|
||||||
ethGasPrice: ethGasPrice ? ethGasPrice.toHexString() : null,
|
|
||||||
maxFeePerGas: ethMaxFee,
|
|
||||||
maxPriorityFeePerGas: ethMaxPriorityFee,
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
case COSMOS_METHODS.COSMOS_SEND_TOKENS:
|
|
||||||
if (!cosmosStargateClient) {
|
|
||||||
throw new Error('Cosmos stargate client not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
options = {
|
|
||||||
type: 'cosmos_sendTokens',
|
|
||||||
signingStargateClient: cosmosStargateClient,
|
|
||||||
// StdFee object
|
|
||||||
cosmosFee: {
|
|
||||||
// This amount is total fees required for transaction
|
|
||||||
amount: [
|
|
||||||
{
|
|
||||||
amount: fees!,
|
|
||||||
denom: requestedNetwork!.nativeDenom!,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
gas: cosmosGasLimit!,
|
|
||||||
},
|
|
||||||
sendMsg,
|
|
||||||
memo: MEMO,
|
|
||||||
};
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new Error('Invalid method');
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await approveWalletConnectRequest(
|
|
||||||
requestEvent,
|
|
||||||
account,
|
|
||||||
namespace,
|
|
||||||
requestedNetwork!.chainId,
|
|
||||||
options,
|
|
||||||
);
|
|
||||||
|
|
||||||
const { topic } = requestEvent;
|
|
||||||
await web3wallet!.respondSessionRequest({ topic, response });
|
|
||||||
navigation.navigate('Laconic');
|
|
||||||
} catch (error: any) {
|
|
||||||
setTxError(error.message);
|
|
||||||
setIsTxErrorDialogOpen(true);
|
|
||||||
}
|
|
||||||
setIsTxLoading(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const rejectRequestHandler = async () => {
|
|
||||||
const response = rejectWalletConnectRequest(requestEvent);
|
|
||||||
const { topic } = requestEvent;
|
|
||||||
await web3wallet!.respondSessionRequest({
|
|
||||||
topic,
|
|
||||||
response,
|
|
||||||
});
|
|
||||||
|
|
||||||
navigation.navigate('Laconic');
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const getAccountBalance = async () => {
|
|
||||||
try {
|
|
||||||
if (!account) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (namespace === EIP155) {
|
|
||||||
if (!provider) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const fetchedBalance = await provider.getBalance(account.address);
|
|
||||||
setBalance(fetchedBalance ? fetchedBalance.toString() : '0');
|
|
||||||
} else {
|
|
||||||
const cosmosBalance = await cosmosStargateClient?.getBalance(
|
|
||||||
account.address,
|
|
||||||
requestedNetwork!.nativeDenom!.toLowerCase(),
|
|
||||||
);
|
|
||||||
|
|
||||||
setBalance(cosmosBalance?.amount!);
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
setTxError(error.message);
|
|
||||||
setIsTxErrorDialogOpen(true);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
getAccountBalance();
|
|
||||||
}, [account, provider, namespace, cosmosStargateClient, requestedNetwork]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
navigation.setOptions({
|
|
||||||
// eslint-disable-next-line react/no-unstable-nested-components
|
|
||||||
header: ({ options, back }) => {
|
|
||||||
const title = getHeaderTitle(options, 'Approve Transaction');
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Appbar.Header>
|
|
||||||
{back && (
|
|
||||||
<Appbar.BackAction
|
|
||||||
onPress={async () => {
|
|
||||||
await rejectRequestHandler();
|
|
||||||
navigation.navigate('Laconic');
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Appbar.Content title={title} />
|
|
||||||
</Appbar.Header>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [navigation, route.name]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const getEthGas = async () => {
|
|
||||||
try {
|
|
||||||
if (!isSufficientFunds || !provider) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await provider.getFeeData();
|
|
||||||
|
|
||||||
setEthMaxFee(data.maxFeePerGas);
|
|
||||||
setEthMaxPriorityFee(data.maxPriorityFeePerGas);
|
|
||||||
setEthGasPrice(data.gasPrice);
|
|
||||||
|
|
||||||
if (transaction.gasLimit) {
|
|
||||||
setEthGasLimit(BigNumber.from(transaction.gasLimit));
|
|
||||||
} else {
|
|
||||||
const transactionObject: Deferrable<providers.TransactionRequest> = {
|
|
||||||
from: transaction.from!,
|
|
||||||
to: transaction.to!,
|
|
||||||
data: transaction.data!,
|
|
||||||
value: transaction.value!,
|
|
||||||
maxFeePerGas: data.maxFeePerGas ?? undefined,
|
|
||||||
maxPriorityFeePerGas: data.maxPriorityFeePerGas ?? undefined,
|
|
||||||
gasPrice: data.maxFeePerGas
|
|
||||||
? undefined
|
|
||||||
: data.gasPrice ?? undefined,
|
|
||||||
};
|
|
||||||
const gasLimit = await provider.estimateGas(transactionObject);
|
|
||||||
setEthGasLimit(gasLimit);
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
setTxError(error.message);
|
|
||||||
setIsTxErrorDialogOpen(true);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
getEthGas();
|
|
||||||
}, [provider, transaction, isSufficientFunds]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const getCosmosGas = async () => {
|
|
||||||
try {
|
|
||||||
if (!cosmosStargateClient) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!isSufficientFunds) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const gasEstimation = await cosmosStargateClient.simulate(
|
|
||||||
transaction.from!,
|
|
||||||
[sendMsg],
|
|
||||||
MEMO,
|
|
||||||
);
|
|
||||||
|
|
||||||
setCosmosGasLimit(
|
|
||||||
String(
|
|
||||||
Math.round(gasEstimation * Number(Config.DEFAULT_GAS_ADJUSTMENT)),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} catch (error: any) {
|
|
||||||
setTxError(error.message);
|
|
||||||
setIsTxErrorDialogOpen(true);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
getCosmosGas();
|
|
||||||
}, [cosmosStargateClient, isSufficientFunds, sendMsg, transaction]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (balance && !isSufficientFunds) {
|
|
||||||
setTxError('Insufficient funds');
|
|
||||||
setIsTxErrorDialogOpen(true);
|
|
||||||
}
|
|
||||||
}, [isSufficientFunds, balance]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{isLoading ? (
|
|
||||||
<View style={styles.spinnerContainer}>
|
|
||||||
<ActivityIndicator size="large" color="#0000ff" />
|
|
||||||
</View>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<ScrollView contentContainerStyle={styles.appContainer}>
|
|
||||||
<View style={styles.dappDetails}>
|
|
||||||
{requestIcon && (
|
|
||||||
<Image
|
|
||||||
style={styles.dappLogo}
|
|
||||||
source={requestIcon ? { uri: requestIcon } : undefined}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Text>{requestName}</Text>
|
|
||||||
<Text variant="bodyMedium">{requestURL}</Text>
|
|
||||||
</View>
|
|
||||||
<View style={styles.dataBoxContainer}>
|
|
||||||
<Text style={styles.dataBoxLabel}>From</Text>
|
|
||||||
<View style={styles.dataBox}>
|
|
||||||
<AccountDetails account={account} />
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
<DataBox
|
|
||||||
label={`Balance (${
|
|
||||||
namespace === EIP155 ? 'wei' : requestedNetwork!.nativeDenom
|
|
||||||
})`}
|
|
||||||
data={
|
|
||||||
balance === '' || balance === undefined
|
|
||||||
? 'Loading balance...'
|
|
||||||
: `${balance}`
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
{transaction && (
|
|
||||||
<View style={styles.approveTransfer}>
|
|
||||||
<DataBox label="To" data={transaction.to!} />
|
|
||||||
<DataBox
|
|
||||||
label={`Amount (${
|
|
||||||
namespace === EIP155 ? 'wei' : requestedNetwork!.nativeDenom
|
|
||||||
})`}
|
|
||||||
data={BigNumber.from(
|
|
||||||
transaction.value?.toString(),
|
|
||||||
).toString()}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{namespace === EIP155 ? (
|
|
||||||
<>
|
|
||||||
{isEIP1559 === false ? (
|
|
||||||
<>
|
|
||||||
<Text style={styles.dataBoxLabel}>
|
|
||||||
{'Gas Price (wei)'}
|
|
||||||
</Text>
|
|
||||||
<TextInput
|
|
||||||
mode="outlined"
|
|
||||||
value={ethGasPrice?.toNumber().toString()}
|
|
||||||
onChangeText={value =>
|
|
||||||
setEthGasPrice(BigNumber.from(value))
|
|
||||||
}
|
|
||||||
style={styles.transactionFeesInput}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Text style={styles.dataBoxLabel}>
|
|
||||||
Max Fee Per Gas (wei)
|
|
||||||
</Text>
|
|
||||||
<TextInput
|
|
||||||
mode="outlined"
|
|
||||||
value={ethMaxFee?.toNumber().toString()}
|
|
||||||
onChangeText={value => {
|
|
||||||
if (IS_NUMBER_REGEX.test(value)) {
|
|
||||||
setEthMaxFee(BigNumber.from(value));
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
style={styles.transactionFeesInput}
|
|
||||||
/>
|
|
||||||
<Text style={styles.dataBoxLabel}>
|
|
||||||
Max Priority Fee Per Gas (wei)
|
|
||||||
</Text>
|
|
||||||
<TextInput
|
|
||||||
mode="outlined"
|
|
||||||
value={ethMaxPriorityFee?.toNumber().toString()}
|
|
||||||
onChangeText={value => {
|
|
||||||
if (IS_NUMBER_REGEX.test(value)) {
|
|
||||||
setEthMaxPriorityFee(BigNumber.from(value));
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
style={styles.transactionFeesInput}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<Text style={styles.dataBoxLabel}>Gas Limit</Text>
|
|
||||||
<TextInput
|
|
||||||
mode="outlined"
|
|
||||||
value={ethGasLimit?.toNumber().toString()}
|
|
||||||
onChangeText={value => {
|
|
||||||
if (IS_NUMBER_REGEX.test(value)) {
|
|
||||||
setEthGasLimit(BigNumber.from(value));
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
style={styles.transactionFeesInput}
|
|
||||||
/>
|
|
||||||
<DataBox
|
|
||||||
label={`${
|
|
||||||
isEIP1559 === true ? 'Max Fee' : 'Gas Fee'
|
|
||||||
} (wei)`}
|
|
||||||
data={fees!}
|
|
||||||
/>
|
|
||||||
<DataBox label="Data" data={transaction.data!} />
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Text style={styles.dataBoxLabel}>{`Fee (${
|
|
||||||
requestedNetwork!.nativeDenom
|
|
||||||
})`}</Text>
|
|
||||||
<TextInput
|
|
||||||
mode="outlined"
|
|
||||||
value={fees}
|
|
||||||
onChangeText={value => setFees(value)}
|
|
||||||
style={styles.transactionFeesInput}
|
|
||||||
/>
|
|
||||||
<Text style={styles.dataBoxLabel}>Gas Limit</Text>
|
|
||||||
<TextInput
|
|
||||||
mode="outlined"
|
|
||||||
value={cosmosGasLimit}
|
|
||||||
onChangeText={value => {
|
|
||||||
if (IS_NUMBER_REGEX.test(value)) {
|
|
||||||
setCosmosGasLimit(value);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</ScrollView>
|
|
||||||
<View style={styles.buttonContainer}>
|
|
||||||
<Button
|
|
||||||
mode="contained"
|
|
||||||
onPress={acceptRequestHandler}
|
|
||||||
loading={isTxLoading}
|
|
||||||
disabled={!balance || !fees}>
|
|
||||||
{isTxLoading ? 'Processing' : 'Yes'}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
mode="contained"
|
|
||||||
onPress={rejectRequestHandler}
|
|
||||||
buttonColor="#B82B0D">
|
|
||||||
No
|
|
||||||
</Button>
|
|
||||||
</View>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<TxErrorDialog
|
|
||||||
error={txError!}
|
|
||||||
visible={isTxErrorDialogOpen}
|
|
||||||
hideDialog={() => {
|
|
||||||
setIsTxErrorDialogOpen(false);
|
|
||||||
if (!isSufficientFunds || !balance || !fees) {
|
|
||||||
rejectRequestHandler();
|
|
||||||
navigation.navigate('Laconic');
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ApproveTransfer;
|
|
@ -16,6 +16,7 @@ import styles from '../styles/stylesheet';
|
|||||||
import { useAccounts } from '../context/AccountsContext';
|
import { useAccounts } from '../context/AccountsContext';
|
||||||
import { useWalletConnect } from '../context/WalletConnectContext';
|
import { useWalletConnect } from '../context/WalletConnectContext';
|
||||||
import { NetworksDataState, StackParamsList } from '../types';
|
import { NetworksDataState, StackParamsList } from '../types';
|
||||||
|
import { web3wallet } from '../utils/wallet-connect/WalletConnectUtils';
|
||||||
import { useNetworks } from '../context/NetworksContext';
|
import { useNetworks } from '../context/NetworksContext';
|
||||||
|
|
||||||
const WCLogo = () => {
|
const WCLogo = () => {
|
||||||
@ -28,11 +29,12 @@ const WCLogo = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const HomeScreen = () => {
|
const HomeScreen = () => {
|
||||||
const { accounts, setAccounts, setCurrentIndex } = useAccounts();
|
const { accounts, setAccounts, currentIndex, setCurrentIndex } =
|
||||||
|
useAccounts();
|
||||||
|
|
||||||
const { networksData, selectedNetwork, setSelectedNetwork, setNetworksData } =
|
const { networksData, selectedNetwork, setSelectedNetwork, setNetworksData } =
|
||||||
useNetworks();
|
useNetworks();
|
||||||
const { web3wallet, setActiveSessions } = useWalletConnect();
|
const { setActiveSessions } = useWalletConnect();
|
||||||
|
|
||||||
const navigation =
|
const navigation =
|
||||||
useNavigation<NativeStackNavigationProp<StackParamsList>>();
|
useNavigation<NativeStackNavigationProp<StackParamsList>>();
|
||||||
@ -109,7 +111,6 @@ const HomeScreen = () => {
|
|||||||
|
|
||||||
hideResetDialog();
|
hideResetDialog();
|
||||||
}, [
|
}, [
|
||||||
web3wallet,
|
|
||||||
setAccounts,
|
setAccounts,
|
||||||
setActiveSessions,
|
setActiveSessions,
|
||||||
setCurrentIndex,
|
setCurrentIndex,
|
||||||
@ -122,6 +123,10 @@ const HomeScreen = () => {
|
|||||||
setCurrentIndex(0);
|
setCurrentIndex(0);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updateIndex = (index: number) => {
|
||||||
|
setCurrentIndex(index);
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchAccounts();
|
fetchAccounts();
|
||||||
}, [networksData, setAccounts, selectedNetwork, fetchAccounts]);
|
}, [networksData, setAccounts, selectedNetwork, fetchAccounts]);
|
||||||
@ -137,7 +142,7 @@ const HomeScreen = () => {
|
|||||||
<>
|
<>
|
||||||
<NetworkDropdown updateNetwork={updateNetwork} />
|
<NetworkDropdown updateNetwork={updateNetwork} />
|
||||||
<View style={styles.accountComponent}>
|
<View style={styles.accountComponent}>
|
||||||
<Accounts />
|
<Accounts currentIndex={currentIndex} updateIndex={updateIndex} />
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.resetContainer}>
|
<View style={styles.resetContainer}>
|
||||||
<Button
|
<Button
|
||||||
|
@ -20,10 +20,10 @@ import {
|
|||||||
rejectWalletConnectRequest,
|
rejectWalletConnectRequest,
|
||||||
WalletConnectRequests,
|
WalletConnectRequests,
|
||||||
} from '../utils/wallet-connect/wallet-connect-requests';
|
} from '../utils/wallet-connect/wallet-connect-requests';
|
||||||
|
import { web3wallet } from '../utils/wallet-connect/WalletConnectUtils';
|
||||||
import { EIP155_SIGNING_METHODS } from '../utils/wallet-connect/EIP155Data';
|
import { EIP155_SIGNING_METHODS } from '../utils/wallet-connect/EIP155Data';
|
||||||
import { useNetworks } from '../context/NetworksContext';
|
import { useNetworks } from '../context/NetworksContext';
|
||||||
import { COSMOS_METHODS } from '../utils/wallet-connect/COSMOSData';
|
import { COSMOS_METHODS } from '../utils/wallet-connect/COSMOSData';
|
||||||
import { useWalletConnect } from '../context/WalletConnectContext';
|
|
||||||
|
|
||||||
type SignRequestProps = NativeStackScreenProps<StackParamsList, 'SignRequest'>;
|
type SignRequestProps = NativeStackScreenProps<StackParamsList, 'SignRequest'>;
|
||||||
|
|
||||||
@ -35,8 +35,6 @@ const SignRequest = ({ route }: SignRequestProps) => {
|
|||||||
const requestIcon = requestSession?.peer?.metadata?.icons[0];
|
const requestIcon = requestSession?.peer?.metadata?.icons[0];
|
||||||
const requestURL = requestSession?.peer?.metadata?.url;
|
const requestURL = requestSession?.peer?.metadata?.url;
|
||||||
|
|
||||||
const { web3wallet } = useWalletConnect();
|
|
||||||
|
|
||||||
const [account, setAccount] = useState<Account>();
|
const [account, setAccount] = useState<Account>();
|
||||||
const [message, setMessage] = useState<string>('');
|
const [message, setMessage] = useState<string>('');
|
||||||
const [namespace, setNamespace] = useState<string>('');
|
const [namespace, setNamespace] = useState<string>('');
|
||||||
@ -88,13 +86,21 @@ const SignRequest = ({ route }: SignRequestProps) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setAccount(requestAccount);
|
if (requestAccount !== account) {
|
||||||
setMessage(decodeURIComponent(requestMessage));
|
setAccount(requestAccount);
|
||||||
setNamespace(requestNamespace);
|
}
|
||||||
setChainId(requestChainId);
|
if (requestMessage !== message) {
|
||||||
|
setMessage(decodeURIComponent(requestMessage));
|
||||||
|
}
|
||||||
|
if (requestNamespace !== namespace) {
|
||||||
|
setNamespace(requestNamespace);
|
||||||
|
}
|
||||||
|
if (requestChainId !== chainId) {
|
||||||
|
setChainId(requestChainId);
|
||||||
|
}
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
},
|
},
|
||||||
[navigation],
|
[account, message, navigation, namespace, chainId],
|
||||||
);
|
);
|
||||||
|
|
||||||
const sanitizePath = useCallback(
|
const sanitizePath = useCallback(
|
||||||
|
@ -6,10 +6,11 @@ import { SvgUri } from 'react-native-svg';
|
|||||||
import { getSdkError } from '@walletconnect/utils';
|
import { getSdkError } from '@walletconnect/utils';
|
||||||
|
|
||||||
import { useWalletConnect } from '../context/WalletConnectContext';
|
import { useWalletConnect } from '../context/WalletConnectContext';
|
||||||
|
import { web3wallet } from '../utils/wallet-connect/WalletConnectUtils';
|
||||||
import styles from '../styles/stylesheet';
|
import styles from '../styles/stylesheet';
|
||||||
|
|
||||||
export default function WalletConnect() {
|
export default function WalletConnect() {
|
||||||
const { web3wallet, activeSessions, setActiveSessions } = useWalletConnect();
|
const { activeSessions, setActiveSessions } = useWalletConnect();
|
||||||
|
|
||||||
const disconnect = async (sessionId: string) => {
|
const disconnect = async (sessionId: string) => {
|
||||||
await web3wallet!.disconnectSession({
|
await web3wallet!.disconnectSession({
|
||||||
@ -22,9 +23,9 @@ export default function WalletConnect() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const sessions = web3wallet?.getActiveSessions() || {};
|
const sessions = web3wallet!.getActiveSessions();
|
||||||
setActiveSessions(sessions);
|
setActiveSessions(sessions);
|
||||||
}, [web3wallet, setActiveSessions]);
|
}, [setActiveSessions]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View>
|
<View>
|
||||||
|
@ -136,7 +136,7 @@ const styles = StyleSheet.create({
|
|||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
padding: 8,
|
padding: 8,
|
||||||
},
|
},
|
||||||
approveTransfer: {
|
approveTransaction: {
|
||||||
height: '40%',
|
height: '40%',
|
||||||
marginBottom: 30,
|
marginBottom: 30,
|
||||||
},
|
},
|
||||||
@ -213,10 +213,10 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
messageBody: {
|
messageBody: {
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderRadius: 6,
|
borderRadius: 10,
|
||||||
paddingVertical: 10,
|
paddingVertical: 10,
|
||||||
paddingHorizontal: 10,
|
paddingHorizontal: 10,
|
||||||
marginVertical: 3,
|
marginVertical: 20,
|
||||||
},
|
},
|
||||||
cameraContainer: {
|
cameraContainer: {
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
@ -272,16 +272,6 @@ const styles = StyleSheet.create({
|
|||||||
marginVertical: 10,
|
marginVertical: 10,
|
||||||
},
|
},
|
||||||
transactionFeesInput: { marginBottom: 10 },
|
transactionFeesInput: { marginBottom: 10 },
|
||||||
approveTransaction: {
|
|
||||||
flexGrow: 1,
|
|
||||||
marginTop: 0,
|
|
||||||
paddingHorizontal: 24,
|
|
||||||
paddingVertical: 5,
|
|
||||||
},
|
|
||||||
transactionLabel: {
|
|
||||||
fontWeight: '700',
|
|
||||||
padding: 8,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default styles;
|
export default styles;
|
||||||
|
19
src/types.ts
19
src/types.ts
@ -1,8 +1,7 @@
|
|||||||
import { PopulatedTransaction } from 'ethers';
|
import { PopulatedTransaction } from 'ethers';
|
||||||
|
|
||||||
import { SignClientTypes, SessionTypes } from '@walletconnect/types';
|
import { SignClientTypes, SessionTypes } from '@walletconnect/types';
|
||||||
import { IWeb3Wallet, Web3WalletTypes } from '@walletconnect/web3wallet';
|
import { Web3WalletTypes } from '@walletconnect/web3wallet';
|
||||||
import { EncodeObject } from '@cosmjs/proto-signing';
|
|
||||||
|
|
||||||
export type StackParamsList = {
|
export type StackParamsList = {
|
||||||
Laconic: undefined;
|
Laconic: undefined;
|
||||||
@ -18,7 +17,7 @@ export type StackParamsList = {
|
|||||||
requestEvent?: Web3WalletTypes.SessionRequest;
|
requestEvent?: Web3WalletTypes.SessionRequest;
|
||||||
requestSessionData?: SessionTypes.Struct;
|
requestSessionData?: SessionTypes.Struct;
|
||||||
};
|
};
|
||||||
ApproveTransfer: {
|
ApproveTransaction: {
|
||||||
transaction: PopulatedTransaction;
|
transaction: PopulatedTransaction;
|
||||||
requestEvent: Web3WalletTypes.SessionRequest;
|
requestEvent: Web3WalletTypes.SessionRequest;
|
||||||
requestSessionData: SessionTypes.Struct;
|
requestSessionData: SessionTypes.Struct;
|
||||||
@ -30,12 +29,6 @@ export type StackParamsList = {
|
|||||||
EditNetwork: {
|
EditNetwork: {
|
||||||
selectedNetwork: NetworksDataState;
|
selectedNetwork: NetworksDataState;
|
||||||
};
|
};
|
||||||
ApproveTransaction: {
|
|
||||||
transactionMessage: EncodeObject;
|
|
||||||
signer: string;
|
|
||||||
requestEvent: Web3WalletTypes.SessionRequest;
|
|
||||||
requestSessionData: SessionTypes.Struct;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Account = {
|
export type Account = {
|
||||||
@ -45,6 +38,11 @@ export type Account = {
|
|||||||
hdPath: string;
|
hdPath: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type AccountsProps = {
|
||||||
|
currentIndex: number;
|
||||||
|
updateIndex: (index: number) => void;
|
||||||
|
};
|
||||||
|
|
||||||
export type NetworkDropdownProps = {
|
export type NetworkDropdownProps = {
|
||||||
updateNetwork: (networksData: NetworksDataState) => void;
|
updateNetwork: (networksData: NetworksDataState) => void;
|
||||||
};
|
};
|
||||||
@ -90,6 +88,7 @@ export type HDPathDialogProps = {
|
|||||||
pathCode: string;
|
pathCode: string;
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
hideDialog: () => void;
|
hideDialog: () => void;
|
||||||
|
updateIndex: (index: number) => void;
|
||||||
updateAccounts: (account: Account) => void;
|
updateAccounts: (account: Account) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -127,6 +126,4 @@ export interface WalletConnectContextProps {
|
|||||||
setActiveSessions: (
|
setActiveSessions: (
|
||||||
activeSessions: Record<string, SessionTypes.Struct>,
|
activeSessions: Record<string, SessionTypes.Struct>,
|
||||||
) => void;
|
) => void;
|
||||||
web3wallet: IWeb3Wallet | undefined;
|
|
||||||
setWeb3wallet: React.Dispatch<React.SetStateAction<IWeb3Wallet | undefined>>;
|
|
||||||
}
|
}
|
||||||
|
@ -1,22 +1,9 @@
|
|||||||
import Config from 'react-native-config';
|
|
||||||
import { COSMOS_TESTNET_CHAINS } from './wallet-connect/COSMOSData';
|
import { COSMOS_TESTNET_CHAINS } from './wallet-connect/COSMOSData';
|
||||||
import { EIP155_CHAINS } from './wallet-connect/EIP155Data';
|
import { EIP155_CHAINS } from './wallet-connect/EIP155Data';
|
||||||
|
|
||||||
export const EIP155 = 'eip155';
|
export const EIP155 = 'eip155';
|
||||||
export const COSMOS = 'cosmos';
|
export const COSMOS = 'cosmos';
|
||||||
export const DEFAULT_NETWORKS = [
|
export const DEFAULT_NETWORKS = [
|
||||||
{
|
|
||||||
chainId: 'laconic_9000-1',
|
|
||||||
networkName: 'laconicd',
|
|
||||||
namespace: COSMOS,
|
|
||||||
rpcUrl: Config.LACONICD_RPC_URL,
|
|
||||||
blockExplorerUrl: '',
|
|
||||||
nativeDenom: 'alnt',
|
|
||||||
addressPrefix: 'laconic',
|
|
||||||
coinType: '118',
|
|
||||||
gasPrice: '1',
|
|
||||||
isDefault: true,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
chainId: '1',
|
chainId: '1',
|
||||||
networkName: EIP155_CHAINS['eip155:1'].name,
|
networkName: EIP155_CHAINS['eip155:1'].name,
|
||||||
|
@ -38,5 +38,4 @@ export const COSMOS_SIGNING_METHODS = {
|
|||||||
export const COSMOS_METHODS = {
|
export const COSMOS_METHODS = {
|
||||||
...COSMOS_SIGNING_METHODS,
|
...COSMOS_SIGNING_METHODS,
|
||||||
COSMOS_SEND_TOKENS: 'cosmos_sendTokens', // Added for pay.laconic.com
|
COSMOS_SEND_TOKENS: 'cosmos_sendTokens', // Added for pay.laconic.com
|
||||||
COSMOS_SEND_TRANSACTION: 'cosmos_sendTransaction', // Added for testnet onboarding app
|
|
||||||
};
|
};
|
||||||
|
@ -6,6 +6,7 @@ import { Core } from '@walletconnect/core';
|
|||||||
import { ICore } from '@walletconnect/types';
|
import { ICore } from '@walletconnect/types';
|
||||||
import { Web3Wallet, IWeb3Wallet } from '@walletconnect/web3wallet';
|
import { Web3Wallet, IWeb3Wallet } from '@walletconnect/web3wallet';
|
||||||
|
|
||||||
|
export let web3wallet: IWeb3Wallet | undefined;
|
||||||
export let core: ICore;
|
export let core: ICore;
|
||||||
|
|
||||||
export async function createWeb3Wallet() {
|
export async function createWeb3Wallet() {
|
||||||
@ -13,7 +14,7 @@ export async function createWeb3Wallet() {
|
|||||||
projectId: Config.WALLET_CONNECT_PROJECT_ID,
|
projectId: Config.WALLET_CONNECT_PROJECT_ID,
|
||||||
});
|
});
|
||||||
|
|
||||||
const web3wallet = await Web3Wallet.init({
|
web3wallet = await Web3Wallet.init({
|
||||||
core,
|
core,
|
||||||
metadata: {
|
metadata: {
|
||||||
name: 'Laconic Wallet',
|
name: 'Laconic Wallet',
|
||||||
@ -22,14 +23,9 @@ export async function createWeb3Wallet() {
|
|||||||
icons: ['https://avatars.githubusercontent.com/u/92608123'],
|
icons: ['https://avatars.githubusercontent.com/u/92608123'],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return web3wallet;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function web3WalletPair(
|
export async function web3WalletPair(params: { uri: string }) {
|
||||||
web3wallet: IWeb3Wallet,
|
|
||||||
params: { uri: string },
|
|
||||||
) {
|
|
||||||
if (web3wallet) {
|
if (web3wallet) {
|
||||||
return await web3wallet.core.pairing.pair({ uri: params.uri });
|
return await web3wallet.core.pairing.pair({ uri: params.uri });
|
||||||
}
|
}
|
||||||
|
@ -40,6 +40,7 @@ export const getNamespaces = async (
|
|||||||
networksData: NetworksDataState[],
|
networksData: NetworksDataState[],
|
||||||
selectedNetwork: NetworksDataState,
|
selectedNetwork: NetworksDataState,
|
||||||
accounts: Account[],
|
accounts: Account[],
|
||||||
|
currentIndex: number,
|
||||||
) => {
|
) => {
|
||||||
const namespaceChainId = `${selectedNetwork.namespace}:${selectedNetwork.chainId}`;
|
const namespaceChainId = `${selectedNetwork.namespace}:${selectedNetwork.chainId}`;
|
||||||
|
|
||||||
@ -100,6 +101,23 @@ export const getNamespaces = async (
|
|||||||
const requiredAddressesArray = await Promise.all(requiredAddressesPromise);
|
const requiredAddressesArray = await Promise.all(requiredAddressesPromise);
|
||||||
const requiredAddresses = requiredAddressesArray.flat();
|
const requiredAddresses = requiredAddressesArray.flat();
|
||||||
|
|
||||||
|
let sortedAccounts = requiredAddresses;
|
||||||
|
|
||||||
|
// If selected network is included in chains requested from dApp,
|
||||||
|
// Put selected account as first account
|
||||||
|
if (walletConnectChains.includes(namespaceChainId)) {
|
||||||
|
const currentAddresses = requiredAddresses.filter(address =>
|
||||||
|
address.includes(namespaceChainId),
|
||||||
|
);
|
||||||
|
sortedAccounts = [
|
||||||
|
currentAddresses[currentIndex],
|
||||||
|
...currentAddresses.filter((address, index) => index !== currentIndex),
|
||||||
|
...requiredAddresses.filter(
|
||||||
|
address => !currentAddresses.includes(address),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
// construct namespace object
|
// construct namespace object
|
||||||
const newNamespaces = {
|
const newNamespaces = {
|
||||||
eip155: {
|
eip155: {
|
||||||
@ -115,7 +133,7 @@ export const getNamespaces = async (
|
|||||||
...(optionalNamespaces.eip155?.events ?? []),
|
...(optionalNamespaces.eip155?.events ?? []),
|
||||||
...(requiredNamespaces.eip155?.events ?? []),
|
...(requiredNamespaces.eip155?.events ?? []),
|
||||||
],
|
],
|
||||||
accounts: requiredAddresses.filter(account => account.includes(EIP155)),
|
accounts: sortedAccounts.filter(account => account.includes(EIP155)),
|
||||||
},
|
},
|
||||||
cosmos: {
|
cosmos: {
|
||||||
chains: walletConnectChains.filter(chain => chain.includes(COSMOS)),
|
chains: walletConnectChains.filter(chain => chain.includes(COSMOS)),
|
||||||
@ -129,12 +147,18 @@ export const getNamespaces = async (
|
|||||||
...(optionalNamespaces.cosmos?.events ?? []),
|
...(optionalNamespaces.cosmos?.events ?? []),
|
||||||
...(requiredNamespaces.cosmos?.events ?? []),
|
...(requiredNamespaces.cosmos?.events ?? []),
|
||||||
],
|
],
|
||||||
accounts: requiredAddresses.filter(account => account.includes(COSMOS)),
|
accounts: sortedAccounts.filter(account => account.includes(COSMOS)),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return newNamespaces;
|
return newNamespaces;
|
||||||
} else {
|
} else {
|
||||||
|
// Set selected account as the first account in supported namespaces
|
||||||
|
const sortedAccounts = [
|
||||||
|
accounts[currentIndex],
|
||||||
|
...accounts.filter((account, index) => index !== currentIndex),
|
||||||
|
];
|
||||||
|
|
||||||
switch (selectedNetwork.namespace) {
|
switch (selectedNetwork.namespace) {
|
||||||
case EIP155:
|
case EIP155:
|
||||||
return {
|
return {
|
||||||
@ -151,7 +175,7 @@ export const getNamespaces = async (
|
|||||||
...(optionalNamespaces.eip155?.events ?? []),
|
...(optionalNamespaces.eip155?.events ?? []),
|
||||||
...(requiredNamespaces.eip155?.events ?? []),
|
...(requiredNamespaces.eip155?.events ?? []),
|
||||||
],
|
],
|
||||||
accounts: accounts.map(ethAccount => {
|
accounts: sortedAccounts.map(ethAccount => {
|
||||||
return `${namespaceChainId}:${ethAccount.address}`;
|
return `${namespaceChainId}:${ethAccount.address}`;
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
@ -176,7 +200,7 @@ export const getNamespaces = async (
|
|||||||
...(optionalNamespaces.cosmos?.events ?? []),
|
...(optionalNamespaces.cosmos?.events ?? []),
|
||||||
...(requiredNamespaces.cosmos?.events ?? []),
|
...(requiredNamespaces.cosmos?.events ?? []),
|
||||||
],
|
],
|
||||||
accounts: accounts.map(cosmosAccount => {
|
accounts: sortedAccounts.map(cosmosAccount => {
|
||||||
return `${namespaceChainId}:${cosmosAccount.address}`;
|
return `${namespaceChainId}:${cosmosAccount.address}`;
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
@ -9,8 +9,6 @@ import {
|
|||||||
StdFee,
|
StdFee,
|
||||||
MsgSendEncodeObject,
|
MsgSendEncodeObject,
|
||||||
} from '@cosmjs/stargate';
|
} from '@cosmjs/stargate';
|
||||||
import { EncodeObject } from '@cosmjs/proto-signing';
|
|
||||||
import { LaconicClient } from '@cerc-io/registry-sdk';
|
|
||||||
|
|
||||||
import { EIP155_SIGNING_METHODS } from './EIP155Data';
|
import { EIP155_SIGNING_METHODS } from './EIP155Data';
|
||||||
import { signDirectMessage, signEthMessage } from '../sign-message';
|
import { signDirectMessage, signEthMessage } from '../sign-message';
|
||||||
@ -51,20 +49,12 @@ interface CosmosSendTokens {
|
|||||||
memo: string;
|
memo: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CosmosSendTransaction {
|
|
||||||
type: 'cosmos_sendTransaction';
|
|
||||||
LaconicClient: LaconicClient;
|
|
||||||
cosmosFee: StdFee;
|
|
||||||
txMsg: EncodeObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type WalletConnectRequests =
|
export type WalletConnectRequests =
|
||||||
| EthSendTransaction
|
| EthSendTransaction
|
||||||
| EthPersonalSign
|
| EthPersonalSign
|
||||||
| CosmosSignDirect
|
| CosmosSignDirect
|
||||||
| CosmosSignAmino
|
| CosmosSignAmino
|
||||||
| CosmosSendTokens
|
| CosmosSendTokens;
|
||||||
| CosmosSendTransaction;
|
|
||||||
|
|
||||||
export async function approveWalletConnectRequest(
|
export async function approveWalletConnectRequest(
|
||||||
requestEvent: SignClientTypes.EventArguments['session_request'],
|
requestEvent: SignClientTypes.EventArguments['session_request'],
|
||||||
@ -191,20 +181,6 @@ export async function approveWalletConnectRequest(
|
|||||||
signature: result.transactionHash,
|
signature: result.transactionHash,
|
||||||
});
|
});
|
||||||
|
|
||||||
case COSMOS_METHODS.COSMOS_SEND_TRANSACTION:
|
|
||||||
if (!(options.type === 'cosmos_sendTransaction')) {
|
|
||||||
throw new Error('Incorrect parameters passed');
|
|
||||||
}
|
|
||||||
const resultFromTx = await options.LaconicClient.signAndBroadcast(
|
|
||||||
address,
|
|
||||||
[options.txMsg],
|
|
||||||
options.cosmosFee,
|
|
||||||
);
|
|
||||||
|
|
||||||
return formatJsonRpcResult(id, {
|
|
||||||
code: resultFromTx.code,
|
|
||||||
});
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new Error(getSdkError('INVALID_METHOD').message);
|
throw new Error(getSdkError('INVALID_METHOD').message);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user