Check Transaction page (Pt. 1 - The Basics) (#1099)
* Component layer and routing for transaction status. * Initial start on redux for transactions. * Initial crack at reducer / actions / saga for transactions. * Finish off check transaction saga, reducer, component, and page.
This commit is contained in:
parent
f46df010db
commit
be61d804e0
|
@ -9,6 +9,7 @@ import SendTransaction from 'containers/Tabs/SendTransaction';
|
|||
import Swap from 'containers/Tabs/Swap';
|
||||
import SignAndVerifyMessage from 'containers/Tabs/SignAndVerifyMessage';
|
||||
import BroadcastTx from 'containers/Tabs/BroadcastTx';
|
||||
import CheckTransaction from 'containers/Tabs/CheckTransaction';
|
||||
import ErrorScreen from 'components/ErrorScreen';
|
||||
import PageNotFound from 'components/PageNotFound';
|
||||
import LogOutPrompt from 'components/LogOutPrompt';
|
||||
|
@ -67,6 +68,7 @@ export default class Root extends Component<Props, State> {
|
|||
<Route path="/contracts" component={Contracts} />
|
||||
<Route path="/ens" component={ENS} exact={true} />
|
||||
<Route path="/sign-and-verify-message" component={SignAndVerifyMessage} />
|
||||
<Route path="/tx-status" component={CheckTransaction} exact={true} />
|
||||
<Route path="/pushTx" component={BroadcastTx} />
|
||||
<RouteNotFound />
|
||||
</Switch>
|
||||
|
@ -120,8 +122,7 @@ const LegacyRoutes = withRouter(props => {
|
|||
history.push('/account/info');
|
||||
break;
|
||||
case '#check-tx-status':
|
||||
history.push('/check-tx-status');
|
||||
break;
|
||||
return <RedirectWithQuery from={pathname} to={'/tx-status'} />;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
import * as interfaces from './actionTypes';
|
||||
import { TypeKeys } from './constants';
|
||||
|
||||
export type TFetchTransactionData = typeof fetchTransactionData;
|
||||
export function fetchTransactionData(txhash: string): interfaces.FetchTransactionDataAction {
|
||||
return {
|
||||
type: TypeKeys.TRANSACTIONS_FETCH_TRANSACTION_DATA,
|
||||
payload: txhash
|
||||
};
|
||||
}
|
||||
|
||||
export type TSetTransactionData = typeof setTransactionData;
|
||||
export function setTransactionData(
|
||||
payload: interfaces.SetTransactionDataAction['payload']
|
||||
): interfaces.SetTransactionDataAction {
|
||||
return {
|
||||
type: TypeKeys.TRANSACTIONS_SET_TRANSACTION_DATA,
|
||||
payload
|
||||
};
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
import { TypeKeys } from './constants';
|
||||
import { TransactionData, TransactionReceipt } from 'libs/nodes';
|
||||
|
||||
export interface FetchTransactionDataAction {
|
||||
type: TypeKeys.TRANSACTIONS_FETCH_TRANSACTION_DATA;
|
||||
payload: string;
|
||||
}
|
||||
|
||||
export interface SetTransactionDataAction {
|
||||
type: TypeKeys.TRANSACTIONS_SET_TRANSACTION_DATA;
|
||||
payload: {
|
||||
txhash: string;
|
||||
data: TransactionData | null;
|
||||
receipt: TransactionReceipt | null;
|
||||
error: string | null;
|
||||
};
|
||||
}
|
||||
|
||||
/*** Union Type ***/
|
||||
export type TransactionsAction = FetchTransactionDataAction | SetTransactionDataAction;
|
|
@ -0,0 +1,5 @@
|
|||
export enum TypeKeys {
|
||||
TRANSACTIONS_FETCH_TRANSACTION_DATA = 'TRANSACTIONS_FETCH_TRANSACTION_DATA',
|
||||
TRANSACTIONS_SET_TRANSACTION_DATA = 'TRANSACTIONS_SET_TRANSACTION_DATA',
|
||||
TRANSACTIONS_SET_TRANSACTION_ERROR = 'TRANSACTIONS_SET_TRANSACTION_ERROR'
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
export * from './actionCreators';
|
||||
export * from './actionTypes';
|
||||
export * from './constants';
|
|
@ -34,6 +34,10 @@ const tabs: TabLink[] = [
|
|||
name: 'Sign & Verify Message',
|
||||
to: '/sign-and-verify-message'
|
||||
},
|
||||
{
|
||||
name: 'NAV_TxStatus',
|
||||
to: '/tx-status'
|
||||
},
|
||||
{
|
||||
name: 'Broadcast Transaction',
|
||||
to: '/pushTx'
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
@import 'common/sass/variables';
|
||||
@import 'common/sass/mixins';
|
||||
|
||||
.TxData {
|
||||
&-row {
|
||||
font-size: 0.9rem;
|
||||
|
||||
&-label {
|
||||
font-weight: bold;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
&-data {
|
||||
@include mono;
|
||||
|
||||
&-more {
|
||||
margin-left: $space-sm;
|
||||
font-size: 0.7rem;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
&-status {
|
||||
font-weight: bold;
|
||||
|
||||
&.is-success {
|
||||
color: $brand-success;
|
||||
}
|
||||
&.is-warning {
|
||||
color: $brand-warning;
|
||||
}
|
||||
&.is-danger {
|
||||
color: $brand-danger;
|
||||
}
|
||||
}
|
||||
|
||||
.Identicon {
|
||||
float: left;
|
||||
margin-right: $space-sm;
|
||||
}
|
||||
|
||||
textarea {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 7.2rem;
|
||||
background: $gray-lighter;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,174 @@
|
|||
import React from 'react';
|
||||
import translate from 'translations';
|
||||
import { Identicon, UnitDisplay, NewTabLink } from 'components/ui';
|
||||
import { TransactionData, TransactionReceipt } from 'libs/nodes';
|
||||
import { NetworkConfig } from 'types/network';
|
||||
import './TransactionDataTable.scss';
|
||||
|
||||
interface TableRow {
|
||||
label: React.ReactElement<string> | string;
|
||||
data: React.ReactElement<string> | string | number | null;
|
||||
}
|
||||
|
||||
const MaybeLink: React.SFC<{
|
||||
href: string | null | undefined | false;
|
||||
children: any; // Too many damn React element types
|
||||
}> = ({ href, children }) => {
|
||||
if (href) {
|
||||
return <NewTabLink href={href}>{children}</NewTabLink>;
|
||||
} else {
|
||||
return <React.Fragment>{children}</React.Fragment>;
|
||||
}
|
||||
};
|
||||
|
||||
interface Props {
|
||||
data: TransactionData;
|
||||
receipt: TransactionReceipt | null;
|
||||
network: NetworkConfig;
|
||||
}
|
||||
|
||||
const TransactionDataTable: React.SFC<Props> = ({ data, receipt, network }) => {
|
||||
const explorer: { [key: string]: string | false | null } = {};
|
||||
const hasInputData = data.input && data.input !== '0x';
|
||||
|
||||
if (!network.isCustom) {
|
||||
explorer.tx = network.blockExplorer && network.blockExplorer.txUrl(data.hash);
|
||||
explorer.block =
|
||||
network.blockExplorer &&
|
||||
!!data.blockNumber &&
|
||||
network.blockExplorer.blockUrl(data.blockNumber);
|
||||
explorer.to = network.blockExplorer && network.blockExplorer.addressUrl(data.to);
|
||||
explorer.from = network.blockExplorer && network.blockExplorer.addressUrl(data.from);
|
||||
explorer.contract =
|
||||
network.blockExplorer &&
|
||||
receipt &&
|
||||
receipt.contractAddress &&
|
||||
network.blockExplorer.addressUrl(receipt.contractAddress);
|
||||
}
|
||||
|
||||
let statusMsg = '';
|
||||
let statusType = '';
|
||||
let statusSeeMore = false;
|
||||
if (receipt) {
|
||||
if (receipt.status === 1) {
|
||||
statusMsg = 'SUCCESSFUL';
|
||||
statusType = 'success';
|
||||
} else if (receipt.status === 0) {
|
||||
statusMsg = 'FAILED';
|
||||
statusType = 'danger';
|
||||
statusSeeMore = true;
|
||||
} else {
|
||||
// Pre-byzantium transactions don't use status, and cannot have their
|
||||
// success determined over the JSON RPC api
|
||||
statusMsg = 'UNKNOWN';
|
||||
statusType = 'warning';
|
||||
statusSeeMore = true;
|
||||
}
|
||||
} else {
|
||||
statusMsg = 'PENDING';
|
||||
statusType = 'warning';
|
||||
}
|
||||
|
||||
const rows: TableRow[] = [
|
||||
{
|
||||
label: 'Status',
|
||||
data: (
|
||||
<React.Fragment>
|
||||
<strong className={`TxData-row-data-status is-${statusType}`}>{statusMsg}</strong>
|
||||
{statusSeeMore &&
|
||||
explorer.tx &&
|
||||
!network.isCustom && (
|
||||
<NewTabLink className="TxData-row-data-more" href={explorer.tx as string}>
|
||||
(See more on {network.blockExplorer.name})
|
||||
</NewTabLink>
|
||||
)}
|
||||
</React.Fragment>
|
||||
)
|
||||
},
|
||||
{
|
||||
label: translate('x_TxHash'),
|
||||
data: <MaybeLink href={explorer.tx}>{data.hash}</MaybeLink>
|
||||
},
|
||||
{
|
||||
label: 'Block Number',
|
||||
data: receipt && <MaybeLink href={explorer.block}>{receipt.blockNumber}</MaybeLink>
|
||||
},
|
||||
{
|
||||
label: translate('OFFLINE_Step1_Label_1'),
|
||||
data: (
|
||||
<MaybeLink href={explorer.from}>
|
||||
<Identicon address={data.from} size="26px" />
|
||||
{data.from}
|
||||
</MaybeLink>
|
||||
)
|
||||
},
|
||||
{
|
||||
label: translate('OFFLINE_Step2_Label_1'),
|
||||
data: (
|
||||
<MaybeLink href={explorer.to}>
|
||||
<Identicon address={data.to} size="26px" />
|
||||
{data.to}
|
||||
</MaybeLink>
|
||||
)
|
||||
},
|
||||
{
|
||||
label: translate('SEND_amount_short'),
|
||||
data: <UnitDisplay value={data.value} unit="ether" symbol={network.unit} />
|
||||
},
|
||||
{
|
||||
label: translate('OFFLINE_Step2_Label_3'),
|
||||
data: <UnitDisplay value={data.gasPrice} unit="gwei" symbol="Gwei" />
|
||||
},
|
||||
{
|
||||
label: translate('OFFLINE_Step2_Label_4'),
|
||||
data: <UnitDisplay value={data.gas} unit="wei" />
|
||||
},
|
||||
{
|
||||
label: 'Gas Used',
|
||||
data: receipt && <UnitDisplay value={receipt.gasUsed} unit="wei" />
|
||||
},
|
||||
{
|
||||
label: 'Transaction Fee',
|
||||
data: receipt && (
|
||||
<UnitDisplay
|
||||
value={receipt.gasUsed.mul(data.gasPrice)}
|
||||
unit="ether"
|
||||
symbol={network.unit}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
label: translate('New contract address'),
|
||||
data: receipt &&
|
||||
receipt.contractAddress && (
|
||||
<MaybeLink href={explorer.contract}>{receipt.contractAddress}</MaybeLink>
|
||||
)
|
||||
},
|
||||
{
|
||||
label: translate('OFFLINE_Step2_Label_5'),
|
||||
data: data.nonce
|
||||
},
|
||||
{
|
||||
label: translate('TRANS_data'),
|
||||
data: hasInputData ? (
|
||||
<textarea className="form-control" value={data.input} disabled={true} />
|
||||
) : null
|
||||
}
|
||||
];
|
||||
|
||||
const filteredRows = rows.filter(row => !!row.data);
|
||||
return (
|
||||
<table className="TxData table table-striped">
|
||||
<tbody>
|
||||
{filteredRows.map((row, idx) => (
|
||||
<tr className="TxData-row" key={idx}>
|
||||
<td className="TxData-row-label">{row.label}</td>
|
||||
<td className="TxData-row-data">{row.data}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
};
|
||||
|
||||
export default TransactionDataTable;
|
|
@ -0,0 +1,35 @@
|
|||
@import 'common/sass/variables';
|
||||
|
||||
.TxStatus {
|
||||
text-align: center;
|
||||
|
||||
&-title {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
&-data {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
&-loading {
|
||||
padding: $space * 3 0;
|
||||
}
|
||||
|
||||
&-error {
|
||||
max-width: 620px;
|
||||
margin: 0 auto;
|
||||
|
||||
&-title {
|
||||
color: $brand-danger;
|
||||
}
|
||||
|
||||
&-desc {
|
||||
font-size: $font-size-bump;
|
||||
margin-bottom: $space-md;
|
||||
}
|
||||
|
||||
&-list {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import translate from 'translations';
|
||||
import { fetchTransactionData, TFetchTransactionData } from 'actions/transactions';
|
||||
import { getTransactionDatas } from 'selectors/transactions';
|
||||
import { getNetworkConfig } from 'selectors/config';
|
||||
import { Spinner } from 'components/ui';
|
||||
import TransactionDataTable from './TransactionDataTable';
|
||||
import { AppState } from 'reducers';
|
||||
import { NetworkConfig } from 'types/network';
|
||||
import { TransactionState } from 'reducers/transactions';
|
||||
import './TransactionStatus.scss';
|
||||
|
||||
interface OwnProps {
|
||||
txHash: string;
|
||||
}
|
||||
|
||||
interface StateProps {
|
||||
tx: TransactionState | null;
|
||||
network: NetworkConfig;
|
||||
}
|
||||
|
||||
interface ActionProps {
|
||||
fetchTransactionData: TFetchTransactionData;
|
||||
}
|
||||
|
||||
type Props = OwnProps & StateProps & ActionProps;
|
||||
|
||||
class TransactionStatus extends React.Component<Props> {
|
||||
public componentDidMount() {
|
||||
this.props.fetchTransactionData(this.props.txHash);
|
||||
}
|
||||
|
||||
public componentWillReceiveProps(nextProps: Props) {
|
||||
if (this.props.txHash !== nextProps.txHash) {
|
||||
this.props.fetchTransactionData(nextProps.txHash);
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { tx, network } = this.props;
|
||||
let content;
|
||||
|
||||
if (tx && tx.data) {
|
||||
content = (
|
||||
<React.Fragment>
|
||||
<h2 className="TxStatus-title">Transaction Found</h2>
|
||||
<div className="TxStatus-data">
|
||||
<TransactionDataTable network={network} data={tx.data} receipt={tx.receipt} />
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
} else if (tx && tx.error) {
|
||||
content = (
|
||||
<div className="TxStatus-error">
|
||||
<h2 className="TxStatus-error-title">{translate('tx_notFound')}</h2>
|
||||
<p className="TxStatus-error-desc">{translate('tx_notFound_1')}</p>
|
||||
<ul className="TxStatus-error-list">
|
||||
<li>Make sure you copied the Transaction Hash correctly</li>
|
||||
<li>{translate('tx_notFound_2')}</li>
|
||||
<li>{translate('tx_notFound_3')}</li>
|
||||
<li>{translate('tx_notFound_4')}</li>
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
} else if (tx && tx.isLoading) {
|
||||
// tx.isLoading... probably.
|
||||
content = (
|
||||
<div className="TxStatus-loading">
|
||||
<Spinner size="x3" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return <div className="TxStatus">{content}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps(state: AppState, ownProps: OwnProps): StateProps {
|
||||
const { txHash } = ownProps;
|
||||
|
||||
return {
|
||||
tx: getTransactionDatas(state)[txHash],
|
||||
network: getNetworkConfig(state)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, { fetchTransactionData })(TransactionStatus);
|
|
@ -0,0 +1,2 @@
|
|||
import TransactionStatus from './TransactionStatus';
|
||||
export default TransactionStatus;
|
|
@ -18,3 +18,4 @@ export { default as TXMetaDataPanel } from './TXMetaDataPanel';
|
|||
export { default as WalletDecrypt } from './WalletDecrypt';
|
||||
export { default as TogglablePassword } from './TogglablePassword';
|
||||
export { default as GenerateKeystoreModal } from './GenerateKeystoreModal';
|
||||
export { default as TransactionStatus } from './TransactionStatus';
|
||||
|
|
|
@ -31,8 +31,8 @@ export interface AAttributes {
|
|||
|
||||
interface NewTabLinkProps extends AAttributes {
|
||||
href: string;
|
||||
content?: React.ReactElement<any> | string | string[];
|
||||
children?: React.ReactElement<any> | string | string[];
|
||||
content?: React.ReactElement<any> | string | string[] | number;
|
||||
children?: React.ReactElement<any> | string | string[] | number;
|
||||
}
|
||||
|
||||
export class NewTabLink extends React.Component<NewTabLinkProps> {
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
.TxHashInput {
|
||||
max-width: 700px;
|
||||
margin: 0 auto;
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
import React from 'react';
|
||||
import translate from 'translations';
|
||||
import { isValidTxHash, isValidETHAddress } from 'libs/validators';
|
||||
import './TxHashInput.scss';
|
||||
|
||||
interface Props {
|
||||
hash?: string;
|
||||
onSubmit(hash: string): void;
|
||||
}
|
||||
|
||||
interface State {
|
||||
hash: string;
|
||||
}
|
||||
|
||||
export default class TxHashInput extends React.Component<Props, State> {
|
||||
public constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = { hash: props.hash || '' };
|
||||
}
|
||||
|
||||
public componentWillReceiveProps(nextProps: Props) {
|
||||
if (this.props.hash !== nextProps.hash && nextProps.hash) {
|
||||
this.setState({ hash: nextProps.hash });
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { hash } = this.state;
|
||||
const validClass = hash ? (isValidTxHash(hash) ? 'is-valid' : 'is-invalid') : '';
|
||||
|
||||
return (
|
||||
<form className="TxHashInput" onSubmit={this.handleSubmit}>
|
||||
<input
|
||||
value={hash}
|
||||
placeholder="0x16e521..."
|
||||
className={`TxHashInput-field form-control ${validClass}`}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
|
||||
{isValidETHAddress(hash) && (
|
||||
<p className="TxHashInput-message help-block is-invalid">
|
||||
You cannot use an address, you must use a transaction hash
|
||||
</p>
|
||||
)}
|
||||
|
||||
<button className="TxHashInput-submit btn btn-primary btn-block">
|
||||
{translate('NAV_CheckTxStatus')}
|
||||
</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
private handleChange = (ev: React.FormEvent<HTMLInputElement>) => {
|
||||
this.setState({ hash: ev.currentTarget.value });
|
||||
};
|
||||
|
||||
private handleSubmit = (ev: React.FormEvent<HTMLFormElement>) => {
|
||||
ev.preventDefault();
|
||||
if (isValidTxHash(this.state.hash)) {
|
||||
this.props.onSubmit(this.state.hash);
|
||||
}
|
||||
};
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
@import 'common/sass/variables';
|
||||
|
||||
.CheckTransaction {
|
||||
&-form {
|
||||
text-align: center;
|
||||
|
||||
&-desc {
|
||||
max-width: 580px;
|
||||
margin: 0 auto $space;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import TabSection from 'containers/TabSection';
|
||||
import TxHashInput from './components/TxHashInput';
|
||||
import { TransactionStatus as TransactionStatusComponent } from 'components';
|
||||
import { NewTabLink } from 'components/ui';
|
||||
import { getNetworkConfig } from 'selectors/config';
|
||||
import { AppState } from 'reducers';
|
||||
import { NetworkConfig } from 'types/network';
|
||||
import './index.scss';
|
||||
|
||||
interface Props {
|
||||
network: NetworkConfig;
|
||||
}
|
||||
|
||||
interface State {
|
||||
hash: string;
|
||||
}
|
||||
|
||||
class CheckTransaction extends React.Component<Props, State> {
|
||||
public state: State = {
|
||||
hash: ''
|
||||
};
|
||||
|
||||
public render() {
|
||||
const { network } = this.props;
|
||||
const { hash } = this.state;
|
||||
|
||||
return (
|
||||
<TabSection>
|
||||
<div className="CheckTransaction Tab-content">
|
||||
<section className="CheckTransaction-form Tab-content-pane">
|
||||
<h1 className="CheckTransaction-form-title">Check Transaction Status</h1>
|
||||
<p className="CheckTransaction-form-desc">
|
||||
Enter your Transaction Hash to check on its status.{' '}
|
||||
{!network.isCustom && (
|
||||
<React.Fragment>
|
||||
If you don’t know your Transaction Hash, you can look it up on the{' '}
|
||||
<NewTabLink href={network.blockExplorer.origin}>
|
||||
{network.blockExplorer.name} explorer
|
||||
</NewTabLink>{' '}
|
||||
by looking up your address.
|
||||
</React.Fragment>
|
||||
)}
|
||||
</p>
|
||||
<TxHashInput onSubmit={this.handleHashSubmit} />
|
||||
</section>
|
||||
|
||||
{hash && (
|
||||
<section className="CheckTransaction-tx Tab-content-pane">
|
||||
<TransactionStatusComponent txHash={hash} />
|
||||
</section>
|
||||
)}
|
||||
</div>
|
||||
</TabSection>
|
||||
);
|
||||
}
|
||||
|
||||
private handleHashSubmit = (hash: string) => {
|
||||
// Reset to re-mount the component
|
||||
this.setState({ hash: '' }, () => {
|
||||
this.setState({ hash });
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export default connect((state: AppState) => ({
|
||||
network: getNetworkConfig(state)
|
||||
}))(CheckTransaction);
|
|
@ -6,10 +6,39 @@ export interface TxObj {
|
|||
to: string;
|
||||
data: string;
|
||||
}
|
||||
|
||||
interface TokenBalanceResult {
|
||||
balance: TokenValue;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
export interface TransactionData {
|
||||
hash: string;
|
||||
nonce: number;
|
||||
blockHash: string | null;
|
||||
blockNumber: number | null;
|
||||
transactionIndex: number | null;
|
||||
from: string;
|
||||
to: string;
|
||||
value: Wei;
|
||||
gasPrice: Wei;
|
||||
gas: Wei;
|
||||
input: string;
|
||||
}
|
||||
|
||||
export interface TransactionReceipt {
|
||||
transactionHash: string;
|
||||
transactionIndex: number;
|
||||
blockHash: string;
|
||||
blockNumber: number;
|
||||
cumulativeGasUsed: Wei;
|
||||
gasUsed: Wei;
|
||||
contractAddress: string | null;
|
||||
logs: string[];
|
||||
logsBloom: string;
|
||||
status: number;
|
||||
}
|
||||
|
||||
export interface INode {
|
||||
ping(): Promise<boolean>;
|
||||
getBalance(address: string): Promise<Wei>;
|
||||
|
@ -17,6 +46,8 @@ export interface INode {
|
|||
getTokenBalances(address: string, tokens: Token[]): Promise<TokenBalanceResult[]>;
|
||||
estimateGas(tx: Partial<IHexStrTransaction>): Promise<Wei>;
|
||||
getTransactionCount(address: string): Promise<string>;
|
||||
getTransactionByHash(txhash: string): Promise<TransactionData>;
|
||||
getTransactionReceipt(txhash: string): Promise<TransactionReceipt>;
|
||||
sendRawTx(tx: string): Promise<string>;
|
||||
sendCallRequest(txObj: TxObj): Promise<string>;
|
||||
getCurrentBlock(): Promise<string>;
|
||||
|
|
|
@ -6,6 +6,7 @@ import {
|
|||
GetBalanceRequest,
|
||||
GetTokenBalanceRequest,
|
||||
GetTransactionCountRequest,
|
||||
GetTransactionByHashRequest,
|
||||
SendRawTxRequest,
|
||||
GetCurrentBlockRequest
|
||||
} from './types';
|
||||
|
@ -58,6 +59,14 @@ export default class EtherscanRequests extends RPCRequests {
|
|||
};
|
||||
}
|
||||
|
||||
public getTransactionByHash(txhash: string): GetTransactionByHashRequest {
|
||||
return {
|
||||
module: 'proxy',
|
||||
action: 'eth_getTransactionByHash',
|
||||
txhash
|
||||
};
|
||||
}
|
||||
|
||||
public getTokenBalance(address: string, token: Token): GetTokenBalanceRequest {
|
||||
return this.ethCall({
|
||||
to: token.address,
|
||||
|
|
|
@ -41,6 +41,12 @@ export interface GetTransactionCountRequest extends EtherscanReqBase {
|
|||
tag: 'latest';
|
||||
}
|
||||
|
||||
export interface GetTransactionByHashRequest extends EtherscanReqBase {
|
||||
module: 'proxy';
|
||||
action: 'eth_getTransactionByHash';
|
||||
txhash: string;
|
||||
}
|
||||
|
||||
export interface GetCurrentBlockRequest extends EtherscanReqBase {
|
||||
module: 'proxy';
|
||||
action: 'eth_blockNumber';
|
||||
|
@ -53,4 +59,5 @@ export type EtherscanRequest =
|
|||
| GetTokenBalanceRequest
|
||||
| EstimateGasRequest
|
||||
| GetTransactionCountRequest
|
||||
| GetTransactionByHashRequest
|
||||
| GetCurrentBlockRequest;
|
||||
|
|
|
@ -3,3 +3,4 @@ export { default as InfuraNode } from './infura';
|
|||
export { default as EtherscanNode } from './etherscan';
|
||||
export { default as CustomNode } from './custom';
|
||||
export { default as Web3Node } from './web3';
|
||||
export * from './INode';
|
||||
|
|
|
@ -2,7 +2,8 @@ import BN from 'bn.js';
|
|||
import { IHexStrTransaction } from 'libs/transaction';
|
||||
import { Wei, TokenValue } from 'libs/units';
|
||||
import { stripHexPrefix } from 'libs/values';
|
||||
import { INode, TxObj } from '../INode';
|
||||
import { hexToNumber } from 'utils/formatters';
|
||||
import { INode, TxObj, TransactionData, TransactionReceipt } from '../INode';
|
||||
import RPCClient from './client';
|
||||
import RPCRequests from './requests';
|
||||
import {
|
||||
|
@ -11,9 +12,11 @@ import {
|
|||
isValidCallRequest,
|
||||
isValidTokenBalance,
|
||||
isValidTransactionCount,
|
||||
isValidTransactionByHash,
|
||||
isValidTransactionReceipt,
|
||||
isValidCurrentBlock,
|
||||
isValidRawTxApi
|
||||
} from '../../validators';
|
||||
} from 'libs/validators';
|
||||
import { Token } from 'types/network';
|
||||
|
||||
export default class RpcNode implements INode {
|
||||
|
@ -46,8 +49,6 @@ export default class RpcNode implements INode {
|
|||
}
|
||||
|
||||
public estimateGas(transaction: Partial<IHexStrTransaction>): Promise<Wei> {
|
||||
// Timeout after 10 seconds
|
||||
|
||||
return this.client
|
||||
.call(this.requests.estimateGas(transaction))
|
||||
.then(isValidEstimateGas)
|
||||
|
@ -106,6 +107,37 @@ export default class RpcNode implements INode {
|
|||
.then(({ result }) => result);
|
||||
}
|
||||
|
||||
public getTransactionByHash(txhash: string): Promise<TransactionData> {
|
||||
return this.client
|
||||
.call(this.requests.getTransactionByHash(txhash))
|
||||
.then(isValidTransactionByHash)
|
||||
.then(({ result }) => ({
|
||||
...result,
|
||||
to: result.to || '0x0',
|
||||
value: Wei(result.value),
|
||||
gasPrice: Wei(result.gasPrice),
|
||||
gas: Wei(result.gas),
|
||||
nonce: hexToNumber(result.nonce),
|
||||
blockNumber: result.blockNumber ? hexToNumber(result.blockNumber) : null,
|
||||
transactionIndex: result.transactionIndex ? hexToNumber(result.transactionIndex) : null
|
||||
}));
|
||||
}
|
||||
|
||||
public getTransactionReceipt(txhash: string): Promise<TransactionReceipt> {
|
||||
return this.client
|
||||
.call(this.requests.getTransactionReceipt(txhash))
|
||||
.then(isValidTransactionReceipt)
|
||||
.then(({ result }) => ({
|
||||
...result,
|
||||
transactionIndex: hexToNumber(result.transactionIndex),
|
||||
blockNumber: hexToNumber(result.blockNumber),
|
||||
cumulativeGasUsed: Wei(result.cumulativeGasUsed),
|
||||
gasUsed: Wei(result.gasUsed),
|
||||
status: result.status ? hexToNumber(result.status) : null,
|
||||
root: result.root || null
|
||||
}));
|
||||
}
|
||||
|
||||
public getCurrentBlock(): Promise<string> {
|
||||
return this.client
|
||||
.call(this.requests.getCurrentBlock())
|
||||
|
|
|
@ -6,7 +6,9 @@ import {
|
|||
GetTokenBalanceRequest,
|
||||
GetTransactionCountRequest,
|
||||
SendRawTxRequest,
|
||||
GetCurrentBlockRequest
|
||||
GetCurrentBlockRequest,
|
||||
GetTransactionByHashRequest,
|
||||
GetTransactionReceiptRequest
|
||||
} from './types';
|
||||
import { hexEncodeData } from './utils';
|
||||
import { TxObj } from '../INode';
|
||||
|
@ -17,6 +19,8 @@ export default class RPCRequests {
|
|||
return { method: 'net_version' };
|
||||
}
|
||||
|
||||
/* TODO: Fix `| any` on all of these */
|
||||
|
||||
public sendRawTx(signedTx: string): SendRawTxRequest | any {
|
||||
return {
|
||||
method: 'eth_sendRawTransaction',
|
||||
|
@ -52,6 +56,20 @@ export default class RPCRequests {
|
|||
};
|
||||
}
|
||||
|
||||
public getTransactionByHash(txhash: string): GetTransactionByHashRequest | any {
|
||||
return {
|
||||
method: 'eth_getTransactionByHash',
|
||||
params: [txhash]
|
||||
};
|
||||
}
|
||||
|
||||
public getTransactionReceipt(txhash: string): GetTransactionReceiptRequest | any {
|
||||
return {
|
||||
method: 'eth_getTransactionReceipt',
|
||||
params: [txhash]
|
||||
};
|
||||
}
|
||||
|
||||
public getTokenBalance(address: string, token: Token): GetTokenBalanceRequest | any {
|
||||
return {
|
||||
method: 'eth_call',
|
||||
|
|
|
@ -69,6 +69,16 @@ export interface GetTransactionCountRequest extends RPCRequestBase {
|
|||
params: [DATA, DEFAULT_BLOCK];
|
||||
}
|
||||
|
||||
export interface GetTransactionByHashRequest extends RPCRequestBase {
|
||||
method: 'eth_getTransactionByHash';
|
||||
params: [string];
|
||||
}
|
||||
|
||||
export interface GetTransactionReceiptRequest extends RPCRequestBase {
|
||||
method: 'eth_getTransactionReceipt';
|
||||
params: [string];
|
||||
}
|
||||
|
||||
export interface GetCurrentBlockRequest extends RPCRequestBase {
|
||||
method: 'eth_blockNumber';
|
||||
}
|
||||
|
@ -80,4 +90,6 @@ export type RPCRequest =
|
|||
| CallRequest
|
||||
| EstimateGasRequest
|
||||
| GetTransactionCountRequest
|
||||
| GetTransactionByHashRequest
|
||||
| GetTransactionReceiptRequest
|
||||
| GetCurrentBlockRequest;
|
||||
|
|
|
@ -167,7 +167,9 @@ export const schema = {
|
|||
properties: {
|
||||
jsonrpc: { type: 'string' },
|
||||
id: { oneOf: [{ type: 'string' }, { type: 'integer' }] },
|
||||
result: { oneOf: [{ type: 'string' }, { type: 'array' }] },
|
||||
result: {
|
||||
oneOf: [{ type: 'string' }, { type: 'array' }, { type: 'object' }]
|
||||
},
|
||||
status: { type: 'string' },
|
||||
message: { type: 'string', maxLength: 2 }
|
||||
}
|
||||
|
@ -236,6 +238,12 @@ export const isValidTokenBalance = (response: JsonRpcResponse) =>
|
|||
export const isValidTransactionCount = (response: JsonRpcResponse) =>
|
||||
isValidEthCall(response, schema.RpcNode)('Transaction Count');
|
||||
|
||||
export const isValidTransactionByHash = (response: JsonRpcResponse) =>
|
||||
isValidEthCall(response, schema.RpcNode)('Transaction By Hash');
|
||||
|
||||
export const isValidTransactionReceipt = (response: JsonRpcResponse) =>
|
||||
isValidEthCall(response, schema.RpcNode)('Transaction Receipt');
|
||||
|
||||
export const isValidCurrentBlock = (response: JsonRpcResponse) =>
|
||||
isValidEthCall(response, schema.RpcNode)('Current Block');
|
||||
|
||||
|
@ -253,3 +261,6 @@ export const isValidGetAccounts = (response: JsonRpcResponse) =>
|
|||
|
||||
export const isValidGetNetVersion = (response: JsonRpcResponse) =>
|
||||
isValidEthCall(response, schema.RpcNode)('Net Version');
|
||||
|
||||
export const isValidTxHash = (hash: string) =>
|
||||
hash.substring(0, 2) === '0x' && hash.length === 66 && isValidHex(hash);
|
||||
|
|
|
@ -17,11 +17,13 @@ export type State = { [key in StaticNetworkIds]: StaticNetworkConfig };
|
|||
// Must be a website that follows the ethplorer convention of /tx/[hash] and
|
||||
// address/[address] to generate the correct functions.
|
||||
// TODO: put this in utils / libs
|
||||
export function makeExplorer(origin: string): BlockExplorerConfig {
|
||||
export function makeExplorer(name: string, origin: string): BlockExplorerConfig {
|
||||
return {
|
||||
name,
|
||||
origin,
|
||||
txUrl: hash => `${origin}/tx/${hash}`,
|
||||
addressUrl: address => `${origin}/address/${address}`
|
||||
addressUrl: address => `${origin}/address/${address}`,
|
||||
blockUrl: blockNum => `${origin}/block/${blockNum}`
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -32,7 +34,7 @@ const INITIAL_STATE: State = {
|
|||
chainId: 1,
|
||||
isCustom: false,
|
||||
color: '#0e97c0',
|
||||
blockExplorer: makeExplorer('https://etherscan.io'),
|
||||
blockExplorer: makeExplorer('Etherscan', 'https://etherscan.io'),
|
||||
tokenExplorer: {
|
||||
name: ethPlorer,
|
||||
address: ETHTokenExplorer
|
||||
|
@ -51,7 +53,7 @@ const INITIAL_STATE: State = {
|
|||
chainId: 3,
|
||||
isCustom: false,
|
||||
color: '#adc101',
|
||||
blockExplorer: makeExplorer('https://ropsten.etherscan.io'),
|
||||
blockExplorer: makeExplorer('Etherscan', 'https://ropsten.etherscan.io'),
|
||||
tokens: require('config/tokens/ropsten.json'),
|
||||
contracts: require('config/contracts/ropsten.json'),
|
||||
isTestnet: true,
|
||||
|
@ -67,7 +69,7 @@ const INITIAL_STATE: State = {
|
|||
chainId: 42,
|
||||
isCustom: false,
|
||||
color: '#adc101',
|
||||
blockExplorer: makeExplorer('https://kovan.etherscan.io'),
|
||||
blockExplorer: makeExplorer('Etherscan', 'https://kovan.etherscan.io'),
|
||||
tokens: require('config/tokens/ropsten.json'),
|
||||
contracts: require('config/contracts/ropsten.json'),
|
||||
isTestnet: true,
|
||||
|
@ -83,7 +85,7 @@ const INITIAL_STATE: State = {
|
|||
chainId: 4,
|
||||
isCustom: false,
|
||||
color: '#adc101',
|
||||
blockExplorer: makeExplorer('https://rinkeby.etherscan.io'),
|
||||
blockExplorer: makeExplorer('Etherscan', 'https://rinkeby.etherscan.io'),
|
||||
tokens: require('config/tokens/rinkeby.json'),
|
||||
contracts: require('config/contracts/rinkeby.json'),
|
||||
isTestnet: true,
|
||||
|
@ -99,7 +101,7 @@ const INITIAL_STATE: State = {
|
|||
chainId: 61,
|
||||
isCustom: false,
|
||||
color: '#669073',
|
||||
blockExplorer: makeExplorer('https://gastracker.io'),
|
||||
blockExplorer: makeExplorer('GasTracker', 'https://gastracker.io'),
|
||||
tokens: require('config/tokens/etc.json'),
|
||||
contracts: require('config/contracts/etc.json'),
|
||||
dPathFormats: {
|
||||
|
@ -114,7 +116,7 @@ const INITIAL_STATE: State = {
|
|||
chainId: 8,
|
||||
isCustom: false,
|
||||
color: '#b37aff',
|
||||
blockExplorer: makeExplorer('https://ubiqscan.io/en'),
|
||||
blockExplorer: makeExplorer('Ubiqscan', 'https://ubiqscan.io/en'),
|
||||
tokens: require('config/tokens/ubq.json'),
|
||||
contracts: require('config/contracts/ubq.json'),
|
||||
dPathFormats: {
|
||||
|
@ -129,7 +131,7 @@ const INITIAL_STATE: State = {
|
|||
chainId: 2,
|
||||
isCustom: false,
|
||||
color: '#673ab7',
|
||||
blockExplorer: makeExplorer('https://www.gander.tech'),
|
||||
blockExplorer: makeExplorer('Gander', 'https://www.gander.tech'),
|
||||
tokens: require('config/tokens/exp.json'),
|
||||
contracts: require('config/contracts/exp.json'),
|
||||
dPathFormats: {
|
||||
|
|
|
@ -10,6 +10,7 @@ import { State as SwapState, swap } from './swap';
|
|||
import { State as WalletState, wallet } from './wallet';
|
||||
import { State as TransactionState, transaction } from './transaction';
|
||||
import { onboardStatus, State as OnboardStatusState } from './onboardStatus';
|
||||
import { State as TransactionsState, transactions } from './transactions';
|
||||
|
||||
export interface AppState {
|
||||
// Custom reducers
|
||||
|
@ -21,11 +22,11 @@ export interface AppState {
|
|||
customTokens: CustomTokensState;
|
||||
rates: RatesState;
|
||||
deterministicWallets: DeterministicWalletsState;
|
||||
// Third party reducers (TODO: Fill these out)
|
||||
form: any;
|
||||
routing: any;
|
||||
swap: SwapState;
|
||||
transaction: TransactionState;
|
||||
transactions: TransactionsState;
|
||||
// Third party reducers (TODO: Fill these out)
|
||||
routing: any;
|
||||
}
|
||||
|
||||
export default combineReducers<AppState>({
|
||||
|
@ -38,6 +39,7 @@ export default combineReducers<AppState>({
|
|||
customTokens,
|
||||
rates,
|
||||
deterministicWallets,
|
||||
routing: routerReducer,
|
||||
transaction
|
||||
transaction,
|
||||
transactions,
|
||||
routing: routerReducer
|
||||
});
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
import {
|
||||
FetchTransactionDataAction,
|
||||
SetTransactionDataAction,
|
||||
TransactionsAction,
|
||||
TypeKeys
|
||||
} from 'actions/transactions';
|
||||
import { TransactionData, TransactionReceipt } from 'libs/nodes';
|
||||
|
||||
export interface TransactionState {
|
||||
data: TransactionData | null;
|
||||
receipt: TransactionReceipt | null;
|
||||
error: string | null;
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
export interface State {
|
||||
txData: { [txhash: string]: TransactionState };
|
||||
}
|
||||
|
||||
export const INITIAL_STATE: State = {
|
||||
txData: {}
|
||||
};
|
||||
|
||||
function fetchTxData(state: State, action: FetchTransactionDataAction): State {
|
||||
return {
|
||||
...state,
|
||||
txData: {
|
||||
...state.txData,
|
||||
[action.payload]: {
|
||||
data: null,
|
||||
receipt: null,
|
||||
error: null,
|
||||
isLoading: true
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function setTxData(state: State, action: SetTransactionDataAction): State {
|
||||
return {
|
||||
...state,
|
||||
txData: {
|
||||
...state.txData,
|
||||
[action.payload.txhash]: {
|
||||
data: action.payload.data,
|
||||
receipt: action.payload.receipt,
|
||||
error: action.payload.error,
|
||||
isLoading: false
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function transactions(state: State = INITIAL_STATE, action: TransactionsAction): State {
|
||||
switch (action.type) {
|
||||
case TypeKeys.TRANSACTIONS_FETCH_TRANSACTION_DATA:
|
||||
return fetchTxData(state, action);
|
||||
case TypeKeys.TRANSACTIONS_SET_TRANSACTION_DATA:
|
||||
return setTxData(state, action);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
|
@ -15,6 +15,7 @@ import { getBityRatesSaga, getShapeShiftRatesSaga, swapProviderSaga } from './sw
|
|||
import wallet from './wallet';
|
||||
import { ens } from './ens';
|
||||
import { transaction } from './transaction';
|
||||
import transactions from './transactions';
|
||||
|
||||
export default {
|
||||
ens,
|
||||
|
@ -33,5 +34,6 @@ export default {
|
|||
transaction,
|
||||
deterministicWallets,
|
||||
swapProviderSaga,
|
||||
rates
|
||||
rates,
|
||||
transactions
|
||||
};
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
import { setTransactionData, FetchTransactionDataAction, TypeKeys } from 'actions/transactions';
|
||||
import { SagaIterator } from 'redux-saga';
|
||||
import { put, select, apply, takeEvery } from 'redux-saga/effects';
|
||||
import { getNodeLib } from 'selectors/config';
|
||||
import { INode, TransactionData, TransactionReceipt } from 'libs/nodes';
|
||||
|
||||
export function* fetchTxData(action: FetchTransactionDataAction): SagaIterator {
|
||||
const txhash = action.payload;
|
||||
let data: TransactionData | null = null;
|
||||
let receipt: TransactionReceipt | null = null;
|
||||
let error: string | null = null;
|
||||
|
||||
const node: INode = yield select(getNodeLib);
|
||||
|
||||
// Fetch data and receipt separately, not in parallel. Receipt should only be
|
||||
// fetched if the tx is mined, and throws if it's not, but that's not really
|
||||
// an "error", since we'd still want to show the unmined tx data.
|
||||
try {
|
||||
data = yield apply(node, node.getTransactionByHash, [txhash]);
|
||||
} catch (err) {
|
||||
console.warn('Failed to fetch transaction data', err);
|
||||
error = err.message;
|
||||
}
|
||||
|
||||
if (data && data.blockHash) {
|
||||
try {
|
||||
receipt = yield apply(node, node.getTransactionReceipt, [txhash]);
|
||||
} catch (err) {
|
||||
console.warn('Failed to fetch transaction receipt', err);
|
||||
receipt = null;
|
||||
}
|
||||
}
|
||||
|
||||
yield put(setTransactionData({ txhash, data, receipt, error }));
|
||||
}
|
||||
|
||||
export default function* transactions(): SagaIterator {
|
||||
yield takeEvery(TypeKeys.TRANSACTIONS_FETCH_TRANSACTION_DATA, fetchTxData);
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
import { AppState } from 'reducers';
|
||||
|
||||
export function getTransactionDatas(state: AppState) {
|
||||
return state.transactions.txData;
|
||||
}
|
|
@ -654,7 +654,3 @@
|
|||
"ONBOARD_resume": "It looks like you didn't finish reading through these slides last time. ProTip: Finish reading through the slides 😉"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import BN from 'bn.js';
|
||||
import { Wei } from 'libs/units';
|
||||
import { stripHexPrefix } from 'libs/values';
|
||||
|
||||
export function toFixedIfLarger(num: number, fixedSize: number = 6): string {
|
||||
return parseFloat(num.toFixed(fixedSize)).toString();
|
||||
|
@ -113,3 +115,7 @@ export function bytesToHuman(bytes: number) {
|
|||
export function ensV3Url(name: string) {
|
||||
return `https://mycrypto.com/?ensname=${name}#ens`;
|
||||
}
|
||||
|
||||
export function hexToNumber(hex: string) {
|
||||
return new BN(stripHexPrefix(hex)).toNumber();
|
||||
}
|
||||
|
|
|
@ -3,9 +3,11 @@ import { StaticNetworksState, CustomNetworksState } from 'reducers/config/networ
|
|||
type StaticNetworkIds = 'ETH' | 'Ropsten' | 'Kovan' | 'Rinkeby' | 'ETC' | 'UBQ' | 'EXP';
|
||||
|
||||
interface BlockExplorerConfig {
|
||||
name: string;
|
||||
origin: string;
|
||||
txUrl(txHash: string): string;
|
||||
addressUrl(address: string): string;
|
||||
blockUrl(blockNum: string | number): string;
|
||||
}
|
||||
|
||||
interface Token {
|
||||
|
@ -32,7 +34,7 @@ interface StaticNetworkConfig {
|
|||
name: StaticNetworkIds;
|
||||
unit: string;
|
||||
color?: string;
|
||||
blockExplorer?: BlockExplorerConfig;
|
||||
blockExplorer: BlockExplorerConfig;
|
||||
tokenExplorer?: {
|
||||
name: string;
|
||||
address(address: string): string;
|
||||
|
|
|
@ -18,7 +18,7 @@ const expectedInitialState = {
|
|||
chainId: 1,
|
||||
isCustom: false,
|
||||
color: '#0e97c0',
|
||||
blockExplorer: makeExplorer('https://etherscan.io'),
|
||||
blockExplorer: makeExplorer('Etherscan', 'https://etherscan.io'),
|
||||
tokenExplorer: {
|
||||
name: ethPlorer,
|
||||
address: ETHTokenExplorer
|
||||
|
@ -37,7 +37,7 @@ const expectedInitialState = {
|
|||
chainId: 3,
|
||||
isCustom: false,
|
||||
color: '#adc101',
|
||||
blockExplorer: makeExplorer('https://ropsten.etherscan.io'),
|
||||
blockExplorer: makeExplorer('Etherscan', 'https://ropsten.etherscan.io'),
|
||||
tokens: require('config/tokens/ropsten.json'),
|
||||
contracts: require('config/contracts/ropsten.json'),
|
||||
isTestnet: true,
|
||||
|
@ -53,7 +53,7 @@ const expectedInitialState = {
|
|||
chainId: 42,
|
||||
isCustom: false,
|
||||
color: '#adc101',
|
||||
blockExplorer: makeExplorer('https://kovan.etherscan.io'),
|
||||
blockExplorer: makeExplorer('Etherscan', 'https://kovan.etherscan.io'),
|
||||
tokens: require('config/tokens/ropsten.json'),
|
||||
contracts: require('config/contracts/ropsten.json'),
|
||||
isTestnet: true,
|
||||
|
@ -69,7 +69,7 @@ const expectedInitialState = {
|
|||
chainId: 4,
|
||||
isCustom: false,
|
||||
color: '#adc101',
|
||||
blockExplorer: makeExplorer('https://rinkeby.etherscan.io'),
|
||||
blockExplorer: makeExplorer('Etherscan', 'https://rinkeby.etherscan.io'),
|
||||
tokens: require('config/tokens/rinkeby.json'),
|
||||
contracts: require('config/contracts/rinkeby.json'),
|
||||
isTestnet: true,
|
||||
|
@ -85,7 +85,7 @@ const expectedInitialState = {
|
|||
chainId: 61,
|
||||
isCustom: false,
|
||||
color: '#669073',
|
||||
blockExplorer: makeExplorer('https://gastracker.io'),
|
||||
blockExplorer: makeExplorer('GasTracker', 'https://gastracker.io'),
|
||||
tokens: require('config/tokens/etc.json'),
|
||||
contracts: require('config/contracts/etc.json'),
|
||||
dPathFormats: {
|
||||
|
@ -100,7 +100,7 @@ const expectedInitialState = {
|
|||
chainId: 8,
|
||||
isCustom: false,
|
||||
color: '#b37aff',
|
||||
blockExplorer: makeExplorer('https://ubiqscan.io/en'),
|
||||
blockExplorer: makeExplorer('Ubiqscan', 'https://ubiqscan.io/en'),
|
||||
tokens: require('config/tokens/ubq.json'),
|
||||
contracts: require('config/contracts/ubq.json'),
|
||||
dPathFormats: {
|
||||
|
@ -115,7 +115,7 @@ const expectedInitialState = {
|
|||
chainId: 2,
|
||||
isCustom: false,
|
||||
color: '#673ab7',
|
||||
blockExplorer: makeExplorer('https://www.gander.tech'),
|
||||
blockExplorer: makeExplorer('Gander', 'https://www.gander.tech'),
|
||||
tokens: require('config/tokens/exp.json'),
|
||||
contracts: require('config/contracts/exp.json'),
|
||||
dPathFormats: {
|
||||
|
|
Loading…
Reference in New Issue