Skip to content

Commit

Permalink
Handle walletconnect signing requests
Browse files Browse the repository at this point in the history
  • Loading branch information
hbriese committed Aug 21, 2023
1 parent 5453f4a commit ad53433
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 45 deletions.
145 changes: 106 additions & 39 deletions app/src/components/walletconnect/useSessionRequestListener.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
import { gql } from '@api/generated';
import { useNavigation } from '@react-navigation/native';
import { Hex, asBigInt } from 'lib';
import { useCallback } from 'react';
import { asBigInt } from 'lib';
import { useEffect, useState } from 'react';
import { showInfo } from '~/provider/SnackbarProvider';
import { logError } from '~/util/analytics';
import { WalletConnectEventArgs, WcClient, asWalletConnectResult } from '~/util/walletconnect';
import { asWalletConnectResult, useWalletConnectWithoutWatching } from '~/util/walletconnect';
import {
SigningRequest,
WC_SIGNING_METHODS,
WC_TRANSACTION_METHODS,
WalletConnectSendTransactionRequest,
normalizeSigningRequest,
} from '~/util/walletconnect/methods';
import { EventEmitter } from '~/util/EventEmitter';
import { usePropose } from '@api/usePropose';
import { useQuery } from '~/gql';
import { useSubscription } from 'urql';

const PROPOSAL_EXECUTED_EMITTER = new EventEmitter<Hex>();
import { getOptimizedDocument, useQuery } from '~/gql';
import { Subject } from 'rxjs';
import { SignClientTypes } from '@walletconnect/types';
import { useMutation, useSubscription } from 'urql';
import { SessionRequestListener_ProposalSubscription } from '@api/generated/graphql';

const Query = gql(/* GraphQL */ `
query UseSessionRequestListener {
Expand All @@ -27,42 +28,66 @@ const Query = gql(/* GraphQL */ `
}
`);

const Subscription = gql(/* GraphQL */ `
subscription SessionRequestListener($accounts: [Address!]!) {
proposal(input: { accounts: $accounts, events: [executed] }) {
const ProposeMessage = gql(/* GraphQL */ `
mutation UseSessionRequestListener_ProposeMessage($input: ProposeMessageInput!) {
proposeMessage(input: $input) {
id
hash
}
}
`);

const ProposalSubscription = gql(/* GraphQL */ `
subscription SessionRequestListener_Proposal($accounts: [Address!]!) {
proposal(input: { accounts: $accounts, events: [approved, executed] }) {
__typename
id
hash
... on TransactionProposal {
transaction {
id
hash
}
}
... on MessageProposal {
signature
}
}
}
`);

type SessionRequestArgs = SignClientTypes.EventArguments['session_request'];

export const useSessionRequestListener = () => {
const { navigate } = useNavigation();
const propose = usePropose();
const proposeTransaction = usePropose();
const proposeMessage = useMutation(ProposeMessage)[1];
const client = useWalletConnectWithoutWatching();

const accounts = useQuery(Query).data.accounts.map((a) => a.address);

const [proposals] = useState(
new Subject<SessionRequestListener_ProposalSubscription['proposal']>(),
);
useEffect(() => proposals.unsubscribe, [proposals]);

const { accounts } = useQuery(Query).data;
useSubscription({
query: Subscription,
variables: { accounts: accounts.map((a) => a.address) },
});
useSubscription(
{ query: getOptimizedDocument(ProposalSubscription), variables: { accounts } },
(_prev, data) => {
proposals.next(data.proposal);
return data;
},
);

return useCallback(
async (client: WcClient, { id, topic, params }: WalletConnectEventArgs['session_request']) => {
useEffect(() => {
const handleRequest = async ({ id, topic, params }: SessionRequestArgs) => {
const method = params.request.method;
const peer = client.session.get(topic).peer.metadata;

if (WC_SIGNING_METHODS.has(method)) {
navigate('Sign', {
topic,
id,
request: params.request as SigningRequest,
});
} else if (WC_TRANSACTION_METHODS.has(method)) {
if (WC_TRANSACTION_METHODS.has(method)) {
const [tx] = (params.request as WalletConnectSendTransactionRequest).params;

const peer = client.session.get(topic).peer.metadata;
showInfo(`${peer.name} has proposed a transaction`);

const proposal = await propose({
const proposal = await proposeTransaction({
account: tx.from,
operations: [
{
Expand All @@ -74,20 +99,62 @@ export const useSessionRequestListener = () => {
gasLimit: tx.gasLimit ? asBigInt(tx.gasLimit) : undefined,
});

PROPOSAL_EXECUTED_EMITTER.listeners.add((proposalHash) => {
if (proposalHash === proposal) {
client.respond({
topic,
response: asWalletConnectResult(id, proposalHash),
});
showInfo(`${peer.name} has proposed a transaction`);

const sub = proposals.subscribe((p) => {
if (
p.hash === proposal &&
p.__typename === 'TransactionProposal' &&
p.transaction?.hash
) {
client.respond({ topic, response: asWalletConnectResult(id, p.transaction.hash) });
sub.unsubscribe();
}
});

navigate('Proposal', { proposal });

// sub is automatically unsubscribed due to proposalsExecuted unsubscribe on unmount
} else if (WC_SIGNING_METHODS.has(method)) {
const request = params.request as SigningRequest;
const { account, message } = normalizeSigningRequest(request);

// TODO: handle EIP712 messages
if (typeof message !== 'string') throw new Error('EIP712 signing unimplemented!');

const proposal = (
await proposeMessage({
input: {
account,
message,
label: `${peer.name} signature request`,
iconUri: peer.icons[0],
},
})
).data?.proposeMessage.hash;
if (!proposal) return;

showInfo(`${peer.name} has requested a signature`);

const sub = proposals.subscribe((p) => {
if (p.hash === proposal && p.__typename === 'MessageProposal' && p.signature) {
client.respond({ topic, response: asWalletConnectResult(id, p.signature) });
sub.unsubscribe();
}
});

navigate('MessageProposal', { proposal });

// sub is automatically unsubscribed due to proposalsApproved unsubscribe on unmount
} else {
logError('Unsupported session_request method executed', { params });
}
},
[navigate, propose],
);
};

client.on('session_request', handleRequest);

return () => {
client.off('session_request', handleRequest);
};
}, [client, navigate, proposals, proposeMessage, proposeTransaction]);
};
6 changes: 0 additions & 6 deletions app/src/util/walletconnect/types.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
import { SignClientTypes, SessionTypes } from '@walletconnect/types';
import { getSdkError } from '@walletconnect/utils';
import { formatJsonRpcResult, formatJsonRpcError } from '@walletconnect/jsonrpc-utils';

export type WalletConnectEvent = SignClientTypes.Event;
export type WalletConnectEventArgs = SignClientTypes.EventArguments;
export type WalletConnectPeer = SignClientTypes.Metadata;
export type WalletConnectSession = SessionTypes.Struct;

export const asWalletConnectResult = formatJsonRpcResult;

export type WalletConnectErrorKey = Parameters<typeof getSdkError>[0];
Expand Down

0 comments on commit ad53433

Please sign in to comment.