// Copyright 2015, 2016 Parity Technologies (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 { observable, autorun, action } from 'mobx'; import { sha3 } from '~/api/util/sha3'; import Contract from '~/api/contract'; import Contracts from '~/contracts'; import { checkIfVerified, checkIfRequested, awaitPuzzle } from '~/contracts/verification'; import { checkIfTxFailed, waitForConfirmations } from '~/util/tx'; export const LOADING = 'fetching-contract'; export const QUERY_DATA = 'query-data'; export const POSTING_REQUEST = 'posting-request'; export const POSTED_REQUEST = 'posted-request'; export const REQUESTING_CODE = 'requesting-code'; export const QUERY_CODE = 'query-code'; export const POSTING_CONFIRMATION = 'posting-confirmation'; export const POSTED_CONFIRMATION = 'posted-confirmation'; export const DONE = 'done'; export default class VerificationStore { @observable step = null; @observable error = null; @observable contract = null; @observable fee = null; @observable isVerified = null; @observable hasRequested = null; @observable isServerRunning = null; @observable consentGiven = false; @observable requestTx = null; @observable code = ''; @observable isCodeValid = null; @observable confirmationTx = null; constructor (api, abi, certifierName, account, isTestnet) { this.api = api; this.account = account; this.isTestnet = isTestnet; this.step = LOADING; Contracts.get().badgeReg.fetchCertifierByName(certifierName) .then(({ address }) => { this.contract = new Contract(api, abi).at(address); this.load(); }) .catch((err) => { console.error('error', err); this.error = 'Failed to fetch the contract: ' + err.message; }); autorun(() => { if (this.error) { console.error('verification: ' + this.error); } }); } @action load = () => { const { contract, account } = this; this.step = LOADING; const isServerRunning = this.isServerRunning() .then((isRunning) => { this.isServerRunning = isRunning; }) .catch((err) => { this.error = 'Failed to check if server is running: ' + err.message; }); const fee = contract.instance.fee.call() .then((fee) => { this.fee = fee; }) .catch((err) => { this.error = 'Failed to fetch the fee: ' + err.message; }); const isVerified = checkIfVerified(contract, account) .then((isVerified) => { this.isVerified = isVerified; }) .catch((err) => { this.error = 'Failed to check if verified: ' + err.message; }); const hasRequested = checkIfRequested(contract, account) .then((txHash) => { this.hasRequested = !!txHash; if (txHash) { this.requestTx = txHash; } }) .catch((err) => { this.error = 'Failed to check if requested: ' + err.message; }); Promise .all([ isServerRunning, fee, isVerified, hasRequested ]) .then(() => { this.step = QUERY_DATA; }); } @action setConsentGiven = (consentGiven) => { this.consentGiven = consentGiven; } @action setCode = (code) => { const { contract, account } = this; if (!contract || !account || code.length === 0) { return; } const confirm = contract.functions.find((fn) => fn.name === 'confirm'); const options = { from: account }; const values = [ sha3.text(code) ]; this.code = code; this.isCodeValid = null; confirm.estimateGas(options, values) .then((gas) => { options.gas = gas.mul(1.2).toFixed(0); return confirm.call(options, values); }) .then((result) => { this.isCodeValid = result === true; }) .catch((err) => { this.error = 'Failed to check if the code is valid: ' + err.message; }); } requestValues = () => [] @action sendRequest = () => { const { api, account, contract, fee, hasRequested } = this; const request = contract.functions.find((fn) => fn.name === 'request'); const options = { from: account, value: fee.toString() }; const values = this.requestValues(); let chain = Promise.resolve(); if (!hasRequested) { this.step = POSTING_REQUEST; chain = request.estimateGas(options, values) .then((gas) => { options.gas = gas.mul(1.2).toFixed(0); return request.postTransaction(options, values); }) .then((handle) => { // TODO: The "request rejected" error doesn't have any property to // distinguish it from other errors, so we can't give a meaningful error here. return api.pollMethod('parity_checkRequest', handle); }) .then((txHash) => { this.requestTx = txHash; return checkIfTxFailed(api, txHash, options.gas) .then((hasFailed) => { if (hasFailed) { throw new Error('Transaction failed, all gas used up.'); } this.step = POSTED_REQUEST; return waitForConfirmations(api, txHash, 1); }); }); } chain .then(() => this.checkIfReceivedCode()) .then((hasReceived) => { if (hasReceived) { return; } this.step = REQUESTING_CODE; return this .requestCode() .then(() => awaitPuzzle(api, contract, account)); }) .then(() => { this.step = QUERY_CODE; }) .catch((err) => { this.error = 'Failed to request a confirmation code: ' + err.message; }); } @action queryCode = () => { this.step = QUERY_CODE; } @action sendConfirmation = () => { const { api, account, contract, code } = this; const token = sha3.text(code); const confirm = contract.functions.find((fn) => fn.name === 'confirm'); const options = { from: account }; const values = [ token ]; this.step = POSTING_CONFIRMATION; confirm.estimateGas(options, values) .then((gas) => { options.gas = gas.mul(1.2).toFixed(0); return confirm.postTransaction(options, values); }) .then((handle) => { // TODO: The "request rejected" error doesn't have any property to // distinguish it from other errors, so we can't give a meaningful error here. return api.pollMethod('parity_checkRequest', handle); }) .then((txHash) => { this.confirmationTx = txHash; return checkIfTxFailed(api, txHash, options.gas) .then((hasFailed) => { if (hasFailed) { throw new Error('Transaction failed, all gas used up.'); } this.step = POSTED_CONFIRMATION; return waitForConfirmations(api, txHash, 1); }); }) .then(() => { this.step = DONE; }) .catch((err) => { this.error = 'Failed to send the verification code: ' + err.message; }); } @action done = () => { this.step = DONE; } }