diff --git a/js/assets/images/certifications/unknown.svg b/js/assets/images/certifications/unknown.svg
new file mode 100644
index 000000000..1554bcc25
--- /dev/null
+++ b/js/assets/images/certifications/unknown.svg
@@ -0,0 +1,4 @@
+
diff --git a/js/src/contracts/abi/badgereg.json b/js/src/contracts/abi/badgereg.json
new file mode 100644
index 000000000..3d18ba393
--- /dev/null
+++ b/js/src/contracts/abi/badgereg.json
@@ -0,0 +1 @@
+[{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_addr","type":"address"},{"name":"_name","type":"bytes32"}],"name":"register","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"fromName","outputs":[{"name":"id","type":"uint256"},{"name":"addr","type":"address"},{"name":"owner","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"badgeCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_fee","type":"uint256"}],"name":"setFee","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_id","type":"uint256"},{"name":"_key","type":"bytes32"}],"name":"meta","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"drain","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_id","type":"uint256"}],"name":"unregister","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_addr","type":"address"}],"name":"fromAddress","outputs":[{"name":"id","type":"uint256"},{"name":"name","type":"bytes32"},{"name":"owner","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_id","type":"uint256"}],"name":"badge","outputs":[{"name":"addr","type":"address"},{"name":"name","type":"bytes32"},{"name":"owner","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_id","type":"uint256"},{"name":"_key","type":"bytes32"},{"name":"_value","type":"bytes32"}],"name":"setMeta","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_addr","type":"address"},{"name":"_name","type":"bytes32"},{"name":"_owner","type":"address"}],"name":"registerAs","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"fee","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"bytes32"},{"indexed":true,"name":"id","type":"uint256"},{"indexed":false,"name":"addr","type":"address"}],"name":"Registered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"bytes32"},{"indexed":true,"name":"id","type":"uint256"}],"name":"Unregistered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"id","type":"uint256"},{"indexed":true,"name":"key","type":"bytes32"},{"indexed":false,"name":"value","type":"bytes32"}],"name":"MetaChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"old","type":"address"},{"indexed":true,"name":"current","type":"address"}],"name":"NewOwner","type":"event"}]
\ No newline at end of file
diff --git a/js/src/contracts/abi/certifier.json b/js/src/contracts/abi/certifier.json
new file mode 100644
index 000000000..905ddde6c
--- /dev/null
+++ b/js/src/contracts/abi/certifier.json
@@ -0,0 +1 @@
+[{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"getAddress","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"getUint","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"}],"name":"certified","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"get","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"}],"name":"Confirmed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"}],"name":"Revoked","type":"event"}]
\ No newline at end of file
diff --git a/js/src/contracts/abi/index.js b/js/src/contracts/abi/index.js
index a6a7f0783..f15765b1a 100644
--- a/js/src/contracts/abi/index.js
+++ b/js/src/contracts/abi/index.js
@@ -14,6 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see .
+import badgereg from './badgereg.json';
import basiccoin from './basiccoin.json';
import basiccoinmanager from './basiccoinmanager.json';
import dappreg from './dappreg.json';
@@ -28,6 +29,7 @@ import tokenreg from './tokenreg.json';
import wallet from './wallet.json';
export {
+ badgereg,
basiccoin,
basiccoinmanager,
dappreg,
diff --git a/js/src/contracts/badgereg.js b/js/src/contracts/badgereg.js
new file mode 100644
index 000000000..f8dbefa78
--- /dev/null
+++ b/js/src/contracts/badgereg.js
@@ -0,0 +1,66 @@
+// Copyright 2015, 2016 Ethcore (UK) Ltd.
+// This file is part of Parity.
+
+// Parity is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Parity is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Parity. If not, see .
+
+import { bytesToHex, hex2Ascii } from '../api/util/format';
+
+import ABI from './abi/certifier.json';
+
+const ZERO = '0x0000000000000000000000000000000000000000000000000000000000000000';
+
+export default class BadgeReg {
+ constructor (api, registry) {
+ this._api = api;
+ this._registry = registry;
+
+ registry.getContract('badgereg');
+ this.certifiers = {}; // by name
+ this.contracts = {}; // by name
+ }
+
+ fetchCertifier (name) {
+ if (this.certifiers[name]) {
+ return Promise.resolve(this.certifiers[name]);
+ }
+ return this._registry.getContract('badgereg')
+ .then((badgeReg) => {
+ return badgeReg.instance.fromName.call({}, [name])
+ .then(([ id, address ]) => {
+ return Promise.all([
+ badgeReg.instance.meta.call({}, [id, 'TITLE']),
+ badgeReg.instance.meta.call({}, [id, 'IMG'])
+ ])
+ .then(([ title, img ]) => {
+ title = bytesToHex(title);
+ title = title === ZERO ? null : hex2Ascii(title);
+ if (bytesToHex(img) === ZERO) img = null;
+
+ const data = { address, name, title, icon: img };
+ this.certifiers[name] = data;
+ return data;
+ });
+ });
+ });
+ }
+
+ checkIfCertified (certifier, address) {
+ if (!this.contracts[certifier]) {
+ this.contracts[certifier] = this._api.newContract(ABI, certifier);
+ }
+ const contract = this.contracts[certifier];
+
+ return contract.instance.certified.call({}, [address]);
+ }
+}
diff --git a/js/src/contracts/contracts.js b/js/src/contracts/contracts.js
index cefece7de..f61a63690 100644
--- a/js/src/contracts/contracts.js
+++ b/js/src/contracts/contracts.js
@@ -20,6 +20,7 @@ import SignatureReg from './signaturereg';
import TokenReg from './tokenreg';
import GithubHint from './githubhint';
import * as smsVerification from './sms-verification';
+import BadgeReg from './badgereg';
let instance = null;
@@ -33,6 +34,7 @@ export default class Contracts {
this._signaturereg = new SignatureReg(api, this._registry);
this._tokenreg = new TokenReg(api, this._registry);
this._githubhint = new GithubHint(api, this._registry);
+ this.badgeReg = new BadgeReg(api, this._registry);
}
get registry () {
diff --git a/js/src/redux/middleware.js b/js/src/redux/middleware.js
index a54f2fae4..c48c64673 100644
--- a/js/src/redux/middleware.js
+++ b/js/src/redux/middleware.js
@@ -20,17 +20,20 @@ import SettingsMiddleware from '../views/Settings/middleware';
import SignerMiddleware from './providers/signerMiddleware';
import statusMiddleware from '../views/Status/middleware';
+import CertificationsMiddleware from './providers/certifications/middleware';
export default function (api) {
const errors = new ErrorsMiddleware();
const signer = new SignerMiddleware(api);
const settings = new SettingsMiddleware();
const status = statusMiddleware();
+ const certifications = new CertificationsMiddleware();
const middleware = [
settings.toMiddleware(),
signer.toMiddleware(),
- errors.toMiddleware()
+ errors.toMiddleware(),
+ certifications.toMiddleware()
];
return middleware.concat(status, thunk);
diff --git a/js/src/redux/providers/certifications/actions.js b/js/src/redux/providers/certifications/actions.js
new file mode 100644
index 000000000..c84f7db55
--- /dev/null
+++ b/js/src/redux/providers/certifications/actions.js
@@ -0,0 +1,23 @@
+// Copyright 2015, 2016 Ethcore (UK) Ltd.
+// This file is part of Parity.
+
+// Parity is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Parity is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Parity. If not, see .
+
+export const fetchCertifications = (address) => ({
+ type: 'fetchCertifications', address
+});
+
+export const addCertification = (address, name, title, icon) => ({
+ type: 'addCertification', address, name, title, icon
+});
diff --git a/js/src/redux/providers/certifications/middleware.js b/js/src/redux/providers/certifications/middleware.js
new file mode 100644
index 000000000..c2f36931c
--- /dev/null
+++ b/js/src/redux/providers/certifications/middleware.js
@@ -0,0 +1,51 @@
+// Copyright 2015, 2016 Ethcore (UK) Ltd.
+// This file is part of Parity.
+
+// Parity is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Parity is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Parity. If not, see .
+
+import Contracts from '../../../contracts';
+import { addCertification } from './actions';
+
+const knownCertifiers = [ 'smsverification' ];
+
+export default class CertificationsMiddleware {
+ toMiddleware () {
+ return (store) => (next) => (action) => {
+ if (action.type !== 'fetchCertifications') {
+ return next(action);
+ }
+
+ const { address } = action;
+ const badgeReg = Contracts.get().badgeReg;
+
+ knownCertifiers.forEach((name) => {
+ badgeReg.fetchCertifier(name)
+ .then((cert) => {
+ return badgeReg.checkIfCertified(cert.address, address)
+ .then((isCertified) => {
+ if (isCertified) {
+ const { name, title, icon } = cert;
+ store.dispatch(addCertification(address, name, title, icon));
+ }
+ });
+ })
+ .catch((err) => {
+ if (err) {
+ console.error(`Failed to check if ${address} certified by ${name}:`, err);
+ }
+ });
+ });
+ };
+ }
+}
diff --git a/js/src/redux/providers/certifications/reducer.js b/js/src/redux/providers/certifications/reducer.js
new file mode 100644
index 000000000..87b0dfd33
--- /dev/null
+++ b/js/src/redux/providers/certifications/reducer.js
@@ -0,0 +1,33 @@
+// Copyright 2015, 2016 Ethcore (UK) Ltd.
+// This file is part of Parity.
+
+// Parity is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Parity is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Parity. If not, see .
+
+const initialState = {};
+
+export default (state = initialState, action) => {
+ if (action.type !== 'addCertification') {
+ return state;
+ }
+
+ const { address, name, icon, title } = action;
+ const certifications = state[address] || [];
+
+ if (certifications.some((c) => c.name === name)) {
+ return state;
+ }
+ const newCertifications = certifications.concat({ name, icon, title });
+
+ return { ...state, [address]: newCertifications };
+};
diff --git a/js/src/redux/reducers.js b/js/src/redux/reducers.js
index 45119d289..3c7a10fe9 100644
--- a/js/src/redux/reducers.js
+++ b/js/src/redux/reducers.js
@@ -18,6 +18,7 @@ import { combineReducers } from 'redux';
import { routerReducer } from 'react-router-redux';
import { apiReducer, balancesReducer, blockchainReducer, compilerReducer, imagesReducer, personalReducer, signerReducer, statusReducer as nodeStatusReducer, snackbarReducer } from './providers';
+import certificationsReducer from './providers/certifications/reducer';
import errorReducer from '../ui/Errors/reducers';
import settingsReducer from '../views/Settings/reducers';
@@ -32,6 +33,7 @@ export default function () {
settings: settingsReducer,
balances: balancesReducer,
+ certifications: certificationsReducer,
blockchain: blockchainReducer,
compiler: compilerReducer,
images: imagesReducer,
diff --git a/js/src/ui/Certifications/certifications.css b/js/src/ui/Certifications/certifications.css
new file mode 100644
index 000000000..077830e3a
--- /dev/null
+++ b/js/src/ui/Certifications/certifications.css
@@ -0,0 +1,46 @@
+/* Copyright 2015, 2016 Ethcore (UK) Ltd.
+/* This file is part of Parity.
+/*
+/* Parity is free software: you can redistribute it and/or modify
+/* it under the terms of the GNU General Public License as published by
+/* the Free Software Foundation, either version 3 of the License, or
+/* (at your option) any later version.
+/*
+/* Parity is distributed in the hope that it will be useful,
+/* but WITHOUT ANY WARRANTY; without even the implied warranty of
+/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+/* GNU General Public License for more details.
+/*
+/* You should have received a copy of the GNU General Public License
+/* along with Parity. If not, see .
+*/
+
+.certifications {
+ margin-top: 1em;
+}
+
+.certification {
+ display: inline-block;
+ position: relative;
+ margin-right: .5em;
+ padding: .3em .6em .2em 2.6em;
+ border-radius: 1em;
+ line-height: 1em;
+ text-transform: uppercase;
+ background-color: rgba(255, 255, 255, 0.07);
+}
+
+.certification:last-child {
+ margin-right: 0;
+}
+
+.icon {
+ position: absolute;
+ top: -.25em;
+ left: 0;
+ width: 2em;
+ height: 2em;
+ margin: 0;
+ padding: 0;
+ border-radius: 1em;
+}
diff --git a/js/src/ui/Certifications/certifications.js b/js/src/ui/Certifications/certifications.js
new file mode 100644
index 000000000..d2f06398e
--- /dev/null
+++ b/js/src/ui/Certifications/certifications.js
@@ -0,0 +1,87 @@
+// Copyright 2015, 2016 Ethcore (UK) Ltd.
+// This file is part of Parity.
+
+// Parity is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Parity is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Parity. If not, see .
+
+import React, { Component, PropTypes } from 'react';
+import { connect } from 'react-redux';
+import { bindActionCreators } from 'redux';
+
+import { hashToImageUrl } from '../../redux/providers/imagesReducer';
+import { fetchCertifications } from '../../redux/providers/certifications/actions';
+
+import defaultIcon from '../../../assets/images/certifications/unknown.svg';
+
+import styles from './certifications.css';
+
+class Certifications extends Component {
+ static propTypes = {
+ account: PropTypes.string.isRequired,
+ certifications: PropTypes.array.isRequired,
+ dappsUrl: PropTypes.string.isRequired,
+
+ fetchCertifications: PropTypes.func.isRequired
+ }
+
+ componentWillMount () {
+ const { account, fetchCertifications } = this.props;
+ fetchCertifications(account);
+ }
+
+ render () {
+ const { certifications } = this.props;
+
+ if (certifications.length === 0) {
+ return null;
+ }
+
+ return (
+