Commit 95452ee3 authored by Luke Schoen's avatar Luke Schoen Committed by Thibaut Sardan
Browse files

feat: Relates to #360. Only allow import from Parity Signer chain account...

feat: Relates to #360. Only allow import from Parity Signer chain account matching current chain. ETC support (#483)

* feat: Relates to #360. Only allow import from Parity Signer chain account matching current chain. ETC support

* review-fix: Refer to non-Parity chain names in the UI. Add console.error

* review-fix: Do not need to chcek health status before calling chainId RPC of light.js on pages accessed through navigation

* review-fix: Rename function name that matches current chain id with imported chain id of address

* review-fix: Remove unnecessary function

* review-fix: Rename function to accountAlreadyExists

* review-fix: Remove FIXME. See https://github.com/paritytech/fether/pull/483#discussion_r270834847

* review-fix: Refactor to use util functions isEtcChainId, chainIdToString, isNotErc20TokenAddress

* fix: Fix typo in comment

* review-fix: Change wording of parity phrase comment

* review-fix: Do not clear isImport as not account related

* fix: Clear error so error when recover from seed phrase not still shown if then click to recover from QR code

* fix: Rename so signerChainId correctly destructured and not undefined

* review-fix: Remove async/await from clear

* fix: Avoid mapping signer chain id to chain name since too much maintenance with Parity Ethereum

* review-fix: Remove await from createAccountStore

* tests: Add colour to fether-react tests

* refactor: No need to parseInt on the signerChainId

* refactor: Use isNotErc20TokenAddress

* refactor: Use isNotErc20TokenAddress again

* refactor: Add isErc20TokenAddress util so more readable

* fix: Replace valueOf with .eq. Fix so obtain BN from props

* refactor: Combine into single if statement when checking if valid Eth/Etc address

* refactor: Update utils without unnecessary return block
parent 9ab36c49
Pipeline #35598 passed with stages
in 10 minutes and 57 seconds
......@@ -33,7 +33,7 @@
"start": "npm-run-all -p start-*",
"start-css": "npm run build-css -- --watch --recursive",
"start-js": "cross-env SKIP_PREFLIGHT_CHECK=true BROWSER=none craco start",
"test": "cross-env SKIP_PREFLIGHT_CHECK=true craco test"
"test": "cross-env SKIP_PREFLIGHT_CHECK=true craco test --color"
},
"dependencies": {
"@craco/craco": "^4.0.0",
......
......@@ -4,16 +4,32 @@
// SPDX-License-Identifier: BSD-3-Clause
import React, { Component } from 'react';
import { addressShort, Card, Form as FetherForm } from 'fether-ui';
import { chainId$, chainName$ } from '@parity/light.js';
import light from '@parity/light.js-react';
import { inject, observer } from 'mobx-react';
import { addressShort, Card, Form as FetherForm } from 'fether-ui';
import RequireHealthOverlay from '../../../RequireHealthOverlay';
import Scanner from '../../../Scanner';
import withAccountsInfo from '../../../utils/withAccountsInfo';
import withHealth from '../../../utils/withHealth';
import i18n, { packageNS } from '../../../i18n';
@withAccountsInfo
@withHealth
@inject('createAccountStore')
@light({
chainId: () => chainId$(),
/**
* It is not necessary to check the health status here before
* calling chainId RPC using light.js like we do in Health.js since
* the AccountImportOptions.js page may only be accessed through
* navigation inside the API, after the API is set.
*
* Reference: https://github.com/paritytech/fether/pull/483#discussion_r271303462
*/
chainName: () => chainName$()
})
@observer
class AccountImportOptions extends Component {
state = {
......@@ -48,7 +64,7 @@ class AccountImportOptions extends Component {
try {
await setPhrase(phrase);
if (this.hasExistingAddressForImport(createAccountStore.address)) {
if (this.accountAlreadyExists(createAccountStore.address)) {
return;
}
......@@ -70,12 +86,15 @@ class AccountImportOptions extends Component {
createAccountStore: { setJsonString }
} = this.props;
this.setState({ isLoading: true });
this.setState({
error: '',
isLoading: true
});
try {
await setJsonString(jsonString);
if (this.hasExistingAddressForImport(createAccountStore.address)) {
if (this.accountAlreadyExists(createAccountStore.address)) {
return;
}
......@@ -89,12 +108,21 @@ class AccountImportOptions extends Component {
}
};
handleSignerImported = async ({ address, chainId: chainIdString }) => {
/**
* The `chainId$` and `chainName$` from light.js corresponds to `chainID` in the
* Genesis configs contained in: paritytech/parity-ethereum/ethcore/res/ethereum
* and was introduced in EIP-155 https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md
* to prevent replay attacks between `foundation` and `classic` chains, which both have
* `networkID` of `1`.
*/
handleSignerImported = async ({ address, chainId: signerChainId }) => {
const {
chainId: currentChainIdBN,
chainName,
createAccountStore: { importFromSigner }
} = this.props;
if (!address || !chainIdString) {
if (!address || !signerChainId) {
this.setState({
error: i18n.t(
`${packageNS}:account.import.signer.error_msg_signer_imported`
......@@ -103,31 +131,45 @@ class AccountImportOptions extends Component {
return;
}
const chainId = parseInt(chainIdString);
if (!currentChainIdBN.eq(signerChainId)) {
console.error(
`Parity Signer account chainId ${signerChainId} must match current chainId ${currentChainIdBN.valueOf()} (${chainName}).`
);
this.setState({
error: i18n.t(
`${packageNS}:account.import.signer.error_msg_signer_imported_network_mismatch`,
{ chain_name: chainName }
)
});
return;
}
if (this.hasExistingAddressForImport(address, chainId)) {
if (this.accountAlreadyExists(address, signerChainId)) {
return;
}
await importFromSigner({ address, chainId });
await importFromSigner({ address, signerChainId });
this.handleNextStep();
};
handleSignerImport = () => {
this.setState({
error: '',
importingFromSigner: true
});
};
hasExistingAddressForImport = (addressForImport, chainId) => {
accountAlreadyExists = (addressForImport, signerChainId) => {
const { accountsInfo } = this.props;
const isExistingAddress = Object.keys(accountsInfo).some(
key =>
key.toLowerCase() === addressForImport.toLowerCase() &&
(!accountsInfo[key].chainId ||
!chainId ||
accountsInfo[key].chainId === chainId)
!signerChainId ||
accountsInfo[key].chainId === signerChainId)
);
if (isExistingAddress) {
......
......@@ -17,6 +17,7 @@ import loading from '../../../assets/img/icons/loading.svg';
class AccountName extends Component {
componentDidMount () {
const { createAccountStore } = this.props;
// Generate a new public address if there's none yet
if (!createAccountStore.address) {
createAccountStore.generateNewAccount();
......
......@@ -17,7 +17,13 @@ import i18n, { packageNS } from '../i18n';
status: { good, syncing }
}
}) => good || syncing,
// Only call light.js chainName$ if we're syncing or good
/**
* Only call light.js chainName$ if we're syncing or good
* to avoid making an RPC call before the API is set
* (since Health.js is always rendered).
*
* Reference: https://github.com/paritytech/fether/pull/483#discussion_r271303462
*/
light({
chainName: () => chainName$()
})
......
......@@ -5,7 +5,7 @@
**Disclaimer of Liability and Warranties**
- The user expressly acknowledges and agrees that Parity Technologies Limited makes the Fether client available to the user at the user's sole risk.
- The user represents that the user has an adequate understanding of the risks, usage and intricacies of cryptographic tokens and blockchain-based open source software, the Ethereum platform and ETH.
- The user represents that the user has an adequate understanding of the risks, usage and intricacies of cryptographic tokens and blockchain-based open source software, the Ethereum platform, and both ETH and ETC.
- The user acknowledges and agrees that, to the fullest extent permitted by any applicable law, the disclaimers of liability contained herein apply to any and all damages or injury whatsoever caused by or related to risks of, use of, or inability to use, the Fether client under any cause or action whatsoever of any kind in any jurisdiction, including, without limitation, actions for breach of warranty, breach of contract or tort (including negligence) and that Parity Technologies Limited shall not be liable for any indirect, incidental, special, exemplary or consequential damages, including for loss of profits, goodwill or data.
- Some jurisdictions do not allow the exclusion of certain warranties or the limitation or exclusion of liability for certain types of damages. Therefore, some of the above limitations in this section may not apply to a user. In particular, nothing in these terms shall affect the statutory rights of any user or limit or exclude liability for death or physical injury arising from the negligence or wilful misconduct of Parity Technologies Limited or for fraud or fraudulent misrepresentation.
- All rights reserved by Parity Technologies Limited. Licensed to the public under the BSD3.0 License (also known as "BSD-3-Clause"): [https://opensource.org/licenses/BSD-3-Clause](https://opensource.org/licenses/BSD-3-Clause)
......@@ -20,23 +20,23 @@ The User acknowledges the following serious risks to any use of Fether and expre
**Risk of Security Weaknesses in the Parity Core Infrastructure Software**
Fether uses open-source libraries and components developed by third parties. While Parity Technologies Limited generally aims to use only widely adopted open-source technology and develop it in line with industry standards, such open-source technology may contain bugs and errors and may not function correctly in all circumstances. As a result, there is a risk that Parity Technologies or the Parity Technologies Team may have introduced unintentional weaknesses or bugs into the core infrastructural elements of Fether causing the system to lose Ethereum tokens ("ETH") stored in one or more User accounts or other accounts or lose sums of other valued tokens.
Fether uses open-source libraries and components developed by third parties. While Parity Technologies Limited generally aims to use only widely adopted open-source technology and develop it in line with industry standards, such open-source technology may contain bugs and errors and may not function correctly in all circumstances. As a result, there is a risk that Parity Technologies or the Parity Technologies Team may have introduced unintentional weaknesses or bugs into the core infrastructural elements of Fether causing the system to lose Ethereum tokens ("ETH") and Ethereum Classic tokens ("ETC") stored in one or more User accounts or other accounts or lose sums of other valued tokens.
**Risk of Weaknesses or Exploitable Breakthroughs in the Field of Cryptography**
Cryptography is an art, not a science, and the state of the art can advance over time. Advances in code cracking, or technical advances such as the development of quantum computers, could present risks to cryptocurrencies and Fether, which could result in the theft or loss of ETH. To the extent possible, Parity Technologies intends to update the protocol underlying Fether to account for any advances in cryptography and to incorporate additional security measures, but it cannot predict the future of cryptography or guarantee that any security updates will be made, timely or successful.
Cryptography is an art, not a science, and the state of the art can advance over time. Advances in code cracking, or technical advances such as the development of quantum computers, could present risks to cryptocurrencies and Fether, which could result in the theft or loss of ETH and ETC. To the extent possible, Parity Technologies intends to update the protocol underlying Fether to account for any advances in cryptography and to incorporate additional security measures, but it cannot predict the future of cryptography or guarantee that any security updates will be made, timely or successful.
**Risk of Ether Mining Attacks**
As with other cryptocurrencies, the blockchain accessed by Fether is susceptible to mining attacks, including but not limited to double-spend attacks, majority mining power attacks, "selfish-mining" attacks, and race condition attacks. Any successful attacks present a risk to the Ethereum ecosystem, expected proper execution and sequencing of ETH transactions, and expected proper execution and sequencing of contract computations. Despite the efforts of Parity Technologies and the Parity Technologies Team, known or novel mining attacks may be successful.
As with other cryptocurrencies, the blockchain accessed by Fether is susceptible to mining attacks, including but not limited to double-spend attacks, majority mining power attacks, "selfish-mining" attacks, and race condition attacks. Any successful attacks present a risk to the Ethereum ecosystem, expected proper execution and sequencing of ETH and ETC transactions, and expected proper execution and sequencing of contract computations. Despite the efforts of Parity Technologies and the Parity Technologies Team, known or novel mining attacks may be successful.
**Risk of Rapid Adoption and Insufficiency of Computational Application Processing Power on the Ethereum Network**
If Ethereum is rapidly adopted, the demand for transaction processing and distributed application computations could rise dramatically and at a pace that exceeds the rate with which ETH miners can bring online additional mining power. Under such a scenario, the entire Ethereum ecosystem could become destabilized, due to the increased cost of running distributed applications. In turn, this could dampen interest in the Ethereum ecosystem and ETH. Insufficiency of computational resources and an associated rise in the price of ETH could result in businesses being unable to acquire scarce computational resources to run their distributed applications. This would represent revenue losses to businesses or worst case, cause businesses to cease operations because such operations have become uneconomical due to distortions in the crypto-economy.
If Ethereum is rapidly adopted, the demand for transaction processing and distributed application computations could rise dramatically and at a pace that exceeds the rate with which ETH and ETC miners can bring online additional mining power. Under such a scenario, the entire Ethereum ecosystem could become destabilized, due to the increased cost of running distributed applications. In turn, this could dampen interest in the Ethereum ecosystem and both ETH and ETC. Insufficiency of computational resources and an associated rise in the price of ETH or ETC could result in businesses being unable to acquire scarce computational resources to run their distributed applications. This would represent revenue losses to businesses or worst case, cause businesses to cease operations because such operations have become uneconomical due to distortions in the crypto-economy.
**Risk of temporary network incoherence**
We recommend any groups handling large or important transactions to maintain a voluntary 24 hour waiting period on any ETH deposited. If we become aware that the integrity of the network is at risk due to issues with Fether, we will endeavour to publish patches in a timely fashion to address the issues. We will endeavour to provide solutions within the voluntary 24 hour waiting period.
We recommend any groups handling large or important transactions to maintain a voluntary 24 hour waiting period on any ETH or ETC deposited. If we become aware that the integrity of the network is at risk due to issues with Fether, we will endeavour to publish patches in a timely fashion to address the issues. We will endeavour to provide solutions within the voluntary 24 hour waiting period.
**Use of Fether by you**
......
......@@ -23,8 +23,8 @@ import withTokens from '../../utils/withTokens';
token: tokens[tokenAddress]
}))
@withAccount
@withBalance // Balance of current token (can be ETH)
@withEthBalance // ETH balance
@withBalance // Balance of current token (can be ETH or ETC)
@withEthBalance // ETH or ETC balance
@observer
class SignedTxSummary extends Component {
handleSubmit = values => {
......
......@@ -8,6 +8,7 @@ import BigNumber from 'bignumber.js';
import { fromWei, toWei } from '@parity/api/lib/util/wei';
import i18n, { packageNS } from '../../../i18n';
import { chainIdToString, isNotErc20TokenAddress } from '../../../utils/chain';
class TxDetails extends Component {
renderDetails = () => {
......@@ -54,7 +55,8 @@ ${this.renderTotalAmount()}`;
};
renderFee = () => {
const { estimatedTxFee } = this.props;
const { estimatedTxFee, values } = this.props;
const currentChainIdBN = values.chainId;
if (!estimatedTxFee) {
return;
......@@ -64,11 +66,15 @@ ${this.renderTotalAmount()}`;
.toFixed(9)
.toString()}`;
return i18n.t(`${packageNS}:tx.form.details.fee`, { fee });
return i18n.t(`${packageNS}:tx.form.details.fee`, {
chain_id: chainIdToString(currentChainIdBN),
fee
});
};
renderTotalAmount = () => {
const { estimatedTxFee, token, values } = this.props;
const currentChainIdBN = values.chainId;
if (!estimatedTxFee || !values.amount || !token.address) {
return;
......@@ -76,12 +82,15 @@ ${this.renderTotalAmount()}`;
const totalAmount = `${fromWei(
estimatedTxFee.plus(
token.address === 'ETH' ? toWei(values.amount.toString()) : 0
isNotErc20TokenAddress(token.address)
? toWei(values.amount.toString())
: 0
),
'ether'
).toString()}`;
return i18n.t(`${packageNS}:tx.form.details.total_amount`, {
chain_id: chainIdToString(currentChainIdBN),
total_amount: totalAmount
});
};
......
......@@ -25,6 +25,11 @@ import { estimateGas } from '../../utils/transaction';
import RequireHealthOverlay from '../../RequireHealthOverlay';
import TokenBalance from '../../Tokens/TokensList/TokenBalance';
import TxDetails from './TxDetails';
import {
chainIdToString,
isEtcChainId,
isNotErc20TokenAddress
} from '../../utils/chain';
import withAccount from '../../utils/withAccount';
import withBalance, { withEthBalance } from '../../utils/withBalance';
import withTokens from '../../utils/withTokens';
......@@ -44,7 +49,9 @@ const debug = Debug('TxForm');
@withAccount
@light({
// We need to wait for 3 values that might take time:
// - ethBalance: to check that we have enough to send amount+fees
// - ethBalance: to check that we have enough balance to send amount+fees
// It may be ETH or ETC corresponding to whether we are connected to the
// 'foundation' or 'classic' chain.
// - chainId & transactionCount: needed to construct the tx
// For the three of them, we add the `startWith()` operator so that the UI is
// not blocked while waiting for their first response.
......@@ -52,8 +59,8 @@ const debug = Debug('TxForm');
transactionCount: ({ account: { address } }) =>
transactionCountOf$(address).pipe(startWith(undefined))
})
@withBalance // Balance of current token (can be ETH)
@withEthBalance // ETH balance
@withBalance // Balance of current token (can be ETH or ETC)
@withEthBalance // ETH or ETC balance
@observer
class TxForm extends Component {
state = {
......@@ -105,7 +112,7 @@ class TxForm extends Component {
const gasPriceBn = new BigNumber(gasPrice);
let output;
if (token.address === 'ETH') {
if (isNotErc20TokenAddress(token.address)) {
output = fromWei(
toWei(balance).minus(gasBn.multipliedBy(toWei(gasPriceBn, 'shannon')))
);
......@@ -405,7 +412,7 @@ class TxForm extends Component {
* Prevalidate form on user's input. These validations are sync.
*/
preValidate = values => {
const { balance, token } = this.props;
const { balance, chainId: currentChainIdBN, token } = this.props;
if (!values) {
return;
......@@ -427,7 +434,8 @@ class TxForm extends Component {
if (this.state.maxSelected) {
return {
amount: i18n.t(
`${packageNS}:tx.form.validation.eth_balance_too_low_for_gas`
`${packageNS}:tx.form.validation.eth_balance_too_low_for_gas`,
{ chain_id: chainIdToString(currentChainIdBN) }
)
};
}
......@@ -438,7 +446,10 @@ class TxForm extends Component {
return {
amount: i18n.t(`${packageNS}:tx.form.validation.positive_amount`)
};
} else if (token.address === 'ETH' && toWei(values.amount).lt(1)) {
} else if (
isNotErc20TokenAddress(token.address) &&
toWei(values.amount).lt(1)
) {
return {
amount: i18n.t(`${packageNS}:tx.form.validation.min_wei`)
};
......@@ -460,7 +471,9 @@ class TxForm extends Component {
};
} else if (!values.to || !isAddress(values.to)) {
return {
to: i18n.t(`${packageNS}:tx.form.validation.invalid_eth_address`)
to: isEtcChainId(currentChainIdBN)
? i18n.t(`${packageNS}:tx.form.validation.invalid_etc_address`)
: i18n.t(`${packageNS}:tx.form.validation.invalid_eth_address`)
};
} else if (values.to === '0x0000000000000000000000000000000000000000') {
return {
......@@ -485,7 +498,7 @@ class TxForm extends Component {
}
try {
const { token } = this.props;
const { chainId: currentChainIdBN, token } = this.props;
const preValidation = this.preValidate(values);
......@@ -541,18 +554,22 @@ class TxForm extends Component {
// Verify that `gas + (eth amount if sending eth) <= ethBalance`
if (
this.estimatedTxFee(values)
.plus(token.address === 'ETH' ? toWei(values.amount) : 0)
.plus(
isNotErc20TokenAddress(token.address) ? toWei(values.amount) : 0
)
.gt(toWei(values.ethBalance))
) {
return token.address !== 'ETH'
return isNotErc20TokenAddress(token.address)
? {
amount: i18n.t(
`${packageNS}:tx.form.validation.eth_balance_too_low_for_gas`
`${packageNS}:tx.form.validation.eth_balance_too_low_for_gas`,
{ chain_id: chainIdToString(currentChainIdBN) }
)
}
: {
amount: i18n.t(
`${packageNS}:tx.form.validation.eth_balance_too_low`
`${packageNS}:tx.form.validation.eth_balance_too_low`,
{ chain_id: chainIdToString(currentChainIdBN) }
)
};
}
......
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="96.717491mm"
height="156.63306mm"
viewBox="0 0 96.717491 156.63306"
version="1.1"
id="svg908"
inkscape:version="0.92.2 2405546, 2018-03-11"
sodipodi:docname="Ethereum_Classic_Logo.svg"
inkscape:export-filename="/home/linux/Desktop/ethereum-classic-logo.png"
inkscape:export-xdpi="120"
inkscape:export-ydpi="120">
<title
id="title825">Ethereum Classic Logo</title>
<defs
id="defs902" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1.2385474"
inkscape:cx="260.56734"
inkscape:cy="325.2205"
inkscape:document-units="mm"
inkscape:current-layer="layer6"
showgrid="false"
inkscape:window-width="1918"
inkscape:window-height="996"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0" />
<metadata
id="metadata905">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title>Ethereum Classic Logo</dc:title>
<cc:license
rdf:resource="http://creativecommons.org/publicdomain/zero/1.0/" />
<dc:creator>
<cc:Agent>
<dc:title>Ethereum Classic</dc:title>
</cc:Agent>
</dc:creator>
<dc:publisher>
<cc:Agent>
<dc:title>Ethereum Classic</dc:title>
</cc:Agent>
</dc:publisher>
<dc:contributor>
<cc:Agent>
<dc:title>Ethereum Classic</dc:title>
</cc:Agent>
</dc:contributor>
<dc:description>Ethereum Classic Logo</dc:description>
</cc:Work>
<cc:License
rdf:about="http://creativecommons.org/publicdomain/zero/1.0/">
<cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
</cc:License>
</rdf:RDF>
</metadata>
<g
inkscape:label="Original"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-56.641256,-70.183469)"
style="display:none"
sodipodi:insensitive="true">
<g
transform="translate(56.415936,69.875162)"
style="display:inline"
id="g1627"
inkscape:export-xdpi="664.21741"
inkscape:export-ydpi="664.21741"
inkscape:export-filename="/home/linux/Desktop/ethereum-classic-logo.png">
<g
id="g896">
<path
inkscape:export-ydpi="120"
inkscape:export-xdpi="120"
inkscape:connector-curvature="0"
id="path838"
d="m 0.22532,85.613348 c 17.045069,9.04101 34.806023,18.517432 48.557268,25.844422 L 96.942808,85.620618 C 79.505468,111.53208 64.980668,133.08532 48.782368,156.94137 32.564033,133.13531 14.643092,106.85834 0.22532,85.613348 Z m 1.863679,-7.13823 46.748789,-24.94739 46.14603,24.76633 -46.10598,24.968892 z"
style="display:inline;fill:#009f42;fill-opacity:1;stroke-width:0.35277781"
sodipodi:nodetypes="cccccccccc" />
<g
inkscape:export-ydpi="120"
inkscape:export-xdpi="120"
id="g842">
<path
inkscape:connector-curvature="0"
id="path1599"
d="m 0.22532,85.613348 c 17.045069,9.04101 34.806023,18.517432 48.557268,25.844422 L 96.942808,85.620618 C 79.505468,111.53208 64.980668,133.08532 48.782368,156.94137 32.564033,133.13531 14.643092,106.85834 0.22532,85.613348 Z m 1.863679,-7.13823 46.748789,-24.94739 46.14603,24.76633 -46.10598,24.968892 z M 48.773048,45.518618 0.23098,71.079908 48.562288,0.308307 96.924578,71.234528 Z"
style="display:inline;fill:#01c853;fill-opacity:1;stroke-width:0.35277781"
sodipodi:nodetypes="ccccccccccccccc" />
</g>
<path
inkscape:export-ydpi="120"
inkscape:export-xdpi="120"
sodipodi:nodetypes="cccccccccccc"
style="display:inline;fill:#009f42;fill-opacity:1;stroke-width:0.35277781"
d="m 48.782588,111.45777 48.16022,-25.837152 c -17.43734,25.911462 -48.16044,71.320752 -48.16044,71.320752 z m 0.0552,-57.930042 46.14603,24.76633 -46.10598,24.968892 z m -0.0647,-8.009112 -0.21076,-45.2103108 48.36229,70.9262228 z"
id="path1593"
inkscape:connector-curvature="0" />
<path
inkscape:export-ydpi="120"
inkscape:export-xdpi="120"
sodipodi:nodetypes="ccccc"
style="display:inline;fill:#007831;fill-opacity:1;stroke-width:0.35277781"
d="m 48.837788,82.301168 46.14603,-4.00711 -46.10598,24.968892 -0.0401,-20.961782 z"
id="path1606"
inkscape:connector-curvature="0" />
<path
inkscape:export-ydpi="120"
inkscape:export-xdpi="120"
inkscape:connector-curvature="0"
id="path850"
d="m 48.837688,82.301168 -46.748689,-3.82605 46.708639,24.787832 0.0401,-20.961782 z"
style="display:inline;fill:#009f42;fill-opacity:1;stroke-width:0.35277781"
sodipodi:nodetypes="ccccc" />
<path
inkscape:export-ydpi="120"
inkscape:export-xdpi="120"
inkscape:connector-curvature="0"
id="path852"
d="m 48.782588,111.45777 48.16022,-25.83715 c -17.43734,25.91146 -31.96214,47.4647 -48.16044,71.32075 z"
style="display:inline;fill:#009f42;fill-opacity:1;stroke-width:0.35277781"
sodipodi:nodetypes="cccc" />
</g>
</g>
</g>
<g
style="display:inline"
transform="translate(-56.641256,-70.183469)"
id="g1008"
inkscape:groupmode="layer"
inkscape:label="Middle" />
<g
inkscape:groupmode="layer"
id="layer5"
inkscape:label="Bottom"
style="display:inline" />
<g
inkscape:groupmode="layer"
id="layer6"
inkscape:label="Top"
style="display:inline">
<g
id="g873">
<g
transform="matrix(1.0408977,0,0,1.0408977,-2.168957,-3.2849971)"
style="display:inline"
id="g1006"
inkscape:export-xdpi="664.21741"
inkscape:export-ydpi="664.21741"
inkscape:export-filename="/home/linux/Desktop/ethereum-classic-logo.png">
<g
id="g1004">
<path
inkscape:export-ydpi="120"
inkscape:export-xdpi="120"
inkscape:connector-curvature="0"
id="path990"
d="m 2.088999,78.475118 46.748789,-24.94739 46.14603,24.76633 -46.10598,24.968892 z"
style="display:inline;fill:#009f42;fill-opacity:1;stroke-width:0.35277781"
sodipodi:nodetypes="ccccc" />
<g
inkscape:export-ydpi="120"
inkscape:export-xdpi="120"
id="g994">