forked from cerc-io/laconic-wallet
Connect wallet to a dapp using WalletConnect (#38)
* Connect with dapp using WalletConnect * Pair dapp with wallet * Sign message taken from dapp and return the signature * Add todos * Move wallet connect functions to seperate screen * Change ui * Change ui for wc modals * Add styles * Remove border radius at the bottom * Make review changes * Add dependancy to useEffect * Move pairing modal methods --------- Co-authored-by: Adw8 <adwait@deepstacksoft.com>
This commit is contained in:
parent
21b749d9a4
commit
150f10b91f
73
App.tsx
73
App.tsx
@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { SignClientTypes } from '@walletconnect/types';
|
||||
import { NavigationContainer } from '@react-navigation/native';
|
||||
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
||||
|
||||
@ -8,12 +9,62 @@ import HomeScreen from './components/HomeScreen';
|
||||
import SignRequest from './components/SignRequest';
|
||||
import InvalidPath from './components/InvalidPath';
|
||||
import QRScanner from './components/QRScanner';
|
||||
import PairingModal from './components/PairingModal';
|
||||
import SignModal from './components/SignModal';
|
||||
import WalletConnect from './components/WalletConnect';
|
||||
|
||||
import { StackParamsList } from './types';
|
||||
import useInitialization, {
|
||||
web3wallet,
|
||||
} from './utils/wallet-connect/WalletConnectUtils';
|
||||
import { EIP155_SIGNING_METHODS } from './utils/wallet-connect/EIP155Lib';
|
||||
|
||||
const Stack = createNativeStackNavigator<StackParamsList>();
|
||||
|
||||
const App = (): React.JSX.Element => {
|
||||
const [modalVisible, setModalVisible] = useState(false);
|
||||
//TODO: Remove any
|
||||
const [currentProposal, setCurrentProposal] = useState<
|
||||
SignClientTypes.EventArguments['session_proposal'] | undefined
|
||||
>();
|
||||
const [requestSession, setRequestSession] = useState<any>();
|
||||
const [requestEventData, setRequestEventData] = useState<any>();
|
||||
const [signModalVisible, setSignModalVisible] = useState(false);
|
||||
|
||||
useInitialization();
|
||||
|
||||
const onSessionProposal = useCallback(
|
||||
(proposal: SignClientTypes.EventArguments['session_proposal']) => {
|
||||
setModalVisible(true);
|
||||
setCurrentProposal(proposal);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const onSessionRequest = useCallback(
|
||||
async (requestEvent: SignClientTypes.EventArguments['session_request']) => {
|
||||
const { topic, params } = requestEvent;
|
||||
const { request } = params;
|
||||
const requestSessionData =
|
||||
web3wallet.engine.signClient.session.get(topic);
|
||||
|
||||
switch (request.method) {
|
||||
case EIP155_SIGNING_METHODS.ETH_SIGN:
|
||||
case EIP155_SIGNING_METHODS.PERSONAL_SIGN:
|
||||
setRequestSession(requestSessionData);
|
||||
setRequestEventData(requestEvent);
|
||||
setSignModalVisible(true);
|
||||
return;
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
useEffect(() => {
|
||||
web3wallet?.on('session_proposal', onSessionProposal);
|
||||
web3wallet?.on('session_request', onSessionRequest);
|
||||
//TODO: Investigate dependancies
|
||||
});
|
||||
|
||||
const linking = {
|
||||
prefixes: ['https://www.laconic-wallet.com'],
|
||||
config: {
|
||||
@ -59,6 +110,13 @@ const App = (): React.JSX.Element => {
|
||||
headerBackVisible: false,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="WalletConnect"
|
||||
component={WalletConnect}
|
||||
options={{
|
||||
title: 'Connect Wallet',
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="QRScanner"
|
||||
component={QRScanner}
|
||||
@ -67,6 +125,19 @@ const App = (): React.JSX.Element => {
|
||||
}}
|
||||
/>
|
||||
</Stack.Navigator>
|
||||
<PairingModal
|
||||
visible={modalVisible}
|
||||
setModalVisible={setModalVisible}
|
||||
currentProposal={currentProposal}
|
||||
setCurrentProposal={setCurrentProposal}
|
||||
/>
|
||||
|
||||
<SignModal
|
||||
visible={signModalVisible}
|
||||
setModalVisible={setSignModalVisible}
|
||||
requestEvent={requestEventData}
|
||||
requestSession={requestSession}
|
||||
/>
|
||||
</NavigationContainer>
|
||||
);
|
||||
};
|
||||
|
@ -117,7 +117,7 @@ const Accounts = ({
|
||||
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
navigation.navigate('QRScanner');
|
||||
navigation.navigate('WalletConnect');
|
||||
}}>
|
||||
<Text
|
||||
variant="titleSmall"
|
||||
|
@ -13,7 +13,9 @@ const InvalidPath = () => {
|
||||
useNavigation<NativeStackNavigationProp<StackParamsList>>();
|
||||
return (
|
||||
<View style={styles.badRequestContainer}>
|
||||
<Text style={styles.messageText}>The signature request was invalid.</Text>
|
||||
<Text style={styles.invalidMessageText}>
|
||||
The signature request was invalid.
|
||||
</Text>
|
||||
<Button
|
||||
mode="contained"
|
||||
onPress={() => {
|
||||
|
118
components/PairingModal.tsx
Normal file
118
components/PairingModal.tsx
Normal file
@ -0,0 +1,118 @@
|
||||
import React from 'react';
|
||||
import { Image, View, Modal } from 'react-native';
|
||||
import { Button, Text } from 'react-native-paper';
|
||||
|
||||
import { PairingModalProps } from '../types';
|
||||
import styles from '../styles/stylesheet';
|
||||
import {
|
||||
currentETHAddress,
|
||||
web3wallet,
|
||||
} from '../utils/wallet-connect/WalletConnectUtils';
|
||||
|
||||
import { SessionTypes } from '@walletconnect/types';
|
||||
import { getSdkError } from '@walletconnect/utils';
|
||||
|
||||
const PairingModal = ({
|
||||
visible,
|
||||
currentProposal,
|
||||
setCurrentProposal,
|
||||
setModalVisible,
|
||||
}: PairingModalProps) => {
|
||||
const url = currentProposal?.params?.proposer?.metadata.url;
|
||||
const methods = currentProposal?.params?.requiredNamespaces.eip155.methods;
|
||||
const events = currentProposal?.params?.requiredNamespaces.eip155.events;
|
||||
const chains = currentProposal?.params?.requiredNamespaces.eip155.chains;
|
||||
const icon = currentProposal?.params.proposer.metadata.icons[0];
|
||||
|
||||
const handleAccept = async () => {
|
||||
if (currentProposal) {
|
||||
const { id, params } = currentProposal;
|
||||
const { requiredNamespaces, relays } = params;
|
||||
const namespaces: SessionTypes.Namespaces = {};
|
||||
Object.keys(requiredNamespaces).forEach(key => {
|
||||
const accounts: string[] = [];
|
||||
requiredNamespaces[key].chains!.map((chain: any) => {
|
||||
[currentETHAddress].map(acc => accounts.push(`${chain}:${acc}`));
|
||||
});
|
||||
|
||||
namespaces[key] = {
|
||||
accounts,
|
||||
methods: requiredNamespaces[key].methods,
|
||||
events: requiredNamespaces[key].events,
|
||||
};
|
||||
});
|
||||
|
||||
await web3wallet.approveSession({
|
||||
id,
|
||||
relayProtocol: relays[0].protocol,
|
||||
namespaces,
|
||||
});
|
||||
|
||||
setModalVisible(false);
|
||||
setCurrentProposal(undefined);
|
||||
}
|
||||
};
|
||||
|
||||
const handleReject = async () => {
|
||||
if (currentProposal) {
|
||||
const { id } = currentProposal;
|
||||
await web3wallet.rejectSession({
|
||||
id,
|
||||
reason: getSdkError('USER_REJECTED_METHODS'),
|
||||
});
|
||||
|
||||
setModalVisible(false);
|
||||
setCurrentProposal(undefined);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal visible={visible} animationType="slide" transparent>
|
||||
<View style={styles.container}>
|
||||
<View style={styles.modalContentContainer}>
|
||||
{icon && (
|
||||
<Image
|
||||
style={styles.dappLogo}
|
||||
source={icon ? { uri: icon } : undefined}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Text variant="bodyMedium">{url}</Text>
|
||||
<View style={styles.marginVertical8} />
|
||||
<Text variant="titleMedium">Connect to this site?</Text>
|
||||
<Text>Chains: {chains}</Text>
|
||||
|
||||
<View style={styles.marginVertical8}>
|
||||
<Text variant="titleMedium">Methods Requested:</Text>
|
||||
{methods?.map(method => (
|
||||
<Text style={styles.centerText} key={method}>
|
||||
{method}
|
||||
</Text>
|
||||
))}
|
||||
</View>
|
||||
|
||||
<View style={styles.marginVertical8}>
|
||||
<Text variant="titleMedium">Events Requested:</Text>
|
||||
{events?.map(event => (
|
||||
<Text style={styles.centerText} key={event}>
|
||||
{event}
|
||||
</Text>
|
||||
))}
|
||||
</View>
|
||||
|
||||
<View style={styles.flexRow}>
|
||||
<Button mode="outlined" onPress={() => handleReject()}>
|
||||
Cancel
|
||||
</Button>
|
||||
<View style={styles.space} />
|
||||
<Button mode="contained" onPress={() => handleAccept()}>
|
||||
Accept
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default PairingModal;
|
92
components/SignModal.tsx
Normal file
92
components/SignModal.tsx
Normal file
@ -0,0 +1,92 @@
|
||||
import React from 'react';
|
||||
import { Button, Text } from 'react-native-paper';
|
||||
import { Image, Modal, View } from 'react-native';
|
||||
|
||||
import { getSignParamsMessage } from '../utils/wallet-connect/Helpers';
|
||||
import { web3wallet } from '../utils/wallet-connect/WalletConnectUtils';
|
||||
import {
|
||||
approveEIP155Request,
|
||||
rejectEIP155Request,
|
||||
} from '../utils/wallet-connect/EIP155Requests';
|
||||
|
||||
import styles from '../styles/stylesheet';
|
||||
import { SignModalProps } from '../types';
|
||||
|
||||
const SignModal = ({
|
||||
visible,
|
||||
setModalVisible,
|
||||
requestEvent,
|
||||
requestSession,
|
||||
}: SignModalProps) => {
|
||||
if (!requestEvent || !requestSession) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const chainID = requestEvent?.params?.chainId?.toUpperCase();
|
||||
const message = getSignParamsMessage(requestEvent?.params?.request?.params);
|
||||
|
||||
const requestName = requestSession?.peer?.metadata?.name;
|
||||
const requestIcon = requestSession?.peer?.metadata?.icons[0];
|
||||
const requestURL = requestSession?.peer?.metadata?.url;
|
||||
|
||||
const { topic } = requestEvent;
|
||||
|
||||
const onApprove = async () => {
|
||||
if (requestEvent) {
|
||||
const response = await approveEIP155Request(requestEvent);
|
||||
await web3wallet.respondSessionRequest({
|
||||
topic,
|
||||
response,
|
||||
});
|
||||
setModalVisible(false);
|
||||
}
|
||||
};
|
||||
|
||||
const onReject = async () => {
|
||||
if (requestEvent) {
|
||||
const response = rejectEIP155Request(requestEvent);
|
||||
await web3wallet.respondSessionRequest({
|
||||
topic,
|
||||
response,
|
||||
});
|
||||
setModalVisible(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal visible={visible} animationType="slide" transparent>
|
||||
<View style={styles.container}>
|
||||
<View style={styles.modalContentContainer}>
|
||||
<Text variant="titleLarge">Sign this message?</Text>
|
||||
|
||||
<Image
|
||||
style={styles.dappLogo}
|
||||
source={{
|
||||
uri: requestIcon,
|
||||
}}
|
||||
/>
|
||||
|
||||
<Text>{requestName}</Text>
|
||||
<Text variant="bodyMedium">{requestURL}</Text>
|
||||
|
||||
<View style={styles.messageBody}>
|
||||
<Text variant="bodyLarge">{message}</Text>
|
||||
</View>
|
||||
<Text>Chains: {chainID}</Text>
|
||||
|
||||
<View style={styles.flexRow}>
|
||||
<Button mode="outlined" onPress={() => onReject()}>
|
||||
Cancel
|
||||
</Button>
|
||||
<View style={styles.space} />
|
||||
<Button mode="contained" onPress={() => onApprove()}>
|
||||
Accept
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default SignModal;
|
42
components/WalletConnect.tsx
Normal file
42
components/WalletConnect.tsx
Normal file
@ -0,0 +1,42 @@
|
||||
import React, { useState } from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { Button, TextInput } from 'react-native-paper';
|
||||
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
|
||||
import { web3WalletPair } from '../utils/wallet-connect/WalletConnectUtils';
|
||||
import styles from '../styles/stylesheet';
|
||||
import { StackParamsList } from '../types';
|
||||
|
||||
const WalletConnect = () => {
|
||||
const [currentWCURI, setCurrentWCURI] = useState<string>('');
|
||||
|
||||
const navigation =
|
||||
useNavigation<NativeStackNavigationProp<StackParamsList>>();
|
||||
|
||||
const pair = async () => {
|
||||
const pairing = await web3WalletPair({ uri: currentWCURI });
|
||||
navigation.navigate('Laconic');
|
||||
return pairing;
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={styles.appContainer}>
|
||||
<TextInput
|
||||
mode="outlined"
|
||||
onChangeText={setCurrentWCURI}
|
||||
value={currentWCURI}
|
||||
placeholder="Enter WalletConnect URI"
|
||||
/>
|
||||
|
||||
<View style={styles.signButton}>
|
||||
<Button mode="contained" onPress={pair}>
|
||||
Pair Session
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default WalletConnect;
|
@ -14,9 +14,15 @@
|
||||
"@cosmjs/amino": "^0.32.2",
|
||||
"@cosmjs/crypto": "^0.32.2",
|
||||
"@ethersproject/shims": "^5.7.0",
|
||||
"@json-rpc-tools/utils": "^1.7.6",
|
||||
"@react-native-async-storage/async-storage": "^1.22.3",
|
||||
"@react-native-community/netinfo": "^11.3.1",
|
||||
"@react-navigation/native": "^6.1.10",
|
||||
"@react-navigation/native-stack": "^6.9.18",
|
||||
"ethers": "5",
|
||||
"@walletconnect/react-native-compat": "^2.11.2",
|
||||
"@walletconnect/web3wallet": "^1.10.2",
|
||||
"ethers": "5.7.2",
|
||||
"fast-text-encoding": "^1.0.6",
|
||||
"metro-react-native-babel-preset": "^0.77.0",
|
||||
"patch-package": "^8.0.0",
|
||||
"postinstall-postinstall": "^2.1.0",
|
||||
@ -43,6 +49,7 @@
|
||||
"@react-native/typescript-config": "0.73.1",
|
||||
"@types/react": "^18.2.6",
|
||||
"@types/react-test-renderer": "^18.0.0",
|
||||
"@walletconnect/jsonrpc-types": "^1.0.3",
|
||||
"babel-jest": "^29.6.3",
|
||||
"babel-plugin-module-resolver": "^5.0.0",
|
||||
"eslint": "^8.19.0",
|
||||
|
@ -131,12 +131,66 @@ const styles = StyleSheet.create({
|
||||
justifyContent: 'center',
|
||||
padding: 20,
|
||||
},
|
||||
messageText: {
|
||||
invalidMessageText: {
|
||||
color: 'black',
|
||||
fontSize: 16,
|
||||
textAlign: 'center',
|
||||
marginBottom: 20,
|
||||
},
|
||||
container: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
modalContentContainer: {
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
borderRadius: 34,
|
||||
borderBottomStartRadius: 0,
|
||||
borderBottomEndRadius: 0,
|
||||
borderWidth: 1,
|
||||
width: '100%',
|
||||
height: '50%',
|
||||
position: 'absolute',
|
||||
backgroundColor: 'white',
|
||||
bottom: 0,
|
||||
},
|
||||
dappLogo: {
|
||||
width: 50,
|
||||
height: 50,
|
||||
borderRadius: 8,
|
||||
marginVertical: 16,
|
||||
},
|
||||
space: {
|
||||
width: 50,
|
||||
},
|
||||
flexRow: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginTop: 20,
|
||||
paddingHorizontal: 16,
|
||||
},
|
||||
marginVertical8: {
|
||||
marginVertical: 8,
|
||||
textAlign: 'center',
|
||||
},
|
||||
subHeading: {
|
||||
textAlign: 'center',
|
||||
fontWeight: '600',
|
||||
},
|
||||
centerText: {
|
||||
textAlign: 'center',
|
||||
},
|
||||
messageBody: {
|
||||
borderWidth: 1,
|
||||
borderRadius: 10,
|
||||
paddingVertical: 10,
|
||||
paddingHorizontal: 10,
|
||||
marginVertical: 20,
|
||||
},
|
||||
});
|
||||
|
||||
export default styles;
|
||||
|
21
types.ts
21
types.ts
@ -1,3 +1,5 @@
|
||||
import { SignClientTypes } from '@walletconnect/types';
|
||||
|
||||
export type StackParamsList = {
|
||||
Laconic: undefined;
|
||||
SignMessage: { selectedNetwork: string; accountInfo: Account } | undefined;
|
||||
@ -6,6 +8,7 @@ export type StackParamsList = {
|
||||
| undefined;
|
||||
InvalidPath: undefined;
|
||||
QRScanner: undefined;
|
||||
WalletConnect: undefined;
|
||||
};
|
||||
|
||||
export type Account = {
|
||||
@ -83,3 +86,21 @@ export type PathState = {
|
||||
secondNumber: string;
|
||||
thirdNumber: string;
|
||||
};
|
||||
|
||||
export interface PairingModalProps {
|
||||
visible: boolean;
|
||||
setModalVisible: (arg1: boolean) => void;
|
||||
currentProposal:
|
||||
| SignClientTypes.EventArguments['session_proposal']
|
||||
| undefined;
|
||||
setCurrentProposal: (
|
||||
arg1: SignClientTypes.EventArguments['session_proposal'] | undefined,
|
||||
) => void;
|
||||
}
|
||||
|
||||
export interface SignModalProps {
|
||||
visible: boolean;
|
||||
setModalVisible: (arg1: boolean) => void;
|
||||
requestSession: any;
|
||||
requestEvent: SignClientTypes.EventArguments['session_request'] | undefined;
|
||||
}
|
||||
|
147
utils/wallet-connect/EIP155Lib.ts
Normal file
147
utils/wallet-connect/EIP155Lib.ts
Normal file
@ -0,0 +1,147 @@
|
||||
// Taken from https://medium.com/walletconnect/how-to-build-a-wallet-in-react-native-with-the-web3wallet-sdk-b6f57bf02f9a
|
||||
|
||||
import { providers, Wallet } from 'ethers';
|
||||
|
||||
/**
|
||||
* Types
|
||||
*/
|
||||
interface IInitArgs {
|
||||
mnemonic?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Library
|
||||
*/
|
||||
export default class EIP155Lib {
|
||||
wallet: Wallet;
|
||||
|
||||
constructor(wallet: Wallet) {
|
||||
this.wallet = wallet;
|
||||
}
|
||||
|
||||
static init({ mnemonic }: IInitArgs) {
|
||||
const wallet = mnemonic
|
||||
? Wallet.fromMnemonic(mnemonic)
|
||||
: Wallet.createRandom();
|
||||
|
||||
return new EIP155Lib(wallet);
|
||||
}
|
||||
|
||||
getMnemonic() {
|
||||
return this.wallet.mnemonic.phrase;
|
||||
}
|
||||
|
||||
getAddress() {
|
||||
return this.wallet.address;
|
||||
}
|
||||
|
||||
signMessage(message: string) {
|
||||
return this.wallet.signMessage(message);
|
||||
}
|
||||
|
||||
_signTypedData(domain: any, types: any, data: any) {
|
||||
return this.wallet._signTypedData(domain, types, data);
|
||||
}
|
||||
|
||||
connect(provider: providers.JsonRpcProvider) {
|
||||
return this.wallet.connect(provider);
|
||||
}
|
||||
|
||||
signTransaction(transaction: providers.TransactionRequest) {
|
||||
return this.wallet.signTransaction(transaction);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @desc Reference list of eip155 chains
|
||||
* @url https://chainlist.org
|
||||
*/
|
||||
|
||||
/**
|
||||
* Types
|
||||
*/
|
||||
export type TEIP155Chain = keyof typeof EIP155_CHAINS;
|
||||
|
||||
/**
|
||||
* Chains
|
||||
*/
|
||||
export const EIP155_MAINNET_CHAINS = {
|
||||
'eip155:1': {
|
||||
chainId: 1,
|
||||
name: 'Ethereum',
|
||||
logo: '/chain-logos/eip155-1.png',
|
||||
rgb: '99, 125, 234',
|
||||
rpc: 'https://cloudflare-eth.com/',
|
||||
},
|
||||
'eip155:43114': {
|
||||
chainId: 43114,
|
||||
name: 'Avalanche C-Chain',
|
||||
logo: '/chain-logos/eip155-43113.png',
|
||||
rgb: '232, 65, 66',
|
||||
rpc: 'https://api.avax.network/ext/bc/C/rpc',
|
||||
},
|
||||
'eip155:137': {
|
||||
chainId: 137,
|
||||
name: 'Polygon',
|
||||
logo: '/chain-logos/eip155-137.png',
|
||||
rgb: '130, 71, 229',
|
||||
rpc: 'https://polygon-rpc.com/',
|
||||
},
|
||||
'eip155:10': {
|
||||
chainId: 10,
|
||||
name: 'Optimism',
|
||||
logo: '/chain-logos/eip155-10.png',
|
||||
rgb: '235, 0, 25',
|
||||
rpc: 'https://mainnet.optimism.io',
|
||||
},
|
||||
};
|
||||
|
||||
export const EIP155_TEST_CHAINS = {
|
||||
'eip155:5': {
|
||||
chainId: 5,
|
||||
name: 'Ethereum Goerli',
|
||||
logo: '/chain-logos/eip155-1.png',
|
||||
rgb: '99, 125, 234',
|
||||
rpc: 'https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161',
|
||||
},
|
||||
'eip155:43113': {
|
||||
chainId: 43113,
|
||||
name: 'Avalanche Fuji',
|
||||
logo: '/chain-logos/eip155-43113.png',
|
||||
rgb: '232, 65, 66',
|
||||
rpc: 'https://api.avax-test.network/ext/bc/C/rpc',
|
||||
},
|
||||
'eip155:80001': {
|
||||
chainId: 80001,
|
||||
name: 'Polygon Mumbai',
|
||||
logo: '/chain-logos/eip155-137.png',
|
||||
rgb: '130, 71, 229',
|
||||
rpc: 'https://matic-mumbai.chainstacklabs.com',
|
||||
},
|
||||
'eip155:420': {
|
||||
chainId: 420,
|
||||
name: 'Optimism Goerli',
|
||||
logo: '/chain-logos/eip155-10.png',
|
||||
rgb: '235, 0, 25',
|
||||
rpc: 'https://goerli.optimism.io',
|
||||
},
|
||||
};
|
||||
|
||||
export const EIP155_CHAINS = {
|
||||
...EIP155_MAINNET_CHAINS,
|
||||
...EIP155_TEST_CHAINS,
|
||||
};
|
||||
|
||||
/**
|
||||
* Methods
|
||||
*/
|
||||
export const EIP155_SIGNING_METHODS = {
|
||||
PERSONAL_SIGN: 'personal_sign',
|
||||
ETH_SIGN: 'eth_sign',
|
||||
ETH_SIGN_TRANSACTION: 'eth_signTransaction',
|
||||
ETH_SIGN_TYPED_DATA: 'eth_signTypedData',
|
||||
ETH_SIGN_TYPED_DATA_V3: 'eth_signTypedData_v3',
|
||||
ETH_SIGN_TYPED_DATA_V4: 'eth_signTypedData_v4',
|
||||
ETH_SEND_RAW_TRANSACTION: 'eth_sendRawTransaction',
|
||||
ETH_SEND_TRANSACTION: 'eth_sendTransaction',
|
||||
};
|
72
utils/wallet-connect/EIP155Requests.ts
Normal file
72
utils/wallet-connect/EIP155Requests.ts
Normal file
@ -0,0 +1,72 @@
|
||||
// Taken from https://medium.com/walletconnect/how-to-build-a-wallet-in-react-native-with-the-web3wallet-sdk-b6f57bf02f9a
|
||||
import {
|
||||
EIP155_CHAINS,
|
||||
EIP155_SIGNING_METHODS,
|
||||
TEIP155Chain,
|
||||
} from './EIP155Lib';
|
||||
import { eip155Wallets } from './EIP155Wallet';
|
||||
import {
|
||||
getSignParamsMessage,
|
||||
getSignTypedDataParamsData,
|
||||
getWalletAddressFromParams,
|
||||
} from './Helpers';
|
||||
import { formatJsonRpcError, formatJsonRpcResult } from '@json-rpc-tools/utils';
|
||||
import { SignClientTypes } from '@walletconnect/types';
|
||||
import { getSdkError } from '@walletconnect/utils';
|
||||
import { providers } from 'ethers';
|
||||
import { currentETHAddress } from './WalletConnectUtils';
|
||||
|
||||
export async function approveEIP155Request(
|
||||
requestEvent: SignClientTypes.EventArguments['session_request'],
|
||||
) {
|
||||
const { params, id } = requestEvent;
|
||||
const { chainId, request } = params;
|
||||
const wallet =
|
||||
eip155Wallets[getWalletAddressFromParams([currentETHAddress], params)];
|
||||
|
||||
switch (request.method) {
|
||||
case EIP155_SIGNING_METHODS.PERSONAL_SIGN:
|
||||
case EIP155_SIGNING_METHODS.ETH_SIGN:
|
||||
const message = getSignParamsMessage(request.params);
|
||||
const signedMessage = await wallet.signMessage(message);
|
||||
return formatJsonRpcResult(id, signedMessage);
|
||||
|
||||
case EIP155_SIGNING_METHODS.ETH_SIGN_TYPED_DATA:
|
||||
case EIP155_SIGNING_METHODS.ETH_SIGN_TYPED_DATA_V3:
|
||||
case EIP155_SIGNING_METHODS.ETH_SIGN_TYPED_DATA_V4:
|
||||
const {
|
||||
domain,
|
||||
types,
|
||||
message: data,
|
||||
} = getSignTypedDataParamsData(request.params);
|
||||
// https://github.com/ethers-io/ethers.js/issues/687#issuecomment-714069471
|
||||
delete types.EIP712Domain;
|
||||
const signedData = await wallet._signTypedData(domain, types, data);
|
||||
return formatJsonRpcResult(id, signedData);
|
||||
|
||||
case EIP155_SIGNING_METHODS.ETH_SEND_TRANSACTION:
|
||||
const provider = new providers.JsonRpcProvider(
|
||||
EIP155_CHAINS[chainId as TEIP155Chain].rpc,
|
||||
);
|
||||
const sendTransaction = request.params[0];
|
||||
const connectedWallet = wallet.connect(provider);
|
||||
const { hash } = await connectedWallet.sendTransaction(sendTransaction);
|
||||
return formatJsonRpcResult(id, hash);
|
||||
|
||||
case EIP155_SIGNING_METHODS.ETH_SIGN_TRANSACTION:
|
||||
const signTransaction = request.params[0];
|
||||
const signature = await wallet.signTransaction(signTransaction);
|
||||
return formatJsonRpcResult(id, signature);
|
||||
|
||||
default:
|
||||
throw new Error(getSdkError('INVALID_METHOD').message);
|
||||
}
|
||||
}
|
||||
|
||||
export function rejectEIP155Request(
|
||||
request: SignClientTypes.EventArguments['session_request'],
|
||||
) {
|
||||
const { id } = request;
|
||||
|
||||
return formatJsonRpcError(id, getSdkError('USER_REJECTED_METHODS').message);
|
||||
}
|
64
utils/wallet-connect/EIP155Wallet.ts
Normal file
64
utils/wallet-connect/EIP155Wallet.ts
Normal file
@ -0,0 +1,64 @@
|
||||
// https://medium.com/walletconnect/how-to-build-a-wallet-in-react-native-with-the-web3wallet-sdk-b6f57bf02f9a
|
||||
// TODO: check and remove if not used
|
||||
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import EIP155Lib from './EIP155Lib';
|
||||
|
||||
export let wallet1: EIP155Lib;
|
||||
export let wallet2: EIP155Lib;
|
||||
export let eip155Wallets: Record<string, EIP155Lib>;
|
||||
export let eip155Addresses: string[];
|
||||
|
||||
export let address1: string;
|
||||
let address2: string;
|
||||
|
||||
/**
|
||||
* Utilities
|
||||
*/
|
||||
export const setLocalStorage = async (mnemonic: any) => {
|
||||
try {
|
||||
const value = await AsyncStorage.setItem('EIP155_MNEMONIC_1', mnemonic);
|
||||
if (value !== null) {
|
||||
return value;
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('setLocalStorage Error:', e);
|
||||
}
|
||||
};
|
||||
|
||||
export const getLocalStorage = async () => {
|
||||
try {
|
||||
const value = await AsyncStorage.getItem('EIP155_MNEMONIC_1');
|
||||
if (value !== null) {
|
||||
return value;
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('getLocalStorage Error:', e);
|
||||
}
|
||||
};
|
||||
|
||||
// Function to create or restore a wallet
|
||||
export async function createOrRestoreEIP155Wallet() {
|
||||
let mnemonic1 = await getLocalStorage();
|
||||
|
||||
if (mnemonic1) {
|
||||
wallet1 = EIP155Lib.init({ mnemonic: mnemonic1 });
|
||||
} else {
|
||||
wallet1 = EIP155Lib.init({});
|
||||
}
|
||||
|
||||
// @notice / Warning!!! : This is a test wallet, do not use it for real transactions
|
||||
setLocalStorage(wallet1?.getMnemonic());
|
||||
address1 = wallet1.getAddress();
|
||||
|
||||
eip155Wallets = {
|
||||
[address1]: wallet1,
|
||||
[address2]: wallet2,
|
||||
};
|
||||
eip155Addresses = Object.keys(eip155Wallets);
|
||||
|
||||
return {
|
||||
eip155Wallets,
|
||||
eip155Addresses,
|
||||
};
|
||||
}
|
106
utils/wallet-connect/Helpers.ts
Normal file
106
utils/wallet-connect/Helpers.ts
Normal file
@ -0,0 +1,106 @@
|
||||
// Taken from https://medium.com/walletconnect/how-to-build-a-wallet-in-react-native-with-the-web3wallet-sdk-b6f57bf02f9a
|
||||
|
||||
import { EIP155_CHAINS, TEIP155Chain } from './EIP155Lib';
|
||||
import { utils } from 'ethers';
|
||||
|
||||
/**
|
||||
* Truncates string (in the middle) via given lenght value
|
||||
*/
|
||||
export function truncate(value: string, length: number) {
|
||||
if (value?.length <= length) {
|
||||
return value;
|
||||
}
|
||||
|
||||
const separator = '...';
|
||||
const stringLength = length - separator.length;
|
||||
const frontLength = Math.ceil(stringLength / 2);
|
||||
const backLength = Math.floor(stringLength / 2);
|
||||
|
||||
return (
|
||||
value.substring(0, frontLength) +
|
||||
separator +
|
||||
value.substring(value.length - backLength)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts hex to utf8 string if it is valid bytes
|
||||
*/
|
||||
export function convertHexToUtf8(value: string) {
|
||||
if (utils.isHexString(value)) {
|
||||
return utils.toUtf8String(value);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets message from various signing request methods by filtering out
|
||||
* a value that is not an address (thus is a message).
|
||||
* If it is a hex string, it gets converted to utf8 string
|
||||
*/
|
||||
export function getSignParamsMessage(params: string[]) {
|
||||
const message = params.filter(p => !utils.isAddress(p))[0];
|
||||
|
||||
return convertHexToUtf8(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets data from various signTypedData request methods by filtering out
|
||||
* a value that is not an address (thus is data).
|
||||
* If data is a string convert it to object
|
||||
*/
|
||||
export function getSignTypedDataParamsData(params: string[]) {
|
||||
const data = params.filter(p => !utils.isAddress(p))[0];
|
||||
|
||||
if (typeof data === 'string') {
|
||||
return JSON.parse(data);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get our address from params checking if params string contains one
|
||||
* of our wallet addresses
|
||||
*/
|
||||
export function getWalletAddressFromParams(addresses: string[], params: any) {
|
||||
const paramsString = JSON.stringify(params);
|
||||
let address = '';
|
||||
|
||||
addresses.forEach(addr => {
|
||||
if (paramsString.includes(addr)) {
|
||||
address = addr;
|
||||
}
|
||||
});
|
||||
|
||||
return address;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if chain is part of EIP155 standard
|
||||
*/
|
||||
export function isEIP155Chain(chain: string) {
|
||||
return chain.includes('eip155');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if chain is part of COSMOS standard
|
||||
*/
|
||||
export function isCosmosChain(chain: string) {
|
||||
return chain.includes('cosmos');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if chain is part of SOLANA standard
|
||||
*/
|
||||
export function isSolanaChain(chain: string) {
|
||||
return chain.includes('solana');
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats chainId to its name
|
||||
*/
|
||||
export function formatChainName(chainId: string) {
|
||||
return EIP155_CHAINS[chainId as TEIP155Chain]?.name ?? chainId;
|
||||
}
|
59
utils/wallet-connect/WalletConnectUtils.tsx
Normal file
59
utils/wallet-connect/WalletConnectUtils.tsx
Normal file
@ -0,0 +1,59 @@
|
||||
import '@walletconnect/react-native-compat';
|
||||
import '@ethersproject/shims';
|
||||
|
||||
import { Core } from '@walletconnect/core';
|
||||
import { ICore } from '@walletconnect/types';
|
||||
import { Web3Wallet, IWeb3Wallet } from '@walletconnect/web3wallet';
|
||||
|
||||
export let web3wallet: IWeb3Wallet;
|
||||
export let core: ICore;
|
||||
export let currentETHAddress: string;
|
||||
|
||||
import { useState, useCallback, useEffect } from 'react';
|
||||
import { createOrRestoreEIP155Wallet } from './EIP155Wallet';
|
||||
|
||||
async function createWeb3Wallet() {
|
||||
const { eip155Addresses } = await createOrRestoreEIP155Wallet();
|
||||
currentETHAddress = eip155Addresses[0];
|
||||
|
||||
// TODO: Move to dotenv
|
||||
const ENV_PROJECT_ID = 'c97365bf9f06d12a7488de36240b0ff4';
|
||||
const core = new Core({
|
||||
projectId: ENV_PROJECT_ID,
|
||||
});
|
||||
|
||||
web3wallet = await Web3Wallet.init({
|
||||
core,
|
||||
metadata: {
|
||||
name: 'Web3Wallet React Native Tutorial',
|
||||
description: 'ReactNative Web3Wallet',
|
||||
url: 'https://walletconnect.com/',
|
||||
icons: ['https://avatars.githubusercontent.com/u/37784886'],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export default function useInitialization() {
|
||||
const [initialized, setInitialized] = useState(false);
|
||||
|
||||
const onInitialize = useCallback(async () => {
|
||||
try {
|
||||
await createWeb3Wallet();
|
||||
setInitialized(true);
|
||||
} catch (err: unknown) {
|
||||
console.log('Error for initializing', err);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!initialized) {
|
||||
onInitialize();
|
||||
}
|
||||
}, [initialized, onInitialize]);
|
||||
|
||||
return initialized;
|
||||
}
|
||||
|
||||
export async function web3WalletPair(params: { uri: string }) {
|
||||
return await web3wallet.core.pairing.pair({ uri: params.uri });
|
||||
}
|
Loading…
Reference in New Issue
Block a user