Sign message with cosmos accounts using signAmino method (#49)

* Add functionality to use cosmos accounts while pairing

* Sign message using cosmos accounts using signAmino method

* Add todo to debug signDirect

* Use cosmos wallet amino method directly

* Add back displaying wallet connect data while pairing

* Reset state for wallet connect data on closing pairing modal
This commit is contained in:
shreerang6921 2024-03-12 13:40:34 +05:30 committed by GitHub
parent 7f1b2e38ef
commit 5b6f4e9f61
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 227 additions and 79 deletions

63
App.tsx
View File

@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import { Button, Snackbar } from 'react-native-paper';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
@ -21,7 +21,6 @@ import useInitialization, {
web3wallet,
} from './utils/wallet-connect/WalletConnectUtils';
import { EIP155_SIGNING_METHODS } from './utils/wallet-connect/EIP155Lib';
import { useAccounts } from './context/AccountsContext';
import { getSignParamsMessage } from './utils/wallet-connect/Helpers';
import { useRequests } from './context/RequestContext';
@ -33,7 +32,6 @@ const App = (): React.JSX.Element => {
const navigation =
useNavigation<NativeStackNavigationProp<StackParamsList>>();
const { accounts } = useAccounts();
const { requestSession, setRequestSession } = useRequests();
const [modalVisible, setModalVisible] = useState(false);
@ -42,13 +40,6 @@ const App = (): React.JSX.Element => {
SignClientTypes.EventArguments['session_proposal'] | undefined
>();
const currentEthAddresses = useMemo(() => {
if (accounts.ethAccounts.length > 0) {
return accounts.ethAccounts.map(account => account.address);
}
return [];
}, [accounts]);
const onSessionProposal = useCallback(
(proposal: SignClientTypes.EventArguments['session_proposal']) => {
setModalVisible(true);
@ -61,28 +52,49 @@ const App = (): React.JSX.Element => {
async (requestEvent: SignClientTypes.EventArguments['session_request']) => {
const { topic, params } = requestEvent;
const { request } = params;
const address = request.params[1];
const message = getSignParamsMessage(request.params);
const requestSessionData =
web3wallet.engine.signClient.session.get(topic);
setRequestSession(requestSessionData);
switch (request.method) {
case EIP155_SIGNING_METHODS.ETH_SIGN:
case EIP155_SIGNING_METHODS.PERSONAL_SIGN:
setRequestSession(requestSessionData);
if (address && message) {
navigation.navigate('SignRequest', {
network: 'eth',
address,
message,
requestEvent,
requestSession,
});
}
return;
navigation.navigate('SignRequest', {
network: 'eth',
address: request.params[1],
message: getSignParamsMessage(request.params),
requestEvent,
requestSession,
});
break;
// TODO: Debug signDirect
case 'cosmos_signDirect':
navigation.navigate('SignRequest', {
network: 'cosmos',
address: request.params.signerAddress,
message: request.params.signDoc.bodyBytes,
requestEvent,
requestSession,
});
break;
case 'cosmos_signAmino':
navigation.navigate('SignRequest', {
network: 'cosmos',
address: request.params.signerAddress,
message: request.params.signDoc.memo,
requestEvent,
requestSession,
});
break;
default:
throw new Error('Invalid method');
}
},
[requestSession, setRequestSession],
[requestSession, setRequestSession, navigation],
);
useEffect(() => {
web3wallet?.on('session_proposal', onSessionProposal);
@ -158,7 +170,6 @@ const App = (): React.JSX.Element => {
setModalVisible={setModalVisible}
currentProposal={currentProposal}
setCurrentProposal={setCurrentProposal}
currentEthAddresses={currentEthAddresses}
setToastVisible={setToastVisible}
/>
<Snackbar

View File

@ -1,4 +1,4 @@
import React from 'react';
import React, { useEffect, useState } from 'react';
import { Image, View, Modal } from 'react-native';
import { Button, Text } from 'react-native-paper';
@ -8,34 +8,127 @@ import { getSdkError } from '@walletconnect/utils';
import { PairingModalProps } from '../types';
import styles from '../styles/stylesheet';
import { web3wallet } from '../utils/wallet-connect/WalletConnectUtils';
import { useAccounts } from '../context/AccountsContext';
const PairingModal = ({
visible,
currentProposal,
currentEthAddresses,
setCurrentProposal,
setModalVisible,
setToastVisible,
}: PairingModalProps) => {
const { accounts } = useAccounts();
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 icon = currentProposal?.params.proposer?.metadata.icons[0];
const [walletConnectData, setWalletConnectData] = useState<{
walletConnectMethods: string[];
walletConnectEvents: string[];
walletConnectChains: string[];
}>({
walletConnectMethods: [],
walletConnectEvents: [],
walletConnectChains: [],
});
useEffect(() => {
if (!currentProposal) {
return;
}
const { params } = currentProposal;
const { requiredNamespaces } = params;
Object.keys(requiredNamespaces).forEach(key => {
switch (key) {
case 'eip155':
const {
methods: ethMethods,
events: ethEvents,
chains: ethChains,
} = currentProposal?.params?.requiredNamespaces.eip155;
setWalletConnectData(prevData => {
return {
walletConnectMethods: [
...prevData.walletConnectMethods,
...ethMethods,
],
walletConnectEvents: [
...prevData.walletConnectEvents,
...ethEvents,
],
walletConnectChains: ethChains
? [...prevData.walletConnectChains, ...ethChains]
: [...prevData.walletConnectChains],
};
});
break;
case 'cosmos':
const {
methods: cosmosMethods,
events: cosmosEvents,
chains: cosmosChains,
} = currentProposal?.params?.requiredNamespaces.cosmos;
setWalletConnectData(prevData => {
return {
walletConnectMethods: [
...prevData.walletConnectMethods,
...cosmosMethods,
],
walletConnectEvents: [
...prevData.walletConnectEvents,
...cosmosEvents,
],
walletConnectChains: cosmosChains
? [...prevData.walletConnectChains, ...cosmosChains]
: [...prevData.walletConnectChains],
};
});
break;
default:
throw new Error(`${key} not supported`);
}
});
}, [currentProposal]);
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[] = [];
let currentAddresses: string[];
switch (key) {
case 'eip155':
if (accounts.ethAccounts.length > 0) {
currentAddresses = accounts.ethAccounts.map(
account => account.address,
);
}
break;
case 'cosmos':
if (accounts.cosmosAccounts.length > 0) {
currentAddresses = accounts.cosmosAccounts.map(
account => account.address,
);
}
break;
default:
throw new Error(`${key} not supported`);
}
const namespaceAccounts: string[] = [];
requiredNamespaces[key].chains!.map((chain: any) => {
currentEthAddresses.map(acc => accounts.push(`${chain}:${acc}`));
currentAddresses.map(acc =>
namespaceAccounts.push(`${chain}:${acc}`),
);
});
namespaces[key] = {
accounts,
accounts: namespaceAccounts,
methods: requiredNamespaces[key].methods,
events: requiredNamespaces[key].events,
};
@ -50,6 +143,11 @@ const PairingModal = ({
setModalVisible(false);
setToastVisible(true);
setCurrentProposal(undefined);
setWalletConnectData({
walletConnectMethods: [],
walletConnectEvents: [],
walletConnectChains: [],
});
}
};
@ -63,6 +161,11 @@ const PairingModal = ({
setModalVisible(false);
setCurrentProposal(undefined);
setWalletConnectData({
walletConnectMethods: [],
walletConnectEvents: [],
walletConnectChains: [],
});
}
};
@ -80,11 +183,11 @@ const PairingModal = ({
<Text variant="bodyMedium">{url}</Text>
<View style={styles.marginVertical8} />
<Text variant="titleMedium">Connect to this site?</Text>
<Text>Chains: {chains}</Text>
<Text>Chains: {walletConnectData.walletConnectChains}</Text>
<View style={styles.marginVertical8}>
<Text variant="titleMedium">Methods Requested:</Text>
{methods?.map(method => (
{walletConnectData.walletConnectMethods.map(method => (
<Text style={styles.centerText} key={method}>
{method}
</Text>
@ -93,7 +196,7 @@ const PairingModal = ({
<View style={styles.marginVertical8}>
<Text variant="titleMedium">Events Requested:</Text>
{events?.map(event => (
{walletConnectData.walletConnectEvents.map(event => (
<Text style={styles.centerText} key={event}>
{event}
</Text>

View File

@ -14,9 +14,9 @@ import styles from '../styles/stylesheet';
import { signMessage } from '../utils/sign-message';
import { retrieveSingleAccount } from '../utils/accounts';
import {
approveEIP155Request,
approveWalletConnectRequest,
rejectEIP155Request,
} from '../utils/wallet-connect/EIP155Requests';
} from '../utils/wallet-connect/WalletConnectRequests';
import { web3wallet } from '../utils/wallet-connect/WalletConnectUtils';
import { useRequests } from '../context/RequestContext';
@ -100,17 +100,20 @@ const SignRequest = ({ route }: SignRequestProps) => {
);
}, [route]);
const handleEIP155Request = async () => {
const handleWalletConnectRequest = async () => {
const { requestEvent } = route.params || {};
if (!account) {
throw new Error('account not found');
}
const response = await approveEIP155Request(
const response = await approveWalletConnectRequest(
requestEvent,
account.counterId,
account,
network,
message,
);
const { topic } = requestEvent;
await web3wallet.respondSessionRequest({ topic, response });
};
@ -131,7 +134,7 @@ const SignRequest = ({ route }: SignRequestProps) => {
const signMessageHandler = async () => {
if (route.params?.requestEvent) {
await handleEIP155Request();
await handleWalletConnectRequest();
} else {
await handleIntent();
}

View File

@ -1,3 +1,4 @@
import { StdSignDoc } from '@cosmjs/amino';
import { SignClientTypes } from '@walletconnect/types';
export type StackParamsList = {
@ -92,7 +93,6 @@ export type PathState = {
export interface PairingModalProps {
visible: boolean;
setModalVisible: (arg1: boolean) => void;
currentEthAddresses: string[];
currentProposal:
| SignClientTypes.EventArguments['session_proposal']
| undefined;

View File

@ -1,34 +0,0 @@
// Taken from https://medium.com/walletconnect/how-to-build-a-wallet-in-react-native-with-the-web3wallet-sdk-b6f57bf02f9a
import { formatJsonRpcError, formatJsonRpcResult } from '@json-rpc-tools/utils';
import { SignClientTypes } from '@walletconnect/types';
import { getSdkError } from '@walletconnect/utils';
import { EIP155_SIGNING_METHODS } from './EIP155Lib';
import { getSignParamsMessage } from './Helpers';
import { signEthMessage } from '../sign-message';
export async function approveEIP155Request(
requestEvent: SignClientTypes.EventArguments['session_request'],
counterId: number,
) {
const { params, id } = requestEvent;
const { request } = params;
switch (request.method) {
case EIP155_SIGNING_METHODS.PERSONAL_SIGN:
const message = getSignParamsMessage(request.params);
const signedMessage = await signEthMessage(message, counterId);
return formatJsonRpcResult(id, signedMessage);
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);
}

View File

@ -0,0 +1,65 @@
// Taken from https://medium.com/walletconnect/how-to-build-a-wallet-in-react-native-with-the-web3wallet-sdk-b6f57bf02f9a
import { formatJsonRpcError, formatJsonRpcResult } from '@json-rpc-tools/utils';
import { SignClientTypes } from '@walletconnect/types';
import { getSdkError } from '@walletconnect/utils';
import { EIP155_SIGNING_METHODS } from './EIP155Lib';
import { signEthMessage } from '../sign-message';
import { Account } from '../../types';
import { getCosmosAccounts, getMnemonic, getPathKey } from '../utils';
export async function approveWalletConnectRequest(
requestEvent: SignClientTypes.EventArguments['session_request'],
account: Account,
network: string,
message: string,
) {
const { params, id } = requestEvent;
const { request } = params;
const path = (await getPathKey(network, account.counterId)).path;
const mnemonic = await getMnemonic();
const cosmosAccount = await getCosmosAccounts(mnemonic, path);
const address = cosmosAccount.data.address;
switch (request.method) {
case EIP155_SIGNING_METHODS.PERSONAL_SIGN:
const signedEthMessage = await signEthMessage(message, account.counterId);
return formatJsonRpcResult(id, signedEthMessage);
// TODO: Debug signDirect
case 'cosmos_signDirect':
const signedCosmosMessage = await cosmosAccount.cosmosWallet.signAmino(
address,
request.params.signDoc,
);
return formatJsonRpcResult(id, {
signature: signedCosmosMessage.signature.signature,
});
case 'cosmos_signAmino':
const signedAminoMessage = await cosmosAccount.cosmosWallet.signAmino(
address,
request.params.signDoc,
);
if (!signedAminoMessage) {
throw new Error('Error signing message');
}
return formatJsonRpcResult(id, {
signature: signedAminoMessage.signature.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);
}