Skip to content
This repository has been archived by the owner on Jan 25, 2022. It is now read-only.

Commit

Permalink
Merge pull request #59 from MetaMask/recipient-address-audit
Browse files Browse the repository at this point in the history
Add support for recipient address audit plugins
  • Loading branch information
danfinlay authored Oct 10, 2019
2 parents 5cae165 + 3db17de commit 8859d80
Show file tree
Hide file tree
Showing 8 changed files with 129 additions and 20 deletions.
48 changes: 48 additions & 0 deletions app/scripts/controllers/address-audit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
const ObservableStore = require('obs-store')
const extend = require('xtend')

/**
* A controller that stores info about audited addresses
*/
class AddressAuditController {
/**
* Creates a AddressAuditController
*
* @param {Object} [config] - Options to configure controller
*/
constructor (opts = {}) {
const { initState } = opts
this.store = new ObservableStore(extend({
addressAudits: {},
}, initState))
}

add ({ address, auditor, status, message }) {
const currentState = this.store.getState()
const currentStateAudits = currentState.addressAudits
const currentAddressAudits = currentStateAudits && currentStateAudits[address] || {}

this.store.updateState({
addressAudits: {
...currentStateAudits,
[address]: {
...currentAddressAudits,
[auditor]: {
address,
auditor,
status,
message,
timestamp: Date.now(),
},
},
},
})
}

clearAudits () {
this.store.updateState({ audits: {} })
}

}

module.exports = AddressAuditController
1 change: 1 addition & 0 deletions app/scripts/controllers/permissions/restrictedMethods.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const pluginRestrictedMethodDescriptions = {
generateSignature: 'Sign messages with your account',

// MetaMaskController#getApi
addAddressAudit: 'Check the recipients of your transaction and show you warnings if they are untrustworthy',
addKnownMethodData: 'Update and store data about a known contract method',
addNewAccount: 'Adds a new account to the default (first) HD seed phrase Keyring',
addNewKeyring: 'Create a new keyring',
Expand Down
10 changes: 10 additions & 0 deletions app/scripts/metamask-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const DetectTokensController = require('./controllers/detect-tokens')
const { PermissionsController } = require('./controllers/permissions')
const PluginsController = require('./controllers/plugins')
const AssetsController = require('./controllers/assets')
const AddressAuditController = require('./controllers/address-audit')
const nodeify = require('./lib/nodeify')
const accountImporter = require('./account-import-strategies')
const getBuyEthUrl = require('./lib/buy-eth-url')
Expand Down Expand Up @@ -263,6 +264,10 @@ module.exports = class MetamaskController extends EventEmitter {
this.isClientOpenAndUnlocked = memState.isUnlocked && this._isClientOpen
})

this.addressAuditController = new AddressAuditController({
initState: initState.AddressAuditController,
})

this.pluginsController = new PluginsController({
setupProvider: this.setupProvider.bind(this),
_txController: this.txController,
Expand Down Expand Up @@ -318,6 +323,7 @@ module.exports = class MetamaskController extends EventEmitter {
PermissionsMetadata: this.permissionsController.store,
PluginsController: this.pluginsController.store,
ThreeBoxController: this.threeBoxController.store,
AddressAuditController: this.addressAuditController.store,
})

this.memStore = new ComposableObservableStore(null, {
Expand All @@ -343,6 +349,7 @@ module.exports = class MetamaskController extends EventEmitter {
PermissionsMetadata: this.permissionsController.store,
PluginsController: this.pluginsController.store,
AssetsController: this.assetsController.store,
AddressAuditController: this.addressAuditController.store,
ThreeBoxController: this.threeBoxController.store,
})
this.memStore.subscribe(this.sendUpdate.bind(this))
Expand Down Expand Up @@ -678,6 +685,9 @@ module.exports = class MetamaskController extends EventEmitter {

// onboarding controller
setSeedPhraseBackedUp: nodeify(onboardingController.setSeedPhraseBackedUp, onboardingController),

// addressAudit controller
addAddressAudit: this.addressAuditController.add.bind(this.addressAuditController),
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export default class ConfirmPageContainer extends Component {
toAddress: PropTypes.string,
toName: PropTypes.string,
toNickname: PropTypes.string,
recipientAudit: PropTypes.object,
// Content
contentComponent: PropTypes.node,
errorKey: PropTypes.string,
Expand Down Expand Up @@ -102,6 +103,7 @@ export default class ConfirmPageContainer extends Component {
lastTx,
ofText,
requestsWaitingText,
recipientAudit,
} = this.props
const renderAssetImage = contentComponent || (!contentComponent && !identiconAddress)

Expand Down Expand Up @@ -130,6 +132,7 @@ export default class ConfirmPageContainer extends Component {
recipientAddress={toAddress}
recipientNickname={toNickname}
assetImage={renderAssetImage ? assetImage : undefined}
audit={recipientAudit}
/>
</ConfirmPageContainerHeader>
{
Expand Down
26 changes: 25 additions & 1 deletion ui/app/components/ui/sender-to-recipient/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

&--default {
border-bottom: 1px solid $geyser;
height: 42px;
min-height: 42px;

.sender-to-recipient {
&__tooltip-wrapper {
Expand All @@ -19,6 +19,25 @@
max-width: 100%;
}

&__audit--warn,
&__audit--approve {
display: flex;
font-size: 12px;
width: 180px;
white-space: normal;
color: red;
}

&__audit--approve {
color: green;
}

&__party-group {
display: flex;
flex-direction: row;
align-items: center;
}

&__party {
display: flex;
flex-direction: row;
Expand All @@ -44,6 +63,11 @@
}
}

&__party--audit {
flex-direction: column;
align-items: flex-start;
}

&__arrow-container {
position: absolute;
height: 100%;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export default class SenderToRecipient extends PureComponent {
assetImage: PropTypes.string,
onRecipientClick: PropTypes.func,
onSenderClick: PropTypes.func,
audit: PropTypes.func,
}

static defaultProps = {
Expand Down Expand Up @@ -90,12 +91,14 @@ export default class SenderToRecipient extends PureComponent {

renderRecipientWithAddress () {
const { t } = this.context
const { recipientName, recipientAddress, recipientNickname, addressOnly, onRecipientClick } = this.props
const { recipientName, recipientAddress, recipientNickname, addressOnly, onRecipientClick, audit = {} } = this.props
const checksummedRecipientAddress = checksumAddress(recipientAddress)

return (
<div
className="sender-to-recipient__party sender-to-recipient__party--recipient sender-to-recipient__party--recipient-with-address"
className={classnames('sender-to-recipient__party sender-to-recipient__party--recipient sender-to-recipient__party--recipient-with-address', {
'sender-to-recipient__party--audit': Boolean(Object.keys(audit).length),
})}
onClick={() => {
this.setState({ recipientAddressCopied: true })
copyToClipboard(checksummedRecipientAddress)
Expand All @@ -104,23 +107,35 @@ export default class SenderToRecipient extends PureComponent {
}
}}
>
{ this.renderRecipientIdenticon() }
<Tooltip
position="bottom"
title={this.state.recipientAddressCopied ? t('copiedExclamation') : t('copyAddress')}
wrapperClassName="sender-to-recipient__tooltip-wrapper"
containerClassName="sender-to-recipient__tooltip-container"
onHidden={() => this.setState({ recipientAddressCopied: false })}
>
<div className="sender-to-recipient__name">
<span>{ addressOnly ? `${t('to')}: ` : '' }</span>
{
addressOnly
? checksummedRecipientAddress
: (recipientNickname || recipientName || this.context.t('newContract'))
}
</div>
</Tooltip>
<div className="sender-to-recipient__party-group">
{ this.renderRecipientIdenticon() }
<Tooltip
position="bottom"
title={this.state.recipientAddressCopied ? t('copiedExclamation') : t('copyAddress')}
wrapperClassName="sender-to-recipient__tooltip-wrapper"
containerClassName="sender-to-recipient__tooltip-container"
onHidden={() => this.setState({ recipientAddressCopied: false })}
>
<div className="sender-to-recipient__name">
<span>{ addressOnly ? `${t('to')}: ` : '' }</span>
{
addressOnly
? checksummedRecipientAddress
: (recipientNickname || recipientName || this.context.t('newContract'))
}
</div>
</Tooltip>
</div>
{
Object.keys(audit).length
? <div className={classnames({
'sender-to-recipient__audit--warn': audit.status === 'warning',
'sender-to-recipient__audit--approve': audit.status !== 'warning',
})}>
{`${audit.auditor} ${audit.status === 'warning' ? 'warning' : 'approval'}: ${audit.message}`}
</div>
: null
}
</div>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ export default class ConfirmTransactionBase extends Component {
insufficientBalance: PropTypes.bool,
hideFiatConversion: PropTypes.bool,
transactionCategory: PropTypes.string,
recipientAudit: PropTypes.object,
}

state = {
Expand Down Expand Up @@ -547,6 +548,7 @@ export default class ConfirmTransactionBase extends Component {
warning,
unapprovedTxCount,
transactionCategory,
recipientAudit,
} = this.props
const { submitting, submitError } = this.state

Expand Down Expand Up @@ -594,6 +596,7 @@ export default class ConfirmTransactionBase extends Component {
onCancelAll={() => this.handleCancelAll()}
onCancel={() => this.handleCancel()}
onSubmit={() => this.handleSubmit()}
recipientAudit={recipientAudit}
/>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const mapStateToProps = (state, ownProps) => {
const isMainnet = getIsMainnet(state)
const { confirmTransaction, metamask } = state
const {
addressAudits,
conversionRate,
identities,
addressBook,
Expand Down Expand Up @@ -76,6 +77,9 @@ const mapStateToProps = (state, ownProps) => {
: addressSlicer(checksumAddress(toAddress))
)

const recipientAudits = addressAudits[txParamsToAddress] || {}
const mostRecentAudit = Object.values(recipientAudits).sort((a, b) => a.timestamp > b.timestamp).find(audit => audit)

const addressBookObject = addressBook[checksumAddress(toAddress)]
const toNickname = addressBookObject ? addressBookObject.name : ''
const isTxReprice = Boolean(lastGasPrice)
Expand Down Expand Up @@ -151,6 +155,7 @@ const mapStateToProps = (state, ownProps) => {
hideFiatConversion: (!isMainnet && !showFiatInTestnets),
metaMetricsSendCount,
transactionCategory,
recipientAudit: mostRecentAudit,
}
}

Expand Down

0 comments on commit 8859d80

Please sign in to comment.