Skip to content

Commit

Permalink
US-1878 Added MMKV as default WC storage. Added signTypedData request (
Browse files Browse the repository at this point in the history
…#769)

* US-1878 Added MMKV as default WC storage. Added signTypedData request

* Updated WC packages. MMKV removed console.logs.

* Removed unsupported eth_signTypedData_v4 method

* Removed console.log
  • Loading branch information
Freshenext authored Nov 7, 2023
1 parent 9d8306f commit c9244a2
Show file tree
Hide file tree
Showing 10 changed files with 290 additions and 102 deletions.
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@
"@rsksmart/rsk-testnet-contract-metadata": "^1.0.16",
"@rsksmart/rsk-utils": "^1.1.0",
"@walletconnect/jsonrpc-types": "^1.0.3",
"@walletconnect/react-native-compat": "^2.9.0",
"@walletconnect/utils": "^2.9.0",
"@walletconnect/web3wallet": "^1.9.0",
"@walletconnect/react-native-compat": "^2.10.2",
"@walletconnect/utils": "^2.10.2",
"@walletconnect/web3wallet": "^1.9.2",
"axios": "^0.27.2",
"buffer": "^4.9.2",
"deprecated-react-native-prop-types": "^2.3.0",
Expand Down
23 changes: 13 additions & 10 deletions src/screens/walletConnect/WalletConnect2Context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,30 @@ import { WalletConnectAdapter } from '@rsksmart/rif-wallet-adapters'
import { RIFWallet } from '@rsksmart/rif-wallet-core'

import {
buildRskAllowedNamespaces,
createWeb3Wallet,
getProposalErrorComparedWithRskNamespace,
rskWalletConnectNamespace,
WalletConnect2SdkErrorString,
} from 'screens/walletConnect/walletConnect2.utils'
import { ChainTypesByIdType } from 'shared/constants/chainConstants'
import { useAppSelector } from 'store/storeUtils'
import { selectChainId } from 'store/slices/settingsSlice'

const onSessionApprove = async (
web3wallet: Web3Wallet,
proposal: Web3WalletTypes.SessionProposal,
walletAddress: string,
chainId: ChainTypesByIdType,
) => {
try {
const namespaces = buildRskAllowedNamespaces({
proposal,
chainId,
walletAddress,
})
return await web3wallet.approveSession({
id: proposal.id,
namespaces: {
eip155: {
...rskWalletConnectNamespace.eip155,
accounts: rskWalletConnectNamespace.eip155.chains.map(
chain => `${chain}:${walletAddress}`,
),
},
},
namespaces,
})
} catch (error) {
return 'Error while approving session' // This is for the developer
Expand Down Expand Up @@ -108,6 +110,7 @@ export const WalletConnect2Provider = ({
children,
wallet,
}: WalletConnect2ProviderProps) => {
const chainId = useAppSelector(selectChainId)
const [sessions, setSessions] = useState<SessionStruct[]>([])
const [pendingSession, setPendingSession] = useState<
PendingSession | undefined
Expand Down Expand Up @@ -161,7 +164,6 @@ export const WalletConnect2Provider = ({
jsonrpc: '2.0',
},
}

adapter
.handleCall(method, params)
.then(signedMessage => {
Expand Down Expand Up @@ -242,6 +244,7 @@ export const WalletConnect2Provider = ({
pendingSession.web3wallet,
pendingSession.proposal,
wallet.smartWalletAddress,
chainId,
)
if (typeof newSession === 'string') {
// @TODO Error occurred - handle it
Expand Down
91 changes: 89 additions & 2 deletions src/screens/walletConnect/walletConnect2.utils.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { Core } from '@walletconnect/core'
import { Web3Wallet, Web3WalletTypes } from '@walletconnect/web3wallet'
import { getSdkError } from '@walletconnect/utils'
import { getSdkError, buildApprovedNamespaces } from '@walletconnect/utils'

import { getEnvSetting } from 'core/config'
import { SETTINGS } from 'core/types'
import { ChainTypesByIdType } from 'shared/constants/chainConstants'
import { AcceptedValue, MMKVStorage } from 'storage/MMKVStorage'

export type WalletConnect2SdkErrorString = Parameters<typeof getSdkError>[0]

Expand Down Expand Up @@ -31,10 +33,44 @@ const WalletConnect2SdkErrorEnum: { [P in WalletConnect2SdkErrorString]: P } = {
SESSION_SETTLEMENT_FAILED: 'SESSION_SETTLEMENT_FAILED',
}

type StorageTypeFromCore = InstanceType<typeof Core>['storage']

class MMKVCoreStorage implements StorageTypeFromCore {
storage = new MMKVStorage('WC2')

getEntries<T = never>(): Promise<[string, T][]> {
const keys = this.storage.getAllKeys()
const values: [string, T][] = keys.map(key => [
key,
this.storage.get(key) as T,
])
return Promise.resolve(values)
}

getItem<T = never>(key: string): Promise<T | undefined> {
return this.storage.get(key)
}

getKeys(): Promise<string[]> {
return Promise.resolve(this.storage.getAllKeys())
}

removeItem(key: string): Promise<void> {
this.storage.delete(key)
return Promise.resolve(undefined)
}

setItem<T = object>(key: string, value: T): Promise<void> {
this.storage.set(key, value as AcceptedValue)
return Promise.resolve(undefined)
}
}

export const createWeb3Wallet = async () => {
const projectId = getEnvSetting(SETTINGS.WALLETCONNECT2_PROJECT_ID) // this should change if we need to vary from testnet/mainnet by using getWalletSetting
const core = new Core({
projectId,
storage: new MMKVCoreStorage(),
})

return await Web3Wallet.init({
Expand All @@ -50,10 +86,61 @@ export const createWeb3Wallet = async () => {
})
}

const WALLETCONNECT_SUPPORTED_METHODS = [
'eth_sendTransaction',
'personal_sign',
'eth_signTransaction',
'eth_signTypedData',
]

const WALLETCONNECT_BUILD_SUPPORTED_CHAINS = (chainId: ChainTypesByIdType) => [
`eip155:${chainId}`,
]

const WALLETCONNECT_BUILD_SUPPORTED_ACCOUNTS = ({
walletAddress,
chainId,
}: {
walletAddress: string
chainId: ChainTypesByIdType
}) => [`eip155:${chainId}:${walletAddress}`]

const WALLETCONNECT_SUPPORTED_EVENTS = ['accountsChanged', 'chainChanged']

export const buildRskAllowedNamespaces = ({
proposal,
chainId,
walletAddress,
}: {
proposal: Web3WalletTypes.SessionProposal
chainId: ChainTypesByIdType
walletAddress: string
}) =>
buildApprovedNamespaces({
proposal: proposal.params,
supportedNamespaces: {
eip155: {
chains: WALLETCONNECT_BUILD_SUPPORTED_CHAINS(chainId),
methods: WALLETCONNECT_SUPPORTED_METHODS,
events: WALLETCONNECT_SUPPORTED_EVENTS,
accounts: WALLETCONNECT_BUILD_SUPPORTED_ACCOUNTS({
walletAddress,
chainId,
}),
},
},
})

export const rskWalletConnectNamespace = {
eip155: {
chains: ['eip155:31'], // @TODO implement eip155:30 here -> make this dynamic according to the current chainId in the app
methods: ['eth_sendTransaction', 'personal_sign'],
methods: [
'eth_sendTransaction',
'personal_sign',
'eth_signTransaction',
'eth_sign',
'eth_signTypedData',
],
events: ['chainChanged', 'accountsChanged'],
},
}
Expand Down
9 changes: 8 additions & 1 deletion src/storage/MMKVStorage.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { MMKV } from 'react-native-mmkv'
import { initializeMMKVFlipper } from 'react-native-mmkv-flipper-plugin'

type AcceptedValue = boolean | string | number | object
export type AcceptedValue = boolean | string | number | object

export class MMKVStorage {
private id = 'mmkv.default'
Expand Down Expand Up @@ -51,4 +51,11 @@ export class MMKVStorage {
this.storage.clearAll()
}
}

public getAllKeys() {
if (this.storage) {
return this.storage.getAllKeys()
}
return []
}
}
5 changes: 3 additions & 2 deletions src/ux/requestsModal/RequestHandler.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { RequestWithBitcoin } from 'shared/types'
import { ReviewBitcoinTransactionContainer } from 'src/ux/requestsModal/ReviewBitcoinTransactionContainer'

import { ReviewTransactionContainer } from './ReviewRelayTransaction/ReviewTransactionContainer'
import { SignMessageRequestContainer } from './SignMessageRequestContainer'
import { SignRequestHandlerContainer } from './SignRequestHandlerContainer'

interface Props {
request: RequestWithBitcoin
Expand All @@ -29,7 +29,8 @@ const RequestTypeSwitch = ({
ComponentToRender = ReviewBitcoinTransactionContainer
break
case 'signMessage':
ComponentToRender = SignMessageRequestContainer
case 'signTypedData':
ComponentToRender = SignRequestHandlerContainer
break
default:
return null
Expand Down
25 changes: 25 additions & 0 deletions src/ux/requestsModal/SignMessageComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { useTranslation } from 'react-i18next'
import { StyleSheet } from 'react-native'

import { Typography } from 'src/components'
import { castStyle } from 'shared/utils'

interface Props {
message: string
}

export const SignMessageComponent = ({ message }: Props) => {
const { t } = useTranslation()

return (
<Typography type={'h3'} style={styles.typographyRowStyle}>
{t('Message')}: {message.toString() || ''}
</Typography>
)
}

const styles = StyleSheet.create({
typographyRowStyle: castStyle.text({
marginBottom: 20,
}),
})
Original file line number Diff line number Diff line change
@@ -1,37 +1,43 @@
import { StyleSheet, View } from 'react-native'
import { SignMessageRequest } from '@rsksmart/rif-wallet-core'
import {
SignMessageRequest,
SignTypedDataRequest,
} from '@rsksmart/rif-wallet-core'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
import { useTranslation } from 'react-i18next'

import { castStyle } from 'shared/utils'
import { sharedColors } from 'shared/constants'
import { AppButton, Typography } from 'src/components'

import { SignMessageComponent } from './SignMessageComponent'
import { SignTypedDataComponent } from './SignTypedDataComponent'

interface SignMessageRequestContainerProps {
request: SignMessageRequest
request: SignMessageRequest | SignTypedDataRequest
onConfirm: () => void
onCancel: () => void
}

export const SignMessageRequestContainer = ({
export const SignRequestHandlerContainer = ({
request,
onCancel,
onConfirm,
}: SignMessageRequestContainerProps) => {
const insets = useSafeAreaInsets()
const { t } = useTranslation()
const {
type,
payload: [message],
} = request
const { type, payload } = request

const onConfirmTap = async () => {
await request.confirm()
onConfirm()
}

const onCancelTap = () => {
request.reject('User rejected')
onCancel()
}

return (
<View style={[styles.viewContainer, { paddingTop: insets.top || 40 }]}>
<Typography type={'h2'} style={styles.typographyHeaderStyle}>
Expand All @@ -40,9 +46,12 @@ export const SignMessageRequestContainer = ({
<Typography type={'h3'} style={styles.typographyRowStyle}>
{t('dapps_sign_message_request_type')}: {type}
</Typography>
<Typography type={'h3'} style={styles.typographyRowStyle}>
{t('Message')}: {message.toString() || ''}
</Typography>

{type === 'signMessage' && (
<SignMessageComponent message={payload[0].toString()} />
)}
{type === 'signTypedData' && <SignTypedDataComponent payload={payload} />}

<View style={styles.buttonsViewStyle}>
<AppButton
accessibilityLabel="Confirm"
Expand Down
49 changes: 49 additions & 0 deletions src/ux/requestsModal/SignTypedDataComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { ScrollView } from 'react-native'
import { SignTypedDataArgs } from '@rsksmart/rif-wallet-core/dist/types'

import { Typography } from 'src/components'

import { requestStyles as styles } from './styles'

interface Props {
payload: SignTypedDataArgs
}
export const SignTypedDataComponent = ({ payload }: Props) => {
const [domain, , messageData] = payload
return (
<ScrollView>
<Typography type={'h2'} style={styles.typographyRowStyle}>
Domain:
</Typography>
<Typography type="h3" style={styles.typographyRowStyle}>
Name: {domain.name}
</Typography>
<Typography type="h3" style={styles.typographyRowStyle}>
Version: {domain.version}
</Typography>
<Typography type="h3" style={styles.typographyRowStyle}>
ChainId: {domain.chainId}
</Typography>
<Typography type="h3" style={styles.typographyRowStyle}>
Verifying Contract: {domain.verifyingContract}
</Typography>

<Typography type={'h2'} style={styles.typographyRowStyle}>
Message:
</Typography>
<Typography type="h3" style={styles.typographyRowStyle}>
From: {messageData.from.name} ({messageData.from.wallet})
</Typography>
<Typography type="h3" style={styles.typographyRowStyle}>
To: {messageData.to.name} ({messageData.to.wallet})
</Typography>

<Typography type={'h2'} style={styles.typographyRowStyle}>
Contents:
</Typography>
<Typography type="h3" style={styles.typographyRowStyle}>
{messageData.contents}
</Typography>
</ScrollView>
)
}
12 changes: 12 additions & 0 deletions src/ux/requestsModal/styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { StyleSheet } from 'react-native'

import { castStyle } from 'shared/utils'

export const requestStyles = StyleSheet.create({
typographyHeaderStyle: castStyle.text({
marginBottom: 40,
}),
typographyRowStyle: castStyle.text({
marginBottom: 20,
}),
})
Loading

0 comments on commit c9244a2

Please sign in to comment.