-
-
Notifications
You must be signed in to change notification settings - Fork 650
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Parity Signer #1349
Parity Signer #1349
Changes from 10 commits
b9a6319
83feb9a
8749a8a
ac6149b
5ee0013
b73a4b4
4a7dd0f
ecdfab7
e04e2e6
a7380d0
1979c68
047b7a9
89397e9
42323be
0753ce8
5c4396d
e64aa22
05419a6
234d199
3f4a578
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import * as types from './actionTypes'; | ||
import { TypeKeys } from './constants'; | ||
|
||
export type TRequestSignature = typeof requestSignature; | ||
export function requestSignature(from: string, rlp: string): types.RequestSignatureAction { | ||
return { | ||
type: TypeKeys.PARITY_SIGNER_REQUEST_SIGNATURE, | ||
payload: { | ||
from, | ||
rlp | ||
} | ||
}; | ||
} | ||
|
||
export type TFinalize = typeof finalize; | ||
export function finalize(signature: string | null): types.FinalizeAction { | ||
return { | ||
type: TypeKeys.PARITY_SIGNER_FINALIZE, | ||
payload: signature | ||
}; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { TypeKeys } from './constants'; | ||
|
||
export interface RequestSignatureAction { | ||
type: TypeKeys.PARITY_SIGNER_REQUEST_SIGNATURE; | ||
payload: { | ||
rlp: string; | ||
from: string; | ||
}; | ||
} | ||
|
||
export interface FinalizeAction { | ||
type: TypeKeys.PARITY_SIGNER_FINALIZE; | ||
payload: string | null; | ||
} | ||
|
||
/*** Union Type ***/ | ||
export type ParitySignerAction = RequestSignatureAction | FinalizeAction; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export enum TypeKeys { | ||
PARITY_SIGNER_REQUEST_SIGNATURE = 'PARITY_SIGNER_REQUEST_SIGNATURE', | ||
PARITY_SIGNER_FINALIZE = 'PARITY_SIGNER_FINALIZE' | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export * from './constants'; | ||
export * from './actionTypes'; | ||
export * from './actionCreators'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,9 +22,10 @@ export const DISABLE_WALLETS: { [key in WalletMode]: DisabledWallets } = { | |
} | ||
}, | ||
[WalletMode.UNABLE_TO_SIGN]: { | ||
wallets: [SecureWalletName.TREZOR, MiscWalletName.VIEW_ONLY], | ||
wallets: [SecureWalletName.TREZOR, SecureWalletName.PARITY_SIGNER, MiscWalletName.VIEW_ONLY], | ||
reasons: { | ||
[SecureWalletName.TREZOR]: 'This wallet can’t sign messages', | ||
[SecureWalletName.PARITY_SIGNER]: 'This wallet can’t sign messages', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not your responsibility, but just a reminder that we should be translating this. |
||
[MiscWalletName.VIEW_ONLY]: 'This wallet can’t sign messages' | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
.ParitySignerUnlock { | ||
&-badge { | ||
display: inline-block; | ||
height: 3em; | ||
margin: 0 0.4em; | ||
} | ||
|
||
&-qr-bounds { | ||
width: 300px; | ||
height: 300px; | ||
display: inline-block; | ||
margin-bottom: 1.5em; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import React, { PureComponent } from 'react'; | ||
import { connect } from 'react-redux'; | ||
import translate from 'translations'; | ||
import { NewTabLink } from 'components/ui'; | ||
import QrSigner from '@parity/qr-signer'; | ||
import { isValidETHAddress } from 'libs/validators'; | ||
import { ParitySignerWallet } from 'libs/wallet'; | ||
import { showNotification, TShowNotification } from 'actions/notifications'; | ||
import './index.scss'; | ||
import AppStoreBadge from 'assets/images/mobile/app-store-badge.png'; | ||
import GooglePlayBadge from 'assets/images/mobile/google-play-badge.png'; | ||
|
||
interface Props { | ||
showNotification: TShowNotification; | ||
onUnlock(param: any): void; | ||
} | ||
|
||
class ParitySignerDecrypt extends PureComponent<Props> { | ||
public render() { | ||
return ( | ||
<div className="ParitySignerUnlock"> | ||
<div className="ParitySignerUnlock-qr-bounds"> | ||
<QrSigner | ||
size={300} | ||
scan={true} | ||
onScan={this.unlockAddress} | ||
styles={{ display: 'inline-block' }} | ||
/> | ||
</div> | ||
<p>{translate('ADD_PARITY_2')}</p> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. At some point we should put together a help article that explains this process, or have some additional descriptive copy here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We are working on a proper help page that describes how to use the signer, I'll add a link to it here once it's done (ideally before publicity but can be done after the PR is merged). |
||
<p> | ||
<NewTabLink href="https://itunes.apple.com/us/app/parity-signer/id1218174838"> | ||
<img className="ParitySignerUnlock-badge" src={AppStoreBadge} alt="App Store" /> | ||
</NewTabLink> | ||
<NewTabLink href="https://play.google.com/store/apps/details?id=com.nativesigner"> | ||
<img className="ParitySignerUnlock-badge" src={GooglePlayBadge} alt="Google Play" /> | ||
</NewTabLink> | ||
</p> | ||
</div> | ||
); | ||
} | ||
|
||
private unlockAddress = (address: string) => { | ||
if (!isValidETHAddress(address)) { | ||
this.props.showNotification('danger', 'Not a valid address!'); | ||
return; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's the user experience for when this happens? Does the signer keep watching for a potentially valid address? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep, it will keep on scanning until a QR code with a valid address is presented. |
||
|
||
this.props.onUnlock(new ParitySignerWallet(address)); | ||
}; | ||
} | ||
|
||
export default connect(() => ({}), { showNotification })(ParitySignerDecrypt); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
.QrSignerModal { | ||
&-qr-bounds { | ||
width: 300px; | ||
height: 300px; | ||
display: inline-block; | ||
margin: 0.3em 0; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
import React from 'react'; | ||
import { connect } from 'react-redux'; | ||
import translate, { translateRaw } from 'translations'; | ||
import QrSigner from '@parity/qr-signer'; | ||
import { AppState } from 'reducers'; | ||
import Modal, { IButton } from 'components/ui/Modal'; | ||
import { TFinalize, finalize } from 'actions/paritySigner'; | ||
import './index.scss'; | ||
|
||
interface State { | ||
scan: boolean; | ||
} | ||
|
||
interface PropsClosed { | ||
isOpen: false; | ||
finalize: TFinalize; | ||
} | ||
|
||
interface PropsOpen { | ||
isOpen: true; | ||
rlp: string; | ||
from: string; | ||
finalize: TFinalize; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We usually split out props into props that get specified by parent component (OwnProps), props that are state from redux (StateProps), and props that are actions (ActionProps), so that we can properly type everything. See something like |
||
} | ||
|
||
type Props = PropsClosed | PropsOpen; | ||
|
||
class QrSignerModal extends React.Component<Props, State> { | ||
constructor(props: Props) { | ||
super(props); | ||
this.state = { | ||
scan: false | ||
}; | ||
} | ||
|
||
public render() { | ||
if (!this.props.isOpen) { | ||
return null; | ||
} | ||
|
||
const { scan } = this.state; | ||
const { from, rlp } = this.props; | ||
|
||
const buttons: IButton[] = [ | ||
{ | ||
disabled: false, | ||
text: translate(scan ? 'ACTION_4' : 'ADD_PARITY_3'), | ||
type: 'primary', | ||
onClick: () => this.setState({ scan: !scan }) | ||
}, | ||
{ | ||
disabled: false, | ||
text: translate('ACTION_2'), | ||
type: 'default', | ||
onClick: this.onClose | ||
} | ||
]; | ||
|
||
return ( | ||
<div className="QrSignerModal"> | ||
<Modal | ||
title={translateRaw('DEP_SIGNTX')} | ||
isOpen={true} | ||
buttons={buttons} | ||
handleClose={this.onClose} | ||
> | ||
<div className="QrSignerModal-qr-bounds"> | ||
<QrSigner size={300} scan={scan} account={from} rlp={rlp} onScan={this.onScan} /> | ||
</div> | ||
</Modal> | ||
</div> | ||
); | ||
} | ||
|
||
private onClose = () => { | ||
if (!this.props.isOpen) { | ||
return; | ||
} | ||
|
||
this.props.finalize(null); | ||
this.setState({ scan: false }); | ||
}; | ||
|
||
private onScan = (signature: string) => { | ||
if (!this.props.isOpen) { | ||
return; | ||
} | ||
|
||
this.props.finalize(signature); | ||
this.setState({ scan: false }); | ||
}; | ||
} | ||
|
||
function mapStateToProps(state: AppState) { | ||
const { requested } = state.paritySigner; | ||
|
||
if (!requested) { | ||
return { isOpen: false }; | ||
} | ||
|
||
const { from, rlp } = requested; | ||
|
||
return { | ||
isOpen: true, | ||
from, | ||
rlp | ||
}; | ||
} | ||
|
||
export default connect(mapStateToProps, { finalize })(QrSignerModal); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import { IFullWallet } from '../IWallet'; | ||
|
||
export default class ParitySignerWallet implements IFullWallet { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Appreciate the consistent nomenclature 👍 |
||
public address: string; | ||
|
||
constructor(address: string) { | ||
this.address = address; | ||
} | ||
|
||
public signRawTransaction = () => | ||
Promise.reject(new Error('Web3 wallets cannot sign raw transactions.')); | ||
|
||
public signMessage = () => | ||
Promise.reject(new Error('Signing via Parity Signer not yet supported.')); | ||
|
||
public getAddressString() { | ||
return this.address; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very minor, but I think it would improve code understanding if the word "signature" was a part of this action as well.