import Clipboard from '@react-native-clipboard/clipboard'
import { fireEvent, render, within } from '@testing-library/react-native'
import { SessionTypes } from '@walletconnect/types'
import { getSdkError } from '@walletconnect/utils'
import { Web3WalletTypes } from '@walletconnect/web3wallet'
import * as React from 'react'
import 'react-native'
import { Provider } from 'react-redux'
import { ActiveDapp, DappSection } from 'src/dapps/types'
import { SerializableTransactionRequest } from 'src/viem/preparedTransactionSerialization'
import {
acceptRequest as acceptRequestV2,
denyRequest as denyRequestV2,
} from 'src/walletConnect/actions'
import ActionRequest from 'src/walletConnect/screens/ActionRequest'
import { createMockStore } from 'test/utils'
import { mockAccount, mockAccount2 } from 'test/values'
describe('ActionRequest with WalletConnect V2', () => {
const v2Session: SessionTypes.Struct = {
expiry: 1670411909,
self: {
metadata: {
icons: ['https://example.com/favicon.ico'],
description: 'A mobile payments wallet that works worldwide',
name: 'App Name',
url: 'https://example.com/',
},
publicKey: 'b991206845c62280479fd1f24087e9c6f0df3921b5f9d94f4619fbf995a81149',
},
relay: {
protocol: 'irn',
},
peer: {
metadata: {
name: 'WalletConnect Example',
description: '',
icons: [],
url: 'https://react-app.walletconnect.com',
},
publicKey: '3c78ff702b703e873a90a9619598effa0e3b01deb977cb277d3b0eecff3a0320',
},
controller: 'b991206845c62280479fd1f24087e9c6f0df3921b5f9d94f4619fbf995a81149',
namespaces: {
eip155: {
accounts: ['eip155:44787:0x047154ac4d7e01b1dc9ddeea9e8996b57895a747'],
methods: [
'eth_sendTransaction',
'eth_signTransaction',
'eth_sign',
'personal_sign',
'eth_signTypedData',
],
events: ['chainChanged', 'accountsChanged'],
},
},
acknowledged: true,
topic: 'd8afe1f5c3efa38bbb62c68005f572a7218afcd48703e4b02bdc5df2549ac5b5',
pairingTopic: '20eca0383221cb6feb7af40d06d5cdd867965dd885e9ad36fb4540d9cc25267b',
requiredNamespaces: {
eip155: {
methods: [
'eth_sendTransaction',
'eth_signTransaction',
'eth_sign',
'personal_sign',
'eth_signTypedData',
],
chains: ['eip155:44787'],
events: ['chainChanged', 'accountsChanged'],
},
},
optionalNamespaces: {},
}
const pendingAction: Web3WalletTypes.EventArguments['session_request'] = {
id: 1669810746892321,
topic: 'd8afe1f5c3efa38bbb62c68005f572a7218afcd48703e4b02bdc5df2549ac5b5',
params: {
chainId: 'eip155:44787',
request: {
method: 'personal_sign',
params: [
'0x4d65737361676520746f207369676e', // hex of 'Message to sign'
'0x047154ac4d7e01b1dc9ddeea9e8996b57895a747',
],
},
},
verifyContext: {
verified: {
origin: '',
validation: 'UNKNOWN',
verifyUrl: '',
},
},
}
const sendTransactionAction = {
...pendingAction,
params: {
...pendingAction.params,
request: {
...pendingAction.params.request,
method: 'eth_sendTransaction',
},
},
}
const preparedTransaction: SerializableTransactionRequest = {
from: mockAccount,
to: mockAccount2,
data: '0xTEST',
nonce: 100,
maxFeePerGas: '12000000000',
maxPriorityFeePerGas: '2000000000',
gas: '100000',
_baseFeePerGas: '5000000000',
}
const supportedChains = ['eip155:44787']
describe('eth_sendTransaction', () => {
const store = createMockStore({
walletConnect: {
sessions: [v2Session],
},
})
beforeEach(() => {
store.clearActions()
})
it('should display a dismiss-only bottom sheet if the user has insufficient gas funds', () => {
const { getByText, queryByText } = render(
)
expect(getByText('walletConnectRequest.notEnoughBalanceForGas.title')).toBeTruthy()
expect(
getByText(
'walletConnectRequest.notEnoughBalanceForGas.description, {"feeCurrencies":"CELO"}'
)
).toBeTruthy()
expect(queryByText('allow')).toBeFalsy()
fireEvent.press(getByText('dismiss'))
expect(store.getActions()).toEqual([
denyRequestV2(sendTransactionAction, getSdkError('USER_REJECTED')),
])
})
it("should display a dismiss-only bottom sheet if the transaction couldn't be prepared", () => {
const { getByText, queryByText } = render(
)
expect(getByText('walletConnectRequest.failedToPrepareTransaction.title')).toBeTruthy()
expect(
getByText(
'walletConnectRequest.failedToPrepareTransaction.description, {"errorMessage":"execution reverted"}'
)
).toBeTruthy()
expect(queryByText('allow')).toBeFalsy()
fireEvent.press(getByText('dismiss'))
expect(store.getActions()).toEqual([
denyRequestV2(sendTransactionAction, getSdkError('USER_REJECTED')),
])
})
it('should accept the request with the prepared transaction', () => {
const { getByText, getByTestId } = render(
)
expect(
within(getByTestId('WalletConnectRequest/ActionRequestPayload/Value')).getByText(
JSON.stringify(preparedTransaction)
)
).toBeTruthy()
expect(
getByText('walletConnectRequest.estimatedNetworkFee, {"networkName":"Celo Alfajores"}')
).toBeTruthy()
const fee = within(getByTestId('EstimatedNetworkFee'))
expect(fee.getByText('0.0007 CELO')).toBeTruthy() // gas * (_baseFeePerGas + maxPriorityFeePerGas)
expect(fee.getByText('₱0.0047')).toBeTruthy()
fireEvent.press(getByText('walletConnectRequest.sendTransactionAction'))
expect(store.getActions()).toEqual([
acceptRequestV2(sendTransactionAction, preparedTransaction),
])
})
})
describe('personal_sign', () => {
const store = createMockStore({
walletConnect: {
sessions: [v2Session],
},
})
beforeEach(() => {
store.clearActions()
})
it('renders the correct elements', () => {
const { getByText, getByTestId, queryByTestId } = render(
)
expect(getByText('walletConnectRequest.signPayloadTitle')).toBeTruthy()
expect(
getByText('walletConnectRequest.signPayload, {"dappName":"WalletConnect Example"}')
).toBeTruthy()
expect(getByText('allow')).toBeTruthy()
expect(
within(getByTestId('WalletConnectRequest/ActionRequestPayload/Value')).getByText(
'Message to sign'
)
).toBeTruthy()
expect(getByText('dappsDisclaimerUnlistedDapp')).toBeTruthy()
expect(queryByTestId('EstimatedNetworkFee')).toBeFalsy()
})
it('copies the request payload', () => {
const { getByTestId } = render(
)
fireEvent.press(getByTestId('WalletConnectRequest/ActionRequestPayload/Copy'))
expect(Clipboard.setString).toHaveBeenCalledWith('Message to sign')
})
it('shows request details with raw string if message cannot be decoded', () => {
pendingAction.params.request.params[0] = 'invalid hex'
const { getByTestId } = render(
)
expect(
within(getByTestId('WalletConnectRequest/ActionRequestPayload/Value')).getByText(
'invalid hex'
)
).toBeTruthy()
})
it('shows request details with empty message', () => {
pendingAction.params.request.params[0] = ''
const { getByTestId } = render(
)
expect(
within(getByTestId('WalletConnectRequest/ActionRequestPayload/Value')).getByText(
'action.emptyMessage'
)
).toBeTruthy()
})
it('dispatches the correct action on press allow', () => {
const { getByText } = render(
)
fireEvent.press(getByText('allow'))
expect(store.getActions()).toEqual([acceptRequestV2(pendingAction)])
})
it('dispatches the correct action on dismiss bottom sheet', () => {
const { unmount } = render(
)
unmount()
expect(store.getActions()).toEqual([
denyRequestV2(pendingAction, getSdkError('USER_REJECTED')),
])
})
})
describe('displayed dapp name fallbacks', () => {
const activeDapp: ActiveDapp = {
id: 'someDappId',
categories: ['someCategory'],
iconUrl: '',
name: 'someDappName',
description: '',
dappUrl: 'https://react-app.walletconnect.com',
openedFrom: DappSection.All,
}
it('should use the name from activeDapp if the request domain matches', () => {
const store = createMockStore({
dapps: {
dappsWebViewEnabled: true,
activeDapp,
},
walletConnect: {
sessions: [
{
...v2Session,
peer: {
metadata: {
name: '',
description: '',
icons: [],
url: 'https://react-app.walletconnect.com/somePath',
},
publicKey: '',
},
},
],
},
})
const { getByText } = render(
)
expect(getByText('walletConnectRequest.signPayloadTitle')).toBeTruthy()
expect(
getByText('walletConnectRequest.signPayload, {"dappName":"someDappName"}')
).toBeTruthy()
})
it("should use the payload domain if activeDapp doesn't match", () => {
const store = createMockStore({
dapps: {
dappsWebViewEnabled: true,
activeDapp,
},
walletConnect: {
sessions: [
{
...v2Session,
peer: {
metadata: {
name: '',
description: '',
icons: [],
url: 'https://some.dapp.com',
},
publicKey: '',
},
},
],
},
})
const { getByText } = render(
)
expect(getByText('walletConnectRequest.signPayloadTitle')).toBeTruthy()
expect(
getByText('walletConnectRequest.signPayload, {"dappName":"some.dapp.com"}')
).toBeTruthy()
})
it('should display an empty fallback', () => {
const store = createMockStore({
dapps: {
dappsWebViewEnabled: true,
activeDapp,
},
walletConnect: {
sessions: [
{
...v2Session,
peer: {
metadata: {
name: '',
description: '',
icons: [],
url: '',
},
publicKey: '',
},
},
],
},
})
const { getByText } = render(
)
expect(getByText('walletConnectRequest.signPayloadTitle')).toBeTruthy()
expect(getByText('walletConnectRequest.signPayload, {"dappName":""}')).toBeTruthy()
})
})
describe('unsupported chain', () => {
it.each([
[
'eth_sendTransaction',
'walletConnectRequest.sendTransactionTitle',
'walletConnectRequest.sendDappTransactionUnknownNetwork',
],
[
'eth_signTransaction',
'walletConnectRequest.signTransactionTitle',
'walletConnectRequest.signDappTransactionUnknownNetwork',
],
])('%s: should show a warning if the chain is not supported', (method, title, description) => {
const store = createMockStore({
walletConnect: {
sessions: [v2Session],
},
})
const { getByText, queryByText, queryByTestId } = render(
)
expect(getByText(title)).toBeTruthy()
expect(getByText(`${description}, {"dappName":"WalletConnect Example"}`)).toBeTruthy()
expect(queryByText('allow')).toBeFalsy()
expect(getByText('dismiss')).toBeTruthy()
expect(
getByText(
'walletConnectRequest.unsupportedChain.title, {"dappName":"WalletConnect Example","chainId":"eip155:123456"}'
)
).toBeTruthy()
expect(queryByTestId('EstimatedNetworkFee')).toBeFalsy()
})
it('should not show a warning if the chain is not supported and the method is personal_sign', () => {
const store = createMockStore({
walletConnect: {
sessions: [v2Session],
},
})
const { getByText, queryByText } = render(
)
expect(getByText('walletConnectRequest.signPayloadTitle')).toBeTruthy()
expect(
getByText('walletConnectRequest.signPayload, {"dappName":"WalletConnect Example"}')
).toBeTruthy()
expect(getByText('allow')).toBeTruthy()
expect(queryByText('dismiss')).toBeFalsy()
expect(
queryByText(
'walletConnectRequest.unsupportedChain.title, {"dappName":"WalletConnect Example","chainId":"eip155:1"}'
)
).toBeFalsy()
})
})
})