diff --git a/Cargo.lock b/Cargo.lock index c3dd51492..f6d5fa213 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1249,7 +1249,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#9054ef95a5d79cbd8fefe4869ec3b4de07e9a72d" +source = "git+https://github.com/ethcore/js-precompiled.git#29c6f1d527db5c6a29c6b6afdb9bde32c6809079" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/dapps/src/apps/fetcher.rs b/dapps/src/apps/fetcher.rs index 041064121..2c1414201 100644 --- a/dapps/src/apps/fetcher.rs +++ b/dapps/src/apps/fetcher.rs @@ -47,7 +47,7 @@ pub struct ContentFetcher { resolver: R, cache: Arc>, sync: Arc, - embeddable_at: Option, + embeddable_on: Option<(String, u16)>, } impl Drop for ContentFetcher { @@ -59,7 +59,7 @@ impl Drop for ContentFetcher { impl ContentFetcher { - pub fn new(resolver: R, sync_status: Arc, embeddable_at: Option) -> Self { + pub fn new(resolver: R, sync_status: Arc, embeddable_on: Option<(String, u16)>) -> Self { let mut dapps_path = env::temp_dir(); dapps_path.push(random_filename()); @@ -68,17 +68,17 @@ impl ContentFetcher { resolver: resolver, sync: sync_status, cache: Arc::new(Mutex::new(ContentCache::default())), - embeddable_at: embeddable_at, + embeddable_on: embeddable_on, } } - fn still_syncing(port: Option) -> Box { + fn still_syncing(address: Option<(String, u16)>) -> Box { Box::new(ContentHandler::error( StatusCode::ServiceUnavailable, "Sync In Progress", "Your node is still syncing. We cannot resolve any content before it's fully synced.", Some("Refresh"), - port, + address, )) } @@ -145,7 +145,7 @@ impl ContentFetcher { match content { // Don't serve dapps if we are still syncing (but serve content) Some(URLHintResult::Dapp(_)) if self.sync.is_major_importing() => { - (None, Self::still_syncing(self.embeddable_at)) + (None, Self::still_syncing(self.embeddable_on.clone())) }, Some(URLHintResult::Dapp(dapp)) => { let (handler, fetch_control) = ContentFetcherHandler::new( @@ -155,9 +155,9 @@ impl ContentFetcher { id: content_id.clone(), dapps_path: self.dapps_path.clone(), on_done: Box::new(on_done), - embeddable_at: self.embeddable_at, + embeddable_on: self.embeddable_on.clone(), }, - self.embeddable_at, + self.embeddable_on.clone(), ); (Some(ContentStatus::Fetching(fetch_control)), Box::new(handler) as Box) @@ -172,13 +172,13 @@ impl ContentFetcher { content_path: self.dapps_path.clone(), on_done: Box::new(on_done), }, - self.embeddable_at, + self.embeddable_on.clone(), ); (Some(ContentStatus::Fetching(fetch_control)), Box::new(handler) as Box) }, None if self.sync.is_major_importing() => { - (None, Self::still_syncing(self.embeddable_at)) + (None, Self::still_syncing(self.embeddable_on.clone())) }, None => { // This may happen when sync status changes in between @@ -188,7 +188,7 @@ impl ContentFetcher { "Resource Not Found", "Requested resource was not found.", None, - self.embeddable_at, + self.embeddable_on.clone(), )) as Box) }, } @@ -293,7 +293,7 @@ struct DappInstaller { id: String, dapps_path: PathBuf, on_done: Box) + Send>, - embeddable_at: Option, + embeddable_on: Option<(String, u16)>, } impl DappInstaller { @@ -386,7 +386,7 @@ impl ContentValidator for DappInstaller { try!(manifest_file.write_all(manifest_str.as_bytes())); // Create endpoint - let app = LocalPageEndpoint::new(target, manifest.clone().into(), PageCache::Enabled, self.embeddable_at); + let app = LocalPageEndpoint::new(target, manifest.clone().into(), PageCache::Enabled, self.embeddable_on.clone()); // Return modified app manifest Ok((manifest.id.clone(), app)) diff --git a/dapps/src/apps/fs.rs b/dapps/src/apps/fs.rs index 104b33035..f0b4ddfa8 100644 --- a/dapps/src/apps/fs.rs +++ b/dapps/src/apps/fs.rs @@ -97,12 +97,12 @@ fn read_manifest(name: &str, mut path: PathBuf) -> EndpointInfo { }) } -pub fn local_endpoints(dapps_path: String, signer_port: Option) -> Endpoints { +pub fn local_endpoints(dapps_path: String, signer_address: Option<(String, u16)>) -> Endpoints { let mut pages = Endpoints::new(); for dapp in local_dapps(dapps_path) { pages.insert( dapp.id, - Box::new(LocalPageEndpoint::new(dapp.path, dapp.info, PageCache::Disabled, signer_port)) + Box::new(LocalPageEndpoint::new(dapp.path, dapp.info, PageCache::Disabled, signer_address.clone())) ); } pages diff --git a/dapps/src/apps/mod.rs b/dapps/src/apps/mod.rs index 11919d6d2..3cb0d8256 100644 --- a/dapps/src/apps/mod.rs +++ b/dapps/src/apps/mod.rs @@ -37,26 +37,26 @@ pub fn utils() -> Box { Box::new(PageEndpoint::with_prefix(parity_ui::App::default(), UTILS_PATH.to_owned())) } -pub fn all_endpoints(dapps_path: String, signer_port: Option) -> Endpoints { +pub fn all_endpoints(dapps_path: String, signer_address: Option<(String, u16)>) -> Endpoints { // fetch fs dapps at first to avoid overwriting builtins - let mut pages = fs::local_endpoints(dapps_path, signer_port); + let mut pages = fs::local_endpoints(dapps_path, signer_address.clone()); // NOTE [ToDr] Dapps will be currently embeded on 8180 - insert::(&mut pages, "ui", Embeddable::Yes(signer_port)); - pages.insert("proxy".into(), ProxyPac::boxed(signer_port)); + insert::(&mut pages, "ui", Embeddable::Yes(signer_address.clone())); + pages.insert("proxy".into(), ProxyPac::boxed(signer_address)); pages } fn insert(pages: &mut Endpoints, id: &str, embed_at: Embeddable) { pages.insert(id.to_owned(), Box::new(match embed_at { - Embeddable::Yes(port) => PageEndpoint::new_safe_to_embed(T::default(), port), + Embeddable::Yes(address) => PageEndpoint::new_safe_to_embed(T::default(), address), Embeddable::No => PageEndpoint::new(T::default()), })); } enum Embeddable { - Yes(Option), + Yes(Option<(String, u16)>), #[allow(dead_code)] No, } diff --git a/dapps/src/handlers/content.rs b/dapps/src/handlers/content.rs index a67fbcd0b..738a9a890 100644 --- a/dapps/src/handlers/content.rs +++ b/dapps/src/handlers/content.rs @@ -32,7 +32,7 @@ pub struct ContentHandler { content: String, mimetype: Mime, write_pos: usize, - safe_to_embed_at_port: Option, + safe_to_embed_on: Option<(String, u16)>, } impl ContentHandler { @@ -44,31 +44,31 @@ impl ContentHandler { Self::new(StatusCode::NotFound, content, mimetype) } - pub fn html(code: StatusCode, content: String, embeddable_at: Option) -> Self { - Self::new_embeddable(code, content, mime!(Text/Html), embeddable_at) + pub fn html(code: StatusCode, content: String, embeddable_on: Option<(String, u16)>) -> Self { + Self::new_embeddable(code, content, mime!(Text/Html), embeddable_on) } - pub fn error(code: StatusCode, title: &str, message: &str, details: Option<&str>, embeddable_at: Option) -> Self { + pub fn error(code: StatusCode, title: &str, message: &str, details: Option<&str>, embeddable_on: Option<(String, u16)>) -> Self { Self::html(code, format!( include_str!("../error_tpl.html"), title=title, message=message, details=details.unwrap_or_else(|| ""), version=version(), - ), embeddable_at) + ), embeddable_on) } pub fn new(code: StatusCode, content: String, mimetype: Mime) -> Self { Self::new_embeddable(code, content, mimetype, None) } - pub fn new_embeddable(code: StatusCode, content: String, mimetype: Mime, embeddable_at: Option) -> Self { + pub fn new_embeddable(code: StatusCode, content: String, mimetype: Mime, embeddable_on: Option<(String, u16)>) -> Self { ContentHandler { code: code, content: content, mimetype: mimetype, write_pos: 0, - safe_to_embed_at_port: embeddable_at, + safe_to_embed_on: embeddable_on, } } } @@ -85,7 +85,7 @@ impl server::Handler for ContentHandler { fn on_response(&mut self, res: &mut server::Response) -> Next { res.set_status(self.code); res.headers_mut().set(header::ContentType(self.mimetype.clone())); - add_security_headers(&mut res.headers_mut(), self.safe_to_embed_at_port.clone()); + add_security_headers(&mut res.headers_mut(), self.safe_to_embed_on.clone()); Next::write() } diff --git a/dapps/src/handlers/fetch.rs b/dapps/src/handlers/fetch.rs index 1a0221bff..a34b58fa7 100644 --- a/dapps/src/handlers/fetch.rs +++ b/dapps/src/handlers/fetch.rs @@ -138,7 +138,7 @@ pub struct ContentFetcherHandler { client: Option, installer: H, request_url: Option, - embeddable_at: Option, + embeddable_on: Option<(String, u16)>, } impl Drop for ContentFetcherHandler { @@ -157,7 +157,7 @@ impl ContentFetcherHandler { url: String, control: Control, handler: H, - embeddable_at: Option, + embeddable_on: Option<(String, u16)>, ) -> (Self, Arc) { let fetch_control = Arc::new(FetchControl::default()); @@ -169,7 +169,7 @@ impl ContentFetcherHandler { status: FetchState::NotStarted(url), installer: handler, request_url: None, - embeddable_at: embeddable_at, + embeddable_on: embeddable_on, }; (handler, fetch_control) @@ -208,7 +208,7 @@ impl server::Handler for ContentFetcherHandler< "Unable To Start Dapp Download", "Could not initialize download of the dapp. It might be a problem with the remote server.", Some(&format!("{}", e)), - self.embeddable_at, + self.embeddable_on.clone(), )), } }, @@ -218,7 +218,7 @@ impl server::Handler for ContentFetcherHandler< "Method Not Allowed", "Only GET requests are allowed.", None, - self.embeddable_at, + self.embeddable_on.clone(), )), }) } else { None }; @@ -241,7 +241,7 @@ impl server::Handler for ContentFetcherHandler< "Download Timeout", &format!("Could not fetch content within {} seconds.", FETCH_TIMEOUT), None, - self.embeddable_at, + self.embeddable_on.clone(), ); Self::close_client(&mut self.client); (Some(FetchState::Error(timeout)), Next::write()) @@ -263,7 +263,7 @@ impl server::Handler for ContentFetcherHandler< "Invalid Dapp", "Downloaded bundle does not contain a valid content.", Some(&format!("{:?}", e)), - self.embeddable_at, + self.embeddable_on.clone(), )) }, Ok((id, result)) => { @@ -284,7 +284,7 @@ impl server::Handler for ContentFetcherHandler< "Download Error", "There was an error when fetching the content.", Some(&format!("{:?}", e)), - self.embeddable_at, + self.embeddable_on.clone(), ); (Some(FetchState::Error(error)), Next::write()) }, diff --git a/dapps/src/handlers/mod.rs b/dapps/src/handlers/mod.rs index 3d96e8a40..b575509a5 100644 --- a/dapps/src/handlers/mod.rs +++ b/dapps/src/handlers/mod.rs @@ -30,18 +30,18 @@ pub use self::fetch::{ContentFetcherHandler, ContentValidator, FetchControl}; use url::Url; use hyper::{server, header, net, uri}; -use signer_address; +use address; /// Adds security-related headers to the Response. -pub fn add_security_headers(headers: &mut header::Headers, embeddable_at: Option) { +pub fn add_security_headers(headers: &mut header::Headers, embeddable_on: Option<(String, u16)>) { headers.set_raw("X-XSS-Protection", vec![b"1; mode=block".to_vec()]); headers.set_raw("X-Content-Type-Options", vec![b"nosniff".to_vec()]); // Embedding header: - if let Some(port) = embeddable_at { + if let Some(embeddable_on) = embeddable_on { headers.set_raw( "X-Frame-Options", - vec![format!("ALLOW-FROM http://{}", signer_address(port)).into_bytes()] + vec![format!("ALLOW-FROM http://{}", address(embeddable_on)).into_bytes()] ); } else { // TODO [ToDr] Should we be more strict here (DENY?)? diff --git a/dapps/src/lib.rs b/dapps/src/lib.rs index 4394d1183..2c9fa33d1 100644 --- a/dapps/src/lib.rs +++ b/dapps/src/lib.rs @@ -112,7 +112,7 @@ pub struct ServerBuilder { handler: Arc, registrar: Arc, sync_status: Arc, - signer_port: Option, + signer_address: Option<(String, u16)>, } impl Extendable for ServerBuilder { @@ -129,7 +129,7 @@ impl ServerBuilder { handler: Arc::new(IoHandler::new()), registrar: registrar, sync_status: Arc::new(|| false), - signer_port: None, + signer_address: None, } } @@ -139,8 +139,8 @@ impl ServerBuilder { } /// Change default signer port. - pub fn with_signer_port(&mut self, signer_port: Option) { - self.signer_port = signer_port; + pub fn with_signer_address(&mut self, signer_address: Option<(String, u16)>) { + self.signer_address = signer_address; } /// Asynchronously start server with no authentication, @@ -152,7 +152,7 @@ impl ServerBuilder { NoAuth, self.handler.clone(), self.dapps_path.clone(), - self.signer_port.clone(), + self.signer_address.clone(), self.registrar.clone(), self.sync_status.clone(), ) @@ -167,7 +167,7 @@ impl ServerBuilder { HttpBasicAuth::single_user(username, password), self.handler.clone(), self.dapps_path.clone(), - self.signer_port.clone(), + self.signer_address.clone(), self.registrar.clone(), self.sync_status.clone(), ) @@ -197,11 +197,11 @@ impl Server { } /// Returns a list of CORS domains for API endpoint. - fn cors_domains(signer_port: Option) -> Vec { - match signer_port { - Some(port) => vec![ + fn cors_domains(signer_address: Option<(String, u16)>) -> Vec { + match signer_address { + Some(signer_address) => vec![ format!("http://{}{}", HOME_PAGE, DAPPS_DOMAIN), - format!("http://{}", signer_address(port)), + format!("http://{}", address(signer_address)), ], None => vec![], } @@ -213,15 +213,15 @@ impl Server { authorization: A, handler: Arc, dapps_path: String, - signer_port: Option, + signer_address: Option<(String, u16)>, registrar: Arc, sync_status: Arc, ) -> Result { let panic_handler = Arc::new(Mutex::new(None)); let authorization = Arc::new(authorization); - let content_fetcher = Arc::new(apps::fetcher::ContentFetcher::new(apps::urlhint::URLHintContract::new(registrar), sync_status, signer_port)); - let endpoints = Arc::new(apps::all_endpoints(dapps_path, signer_port.clone())); - let cors_domains = Self::cors_domains(signer_port); + let content_fetcher = Arc::new(apps::fetcher::ContentFetcher::new(apps::urlhint::URLHintContract::new(registrar), sync_status, signer_address.clone())); + let endpoints = Arc::new(apps::all_endpoints(dapps_path, signer_address.clone())); + let cors_domains = Self::cors_domains(signer_address.clone()); let special = Arc::new({ let mut special = HashMap::new(); @@ -238,7 +238,7 @@ impl Server { try!(hyper::Server::http(addr)) .handle(move |ctrl| router::Router::new( ctrl, - signer_port.clone(), + signer_address.clone(), content_fetcher.clone(), endpoints.clone(), special.clone(), @@ -302,8 +302,8 @@ pub fn random_filename() -> String { rng.gen_ascii_chars().take(12).collect() } -fn signer_address(port: u16) -> String { - format!("127.0.0.1:{}", port) +fn address(address: (String, u16)) -> String { + format!("{}:{}", address.0, address.1) } #[cfg(test)] @@ -332,7 +332,7 @@ mod util_tests { // when let none = Server::cors_domains(None); - let some = Server::cors_domains(Some(18180)); + let some = Server::cors_domains(Some(("127.0.0.1".into(), 18180))); // then assert_eq!(none, Vec::::new()); diff --git a/dapps/src/page/builtin.rs b/dapps/src/page/builtin.rs index bf5bf088f..40c0e23a6 100644 --- a/dapps/src/page/builtin.rs +++ b/dapps/src/page/builtin.rs @@ -25,7 +25,7 @@ pub struct PageEndpoint { /// Prefix to strip from the path (when `None` deducted from `app_id`) pub prefix: Option, /// Safe to be loaded in frame by other origin. (use wisely!) - safe_to_embed_at_port: Option, + safe_to_embed_on: Option<(String, u16)>, info: EndpointInfo, } @@ -36,7 +36,7 @@ impl PageEndpoint { PageEndpoint { app: Arc::new(app), prefix: None, - safe_to_embed_at_port: None, + safe_to_embed_on: None, info: EndpointInfo::from(info), } } @@ -49,7 +49,7 @@ impl PageEndpoint { PageEndpoint { app: Arc::new(app), prefix: Some(prefix), - safe_to_embed_at_port: None, + safe_to_embed_on: None, info: EndpointInfo::from(info), } } @@ -57,12 +57,12 @@ impl PageEndpoint { /// Creates new `PageEndpoint` which can be safely used in iframe /// even from different origin. It might be dangerous (clickjacking). /// Use wisely! - pub fn new_safe_to_embed(app: T, port: Option) -> Self { + pub fn new_safe_to_embed(app: T, address: Option<(String, u16)>) -> Self { let info = app.info(); PageEndpoint { app: Arc::new(app), prefix: None, - safe_to_embed_at_port: port, + safe_to_embed_on: address, info: EndpointInfo::from(info), } } @@ -79,9 +79,9 @@ impl Endpoint for PageEndpoint { app: BuiltinDapp::new(self.app.clone()), prefix: self.prefix.clone(), path: path, - file: handler::ServedFile::new(self.safe_to_embed_at_port.clone()), + file: handler::ServedFile::new(self.safe_to_embed_on.clone()), cache: PageCache::Disabled, - safe_to_embed_at_port: self.safe_to_embed_at_port.clone(), + safe_to_embed_on: self.safe_to_embed_on.clone(), }) } } diff --git a/dapps/src/page/handler.rs b/dapps/src/page/handler.rs index f908a69c5..74eabf917 100644 --- a/dapps/src/page/handler.rs +++ b/dapps/src/page/handler.rs @@ -60,13 +60,13 @@ pub enum ServedFile { } impl ServedFile { - pub fn new(embeddable_at: Option) -> Self { + pub fn new(embeddable_on: Option<(String, u16)>) -> Self { ServedFile::Error(ContentHandler::error( StatusCode::NotFound, "404 Not Found", "Requested dapp resource was not found.", None, - embeddable_at, + embeddable_on, )) } } @@ -97,7 +97,7 @@ pub struct PageHandler { /// Requested path. pub path: EndpointPath, /// Flag indicating if the file can be safely embeded (put in iframe). - pub safe_to_embed_at_port: Option, + pub safe_to_embed_on: Option<(String, u16)>, /// Cache settings for this page. pub cache: PageCache, } @@ -133,7 +133,7 @@ impl server::Handler for PageHandler { self.app.file(&self.extract_path(url.path())) }, _ => None, - }.map_or_else(|| ServedFile::new(self.safe_to_embed_at_port.clone()), |f| ServedFile::File(f)); + }.map_or_else(|| ServedFile::new(self.safe_to_embed_on.clone()), |f| ServedFile::File(f)); Next::write() } @@ -162,7 +162,7 @@ impl server::Handler for PageHandler { } // Security headers: - add_security_headers(&mut res.headers_mut(), self.safe_to_embed_at_port); + add_security_headers(&mut res.headers_mut(), self.safe_to_embed_on.clone()); Next::write() }, ServedFile::Error(ref mut handler) => { @@ -246,7 +246,7 @@ fn should_extract_path_with_appid() { }, file: ServedFile::new(None), cache: Default::default(), - safe_to_embed_at_port: None, + safe_to_embed_on: None, }; // when diff --git a/dapps/src/page/local.rs b/dapps/src/page/local.rs index aa98a68cd..ec24cac36 100644 --- a/dapps/src/page/local.rs +++ b/dapps/src/page/local.rs @@ -27,17 +27,17 @@ pub struct LocalPageEndpoint { mime: Option, info: Option, cache: PageCache, - embeddable_at: Option, + embeddable_on: Option<(String, u16)>, } impl LocalPageEndpoint { - pub fn new(path: PathBuf, info: EndpointInfo, cache: PageCache, embeddable_at: Option) -> Self { + pub fn new(path: PathBuf, info: EndpointInfo, cache: PageCache, embeddable_on: Option<(String, u16)>) -> Self { LocalPageEndpoint { path: path, mime: None, info: Some(info), cache: cache, - embeddable_at: embeddable_at, + embeddable_on: embeddable_on, } } @@ -47,7 +47,7 @@ impl LocalPageEndpoint { mime: Some(mime), info: None, cache: cache, - embeddable_at: None, + embeddable_on: None, } } @@ -68,7 +68,7 @@ impl Endpoint for LocalPageEndpoint { prefix: None, path: path, file: handler::ServedFile::new(None), - safe_to_embed_at_port: self.embeddable_at, + safe_to_embed_on: self.embeddable_on.clone(), cache: self.cache, }) } else { @@ -77,7 +77,7 @@ impl Endpoint for LocalPageEndpoint { prefix: None, path: path, file: handler::ServedFile::new(None), - safe_to_embed_at_port: self.embeddable_at, + safe_to_embed_on: self.embeddable_on.clone(), cache: self.cache, }) } diff --git a/dapps/src/proxypac.rs b/dapps/src/proxypac.rs index 2d7c4e3ce..88ecb6ab3 100644 --- a/dapps/src/proxypac.rs +++ b/dapps/src/proxypac.rs @@ -19,24 +19,24 @@ use endpoint::{Endpoint, Handler, EndpointPath}; use handlers::ContentHandler; use apps::{HOME_PAGE, DAPPS_DOMAIN}; -use signer_address; +use address; pub struct ProxyPac { - signer_port: Option, + signer_address: Option<(String, u16)>, } impl ProxyPac { - pub fn boxed(signer_port: Option) -> Box { + pub fn boxed(signer_address: Option<(String, u16)>) -> Box { Box::new(ProxyPac { - signer_port: signer_port + signer_address: signer_address }) } } impl Endpoint for ProxyPac { fn to_handler(&self, path: EndpointPath) -> Box { - let signer = self.signer_port - .map(signer_address) + let signer = self.signer_address.clone() + .map(address) .unwrap_or_else(|| format!("{}:{}", path.host, path.port)); let content = format!( diff --git a/dapps/src/router/mod.rs b/dapps/src/router/mod.rs index fe8061ef0..cb9133886 100644 --- a/dapps/src/router/mod.rs +++ b/dapps/src/router/mod.rs @@ -20,7 +20,7 @@ pub mod auth; mod host_validation; -use signer_address; +use address; use std::sync::Arc; use std::collections::HashMap; use url::{Url, Host}; @@ -43,7 +43,7 @@ pub enum SpecialEndpoint { pub struct Router { control: Option, - signer_port: Option, + signer_address: Option<(String, u16)>, endpoints: Arc, fetch: Arc, special: Arc>>, @@ -117,14 +117,14 @@ impl server::Handler for Router { "404 Not Found", "Requested content was not found.", None, - self.signer_port, + self.signer_address.clone(), )) }, // Redirect any other GET request to signer. _ if *req.method() == hyper::Method::Get => { - if let Some(port) = self.signer_port { + if let Some(signer_address) = self.signer_address.clone() { trace!(target: "dapps", "Redirecting to signer interface."); - Redirection::boxed(&format!("http://{}", signer_address(port))) + Redirection::boxed(&format!("http://{}", address(signer_address))) } else { trace!(target: "dapps", "Signer disabled, returning 404."); Box::new(ContentHandler::error( @@ -132,7 +132,7 @@ impl server::Handler for Router { "404 Not Found", "Your homepage is not available when Trusted Signer is disabled.", Some("You can still access dapps by writing a correct address, though. Re-enabled Signer to get your homepage back."), - self.signer_port, + self.signer_address.clone(), )) } }, @@ -168,7 +168,7 @@ impl server::Handler for Router { impl Router { pub fn new( control: Control, - signer_port: Option, + signer_address: Option<(String, u16)>, content_fetcher: Arc, endpoints: Arc, special: Arc>>, @@ -181,7 +181,7 @@ impl Router { .to_handler(EndpointPath::default()); Router { control: Some(control), - signer_port: signer_port, + signer_address: signer_address, endpoints: endpoints, fetch: content_fetcher, special: special, diff --git a/dapps/src/tests/helpers.rs b/dapps/src/tests/helpers.rs index 1fa2e777a..f7c9e8ba6 100644 --- a/dapps/src/tests/helpers.rs +++ b/dapps/src/tests/helpers.rs @@ -76,7 +76,7 @@ pub fn init_server(hosts: Option>, is_syncing: bool) -> (Server, Arc dapps_path.push("non-existent-dir-to-prevent-fs-files-from-loading"); let mut builder = ServerBuilder::new(dapps_path.to_str().unwrap().into(), registrar.clone()); builder.with_sync_status(Arc::new(move || is_syncing)); - builder.with_signer_port(Some(SIGNER_PORT)); + builder.with_signer_address(Some(("127.0.0.1".into(), SIGNER_PORT))); ( builder.start_unsecured_http(&"127.0.0.1:0".parse().unwrap(), hosts).unwrap(), registrar, @@ -89,7 +89,7 @@ pub fn serve_with_auth(user: &str, pass: &str) -> Server { let mut dapps_path = env::temp_dir(); dapps_path.push("non-existent-dir-to-prevent-fs-files-from-loading"); let mut builder = ServerBuilder::new(dapps_path.to_str().unwrap().into(), registrar); - builder.with_signer_port(Some(SIGNER_PORT)); + builder.with_signer_address(Some(("127.0.0.1".into(), SIGNER_PORT))); builder.start_basic_auth_http(&"127.0.0.1:0".parse().unwrap(), None, user, pass).unwrap() } diff --git a/ethcore/res/ethereum/frontier.json b/ethcore/res/ethereum/frontier.json index ecaefa4c3..be473237c 100644 --- a/ethcore/res/ethereum/frontier.json +++ b/ethcore/res/ethereum/frontier.json @@ -131,10 +131,10 @@ "0x807640a13483f8ac783c557fcdf27be11ea4ac7a" ], "eip150Transition": "0x259518", - "eip155Transition": 2642462, - "eip160Transition": 2642462, - "eip161abcTransition": 2642462, - "eip161dTransition": 2642462 + "eip155Transition": "0x7fffffffffffffff", + "eip160Transition": "0x7fffffffffffffff", + "eip161abcTransition": "0x7fffffffffffffff", + "eip161dTransition": "0x7fffffffffffffff" } } }, @@ -176,7 +176,13 @@ "enode://a979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c@52.16.188.185:30303", "enode://de471bccee3d042261d52e9bff31458daecc406142b401d4cd848f677479f73104b9fdeb090af9583d3391b7f10cb2ba9e26865dd5fca4fcdc0fb1e3b723c786@54.94.239.50:30303", "enode://1118980bf48b0a3640bdba04e0fe78b1add18e1cd99bf22d53daac1fd9972ad650df52176e7c7d89d1114cfef2bc23a2959aa54998a46afcf7d91809f0855082@52.74.57.123:30303", - "enode://4cd540b2c3292e17cff39922e864094bf8b0741fcc8c5dcea14957e389d7944c70278d872902e3d0345927f621547efa659013c400865485ab4bfa0c6596936f@138.201.144.135:30303" + "enode://4cd540b2c3292e17cff39922e864094bf8b0741fcc8c5dcea14957e389d7944c70278d872902e3d0345927f621547efa659013c400865485ab4bfa0c6596936f@138.201.144.135:30303", + + "enode://89d5dc2a81e574c19d0465f497c1af96732d1b61a41de89c2a37f35707689ac416529fae1038809852b235c2d30fd325abdc57c122feeefbeaaf802cc7e9580d@45.55.33.62:30303", + "enode://605e04a43b1156966b3a3b66b980c87b7f18522f7f712035f84576016be909a2798a438b2b17b1a8c58db314d88539a77419ca4be36148c086900fba487c9d39@188.166.255.12:30303", + "enode://016b20125f447a3b203a3cae953b2ede8ffe51290c071e7599294be84317635730c397b8ff74404d6be412d539ee5bb5c3c700618723d3b53958c92bd33eaa82@159.203.210.80:30303", + "enode://01f76fa0561eca2b9a7e224378dd854278735f1449793c46ad0c4e79e8775d080c21dcc455be391e90a98153c3b05dcc8935c8440de7b56fe6d67251e33f4e3c@10.6.6.117:30303", + "enode://fe11ef89fc5ac9da358fc160857855f25bbf9e332c79b9ca7089330c02b728b2349988c6062f10982041702110745e203d26975a6b34bcc97144f9fe439034e8@10.1.72.117:30303" ], "accounts": { "0000000000000000000000000000000000000001": { "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } }, diff --git a/ethcore/res/ethereum/tests b/ethcore/res/ethereum/tests index 853333e7d..9028c4801 160000 --- a/ethcore/res/ethereum/tests +++ b/ethcore/res/ethereum/tests @@ -1 +1 @@ -Subproject commit 853333e7da312775fb8f32f2c2771b8578cd0d79 +Subproject commit 9028c4801fd39fbb71a9796979182549a24e81c8 diff --git a/ethcore/src/account_provider.rs b/ethcore/src/account_provider.rs index 064c3e935..e906aefe9 100644 --- a/ethcore/src/account_provider.rs +++ b/ethcore/src/account_provider.rs @@ -95,6 +95,7 @@ impl KeyDirectory for NullDir { struct AddressBook { path: PathBuf, cache: HashMap, + transient: bool, } impl AddressBook { @@ -106,11 +107,18 @@ impl AddressBook { let mut r = AddressBook { path: path, cache: HashMap::new(), + transient: false, }; r.revert(); r } + pub fn transient() -> Self { + let mut book = AddressBook::new(Default::default()); + book.transient = true; + book + } + pub fn get(&self) -> HashMap { self.cache.clone() } @@ -134,6 +142,7 @@ impl AddressBook { } fn revert(&mut self) { + if self.transient { return; } trace!(target: "addressbook", "revert"); let _ = fs::File::open(self.path.clone()) .map_err(|e| trace!(target: "addressbook", "Couldn't open address book: {}", e)) @@ -144,6 +153,7 @@ impl AddressBook { } fn save(&mut self) { + if self.transient { return; } trace!(target: "addressbook", "save"); let _ = fs::File::create(self.path.clone()) .map_err(|e| warn!(target: "addressbook", "Couldn't open address book for writing: {}", e)) @@ -175,7 +185,7 @@ impl AccountProvider { pub fn transient_provider() -> Self { AccountProvider { unlocked: Mutex::new(HashMap::new()), - address_book: Mutex::new(AddressBook::new(Default::default())), + address_book: Mutex::new(AddressBook::transient()), sstore: Box::new(EthStore::open(Box::new(NullDir::default())) .expect("NullDir load always succeeds; qed")) } diff --git a/js/.gitignore b/js/.gitignore index acb73a82a..c1e496d91 100644 --- a/js/.gitignore +++ b/js/.gitignore @@ -6,3 +6,4 @@ build .dist .happypack .npmjs +.eslintcache diff --git a/js/package.json b/js/package.json index 41dc33903..ffff60d05 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.24", + "version": "0.2.28", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", @@ -39,9 +39,11 @@ "clean": "rm -rf ./build ./coverage", "coveralls": "npm run testCoverage && coveralls < coverage/lcov.info", "lint": "eslint --ignore-path .gitignore ./src/", + "lint:cached": "eslint --cache --ignore-path .gitignore ./src/", "test": "mocha 'src/**/*.spec.js'", "test:coverage": "istanbul cover _mocha -- 'src/**/*.spec.js'", - "test:e2e": "mocha 'src/**/*.e2e.js'" + "test:e2e": "mocha 'src/**/*.e2e.js'", + "prepush": "npm run lint:cached" }, "devDependencies": { "babel-cli": "^6.10.1", @@ -84,6 +86,7 @@ "happypack": "^2.2.1", "history": "^2.0.0", "html-loader": "^0.4.4", + "husky": "^0.11.9", "ignore-styles": "2.0.0", "image-webpack-loader": "^1.8.0", "istanbul": "^1.0.0-alpha.2", @@ -118,6 +121,7 @@ "bytes": "^2.4.0", "chart.js": "^2.3.0", "es6-promise": "^3.2.1", + "ethereumjs-tx": "^1.1.2", "file-saver": "^1.3.3", "format-json": "^1.0.3", "format-number": "^2.0.1", @@ -148,6 +152,7 @@ "redux-actions": "^0.10.1", "redux-thunk": "^2.1.0", "rlp": "^2.0.0", + "scryptsy": "^2.0.0", "store": "^1.3.20", "utf8": "^2.1.1", "validator": "^5.7.0", diff --git a/js/scripts/release.sh b/js/scripts/release.sh index fd95e00b8..5e631cf98 100755 --- a/js/scripts/release.sh +++ b/js/scripts/release.sh @@ -63,7 +63,7 @@ if [ "$BRANCH" == "master" ]; then echo "*** Publishing $PACKAGE to npmjs" cd .npmjs - npm publish --access public + npm publish --access public || true cd ../.. fi diff --git a/js/src/api/contract/contract.js b/js/src/api/contract/contract.js index bb6c15d8d..44e66c925 100644 --- a/js/src/api/contract/contract.js +++ b/js/src/api/contract/contract.js @@ -136,27 +136,30 @@ export default class Contract { } parseEventLogs (logs) { - return logs.map((log) => { - const signature = log.topics[0].substr(2); - const event = this.events.find((evt) => evt.signature === signature); + return logs + .map((log) => { + const signature = log.topics[0].substr(2); + const event = this.events.find((evt) => evt.signature === signature); - if (!event) { - throw new Error(`Unable to find event matching signature ${signature}`); - } + if (!event) { + console.warn(`Unable to find event matching signature ${signature}`); + return null; + } - const decoded = event.decodeLog(log.topics, log.data); + const decoded = event.decodeLog(log.topics, log.data); - log.params = {}; - log.event = event.name; + log.params = {}; + log.event = event.name; - decoded.params.forEach((param) => { - const { type, value } = param.token; + decoded.params.forEach((param) => { + const { type, value } = param.token; - log.params[param.name] = { type, value }; - }); + log.params[param.name] = { type, value }; + }); - return log; - }); + return log; + }) + .filter((log) => log); } parseTransactionEvents (receipt) { diff --git a/js/src/api/contract/contract.spec.js b/js/src/api/contract/contract.spec.js index 9065b4fad..96929cc11 100644 --- a/js/src/api/contract/contract.spec.js +++ b/js/src/api/contract/contract.spec.js @@ -119,19 +119,6 @@ describe('api/contract/Contract', () => { }); describe('parseTransactionEvents', () => { - it('checks for unmatched signatures', () => { - const contract = new Contract(eth, [{ anonymous: false, name: 'Message', type: 'event' }]); - expect(() => contract.parseTransactionEvents({ - logs: [{ - data: '0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000063cf90d3f0410092fc0fca41846f5962239791950000000000000000000000000000000000000000000000000000000056e6c85f0000000000000000000000000000000000000000000000000001000000004fcd00000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000d706f7374286d6573736167652900000000000000000000000000000000000000', - topics: [ - '0x954ba6c157daf8a26539574ffa64203c044691aa57251af95f4b48d85ec00dd5', - '0x0000000000000000000000000000000000000000000000000001000000004fe0' - ] - }] - })).to.throw(/event matching signature/); - }); - it('parses a transaction log into the data', () => { const contract = new Contract(eth, [ { diff --git a/js/src/api/format/input.js b/js/src/api/format/input.js index b46148cdc..830ca0e21 100644 --- a/js/src/api/format/input.js +++ b/js/src/api/format/input.js @@ -93,6 +93,10 @@ export function inFilter (options) { } export function inHex (str) { + if (str && str.toString) { + str = str.toString(16); + } + if (str && str.substr(0, 2) === '0x') { return str.toLowerCase(); } diff --git a/js/src/api/rpc/parity/parity.js b/js/src/api/rpc/parity/parity.js index f1739f848..a33828b80 100644 --- a/js/src/api/rpc/parity/parity.js +++ b/js/src/api/rpc/parity/parity.js @@ -60,6 +60,11 @@ export default class Parity { .then(outNumber); } + dappsInterface () { + return this._transport + .execute('parity_dappsInterface'); + } + defaultExtraData () { return this._transport .execute('parity_defaultExtraData'); @@ -176,6 +181,12 @@ export default class Parity { .then(outAddress); } + nextNonce (account) { + return this._transport + .execute('parity_nextNonce', inAddress(account)) + .then(outNumber); + } + nodeName () { return this._transport .execute('parity_nodeName'); diff --git a/js/src/api/rpc/signer/signer.js b/js/src/api/rpc/signer/signer.js index 7f905cf50..126ce651a 100644 --- a/js/src/api/rpc/signer/signer.js +++ b/js/src/api/rpc/signer/signer.js @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import { inNumber16 } from '../../format/input'; +import { inNumber16, inData } from '../../format/input'; import { outSignerRequest } from '../../format/output'; export default class Signer { @@ -27,6 +27,11 @@ export default class Signer { .execute('signer_confirmRequest', inNumber16(requestId), options, password); } + confirmRequestRaw (requestId, data) { + return this._transport + .execute('signer_confirmRequestRaw', inNumber16(requestId), inData(data)); + } + generateAuthorizationToken () { return this._transport .execute('signer_generateAuthorizationToken'); diff --git a/js/src/contracts/abi/index.js b/js/src/contracts/abi/index.js index 599f8a13b..80f49dc5b 100644 --- a/js/src/contracts/abi/index.js +++ b/js/src/contracts/abi/index.js @@ -24,6 +24,7 @@ import owned from './owned.json'; import registry from './registry.json'; import signaturereg from './signaturereg.json'; import tokenreg from './tokenreg.json'; +import wallet from './wallet.json'; export { basiccoin, @@ -35,5 +36,6 @@ export { owned, registry, signaturereg, - tokenreg + tokenreg, + wallet }; diff --git a/js/src/contracts/abi/wallet.json b/js/src/contracts/abi/wallet.json new file mode 100644 index 000000000..8048d239c --- /dev/null +++ b/js/src/contracts/abi/wallet.json @@ -0,0 +1 @@ +[{"constant":false,"inputs":[{"name":"_owner","type":"address"}],"name":"removeOwner","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_addr","type":"address"}],"name":"isOwner","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":true,"inputs":[],"name":"m_numOwners","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[],"name":"m_lastDay","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[],"name":"resetSpentToday","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"m_spentToday","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[{"name":"_owner","type":"address"}],"name":"addOwner","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"m_required","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[{"name":"_h","type":"bytes32"}],"name":"confirm","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":false,"inputs":[{"name":"_newLimit","type":"uint256"}],"name":"setDailyLimit","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"},{"name":"_data","type":"bytes"}],"name":"execute","outputs":[{"name":"_r","type":"bytes32"}],"type":"function"},{"constant":false,"inputs":[{"name":"_operation","type":"bytes32"}],"name":"revoke","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_newRequired","type":"uint256"}],"name":"changeRequirement","outputs":[],"type":"function"},{"constant":true,"inputs":[{"name":"_operation","type":"bytes32"},{"name":"_owner","type":"address"}],"name":"hasConfirmed","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":true,"inputs":[{"name":"ownerIndex","type":"uint256"}],"name":"getOwner","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"}],"name":"kill","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"}],"name":"changeOwner","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"m_dailyLimit","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"inputs":[{"name":"_owners","type":"address[]"},{"name":"_required","type":"uint256"},{"name":"_daylimit","type":"uint256"}],"type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"operation","type":"bytes32"}],"name":"Confirmation","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"operation","type":"bytes32"}],"name":"Revoke","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"oldOwner","type":"address"},{"indexed":false,"name":"newOwner","type":"address"}],"name":"OwnerChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"newOwner","type":"address"}],"name":"OwnerAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"oldOwner","type":"address"}],"name":"OwnerRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"newRequirement","type":"uint256"}],"name":"RequirementChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_from","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"to","type":"address"},{"indexed":false,"name":"data","type":"bytes"}],"name":"SingleTransact","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"operation","type":"bytes32"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"to","type":"address"},{"indexed":false,"name":"data","type":"bytes"}],"name":"MultiTransact","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"operation","type":"bytes32"},{"indexed":false,"name":"initiator","type":"address"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"to","type":"address"},{"indexed":false,"name":"data","type":"bytes"}],"name":"ConfirmationNeeded","type":"event"}] diff --git a/js/src/jsonrpc/interfaces/parity.js b/js/src/jsonrpc/interfaces/parity.js index 66a8ea962..5dd313e00 100644 --- a/js/src/jsonrpc/interfaces/parity.js +++ b/js/src/jsonrpc/interfaces/parity.js @@ -109,6 +109,15 @@ export default { } }, + dappsInterface: { + desc: 'Returns the interface the dapps are running on, error if not enabled', + params: [], + returns: { + type: String, + desc: 'The interface' + } + }, + defaultExtraData: { desc: 'Returns the default extra data', params: [], @@ -347,6 +356,20 @@ export default { } }, + nextNonce: { + desc: 'Returns next available nonce for transaction from given account. Includes pending block and transaction queue.', + params: [ + { + type: Address, + desc: 'Account' + } + ], + returns: { + type: Quantity, + desc: 'Next valid nonce' + } + }, + nodeName: { desc: 'Returns node name (identity)', params: [], diff --git a/js/src/jsonrpc/interfaces/signer.js b/js/src/jsonrpc/interfaces/signer.js index f394dbb61..f50bb1115 100644 --- a/js/src/jsonrpc/interfaces/signer.js +++ b/js/src/jsonrpc/interfaces/signer.js @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import { Quantity } from '../types'; +import { Quantity, Data } from '../types'; export default { generateAuthorizationToken: { @@ -57,6 +57,24 @@ export default { } }, + confirmRequestRaw: { + desc: 'Confirm a request in the signer queue providing signed request.', + params: [ + { + type: Quantity, + desc: 'The request id' + }, + { + type: Data, + desc: 'Signed request (transaction RLP)' + } + ], + returns: { + type: Boolean, + desc: 'The status of the confirmation' + } + }, + rejectRequest: { desc: 'Rejects a request in the signer queue', params: [ diff --git a/js/src/modals/AddContract/addContract.css b/js/src/modals/AddContract/addContract.css new file mode 100644 index 000000000..ed92a86d5 --- /dev/null +++ b/js/src/modals/AddContract/addContract.css @@ -0,0 +1,32 @@ +/* Copyright 2015, 2016 Ethcore (UK) Ltd. +/* This file is part of Parity. +/* +/* Parity is free software: you can redistribute it and/or modify +/* it under the terms of the GNU General Public License as published by +/* the Free Software Foundation, either version 3 of the License, or +/* (at your option) any later version. +/* +/* Parity is distributed in the hope that it will be useful, +/* but WITHOUT ANY WARRANTY; without even the implied warranty of +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +/* GNU General Public License for more details. +/* +/* You should have received a copy of the GNU General Public License +/* along with Parity. If not, see . +*/ + +.spaced { + margin: 0.25em 0; +} + +.typeContainer { + display: flex; + flex-direction: column; + + .desc { + font-size: 0.8em; + margin-bottom: 0.5em; + color: #ccc; + z-index: 2; + } +} diff --git a/js/src/modals/AddContract/addContract.js b/js/src/modals/AddContract/addContract.js index 418378136..57773d2dc 100644 --- a/js/src/modals/AddContract/addContract.js +++ b/js/src/modals/AddContract/addContract.js @@ -17,10 +17,38 @@ import React, { Component, PropTypes } from 'react'; import ContentAdd from 'material-ui/svg-icons/content/add'; import ContentClear from 'material-ui/svg-icons/content/clear'; +import NavigationArrowForward from 'material-ui/svg-icons/navigation/arrow-forward'; +import NavigationArrowBack from 'material-ui/svg-icons/navigation/arrow-back'; + +import { RadioButton, RadioButtonGroup } from 'material-ui/RadioButton'; import { Button, Modal, Form, Input, InputAddress } from '../../ui'; import { ERRORS, validateAbi, validateAddress, validateName } from '../../util/validation'; +import { eip20, wallet } from '../../contracts/abi'; +import styles from './addContract.css'; + +const ABI_TYPES = [ + { + label: 'Token', readOnly: true, value: JSON.stringify(eip20), + type: 'token', + description: (A standard ERC 20 token) + }, + { + label: 'Multisig Wallet', readOnly: true, + type: 'multisig', + value: JSON.stringify(wallet), + description: (Official Multisig contract: see contract code) + }, + { + label: 'Custom Contract', value: '', + type: 'custom', + description: 'Contract created from custom ABI' + } +]; + +const STEPS = [ 'choose a contract type', 'enter contract details' ]; + export default class AddContract extends Component { static contextTypes = { api: PropTypes.object.isRequired @@ -34,44 +62,101 @@ export default class AddContract extends Component { state = { abi: '', abiError: ERRORS.invalidAbi, + abiType: ABI_TYPES[2], + abiTypeIndex: 2, abiParsed: null, address: '', addressError: ERRORS.invalidAddress, name: '', nameError: ERRORS.invalidName, - description: '' + description: '', + step: 0 }; + componentDidMount () { + this.onChangeABIType(null, this.state.abiTypeIndex); + } + render () { + const { step } = this.state; + return ( - { this.renderFields() } + steps={ STEPS } + current={ step } + > + { this.renderStep(step) } ); } + renderStep (step) { + switch (step) { + case 0: + return this.renderContractTypeSelector(); + default: + return this.renderFields(); + } + } + + renderContractTypeSelector () { + const { abiTypeIndex } = this.state; + + return ( + + { this.renderAbiTypes() } + + ); + } + renderDialogActions () { - const { addressError, nameError } = this.state; + const { addressError, nameError, step } = this.state; const hasError = !!(addressError || nameError); - return ([ + const cancelBtn = (