Merge branch 'master' into ng-accounts-backup

This commit is contained in:
Nicolas Gotchac
2016-11-14 20:31:10 +01:00
614 changed files with 20356 additions and 7948 deletions

View File

@@ -14,6 +14,8 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { stringify } from 'qs';
const options = {
method: 'GET',
headers: {
@@ -23,19 +25,14 @@ const options = {
export function call (module, action, _params, test) {
const host = test ? 'testnet.etherscan.io' : 'api.etherscan.io';
let params = '';
if (_params) {
Object.keys(_params).map((param) => {
const value = _params[param];
const query = stringify(Object.assign({
module, action
}, _params || {}));
params = `${params}&${param}=${value}`;
});
}
return fetch(`http://${host}/api?module=${module}&action=${action}${params}`, options)
return fetch(`https://${host}/api?${query}`, options)
.then((response) => {
if (response.status !== 200) {
if (!response.ok) {
throw { code: response.status, message: response.statusText }; // eslint-disable-line
}

View File

@@ -16,10 +16,13 @@
import { account } from './account';
import { stats } from './stats';
import { txLink, addressLink } from './links';
const etherscan = {
account: account,
stats: stats
stats: stats,
txLink: txLink,
addressLink: addressLink
};
export default etherscan;

View File

@@ -14,9 +14,10 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { renderAccounts } from './accountSelector';
export default from './accountSelector';
export {
renderAccounts
export const txLink = (hash, isTestnet = false) => {
return `https://${isTestnet ? 'testnet.' : ''}etherscan.io/tx/${hash}`;
};
export const addressLink = (address, isTestnet = false) => {
return `https://${isTestnet ? 'testnet.' : ''}etherscan.io/address/${address}`;
};

View File

@@ -135,10 +135,11 @@ APIs implement the calls as exposed in the [Ethcore JSON Ethereum RPC](https://g
- [ethapi.db](https://github.com/ethcore/ethereum-rpc-json/blob/master/interfaces.md#db)
- [ethapi.eth](https://github.com/ethcore/ethereum-rpc-json/blob/master/interfaces.md#eth)
- [ethapi.ethcore](https://github.com/ethcore/ethereum-rpc-json/blob/master/interfaces.md#ethcore)
- [ethapi.parity](https://github.com/ethcore/ethereum-rpc-json/blob/master/interfaces.md#parity)
- [ethapi.net](https://github.com/ethcore/ethereum-rpc-json/blob/master/interfaces.md#net)
- [ethapi.personal](https://github.com/ethcore/ethereum-rpc-json/blob/master/interfaces.md#personal)
- [ethapi.shh](https://github.com/ethcore/ethereum-rpc-json/blob/master/interfaces.md#shh)
- [ethapi.signer](https://github.com/ethcore/ethereum-rpc-json/blob/master/interfaces.md#signer)
- [ethapi.trace](https://github.com/ethcore/ethereum-rpc-json/blob/master/interfaces.md#trace)
- [ethapi.web3](https://github.com/ethcore/ethereum-rpc-json/blob/master/interfaces.md#web3)

View File

@@ -17,7 +17,7 @@
import { Http, Ws } from './transport';
import Contract from './contract';
import { Db, Eth, Ethcore, Net, Personal, Shh, Trace, Web3 } from './rpc';
import { Db, Eth, Parity, Net, Personal, Shh, Signer, Trace, Web3 } from './rpc';
import Subscriptions from './subscriptions';
import util from './util';
import { isFunction } from './util/types';
@@ -32,10 +32,11 @@ export default class Api {
this._db = new Db(transport);
this._eth = new Eth(transport);
this._ethcore = new Ethcore(transport);
this._net = new Net(transport);
this._parity = new Parity(transport);
this._personal = new Personal(transport);
this._shh = new Shh(transport);
this._signer = new Signer(transport);
this._trace = new Trace(transport);
this._web3 = new Web3(transport);
@@ -50,8 +51,8 @@ export default class Api {
return this._eth;
}
get ethcore () {
return this._ethcore;
get parity () {
return this._parity;
}
get net () {
@@ -66,6 +67,10 @@ export default class Api {
return this._shh;
}
get signer () {
return this._signer;
}
get trace () {
return this._trace;
}

View File

@@ -34,7 +34,7 @@ describe('api/Api', () => {
});
describe('interface', () => {
const api = new Api(new Api.Transport.Http(TEST_HTTP_URL));
const api = new Api(new Api.Transport.Http(TEST_HTTP_URL, -1));
Object.keys(ethereumRpc).sort().forEach((endpoint) => {
describe(endpoint, () => {

View File

@@ -102,7 +102,7 @@ export default class Contract {
options.gas = gas.toFixed(0);
setState({ state: 'postTransaction', gas });
return this._api.eth.postTransaction(this._encodeOptions(this.constructors[0], options, values));
return this._api.parity.postTransaction(this._encodeOptions(this.constructors[0], options, values));
})
.then((requestId) => {
setState({ state: 'checkRequest', requestId });
@@ -136,27 +136,30 @@ export default class Contract {
}
parseEventLogs (logs) {
return logs.map((log) => {
const signature = log.topics[0].substr(2);
const event = this.events.find((evt) => evt.signature === signature);
return logs
.map((log) => {
const signature = log.topics[0].substr(2);
const event = this.events.find((evt) => evt.signature === signature);
if (!event) {
throw new Error(`Unable to find event matching signature ${signature}`);
}
if (!event) {
console.warn(`Unable to find event matching signature ${signature}`);
return null;
}
const decoded = event.decodeLog(log.topics, log.data);
const decoded = event.decodeLog(log.topics, log.data);
log.params = {};
log.event = event.name;
log.params = {};
log.event = event.name;
decoded.params.forEach((param) => {
const { type, value } = param.token;
decoded.params.forEach((param) => {
const { type, value } = param.token;
log.params[param.name] = { type, value };
});
log.params[param.name] = { type, value };
});
return log;
});
return log;
})
.filter((log) => log);
}
parseTransactionEvents (receipt) {
@@ -166,7 +169,7 @@ export default class Contract {
}
_pollCheckRequest = (requestId) => {
return this._api.pollMethod('eth_checkRequest', requestId);
return this._api.pollMethod('parity_checkRequest', requestId);
}
_pollTransactionReceipt = (txhash, gas) => {
@@ -208,7 +211,7 @@ export default class Contract {
if (!func.constant) {
func.postTransaction = (options, values = []) => {
return this._api.eth
return this._api.parity
.postTransaction(this._encodeOptions(func, this._addOptionsTo(options), values));
};
@@ -306,7 +309,6 @@ export default class Contract {
try {
subscriptions[idx].callback(null, this.parseEventLogs(logs));
} catch (error) {
this.unsubscribe(idx);
console.error('_sendSubscriptionChanges', error);
}
});

View File

@@ -25,7 +25,7 @@ import Api from '../api';
import Contract from './contract';
import { isInstanceOf, isFunction } from '../util/types';
const transport = new Api.Transport.Http(TEST_HTTP_URL);
const transport = new Api.Transport.Http(TEST_HTTP_URL, -1);
const eth = new Api(transport);
describe('api/contract/Contract', () => {
@@ -119,19 +119,6 @@ describe('api/contract/Contract', () => {
});
describe('parseTransactionEvents', () => {
it('checks for unmatched signatures', () => {
const contract = new Contract(eth, [{ anonymous: false, name: 'Message', type: 'event' }]);
expect(() => contract.parseTransactionEvents({
logs: [{
data: '0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000063cf90d3f0410092fc0fca41846f5962239791950000000000000000000000000000000000000000000000000000000056e6c85f0000000000000000000000000000000000000000000000000001000000004fcd00000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000d706f7374286d6573736167652900000000000000000000000000000000000000',
topics: [
'0x954ba6c157daf8a26539574ffa64203c044691aa57251af95f4b48d85ec00dd5',
'0x0000000000000000000000000000000000000000000000000001000000004fe0'
]
}]
})).to.throw(/event matching signature/);
});
it('parses a transaction log into the data', () => {
const contract = new Contract(eth, [
{
@@ -249,9 +236,9 @@ describe('api/contract/Contract', () => {
before(() => {
scope = mockHttp([
{ method: 'eth_estimateGas', reply: { result: 1000 } },
{ method: 'eth_postTransaction', reply: { result: '0x678' } },
{ method: 'eth_checkRequest', reply: { result: null } },
{ method: 'eth_checkRequest', reply: { result: '0x890' } },
{ method: 'parity_postTransaction', reply: { result: '0x678' } },
{ method: 'parity_checkRequest', reply: { result: null } },
{ method: 'parity_checkRequest', reply: { result: '0x890' } },
{ method: 'eth_getTransactionReceipt', reply: { result: null } },
{ method: 'eth_getTransactionReceipt', reply: { result: RECEIPT_PEND } },
{ method: 'eth_getTransactionReceipt', reply: { result: RECEIPT_DONE } },
@@ -266,7 +253,7 @@ describe('api/contract/Contract', () => {
});
it('passes the options through to postTransaction (incl. gas calculation)', () => {
expect(scope.body.eth_postTransaction.params).to.deep.equal([
expect(scope.body.parity_postTransaction.params).to.deep.equal([
{ data: '0x123', gas: '0x4b0' }
]);
});
@@ -280,8 +267,8 @@ describe('api/contract/Contract', () => {
it('fails when gasUsed == gas', () => {
mockHttp([
{ method: 'eth_estimateGas', reply: { result: 1000 } },
{ method: 'eth_postTransaction', reply: { result: '0x678' } },
{ method: 'eth_checkRequest', reply: { result: '0x789' } },
{ method: 'parity_postTransaction', reply: { result: '0x678' } },
{ method: 'parity_checkRequest', reply: { result: '0x789' } },
{ method: 'eth_getTransactionReceipt', reply: { result: RECEIPT_EXCP } }
]);
@@ -295,8 +282,8 @@ describe('api/contract/Contract', () => {
it('fails when no code was deployed', () => {
mockHttp([
{ method: 'eth_estimateGas', reply: { result: 1000 } },
{ method: 'eth_postTransaction', reply: { result: '0x678' } },
{ method: 'eth_checkRequest', reply: { result: '0x789' } },
{ method: 'parity_postTransaction', reply: { result: '0x678' } },
{ method: 'parity_checkRequest', reply: { result: '0x789' } },
{ method: 'eth_getTransactionReceipt', reply: { result: RECEIPT_DONE } },
{ method: 'eth_getCode', reply: { result: '0x' } }
]);
@@ -360,15 +347,15 @@ describe('api/contract/Contract', () => {
describe('postTransaction', () => {
beforeEach(() => {
scope = mockHttp([{ method: 'eth_postTransaction', reply: { result: ['hashId'] } }]);
scope = mockHttp([{ method: 'parity_postTransaction', reply: { result: ['hashId'] } }]);
});
it('encodes options and mades an eth_postTransaction call', () => {
it('encodes options and mades an parity_postTransaction call', () => {
return func
.postTransaction({ someExtras: 'foo' }, VALUES)
.then(() => {
expect(scope.isDone()).to.be.true;
expect(scope.body.eth_postTransaction.params[0]).to.deep.equal({
expect(scope.body.parity_postTransaction.params[0]).to.deep.equal({
someExtras: 'foo',
to: ADDR,
data: ENCODED

View File

@@ -93,6 +93,10 @@ export function inFilter (options) {
}
export function inHex (str) {
if (str && str.toString) {
str = str.toString(16);
}
if (str && str.substr(0, 2) === '0x') {
return str.toLowerCase();
}

View File

@@ -70,6 +70,20 @@ export function outDate (date) {
return new Date(outNumber(date).toNumber() * 1000);
}
export function outHistogram (histogram) {
if (histogram) {
Object.keys(histogram).forEach((key) => {
switch (key) {
case 'bucketBounds':
case 'counts':
histogram[key] = histogram[key].map(outNumber);
}
});
}
return histogram;
}
export function outLog (log) {
Object.keys(log).forEach((key) => {
switch (key) {
@@ -139,6 +153,28 @@ export function outSignerRequest (request) {
return request;
}
export function outSyncing (syncing) {
if (syncing && syncing !== 'false') {
Object.keys(syncing).forEach((key) => {
switch (key) {
case 'currentBlock':
case 'highestBlock':
case 'startingBlock':
case 'warpChunksAmount':
case 'warpChunksProcessed':
syncing[key] = outNumber(syncing[key]);
break;
case 'blockGap':
syncing[key] = syncing[key] ? syncing[key].map(outNumber) : syncing[key];
break;
}
});
}
return syncing;
}
export function outTransaction (tx) {
if (tx) {
Object.keys(tx).forEach((key) => {

View File

@@ -16,7 +16,7 @@
import BigNumber from 'bignumber.js';
import { outBlock, outAccountInfo, outAddress, outDate, outNumber, outPeers, outReceipt, outTransaction, outTrace } from './output';
import { outBlock, outAccountInfo, outAddress, outDate, outHistogram, outNumber, outPeers, outReceipt, outSyncing, outTransaction, outTrace } from './output';
import { isAddress, isBigNumber, isInstanceOf } from '../../../test/types';
describe('api/format/output', () => {
@@ -120,6 +120,18 @@ describe('api/format/output', () => {
});
});
describe('outHistogram', () => {
['bucketBounds', 'counts'].forEach((type) => {
it(`formats ${type} as number arrays`, () => {
expect(
outHistogram({ [type]: [0x123, 0x456, 0x789] })
).to.deep.equal({
[type]: [new BigNumber(0x123), new BigNumber(0x456), new BigNumber(0x789)]
});
});
});
});
describe('outNumber', () => {
it('returns a BigNumber equalling the value', () => {
const bn = outNumber('0x123456');
@@ -191,6 +203,22 @@ describe('api/format/output', () => {
});
});
describe('outSyncing', () => {
['currentBlock', 'highestBlock', 'startingBlock', 'warpChunksAmount', 'warpChunksProcessed'].forEach((input) => {
it(`formats ${input} numbers as a number`, () => {
expect(outSyncing({ [input]: '0x123' })).to.deep.equal({
[input]: new BigNumber('0x123')
});
});
});
it('formats blockGap properly', () => {
expect(outSyncing({ blockGap: [0x123, 0x456] })).to.deep.equal({
blockGap: [new BigNumber(0x123), new BigNumber(0x456)]
});
});
});
describe('outTransaction', () => {
['from', 'to'].forEach((input) => {
it(`formats ${input} address as address`, () => {

View File

@@ -19,7 +19,7 @@ import { TEST_HTTP_URL, mockHttp } from '../../../../test/mockRpc';
import Http from '../../transport/http';
import Db from './db';
const instance = new Db(new Http(TEST_HTTP_URL));
const instance = new Db(new Http(TEST_HTTP_URL, -1));
describe('api/rpc/Db', () => {
let scope;

View File

@@ -15,7 +15,7 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { inAddress, inBlockNumber, inData, inFilter, inHash, inHex, inNumber16, inOptions } from '../../format/input';
import { outAddress, outBlock, outLog, outNumber, outReceipt, outTransaction } from '../../format/output';
import { outAddress, outBlock, outLog, outNumber, outReceipt, outSyncing, outTransaction } from '../../format/output';
export default class Eth {
constructor (transport) {
@@ -39,11 +39,6 @@ export default class Eth {
.execute('eth_call', inOptions(options), inBlockNumber(blockNumber));
}
checkRequest (requestId) {
return this._transport
.execute('eth_checkRequest', inNumber16(requestId));
}
coinbase () {
return this._transport
.execute('eth_coinbase')
@@ -267,11 +262,6 @@ export default class Eth {
.execute('eth_pendingTransactions');
}
postTransaction (options) {
return this._transport
.execute('eth_postTransaction', inOptions(options));
}
protocolVersion () {
return this._transport
.execute('eth_protocolVersion');
@@ -314,7 +304,8 @@ export default class Eth {
syncing () {
return this._transport
.execute('eth_syncing');
.execute('eth_syncing')
.then(outSyncing);
}
uninstallFilter (filterId) {

View File

@@ -20,7 +20,7 @@ import { isBigNumber } from '../../../../test/types';
import Http from '../../transport/http';
import Eth from './eth';
const instance = new Eth(new Http(TEST_HTTP_URL));
const instance = new Eth(new Http(TEST_HTTP_URL, -1));
describe('rpc/Eth', () => {
const address = '0x63Cf90D3f0410092FC0fca41846f596223979195';

View File

@@ -1,180 +0,0 @@
// 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 <http://www.gnu.org/licenses/>.
import { inAddress, inData, inNumber16 } from '../../format/input';
import { outAddress, outNumber, outPeers } from '../../format/output';
export default class Ethcore {
constructor (transport) {
this._transport = transport;
}
acceptNonReservedPeers () {
return this._transport
.execute('ethcore_acceptNonReservedPeers');
}
addReservedPeer (encode) {
return this._transport
.execute('ethcore_addReservedPeer', encode);
}
dappsPort () {
return this._transport
.execute('ethcore_dappsPort')
.then(outNumber);
}
defaultExtraData () {
return this._transport
.execute('ethcore_defaultExtraData');
}
devLogs () {
return this._transport
.execute('ethcore_devLogs');
}
devLogsLevels () {
return this._transport
.execute('ethcore_devLogsLevels');
}
dropNonReservedPeers () {
return this._transport
.execute('ethcore_dropNonReservedPeers');
}
extraData () {
return this._transport
.execute('ethcore_extraData');
}
gasFloorTarget () {
return this._transport
.execute('ethcore_gasFloorTarget')
.then(outNumber);
}
generateSecretPhrase () {
return this._transport
.execute('ethcore_generateSecretPhrase');
}
hashContent (url) {
return this._transport
.execute('ethcore_hashContent', url);
}
minGasPrice () {
return this._transport
.execute('ethcore_minGasPrice')
.then(outNumber);
}
netChain () {
return this._transport
.execute('ethcore_netChain');
}
netPeers () {
return this._transport
.execute('ethcore_netPeers')
.then(outPeers);
}
netMaxPeers () {
return this._transport
.execute('ethcore_netMaxPeers')
.then(outNumber);
}
netPort () {
return this._transport
.execute('ethcore_netPort')
.then(outNumber);
}
nodeName () {
return this._transport
.execute('ethcore_nodeName');
}
phraseToAddress (phrase) {
return this._transport
.execute('ethcore_phraseToAddress', phrase)
.then(outAddress);
}
registryAddress () {
return this._transport
.execute('ethcore_registryAddress')
.then(outAddress);
}
removeReservedPeer (encode) {
return this._transport
.execute('ethcore_removeReservedPeer', encode);
}
rpcSettings () {
return this._transport
.execute('ethcore_rpcSettings');
}
setAuthor (address) {
return this._transport
.execute('ethcore_setAuthor', inAddress(address));
}
setExtraData (data) {
return this._transport
.execute('ethcore_setExtraData', inData(data));
}
setGasFloorTarget (quantity) {
return this._transport
.execute('ethcore_setGasFloorTarget', inNumber16(quantity));
}
setMinGasPrice (quantity) {
return this._transport
.execute('ethcore_setMinGasPrice', inNumber16(quantity));
}
setTransactionsLimit (quantity) {
return this._transport
.execute('ethcore_setTransactionsLimit', inNumber16(quantity));
}
signerPort () {
return this._transport
.execute('ethcore_signerPort')
.then(outNumber);
}
transactionsLimit () {
return this._transport
.execute('ethcore_transactionsLimit')
.then(outNumber);
}
unsignedTransactionsCount () {
return this._transport
.execute('ethcore_unsignedTransactionsCount')
.then(outNumber);
}
}

View File

@@ -16,9 +16,10 @@
export Db from './db';
export Eth from './eth';
export Ethcore from './ethcore';
export Parity from './parity';
export Net from './net';
export Personal from './personal';
export Shh from './shh';
export Signer from './signer';
export Trace from './trace';
export Web3 from './web3';

View File

@@ -20,7 +20,7 @@ import { isBigNumber } from '../../../../test/types';
import Http from '../../transport/http';
import Net from './net';
const instance = new Net(new Http(TEST_HTTP_URL));
const instance = new Net(new Http(TEST_HTTP_URL, -1));
describe('api/rpc/Net', () => {
describe('peerCount', () => {

View File

@@ -14,4 +14,4 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default from './status';
export default from './parity';

View File

@@ -16,20 +16,30 @@
import { createHttpApi } from '../../../../test/e2e/ethapi';
describe('ethapi.ethcore', () => {
describe('ethapi.parity', () => {
const ethapi = createHttpApi();
describe('gasFloorTarget', () => {
it('returns and translates the target', () => {
return ethapi.ethcore.gasFloorTarget().then((value) => {
return ethapi.parity.gasFloorTarget().then((value) => {
expect(value.gt(0)).to.be.true;
});
});
});
describe('gasPriceHistogram', () => {
it('returns and translates the target', () => {
return ethapi.parity.gasPriceHistogram().then((result) => {
expect(Object.keys(result)).to.deep.equal(['bucketBounds', 'counts']);
expect(result.bucketBounds.length > 0).to.be.true;
expect(result.counts.length > 0).to.be.true;
});
});
});
describe('netChain', () => {
it('returns and the chain', () => {
return ethapi.ethcore.netChain().then((value) => {
return ethapi.parity.netChain().then((value) => {
expect(value).to.equal('morden');
});
});
@@ -37,7 +47,7 @@ describe('ethapi.ethcore', () => {
describe('netPort', () => {
it('returns and translates the port', () => {
return ethapi.ethcore.netPort().then((value) => {
return ethapi.parity.netPort().then((value) => {
expect(value.gt(0)).to.be.true;
});
});
@@ -45,7 +55,7 @@ describe('ethapi.ethcore', () => {
describe('transactionsLimit', () => {
it('returns and translates the limit', () => {
return ethapi.ethcore.transactionsLimit().then((value) => {
return ethapi.parity.transactionsLimit().then((value) => {
expect(value.gt(0)).to.be.true;
});
});
@@ -53,7 +63,7 @@ describe('ethapi.ethcore', () => {
describe('rpcSettings', () => {
it('returns and translates the settings', () => {
return ethapi.ethcore.rpcSettings().then((value) => {
return ethapi.parity.rpcSettings().then((value) => {
expect(value).to.be.ok;
});
});

View File

@@ -0,0 +1,284 @@
// 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 <http://www.gnu.org/licenses/>.
import { inAddress, inData, inHex, inNumber16, inOptions } from '../../format/input';
import { outAccountInfo, outAddress, outHistogram, outNumber, outPeers } from '../../format/output';
export default class Parity {
constructor (transport) {
this._transport = transport;
}
acceptNonReservedPeers () {
return this._transport
.execute('parity_acceptNonReservedPeers');
}
accounts () {
return this._transport
.execute('parity_accounts')
.then(outAccountInfo);
}
accountsInfo () {
return this._transport
.execute('parity_accountsInfo')
.then(outAccountInfo);
}
addReservedPeer (encode) {
return this._transport
.execute('parity_addReservedPeer', encode);
}
changePassword (account, password, newPassword) {
return this._transport
.execute('parity_changePassword', inAddress(account), password, newPassword);
}
checkRequest (requestId) {
return this._transport
.execute('parity_checkRequest', inNumber16(requestId));
}
dappsPort () {
return this._transport
.execute('parity_dappsPort')
.then(outNumber);
}
dappsInterface () {
return this._transport
.execute('parity_dappsInterface');
}
defaultExtraData () {
return this._transport
.execute('parity_defaultExtraData');
}
devLogs () {
return this._transport
.execute('parity_devLogs');
}
devLogsLevels () {
return this._transport
.execute('parity_devLogsLevels');
}
dropNonReservedPeers () {
return this._transport
.execute('parity_dropNonReservedPeers');
}
enode () {
return this._transport
.execute('parity_enode');
}
extraData () {
return this._transport
.execute('parity_extraData');
}
gasFloorTarget () {
return this._transport
.execute('parity_gasFloorTarget')
.then(outNumber);
}
gasPriceHistogram () {
return this._transport
.execute('parity_gasPriceHistogram')
.then(outHistogram);
}
generateSecretPhrase () {
return this._transport
.execute('parity_generateSecretPhrase');
}
hashContent (url) {
return this._transport
.execute('parity_hashContent', url);
}
listGethAccounts () {
return this._transport
.execute('parity_listGethAccounts')
.then((accounts) => (accounts || []).map(outAddress));
}
importGethAccounts (accounts) {
return this._transport
.execute('parity_importGethAccounts', (accounts || []).map(inAddress))
.then((accounts) => (accounts || []).map(outAddress));
}
minGasPrice () {
return this._transport
.execute('parity_minGasPrice')
.then(outNumber);
}
mode () {
return this._transport
.execute('parity_mode');
}
netChain () {
return this._transport
.execute('parity_netChain');
}
netPeers () {
return this._transport
.execute('parity_netPeers')
.then(outPeers);
}
netMaxPeers () {
return this._transport
.execute('parity_netMaxPeers')
.then(outNumber);
}
netPort () {
return this._transport
.execute('parity_netPort')
.then(outNumber);
}
newAccountFromPhrase (phrase, password) {
return this._transport
.execute('parity_newAccountFromPhrase', phrase, password)
.then(outAddress);
}
newAccountFromSecret (secret, password) {
return this._transport
.execute('parity_newAccountFromSecret', inHex(secret), password)
.then(outAddress);
}
newAccountFromWallet (json, password) {
return this._transport
.execute('parity_newAccountFromWallet', json, password)
.then(outAddress);
}
nextNonce (account) {
return this._transport
.execute('parity_nextNonce', inAddress(account))
.then(outNumber);
}
nodeName () {
return this._transport
.execute('parity_nodeName');
}
phraseToAddress (phrase) {
return this._transport
.execute('parity_phraseToAddress', phrase)
.then(outAddress);
}
postTransaction (options) {
return this._transport
.execute('parity_postTransaction', inOptions(options));
}
registryAddress () {
return this._transport
.execute('parity_registryAddress')
.then(outAddress);
}
removeReservedPeer (encode) {
return this._transport
.execute('parity_removeReservedPeer', encode);
}
rpcSettings () {
return this._transport
.execute('parity_rpcSettings');
}
setAccountName (address, name) {
return this._transport
.execute('parity_setAccountName', inAddress(address), name);
}
setAccountMeta (address, meta) {
return this._transport
.execute('parity_setAccountMeta', inAddress(address), JSON.stringify(meta));
}
setAuthor (address) {
return this._transport
.execute('parity_setAuthor', inAddress(address));
}
setExtraData (data) {
return this._transport
.execute('parity_setExtraData', inData(data));
}
setGasFloorTarget (quantity) {
return this._transport
.execute('parity_setGasFloorTarget', inNumber16(quantity));
}
setMinGasPrice (quantity) {
return this._transport
.execute('parity_setMinGasPrice', inNumber16(quantity));
}
setMode (mode) {
return this._transport
.execute('parity_setMode', mode);
}
setTransactionsLimit (quantity) {
return this._transport
.execute('parity_setTransactionsLimit', inNumber16(quantity));
}
signerPort () {
return this._transport
.execute('parity_signerPort')
.then(outNumber);
}
testPassword (account, password) {
return this._transport
.execute('parity_testPassword', inAddress(account), password);
}
transactionsLimit () {
return this._transport
.execute('parity_transactionsLimit')
.then(outNumber);
}
unsignedTransactionsCount () {
return this._transport
.execute('parity_unsignedTransactionsCount')
.then(outNumber);
}
}

View File

@@ -18,14 +18,36 @@ import { TEST_HTTP_URL, mockHttp } from '../../../../test/mockRpc';
import { isBigNumber } from '../../../../test/types';
import Http from '../../transport/http';
import Ethcore from './ethcore';
import Parity from './parity';
const instance = new Ethcore(new Http(TEST_HTTP_URL));
const instance = new Parity(new Http(TEST_HTTP_URL, -1));
describe('api/rpc/parity', () => {
describe('accountsInfo', () => {
it('retrieves the available account info', () => {
mockHttp([{ method: 'parity_accountsInfo', reply: {
result: {
'0x63cf90d3f0410092fc0fca41846f596223979195': {
name: 'name', uuid: 'uuid', meta: '{"data":"data"}'
}
}
} }]);
return instance.accountsInfo().then((result) => {
expect(result).to.deep.equal({
'0x63Cf90D3f0410092FC0fca41846f596223979195': {
name: 'name', uuid: 'uuid', meta: {
data: 'data'
}
}
});
});
});
});
describe('api/rpc/Ethcore', () => {
describe('gasFloorTarget', () => {
it('returns the gasfloor, formatted', () => {
mockHttp([{ method: 'ethcore_gasFloorTarget', reply: { result: '0x123456' } }]);
mockHttp([{ method: 'parity_gasFloorTarget', reply: { result: '0x123456' } }]);
return instance.gasFloorTarget().then((count) => {
expect(isBigNumber(count)).to.be.true;
@@ -36,7 +58,7 @@ describe('api/rpc/Ethcore', () => {
describe('minGasPrice', () => {
it('returns the min gasprice, formatted', () => {
mockHttp([{ method: 'ethcore_minGasPrice', reply: { result: '0x123456' } }]);
mockHttp([{ method: 'parity_minGasPrice', reply: { result: '0x123456' } }]);
return instance.minGasPrice().then((count) => {
expect(isBigNumber(count)).to.be.true;
@@ -47,7 +69,7 @@ describe('api/rpc/Ethcore', () => {
describe('netMaxPeers', () => {
it('returns the max peers, formatted', () => {
mockHttp([{ method: 'ethcore_netMaxPeers', reply: { result: 25 } }]);
mockHttp([{ method: 'parity_netMaxPeers', reply: { result: 25 } }]);
return instance.netMaxPeers().then((count) => {
expect(isBigNumber(count)).to.be.true;
@@ -58,7 +80,7 @@ describe('api/rpc/Ethcore', () => {
describe('newPeers', () => {
it('returns the peer structure, formatted', () => {
mockHttp([{ method: 'ethcore_netPeers', reply: { result: { active: 123, connected: 456, max: 789 } } }]);
mockHttp([{ method: 'parity_netPeers', reply: { result: { active: 123, connected: 456, max: 789 } } }]);
return instance.netPeers().then((peers) => {
expect(peers.active.eq(123)).to.be.true;
@@ -70,7 +92,7 @@ describe('api/rpc/Ethcore', () => {
describe('netPort', () => {
it('returns the connected port, formatted', () => {
mockHttp([{ method: 'ethcore_netPort', reply: { result: 33030 } }]);
mockHttp([{ method: 'parity_netPort', reply: { result: 33030 } }]);
return instance.netPort().then((count) => {
expect(isBigNumber(count)).to.be.true;
@@ -81,7 +103,7 @@ describe('api/rpc/Ethcore', () => {
describe('transactionsLimit', () => {
it('returns the tx limit, formatted', () => {
mockHttp([{ method: 'ethcore_transactionsLimit', reply: { result: 1024 } }]);
mockHttp([{ method: 'parity_transactionsLimit', reply: { result: 1024 } }]);
return instance.transactionsLimit().then((count) => {
expect(isBigNumber(count)).to.be.true;

View File

@@ -14,107 +14,31 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { inAddress, inNumber10, inNumber16, inOptions } from '../../format/input';
import { outAccountInfo, outAddress, outSignerRequest } from '../../format/output';
import { inAddress, inNumber10, inOptions } from '../../format/input';
import { outAddress } from '../../format/output';
export default class Personal {
constructor (transport) {
this._transport = transport;
}
accountsInfo () {
return this._transport
.execute('personal_accountsInfo')
.then(outAccountInfo);
}
confirmRequest (requestId, options, password) {
return this._transport
.execute('personal_confirmRequest', inNumber16(requestId), options, password);
}
changePassword (account, password, newPassword) {
return this._transport
.execute('personal_changePassword', inAddress(account), password, newPassword);
}
generateAuthorizationToken () {
return this._transport
.execute('personal_generateAuthorizationToken');
}
listAccounts () {
return this._transport
.execute('personal_listAccounts')
.then((accounts) => (accounts || []).map(outAddress));
}
listGethAccounts () {
return this._transport
.execute('personal_listGethAccounts')
.then((accounts) => (accounts || []).map(outAddress));
}
importGethAccounts (accounts) {
return this._transport
.execute('personal_importGethAccounts', (accounts || []).map(inAddress))
.then((accounts) => (accounts || []).map(outAddress));
}
newAccount (password) {
return this._transport
.execute('personal_newAccount', password)
.then(outAddress);
}
newAccountFromPhrase (phrase, password) {
return this._transport
.execute('personal_newAccountFromPhrase', phrase, password)
.then(outAddress);
}
newAccountFromWallet (json, password) {
return this._transport
.execute('personal_newAccountFromWallet', json, password)
.then(outAddress);
}
rejectRequest (requestId) {
return this._transport
.execute('personal_rejectRequest', inNumber16(requestId));
}
requestsToConfirm () {
return this._transport
.execute('personal_requestsToConfirm')
.then((requests) => (requests || []).map(outSignerRequest));
}
setAccountName (address, name) {
return this._transport
.execute('personal_setAccountName', inAddress(address), name);
}
setAccountMeta (address, meta) {
return this._transport
.execute('personal_setAccountMeta', inAddress(address), JSON.stringify(meta));
}
signAndSendTransaction (options, password) {
return this._transport
.execute('personal_signAndSendTransaction', inOptions(options), password);
}
signerEnabled () {
return this._transport
.execute('personal_signerEnabled');
}
testPassword (account, password) {
return this._transport
.execute('personal_testPassword', inAddress(account), password);
}
unlockAccount (account, password, duration = 1) {
return this._transport
.execute('personal_unlockAccount', inAddress(account), password, inNumber10(duration));

View File

@@ -19,35 +19,13 @@ import { TEST_HTTP_URL, mockHttp } from '../../../../test/mockRpc';
import Http from '../../transport/http';
import Personal from './personal';
const instance = new Personal(new Http(TEST_HTTP_URL));
const instance = new Personal(new Http(TEST_HTTP_URL, -1));
describe('rpc/Personal', () => {
const account = '0x63cf90d3f0410092fc0fca41846f596223979195';
const checksum = '0x63Cf90D3f0410092FC0fca41846f596223979195';
let scope;
describe('accountsInfo', () => {
it('retrieves the available account info', () => {
scope = mockHttp([{ method: 'personal_accountsInfo', reply: {
result: {
'0x63cf90d3f0410092fc0fca41846f596223979195': {
name: 'name', uuid: 'uuid', meta: '{"data":"data"}'
}
}
} }]);
return instance.accountsInfo().then((result) => {
expect(result).to.deep.equal({
'0x63Cf90D3f0410092FC0fca41846f596223979195': {
name: 'name', uuid: 'uuid', meta: {
data: 'data'
}
}
});
});
});
});
describe('listAccounts', () => {
it('retrieves a list of available accounts', () => {
scope = mockHttp([{ method: 'personal_listAccounts', reply: { result: [account] } }]);

View File

@@ -14,4 +14,4 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default from './events';
export default from './signer';

View File

@@ -0,0 +1,55 @@
// 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 <http://www.gnu.org/licenses/>.
import { inNumber16, inData } from '../../format/input';
import { outSignerRequest } from '../../format/output';
export default class Signer {
constructor (transport) {
this._transport = transport;
}
confirmRequest (requestId, options, password) {
return this._transport
.execute('signer_confirmRequest', inNumber16(requestId), options, password);
}
confirmRequestRaw (requestId, data) {
return this._transport
.execute('signer_confirmRequestRaw', inNumber16(requestId), inData(data));
}
generateAuthorizationToken () {
return this._transport
.execute('signer_generateAuthorizationToken');
}
rejectRequest (requestId) {
return this._transport
.execute('signer_rejectRequest', inNumber16(requestId));
}
requestsToConfirm () {
return this._transport
.execute('signer_requestsToConfirm')
.then((requests) => (requests || []).map(outSignerRequest));
}
signerEnabled () {
return this._transport
.execute('signer_signerEnabled');
}
}

View File

@@ -19,7 +19,7 @@ import { TEST_HTTP_URL, mockHttp } from '../../../../test/mockRpc';
import Http from '../../transport/http';
import Trace from './trace';
const instance = new Trace(new Http(TEST_HTTP_URL));
const instance = new Trace(new Http(TEST_HTTP_URL, -1));
describe('api/rpc/Trace', () => {
let scope;

View File

@@ -19,7 +19,7 @@ import { TEST_HTTP_URL, mockHttp } from '../../../../test/mockRpc';
import Http from '../../transport/http';
import Web3 from './web3';
const instance = new Web3(new Http(TEST_HTTP_URL));
const instance = new Web3(new Http(TEST_HTTP_URL, -1));
describe('api/rpc/Web3', () => {
let scope;

View File

@@ -24,9 +24,9 @@ import Signer from './signer';
const events = {
'logging': { module: 'logging' },
'eth_blockNumber': { module: 'eth' },
'personal_accountsInfo': { module: 'personal' },
'personal_listAccounts': { module: 'personal' },
'personal_requestsToConfirm': { module: 'signer' }
'parity_accountsInfo': { module: 'personal' },
'eth_accounts': { module: 'personal' },
'signer_requestsToConfirm': { module: 'signer' }
};
export default class Manager {
@@ -107,7 +107,6 @@ export default class Manager {
callback(error, data);
} catch (error) {
console.error(`Unable to update callback for subscriptionId ${subscriptionId}`, error);
this.unsubscribe(subscriptionId);
}
}

View File

@@ -37,18 +37,18 @@ export default class Personal {
}
_listAccounts = () => {
return this._api.personal
.listAccounts()
return this._api.eth
.accounts()
.then((accounts) => {
this._updateSubscriptions('personal_listAccounts', null, accounts);
this._updateSubscriptions('eth_accounts', null, accounts);
});
}
_accountsInfo = () => {
return this._api.personal
return this._api.parity
.accountsInfo()
.then((info) => {
this._updateSubscriptions('personal_accountsInfo', null, info);
this._updateSubscriptions('parity_accountsInfo', null, info);
});
}
@@ -59,16 +59,16 @@ export default class Personal {
}
switch (data.method) {
case 'personal_importGethAccounts':
case 'parity_importGethAccounts':
case 'personal_newAccount':
case 'personal_newAccountFromPhrase':
case 'personal_newAccountFromWallet':
case 'parity_newAccountFromPhrase':
case 'parity_newAccountFromWallet':
this._listAccounts();
this._accountsInfo();
return;
case 'personal_setAccountName':
case 'personal_setAccountMeta':
case 'parity_setAccountName':
case 'parity_setAccountMeta':
this._accountsInfo();
return;
}

View File

@@ -34,14 +34,15 @@ function stubApi (accounts, info) {
return {
_calls,
personal: {
parity: {
accountsInfo: () => {
const stub = sinon.stub().resolves(info || TEST_INFO)();
_calls.accountsInfo.push(stub);
return stub;
},
listAccounts: () => {
}
},
eth: {
accounts: () => {
const stub = sinon.stub().resolves(accounts || TEST_LIST)();
_calls.listAccounts.push(stub);
return stub;
@@ -85,17 +86,17 @@ describe('api/subscriptions/personal', () => {
expect(personal.isStarted).to.be.true;
});
it('calls personal_accountsInfo', () => {
it('calls parity_accountsInfo', () => {
expect(api._calls.accountsInfo.length).to.be.ok;
});
it('calls personal_listAccounts', () => {
it('calls eth_accounts', () => {
expect(api._calls.listAccounts.length).to.be.ok;
});
it('updates subscribers', () => {
expect(cb.firstCall).to.have.been.calledWith('personal_listAccounts', null, TEST_LIST);
expect(cb.secondCall).to.have.been.calledWith('personal_accountsInfo', null, TEST_INFO);
expect(cb.firstCall).to.have.been.calledWith('eth_accounts', null, TEST_LIST);
expect(cb.secondCall).to.have.been.calledWith('parity_accountsInfo', null, TEST_INFO);
});
});

View File

@@ -49,10 +49,10 @@ export default class Signer {
return;
}
return this._api.personal
return this._api.signer
.requestsToConfirm()
.then((requests) => {
this._updateSubscriptions('personal_requestsToConfirm', null, requests);
this._updateSubscriptions('signer_requestsToConfirm', null, requests);
nextTimeout();
})
.catch(nextTimeout);
@@ -65,7 +65,7 @@ export default class Signer {
}
switch (data.method) {
case 'eth_postTransaction':
case 'parity_postTransaction':
case 'eth_sendTranasction':
case 'eth_sendRawTransaction':
this._listRequests(false);

View File

@@ -19,11 +19,14 @@ import JsonRpcBase from '../jsonRpcBase';
/* global fetch */
export default class Http extends JsonRpcBase {
constructor (url) {
constructor (url, connectTimeout = 1000) {
super();
this._connected = true;
this._url = url;
this._connectTimeout = connectTimeout;
this._pollConnection();
}
_encodeOptions (method, params) {
@@ -56,6 +59,8 @@ export default class Http extends JsonRpcBase {
if (response.status !== 200) {
this._connected = false;
this.error(JSON.stringify({ status: response.status, statusText: response.statusText }));
console.error(`${method}(${JSON.stringify(params)}): ${response.status}: ${response.statusText}`);
throw new Error(`${response.status}: ${response.statusText}`);
}
@@ -66,11 +71,26 @@ export default class Http extends JsonRpcBase {
if (response.error) {
this.error(JSON.stringify(response));
throw new Error(`${response.error.code}: ${response.error.message}`);
console.error(`${method}(${JSON.stringify(params)}): ${response.error.code}: ${response.error.message}`);
throw new Error(`${method}: ${response.error.code}: ${response.error.message}`);
}
this.log(JSON.stringify(response));
return response.result;
});
}
_pollConnection = () => {
if (this._connectTimeout <= 0) {
return;
}
const nextTimeout = () => setTimeout(this._pollConnection, this._connectTimeout);
this
.execute('net_listening')
.then(nextTimeout)
.catch(nextTimeout);
}
}

View File

@@ -17,7 +17,7 @@
import { TEST_HTTP_URL, mockHttp } from '../../../../test/mockRpc';
import Http from './http';
const transport = new Http(TEST_HTTP_URL);
const transport = new Http(TEST_HTTP_URL, -1);
describe('api/transport/Http', () => {
describe('instance', () => {

View File

@@ -107,7 +107,9 @@ export default class Ws extends JsonRpcBase {
if (result.error) {
this.error(event.data);
reject(new Error(`${result.error.code}: ${result.error.message}`));
console.error(`${method}(${JSON.stringify(params)}): ${result.error.code}: ${result.error.message}`);
reject(new Error(`${method}: ${result.error.code}: ${result.error.message}`));
delete this._messages[result.id];
return;
}

View File

@@ -24,6 +24,7 @@ import owned from './owned.json';
import registry from './registry.json';
import signaturereg from './signaturereg.json';
import tokenreg from './tokenreg.json';
import wallet from './wallet.json';
export {
basiccoin,
@@ -35,5 +36,6 @@ export {
owned,
registry,
signaturereg,
tokenreg
tokenreg,
wallet
};

View File

@@ -0,0 +1 @@
[{"constant":false,"inputs":[{"name":"_owner","type":"address"}],"name":"removeOwner","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_addr","type":"address"}],"name":"isOwner","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":true,"inputs":[],"name":"m_numOwners","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[],"name":"m_lastDay","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[],"name":"resetSpentToday","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"m_spentToday","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[{"name":"_owner","type":"address"}],"name":"addOwner","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"m_required","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[{"name":"_h","type":"bytes32"}],"name":"confirm","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":false,"inputs":[{"name":"_newLimit","type":"uint256"}],"name":"setDailyLimit","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"},{"name":"_data","type":"bytes"}],"name":"execute","outputs":[{"name":"_r","type":"bytes32"}],"type":"function"},{"constant":false,"inputs":[{"name":"_operation","type":"bytes32"}],"name":"revoke","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_newRequired","type":"uint256"}],"name":"changeRequirement","outputs":[],"type":"function"},{"constant":true,"inputs":[{"name":"_operation","type":"bytes32"},{"name":"_owner","type":"address"}],"name":"hasConfirmed","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":true,"inputs":[{"name":"ownerIndex","type":"uint256"}],"name":"getOwner","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"}],"name":"kill","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"}],"name":"changeOwner","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"m_dailyLimit","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"inputs":[{"name":"_owners","type":"address[]"},{"name":"_required","type":"uint256"},{"name":"_daylimit","type":"uint256"}],"type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"operation","type":"bytes32"}],"name":"Confirmation","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"operation","type":"bytes32"}],"name":"Revoke","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"oldOwner","type":"address"},{"indexed":false,"name":"newOwner","type":"address"}],"name":"OwnerChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"newOwner","type":"address"}],"name":"OwnerAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"oldOwner","type":"address"}],"name":"OwnerRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"newRequirement","type":"uint256"}],"name":"RequirementChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_from","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"to","type":"address"},{"indexed":false,"name":"data","type":"bytes"}],"name":"SingleTransact","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"operation","type":"bytes32"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"to","type":"address"},{"indexed":false,"name":"data","type":"bytes"}],"name":"MultiTransact","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"operation","type":"bytes32"},{"indexed":false,"name":"initiator","type":"address"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"to","type":"address"},{"indexed":false,"name":"data","type":"bytes"}],"name":"ConfirmationNeeded","type":"event"}]

View File

@@ -14,22 +14,6 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
// 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 <http://www.gnu.org/licenses/>.
export default class DappReg {
constructor (api, registry) {
this._api = api;
@@ -69,4 +53,12 @@ export default class DappReg {
getImage (id) {
return this.meta(id, 'IMG');
}
getContent (id) {
return this.meta(id, 'CONTENT');
}
getManifest (id) {
return this.meta(id, 'MANIFEST');
}
}

View File

@@ -32,7 +32,7 @@ export default class Registry {
return;
}
this._api.ethcore
this._api.parity
.registryAddress()
.then((address) => {
this._instance = this._api.newContract(abis.registry, address).instance;

View File

@@ -0,0 +1,60 @@
/*
This Token Contract implements the standard token functionality (https://github.com/ethereum/EIPs/issues/20) as well as the following OPTIONAL extras intended for use by humans.
In other words. This is intended for deployment in something like a Token Factory or Mist wallet, and then used by humans.
Imagine coins, currencies, shares, voting weight, etc.
Machine-based, rapid creation of many tokens would not necessarily need these extra features or will be minted in other manners.
1) Initial Finite Supply (upon creation one specifies how much is minted).
2) In the absence of a token registry: Optional Decimal, Symbol & Name.
3) Optional approveAndCall() functionality to notify a contract if an approval() has occurred.
.*/
import "StandardToken.sol";
contract HumanStandardToken is StandardToken {
function () {
//if ether is sent to this address, send it back.
throw;
}
/* Public variables of the token */
/*
NOTE:
The following variables are OPTIONAL vanities. One does not have to include them.
They allow one to customise the token contract & in no way influences the core functionality.
Some wallets/interfaces might not even bother to look at this information.
*/
string public name; //fancy name: eg Simon Bucks
uint8 public decimals; //How many decimals to show. ie. There could 1000 base units with 3 decimals. Meaning 0.980 SBX = 980 base units. It's like comparing 1 wei to 1 ether.
string public symbol; //An identifier: eg SBX
string public version = 'H0.1'; //human 0.1 standard. Just an arbitrary versioning scheme.
function HumanStandardToken(
uint256 _initialAmount,
string _tokenName,
uint8 _decimalUnits,
string _tokenSymbol
) {
balances[msg.sender] = _initialAmount; // Give the creator all initial tokens
totalSupply = _initialAmount; // Update total supply
name = _tokenName; // Set the name for display purposes
decimals = _decimalUnits; // Amount of decimals for display purposes
symbol = _tokenSymbol; // Set the symbol for display purposes
}
/* Approves and then calls the receiving contract */
function approveAndCall(address _spender, uint256 _value, bytes _extraData) returns (bool success) {
allowed[msg.sender][_spender] = _value;
Approval(msg.sender, _spender, _value);
//call the receiveApproval function on the contract you want to be notified. This crafts the function signature manually so one doesn't have to include a contract in here just for this.
//receiveApproval(address _from, uint256 _value, address _tokenContract, bytes _extraData)
//it is assumed that when does this that the call *should* succeed, otherwise one would use vanilla approve instead.
if(!_spender.call(bytes4(bytes32(sha3("receiveApproval(address,uint256,address,bytes)"))), msg.sender, _value, this, _extraData)) { throw; }
return true;
}
}

View File

@@ -0,0 +1,55 @@
/*
You should inherit from StandardToken or, for a token like you would want to
deploy in something like Mist, see HumanStandardToken.sol.
(This implements ONLY the standard functions and NOTHING else.
If you deploy this, you won't have anything useful.)
Implements ERC 20 Token standard: https://github.com/ethereum/EIPs/issues/20
.*/
import "Token.sol";
contract StandardToken is Token {
function transfer(address _to, uint256 _value) returns (bool success) {
//Default assumes totalSupply can't be over max (2^256 - 1).
//If your token leaves out totalSupply and can issue more tokens as time goes on, you need to check if it doesn't wrap.
//Replace the if with this one instead.
//if (balances[msg.sender] >= _value && balances[_to] + _value > balances[_to]) {
if (balances[msg.sender] >= _value && _value > 0) {
balances[msg.sender] -= _value;
balances[_to] += _value;
Transfer(msg.sender, _to, _value);
return true;
} else { return false; }
}
function transferFrom(address _from, address _to, uint256 _value) returns (bool success) {
//same as above. Replace this line with the following if you want to protect against wrapping uints.
//if (balances[_from] >= _value && allowed[_from][msg.sender] >= _value && balances[_to] + _value > balances[_to]) {
if (balances[_from] >= _value && allowed[_from][msg.sender] >= _value && _value > 0) {
balances[_to] += _value;
balances[_from] -= _value;
allowed[_from][msg.sender] -= _value;
Transfer(_from, _to, _value);
return true;
} else { return false; }
}
function balanceOf(address _owner) constant returns (uint256 balance) {
return balances[_owner];
}
function approve(address _spender, uint256 _value) returns (bool success) {
allowed[msg.sender][_spender] = _value;
Approval(msg.sender, _spender, _value);
return true;
}
function allowance(address _owner, address _spender) constant returns (uint256 remaining) {
return allowed[_owner][_spender];
}
mapping (address => uint256) balances;
mapping (address => mapping (address => uint256)) allowed;
}

View File

@@ -0,0 +1,47 @@
// Abstract contract for the full ERC 20 Token standard
// https://github.com/ethereum/EIPs/issues/20
contract Token {
/* This is a slight change to the ERC20 base standard.
function totalSupply() constant returns (uint256 supply);
is replaced with:
uint256 public totalSupply;
This automatically creates a getter function for the totalSupply.
This is moved to the base contract since public getter functions are not
currently recognised as an implementation of the matching abstract
function by the compiler.
*/
/// total amount of tokens
uint256 public totalSupply;
/// @param _owner The address from which the balance will be retrieved
/// @return The balance
function balanceOf(address _owner) constant returns (uint256 balance);
/// @notice send `_value` token to `_to` from `msg.sender`
/// @param _to The address of the recipient
/// @param _value The amount of token to be transferred
/// @return Whether the transfer was successful or not
function transfer(address _to, uint256 _value) returns (bool success);
/// @notice send `_value` token to `_to` from `_from` on the condition it is approved by `_from`
/// @param _from The address of the sender
/// @param _to The address of the recipient
/// @param _value The amount of token to be transferred
/// @return Whether the transfer was successful or not
function transferFrom(address _from, address _to, uint256 _value) returns (bool success);
/// @notice `msg.sender` approves `_addr` to spend `_value` tokens
/// @param _spender The address of the account able to transfer the tokens
/// @param _value The amount of wei to be approved for transfer
/// @return Whether the approval was successful or not
function approve(address _spender, uint256 _value) returns (bool success);
/// @param _owner The address of the account owning tokens
/// @param _spender The address of the account able to transfer the tokens
/// @return Amount of remaining tokens allowed to spent
function allowance(address _owner, address _spender) constant returns (uint256 remaining);
event Transfer(address indexed _from, address indexed _to, uint256 _value);
event Approval(address indexed _owner, address indexed _spender, uint256 _value);
}

View File

@@ -4,6 +4,7 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<link rel="icon" href="/parity-logo-black-no-text.png" type="image/png">
<title>Basic Token Deployment</title>
</head>
<body>

View File

@@ -21,3 +21,7 @@
.iconMenu option {
padding-left: 30px;
}
.menu {
display: none;
}

View File

@@ -16,7 +16,7 @@
import React, { Component, PropTypes } from 'react';
// import { api } from '../parity';
import { api } from '../parity';
import { attachInstances } from '../services';
import Header from './Header';
@@ -83,7 +83,7 @@ export default class Application extends Component {
Promise
.all([
attachInstances(),
null // api.personal.accountsInfo()
api.parity.accounts()
])
.then(([{ managerInstance, registryInstance, tokenregInstance }, accountsInfo]) => {
accountsInfo = accountsInfo || {};

View File

@@ -148,7 +148,7 @@ export default class Deployment extends Component {
addresses={ addresses }
onChange={ this.onChangeFrom } />
<div className={ styles.hint }>
the owner account to eploy from
the owner account to deploy from
</div>
</div>
<div className={ nameError ? error : styles.input }>
@@ -296,7 +296,7 @@ export default class Deployment extends Component {
.then((signerRequestId) => {
this.setState({ signerRequestId, deployState: 'Transaction posted, Waiting for transaction authorization' });
return api.pollMethod('eth_checkRequest', signerRequestId);
return api.pollMethod('parity_checkRequest', signerRequestId);
})
.then((txHash) => {
this.setState({ txHash, deployState: 'Transaction authorized, Waiting for network confirmations' });

View File

@@ -279,7 +279,7 @@ export default class Send extends Component {
.then((signerRequestId) => {
this.setState({ signerRequestId, sendState: 'Transaction posted, Waiting for transaction authorization' });
return api.pollMethod('eth_checkRequest', signerRequestId);
return api.pollMethod('parity_checkRequest', signerRequestId);
})
.then((txHash) => {
this.setState({ txHash, sendState: 'Transaction authorized, Waiting for network confirmations' });

View File

@@ -100,8 +100,8 @@ export function attachInstances () {
return Promise
.all([
api.ethcore.registryAddress(),
api.ethcore.netChain()
api.parity.registryAddress(),
api.parity.netChain()
])
.then(([registryAddress, netChain]) => {
const registry = api.newContract(abis.registry, registryAddress).instance;

View File

@@ -1,16 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>GAVcoin</title>
</head>
<body>
<div id="container"></div>
<script src="vendor.js"></script>
<script src="commons.js"></script>
<script src="/parity-utils/parity.js"></script>
<script src="gavcoin.js"></script>
</body>
</html>

View File

@@ -1,33 +0,0 @@
// 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 <http://www.gnu.org/licenses/>.
import ReactDOM from 'react-dom';
import React from 'react';
import injectTapEventPlugin from 'react-tap-event-plugin';
injectTapEventPlugin();
import Application from './gavcoin/Application';
import '../../assets/fonts/Roboto/font.css';
import '../../assets/fonts/RobotoMono/font.css';
import './style.css';
import './gavcoin.html';
ReactDOM.render(
<Application />,
document.querySelector('#container')
);

View File

@@ -1,63 +0,0 @@
// 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 <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import IdentityIcon from '../../IdentityIcon';
import styles from './accountItem.css';
export default class AccountItem extends Component {
static propTypes = {
account: PropTypes.object,
gavBalance: PropTypes.bool
};
render () {
const { account, gavBalance } = this.props;
let balance;
let token;
if (gavBalance) {
if (account.gavBalance) {
balance = account.gavBalance;
token = 'GAV';
}
} else {
if (account.ethBalance) {
balance = account.ethBalance;
token = 'ETH';
}
}
return (
<div className={ styles.account }>
<div className={ styles.image }>
<IdentityIcon address={ account.address } />
</div>
<div className={ styles.details }>
<div className={ styles.name }>
{ account.name || account.address }
</div>
<div className={ styles.balance }>
{ balance }<small> { token }</small>
</div>
</div>
</div>
);
}
}

View File

@@ -1,102 +0,0 @@
// 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 <http://www.gnu.org/licenses/>.
import BigNumber from 'bignumber.js';
import React, { Component, PropTypes } from 'react';
import { MenuItem, SelectField } from 'material-ui';
import AccountItem from './AccountItem';
const NAME_ID = ' ';
let lastSelectedAccount = {};
export default class AccountSelect extends Component {
static propTypes = {
accounts: PropTypes.array,
account: PropTypes.object,
anyAccount: PropTypes.bool,
gavBalance: PropTypes.bool,
onSelect: PropTypes.func,
errorText: PropTypes.string,
floatingLabelText: PropTypes.string,
hintText: PropTypes.string
}
componentDidMount () {
this.props.onSelect(lastSelectedAccount);
}
render () {
const { account, accounts, anyAccount, errorText, floatingLabelText, gavBalance, hintText } = this.props;
return (
<SelectField
autoComplete='off'
floatingLabelFixed
floatingLabelText={ floatingLabelText }
fullWidth
hintText={ hintText }
errorText={ errorText }
name={ NAME_ID }
id={ NAME_ID }
value={ account }
onChange={ this.onSelect }>
{ renderAccounts(accounts, { anyAccount, gavBalance }) }
</SelectField>
);
}
onSelect = (event, idx, account) => {
lastSelectedAccount = account || {};
this.props.onSelect(lastSelectedAccount);
}
}
function isPositive (numberStr) {
return new BigNumber(numberStr.replace(',', '')).gt(0);
}
export function renderAccounts (accounts, options = {}) {
return accounts
.filter((account) => {
if (options.anyAccount) {
return true;
}
if (account.uuid) {
return isPositive(account[options.gavBalance ? 'gavBalance' : 'ethBalance']);
}
return false;
})
.map((account) => {
const item = (
<AccountItem
account={ account }
key={ account.address }
gavBalance={ options.gavBalance || false } />
);
return (
<MenuItem
key={ account.address }
value={ account }
label={ item }>
{ item }
</MenuItem>
);
});
}

View File

@@ -1,106 +0,0 @@
// 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 <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import { TextField } from 'material-ui';
import IdentityIcon from '../IdentityIcon';
import AccountSelector from '../AccountSelector';
import styles from './accountSelectorText.css';
const NAME_ID = ' ';
export default class AccountSelectorText extends Component {
static propTypes = {
accounts: PropTypes.array,
account: PropTypes.object,
errorText: PropTypes.string,
gavBalance: PropTypes.bool,
anyAccount: PropTypes.bool,
floatingLabelText: PropTypes.string,
hintText: PropTypes.string,
selector: PropTypes.bool,
onChange: PropTypes.func
}
render () {
const { selector } = this.props;
return selector
? this.renderDropDown()
: this.renderTextField();
}
renderDropDown () {
const { account, accounts, anyAccount, errorText, gavBalance, hintText, floatingLabelText, onChange } = this.props;
return (
<AccountSelector
anyAccount={ anyAccount }
gavBalance={ gavBalance }
accounts={ accounts }
account={ account }
errorText={ errorText }
floatingLabelText={ floatingLabelText }
hintText={ hintText }
onSelect={ onChange } />
);
}
renderTextField () {
const { account, errorText, hintText, floatingLabelText } = this.props;
return (
<div className={ styles.addrtext }>
<TextField
className={ styles.input }
autoComplete='off'
floatingLabelFixed
floatingLabelText={ floatingLabelText }
fullWidth
hintText={ hintText }
errorText={ errorText }
name={ NAME_ID }
id={ NAME_ID }
value={ account.address || '' }
onChange={ this.onChangeAddress } />
{ this.renderAddressIcon() }
</div>
);
}
renderAddressIcon () {
const { account } = this.props;
if (!account.address) {
return null;
}
return (
<div className={ styles.addricon }>
<IdentityIcon address={ account.address } />
</div>
);
}
onChangeAddress = (event, address) => {
const lower = address.toLowerCase();
const account = this.props.accounts.find((_account) => _account.address.toLowerCase() === lower);
this.props.onChange(account || { address });
}
}

View File

@@ -1,17 +0,0 @@
// 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 <http://www.gnu.org/licenses/>.
export default from './accountSelectorText';

View File

@@ -1,47 +0,0 @@
/* 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 <http://www.gnu.org/licenses/>.
*/
.accounts {
padding: 4em 2em 0 2em;
text-align: center;
width: 100%;
}
.account {
margin: 0.5em !important;
background: rgb(50, 100, 150) !important;
display: inline-block !important;
}
.account img {
margin-bottom: -11px;
margin-left: -11px;
}
.balance {
font-family: 'Roboto Mono', monospace;
color: rgba(255, 255, 255, 1) !important;
}
.name {
color: rgba(255, 255, 255, 0.7) !important;
margin-right: 1em;
text-transform: uppercase;
}
.none {
color: rgba(255, 255, 255, 0.7);
}

View File

@@ -1,83 +0,0 @@
// 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 <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import { Chip } from 'material-ui';
import IdentityIcon from '../IdentityIcon';
import styles from './accounts.css';
export default class Accounts extends Component {
static contextTypes = {
api: PropTypes.object.isRequired,
instance: PropTypes.object.isRequired
}
static propTypes = {
accounts: PropTypes.array
}
render () {
const has = this._hasAccounts();
return (
<div className={ styles.accounts }>
{ has ? this.renderAccounts() : this.renderEmpty() }
</div>
);
}
renderEmpty () {
return (
<div className={ styles.none }>
You currently do not have any GAVcoin in any of your addresses, buy some
</div>
);
}
renderAccounts () {
const { accounts } = this.props;
return accounts
.filter((account) => account.hasGav)
.map((account) => {
return (
<Chip
className={ styles.account }
key={ account.address }>
<IdentityIcon address={ account.address } />
<span className={ styles.name }>
{ account.name }
</span>
<span className={ styles.balance }>
{ account.gavBalance }
</span>
</Chip>
);
});
}
_hasAccounts () {
const { accounts } = this.props;
if (!accounts || !accounts.length) {
return false;
}
return accounts.filter((account) => account.hasGav).length !== 0;
}
}

View File

@@ -1,206 +0,0 @@
// 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 <http://www.gnu.org/licenses/>.
import BigNumber from 'bignumber.js';
import React, { Component, PropTypes } from 'react';
import { Dialog, FlatButton, TextField } from 'material-ui';
import { api } from '../../parity';
import AccountSelector from '../../AccountSelector';
import { ERRORS, validateAccount, validatePositiveNumber } from '../validation';
import styles from '../actions.css';
const NAME_ID = ' ';
export default class ActionBuyIn extends Component {
static contextTypes = {
instance: PropTypes.object.isRequired
}
static propTypes = {
accounts: PropTypes.array,
price: PropTypes.object,
onClose: PropTypes.func
}
state = {
account: {},
accountError: ERRORS.invalidAccount,
amount: 0,
amountError: ERRORS.invalidAmount,
maxPrice: api.util.fromWei(this.props.price.mul(1.2)).toString(),
maxPriceError: null,
sending: false,
complete: false
}
render () {
const { complete } = this.state;
if (complete) {
return null;
}
return (
<Dialog
title='buy coins for a specific account'
modal open
className={ styles.dialog }
actions={ this.renderActions() }>
{ this.renderFields() }
</Dialog>
);
}
renderActions () {
const { complete } = this.state;
if (complete) {
return (
<FlatButton
label='Done'
primary
onTouchTap={ this.props.onClose } />
);
}
const { accountError, amountError, maxPriceError, sending } = this.state;
const hasError = !!(amountError || accountError || maxPriceError);
return ([
<FlatButton
label='Cancel'
primary
onTouchTap={ this.props.onClose } />,
<FlatButton
label='Buy'
primary
disabled={ hasError || sending }
onTouchTap={ this.onSend } />
]);
}
renderFields () {
const maxPriceLabel = `maximum price in ETH (current ${api.util.fromWei(this.props.price).toFormat(3)})`;
return (
<div>
<AccountSelector
accounts={ this.props.accounts }
account={ this.state.account }
errorText={ this.state.accountError }
floatingLabelText='from account'
hintText='the account the transaction will be made from'
onSelect={ this.onChangeAddress } />
<TextField
autoComplete='off'
floatingLabelFixed
floatingLabelText='amount in ETH'
fullWidth
hintText='the amount of ETH you wish to spend'
errorText={ this.state.amountError }
name={ NAME_ID }
id={ NAME_ID }
value={ this.state.amount }
onChange={ this.onChangeAmount } />
<TextField
autoComplete='off'
floatingLabelFixed
floatingLabelText={ maxPriceLabel }
fullWidth
hintText='the maxium price allowed for buying'
errorText={ this.state.maxPriceError }
name={ NAME_ID }
id={ NAME_ID }
value={ this.state.maxPrice }
onChange={ this.onChangeMaxPrice } />
</div>
);
}
onChangeAddress = (account) => {
this.setState({
account,
accountError: validateAccount(account)
}, this.validateTotal);
}
onChangeAmount = (event, amount) => {
this.setState({
amount,
amountError: validatePositiveNumber(amount)
}, this.validateTotal);
}
onChangeMaxPrice = (event, maxPrice) => {
this.setState({
maxPrice,
maxPriceError: validatePositiveNumber(maxPrice)
});
}
validateTotal = () => {
const { account, accountError, amount, amountError } = this.state;
if (accountError || amountError) {
return;
}
if (new BigNumber(amount).gt(account.ethBalance.replace(',', ''))) {
this.setState({
amountError: ERRORS.invalidTotal
});
}
}
onSend = () => {
const { instance } = this.context;
const maxPrice = api.util.toWei(this.state.maxPrice);
const values = [this.state.account.address, maxPrice.toString()];
const options = {
from: this.state.account.address,
value: api.util.toWei(this.state.amount).toString()
};
this.setState({
sending: true
});
instance.buyin
.estimateGas(options, values)
.then((gasEstimate) => {
options.gas = gasEstimate.mul(1.2).toFixed(0);
console.log(`buyin: gas estimated as ${gasEstimate.toFixed(0)} setting to ${options.gas}`);
return instance.buyin.postTransaction(options, values);
})
.then(() => {
this.props.onClose();
this.setState({
sending: false,
complete: true
});
})
.catch((error) => {
console.error('error', error);
this.setState({
sending: false
});
});
}
}

View File

@@ -1,191 +0,0 @@
// 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 <http://www.gnu.org/licenses/>.
import BigNumber from 'bignumber.js';
import React, { Component, PropTypes } from 'react';
import { Dialog, FlatButton, TextField } from 'material-ui';
import { api } from '../../parity';
import AccountSelector from '../../AccountSelector';
import { ERRORS, validateAccount, validatePositiveNumber } from '../validation';
import styles from '../actions.css';
const DIVISOR = 10 ** 6;
const NAME_ID = ' ';
export default class ActionRefund extends Component {
static contextTypes = {
instance: PropTypes.object.isRequired
}
static propTypes = {
accounts: PropTypes.array,
price: PropTypes.object,
onClose: PropTypes.func
}
state = {
account: {},
accountError: ERRORS.invalidAccount,
complete: false,
sending: false,
amount: 0,
amountError: ERRORS.invalidAmount,
price: api.util.fromWei(this.props.price).toString(),
priceError: null
}
render () {
const { complete } = this.state;
if (complete) {
return null;
}
return (
<Dialog
title='return coins for a refund'
modal open
className={ styles.dialog }
actions={ this.renderActions() }>
{ this.renderFields() }
</Dialog>
);
}
renderActions () {
if (this.state.complete) {
return (
<FlatButton
label='Done'
primary
onTouchTap={ this.props.onClose } />
);
}
const hasError = !!(this.state.priceError || this.state.amountError || this.state.accountError);
return ([
<FlatButton
label='Cancel'
primary
onTouchTap={ this.props.onClose } />,
<FlatButton
label='Refund'
primary
disabled={ hasError || this.state.sending }
onTouchTap={ this.onSend } />
]);
}
renderFields () {
const priceLabel = `price in ETH (current ${api.util.fromWei(this.props.price).toFormat(3)})`;
return (
<div>
<AccountSelector
gavBalance
accounts={ this.props.accounts }
account={ this.state.account }
errorText={ this.state.accountError }
floatingLabelText='from account'
hintText='the account the transaction will be made from'
onSelect={ this.onChangeAddress } />
<TextField
autoComplete='off'
floatingLabelFixed
floatingLabelText='number of coins'
fullWidth
hintText='the number of coins to exchange for an ETH refund'
errorText={ this.state.amountError }
name={ NAME_ID }
id={ NAME_ID }
value={ this.state.amount }
onChange={ this.onChangeAmount } />
<TextField
autoComplete='off'
floatingLabelFixed
floatingLabelText={ priceLabel }
fullWidth
hintText='the price the refund is requested at'
errorText={ this.state.priceError }
name={ NAME_ID }
id={ NAME_ID }
value={ this.state.price }
onChange={ this.onChangePrice } />
</div>
);
}
onChangeAddress = (account) => {
this.setState({
account,
accountError: validateAccount(account)
});
}
onChangeAmount = (event, amount) => {
this.setState({
amount,
amountError: validatePositiveNumber(amount)
});
}
onChangePrice = (event, price) => {
this.setState({
price,
priceError: validatePositiveNumber(price)
});
}
onSend = () => {
const { instance } = this.context;
const price = api.util.toWei(this.state.price);
const amount = new BigNumber(this.state.amount).mul(DIVISOR);
const values = [price.toString(), amount.toFixed(0)];
const options = {
from: this.state.account.address
};
this.setState({
sending: true
});
instance.refund
.estimateGas(options, values)
.then((gasEstimate) => {
options.gas = gasEstimate.mul(1.2).toFixed(0);
console.log(`refund: gas estimated as ${gasEstimate.toFixed(0)} setting to ${options.gas}`);
return instance.refund.postTransaction(options, values);
})
.then(() => {
this.props.onClose();
this.setState({
sending: false,
complete: true
});
})
.catch((error) => {
console.error('error', error);
this.setState({
sending: false
});
});
}
}

View File

@@ -1,17 +0,0 @@
// 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 <http://www.gnu.org/licenses/>.
export default from './actionRefund';

View File

@@ -1,220 +0,0 @@
// 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 <http://www.gnu.org/licenses/>.
import BigNumber from 'bignumber.js';
import React, { Component, PropTypes } from 'react';
import { Dialog, FlatButton, TextField, Toggle } from 'material-ui';
import AccountSelector from '../../AccountSelector';
import AccountSelectorText from '../../AccountSelectorText';
import { ERRORS, validateAccount, validatePositiveNumber } from '../validation';
import styles from '../actions.css';
const DIVISOR = 10 ** 6;
const NAME_ID = ' ';
export default class ActionTransfer extends Component {
static contextTypes = {
instance: PropTypes.object.isRequired
}
static propTypes = {
accounts: PropTypes.array,
price: PropTypes.object,
onClose: PropTypes.func
}
state = {
fromAccount: {},
fromAccountError: ERRORS.invalidAccount,
toAccount: {},
toAccountError: ERRORS.invalidRecipient,
inputAccount: false,
complete: false,
sending: false,
amount: 0,
amountError: ERRORS.invalidAmount
}
render () {
const { complete } = this.state;
if (complete) {
return null;
}
return (
<Dialog
title='send coins to another account'
modal open
className={ styles.dialog }
actions={ this.renderActions() }>
{ this.renderFields() }
</Dialog>
);
}
renderActions () {
const { complete, sending, amountError, fromAccountError, toAccountError } = this.state;
if (complete) {
return (
<FlatButton
label='Done'
primary
onTouchTap={ this.props.onClose } />
);
}
const hasError = !!(amountError || fromAccountError || toAccountError);
return ([
<FlatButton
label='Cancel'
primary
onTouchTap={ this.props.onClose } />,
<FlatButton
label='Transfer'
primary
disabled={ hasError || sending }
onTouchTap={ this.onSend } />
]);
}
renderFields () {
const { accounts } = this.props;
const { fromAccount, fromAccountError, toAccount, toAccountError, inputAccount, amount, amountError } = this.state;
return (
<div>
<AccountSelector
gavBalance
accounts={ accounts }
account={ fromAccount }
errorText={ fromAccountError }
floatingLabelText='from account'
hintText='the account the transaction will be made from'
onSelect={ this.onChangeFromAccount } />
<div className={ styles.overlay }>
<AccountSelectorText
gavBalance anyAccount
selector={ !inputAccount }
accounts={ accounts }
account={ toAccount }
errorText={ toAccountError }
floatingLabelText='to account'
hintText='the account the coins will be sent to'
onChange={ this.onChangeToAccount } />
<Toggle
className={ styles.overlaytoggle }
label='Edit'
labelPosition='right'
toggled={ inputAccount }
onToggle={ this.onChangeToInput } />
</div>
<TextField
autoComplete='off'
floatingLabelFixed
floatingLabelText='number of coins'
fullWidth
hintText='the number of coins to transfer'
errorText={ amountError }
name={ NAME_ID }
id={ NAME_ID }
value={ amount }
onChange={ this.onChangeAmount } />
</div>
);
}
onChangeFromAccount = (fromAccount) => {
this.setState({
fromAccount,
fromAccountError: validateAccount(fromAccount)
}, this.validateTotal);
}
onChangeToAccount = (toAccount) => {
this.setState({
toAccount,
toAccountError: validateAccount(toAccount)
});
}
onChangeToInput = () => {
this.setState({
inputAccount: !this.state.inputAccount
});
}
onChangeAmount = (event, amount) => {
this.setState({
amount,
amountError: validatePositiveNumber(amount)
}, this.validateTotal);
}
validateTotal = () => {
const { fromAccount, fromAccountError, amount, amountError } = this.state;
if (fromAccountError || amountError) {
return;
}
if (new BigNumber(amount).gt(fromAccount.gavBalance.replace(',', ''))) {
this.setState({
amountError: ERRORS.invalidTotal
});
}
}
onSend = () => {
const { instance } = this.context;
const amount = new BigNumber(this.state.amount).mul(DIVISOR);
const values = [this.state.toAccount.address, amount.toFixed(0)];
const options = {
from: this.state.fromAccount.address
};
this.setState({
sending: true
});
instance.transfer
.estimateGas(options, values)
.then((gasEstimate) => {
options.gas = gasEstimate.mul(1.2).toFixed(0);
console.log(`transfer: gas estimated as ${gasEstimate.toFixed(0)} setting to ${options.gas}`);
return instance.transfer.postTransaction(options, values);
})
.then(() => {
this.props.onClose();
this.setState({
sending: false,
complete: true
});
})
.catch((error) => {
console.error('error', error);
this.setState({
sending: false
});
});
}
}

View File

@@ -1,17 +0,0 @@
// 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 <http://www.gnu.org/licenses/>.
export default from './actionTransfer';

View File

@@ -1,17 +0,0 @@
// 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 <http://www.gnu.org/licenses/>.
export default from './stepComplete';

View File

@@ -1,29 +0,0 @@
// 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 <http://www.gnu.org/licenses/>.
import React, { Component } from 'react';
import styles from '../actions.css';
export default class StepComplete extends Component {
render () {
return (
<div className={ styles.dialogtext }>
Your transaction has been posted. Please visit the <a href='http://127.0.0.1:8080/#/signer' className={ styles.link } target='_blank'>Parity Signer</a> to authenticate the transfer.
</div>
);
}
}

View File

@@ -1,72 +0,0 @@
/* 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 <http://www.gnu.org/licenses/>.
*/
.actions {
text-align: center;
padding: 2em 2em 0 2em;
width: 100%;
}
.button {
margin: 0 0.5em;
}
.button button {
background-color: rgba(50, 100, 150, 1) !important;
height: 56px !important;
padding: 0 10px !important;
}
.button button[disabled] {
background-color: rgba(50, 50, 50, 0.25) !important;
}
.dialog {
}
.dialog h3 {
color: rgba(50, 100, 150, 1) !important;
text-transform: uppercase;
}
.dialogtext {
padding-top: 1em;
}
.link, .link:hover, .link:visited {
color: rgb(0, 188, 212);
text-decoration: none;
}
.overlay {
position: relative;
}
.overlay svg {
opacity: 0;
}
.toggle {
text-align: right;
}
.overlaytoggle {
position: absolute !important;
top: 40px;
right: 0;
display: inline-block !important;
width: auto !important;
}

View File

@@ -1,76 +0,0 @@
// 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 <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import { RaisedButton } from 'material-ui';
import ActionAddShoppingCart from 'material-ui/svg-icons/action/add-shopping-cart';
// import AvReplay from 'material-ui/svg-icons/av/replay';
import ContentSend from 'material-ui/svg-icons/content/send';
import styles from './actions.css';
export default class Actions extends Component {
static propTypes = {
onAction: PropTypes.func.isRequired,
gavBalance: PropTypes.object.isRequired
}
render () {
const { gavBalance } = this.props;
return (
<div className={ styles.actions }>
<RaisedButton
className={ styles.button }
icon={ <ActionAddShoppingCart /> }
label='buy coins'
primary
onTouchTap={ this.onBuyIn } />
<RaisedButton
disabled={ !gavBalance || gavBalance.eq(0) }
className={ styles.button }
icon={ <ContentSend /> }
label='send coins'
primary
onTouchTap={ this.onTransfer } />
</div>
);
// <RaisedButton
// className={ styles.button }
// icon={ <AvReplay /> }
// label='claim refund'
// primary
// onTouchTap={ this.onRefund } />
}
onBuyIn = () => {
this.props.onAction('BuyIn');
}
onTransfer = () => {
const { gavBalance } = this.props;
if (gavBalance && gavBalance.gt(0)) {
this.props.onAction('Transfer');
}
}
onRefund = () => {
this.props.onAction('Refund');
}
}

View File

@@ -1,56 +0,0 @@
// 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 <http://www.gnu.org/licenses/>.
import BigNumber from 'bignumber.js';
import { api } from '../parity';
export const ERRORS = {
invalidAccount: 'please select an account to transact with',
invalidRecipient: 'please select an account to send to',
invalidAddress: 'the address is not in the correct format',
invalidAmount: 'please enter a positive amount > 0',
invalidTotal: 'the amount is greater than the availale balance'
};
export function validatePositiveNumber (value) {
let bn = null;
try {
bn = new BigNumber(value);
} catch (e) {
}
if (!bn || !bn.gt(0)) {
return ERRORS.invalidAmount;
}
return null;
}
export function validateAccount (account) {
if (!account || !account.address) {
return ERRORS.invalidAccount;
}
if (!api.util.isAddressValid(account.address)) {
return ERRORS.invalidAddress;
}
account.address = api.util.toChecksumAddress(account.address);
return null;
}

View File

@@ -1,240 +0,0 @@
// 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 <http://www.gnu.org/licenses/>.
import BigNumber from 'bignumber.js';
import React, { Component, PropTypes } from 'react';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
import lightBaseTheme from 'material-ui/styles/baseThemes/lightBaseTheme';
const muiTheme = getMuiTheme(lightBaseTheme);
import { api } from '../parity';
import * as abis from '../../../contracts/abi';
import Accounts from '../Accounts';
import Actions, { ActionBuyIn, ActionRefund, ActionTransfer } from '../Actions';
import Events from '../Events';
import Loading from '../Loading';
import Status from '../Status';
const DIVISOR = 10 ** 6;
export default class Application extends Component {
static childContextTypes = {
api: PropTypes.object,
contract: PropTypes.object,
instance: PropTypes.object,
muiTheme: PropTypes.object
};
state = {
action: null,
address: null,
accounts: [],
blockNumber: new BigNumber(-1),
ethBalance: new BigNumber(0),
gavBalance: new BigNumber(0),
instance: null,
loading: true,
price: null,
remaining: null,
totalSupply: null
}
componentDidMount () {
this.attachInterface();
}
render () {
const { accounts, address, blockNumber, gavBalance, loading, price, remaining, totalSupply } = this.state;
if (loading) {
return (
<Loading />
);
}
return (
<div>
{ this.renderModals() }
<Status
address={ address }
blockNumber={ blockNumber }
gavBalance={ gavBalance }
price={ price }
remaining={ remaining }
totalSupply={ totalSupply }>
<Accounts
accounts={ accounts } />
</Status>
<Actions
gavBalance={ gavBalance }
onAction={ this.onAction } />
<Events
accounts={ accounts } />
</div>
);
}
renderModals () {
const { action, accounts, price } = this.state;
switch (action) {
case 'BuyIn':
return (
<ActionBuyIn
accounts={ accounts }
price={ price }
onClose={ this.onActionClose } />
);
case 'Refund':
return (
<ActionRefund
accounts={ accounts }
price={ price }
onClose={ this.onActionClose } />
);
case 'Transfer':
return (
<ActionTransfer
accounts={ accounts }
onClose={ this.onActionClose } />
);
default:
return null;
}
}
getChildContext () {
const { contract, instance } = this.state;
return {
api,
contract,
instance,
muiTheme
};
}
onAction = (action) => {
this.setState({
action
});
}
onActionClose = () => {
this.setState({
action: null
});
}
onNewBlockNumber = (_error, blockNumber) => {
const { instance, accounts } = this.state;
if (_error) {
console.error('onNewBlockNumber', _error);
return;
}
Promise
.all([
instance.totalSupply.call(),
instance.remaining.call(),
instance.price.call()
])
.then(([totalSupply, remaining, price]) => {
this.setState({
blockNumber,
totalSupply,
remaining,
price
});
const gavQueries = accounts.map((account) => instance.balanceOf.call({}, [account.address]));
const ethQueries = accounts.map((account) => api.eth.getBalance(account.address));
return Promise.all([
Promise.all(gavQueries),
Promise.all(ethQueries)
]);
})
.then(([gavBalances, ethBalances]) => {
this.setState({
ethBalance: ethBalances.reduce((total, balance) => total.add(balance), new BigNumber(0)),
gavBalance: gavBalances.reduce((total, balance) => total.add(balance), new BigNumber(0)),
accounts: accounts.map((account, idx) => {
const ethBalance = ethBalances[idx];
const gavBalance = gavBalances[idx];
account.ethBalance = api.util.fromWei(ethBalance).toFormat(3);
account.gavBalance = gavBalance.div(DIVISOR).toFormat(6);
account.hasGav = gavBalance.gt(0);
return account;
})
});
})
.catch((error) => {
console.error('onNewBlockNumber', error);
});
}
attachInterface = () => {
api.ethcore
.registryAddress()
.then((registryAddress) => {
console.log(`the registry was found at ${registryAddress}`);
const registry = api.newContract(abis.registry, registryAddress).instance;
return Promise
.all([
registry.getAddress.call({}, [api.util.sha3('gavcoin'), 'A']),
api.eth.accounts(),
null // api.personal.accountsInfo()
]);
})
.then(([address, addresses, infos]) => {
infos = infos || {};
console.log(`gavcoin was found at ${address}`);
const contract = api.newContract(abis.gavcoin, address);
this.setState({
loading: false,
address,
contract,
instance: contract.instance,
accounts: addresses.map((address) => {
const info = infos[address] || {};
return {
address,
name: info.name,
uuid: info.uuid
};
})
});
api.subscribe('eth_blockNumber', this.onNewBlockNumber);
})
.catch((error) => {
console.error('attachInterface', error);
});
}
}

View File

@@ -1,17 +0,0 @@
// 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 <http://www.gnu.org/licenses/>.
export default from './application';

View File

@@ -1,129 +0,0 @@
// 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 <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import IdentityIcon from '../../IdentityIcon';
import { formatBlockNumber, formatCoins, formatEth } from '../../format';
import styles from '../events.css';
const EMPTY_COLUMN = (
<td></td>
);
export default class Event extends Component {
static contextTypes = {
accounts: PropTypes.array.isRequired
}
static propTypes = {
event: PropTypes.object,
value: PropTypes.object,
price: PropTypes.object,
fromAddress: PropTypes.string,
toAddress: PropTypes.string
}
render () {
const { event, fromAddress, toAddress, price, value } = this.props;
const { blockNumber, state, type } = event;
const cls = `${styles.event} ${styles[state]} ${styles[type.toLowerCase()]}`;
return (
<tr className={ cls }>
{ this.renderBlockNumber(blockNumber) }
{ this.renderType(type) }
{ this.renderValue(value) }
{ this.renderPrice(price) }
{ this.renderAddress(fromAddress) }
{ this.renderAddress(toAddress) }
</tr>
);
}
renderBlockNumber (blockNumber) {
return (
<td className={ styles.blocknumber }>
{ formatBlockNumber(blockNumber) }
</td>
);
}
renderAddress (address) {
if (!address) {
return EMPTY_COLUMN;
}
return (
<td className={ styles.account }>
<IdentityIcon address={ address } />
{ this.renderAddressName(address) }
</td>
);
}
renderAddressName (address) {
const { accounts } = this.context;
const account = accounts.find((_account) => _account.address === address);
if (account && account.name) {
return (
<div className={ styles.name }>
{ account.name }
</div>
);
}
return (
<div className={ styles.address }>
{ address }
</div>
);
}
renderPrice (price) {
if (!price) {
return EMPTY_COLUMN;
}
return (
<td className={ styles.ethvalue }>
{ formatEth(price) }<small> ETH</small>
</td>
);
}
renderValue (value) {
if (!value) {
return EMPTY_COLUMN;
}
return (
<td className={ styles.gavvalue }>
{ formatCoins(value) }<small> GAV</small>
</td>
);
}
renderType (type) {
return (
<td className={ styles.type }>
{ type }
</td>
);
}
}

View File

@@ -1,38 +0,0 @@
// 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 <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import Event from '../Event';
export default class EventBuyin extends Component {
static propTypes = {
event: PropTypes.object
}
render () {
const { event } = this.props;
const { buyer, price, amount } = event.params;
return (
<Event
event={ event }
fromAddress={ buyer }
value={ amount }
price={ price } />
);
}
}

View File

@@ -1,17 +0,0 @@
// 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 <http://www.gnu.org/licenses/>.
export default from './eventBuyin';

View File

@@ -1,17 +0,0 @@
// 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 <http://www.gnu.org/licenses/>.
export default from './eventNewTranch';

View File

@@ -1,38 +0,0 @@
// 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 <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import Event from '../Event';
export default class EventRefund extends Component {
static propTypes = {
event: PropTypes.object
}
render () {
const { event } = this.props;
const { buyer, price, amount } = event.params;
return (
<Event
event={ event }
fromAddress={ buyer }
value={ amount }
price={ price } />
);
}
}

View File

@@ -1,17 +0,0 @@
// 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 <http://www.gnu.org/licenses/>.
export default from './eventRefund';

View File

@@ -1,38 +0,0 @@
// 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 <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import Event from '../Event';
export default class EventTransfer extends Component {
static propTypes = {
event: PropTypes.object
}
render () {
const { event } = this.props;
const { from, to, value } = event.params;
return (
<Event
event={ event }
fromAddress={ from }
toAddress={ to }
value={ value } />
);
}
}

View File

@@ -1,17 +0,0 @@
// 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 <http://www.gnu.org/licenses/>.
export default from './eventTransfer';

View File

@@ -1,94 +0,0 @@
/* 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 <http://www.gnu.org/licenses/>.
*/
.events {
padding: 4em 2em;
}
.list {
width: 100%;
border: none;
border-spacing: 0;
}
.list td {
vertical-align: top;
padding: 4px 0.5em;
max-height: 32px;
}
.event {
line-height: 32px;
vertical-align: top;
}
.blocknumber,
.ethvalue,
.gavvalue {
font-family: 'Roboto Mono', monospace;
}
.blocknumber,
.gavvalue {
text-align: right;
}
.ethvalue {
text-align: center;
}
.type {
}
.account {
}
.account img {
margin-bottom: -11px;
}
.address {
}
.name {
text-transform: uppercase;
}
.event div {
display: inline;
margin-right: 1em;
vertical-align: top;
}
.mined {
}
.pending {
opacity: 0.5;
}
.buyin {
}
.refund {
}
.transfer {
}
.newtranch {
background: rgba(50, 250, 50, 0.1);
}

View File

@@ -1,161 +0,0 @@
// 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 <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import { api } from '../parity';
import EventBuyin from './EventBuyin';
import EventNewTranch from './EventNewTranch';
import EventRefund from './EventRefund';
import EventTransfer from './EventTransfer';
import styles from './events.css';
export default class Events extends Component {
static childContextTypes = {
accounts: PropTypes.array
}
static contextTypes = {
contract: PropTypes.object.isRequired,
instance: PropTypes.object.isRequired
}
static propTypes = {
accounts: PropTypes.array
}
state = {
allEvents: [],
minedEvents: [],
pendingEvents: []
}
componentDidMount () {
this.setupFilters();
}
render () {
return (
<div className={ styles.events }>
<table className={ styles.list }>
<tbody>
{ this.renderEvents() }
</tbody>
</table>
</div>
);
}
renderEvents () {
const { allEvents } = this.state;
if (!allEvents.length) {
return null;
}
return allEvents
.map((event) => {
switch (event.type) {
case 'Buyin':
return <EventBuyin key={ event.key } event={ event } />;
case 'NewTranch':
return <EventNewTranch key={ event.key } event={ event } />;
case 'Refund':
return <EventRefund key={ event.key } event={ event } />;
case 'Transfer':
return <EventTransfer key={ event.key } event={ event } />;
}
});
}
getChildContext () {
const { accounts } = this.props;
return {
accounts
};
}
setupFilters () {
const { contract } = this.context;
const sortEvents = (a, b) => b.blockNumber.cmp(a.blockNumber) || b.logIndex.cmp(a.logIndex);
const logToEvent = (log) => {
const key = api.util.sha3(JSON.stringify(log));
const { blockNumber, logIndex, transactionHash, transactionIndex, params, type } = log;
return {
type: log.event,
state: type,
blockNumber,
logIndex,
transactionHash,
transactionIndex,
params: Object.keys(params).reduce((data, name) => {
data[name] = params[name].value;
return data;
}, {}),
key
};
};
const options = {
fromBlock: 0,
toBlock: 'pending',
limit: 50
};
contract.subscribe(null, options, (error, _logs) => {
if (error) {
console.error('setupFilters', error);
return;
}
if (!_logs.length) {
return;
}
const logs = _logs.map(logToEvent);
const minedEvents = logs
.filter((log) => log.state === 'mined')
.reverse()
.concat(this.state.minedEvents)
.sort(sortEvents);
const pendingEvents = logs
.filter((log) => log.state === 'pending')
.reverse()
.concat(this.state.pendingEvents.filter((event) => {
return !logs.find((log) => {
const isMined = (log.state === 'mined') && (log.transactionHash === event.transactionHash);
const isPending = (log.state === 'pending') && (log.key === event.key);
return isMined || isPending;
});
}))
.sort(sortEvents);
const allEvents = pendingEvents.concat(minedEvents);
this.setState({
allEvents,
minedEvents,
pendingEvents
});
});
}
}

View File

@@ -1,36 +0,0 @@
// 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 <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import { api } from '../parity';
import styles from './identityIcon.css';
export default class IdentityIcon extends Component {
static propTypes = {
address: PropTypes.string.isRequired
}
render () {
const { address } = this.props;
return (
<img
className={ styles.icon }
src={ api.util.createIdentityImg(address, 4) } />
);
}
}

View File

@@ -1,17 +0,0 @@
// 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 <http://www.gnu.org/licenses/>.
export default from './identityIcon';

View File

@@ -1,17 +0,0 @@
// 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 <http://www.gnu.org/licenses/>.
export default from './loading';

View File

@@ -1,31 +0,0 @@
// 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 <http://www.gnu.org/licenses/>.
import React, { Component } from 'react';
import { CircularProgress } from 'material-ui';
import styles from './loading.css';
export default class Loading extends Component {
render () {
return (
<div className={ styles.loading }>
<CircularProgress size={ 120 } thickness={ 7 } />
</div>
);
}
}

View File

@@ -1,53 +0,0 @@
/* 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 <http://www.gnu.org/licenses/>.
*/
.status {
background: rgba(25, 75, 125, 1);
color: rgba(255, 255, 255, 1);
padding: 4em 0 2em 0;
display: flex;
flex-wrap: wrap;
}
.title {
margin-top: 0;
font-weight: 300;
font-size: 2.5rem;
text-transform: uppercase;
}
.item {
flex: 0 1 30%;
width: 30%;
margin: 0 1.5%;
text-align: center;
}
.byline {
font-size: 1.25em;
color: rgba(255, 255, 255, 0.7);
}
.heading {
text-transform: uppercase;
letter-spacing: 0.25em;
font-size: 1.5em;
color: rgba(255, 255, 255, 0.7);
}
.hero {
font-size: 4em;
}

View File

@@ -1,74 +0,0 @@
// 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 <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import { formatBlockNumber, formatCoins, formatEth } from '../format';
import styles from './status.css';
export default class Status extends Component {
static propTypes = {
address: PropTypes.string,
gavBalance: PropTypes.object,
blockNumber: PropTypes.object,
totalSupply: PropTypes.object,
remaining: PropTypes.object,
price: PropTypes.object,
children: PropTypes.node
}
render () {
const { blockNumber, gavBalance, totalSupply, remaining, price } = this.props;
if (!totalSupply) {
return null;
}
return (
<div className={ styles.status }>
<div className={ styles.item }>
<div className={ styles.heading }>&nbsp;</div>
<div className={ styles.hero }>
{ formatCoins(remaining, -1) }
</div>
<div className={ styles.byline }>
available for { formatEth(price) }ETH
</div>
</div>
<div className={ styles.item }>
<div className={ styles.heading }>GAVcoin</div>
<div className={ styles.hero }>
{ formatCoins(totalSupply, -1) }
</div>
<div className={ styles.byline }>
total at { formatBlockNumber(blockNumber) }
</div>
</div>
<div className={ styles.item }>
<div className={ styles.heading }>&nbsp;</div>
<div className={ styles.hero }>
{ formatCoins(gavBalance, -1) }
</div>
<div className={ styles.byline }>
coin balance
</div>
</div>
{ this.props.children }
</div>
);
}
}

View File

@@ -1,52 +0,0 @@
// 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 <http://www.gnu.org/licenses/>.
import BigNumber from 'bignumber.js';
import { api } from '../parity';
const DIVISOR = 10 ** 6;
const ZERO = new BigNumber(0);
export function formatBlockNumber (blockNumber) {
return ZERO.eq(blockNumber || 0)
? 'Pending'
: `#${blockNumber.toFormat()}`;
}
export function formatCoins (amount, decimals = 6) {
const adjusted = amount.div(DIVISOR);
if (decimals === -1) {
if (adjusted.gte(10000)) {
decimals = 0;
} else if (adjusted.gte(1000)) {
decimals = 1;
} else if (adjusted.gte(100)) {
decimals = 2;
} else if (adjusted.gte(10)) {
decimals = 3;
} else {
decimals = 4;
}
}
return adjusted.toFormat(decimals);
}
export function formatEth (eth, decimals = 3) {
return api.util.fromWei(eth).toFormat(decimals);
}

View File

@@ -4,13 +4,13 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<link rel="icon" href="/parity-logo-black-no-text.png" type="image/png">
<title>GitHub Hint</title>
</head>
<body>
<div id="container"></div>
<script src="vendor.js"></script>
<script src="commons.js"></script>
<script src="/parity-utils/parity.js"></script>
<script src="githubhint.js"></script>
</body>
</html>

View File

@@ -82,6 +82,10 @@
.capture {
}
.capture+.capture {
margin-top: 0.5em;
}
.capture * {
display: inline-block;
padding: 0.75em;
@@ -108,14 +112,36 @@
background: #fcc;
}
.hashError {
.hashError, .hashWarning, .hashOk {
padding-top: 0.5em;
color: #f66;
text-align: center;
}
.hashOk {
padding-top: 0.5em;
opacity: 0.5;
text-align: center;
.hashError {
color: #f66;
}
.hashWarning {
color: #f80;
}
.hashOk {
opacity: 0.5;
}
.typeButtons {
text-align: center;
padding: 0 0 1em 0;
}
.typeButtons>div {
border-radius: 0 !important;
&:first-child {
border-radius: 5px 0 0 5px !important;
}
&:last-child {
border-radius: 0 5px 5px 0 !important;
}
}

View File

@@ -29,14 +29,21 @@ const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
export default class Application extends Component {
state = {
fromAddress: null,
loading: true,
url: '',
urlError: null,
commit: '',
commitError: null,
contentHash: '',
contentHashError: null,
contentHashOwner: null,
registerBusy: false,
registerError: null,
registerState: ''
registerState: '',
registerType: 'file',
repo: '',
repoError: null
}
componentDidMount () {
@@ -63,25 +70,70 @@ export default class Application extends Component {
}
renderPage () {
const { registerBusy, url, urlError, contentHash, contentHashError } = this.state;
const { fromAddress, registerBusy, url, urlError, contentHash, contentHashError, contentHashOwner, commit, commitError, registerType, repo, repoError } = this.state;
let hashClass = null;
if (contentHashError) {
hashClass = contentHashOwner !== fromAddress ? styles.hashError : styles.hashWarning;
} else {
hashClass = styles.hashOk;
}
let valueInputs = null;
if (registerType === 'content') {
valueInputs = [
<div className={ styles.capture } key='repo'>
<input
type='text'
placeholder='owner/repo'
disabled={ registerBusy }
value={ repo }
className={ repoError ? styles.error : null }
onChange={ this.onChangeRepo } />
</div>,
<div className={ styles.capture } key='hash'>
<input
type='text'
placeholder='commit hash sha3'
disabled={ registerBusy }
value={ commit }
className={ commitError ? styles.error : null }
onChange={ this.onChangeCommit } />
</div>
];
} else {
valueInputs = (
<div className={ styles.capture } key='url'>
<input
type='text'
placeholder='http://domain/filename'
disabled={ registerBusy }
value={ url }
className={ urlError ? styles.error : null }
onChange={ this.onChangeUrl } />
</div>
);
}
return (
<div className={ styles.container }>
<div className={ styles.form }>
<div className={ styles.typeButtons }>
<Button
disabled={ registerBusy }
invert={ registerType !== 'file' }
onClick={ this.onClickTypeNormal }>File Link</Button>
<Button
disabled={ registerBusy }
invert={ registerType !== 'content' }
onClick={ this.onClickTypeContent }>Content Bundle</Button>
</div>
<div className={ styles.box }>
<div className={ styles.description }>
Provide a valid URL to register. The content information can be used in other contracts that allows for reverse lookups, e.g. image registries, dapp registries, etc.
</div>
<div className={ styles.capture }>
<input
type='text'
placeholder='http://domain/filename'
disabled={ registerBusy }
value={ url }
className={ urlError ? styles.error : null }
onChange={ this.onChangeUrl } />
</div>
<div className={ contentHashError ? styles.hashError : styles.hashOk }>
{ valueInputs }
<div className={ hashClass }>
{ contentHashError || contentHash }
</div>
{ registerBusy ? this.renderProgress() : this.renderButtons() }
@@ -92,7 +144,7 @@ export default class Application extends Component {
}
renderButtons () {
const { accounts, fromAddress, url, urlError, contentHashError } = this.state;
const { accounts, fromAddress, urlError, repoError, commitError, contentHashError, contentHashOwner } = this.state;
const account = accounts[fromAddress];
return (
@@ -105,7 +157,7 @@ export default class Application extends Component {
</div>
<Button
onClick={ this.onClickRegister }
disabled={ !!contentHashError || !!urlError || url.length === 0 }>register url</Button>
disabled={ (contentHashError && contentHashOwner !== fromAddress) || urlError || repoError || commitError }>register url</Button>
</div>
);
}
@@ -138,57 +190,107 @@ export default class Application extends Component {
);
}
onClickContentHash = () => {
this.setState({ fileHash: false, commit: '' });
onClickTypeNormal = () => {
const { url } = this.state;
this.setState({ registerType: 'file', commitError: null, repoError: null }, () => {
this.onChangeUrl({ target: { value: url } });
});
}
onClickFileHash = () => {
this.setState({ fileHash: true, commit: 0 });
onClickTypeContent = () => {
const { repo, commit } = this.state;
this.setState({ registerType: 'content', urlError: null }, () => {
this.onChangeRepo({ target: { value: repo } });
this.onChangeCommit({ target: { value: commit } });
});
}
onChangeCommit = (event) => {
let commit = event.target.value;
const commitError = null;
let hasContent = false;
this.setState({ commit, commitError, contentHashError: null }, () => {
const { repo } = this.state || '';
const parts = repo.split('/');
hasContent = commit.length !== 0 && parts.length === 2 && parts[0].length !== 0 && parts[1].length !== 0;
if (!commitError && hasContent) {
this.setState({ contentHashError: 'hash lookup in progress' });
this.lookupHash(`https://codeload.github.com/${repo}/zip/${commit}`);
}
});
}
onChangeRepo = (event) => {
let repo = event.target.value;
const repoError = null;
let hasContent = false;
// TODO: field validation
if (!repoError) {
repo = repo.replace('https://github.com/', '');
}
this.setState({ repo, repoError, contentHashError: null }, () => {
const { commit } = this.state || '';
const parts = repo.split('/');
hasContent = commit.length !== 0 && parts.length === 2 && parts[0].length !== 0 && parts[1].length !== 0;
if (!repoError && hasContent) {
this.setState({ contentHashError: 'hash lookup in progress' });
this.lookupHash(`https://codeload.github.com/${repo}/zip/${commit}`);
}
});
}
onChangeUrl = (event) => {
const url = event.target.value;
let urlError = null;
let url = event.target.value;
const urlError = null;
let hasContent = false;
if (url && url.length) {
var re = /^https?:\/\/(?:www\.|(?!www))[^\s\.]+\.[^\s]{2,}/g;
urlError = re.test(url)
? null
: 'not matching rexex';
// TODO: field validation
if (!urlError) {
const parts = url.split('/');
hasContent = parts.length !== 0;
if (parts[2] === 'github.com' || parts[2] === 'raw.githubusercontent.com') {
url = `https://raw.githubusercontent.com/${parts.slice(3).join('/')}`.replace('/blob/', '/');
}
}
this.setState({ url, urlError, contentHashError: 'hash lookup in progress' }, () => {
this.lookupHash();
this.setState({ url, urlError, contentHashError: null }, () => {
if (!urlError && hasContent) {
this.setState({ contentHashError: 'hash lookup in progress' });
this.lookupHash(url);
}
});
}
onClickRegister = () => {
const { url, urlError, contentHash, contentHashError, fromAddress, instance } = this.state;
const { commit, commitError, contentHashError, contentHashOwner, fromAddress, url, urlError, registerType, repo, repoError } = this.state;
if (!!contentHashError || !!urlError || url.length === 0) {
// TODO: No errors are currently set, validation to be expanded and added for each
// field (query is fast to pick up the issues, so not burning atm)
if ((contentHashError && contentHashOwner !== fromAddress) || repoError || urlError || commitError) {
return;
}
this.setState({ registerBusy: true, registerState: 'Estimating gas for the transaction' });
if (registerType === 'file') {
this.registerUrl(url);
} else {
this.registerContent(repo, commit);
}
}
const values = [contentHash, url];
const options = { from: fromAddress };
instance
.hintURL.estimateGas(options, values)
.then((gas) => {
this.setState({ registerState: 'Gas estimated, Posting transaction to the network' });
const gasPassed = gas.mul(1.2);
options.gas = gasPassed.toFixed(0);
console.log(`gas estimated at ${gas.toFormat(0)}, passing ${gasPassed.toFormat(0)}`);
return instance.hintURL.postTransaction(options, values);
})
trackRequest (promise) {
return promise
.then((signerRequestId) => {
this.setState({ signerRequestId, registerState: 'Transaction posted, Waiting for transaction authorization' });
return api.pollMethod('eth_checkRequest', signerRequestId);
return api.pollMethod('parity_checkRequest', signerRequestId);
})
.then((txHash) => {
this.setState({ txHash, registerState: 'Transaction authorized, Waiting for network confirmations' });
@@ -202,7 +304,7 @@ export default class Application extends Component {
});
})
.then((txReceipt) => {
this.setState({ txReceipt, registerBusy: false, registerState: 'Network confirmed, Received transaction receipt', url: '', contentHash: '' });
this.setState({ txReceipt, registerBusy: false, registerState: 'Network confirmed, Received transaction receipt', url: '', commit: '', repo: '', commitError: null, contentHash: '', contentHashOwner: null, contentHashError: null });
})
.catch((error) => {
console.error('onSend', error);
@@ -210,6 +312,52 @@ export default class Application extends Component {
});
}
registerContent (repo, commit) {
const { contentHash, fromAddress, instance } = this.state;
this.setState({ registerBusy: true, registerState: 'Estimating gas for the transaction' });
const values = [contentHash, repo, commit.substr(0, 2) === '0x' ? commit : `0x${commit}`];
const options = { from: fromAddress };
this.trackRequest(
instance
.hint.estimateGas(options, values)
.then((gas) => {
this.setState({ registerState: 'Gas estimated, Posting transaction to the network' });
const gasPassed = gas.mul(1.2);
options.gas = gasPassed.toFixed(0);
console.log(`gas estimated at ${gas.toFormat(0)}, passing ${gasPassed.toFormat(0)}`);
return instance.hint.postTransaction(options, values);
})
);
}
registerUrl (url) {
const { contentHash, fromAddress, instance } = this.state;
this.setState({ registerBusy: true, registerState: 'Estimating gas for the transaction' });
const values = [contentHash, url];
const options = { from: fromAddress };
this.trackRequest(
instance
.hintURL.estimateGas(options, values)
.then((gas) => {
this.setState({ registerState: 'Gas estimated, Posting transaction to the network' });
const gasPassed = gas.mul(1.2);
options.gas = gasPassed.toFixed(0);
console.log(`gas estimated at ${gas.toFormat(0)}, passing ${gasPassed.toFormat(0)}`);
return instance.hintURL.postTransaction(options, values);
})
);
}
onSelectFromAddress = () => {
const { accounts, fromAddress } = this.state;
const addresses = Object.keys(accounts);
@@ -229,10 +377,16 @@ export default class Application extends Component {
this.setState({ fromAddress: addresses[index] });
}
lookupHash () {
const { url, instance } = this.state;
lookupHash (url) {
const { instance } = this.state;
api.ethcore
if (!url || !url.length) {
return;
}
console.log(`lookupHash ${url}`);
api.parity
.hashContent(url)
.then((contentHash) => {
console.log('lookupHash', contentHash);
@@ -243,13 +397,17 @@ export default class Application extends Component {
instance.entries
.call({}, [contentHash])
.then(([accountSlashRepo, commit, owner]) => {
console.log('lookupHash', accountSlashRepo, api.util.bytesToHex(commit), owner);
.then(([accountSlashRepo, commit, contentHashOwner]) => {
console.log('lookupHash', accountSlashRepo, api.util.bytesToHex(commit), contentHashOwner);
if (owner !== ZERO_ADDRESS) {
this.setState({ contentHashError: contentHash, contentHash: null });
if (contentHashOwner !== ZERO_ADDRESS) {
this.setState({
contentHashError: contentHash,
contentHashOwner,
contentHash
});
} else {
this.setState({ contentHashError: null, contentHash });
this.setState({ contentHashError: null, contentHashOwner, contentHash });
}
});
})

View File

@@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
const { api } = window.parity;
const api = window.parent.secureApi;
export {
api

View File

@@ -18,7 +18,7 @@ import * as abis from '../../contracts/abi';
import { api } from './parity';
export function attachInterface () {
return api.ethcore
return api.parity
.registryAddress()
.then((registryAddress) => {
console.log(`the registry was found at ${registryAddress}`);
@@ -28,26 +28,26 @@ export function attachInterface () {
return Promise
.all([
registry.getAddress.call({}, [api.util.sha3('githubhint'), 'A']),
api.eth.accounts(),
null // api.personal.accountsInfo()
api.parity.accounts()
]);
})
.then(([address, addresses, accountsInfo]) => {
accountsInfo = accountsInfo || {};
.then(([address, accountsInfo]) => {
console.log(`githubhint was found at ${address}`);
const contract = api.newContract(abis.githubhint, address);
const accounts = addresses.reduce((obj, address) => {
const info = accountsInfo[address] || {};
const accounts = Object
.keys(accountsInfo)
.filter((address) => accountsInfo[address].uuid)
.reduce((obj, address) => {
const account = accountsInfo[address];
return Object.assign(obj, {
[address]: {
address,
name: info.name,
uuid: info.uuid
}
});
}, {});
return Object.assign(obj, {
[address]: {
address,
name: account.name
}
});
}, {});
const fromAddress = Object.keys(accounts)[0];
return {

View File

@@ -4,6 +4,7 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<link rel="icon" href="/parity-logo-black-no-text.png" type="image/png">
<title>Token Registry</title>
</head>
<body>

View File

@@ -36,6 +36,8 @@ export default class Accounts extends Component {
render () {
const { all, selected } = this.props;
const origin = { horizontal: 'right', vertical: 'top' };
const accountsButton = (
<IconButton className={ styles.button }>
{ selected
@@ -49,7 +51,9 @@ export default class Accounts extends Component {
value={ selected ? this.renderAccount(selected) : null }
onChange={ this.onAccountSelect }
iconButtonElement={ accountsButton }
animated={ false }
anchorOrigin={ origin }
targetOrigin={ origin }
>
{ Object.values(all).map(this.renderAccount) }
</IconMenu>

View File

@@ -37,3 +37,27 @@
font-size: 80%;
background-color: #f0f0f0;
}
.actions {
margin: 1em;
* {
font-size: 1.3rem !important;
}
> * {
padding-bottom: 0 !important;
}
}
.warning {
background: #f80;
bottom: 0;
color: #fff;
left: 0;
opacity: 1;
padding: 1.5em;
position: fixed;
right: 50%;
z-index: 100;
}

View File

@@ -13,7 +13,6 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
@@ -21,6 +20,7 @@ import lightBaseTheme from 'material-ui/styles/baseThemes/lightBaseTheme';
const muiTheme = getMuiTheme(lightBaseTheme);
import CircularProgress from 'material-ui/CircularProgress';
import { Card, CardText } from 'material-ui/Card';
import styles from './application.css';
import Accounts from '../Accounts';
import Events from '../Events';
@@ -35,6 +35,7 @@ export default class Application extends Component {
muiTheme: PropTypes.object.isRequired,
api: PropTypes.object.isRequired
};
getChildContext () {
return { muiTheme, api: window.parity.api };
}
@@ -52,18 +53,19 @@ export default class Application extends Component {
};
render () {
const { api } = window.parity;
const {
actions,
accounts, contacts,
contract, fee,
lookup,
events,
names,
records
events
} = this.props;
let warning = null;
return (
<div>
{ warning }
<div className={ styles.header }>
<h1>RΞgistry</h1>
<Accounts { ...accounts } actions={ actions.accounts } />
@@ -71,12 +73,11 @@ export default class Application extends Component {
{ contract && fee ? (
<div>
<Lookup { ...lookup } accounts={ accounts.all } contacts={ contacts } actions={ actions.lookup } />
<Names { ...names } fee={ fee } actions={ actions.names } />
<Records { ...records } actions={ actions.records } />
{ this.renderActions() }
<Events { ...events } accounts={ accounts.all } contacts={ contacts } actions={ actions.events } />
<p className={ styles.address }>
The Registry is provided by the contract at <code>{ contract.address }.</code>
</p>
<div className={ styles.warning }>
WARNING: The name registry is experimental. Please ensure that you understand the risks, benefits & consequences of registering a name before doing so. A non-refundable fee of { api.util.fromWei(fee).toFormat(3) }<small>ETH</small> is required for all registrations.
</div>
</div>
) : (
<CircularProgress size={ 60 } />
@@ -85,4 +86,34 @@ export default class Application extends Component {
);
}
renderActions () {
const {
actions,
accounts,
fee,
names,
records
} = this.props;
const hasAccount = !!accounts.selected;
if (!hasAccount) {
return (
<Card className={ styles.actions }>
<CardText>
Please select a valid account in order
to execute actions.
</CardText>
</Card>
);
}
return (
<div>
<Names { ...names } fee={ fee } actions={ actions.names } />
<Records { ...records } actions={ actions.records } />
</div>
);
}
}

View File

@@ -93,6 +93,37 @@ export default class Events extends Component {
render () {
const { subscriptions, pending, accounts, contacts } = this.props;
const eventsObject = this.props.events
.filter((e) => eventTypes[e.type])
.reduce((eventsObject, event) => {
const txHash = event.transaction;
if (
(eventsObject[txHash] && eventsObject[txHash].state === 'pending') ||
!eventsObject[txHash]
) {
eventsObject[txHash] = event;
}
return eventsObject;
}, {});
const events = Object
.values(eventsObject)
.sort((evA, evB) => {
if (evA.state === 'pending') {
return -1;
}
if (evB.state === 'pending') {
return 1;
}
return evB.timestamp - evA.timestamp;
})
.map((e) => eventTypes[e.type](e, accounts, contacts));
return (
<Card className={ styles.events }>
<CardHeader title='Event Log' />
@@ -122,11 +153,7 @@ export default class Events extends Component {
<CardText>
<table className={ styles.eventsList }>
<tbody>
{
this.props.events
.filter((e) => eventTypes[e.type])
.map((e) => eventTypes[e.type](e, accounts, contacts))
}
{ events }
</tbody>
</table>
</CardText>

View File

@@ -29,12 +29,19 @@ import styles from './names.css';
const useSignerText = (<p>Use the <a href='/#/signer' className={ styles.link } target='_blank'>Signer</a> to authenticate the following changes.</p>);
const renderNames = (names) => {
const out = [];
for (let name of names) {
out.push((<code>{ name }</code>), ', ');
}
out.pop();
return out;
const values = Object.values(names);
return values
.map((name, index) => (
<span key={ index }>
<code>{ name }</code>
{
index < values.length - 1
? (<span>, </span>)
: null
}
</span>
));
};
const renderQueue = (queue) => {
@@ -70,7 +77,6 @@ export default class Names extends Component {
static propTypes = {
actions: PropTypes.object.isRequired,
fee: PropTypes.object.isRequired,
hasAccount: PropTypes.bool.isRequired,
pending: PropTypes.bool.isRequired,
queue: PropTypes.array.isRequired
}
@@ -82,20 +88,18 @@ export default class Names extends Component {
render () {
const { action, name } = this.state;
const { fee, hasAccount, pending, queue } = this.props;
const { fee, pending, queue } = this.props;
return (
<Card className={ styles.names }>
<CardHeader title={ 'Manage Names' } />
<CardText>
{ !hasAccount
? (<p className={ styles.noSpacing }>Please select an account first.</p>)
: (action === 'reserve'
? (<p className={ styles.noSpacing }>
The fee to reserve a name is <code>{ fromWei(fee).toFixed(3) }</code>ETH.
</p>)
: (<p className={ styles.noSpacing }>To drop a name, you have to be the owner.</p>)
)
{ (action === 'reserve'
? (<p className={ styles.noSpacing }>
The fee to reserve a name is <code>{ fromWei(fee).toFixed(3) }</code>ETH.
</p>)
: (<p className={ styles.noSpacing }>To drop a name, you have to be the owner.</p>)
)
}
<TextField
hintText='name'
@@ -103,7 +107,7 @@ export default class Names extends Component {
onChange={ this.onNameChange }
/>
<DropDownMenu
disabled={ !hasAccount || pending }
disabled={ pending }
value={ action }
onChange={ this.onActionChange }
>
@@ -111,7 +115,7 @@ export default class Names extends Component {
<MenuItem value='drop' primaryText='drop this name' />
</DropDownMenu>
<RaisedButton
disabled={ !hasAccount || pending }
disabled={ pending }
className={ styles.spacing }
label={ action === 'reserve' ? 'Reserve' : 'Drop' }
primary

View File

@@ -15,16 +15,11 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
const initialState = {
hasAccount: false,
pending: false,
queue: []
};
export default (state = initialState, action) => {
if (action.type === 'accounts select') {
return { ...state, hasAccount: !!action.address };
}
if (action.type === 'names reserve start') {
return { ...state, pending: true };
}

View File

@@ -11,7 +11,6 @@ export default class Records extends Component {
static propTypes = {
actions: PropTypes.object.isRequired,
hasAccount: PropTypes.bool.isRequired,
pending: PropTypes.bool.isRequired,
name: PropTypes.string.isRequired,
type: PropTypes.string.isRequired,
@@ -21,7 +20,7 @@ export default class Records extends Component {
state = { name: '', type: 'A', value: '' };
render () {
const { hasAccount, pending } = this.props;
const { pending } = this.props;
const name = this.state.name || this.props.name;
const type = this.state.type || this.props.type;
const value = this.state.value || this.props.value;
@@ -30,10 +29,10 @@ export default class Records extends Component {
<Card className={ styles.records }>
<CardHeader title={ 'Manage Entries of a Name' } />
<CardText>
{ !hasAccount
? (<p className={ styles.noSpacing }>Please select an account first.</p>)
: (<p className={ styles.noSpacing }>You can only modify entries of names that you previously registered.</p>)
}
<p className={ styles.noSpacing }>
You can only modify entries of names that you previously registered.
</p>
<TextField
className={ styles.spacing }
hintText='name'
@@ -48,7 +47,7 @@ export default class Records extends Component {
onChange={ this.onValueChange }
/>
<RaisedButton
disabled={ !hasAccount || pending }
disabled={ pending }
className={ styles.spacing }
label='Save'
primary

View File

@@ -1,14 +1,9 @@
const initialState = {
hasAccount: false,
pending: false,
name: '', type: '', value: ''
};
export default (state = initialState, action) => {
if (action.type === 'accounts select') {
return { ...state, hasAccount: !!action.address };
}
if (action.type === 'records update start') {
return {
...state,
@@ -17,7 +12,7 @@ export default (state = initialState, action) => {
};
}
if (action.type === 'records update error' && action.type === 'records update success') {
if (action.type === 'records update error' || action.type === 'records update success') {
return {
...state,
pending: false,

View File

@@ -29,7 +29,7 @@ export { addresses, accounts, lookup, events, names, records };
export const setContract = (contract) => ({ type: 'set contract', contract });
export const fetchContract = () => (dispatch) =>
api.ethcore.registryAddress()
api.parity.registryAddress()
.then((address) => {
const contract = api.newContract(registryAbi, address);
dispatch(setContract(contract));

View File

@@ -19,15 +19,17 @@ import { api } from '../parity';
export const set = (addresses) => ({ type: 'addresses set', addresses });
export const fetch = () => (dispatch) => {
return Promise
.all([
api.eth.accounts(),
null // api.personal.accountsInfo()
])
.then(([ accounts, data ]) => {
const addresses = accounts.map((address) => {
return { address, isAccount: true };
});
return api.parity
.accounts()
.then((accountsInfo) => {
const addresses = Object
.keys(accountsInfo)
.filter((address) => accountsInfo[address] && !accountsInfo[address].meta.deleted)
.map((address) => ({
...accountsInfo[address],
address,
isAccount: !!accountsInfo[address].uuid
}));
dispatch(set(addresses));
})
.catch((error) => {

Some files were not shown because too many files have changed in this diff Show More