Skip to content

Commit

Permalink
feat(suite): #4512 AOPP integration to Sign&Verify
Browse files Browse the repository at this point in the history
  • Loading branch information
marekrjpolak committed Nov 29, 2021
1 parent 1947a7d commit 48a5687
Show file tree
Hide file tree
Showing 6 changed files with 181 additions and 14 deletions.
61 changes: 57 additions & 4 deletions packages/suite/src/actions/wallet/signVerifyActions.ts
@@ -1,7 +1,11 @@
import TrezorConnect, { ButtonRequestMessage, UI, Unsuccessful, Success } from 'trezor-connect';
import { SIGN_VERIFY } from './constants';
import { addToast } from '@suite-actions/notificationActions';
import { openModal } from '@suite-actions/modalActions';
import { PROTOCOL_SCHEME } from '@suite-reducers/protocolReducer';
import { openModal, openDeferredModal } from '@suite-actions/modalActions';
import { unfilteredFetch } from '@suite-utils/env';
import { getProtocolInfo } from '@suite-utils/parseUri';
import { isHex } from '@wallet-utils/ethUtils';
import type { Dispatch, GetState, TrezorDevice } from '@suite-types';
import type { Account } from '@wallet-types';

Expand Down Expand Up @@ -74,7 +78,7 @@ const showAddressByNetwork =
};

const signByNetwork =
(path: string | number[], message: string, hex: boolean) =>
(path: string | number[], message: string, hex: boolean, aopp: boolean) =>
({ account, device, coin, useEmptyPassphrase }: StateParams) => {
const params = {
device,
Expand All @@ -83,6 +87,7 @@ const signByNetwork =
message,
useEmptyPassphrase,
hex,
no_script_type: aopp,
};
switch (account.networkType) {
case 'bitcoin':
Expand Down Expand Up @@ -167,10 +172,10 @@ export const showAddress =
.catch(onError(dispatch, 'verify-address-error'));

export const sign =
(path: string | number[], message: string, hex = false) =>
(path: string | number[], message: string, hex = false, aopp = false) =>
(dispatch: Dispatch, getState: GetState) =>
getStateParams(getState)
.then(signByNetwork(path, message, hex))
.then(signByNetwork(path, message, hex, aopp))
.then(throwWhenFailed)
.then(onSignSuccess(dispatch))
.catch(onError(dispatch, 'sign-message-error'));
Expand All @@ -183,3 +188,51 @@ export const verify =
.then(throwWhenFailed)
.then(onVerifySuccess(dispatch))
.catch(onError(dispatch, 'verify-message-error'));

export const importAopp = (symbol?: Account['symbol']) => async (dispatch: Dispatch) => {
const result = await dispatch(openDeferredModal({ type: 'qr-reader', allowPaste: true }));
if (!result?.uri) return;
const info = getProtocolInfo(result.uri);
if (info?.scheme !== PROTOCOL_SCHEME.AOPP) return;
if (info.asset && symbol && info.asset !== symbol) return;
return {
asset: info.asset,
message: info.msg,
callback: info.callback,
format: info.format,
};
};

export const sendAopp =
(address: string, sig: string, callback: string) =>
async (dispatch: Dispatch, getState: GetState) => {
const network = getState().wallet.selectedAccount.account?.networkType;
const signature =
network === 'ethereum' && isHex(sig) ? Buffer.from(sig, 'hex').toString('base64') : sig;

const confirm = await dispatch(
openDeferredModal({ type: 'send-aopp-message', address, signature, callback }),
);
if (!confirm) return;

const { success, error } = await unfilteredFetch(callback, {
method: 'POST',
headers: { 'content-type': 'application/json; utf-8' },
body: JSON.stringify({
version: 0,
address,
signature,
}),
})
.then(res => ({
success: res.status === 204,
error: undefined,
}))
.catch(err => ({
success: false,
error: err.message,
}));

if (success) dispatch(addToast({ type: 'aopp-success' }));
else dispatch(addToast({ type: 'aopp-error', error }));
};
17 changes: 17 additions & 0 deletions packages/suite/src/components/suite/hocNotification/index.tsx
Expand Up @@ -24,6 +24,23 @@ const hocNotification = (notification: NotificationEntry, View: React.ComponentT
return withCoinProtocol(View, notification);
case 'aopp-protocol':
return withAoppProtocol(View, notification);
case 'aopp-success':
return simple(View, {
notification,
variant: 'success',
message: 'TOAST_AOPP_SUCCESS',
});
case 'aopp-error':
return simple(View, {
notification,
variant: 'error',
message: {
id: 'TOAST_AOPP_ERROR',
values: {
error: notification.error,
},
},
});
case 'acquire-error':
return simple(View, {
notification,
Expand Down
45 changes: 43 additions & 2 deletions packages/suite/src/hooks/wallet/sign-verify/useSignVerifyForm.ts
@@ -1,10 +1,12 @@
import { useEffect } from 'react';
import { useEffect, useCallback } from 'react';
import { useForm, useController } from 'react-hook-form';
import { useTranslation } from '@suite-hooks';
import { useTranslation, useSelector, useActions } from '@suite-hooks';
import * as protocolActions from '@suite-actions/protocolActions';
import { isHex } from '@wallet-utils/ethUtils';
import { isASCII } from '@suite-utils/validators';
import { isAddressValid } from '@wallet-utils/validation';
import type { Account } from '@wallet-types';
import type { AoppState } from '@suite-reducers/protocolReducer';

export const MAX_LENGTH_MESSAGE = 1024;
export const MAX_LENGTH_SIGNATURE = 255;
Expand All @@ -15,6 +17,8 @@ export type SignVerifyFields = {
path: string;
signature: string;
hex: boolean;
aopp: boolean;
callback: string;
};

const DEFAULT_VALUES: SignVerifyFields = {
Expand All @@ -23,6 +27,31 @@ const DEFAULT_VALUES: SignVerifyFields = {
path: '',
signature: '',
hex: false,
aopp: false,
callback: '',
};

const useAoppListener = (account: Account | undefined, setAopp: (aopp: AoppState) => void) => {
const { aoppState } = useSelector(state => ({
aoppState: state.protocol.aopp,
}));

const { fillAopp } = useActions({
fillAopp: protocolActions.fillAopp,
});

const shouldFill = useCallback(
(aopp: typeof aoppState): aopp is AoppState =>
!!aopp.shouldFill && !!aopp.message && aopp.asset === account?.symbol,
[account],
);

useEffect(() => {
if (shouldFill(aoppState)) {
setAopp(aoppState);
fillAopp(false);
}
}, [aoppState, shouldFill, fillAopp, setAopp]);
};

export const useSignVerifyForm = (page: 'sign' | 'verify', account?: Account) => {
Expand Down Expand Up @@ -71,6 +100,9 @@ export const useSignVerifyForm = (page: 'sign' | 'verify', account?: Account) =>
name: 'hex',
});

register('aopp');
register('callback');

const messageRef = register({
maxLength: {
value: MAX_LENGTH_MESSAGE,
Expand Down Expand Up @@ -117,6 +149,14 @@ export const useSignVerifyForm = (page: 'sign' | 'verify', account?: Account) =>
});
}, [reset, account, page]);

const formSetAopp = (aopp: AoppState) => {
setValue('aopp', !!aopp);
setValue('callback', aopp?.callback ?? '');
setValue('message', aopp?.message ?? '');
};

useAoppListener(account, formSetAopp);

return {
formDirty: isDirty,
formReset: () => reset(),
Expand All @@ -129,6 +169,7 @@ export const useSignVerifyForm = (page: 'sign' | 'verify', account?: Account) =>
signature: errors.signature?.message,
},
formSetSignature: (value: string) => setValue('signature', value),
formSetAopp,
messageRef,
signatureRef,
hexField: {
Expand Down
4 changes: 3 additions & 1 deletion packages/suite/src/reducers/suite/notificationReducer.ts
Expand Up @@ -30,7 +30,8 @@ export type ToastPayload = (
| 'backup-success'
| 'backup-failed'
| 'sign-message-success'
| 'verify-message-success';
| 'verify-message-success'
| 'aopp-success';
}
| {
type: 'tx-sent';
Expand Down Expand Up @@ -65,6 +66,7 @@ export type ToastPayload = (
| 'verify-address-error'
| 'sign-message-error'
| 'verify-message-error'
| 'aopp-error'
| 'sign-tx-error'
| 'metadata-auth-error'
| 'metadata-not-found-error'
Expand Down
16 changes: 16 additions & 0 deletions packages/suite/src/support/messages.ts
Expand Up @@ -6882,6 +6882,14 @@ export default defineMessages({
id: 'TR_DO_YOU_REALLY_WANT_TO_SKIP',
defaultMessage: 'Do you really want to skip this step?',
},
TR_AOPP_IMPORT: {
id: 'TR_AOPP_IMPORT',
defaultMessage: 'Import AOPP',
},
TR_AOPP_SEND: {
id: 'TR_AOPP_SEND',
defaultMessage: 'Send ownership proof',
},
TOAST_AOPP_FILL_HEADER: {
id: 'TOAST_AOPP_FILL_HEADER',
defaultMessage: 'Go to Sign & Verify form',
Expand All @@ -6890,4 +6898,12 @@ export default defineMessages({
id: 'TOAST_AOPP_FILL_ACTION',
defaultMessage: 'Fill form',
},
TOAST_AOPP_SUCCESS: {
id: 'TOAST_AOPP_SUCCESS',
defaultMessage: 'Address ownership proof sent',
},
TOAST_AOPP_ERROR: {
id: 'TOAST_AOPP_ERROR',
defaultMessage: 'Address ownership proof failed: {error}',
},
});
52 changes: 45 additions & 7 deletions packages/suite/src/views/wallet/sign-verify/index.tsx
Expand Up @@ -4,7 +4,7 @@ import { Input, Button, Textarea, Card, Switch, variables } from '@trezor/compon
import { WalletLayout, WalletLayoutHeader } from '@wallet-components';
import { CharacterCount, Translation } from '@suite-components';
import { useActions, useDevice, useSelector, useTranslation } from '@suite-hooks';
import { sign as signAction, verify as verifyAction } from '@wallet-actions/signVerifyActions';
import * as signVerifyActions from '@wallet-actions/signVerifyActions';
import Navigation, { NavPages } from './components/Navigation';
import SignAddressInput from './components/SignAddressInput';
import { useCopySignedMessage } from '@wallet-hooks/sign-verify/useCopySignedMessage';
Expand All @@ -21,6 +21,9 @@ const Row = styled.div`
& + & {
padding-top: 12px;
}
& > * + * {
margin-left: 10px;
}
`;

const StyledButton = styled(Button)`
Expand Down Expand Up @@ -49,7 +52,7 @@ const SignVerify = () => {
revealedAddresses: state.wallet.receive,
}));

const { isLocked } = useDevice();
const { isLocked, device } = useDevice();
const { translationString } = useTranslation();

const {
Expand All @@ -59,22 +62,25 @@ const SignVerify = () => {
formValues,
formErrors,
formSetSignature,
formSetAopp,
messageRef,
signatureRef,
hexField,
addressField,
pathField,
} = useSignVerifyForm(page, selectedAccount.account);

const { sign, verify } = useActions({
sign: signAction,
verify: verifyAction,
const { sign, verify, importAopp, sendAopp } = useActions({
sign: signVerifyActions.sign,
verify: signVerifyActions.verify,
importAopp: signVerifyActions.importAopp,
sendAopp: signVerifyActions.sendAopp,
});

const onSubmit = async (data: SignVerifyFields) => {
const { address, path, message, signature, hex } = data;
const { address, path, message, signature, hex, aopp } = data;
if (page === 'sign') {
const result = await sign(path, message, hex);
const result = await sign(path, message, hex, aopp);
if (result?.signSignature) formSetSignature(result.signSignature);
} else {
await verify(address, message, signature, hex);
Expand All @@ -88,6 +94,21 @@ const SignVerify = () => {
return (
<WalletLayout title="TR_NAV_SIGN_VERIFY" account={selectedAccount}>
<WalletLayoutHeader title="TR_NAV_SIGN_VERIFY">
{!device?.unavailableCapabilities?.aopp && (
/* TODO: This button available only for networks with aopp feature */
<Button
type="button"
data-test="@sign-verify/import-aopp"
variant="tertiary"
onClick={() =>
importAopp(selectedAccount.account?.symbol).then(
aopp => aopp && formSetAopp(aopp),
)
}
>
<Translation id="TR_AOPP_IMPORT" />
</Button>
)}
{page === 'sign' && canCopy && (
<Button type="button" variant="tertiary" onClick={copy}>
<Translation id="TR_COPY_TO_CLIPBOARD" />
Expand Down Expand Up @@ -182,6 +203,23 @@ const SignVerify = () => {
>
<Translation id={page === 'sign' ? 'TR_SIGN' : 'TR_VERIFY'} />
</StyledButton>
{formValues.callback && (
<StyledButton
type="button"
variant="secondary"
data-test="@sign-verify/send-aopp"
isDisabled={!formValues.signature}
onClick={() =>
sendAopp(
formValues.address,
formValues.signature,
formValues.callback,
)
}
>
<Translation id="TR_AOPP_SEND" />
</StyledButton>
)}
</Row>
</Form>
</Card>
Expand Down

0 comments on commit 48a5687

Please sign in to comment.