diff --git a/common/actions/ens/actionCreators.ts b/common/actions/ens/actionCreators.ts deleted file mode 100644 index 7bfb11c7..00000000 --- a/common/actions/ens/actionCreators.ts +++ /dev/null @@ -1,22 +0,0 @@ -import * as interfaces from './actionTypes'; -import * as constants from './constants'; - -export function resolveEnsName(name: string): interfaces.ResolveEnsNameAction { - return { - type: constants.ENS_RESOLVE, - payload: name - }; -} - -export function cacheEnsAddress( - ensName: string, - address: string -): interfaces.CacheEnsAddressAction { - return { - type: constants.ENS_CACHE, - payload: { - ensName, - address - } - }; -} diff --git a/common/actions/ens/actionCreators/index.ts b/common/actions/ens/actionCreators/index.ts new file mode 100644 index 00000000..9ea71985 --- /dev/null +++ b/common/actions/ens/actionCreators/index.ts @@ -0,0 +1 @@ +export * from './resolveDomain'; diff --git a/common/actions/ens/actionCreators/resolveDomain.ts b/common/actions/ens/actionCreators/resolveDomain.ts new file mode 100644 index 00000000..c402ed24 --- /dev/null +++ b/common/actions/ens/actionCreators/resolveDomain.ts @@ -0,0 +1,35 @@ +import * as ActionTypes from '../actionTypes'; +import { TypeKeys } from '../constants'; +import { DomainRequest } from 'libs/ens'; +import { ResolveDomainCached } from 'actions/ens'; + +export type TResolveDomainRequested = typeof resolveDomainRequested; +export const resolveDomainRequested = (domain: string): ActionTypes.ResolveDomainRequested => ({ + type: TypeKeys.ENS_RESOLVE_DOMAIN_REQUESTED, + payload: { domain } +}); + +export const resolveDomainCached = ( + payload: ResolveDomainCached['payload'] +): ResolveDomainCached => ({ + type: TypeKeys.ENS_RESOLVE_DOMAIN_CACHED, + payload +}); + +export type TResolveDomainSucceeded = typeof resolveDomainSucceeded; +export const resolveDomainSucceeded = ( + domain: string, + domainData: DomainRequest +): ActionTypes.ResolveDomainSucceeded => ({ + type: TypeKeys.ENS_RESOLVE_DOMAIN_SUCCEEDED, + payload: { domain, domainData } +}); + +export type TResolveDomainFailed = typeof resolveDomainFailed; +export const resolveDomainFailed = ( + domain: string, + error: Error +): ActionTypes.ResolveDomainFailed => ({ + type: TypeKeys.ENS_RESOLVE_DOMAIN_FAILED, + payload: { domain, error } +}); diff --git a/common/actions/ens/actionTypes.ts b/common/actions/ens/actionTypes.ts deleted file mode 100644 index d87a9445..00000000 --- a/common/actions/ens/actionTypes.ts +++ /dev/null @@ -1,17 +0,0 @@ -/*** Resolve ENS name ***/ -export interface ResolveEnsNameAction { - type: 'ENS_RESOLVE'; - payload: string; -} - -/*** Cache ENS address ***/ -export interface CacheEnsAddressAction { - type: 'ENS_CACHE'; - payload: { - ensName: string; - address: string; - }; -} - -/*** Union Type ***/ -export type EnsAction = ResolveEnsNameAction | CacheEnsAddressAction; diff --git a/common/actions/ens/actionTypes/actionTypes.ts b/common/actions/ens/actionTypes/actionTypes.ts new file mode 100644 index 00000000..22b4ce18 --- /dev/null +++ b/common/actions/ens/actionTypes/actionTypes.ts @@ -0,0 +1,5 @@ +import { ResolveDomainAction } from './resolveDomain'; + +export * from './resolveDomain'; + +export type EnsAction = ResolveDomainAction; diff --git a/common/actions/ens/actionTypes/index.ts b/common/actions/ens/actionTypes/index.ts new file mode 100644 index 00000000..3eb256de --- /dev/null +++ b/common/actions/ens/actionTypes/index.ts @@ -0,0 +1 @@ +export * from './actionTypes'; diff --git a/common/actions/ens/actionTypes/resolveDomain.ts b/common/actions/ens/actionTypes/resolveDomain.ts new file mode 100644 index 00000000..aab2d357 --- /dev/null +++ b/common/actions/ens/actionTypes/resolveDomain.ts @@ -0,0 +1,28 @@ +import { TypeKeys } from '../constants'; +import { DomainRequest } from 'libs/ens'; + +export interface ResolveDomainRequested { + type: TypeKeys.ENS_RESOLVE_DOMAIN_REQUESTED; + payload: { domain: string }; +} + +export interface ResolveDomainSucceeded { + type: TypeKeys.ENS_RESOLVE_DOMAIN_SUCCEEDED; + payload: { domain: string; domainData: DomainRequest }; +} + +export interface ResolveDomainCached { + type: TypeKeys.ENS_RESOLVE_DOMAIN_CACHED; + payload: { domain: string }; +} + +export interface ResolveDomainFailed { + type: TypeKeys.ENS_RESOLVE_DOMAIN_FAILED; + payload: { domain: string; error: Error }; +} + +export type ResolveDomainAction = + | ResolveDomainRequested + | ResolveDomainSucceeded + | ResolveDomainFailed + | ResolveDomainCached; diff --git a/common/actions/ens/constants.ts b/common/actions/ens/constants.ts index 2948a37a..6a752c30 100644 --- a/common/actions/ens/constants.ts +++ b/common/actions/ens/constants.ts @@ -1,2 +1,6 @@ -export const ENS_RESOLVE = 'ENS_RESOLVE'; -export const ENS_CACHE = 'ENS_CACHE'; +export enum TypeKeys { + ENS_RESOLVE_DOMAIN_REQUESTED = 'ENS_RESOLVE_DOMAIN_REQUESTED', + ENS_RESOLVE_DOMAIN_SUCCEEDED = 'ENS_RESOLVE_DOMAIN_SUCCEEDED', + ENS_RESOLVE_DOMAIN_CACHED = 'ENS_RESOLVE_DOMAIN_CACHED', + ENS_RESOLVE_DOMAIN_FAILED = 'ENS_RESOLVE_DOMAIN_FAILED' +} diff --git a/common/actions/transaction/actionTypes/fields.ts b/common/actions/transaction/actionTypes/fields.ts index 3ca3cb1d..c390844b 100644 --- a/common/actions/transaction/actionTypes/fields.ts +++ b/common/actions/transaction/actionTypes/fields.ts @@ -52,7 +52,6 @@ interface SetToFieldAction { payload: { raw: string; value: Address | null; - error?: string | null; }; } diff --git a/common/actions/transaction/actionTypes/meta.ts b/common/actions/transaction/actionTypes/meta.ts index 3ec3f068..3550d79f 100644 --- a/common/actions/transaction/actionTypes/meta.ts +++ b/common/actions/transaction/actionTypes/meta.ts @@ -7,7 +7,6 @@ interface SetTokenToMetaAction { payload: { raw: string; value: Address | null; - error?: string | null; }; } diff --git a/common/components/AddressField.tsx b/common/components/AddressField.tsx index ef6c90c2..28d97b5d 100644 --- a/common/components/AddressField.tsx +++ b/common/components/AddressField.tsx @@ -8,7 +8,7 @@ interface Props { export const AddressField: React.SFC = ({ isReadOnly }) => ( ( + withProps={({ currentTo, isValid, onChange, readOnly }) => ( = ({ isReadOnly }) => ( readOnly={!!(isReadOnly || readOnly)} onChange={onChange} /> - {errorMsg && ( -
- {errorMsg} -
- )}
)} /> diff --git a/common/components/AddressFieldFactory/AddressFieldFactory.tsx b/common/components/AddressFieldFactory/AddressFieldFactory.tsx index 698a7cfc..70dd5cee 100644 --- a/common/components/AddressFieldFactory/AddressFieldFactory.tsx +++ b/common/components/AddressFieldFactory/AddressFieldFactory.tsx @@ -18,14 +18,12 @@ export interface CallbackProps { isValid: boolean; readOnly: boolean; currentTo: ICurrentTo; - errorMsg?: string | null; onChange(ev: React.FormEvent): void; } -type Props = DispatchProps & DispatchProps & OwnProps; +type Props = DispatchProps & OwnProps; -//TODO: add ens resolving -class AddressFieldFactoryClass extends React.Component { +class AddressFieldFactoryClass extends React.Component { public componentDidMount() { // this 'to' parameter can be either token or actual field related const { to } = this.props; @@ -44,14 +42,17 @@ class AddressFieldFactoryClass extends React.Component { }; } -const AddressField = connect(null, { setCurrentTo })(AddressFieldFactoryClass); +const AddressFieldFactory = connect(null, { setCurrentTo })(AddressFieldFactoryClass); interface DefaultAddressFieldProps { withProps(props: CallbackProps): React.ReactElement | null; } const DefaultAddressField: React.SFC = ({ withProps }) => ( - } /> + } + /> ); export { DefaultAddressField as AddressFieldFactory }; diff --git a/common/components/AddressFieldFactory/AddressInputFactory.tsx b/common/components/AddressFieldFactory/AddressInputFactory.tsx index 3b868d17..bb00ddd2 100644 --- a/common/components/AddressFieldFactory/AddressInputFactory.tsx +++ b/common/components/AddressFieldFactory/AddressInputFactory.tsx @@ -1,29 +1,51 @@ import React, { Component } from 'react'; -import { Identicon } from 'components/ui'; +import { Identicon, Spinner } from 'components/ui'; import translate from 'translations'; -//import { EnsAddress } from './components'; import { Query } from 'components/renderCbs'; import { ICurrentTo, getCurrentTo, isValidCurrentTo } from 'selectors/transaction'; import { connect } from 'react-redux'; import { AppState } from 'reducers'; import { CallbackProps } from 'components/AddressFieldFactory'; +import { addHexPrefix } from 'ethereumjs-util'; +import { getResolvingDomain } from 'selectors/ens'; +import { isValidENSAddress } from 'libs/validators'; interface StateProps { currentTo: ICurrentTo; isValid: boolean; + isResolving: boolean; } + interface OwnProps { onChange(ev: React.FormEvent): void; withProps(props: CallbackProps): React.ReactElement | null; } +const ENSStatus: React.SFC<{ isLoading: boolean; ensAddress: string; rawAddress: string }> = ({ + isLoading, + ensAddress, + rawAddress +}) => { + const isENS = isValidENSAddress(ensAddress); + const text = 'Loading ENS address...'; + if (isLoading) { + return ( + <> + {text} + + ); + } else { + return isENS ? <>{`Resolved Address: ${rawAddress}`} : null; + } +}; + type Props = OwnProps & StateProps; -//TODO: ENS handling class AddressInputFactoryClass extends Component { public render() { - const { currentTo, onChange, isValid, withProps } = this.props; - const { raw } = currentTo; + const { currentTo, onChange, isValid, withProps, isResolving } = this.props; + const { value } = currentTo; + const addr = addHexPrefix(value ? value.toString('hex') : '0'); return (
@@ -35,15 +57,14 @@ class AddressInputFactoryClass extends Component { currentTo, isValid, onChange, - readOnly: !!readOnly, - errorMsg: currentTo.error + readOnly: !!readOnly || this.props.isResolving }) } /> - {/**/} +
- +
); @@ -52,5 +73,6 @@ class AddressInputFactoryClass extends Component { export const AddressInputFactory = connect((state: AppState) => ({ currentTo: getCurrentTo(state), + isResolving: getResolvingDomain(state), isValid: isValidCurrentTo(state) }))(AddressInputFactoryClass); diff --git a/common/components/AddressFieldFactory/components/EnsAddress.tsx b/common/components/AddressFieldFactory/components/EnsAddress.tsx deleted file mode 100644 index b26084e4..00000000 --- a/common/components/AddressFieldFactory/components/EnsAddress.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import React from 'react'; -/* - - public onChange = (e: React.SyntheticEvent) => { - const newValue = (e.target as HTMLInputElement).value; - const { onChange } = this.props; - if (!onChange) { - return; - } - // FIXME debounce? - if (isValidENSAddress(newValue)) { - this.props.resolveEnsName(newValue); - } - onChange(newValue); - }; -} -function mapStateToProps(state: AppState, props: PublicProps) { - return { - ensAddress: getEnsAddress(state, props.value) - }; -} -export default connect(mapStateToProps, { resolveEnsName })(AddressField); -*/ - -interface EnsAddressProps { - ensAddress: string | null; -} - -export const EnsAddress: React.SFC = ({ ensAddress }) => - (!!ensAddress && ( -

- ↳ - {ensAddress} -

- )) || - null; diff --git a/common/components/AddressFieldFactory/components/index.ts b/common/components/AddressFieldFactory/components/index.ts deleted file mode 100644 index ee054d13..00000000 --- a/common/components/AddressFieldFactory/components/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './EnsAddress'; diff --git a/common/components/Footer/index.scss b/common/components/Footer/index.scss index 866f031e..14907166 100644 --- a/common/components/Footer/index.scss +++ b/common/components/Footer/index.scss @@ -142,13 +142,6 @@ font-size: $font-size-small; margin: $space-sm 0; } - - @media screen and (max-width: $grid-float-breakpoint) { - .row { - margin-left: -0.5rem; - margin-right: -0.5rem; - } - } } .Modal { diff --git a/common/components/renderCbs/UnitConverter.tsx b/common/components/renderCbs/UnitConverter.tsx index c42f0cea..bd63121e 100644 --- a/common/components/renderCbs/UnitConverter.tsx +++ b/common/components/renderCbs/UnitConverter.tsx @@ -1,9 +1,11 @@ import { toTokenBase } from 'libs/units'; - import React, { Component } from 'react'; +import { connect } from 'react-redux'; +import { AppState } from 'reducers'; +import { getDecimal } from 'selectors/transaction'; interface IChildren { - onUserInput: UnitConverter['onUserInput']; + onUserInput: UnitConverterClass['onUserInput']; convertedUnit: string; } interface IFakeEvent { @@ -24,7 +26,7 @@ interface State { const initialState = { userInput: '' }; -export class UnitConverter extends Component { +class UnitConverterClass extends Component { public state: State = initialState; public componentWillReceiveProps(nextProps: Props) { @@ -58,3 +60,11 @@ export class UnitConverter extends Component { this.props.onChange(fakeEvent); }; } + +const mapStateToProps = (state: AppState) => { + return { + decimal: getDecimal(state) + }; +}; + +export const UnitConverter = connect(mapStateToProps)(UnitConverterClass); diff --git a/common/components/ui/Identicon.tsx b/common/components/ui/Identicon.tsx index abd54e61..263fdbaa 100644 --- a/common/components/ui/Identicon.tsx +++ b/common/components/ui/Identicon.tsx @@ -10,9 +10,7 @@ interface Props { export default function Identicon(props: Props) { const size = props.size || '4rem'; // FIXME breaks on failed checksums - const identiconDataUrl = isValidETHAddress(props.address.toLowerCase()) - ? toDataUrl(props.address.toLowerCase()) - : ''; + const identiconDataUrl = isValidETHAddress(props.address) ? toDataUrl(props.address) : ''; return (
{ + outputType: K; + + encodeInput(x: T): string; + decodeOutput(argStr: string): K; +} + +export interface ABIFuncParamless { + outputType: T; + encodeInput(): string; + decodeOutput(argStr: string): T; +} diff --git a/common/libs/ens/contracts/auction/auction.d.ts b/common/libs/ens/contracts/auction/auction.d.ts new file mode 100644 index 00000000..8980555f --- /dev/null +++ b/common/libs/ens/contracts/auction/auction.d.ts @@ -0,0 +1,49 @@ +import { ABIFunc, ABIFuncParamless } from '../AbiFunc'; +export interface IAuction { + releaseDeed: ABIFunc<{ _hash: bytes32 }>; + getAllowedTime: ABIFunc<{ _hash: bytes32 }, { timestamp: uint256 }>; + invalidateName: ABIFunc<{ unhashedName: string }>; + shaBid: ABIFunc< + { hash: bytes32; owner: address; value: uint256; salt: bytes32 }, + { sealedBid: bytes32 } + >; + cancelBid: ABIFunc<{ bidder: address; seal: bytes32 }>; + entries: ABIFunc< + { _hash: bytes32 }, + { + mode: uint8; + deedAddress: address; + registrationDate: uint256; + value: uint256; + highestBid: uint256; + } + >; + ens: ABIFuncParamless<{ ensAddress: address }>; + unsealBid: ABIFunc<{ _hash: bytes32; _value: uint256; _salt: bytes32 }>; + transferRegistrars: ABIFunc<{ _hash: bytes32 }>; + sealedBids: ABIFunc<{ address_0: address; bytes32_1: bytes32 }, { deedAddress: address }>; + state: ABIFunc<{ _hash: bytes32 }, { state: uint8 }>; + transfer: ABIFunc<{ _hash: bytes32; newOwner: address }>; + isAllowed: ABIFunc<{ _hash: bytes32; _timestamp: uint256 }, { allowed: bool }>; + finalizeAuction: ABIFunc<{ _hash: bytes32 }>; + registryStarted: ABIFuncParamless<{ registryStartDate: uint256 }>; + launchLength: ABIFuncParamless<{ launchLength: uint32 }>; + newBid: ABIFunc<{ sealedBid: bytes32 }>; + eraseNode: ABIFunc<{ labels: bytes32[] }>; + startAuctions: ABIFunc<{ _hashes: bytes32[] }>; + acceptRegistrarTransfer: ABIFunc<{ + hash: bytes32; + deed: address; + registrationDate: uint256; + }>; + startAuction: ABIFunc<{ _hash: bytes32 }>; + rootNode: ABIFuncParamless<{ rootNode: bytes32 }>; + startAuctionsAndBid: ABIFunc<{ hashes: bytes32[]; sealedBid: bytes32 }>; +} + +type bytes32 = any; +type uint256 = any; +type address = any; +type uint8 = any; +type bool = boolean; +type uint32 = any; diff --git a/common/libs/ens/contracts/auction/auction.json b/common/libs/ens/contracts/auction/auction.json new file mode 100644 index 00000000..b22d24b0 --- /dev/null +++ b/common/libs/ens/contracts/auction/auction.json @@ -0,0 +1,550 @@ +[ + { + "constant": false, + "inputs": [ + { + "name": "_hash", + "type": "bytes32" + } + ], + "name": "releaseDeed", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_hash", + "type": "bytes32" + } + ], + "name": "getAllowedTime", + "outputs": [ + { + "name": "timestamp", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "unhashedName", + "type": "string" + } + ], + "name": "invalidateName", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "hash", + "type": "bytes32" + }, + { + "name": "owner", + "type": "address" + }, + { + "name": "value", + "type": "uint256" + }, + { + "name": "salt", + "type": "bytes32" + } + ], + "name": "shaBid", + "outputs": [ + { + "name": "sealedBid", + "type": "bytes32" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "bidder", + "type": "address" + }, + { + "name": "seal", + "type": "bytes32" + } + ], + "name": "cancelBid", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_hash", + "type": "bytes32" + } + ], + "name": "entries", + "outputs": [ + { + "name": "", + "type": "uint8" + }, + { + "name": "", + "type": "address" + }, + { + "name": "", + "type": "uint256" + }, + { + "name": "", + "type": "uint256" + }, + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "ens", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_hash", + "type": "bytes32" + }, + { + "name": "_value", + "type": "uint256" + }, + { + "name": "_salt", + "type": "bytes32" + } + ], + "name": "unsealBid", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_hash", + "type": "bytes32" + } + ], + "name": "transferRegistrars", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "address" + }, + { + "name": "", + "type": "bytes32" + } + ], + "name": "sealedBids", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_hash", + "type": "bytes32" + } + ], + "name": "state", + "outputs": [ + { + "name": "", + "type": "uint8" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_hash", + "type": "bytes32" + }, + { + "name": "newOwner", + "type": "address" + } + ], + "name": "transfer", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_hash", + "type": "bytes32" + }, + { + "name": "_timestamp", + "type": "uint256" + } + ], + "name": "isAllowed", + "outputs": [ + { + "name": "allowed", + "type": "bool" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_hash", + "type": "bytes32" + } + ], + "name": "finalizeAuction", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "registryStarted", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "launchLength", + "outputs": [ + { + "name": "", + "type": "uint32" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "sealedBid", + "type": "bytes32" + } + ], + "name": "newBid", + "outputs": [], + "payable": true, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "labels", + "type": "bytes32[]" + } + ], + "name": "eraseNode", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_hashes", + "type": "bytes32[]" + } + ], + "name": "startAuctions", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "hash", + "type": "bytes32" + }, + { + "name": "deed", + "type": "address" + }, + { + "name": "registrationDate", + "type": "uint256" + } + ], + "name": "acceptRegistrarTransfer", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_hash", + "type": "bytes32" + } + ], + "name": "startAuction", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "rootNode", + "outputs": [ + { + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "hashes", + "type": "bytes32[]" + }, + { + "name": "sealedBid", + "type": "bytes32" + } + ], + "name": "startAuctionsAndBid", + "outputs": [], + "payable": true, + "type": "function" + }, + { + "inputs": [ + { + "name": "_ens", + "type": "address" + }, + { + "name": "_rootNode", + "type": "bytes32" + }, + { + "name": "_startDate", + "type": "uint256" + } + ], + "payable": false, + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "hash", + "type": "bytes32" + }, + { + "indexed": false, + "name": "registrationDate", + "type": "uint256" + } + ], + "name": "AuctionStarted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "hash", + "type": "bytes32" + }, + { + "indexed": true, + "name": "bidder", + "type": "address" + }, + { + "indexed": false, + "name": "deposit", + "type": "uint256" + } + ], + "name": "NewBid", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "hash", + "type": "bytes32" + }, + { + "indexed": true, + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + }, + { + "indexed": false, + "name": "status", + "type": "uint8" + } + ], + "name": "BidRevealed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "hash", + "type": "bytes32" + }, + { + "indexed": true, + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + }, + { + "indexed": false, + "name": "registrationDate", + "type": "uint256" + } + ], + "name": "HashRegistered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "hash", + "type": "bytes32" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + } + ], + "name": "HashReleased", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "hash", + "type": "bytes32" + }, + { + "indexed": true, + "name": "name", + "type": "string" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + }, + { + "indexed": false, + "name": "registrationDate", + "type": "uint256" + } + ], + "name": "HashInvalidated", + "type": "event" + } +] diff --git a/common/libs/ens/contracts/auction/outputMappings.ts b/common/libs/ens/contracts/auction/outputMappings.ts new file mode 100644 index 00000000..bb0a8c2e --- /dev/null +++ b/common/libs/ens/contracts/auction/outputMappings.ts @@ -0,0 +1,9 @@ +export default { + ens: ['ensAddress'], + entries: ['mode', 'deedAddress', 'registrationDate', 'value', 'highestBid'], + sealedBids: ['deedAddress'], + state: ['state'], + registryStarted: ['registryStartDate'], + launchLength: ['launchLength'], + rootNode: ['rootNode'] +}; diff --git a/common/libs/ens/contracts/deed/deed.d.ts b/common/libs/ens/contracts/deed/deed.d.ts new file mode 100644 index 00000000..79ae4696 --- /dev/null +++ b/common/libs/ens/contracts/deed/deed.d.ts @@ -0,0 +1,14 @@ +import { ABIFunc, ABIFuncParamless } from '../AbiFunc'; + +export interface IDeed { + creationDate: ABIFuncParamless<{ creationDate: uint256 }>; + destroyDeed: ABIFuncParamless; + setOwner: ABIFunc<{ newOwner: address }>; + registrar: ABIFuncParamless<{ registrarAddress: address }>; + owner: ABIFuncParamless<{ ownerAddress: address }>; + closeDeed: ABIFunc<{ refundRatio: uint256 }>; + setRegistrar: ABIFunc<{ newRegistrar: address }>; + setBalance: ABIFunc<{ newValue: uint256 }>; +} +type uint256 = any; +type address = any; diff --git a/common/libs/ens/contracts/deed/deed.json b/common/libs/ens/contracts/deed/deed.json new file mode 100644 index 00000000..ac7ac0b6 --- /dev/null +++ b/common/libs/ens/contracts/deed/deed.json @@ -0,0 +1,98 @@ +[{ + "constant": true, + "inputs": [], + "name": "creationDate", + "outputs": [{ + "name": "", + "type": "uint256" + }], + "payable": false, + "type": "function" +}, { + "constant": false, + "inputs": [], + "name": "destroyDeed", + "outputs": [], + "payable": false, + "type": "function" +}, { + "constant": false, + "inputs": [{ + "name": "newOwner", + "type": "address" + }], + "name": "setOwner", + "outputs": [], + "payable": false, + "type": "function" +}, { + "constant": true, + "inputs": [], + "name": "registrar", + "outputs": [{ + "name": "", + "type": "address" + }], + "payable": false, + "type": "function" +}, { + "constant": true, + "inputs": [], + "name": "owner", + "outputs": [{ + "name": "", + "type": "address" + }], + "payable": false, + "type": "function" +}, { + "constant": false, + "inputs": [{ + "name": "refundRatio", + "type": "uint256" + }], + "name": "closeDeed", + "outputs": [], + "payable": false, + "type": "function" +}, { + "constant": false, + "inputs": [{ + "name": "newRegistrar", + "type": "address" + }], + "name": "setRegistrar", + "outputs": [], + "payable": false, + "type": "function" +}, { + "constant": false, + "inputs": [{ + "name": "newValue", + "type": "uint256" + }], + "name": "setBalance", + "outputs": [], + "payable": true, + "type": "function" +}, { + "inputs": [], + "type": "constructor" +}, { + "payable": true, + "type": "fallback" +}, { + "anonymous": false, + "inputs": [{ + "indexed": false, + "name": "newOwner", + "type": "address" + }], + "name": "OwnerChanged", + "type": "event" +}, { + "anonymous": false, + "inputs": [], + "name": "DeedClosed", + "type": "event" +}] \ No newline at end of file diff --git a/common/libs/ens/contracts/deed/outputMappings.ts b/common/libs/ens/contracts/deed/outputMappings.ts new file mode 100644 index 00000000..03a1c574 --- /dev/null +++ b/common/libs/ens/contracts/deed/outputMappings.ts @@ -0,0 +1,5 @@ +export default { + creationDate: ['creationDate'], + registrar: ['registrarAddress'], + owner: ['ownerAddress'] +}; diff --git a/common/libs/ens/contracts/index.ts b/common/libs/ens/contracts/index.ts new file mode 100644 index 00000000..30b1249d --- /dev/null +++ b/common/libs/ens/contracts/index.ts @@ -0,0 +1,26 @@ +import Contract from 'libs/contracts'; +const auctionABI = require('./auction/auction.json'); +import auctionOutputMappings from './auction/outputMappings'; +import { IAuction } from './auction/auction'; + +const deedABI = require('./deed/deed.json'); +import deedOutputMappings from './deed/outputMappings'; +import { IDeed } from './deed/deed'; + +const registryABI = require('./registry/registry.json'); +import registryOutputMappings from './registry/outputMappings'; +import { IRegistry } from './registry/registry'; + +const resolverABI = require('./resolver/resolver.json'); +import resolverOutputMappings from './resolver/outputMappings'; +import { IResolver } from './resolver/resolver'; + +const auction: IAuction & Contract = new Contract(auctionABI, auctionOutputMappings) as any; + +const deed: IDeed & Contract = new Contract(deedABI, deedOutputMappings) as any; + +const registry: IRegistry & Contract = new Contract(registryABI, registryOutputMappings) as any; + +const resolver: IResolver & Contract = new Contract(resolverABI, resolverOutputMappings) as any; + +export default { auction, deed, registry, resolver }; diff --git a/common/libs/ens/contracts/registry/outputMappings.ts b/common/libs/ens/contracts/registry/outputMappings.ts new file mode 100644 index 00000000..a379d56a --- /dev/null +++ b/common/libs/ens/contracts/registry/outputMappings.ts @@ -0,0 +1,5 @@ +export default { + resolver: ['resolverAddress'], + owner: ['ownerAddress'], + ttl: ['timeToLive'] +}; diff --git a/common/libs/ens/contracts/registry/registry.d.ts b/common/libs/ens/contracts/registry/registry.d.ts new file mode 100644 index 00000000..8f7bd67c --- /dev/null +++ b/common/libs/ens/contracts/registry/registry.d.ts @@ -0,0 +1,15 @@ +import { ABIFunc, ABIFuncParamless } from '../AbiFunc'; + +export interface IRegistry { + resolver: ABIFunc<{ node: bytes32 }, { resolverAddress: address }>; + owner: ABIFunc<{ node: bytes32 }, { ownerAddress: address }>; + setSubnodeOwner: ABIFunc<{ node: bytes32; label: bytes32; owner: address }>; + setTTL: ABIFunc<{ node: bytes32; ttl: uint64 }>; + ttl: ABIFunc<{ node: bytes32 }, { timeToLive: uint64 }>; + setResolver: ABIFunc<{ node: bytes32; resolver: address }>; + setOwner: ABIFunc<{ node: bytes32; owner: address }>; +} + +type bytes32 = any; +type address = any; +type uint64 = any; diff --git a/common/libs/ens/contracts/registry/registry.json b/common/libs/ens/contracts/registry/registry.json new file mode 100644 index 00000000..cf3066a3 --- /dev/null +++ b/common/libs/ens/contracts/registry/registry.json @@ -0,0 +1,151 @@ +[{ + "constant": true, + "inputs": [{ + "name": "node", + "type": "bytes32" + }], + "name": "resolver", + "outputs": [{ + "name": "", + "type": "address" + }], + "payable": false, + "type": "function" +}, { + "constant": true, + "inputs": [{ + "name": "node", + "type": "bytes32" + }], + "name": "owner", + "outputs": [{ + "name": "", + "type": "address" + }], + "payable": false, + "type": "function" +}, { + "constant": false, + "inputs": [{ + "name": "node", + "type": "bytes32" + }, { + "name": "label", + "type": "bytes32" + }, { + "name": "owner", + "type": "address" + }], + "name": "setSubnodeOwner", + "outputs": [], + "payable": false, + "type": "function" +}, { + "constant": false, + "inputs": [{ + "name": "node", + "type": "bytes32" + }, { + "name": "ttl", + "type": "uint64" + }], + "name": "setTTL", + "outputs": [], + "payable": false, + "type": "function" +}, { + "constant": true, + "inputs": [{ + "name": "node", + "type": "bytes32" + }], + "name": "ttl", + "outputs": [{ + "name": "", + "type": "uint64" + }], + "payable": false, + "type": "function" +}, { + "constant": false, + "inputs": [{ + "name": "node", + "type": "bytes32" + }, { + "name": "resolver", + "type": "address" + }], + "name": "setResolver", + "outputs": [], + "payable": false, + "type": "function" +}, { + "constant": false, + "inputs": [{ + "name": "node", + "type": "bytes32" + }, { + "name": "owner", + "type": "address" + }], + "name": "setOwner", + "outputs": [], + "payable": false, + "type": "function" +}, { + "anonymous": false, + "inputs": [{ + "indexed": true, + "name": "node", + "type": "bytes32" + }, { + "indexed": false, + "name": "owner", + "type": "address" + }], + "name": "Transfer", + "type": "event" +}, { + "anonymous": false, + "inputs": [{ + "indexed": true, + "name": "node", + "type": "bytes32" + }, { + "indexed": true, + "name": "label", + "type": "bytes32" + }, { + "indexed": false, + "name": "owner", + "type": "address" + }], + "name": "NewOwner", + "type": "event" +}, { + "anonymous": false, + "inputs": [{ + "indexed": true, + "name": "node", + "type": "bytes32" + }, { + "indexed": false, + "name": "resolver", + "type": "address" + }], + "name": "NewResolver", + "type": "event" +}, { + "anonymous": false, + "inputs": [{ + "indexed": true, + "name": "node", + "type": "bytes32" + }, { + "indexed": false, + "name": "ttl", + "type": "uint64" + }], + "name": "NewTTL", + "type": "event" +}] diff --git a/common/libs/ens/contracts/resolver/outputMappings.ts b/common/libs/ens/contracts/resolver/outputMappings.ts new file mode 100644 index 00000000..3fe9e0a3 --- /dev/null +++ b/common/libs/ens/contracts/resolver/outputMappings.ts @@ -0,0 +1,4 @@ +export default { + supportsInterface: ['doesSupportInterface'], + has: ['has'] +}; diff --git a/common/libs/ens/contracts/resolver/resolver.d.ts b/common/libs/ens/contracts/resolver/resolver.d.ts new file mode 100644 index 00000000..15e231e1 --- /dev/null +++ b/common/libs/ens/contracts/resolver/resolver.d.ts @@ -0,0 +1,15 @@ +import { ABIFunc, ABIFuncParamless } from '../AbiFunc'; + +export interface IResolver { + supportsInterface: ABIFunc<{ interfaceID: bytes4 }, { doesSupportInterface: bool }>; + addr: ABIFunc<{ node: bytes32 }, { ret: address }>; + has: ABIFunc<{ node: bytes32; kind: bytes32 }, { has: bool }>; + setAddr: ABIFunc<{ node: bytes32; addr: address }>; + content: ABIFunc<{ node: bytes32 }, { ret: bytes32 }>; + setContent: ABIFunc<{ node: bytes32; hash: bytes32 }>; +} + +type bytes4 = any; +type bool = boolean; +type bytes32 = any; +type address = any; diff --git a/common/libs/ens/contracts/resolver/resolver.json b/common/libs/ens/contracts/resolver/resolver.json new file mode 100644 index 00000000..415eda43 --- /dev/null +++ b/common/libs/ens/contracts/resolver/resolver.json @@ -0,0 +1,91 @@ +[{ + "constant": true, + "inputs": [{ + "name": "interfaceID", + "type": "bytes4" + }], + "name": "supportsInterface", + "outputs": [{ + "name": "", + "type": "bool" + }], + "payable": false, + "type": "function" +}, { + "constant": true, + "inputs": [{ + "name": "node", + "type": "bytes32" + }], + "name": "addr", + "outputs": [{ + "name": "ret", + "type": "address" + }], + "payable": false, + "type": "function" +}, { + "constant": true, + "inputs": [{ + "name": "node", + "type": "bytes32" + }, { + "name": "kind", + "type": "bytes32" + }], + "name": "has", + "outputs": [{ + "name": "", + "type": "bool" + }], + "payable": false, + "type": "function" +}, { + "constant": false, + "inputs": [{ + "name": "node", + "type": "bytes32" + }, { + "name": "addr", + "type": "address" + }], + "name": "setAddr", + "outputs": [], + "payable": false, + "type": "function" +}, { + "constant": true, + "inputs": [{ + "name": "node", + "type": "bytes32" + }], + "name": "content", + "outputs": [{ + "name": "ret", + "type": "bytes32" + }], + "payable": false, + "type": "function" +}, { + "constant": false, + "inputs": [{ + "name": "node", + "type": "bytes32" + }, { + "name": "hash", + "type": "bytes32" + }], + "name": "setContent", + "outputs": [], + "payable": false, + "type": "function" +}, { + "inputs": [{ + "name": "ensAddr", + "type": "address" + }], + "type": "constructor" +}, { + "payable": false, + "type": "fallback" +}] diff --git a/common/libs/ens/index.ts b/common/libs/ens/index.ts new file mode 100644 index 00000000..3e1f8aba --- /dev/null +++ b/common/libs/ens/index.ts @@ -0,0 +1,86 @@ +import uts46 from 'idna-uts46'; +import ethUtil from 'ethereumjs-util'; + +export function normalise(name: string) { + try { + return uts46.toUnicode(name, { useStd3ASCII: true, transitional: false }); + } catch (e) { + throw e; + } +} + +export const getNameHash = (name: string = ''): string => { + if (name === '') { + throw new Error('Empty string provided'); + } + + const normalizedName = normalise(name); + const sha3 = ethUtil.sha3; + const labels = normalizedName.split('.'); + const emptyNode = Buffer.alloc(32); + const rawNode = labels.reduceRight((node, currentLabel) => { + return sha3(Buffer.concat([node, sha3(currentLabel)])); + }, emptyNode); + + return `0x${rawNode.toString('hex')}`; +}; + +export interface IBaseDomainRequest { + name: string; + labelHash: string; + mode: NameState; + highestBid: string; + value: string; + deedAddress: string; + registrationDate: string; + nameHash: string; + mappedMode: string; +} + +export interface IOwnedDomainRequest extends IBaseDomainRequest { + ownerAddress: string; + resolvedAddress: string; +} + +export interface IRevealDomainRequest extends IBaseDomainRequest { + ownerAddress: string; +} + +export type DomainRequest = IOwnedDomainRequest | IRevealDomainRequest | IBaseDomainRequest; + +export interface IDomainData { + mode: Mode; + deedAddress: string; + registrationDate: string; + value: string; + highestBid: string; +} + +export enum NameState { + Open = '0', + Auction = '1', + Owned = '2', + Forbidden = '3', + Reveal = '4', + NotYetAvailable = '5' +} + +export const modeStrMap = name => [ + `${name} is available and the auction hasn’t started`, + `${name} is available and the auction has been started`, + `${name} is taken and currently owned by someone`, + `${name} is forbidden`, + `${name} is currently in the ‘reveal’ stage of the auction`, + `${name} is not yet available due to the ‘soft launch’ of names.` +]; + +export interface IModeMap { + [x: string]: ( + domainData: IDomainData, + nameHash?: string, + hash?: Buffer + ) => + | {} + | { ownerAddress: string; resolvedAddress: string } + | { auctionCloseTime: string; revealBidTime: string }; +} diff --git a/common/libs/ens/networkConfigs/index.ts b/common/libs/ens/networkConfigs/index.ts new file mode 100644 index 00000000..e909f6b8 --- /dev/null +++ b/common/libs/ens/networkConfigs/index.ts @@ -0,0 +1,15 @@ +const main: IEnsAddresses = require('./main.json'); +const rinkeby: IEnsAddresses = require('./rinkeby.json'); +const ropsten: IEnsAddresses = require('./ropsten.json'); + +interface IEnsAddresses { + public: { + resolver: string; + reverse: string; + ethAuction: string; + }; + + registry: string; +} + +export default { main, rinkeby, ropsten }; diff --git a/common/libs/ens/networkConfigs/main.json b/common/libs/ens/networkConfigs/main.json new file mode 100644 index 00000000..5b2c4341 --- /dev/null +++ b/common/libs/ens/networkConfigs/main.json @@ -0,0 +1,8 @@ +{ + "public": { + "resolver": "0x5FfC014343cd971B7eb70732021E26C35B744cc4", + "reverse": "0x9062c0a6dbd6108336bcbe4593a3d1ce05512069", + "ethAuction": "0x6090a6e47849629b7245dfa1ca21d94cd15878ef" + }, + "registry": "0x314159265dD8dbb310642f98f50C066173C1259b" +} diff --git a/common/libs/ens/networkConfigs/rinkeby.json b/common/libs/ens/networkConfigs/rinkeby.json new file mode 100644 index 00000000..c64ee1bc --- /dev/null +++ b/common/libs/ens/networkConfigs/rinkeby.json @@ -0,0 +1,8 @@ +{ + "public": { + "resolver": "0xb14fdee4391732ea9d2267054ead2084684c0ad8", + "reverse": "0x0000000000000000000000000000000000000000", + "ethAuction": "0x0000000000000000000000000000000000000000" + }, + "registry": "0xe7410170f87102df0055eb195163a03b7f2bff4a" +} diff --git a/common/libs/ens/networkConfigs/ropsten.json b/common/libs/ens/networkConfigs/ropsten.json new file mode 100644 index 00000000..e2dea24e --- /dev/null +++ b/common/libs/ens/networkConfigs/ropsten.json @@ -0,0 +1,8 @@ +{ + "public": { + "resolver": "0x4c641fb9bad9b60ef180c31f56051ce826d21a9a", + "reverse": "0xdb6cead81ce14a63c284728eed17738a81327ff0", + "ethAuction": "0xc19fd9004b5c9789391679de6d766b981db94610" + }, + "registry": "0x112234455c3a32fd11230c42e7bccd4a84e02010" +} diff --git a/common/libs/validators.ts b/common/libs/validators.ts index 913c23af..f7eb844f 100644 --- a/common/libs/validators.ts +++ b/common/libs/validators.ts @@ -22,6 +22,9 @@ export function isValidETHAddress(address: string): boolean { } } +export const isCreationAddress = (address: string): boolean => + address === '0x0' || address === '0x0000000000000000000000000000000000000000'; + export function isValidBTCAddress(address: string): boolean { return WalletAddressValidator.validate(address, 'BTC'); } diff --git a/common/reducers/ens.ts b/common/reducers/ens.ts deleted file mode 100644 index b225b150..00000000 --- a/common/reducers/ens.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { CacheEnsAddressAction, EnsAction } from 'actions/ens'; - -export interface State { - [key: string]: string; -} - -export const INITIAL_STATE: State = {}; - -function cacheEnsAddress(state: State, action: CacheEnsAddressAction): State { - const { ensName, address } = action.payload; - return { ...state, [ensName]: address }; -} - -export function ens(state: State = INITIAL_STATE, action: EnsAction): State { - switch (action.type) { - case 'ENS_CACHE': - return cacheEnsAddress(state, action); - default: - return state; - } -} diff --git a/common/reducers/ens/domainRequests.ts b/common/reducers/ens/domainRequests.ts new file mode 100644 index 00000000..efd9226b --- /dev/null +++ b/common/reducers/ens/domainRequests.ts @@ -0,0 +1,82 @@ +import { + EnsAction, + ResolveDomainRequested, + ResolveDomainFailed, + ResolveDomainSucceeded, + ResolveDomainCached +} from 'actions/ens'; +import { DomainRequest } from 'libs/ens'; +import { TypeKeys } from 'actions/ens/constants'; + +export interface State { + [key: string]: { + state: REQUEST_STATES; + data?: DomainRequest; + error?: boolean; + errorMsg?: string; + }; +} + +const INITIAL_STATE: State = {}; + +export enum REQUEST_STATES { + pending = 'PENDING', + success = 'SUCCESS', + failed = 'FAILED' +} + +const resolveDomainRequested = (state: State, action: ResolveDomainRequested): State => { + const { domain } = action.payload; + const nextDomain = { + ...state[domain], + state: REQUEST_STATES.pending + }; + + return { ...state, [domain]: nextDomain }; +}; + +const resolveDomainSuccess = (state: State, action: ResolveDomainSucceeded): State => { + const { domain, domainData } = action.payload; + const nextDomain = { + data: domainData, + state: REQUEST_STATES.success + }; + + return { ...state, [domain]: nextDomain }; +}; + +const resolveDomainCached = (state: State, action: ResolveDomainCached): State => { + const { domain } = action.payload; + const nextDomain = { + ...state[domain], + state: REQUEST_STATES.success + }; + + return { ...state, [domain]: nextDomain }; +}; + +const resolveDomainFailed = (state: State, action: ResolveDomainFailed): State => { + const { domain, error } = action.payload; + const nextDomain = { + error: true, + errorMsg: error.message, + state: REQUEST_STATES.failed + }; + + return { ...state, [domain]: nextDomain }; +}; + +export default (state: State = INITIAL_STATE, action: EnsAction): State => { + switch (action.type) { + case TypeKeys.ENS_RESOLVE_DOMAIN_REQUESTED: + return resolveDomainRequested(state, action); + case TypeKeys.ENS_RESOLVE_DOMAIN_SUCCEEDED: + return resolveDomainSuccess(state, action); + case TypeKeys.ENS_RESOLVE_DOMAIN_FAILED: + return resolveDomainFailed(state, action); + case TypeKeys.ENS_RESOLVE_DOMAIN_CACHED: + return resolveDomainCached(state, action); + default: + return state; + } +}; diff --git a/common/reducers/ens/domainSelector.ts b/common/reducers/ens/domainSelector.ts new file mode 100644 index 00000000..5c59b8e1 --- /dev/null +++ b/common/reducers/ens/domainSelector.ts @@ -0,0 +1,40 @@ +import { + EnsAction, + ResolveDomainSucceeded, + ResolveDomainCached, + ResolveDomainRequested +} from 'actions/ens'; +import { TypeKeys } from 'actions/ens/constants'; + +export interface State { + currentDomain: null | string; +} + +const INITIAL_STATE: State = { + currentDomain: null +}; + +const setCurrentDomainName = ( + state: State, + action: ResolveDomainSucceeded | ResolveDomainCached | ResolveDomainRequested +): State => { + const { domain: domainName } = action.payload; + return { ...state, currentDomain: domainName }; +}; + +const clearCurrentDomainName = (): State => { + return { currentDomain: null }; +}; + +export default (state: State = INITIAL_STATE, action: EnsAction): State => { + switch (action.type) { + case TypeKeys.ENS_RESOLVE_DOMAIN_CACHED: + case TypeKeys.ENS_RESOLVE_DOMAIN_REQUESTED: + case TypeKeys.ENS_RESOLVE_DOMAIN_SUCCEEDED: + return setCurrentDomainName(state, action); + case TypeKeys.ENS_RESOLVE_DOMAIN_FAILED: + return clearCurrentDomainName(); + default: + return state; + } +}; diff --git a/common/reducers/ens/index.ts b/common/reducers/ens/index.ts new file mode 100644 index 00000000..7f2ff3ed --- /dev/null +++ b/common/reducers/ens/index.ts @@ -0,0 +1,11 @@ +import domainSelector, { State as DSState } from './domainSelector'; +import domainRequests, { State as DRState } from './domainRequests'; + +import { combineReducers } from 'redux'; + +export interface State { + domainSelector: DSState; + domainRequests: DRState; +} + +export const ens = combineReducers({ domainSelector, domainRequests }); diff --git a/common/reducers/transaction/fields/fields.ts b/common/reducers/transaction/fields/fields.ts index 67d63402..9cb9c4d0 100644 --- a/common/reducers/transaction/fields/fields.ts +++ b/common/reducers/transaction/fields/fields.ts @@ -13,7 +13,7 @@ import { State } from './typings'; import { gasPricetoBase } from 'libs/units'; const INITIAL_STATE: State = { - to: { raw: '', value: null, error: null }, + to: { raw: '', value: null }, data: { raw: '', value: null }, nonce: { raw: '', value: null }, value: { raw: '', value: null }, diff --git a/common/reducers/transaction/meta/meta.ts b/common/reducers/transaction/meta/meta.ts index 045c6acb..57a1e0bf 100644 --- a/common/reducers/transaction/meta/meta.ts +++ b/common/reducers/transaction/meta/meta.ts @@ -18,7 +18,7 @@ const INITIAL_STATE: State = { previousUnit: 'ether', decimal: getDecimalFromEtherUnit('ether'), tokenValue: { raw: '', value: null }, - tokenTo: { raw: '', value: null, error: null }, + tokenTo: { raw: '', value: null }, from: null }; diff --git a/common/sagas/ens/ens.ts b/common/sagas/ens/ens.ts new file mode 100644 index 00000000..a0039584 --- /dev/null +++ b/common/sagas/ens/ens.ts @@ -0,0 +1,68 @@ +import { + resolveDomainFailed, + resolveDomainSucceeded, + ResolveDomainRequested, + resolveDomainCached +} from 'actions/ens'; +import { TypeKeys } from 'actions/ens/constants'; +import { SagaIterator, delay, buffers } from 'redux-saga'; +import { INode } from 'libs/nodes/INode'; +import { getNodeLib } from 'selectors/config'; +import { call, put, select, all, actionChannel, take, fork, race } from 'redux-saga/effects'; +import { showNotification } from 'actions/notifications'; +import { resolveDomainRequest } from './modeMap'; +import { getCurrentDomainName, getCurrentDomainData } from 'selectors/ens'; + +function* shouldResolveDomain(domain: string) { + const currentDomainName = yield select(getCurrentDomainName); + if (currentDomainName === domain) { + const currentDomainData = yield select(getCurrentDomainData); + if (currentDomainData) { + return false; + } + } + return true; +} + +function* resolveDomain(): SagaIterator { + const requestChan = yield actionChannel( + TypeKeys.ENS_RESOLVE_DOMAIN_REQUESTED, + buffers.sliding(1) + ); + + while (true) { + const { payload }: ResolveDomainRequested = yield take(requestChan); + + const { domain } = payload; + + try { + const shouldResolve = yield call(shouldResolveDomain, domain); + if (!shouldResolve) { + yield put(resolveDomainCached({ domain })); + continue; + } + + const node: INode = yield select(getNodeLib); + const result = yield race({ + domainData: call(resolveDomainRequest, domain, node), + err: call(delay, 4000) + }); + + const { domainData } = result; + if (!domainData) { + throw Error(); + } + const domainSuccessAction = resolveDomainSucceeded(domain, domainData); + yield put(domainSuccessAction); + yield; + } catch (e) { + const domainFailAction = resolveDomainFailed(domain, e); + yield put(domainFailAction); + yield put(showNotification('danger', e.message || 'Could not resolve ENS address', 5000)); + } + } +} + +export function* ens(): SagaIterator { + yield all([fork(resolveDomain)]); +} diff --git a/common/sagas/ens/helpers.ts b/common/sagas/ens/helpers.ts new file mode 100644 index 00000000..8024ee89 --- /dev/null +++ b/common/sagas/ens/helpers.ts @@ -0,0 +1,11 @@ +import { select, apply, call } from 'redux-saga/effects'; +import { INode } from 'libs/nodes/INode'; +import { getNodeLib } from 'selectors/config'; +import { SagaIterator } from 'redux-saga'; + +export function* makeEthCallAndDecode({ to, data, decoder }): SagaIterator { + const node: INode = yield select(getNodeLib); + const result: string = yield apply(node, node.sendCallRequest, [{ data, to }]); + const decodedResult = yield call(decoder, result); + return decodedResult; +} diff --git a/common/sagas/ens/index.ts b/common/sagas/ens/index.ts new file mode 100644 index 00000000..d916e47d --- /dev/null +++ b/common/sagas/ens/index.ts @@ -0,0 +1 @@ +export * from './ens'; diff --git a/common/sagas/ens/modeMap.ts b/common/sagas/ens/modeMap.ts new file mode 100644 index 00000000..12f6e5d6 --- /dev/null +++ b/common/sagas/ens/modeMap.ts @@ -0,0 +1,93 @@ +import { IDomainData, NameState, getNameHash } from 'libs/ens'; +import ENS from 'libs/ens/contracts'; +import { SagaIterator } from 'redux-saga'; +import { call } from 'redux-saga/effects'; +import networkConfigs from 'libs/ens/networkConfigs'; +import { makeEthCallAndDecode } from 'sagas/ens/helpers'; +import ethUtil from 'ethereumjs-util'; + +const { main } = networkConfigs; + +function* nameStateOwned({ deedAddress }: IDomainData, nameHash: string) { + // Return the owner's address, and the resolved address if it exists + const { ownerAddress }: typeof ENS.deed.owner.outputType = yield call(makeEthCallAndDecode, { + to: deedAddress, + data: ENS.deed.owner.encodeInput(), + decoder: ENS.deed.owner.decodeOutput + }); + + const { resolverAddress }: typeof ENS.registry.resolver.outputType = yield call( + makeEthCallAndDecode, + { + to: main.registry, + decoder: ENS.registry.resolver.decodeOutput, + data: ENS.registry.resolver.encodeInput({ + node: nameHash + }) + } + ); + + let resolvedAddress = '0x0'; + + if (resolverAddress !== '0x0') { + const result: typeof ENS.resolver.addr.outputType = yield call(makeEthCallAndDecode, { + to: resolverAddress, + data: ENS.resolver.addr.encodeInput({ node: nameHash }), + decoder: ENS.resolver.addr.decodeOutput + }); + + resolvedAddress = result.ret; + } + + return { ownerAddress, resolvedAddress }; +} + +function* nameStateReveal({ deedAddress }: IDomainData): SagaIterator { + const { ownerAddress }: typeof ENS.deed.owner.outputType = yield call(makeEthCallAndDecode, { + to: deedAddress, + data: ENS.deed.owner.encodeInput(), + decoder: ENS.deed.owner.decodeOutput + }); + return ownerAddress; +} + +interface IModeMap { + [x: string]: ( + domainData: IDomainData, + nameHash?: string, + hash?: Buffer + ) => + | {} + | { ownerAddress: string; resolvedAddress: string } + | { auctionCloseTime: string; revealBidTime: string }; +} + +const modeMap: IModeMap = { + [NameState.Open]: (_: IDomainData) => ({}), + [NameState.Auction]: (_: IDomainData) => ({}), + [NameState.Owned]: nameStateOwned, + [NameState.Forbidden]: (_: IDomainData) => ({}), + [NameState.Reveal]: nameStateReveal, + [NameState.NotYetAvailable]: (_: IDomainData) => ({}) +}; + +export function* resolveDomainRequest(name: string): SagaIterator { + const hash = ethUtil.sha3(name); + const nameHash = getNameHash(`${name}.eth`); + + const domainData: typeof ENS.auction.entries.outputType = yield call(makeEthCallAndDecode, { + to: main.public.ethAuction, + data: ENS.auction.entries.encodeInput({ _hash: hash }), + decoder: ENS.auction.entries.decodeOutput + }); + const nameStateHandler = modeMap[domainData.mode]; + const result = yield call(nameStateHandler, domainData, nameHash); + + return { + name, + ...domainData, + ...result, + labelHash: hash.toString('hex'), + nameHash + }; +} diff --git a/common/sagas/index.ts b/common/sagas/index.ts index 812360aa..3bc3e5a9 100644 --- a/common/sagas/index.ts +++ b/common/sagas/index.ts @@ -12,9 +12,11 @@ import { import { liteSend } from './swap/liteSend'; import { getBityRatesSaga, getShapeShiftRatesSaga, swapProviderSaga } from './swap/rates'; import wallet from './wallet'; +import { ens } from './ens'; import { transaction } from './transaction'; export default { + ens, liteSend, configSaga, postBityOrderSaga, diff --git a/common/sagas/transaction/current/currentTo.ts b/common/sagas/transaction/current/currentTo.ts index 4bc805f7..4717c141 100644 --- a/common/sagas/transaction/current/currentTo.ts +++ b/common/sagas/transaction/current/currentTo.ts @@ -3,26 +3,43 @@ import { SetCurrentToAction } from 'actions/transaction/actionTypes/current'; import { setToField } from 'actions/transaction/actionCreators/fields'; import { setTokenTo } from 'actions/transaction/actionCreators/meta'; import { Address } from 'libs/units'; -import { select, call, put, takeLatest } from 'redux-saga/effects'; +import { select, call, put, takeLatest, take } from 'redux-saga/effects'; import { SagaIterator } from 'redux-saga'; import { isValidENSAddress, isValidETHAddress } from 'libs/validators'; import { TypeKeys } from 'actions/transaction/constants'; +import { getResolvedAddress } from 'selectors/ens'; +import { resolveDomainRequested, TypeKeys as ENSTypekeys } from 'actions/ens'; +import { SetToFieldAction, SetTokenToMetaAction } from 'actions/transaction'; export function* setCurrentTo({ payload: raw }: SetCurrentToAction): SagaIterator { const validAddress: boolean = yield call(isValidETHAddress, raw); const validEns: boolean = yield call(isValidENSAddress, raw); - const etherTransaction: boolean = yield select(isEtherTransaction); let value: Buffer | null = null; - let error: string | null = null; if (validAddress) { value = Address(raw); } else if (validEns) { - // TODO: Resolve ENS on networks that support it, error on ones that don't - error = 'ENS is not supported yet'; + yield call(setField, { value, raw }); + + const [domain] = raw.split('.'); + yield put(resolveDomainRequested(domain)); + yield take([ + ENSTypekeys.ENS_RESOLVE_DOMAIN_FAILED, + ENSTypekeys.ENS_RESOLVE_DOMAIN_SUCCEEDED, + ENSTypekeys.ENS_RESOLVE_DOMAIN_CACHED + ]); + const resolvedAddress: string | null = yield select(getResolvedAddress, true); + if (resolvedAddress) { + value = Address(resolvedAddress); + } } - const payload = { raw, value, error }; + yield call(setField, { value, raw }); +} + +export function* setField(payload: SetToFieldAction['payload'] | SetTokenToMetaAction['payload']) { + const etherTransaction: boolean = yield select(isEtherTransaction); + if (etherTransaction) { yield put(setToField(payload)); } else { diff --git a/common/sass/variables/tables.scss b/common/sass/variables/tables.scss index 1e11198e..da749cf7 100644 --- a/common/sass/variables/tables.scss +++ b/common/sass/variables/tables.scss @@ -1,7 +1,8 @@ $table-cell-padding: $space-sm; $table-condensed-cell-padding: $space-xs; $table-bg: transparent; -$table-bg-accent: #f9f9f9; +$table-bg-accent: #f5f5f5; $table-bg-hover: $gray-lightest; $table-bg-active: $table-bg-hover; -$table-border-color: #ddd; +$table-border-color: transparent; +$table-cell-padding: 0.75rem 1rem; diff --git a/common/selectors/ens.ts b/common/selectors/ens.ts index 3cad6c3a..24b27904 100644 --- a/common/selectors/ens.ts +++ b/common/selectors/ens.ts @@ -1,5 +1,54 @@ import { AppState } from 'reducers'; +import { IOwnedDomainRequest, IBaseDomainRequest } from 'libs/ens'; +import { REQUEST_STATES } from 'reducers/ens/domainRequests'; +import { isCreationAddress } from 'libs/validators'; -export function getEnsAddress(state: AppState, ensName: string): null | string { - return state.ens[ensName]; -} +export const getEns = (state: AppState) => state.ens; + +export const getCurrentDomainName = (state: AppState) => getEns(state).domainSelector.currentDomain; + +export const getDomainRequests = (state: AppState) => getEns(state).domainRequests; + +export const getCurrentDomainData = (state: AppState) => { + const currentDomain = getCurrentDomainName(state); + const domainRequests = getDomainRequests(state); + + if (!currentDomain || !domainRequests[currentDomain] || domainRequests[currentDomain].error) { + return null; + } + + const domainData = domainRequests[currentDomain].data || null; + + return domainData; +}; + +export const getResolvedAddress = (state: AppState, noGenesisAddress: boolean = false) => { + const data = getCurrentDomainData(state); + if (!data) { + return null; + } + + if (isOwned(data)) { + const { resolvedAddress } = data; + if (noGenesisAddress) { + return !isCreationAddress(resolvedAddress) ? resolvedAddress : null; + } + return data.resolvedAddress; + } + return null; +}; + +export const getResolvingDomain = (state: AppState) => { + const currentDomain = getCurrentDomainName(state); + const domainRequests = getDomainRequests(state); + + if (!currentDomain || !domainRequests[currentDomain]) { + return null; + } + + return domainRequests[currentDomain].state === REQUEST_STATES.pending; +}; + +const isOwned = (data: IBaseDomainRequest): data is IOwnedDomainRequest => { + return !!(data as IOwnedDomainRequest).ownerAddress; +}; diff --git a/common/selectors/transaction/current.ts b/common/selectors/transaction/current.ts index 071c5254..94abccc6 100644 --- a/common/selectors/transaction/current.ts +++ b/common/selectors/transaction/current.ts @@ -13,7 +13,6 @@ interface ICurrentValue { interface ICurrentTo { raw: string; value: Address | null; - error?: string | null; } const isEtherTransaction = (state: AppState) => { diff --git a/common/typescript/bn.d.ts b/common/typescript/bn.d.ts index 550bca64..2ad10986 100644 --- a/common/typescript/bn.d.ts +++ b/common/typescript/bn.d.ts @@ -369,7 +369,7 @@ declare module 'bn.js' { * @description reduct */ - modn(b: number): BN; + modn(b: number): number; //API consistency https://github.com/indutny/bn.js/pull/130 /** * @description rounded division diff --git a/spec/libs/ens.spec.ts b/spec/libs/ens.spec.ts new file mode 100644 index 00000000..63821fe4 --- /dev/null +++ b/spec/libs/ens.spec.ts @@ -0,0 +1,21 @@ +import * as ens from 'libs/ens'; + +// TODO: write tests for: +// ens.placeBid +// ens.unsealBid +// ens.resolveDomainRequest + +describe('ENS', () => { + it('converts a domain name to a normalized Unicode', () => { + const data = ens.normalise('xn--s-qfa0g.de'); + expect(data).toBe('süß.de'); + }); + + it('converts a string to hexacedimal', () => { + const unicodeToHash = ens.getNameHash('Süß.de'); + const asciiToHash = ens.getNameHash('xn--s-qfa0g.de'); + expect(unicodeToHash && asciiToHash).toBe( + '0x26eb2a1d5e19a5d10e4a0001e7f3b22366f27d7203c6985b6b41fe65be107f8b' + ); + }); +}); diff --git a/spec/reducers/customTokens.spec.ts b/spec/reducers/customTokens.spec.ts index 02e08743..2b597e98 100644 --- a/spec/reducers/customTokens.spec.ts +++ b/spec/reducers/customTokens.spec.ts @@ -15,23 +15,15 @@ describe('customTokens reducer', () => { }; it('should handle CUSTOM_TOKEN_ADD', () => { - expect( - customTokens(undefined, customTokensActions.addCustomToken(token1)) - ).toEqual([token1]); + expect(customTokens(undefined, customTokensActions.addCustomToken(token1))).toEqual([token1]); }); it('should handle CUSTOM_TOKEN_REMOVE', () => { - const state1 = customTokens( - undefined, - customTokensActions.addCustomToken(token1) - ); - const state2 = customTokens( - state1, - customTokensActions.addCustomToken(token2) - ); + const state1 = customTokens(undefined, customTokensActions.addCustomToken(token1)); + const state2 = customTokens(state1, customTokensActions.addCustomToken(token2)); - expect( - customTokens(state2, customTokensActions.removeCustomToken(token2.symbol)) - ).toEqual([token1]); + expect(customTokens(state2, customTokensActions.removeCustomToken(token2.symbol))).toEqual([ + token1 + ]); }); }); diff --git a/spec/reducers/deterministicWallets.spec.ts b/spec/reducers/deterministicWallets.spec.ts index 90ebbffe..cc40c4d6 100644 --- a/spec/reducers/deterministicWallets.spec.ts +++ b/spec/reducers/deterministicWallets.spec.ts @@ -1,7 +1,4 @@ -import { - deterministicWallets, - INITIAL_STATE -} from 'reducers/deterministicWallets'; +import { deterministicWallets, INITIAL_STATE } from 'reducers/deterministicWallets'; import * as dWalletActions from 'actions/deterministicWallets'; import { TokenValue } from 'libs/units'; @@ -23,10 +20,7 @@ describe('deterministicWallets reducer', () => { it('should handle DW_SET_WALLETS', () => { const wallets = [wallet]; expect( - deterministicWallets( - undefined, - dWalletActions.setDeterministicWallets(wallets) - ) + deterministicWallets(undefined, dWalletActions.setDeterministicWallets(wallets)) ).toEqual({ ...INITIAL_STATE, wallets @@ -35,12 +29,7 @@ describe('deterministicWallets reducer', () => { it('should handle DW_SET_DESIRED_TOKEN', () => { const desiredToken = 'OMG'; - expect( - deterministicWallets( - undefined, - dWalletActions.setDesiredToken(desiredToken) - ) - ).toEqual({ + expect(deterministicWallets(undefined, dWalletActions.setDesiredToken(desiredToken))).toEqual({ ...INITIAL_STATE, desiredToken }); @@ -56,10 +45,7 @@ describe('deterministicWallets reducer', () => { address: 'wallet2' }; const wallets = [wallet1, wallet2]; - const state = deterministicWallets( - undefined, - dWalletActions.setDeterministicWallets(wallets) - ); + const state = deterministicWallets(undefined, dWalletActions.setDeterministicWallets(wallets)); const wallet2Update = { ...wallet, @@ -69,10 +55,7 @@ describe('deterministicWallets reducer', () => { }; expect( - deterministicWallets( - state, - dWalletActions.updateDeterministicWallet(wallet2Update) - ) + deterministicWallets(state, dWalletActions.updateDeterministicWallet(wallet2Update)) ).toEqual({ ...INITIAL_STATE, wallets: [wallet1, wallet2Update] diff --git a/spec/reducers/ens.spec.ts b/spec/reducers/ens.spec.ts index 640d2833..70f41924 100644 --- a/spec/reducers/ens.spec.ts +++ b/spec/reducers/ens.spec.ts @@ -1,15 +1,16 @@ -import { ens, INITIAL_STATE } from 'reducers/ens'; +import { ens } from 'reducers/ens'; import * as ensActions from 'actions/ens'; +import { createStore } from 'redux'; +const store = createStore(ens); +const INITIAL_STATE = store.getState(); describe('customTokens reducer', () => { - it('should handle ENS_CACHE', () => { + it('handles resolveDomainRequested', () => { const ensName = 'ensName'; - const address = 'address'; - expect( - ens(undefined, ensActions.cacheEnsAddress(ensName, address)) - ).toEqual({ + expect(ens(undefined as any, ensActions.resolveDomainRequested(ensName))).toEqual({ ...INITIAL_STATE, - [ensName]: address + domainRequests: { ensName: { state: 'PENDING' } }, + domainSelector: { currentDomain: 'ensName' } }); }); }); diff --git a/spec/reducers/notifications.spec.ts b/spec/reducers/notifications.spec.ts index c7d3e96e..4457b5e2 100644 --- a/spec/reducers/notifications.spec.ts +++ b/spec/reducers/notifications.spec.ts @@ -19,10 +19,7 @@ describe('customTokens reducer', () => { const state2 = notifications(state1, notification2); expect( - notifications( - state2, - notificationsActions.closeNotification(notification2.payload) - ) + notifications(state2, notificationsActions.closeNotification(notification2.payload)) ).toEqual([notification1.payload]); }); }); diff --git a/spec/reducers/rates.spec.ts b/spec/reducers/rates.spec.ts index 54fab934..70087860 100644 --- a/spec/reducers/rates.spec.ts +++ b/spec/reducers/rates.spec.ts @@ -15,9 +15,7 @@ describe('rates reducer', () => { } }; - expect( - rates(undefined, ratesActions.fetchCCRatesSucceeded(fakeCCResp)) - ).toEqual({ + expect(rates(undefined, ratesActions.fetchCCRatesSucceeded(fakeCCResp))).toEqual({ ...INITIAL_STATE, rates: { ...INITIAL_STATE.rates, @@ -27,8 +25,6 @@ describe('rates reducer', () => { }); it('should handle RATES_FETCH_CC_FAILED', () => { - expect(rates(undefined, ratesActions.fetchCCRatesFailed())).toHaveProperty( - 'ratesError' - ); + expect(rates(undefined, ratesActions.fetchCCRatesFailed())).toHaveProperty('ratesError'); }); }); diff --git a/spec/sagas/notifications.spec.ts b/spec/sagas/notifications.spec.ts index 37a05acc..53b577e5 100644 --- a/spec/sagas/notifications.spec.ts +++ b/spec/sagas/notifications.spec.ts @@ -1,26 +1,14 @@ import { delay } from 'redux-saga'; import { call, put } from 'redux-saga/effects'; import { handleNotification } from 'sagas/notifications'; -import { - ShowNotificationAction, - showNotification, - closeNotification -} from 'actions/notifications'; +import { ShowNotificationAction, showNotification, closeNotification } from 'actions/notifications'; describe('handleNotification*', () => { const level = 'success'; const msg = 'msg'; const duration = 10; - const notificationAction1: ShowNotificationAction = showNotification( - level, - msg, - duration - ); - const notificationAction2: ShowNotificationAction = showNotification( - level, - msg, - 0 - ); + const notificationAction1: ShowNotificationAction = showNotification(level, msg, duration); + const notificationAction2: ShowNotificationAction = showNotification(level, msg, 0); const gen1 = handleNotification(notificationAction1); const gen2 = handleNotification(notificationAction2); diff --git a/spec/sagas/transaction/current/currentTo.spec.ts b/spec/sagas/transaction/current/currentTo.spec.ts index 7bc601cb..2a2dbae7 100644 --- a/spec/sagas/transaction/current/currentTo.spec.ts +++ b/spec/sagas/transaction/current/currentTo.spec.ts @@ -1,55 +1,57 @@ -import { isEtherTransaction } from 'selectors/transaction'; -import { setToField } from 'actions/transaction/actionCreators/fields'; -import { setTokenTo } from 'actions/transaction/actionCreators/meta'; import { Address } from 'libs/units'; -import { select, call, put } from 'redux-saga/effects'; +import { call, select, put } from 'redux-saga/effects'; import { isValidETHAddress, isValidENSAddress } from 'libs/validators'; -import { setCurrentTo } from 'sagas/transaction/current/currentTo'; +import { setCurrentTo, setField } from 'sagas/transaction/current/currentTo'; +import { isEtherTransaction } from 'selectors/transaction'; import { cloneableGenerator } from 'redux-saga/utils'; +import { setToField, setTokenTo } from 'actions/transaction'; +const raw = '0xa'; + +const payload = { + raw, + value: Address(raw) +}; describe('setCurrentTo*', () => { - const raw = '0xa'; const action: any = { payload: raw }; const validAddress = true; const validEns = false; - const etherTransaction = true; - const payload = { - raw, - value: Address(raw), - error: null - }; - const gens: any = {}; - gens.gen = cloneableGenerator(setCurrentTo)(action); + const gen = setCurrentTo(action); it('should call isValidETHAddress', () => { - expect(gens.gen.next().value).toEqual(call(isValidETHAddress, raw)); + expect(gen.next().value).toEqual(call(isValidETHAddress, raw)); }); it('should call isValidENSAddress', () => { - expect(gens.gen.next(validAddress).value).toEqual(call(isValidENSAddress, raw)); + expect(gen.next(validAddress).value).toEqual(call(isValidENSAddress, raw)); }); - it('should select isEtherTransaction', () => { - expect(gens.gen.next(validEns).value).toEqual(select(isEtherTransaction)); + it('should call setField', () => { + expect(gen.next(validEns).value).toEqual(call(setField, payload)); }); - it('should put setToField if etherTransaction', () => { - gens.ethTransaction = gens.gen.clone(); - expect(gens.ethTransaction.next(etherTransaction).value).toEqual(put(setToField(payload))); - }); - - it('setToField should be done', () => { - expect(gens.ethTransaction.next().done).toEqual(true); - }); - - it('should put setTokenTo if !etherTransaction', () => { - expect(gens.gen.next(!etherTransaction).value).toEqual(put(setTokenTo(payload))); - }); - - it('setTokenTo should be done', () => { - expect(gens.gen.next().done).toEqual(true); + it('should be done', () => { + expect(gen.next().done).toEqual(true); + }); +}); + +describe('setField', () => { + const etherTransaction = cloneableGenerator(setField)(payload); + it('should select etherTransaction', () => { + expect(etherTransaction.next().value).toEqual(select(isEtherTransaction)); + }); + + it('should put setTokenTo field if its a token transaction ', () => { + const tokenTransaction = etherTransaction.clone(); + + expect(tokenTransaction.next(false).value).toEqual(put(setTokenTo(payload))); + expect(tokenTransaction.next().done).toBe(true); + }); + it('should put setToField if its an etherTransaction', () => { + expect(etherTransaction.next(true).value).toEqual(put(setToField(payload))); + expect(etherTransaction.next().done).toBe(true); }); });