Merge branch 'lightrpc' into light-txq
This commit is contained in:
		
						commit
						2c43b02e13
					
				
							
								
								
									
										55
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										55
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @ -122,6 +122,14 @@ dependencies = [ | ||||
|  "rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "bit-set" | ||||
| version = "0.2.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| dependencies = [ | ||||
|  "bit-vec 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "bit-set" | ||||
| version = "0.4.0" | ||||
| @ -381,6 +389,7 @@ dependencies = [ | ||||
|  "ethkey 0.2.0", | ||||
|  "ethstore 0.1.0", | ||||
|  "evmjit 1.6.0", | ||||
|  "hardware-wallet 1.6.0", | ||||
|  "heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)", | ||||
|  "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| @ -863,6 +872,18 @@ name = "hamming" | ||||
| version = "0.1.3" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "hardware-wallet" | ||||
| version = "1.6.0" | ||||
| dependencies = [ | ||||
|  "ethcore-bigint 0.1.2", | ||||
|  "ethkey 0.2.0", | ||||
|  "hidapi 0.3.1 (git+https://github.com/ethcore/hidapi-rs)", | ||||
|  "libusb 0.3.0 (git+https://github.com/ethcore/libusb-rs)", | ||||
|  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "parking_lot 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "heapsize" | ||||
| version = "0.3.6" | ||||
| @ -871,6 +892,15 @@ dependencies = [ | ||||
|  "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "hidapi" | ||||
| version = "0.3.1" | ||||
| source = "git+https://github.com/ethcore/hidapi-rs#9a127c1dca7e327e4fdd428406a76c9f5ef48563" | ||||
| dependencies = [ | ||||
|  "gcc 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "hpack" | ||||
| version = "0.2.0" | ||||
| @ -1108,6 +1138,25 @@ name = "libc" | ||||
| version = "0.2.16" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "libusb" | ||||
| version = "0.3.0" | ||||
| source = "git+https://github.com/ethcore/libusb-rs#32bacf61abd981d5cbd4a8fecca5a2dc0b762a96" | ||||
| dependencies = [ | ||||
|  "bit-set 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "libusb-sys 0.2.3 (git+https://github.com/ethcore/libusb-sys)", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "libusb-sys" | ||||
| version = "0.2.3" | ||||
| source = "git+https://github.com/ethcore/libusb-sys#c10b1180646c9dc3f23a9b6bb825abcd3b7487ce" | ||||
| dependencies = [ | ||||
|  "gcc 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "linked-hash-map" | ||||
| version = "0.2.1" | ||||
| @ -1575,7 +1624,7 @@ dependencies = [ | ||||
| [[package]] | ||||
| name = "parity-ui-precompiled" | ||||
| version = "1.4.0" | ||||
| source = "git+https://github.com/ethcore/js-precompiled.git#cb0dd77b70c552bb68288a94c7d5d37ecdd611c8" | ||||
| source = "git+https://github.com/ethcore/js-precompiled.git#086ef689513b478463289c5b5845fb6a232f17ec" | ||||
| dependencies = [ | ||||
|  "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| ] | ||||
| @ -2487,6 +2536,7 @@ dependencies = [ | ||||
| "checksum aster 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)" = "07d344974f0a155f091948aa389fb1b912d3a58414fbdb9c8d446d193ee3496a" | ||||
| "checksum base32 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1b9605ba46d61df0410d8ac686b0007add8172eba90e8e909c347856fe794d8c" | ||||
| "checksum bigint 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2311bcd71b281e142a095311c22509f0d6bcd87b3000d7dbaa810929b9d6f6ae" | ||||
| "checksum bit-set 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e6e1e6fb1c9e3d6fcdec57216a74eaa03e41f52a22f13a16438251d8e88b89da" | ||||
| "checksum bit-set 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d9bf6104718e80d7b26a68fdbacff3481cfc05df670821affc7e9cbc1884400c" | ||||
| "checksum bit-vec 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "5b97c2c8e8bbb4251754f559df8af22fb264853c7d009084a576cdf12565089d" | ||||
| "checksum bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8dead7461c1127cf637931a1e50934eb6eee8bff2f74433ac7909e9afcee04a3" | ||||
| @ -2526,6 +2576,7 @@ dependencies = [ | ||||
| "checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" | ||||
| "checksum hamming 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "65043da274378d68241eb9a8f8f8aa54e349136f7b8e12f63e3ef44043cc30e1" | ||||
| "checksum heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "abb306abb8d398e053cfb1b3e7b72c2f580be048b85745c52652954f8ad1439c" | ||||
| "checksum hidapi 0.3.1 (git+https://github.com/ethcore/hidapi-rs)" = "<none>" | ||||
| "checksum hpack 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d2da7d3a34cf6406d9d700111b8eafafe9a251de41ae71d8052748259343b58" | ||||
| "checksum httparse 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "46534074dbb80b070d60a5cb8ecadd8963a00a438ae1a95268850a7ef73b67ae" | ||||
| "checksum hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)" = "<none>" | ||||
| @ -2548,6 +2599,8 @@ dependencies = [ | ||||
| "checksum lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "49247ec2a285bb3dcb23cbd9c35193c025e7251bfce77c1d5da97e6362dffe7f" | ||||
| "checksum lazycell 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ce12306c4739d86ee97c23139f3a34ddf0387bbf181bc7929d287025a8c3ef6b" | ||||
| "checksum libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)" = "408014cace30ee0f767b1c4517980646a573ec61a57957aeeabcac8ac0a02e8d" | ||||
| "checksum libusb 0.3.0 (git+https://github.com/ethcore/libusb-rs)" = "<none>" | ||||
| "checksum libusb-sys 0.2.3 (git+https://github.com/ethcore/libusb-sys)" = "<none>" | ||||
| "checksum linked-hash-map 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bda158e0dabeb97ee8a401f4d17e479d6b891a14de0bba79d5cc2d4d325b5e48" | ||||
| "checksum linked-hash-map 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6d262045c5b87c0861b3f004610afd0e2c851e2908d08b6c870cbb9d5f494ecd" | ||||
| "checksum log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ab83497bf8bf4ed2a74259c1c802351fcd67a65baa86394b6ba73c36f4838054" | ||||
|  | ||||
| @ -42,6 +42,7 @@ rlp = { path = "../util/rlp" } | ||||
| ethcore-stratum = { path = "../stratum" } | ||||
| lru-cache = "0.1.0" | ||||
| ethcore-bloom-journal = { path = "../util/bloom" } | ||||
| hardware-wallet = { path = "../hw" } | ||||
| ethabi = "0.2.2" | ||||
| 
 | ||||
| [dependencies.hyper] | ||||
|  | ||||
| @ -29,6 +29,7 @@ use ethstore::{SimpleSecretStore, SecretStore, Error as SSError, EthStore, EthMu | ||||
| use ethstore::dir::MemoryDirectory; | ||||
| use ethstore::ethkey::{Address, Message, Public, Secret, Random, Generator}; | ||||
| use ethjson::misc::AccountMeta; | ||||
| use hardware_wallet::{Error as HardwareError, HardwareWalletManager, KeyPath}; | ||||
| pub use ethstore::ethkey::Signature; | ||||
| 
 | ||||
| /// Type of unlock.
 | ||||
| @ -55,6 +56,10 @@ struct AccountData { | ||||
| pub enum SignError { | ||||
| 	/// Account is not unlocked
 | ||||
| 	NotUnlocked, | ||||
| 	/// Account does not exist.
 | ||||
| 	NotFound, | ||||
| 	/// Low-level hardware device error.
 | ||||
| 	Hardware(HardwareError), | ||||
| 	/// Low-level error from store
 | ||||
| 	SStore(SSError) | ||||
| } | ||||
| @ -63,11 +68,19 @@ impl fmt::Display for SignError { | ||||
| 	fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { | ||||
| 		match *self { | ||||
| 			SignError::NotUnlocked => write!(f, "Account is locked"), | ||||
| 			SignError::NotFound => write!(f, "Account does not exist"), | ||||
| 			SignError::Hardware(ref e) => write!(f, "{}", e), | ||||
| 			SignError::SStore(ref e) => write!(f, "{}", e), | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| impl From<HardwareError> for SignError { | ||||
| 	fn from(e: HardwareError) -> Self { | ||||
| 		SignError::Hardware(e) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| impl From<SSError> for SignError { | ||||
| 	fn from(e: SSError) -> Self { | ||||
| 		SignError::SStore(e) | ||||
| @ -107,17 +120,47 @@ pub struct AccountProvider { | ||||
| 	sstore: Box<SecretStore>, | ||||
| 	/// Accounts unlocked with rolling tokens
 | ||||
| 	transient_sstore: EthMultiStore, | ||||
| 	/// Accounts in hardware wallets.
 | ||||
| 	hardware_store: Option<HardwareWalletManager>, | ||||
| } | ||||
| 
 | ||||
| /// Account management settings.
 | ||||
| pub struct AccountProviderSettings { | ||||
| 	/// Enable hardware wallet support.
 | ||||
| 	pub enable_hardware_wallets: bool, | ||||
| 	/// Use the classic chain key on the hardware wallet.
 | ||||
| 	pub hardware_wallet_classic_key: bool, | ||||
| } | ||||
| 
 | ||||
| impl Default for AccountProviderSettings { | ||||
| 	fn default() -> Self { | ||||
| 		AccountProviderSettings { | ||||
| 			enable_hardware_wallets: false, | ||||
| 			hardware_wallet_classic_key: false, | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| impl AccountProvider { | ||||
| 	/// Creates new account provider.
 | ||||
| 	pub fn new(sstore: Box<SecretStore>) -> Self { | ||||
| 	pub fn new(sstore: Box<SecretStore>, settings: AccountProviderSettings) -> Self { | ||||
| 		let mut hardware_store = None; | ||||
| 		if settings.enable_hardware_wallets { | ||||
| 			match HardwareWalletManager::new() { | ||||
| 				Ok(manager) => { | ||||
| 					manager.set_key_path(if settings.hardware_wallet_classic_key { KeyPath::EthereumClassic } else { KeyPath::Ethereum }); | ||||
| 					hardware_store = Some(manager) | ||||
| 				}, | ||||
| 				Err(e) => warn!("Error initializing hardware wallets: {}", e), | ||||
| 			} | ||||
| 		} | ||||
| 		AccountProvider { | ||||
| 			unlocked: RwLock::new(HashMap::new()), | ||||
| 			address_book: RwLock::new(AddressBook::new(&sstore.local_path())), | ||||
| 			dapps_settings: RwLock::new(DappsSettingsStore::new(&sstore.local_path())), | ||||
| 			sstore: sstore, | ||||
| 			transient_sstore: transient_sstore(), | ||||
| 			hardware_store: hardware_store, | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| @ -129,6 +172,7 @@ impl AccountProvider { | ||||
| 			dapps_settings: RwLock::new(DappsSettingsStore::transient()), | ||||
| 			sstore: Box::new(EthStore::open(Box::new(MemoryDirectory::default())).expect("MemoryDirectory load always succeeds; qed")), | ||||
| 			transient_sstore: transient_sstore(), | ||||
| 			hardware_store: None, | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| @ -176,6 +220,12 @@ impl AccountProvider { | ||||
| 		Ok(accounts.into_iter().map(|a| a.address).collect()) | ||||
| 	} | ||||
| 
 | ||||
| 	/// Returns addresses of hardware accounts.
 | ||||
| 	pub fn hardware_accounts(&self) -> Result<Vec<Address>, Error> { | ||||
| 		let accounts = self.hardware_store.as_ref().map_or(Vec::new(), |h| h.list_wallets()); | ||||
| 		Ok(accounts.into_iter().map(|a| a.address).collect()) | ||||
| 	} | ||||
| 
 | ||||
| 	/// Sets a whitelist of accounts exposed for unknown dapps.
 | ||||
| 	/// `None` means that all accounts will be visible.
 | ||||
| 	pub fn set_new_dapps_whitelist(&self, accounts: Option<Vec<Address>>) -> Result<(), Error> { | ||||
| @ -271,21 +321,43 @@ impl AccountProvider { | ||||
| 
 | ||||
| 	/// Returns each account along with name and meta.
 | ||||
| 	pub fn accounts_info(&self) -> Result<HashMap<Address, AccountMeta>, Error> { | ||||
| 		let r: HashMap<Address, AccountMeta> = self.sstore.accounts()? | ||||
| 		let r = self.sstore.accounts()? | ||||
| 			.into_iter() | ||||
| 			.map(|a| (a.address.clone(), self.account_meta(a.address).ok().unwrap_or_default())) | ||||
| 			.collect(); | ||||
| 		Ok(r) | ||||
| 	} | ||||
| 
 | ||||
| 	/// Returns each hardware account along with name and meta.
 | ||||
| 	pub fn hardware_accounts_info(&self) -> Result<HashMap<Address, AccountMeta>, Error> { | ||||
| 		let r = self.hardware_accounts()? | ||||
| 			.into_iter() | ||||
| 			.map(|address| (address.clone(), self.account_meta(address).ok().unwrap_or_default())) | ||||
| 			.collect(); | ||||
| 		Ok(r) | ||||
| 	} | ||||
| 
 | ||||
| 	/// Returns each hardware account along with name and meta.
 | ||||
| 	pub fn is_hardware_address(&self, address: Address) -> bool { | ||||
| 		self.hardware_store.as_ref().and_then(|s| s.wallet_info(&address)).is_some() | ||||
| 	} | ||||
| 
 | ||||
| 	/// Returns each account along with name and meta.
 | ||||
| 	pub fn account_meta(&self, address: Address) -> Result<AccountMeta, Error> { | ||||
| 		let account = self.sstore.account_ref(&address)?; | ||||
| 		Ok(AccountMeta { | ||||
| 			name: self.sstore.name(&account)?, | ||||
| 			meta: self.sstore.meta(&account)?, | ||||
| 			uuid: self.sstore.uuid(&account).ok().map(Into::into),	// allowed to not have a Uuid
 | ||||
| 		}) | ||||
| 		if let Some(info) = self.hardware_store.as_ref().and_then(|s| s.wallet_info(&address)) { | ||||
| 			Ok(AccountMeta { | ||||
| 				name: info.name, | ||||
| 				meta: info.manufacturer, | ||||
| 				uuid: None, | ||||
| 			}) | ||||
| 		} else { | ||||
| 			let account = self.sstore.account_ref(&address)?; | ||||
| 			Ok(AccountMeta { | ||||
| 				name: self.sstore.name(&account)?, | ||||
| 				meta: self.sstore.meta(&account)?, | ||||
| 				uuid: self.sstore.uuid(&account).ok().map(Into::into),	// allowed to not have a Uuid
 | ||||
| 			}) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	/// Returns each account along with name and meta.
 | ||||
| @ -505,6 +577,15 @@ impl AccountProvider { | ||||
| 		self.sstore.set_vault_meta(name, meta) | ||||
| 			.map_err(Into::into) | ||||
| 	} | ||||
| 
 | ||||
| 	/// Sign transaction with hardware wallet.
 | ||||
| 	pub fn sign_with_hardware(&self, address: Address, transaction: &[u8]) -> Result<Signature, SignError> { | ||||
| 		match self.hardware_store.as_ref().map(|s| s.sign_transaction(&address, transaction)) { | ||||
| 			None | Some(Err(HardwareError::KeyNotFound)) => Err(SignError::NotFound), | ||||
| 			Some(Err(e)) => Err(From::from(e)), | ||||
| 			Some(Ok(s)) => Ok(s), | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| #[cfg(test)] | ||||
|  | ||||
| @ -105,6 +105,7 @@ extern crate linked_hash_map; | ||||
| extern crate lru_cache; | ||||
| extern crate ethcore_stratum; | ||||
| extern crate ethabi; | ||||
| extern crate hardware_wallet; | ||||
| 
 | ||||
| #[macro_use] | ||||
| extern crate log; | ||||
|  | ||||
| @ -154,7 +154,7 @@ impl Transaction { | ||||
| 	pub fn hash(&self, network_id: Option<u64>) -> H256 { | ||||
| 		let mut stream = RlpStream::new(); | ||||
| 		self.rlp_append_unsigned_transaction(&mut stream, network_id); | ||||
| 		stream.out().sha3() | ||||
| 		stream.as_raw().sha3() | ||||
| 	} | ||||
| 
 | ||||
| 	/// Signs the transaction as coming from `sender`.
 | ||||
|  | ||||
| @ -21,6 +21,53 @@ use Public; | ||||
| use bigint::hash::{H256, FixedHash}; | ||||
| pub use self::derivation::Error as DerivationError; | ||||
| 
 | ||||
| /// Represents label that can be stored as a part of key derivation
 | ||||
| pub trait Label { | ||||
| 	/// Length of the data that label occupies
 | ||||
| 	fn len() -> usize; | ||||
| 
 | ||||
| 	/// Store label data to the key derivation sequence
 | ||||
| 	/// Must not use more than `len()` bytes from slice
 | ||||
| 	fn store(&self, target: &mut [u8]); | ||||
| } | ||||
| 
 | ||||
| impl Label for u32 { | ||||
| 	fn len() -> usize { 4 } | ||||
| 
 | ||||
| 	fn store(&self, target: &mut [u8]) { | ||||
| 		use byteorder::{BigEndian, ByteOrder}; | ||||
| 
 | ||||
| 		BigEndian::write_u32(&mut target[0..4], *self); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| /// Key derivation over generic label `T`
 | ||||
| pub enum Derivation<T: Label> { | ||||
| 	/// Soft key derivation (allow proof of parent)
 | ||||
| 	Soft(T), | ||||
| 	/// Hard key derivation (does not allow proof of parent)
 | ||||
| 	Hard(T), | ||||
| } | ||||
| 
 | ||||
| impl From<u32> for Derivation<u32> { | ||||
| 	fn from(index: u32) -> Self { | ||||
| 		if index < (2 << 30) { | ||||
| 			Derivation::Soft(index) | ||||
| 		} | ||||
| 		else { | ||||
| 			Derivation::Hard(index) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| impl Label for H256 { | ||||
| 	fn len() -> usize { 32 } | ||||
| 
 | ||||
| 	fn store(&self, target: &mut [u8]) { | ||||
| 		self.copy_to(&mut target[0..32]); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| /// Extended secret key, allows deterministic derivation of subsequent keys.
 | ||||
| pub struct ExtendedSecret { | ||||
| 	secret: Secret, | ||||
| @ -49,7 +96,7 @@ impl ExtendedSecret { | ||||
| 	} | ||||
| 
 | ||||
| 	/// Derive new private key
 | ||||
| 	pub fn derive(&self, index: u32) -> ExtendedSecret { | ||||
| 	pub fn derive<T>(&self, index: Derivation<T>) -> ExtendedSecret where T: Label { | ||||
| 		let (derived_key, next_chain_code) = derivation::private(*self.secret, self.chain_code, index); | ||||
| 
 | ||||
| 		let derived_secret = Secret::from_slice(&*derived_key) | ||||
| @ -88,7 +135,7 @@ impl ExtendedPublic { | ||||
| 
 | ||||
| 	/// Derive new public key
 | ||||
| 	/// Operation is defined only for index belongs [0..2^31)
 | ||||
| 	pub fn derive(&self, index: u32) -> Result<Self, DerivationError> { | ||||
| 	pub fn derive<T>(&self, index: Derivation<T>) -> Result<Self, DerivationError> where T: Label { | ||||
| 		let (derived_key, next_chain_code) = derivation::public(self.public, self.chain_code, index)?; | ||||
| 		Ok(ExtendedPublic::new(derived_key, next_chain_code)) | ||||
| 	} | ||||
| @ -147,7 +194,7 @@ impl ExtendedKeyPair { | ||||
| 		&self.public | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn derive(&self, index: u32) -> Result<Self, DerivationError> { | ||||
| 	pub fn derive<T>(&self, index: Derivation<T>) -> Result<Self, DerivationError> where T: Label { | ||||
| 		let derived = self.secret.derive(index); | ||||
| 
 | ||||
| 		Ok(ExtendedKeyPair { | ||||
| @ -167,11 +214,11 @@ mod derivation { | ||||
| 	use rcrypto::sha2::Sha512; | ||||
| 	use bigint::hash::{H512, H256, FixedHash}; | ||||
| 	use bigint::prelude::{U256, U512, Uint}; | ||||
| 	use byteorder::{BigEndian, ByteOrder}; | ||||
| 	use secp256k1; | ||||
| 	use secp256k1::key::{SecretKey, PublicKey}; | ||||
| 	use SECP256K1; | ||||
| 	use keccak; | ||||
| 	use super::{Label, Derivation}; | ||||
| 
 | ||||
| 	#[derive(Debug)] | ||||
| 	pub enum Error { | ||||
| @ -183,20 +230,18 @@ mod derivation { | ||||
| 
 | ||||
| 	// Deterministic derivation of the key using secp256k1 elliptic curve.
 | ||||
| 	// Derivation can be either hardened or not.
 | ||||
| 	// For hardened derivation, pass index at least 2^31
 | ||||
| 	// For hardened derivation, pass u32 index at least 2^31 or custom Derivation::Hard(T) enum
 | ||||
| 	//
 | ||||
| 	// Can panic if passed `private_key` is not a valid secp256k1 private key
 | ||||
| 	// (outside of (0..curve_n()]) field
 | ||||
| 	pub fn private(private_key: H256, chain_code: H256, index: u32) -> (H256, H256) { | ||||
| 		if index < (2 << 30) { | ||||
| 			private_soft(private_key, chain_code, index) | ||||
| 		} | ||||
| 		else { | ||||
| 			private_hard(private_key, chain_code, index) | ||||
| 	pub fn private<T>(private_key: H256, chain_code: H256, index: Derivation<T>) -> (H256, H256) where T: Label { | ||||
| 		match index { | ||||
| 			Derivation::Soft(index) => private_soft(private_key, chain_code, index), | ||||
| 			Derivation::Hard(index) => private_hard(private_key, chain_code, index), | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	fn hmac_pair(data: [u8; 37], private_key: H256, chain_code: H256) -> (H256, H256) { | ||||
| 	fn hmac_pair(data: &[u8], private_key: H256, chain_code: H256) -> (H256, H256) { | ||||
| 		let private: U256 = private_key.into(); | ||||
| 
 | ||||
| 		// produces 512-bit derived hmac (I)
 | ||||
| @ -216,8 +261,8 @@ mod derivation { | ||||
| 
 | ||||
| 	// Can panic if passed `private_key` is not a valid secp256k1 private key
 | ||||
| 	// (outside of (0..curve_n()]) field
 | ||||
| 	fn private_soft(private_key: H256, chain_code: H256, index: u32) -> (H256, H256) { | ||||
| 		let mut data = [0u8; 37]; | ||||
| 	fn private_soft<T>(private_key: H256, chain_code: H256, index: T) -> (H256, H256) where T: Label { | ||||
| 		let mut data = vec![0u8; 33 + T::len()]; | ||||
| 
 | ||||
| 		let sec_private = SecretKey::from_slice(&SECP256K1, &*private_key) | ||||
| 			.expect("Caller should provide valid private key"); | ||||
| @ -226,26 +271,26 @@ mod derivation { | ||||
| 		let public_serialized = sec_public.serialize_vec(&SECP256K1, true); | ||||
| 
 | ||||
| 		// curve point (compressed public key) --  index
 | ||||
| 		//             0.33                    --  33..37
 | ||||
| 		//             0.33                    --  33..end
 | ||||
| 		data[0..33].copy_from_slice(&public_serialized); | ||||
| 		BigEndian::write_u32(&mut data[33..37], index); | ||||
| 		index.store(&mut data[33..]); | ||||
| 
 | ||||
| 		hmac_pair(data, private_key, chain_code) | ||||
| 		hmac_pair(&data, private_key, chain_code) | ||||
| 	} | ||||
| 
 | ||||
| 	// Deterministic derivation of the key using secp256k1 elliptic curve
 | ||||
| 	// This is hardened derivation and does not allow to associate
 | ||||
| 	// corresponding public keys of the original and derived private keys
 | ||||
| 	fn private_hard(private_key: H256, chain_code: H256, index: u32) -> (H256, H256) { | ||||
| 		let mut data = [0u8; 37]; | ||||
| 	fn private_hard<T>(private_key: H256, chain_code: H256, index: T) -> (H256, H256) where T: Label { | ||||
| 		let mut data: Vec<u8> = vec![0u8; 33 + T::len()]; | ||||
| 		let private: U256 = private_key.into(); | ||||
| 
 | ||||
| 		// 0x00 (padding) -- private_key --  index
 | ||||
| 		//  0             --    1..33    -- 33..37
 | ||||
| 		//  0             --    1..33    -- 33..end
 | ||||
| 		private.to_big_endian(&mut data[1..33]); | ||||
| 		BigEndian::write_u32(&mut data[33..37], index); | ||||
| 		index.store(&mut data[33..(33 + T::len())]); | ||||
| 
 | ||||
| 		hmac_pair(data, private_key, chain_code) | ||||
| 		hmac_pair(&data, private_key, chain_code) | ||||
| 	} | ||||
| 
 | ||||
| 	fn private_add(k1: U256, k2: U256) -> U256 { | ||||
| @ -266,11 +311,11 @@ mod derivation { | ||||
| 		H256::from_slice(&secp256k1::constants::CURVE_ORDER).into() | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn public(public_key: H512, chain_code: H256, index: u32) -> Result<(H512, H256), Error> { | ||||
| 		if index >= (2 << 30) { | ||||
| 			// public derivation is only defined on 'soft' index space [0..2^31)
 | ||||
| 			return Err(Error::InvalidHardenedUse) | ||||
| 		} | ||||
| 	pub fn public<T>(public_key: H512, chain_code: H256, derivation: Derivation<T>) -> Result<(H512, H256), Error> where T: Label { | ||||
| 		let index = match derivation { | ||||
| 			Derivation::Soft(index) => index, | ||||
| 			Derivation::Hard(_) => { return Err(Error::InvalidHardenedUse); } | ||||
| 		}; | ||||
| 
 | ||||
| 		let mut public_sec_raw = [0u8; 65]; | ||||
| 		public_sec_raw[0] = 4; | ||||
| @ -278,11 +323,11 @@ mod derivation { | ||||
| 		let public_sec = PublicKey::from_slice(&SECP256K1, &public_sec_raw).map_err(|_| Error::InvalidPoint)?; | ||||
| 		let public_serialized = public_sec.serialize_vec(&SECP256K1, true); | ||||
| 
 | ||||
| 		let mut data = [0u8; 37]; | ||||
| 		let mut data = vec![0u8; 33 + T::len()]; | ||||
| 		// curve point (compressed public key) --  index
 | ||||
| 		//             0.33                    --  33..37
 | ||||
| 		//             0.33                    --  33..end
 | ||||
| 		data[0..33].copy_from_slice(&public_serialized); | ||||
| 		BigEndian::write_u32(&mut data[33..37], index); | ||||
| 		index.store(&mut data[33..(33 + T::len())]); | ||||
| 
 | ||||
| 		// HMAC512SHA produces [derived private(256); new chain code(256)]
 | ||||
| 		let mut hmac = Hmac::new(Sha512::new(), &*chain_code); | ||||
| @ -351,7 +396,7 @@ mod tests { | ||||
| 	use secret::Secret; | ||||
| 	use std::str::FromStr; | ||||
| 	use bigint::hash::{H128, H256}; | ||||
| 	use super::derivation; | ||||
| 	use super::{derivation, Derivation}; | ||||
| 
 | ||||
| 	fn master_chain_basic() -> (H256, H256) { | ||||
| 		let seed = H128::from_str("000102030405060708090a0b0c0d0e0f") | ||||
| @ -375,23 +420,48 @@ mod tests { | ||||
| 
 | ||||
| 		// hardened
 | ||||
| 		assert_eq!(&**extended_secret.secret(), &*secret); | ||||
| 		assert_eq!(&**extended_secret.derive(2147483648).secret(), &"0927453daed47839608e414a3738dfad10aed17c459bbd9ab53f89b026c834b6".into()); | ||||
| 		assert_eq!(&**extended_secret.derive(2147483649).secret(), &"44238b6a29c6dcbe9b401364141ba11e2198c289a5fed243a1c11af35c19dc0f".into()); | ||||
| 		assert_eq!(&**extended_secret.derive(2147483648.into()).secret(), &"0927453daed47839608e414a3738dfad10aed17c459bbd9ab53f89b026c834b6".into()); | ||||
| 		assert_eq!(&**extended_secret.derive(2147483649.into()).secret(), &"44238b6a29c6dcbe9b401364141ba11e2198c289a5fed243a1c11af35c19dc0f".into()); | ||||
| 
 | ||||
| 		// normal
 | ||||
| 		assert_eq!(&**extended_secret.derive(0).secret(), &"bf6a74e3f7b36fc4c96a1e12f31abc817f9f5904f5a8fc27713163d1f0b713f6".into()); | ||||
| 		assert_eq!(&**extended_secret.derive(1).secret(), &"bd4fca9eb1f9c201e9448c1eecd66e302d68d4d313ce895b8c134f512205c1bc".into()); | ||||
| 		assert_eq!(&**extended_secret.derive(2).secret(), &"86932b542d6cab4d9c65490c7ef502d89ecc0e2a5f4852157649e3251e2a3268".into()); | ||||
| 		assert_eq!(&**extended_secret.derive(0.into()).secret(), &"bf6a74e3f7b36fc4c96a1e12f31abc817f9f5904f5a8fc27713163d1f0b713f6".into()); | ||||
| 		assert_eq!(&**extended_secret.derive(1.into()).secret(), &"bd4fca9eb1f9c201e9448c1eecd66e302d68d4d313ce895b8c134f512205c1bc".into()); | ||||
| 		assert_eq!(&**extended_secret.derive(2.into()).secret(), &"86932b542d6cab4d9c65490c7ef502d89ecc0e2a5f4852157649e3251e2a3268".into()); | ||||
| 
 | ||||
| 		let extended_public = ExtendedPublic::from_secret(&extended_secret).expect("Extended public should be created"); | ||||
| 		let derived_public = extended_public.derive(0).expect("First derivation of public should succeed"); | ||||
| 		let derived_public = extended_public.derive(0.into()).expect("First derivation of public should succeed"); | ||||
| 		assert_eq!(&*derived_public.public(), &"f7b3244c96688f92372bfd4def26dc4151529747bab9f188a4ad34e141d47bd66522ff048bc6f19a0a4429b04318b1a8796c000265b4fa200dae5f6dda92dd94".into()); | ||||
| 
 | ||||
| 		let keypair = ExtendedKeyPair::with_secret( | ||||
| 			Secret::from_str("a100df7a048e50ed308ea696dc600215098141cb391e9527329df289f9383f65").unwrap(), | ||||
| 			064.into(), | ||||
| 		); | ||||
| 		assert_eq!(&**keypair.derive(2147483648).expect("Derivation of keypair should succeed").secret().secret(), &"edef54414c03196557cf73774bc97a645c9a1df2164ed34f0c2a78d1375a930c".into()); | ||||
| 		assert_eq!(&**keypair.derive(2147483648u32.into()).expect("Derivation of keypair should succeed").secret().secret(), &"edef54414c03196557cf73774bc97a645c9a1df2164ed34f0c2a78d1375a930c".into()); | ||||
| 	} | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn h256_soft_match() { | ||||
| 		let secret = Secret::from_str("a100df7a048e50ed308ea696dc600215098141cb391e9527329df289f9383f65").unwrap(); | ||||
| 		let derivation_secret = H256::from_str("51eaf04f9dbbc1417dc97e789edd0c37ecda88bac490434e367ea81b71b7b015").unwrap(); | ||||
| 
 | ||||
| 		let extended_secret = ExtendedSecret::with_code(secret.clone(), 0u64.into()); | ||||
| 		let extended_public = ExtendedPublic::from_secret(&extended_secret).expect("Extended public should be created"); | ||||
| 
 | ||||
| 		let derived_secret0 = extended_secret.derive(Derivation::Soft(derivation_secret)); | ||||
| 		let derived_public0 = extended_public.derive(Derivation::Soft(derivation_secret)).expect("First derivation of public should succeed"); | ||||
| 
 | ||||
| 		let public_from_secret0 = ExtendedPublic::from_secret(&derived_secret0).expect("Extended public should be created"); | ||||
| 
 | ||||
| 		assert_eq!(public_from_secret0.public(), derived_public0.public()); | ||||
| 	} | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn h256_hard() { | ||||
| 		let secret = Secret::from_str("a100df7a048e50ed308ea696dc600215098141cb391e9527329df289f9383f65").unwrap(); | ||||
| 		let derivation_secret = H256::from_str("51eaf04f9dbbc1417dc97e789edd0c37ecda88bac490434e367ea81b71b7b015").unwrap(); | ||||
| 		let extended_secret = ExtendedSecret::with_code(secret.clone(), 1u64.into()); | ||||
| 
 | ||||
| 		assert_eq!(&**extended_secret.derive(Derivation::Hard(derivation_secret)).secret(), &"2bc2d696fb744d77ff813b4a1ef0ad64e1e5188b622c54ba917acc5ebc7c5486".into()); | ||||
| 	} | ||||
| 
 | ||||
| 	#[test] | ||||
| @ -400,8 +470,8 @@ mod tests { | ||||
| 		let extended_secret = ExtendedSecret::with_code(secret.clone(), 1.into()); | ||||
| 		let extended_public = ExtendedPublic::from_secret(&extended_secret).expect("Extended public should be created"); | ||||
| 
 | ||||
| 		let derived_secret0 = extended_secret.derive(0); | ||||
| 		let derived_public0 = extended_public.derive(0).expect("First derivation of public should succeed"); | ||||
| 		let derived_secret0 = extended_secret.derive(0.into()); | ||||
| 		let derived_public0 = extended_public.derive(0.into()).expect("First derivation of public should succeed"); | ||||
| 
 | ||||
| 		let public_from_secret0 = ExtendedPublic::from_secret(&derived_secret0).expect("Extended public should be created"); | ||||
| 
 | ||||
| @ -429,7 +499,7 @@ mod tests { | ||||
| 		/// xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7
 | ||||
| 		/// H(0)
 | ||||
| 		test_extended( | ||||
| 			|secret| secret.derive(2147483648), | ||||
| 			|secret| secret.derive(2147483648.into()), | ||||
| 			H256::from_str("edb2e14f9ee77d26dd93b4ecede8d16ed408ce149b6cd80b0715a2d911a0afea") | ||||
| 				.expect("Private should be decoded ok") | ||||
| 		); | ||||
| @ -440,7 +510,7 @@ mod tests { | ||||
| 		/// xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs
 | ||||
| 		/// H(0)/1
 | ||||
| 		test_extended( | ||||
| 			|secret| secret.derive(2147483648).derive(1), | ||||
| 			|secret| secret.derive(2147483648.into()).derive(1.into()), | ||||
| 			H256::from_str("3c6cb8d0f6a264c91ea8b5030fadaa8e538b020f0a387421a12de9319dc93368") | ||||
| 				.expect("Private should be decoded ok") | ||||
| 		); | ||||
|  | ||||
| @ -22,8 +22,9 @@ use std::{env, process, fs}; | ||||
| use std::io::Read; | ||||
| use docopt::Docopt; | ||||
| use ethstore::ethkey::Address; | ||||
| use ethstore::dir::{KeyDirectory, ParityDirectory, DiskDirectory, GethDirectory, DirectoryType}; | ||||
| use ethstore::{EthStore, SecretStore, import_accounts, Error, PresaleWallet}; | ||||
| use ethstore::dir::{KeyDirectory, ParityDirectory, RootDiskDirectory, GethDirectory, DirectoryType}; | ||||
| use ethstore::{EthStore, SimpleSecretStore, SecretStore, import_accounts, Error, PresaleWallet, | ||||
| 	SecretVaultRef, StoreAccountRef}; | ||||
| 
 | ||||
| pub const USAGE: &'static str = r#" | ||||
| Ethereum key management. | ||||
| @ -97,7 +98,7 @@ fn key_dir(location: &str) -> Result<Box<KeyDirectory>, Error> { | ||||
| 		"parity-test" => Box::new(ParityDirectory::create(DirectoryType::Testnet)?), | ||||
| 		"geth" => Box::new(GethDirectory::create(DirectoryType::Main)?), | ||||
| 		"geth-test" => Box::new(GethDirectory::create(DirectoryType::Testnet)?), | ||||
| 		path => Box::new(DiskDirectory::create(path)?), | ||||
| 		path => Box::new(RootDiskDirectory::create(path)?), | ||||
| 	}; | ||||
| 
 | ||||
| 	Ok(dir) | ||||
| @ -130,16 +131,17 @@ fn execute<S, I>(command: I) -> Result<String, Error> where I: IntoIterator<Item | ||||
| 	return if args.cmd_insert { | ||||
| 		let secret = args.arg_secret.parse().map_err(|_| Error::InvalidSecret)?; | ||||
| 		let password = load_password(&args.arg_password)?; | ||||
| 		let address = store.insert_account(secret, &password)?; | ||||
| 		let address = store.insert_account(SecretVaultRef::Root, secret, &password)?; | ||||
| 		Ok(format!("0x{:?}", address)) | ||||
| 	} else if args.cmd_change_pwd { | ||||
| 		let address = args.arg_address.parse().map_err(|_| Error::InvalidAccount)?; | ||||
| 		let old_pwd = load_password(&args.arg_old_pwd)?; | ||||
| 		let new_pwd = load_password(&args.arg_new_pwd)?; | ||||
| 		let ok = store.change_password(&address, &old_pwd, &new_pwd).is_ok(); | ||||
| 		let ok = store.change_password(&StoreAccountRef::root(address), &old_pwd, &new_pwd).is_ok(); | ||||
| 		Ok(format!("{}", ok)) | ||||
| 	} else if args.cmd_list { | ||||
| 		let accounts = store.accounts()?; | ||||
| 		let accounts: Vec<_> = accounts.into_iter().map(|a| a.address).collect(); | ||||
| 		Ok(format_accounts(&accounts)) | ||||
| 	} else if args.cmd_import { | ||||
| 		let src = key_dir(&args.flag_src)?; | ||||
| @ -150,23 +152,23 @@ fn execute<S, I>(command: I) -> Result<String, Error> where I: IntoIterator<Item | ||||
| 		let wallet = PresaleWallet::open(&args.arg_path)?; | ||||
| 		let password = load_password(&args.arg_password)?; | ||||
| 		let kp = wallet.decrypt(&password)?; | ||||
| 		let address = store.insert_account(kp.secret().clone(), &password)?; | ||||
| 		let address = store.insert_account(SecretVaultRef::Root, kp.secret().clone(), &password)?; | ||||
| 		Ok(format!("0x{:?}", address)) | ||||
| 	} else if args.cmd_remove { | ||||
| 		let address = args.arg_address.parse().map_err(|_| Error::InvalidAccount)?; | ||||
| 		let password = load_password(&args.arg_password)?; | ||||
| 		let ok = store.remove_account(&address, &password).is_ok(); | ||||
| 		let ok = store.remove_account(&StoreAccountRef::root(address), &password).is_ok(); | ||||
| 		Ok(format!("{}", ok)) | ||||
| 	} else if args.cmd_sign { | ||||
| 		let address = args.arg_address.parse().map_err(|_| Error::InvalidAccount)?; | ||||
| 		let message = args.arg_message.parse().map_err(|_| Error::InvalidMessage)?; | ||||
| 		let password = load_password(&args.arg_password)?; | ||||
| 		let signature = store.sign(&address, &password, &message)?; | ||||
| 		let signature = store.sign(&StoreAccountRef::root(address), &password, &message)?; | ||||
| 		Ok(format!("0x{:?}", signature)) | ||||
| 	} else if args.cmd_public { | ||||
| 		let address = args.arg_address.parse().map_err(|_| Error::InvalidAccount)?; | ||||
| 		let password = load_password(&args.arg_password)?; | ||||
| 		let public = store.public(&address, &password)?; | ||||
| 		let public = store.public(&StoreAccountRef::root(address), &password)?; | ||||
| 		Ok(format!("0x{:?}", public)) | ||||
| 	} else { | ||||
| 		Ok(format!("{}", USAGE)) | ||||
|  | ||||
| @ -234,6 +234,10 @@ impl<T> VaultKeyDirectoryProvider for DiskDirectory<T> where T: KeyFileManager { | ||||
| 			}) | ||||
| 			.collect()) | ||||
| 	} | ||||
| 
 | ||||
| 	fn vault_meta(&self, name: &str) -> Result<String, Error> { | ||||
| 		VaultDiskDirectory::meta_at(&self.path, name) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| impl KeyFileManager for DiskKeyFileManager { | ||||
| @ -242,7 +246,12 @@ impl KeyFileManager for DiskKeyFileManager { | ||||
| 		Ok(SafeAccount::from_file(key_file, filename)) | ||||
| 	} | ||||
| 
 | ||||
| 	fn write<T>(&self, account: SafeAccount, writer: &mut T) -> Result<(), Error> where T: io::Write { | ||||
| 	fn write<T>(&self, mut account: SafeAccount, writer: &mut T) -> Result<(), Error> where T: io::Write { | ||||
| 		// when account is moved back to root directory from vault
 | ||||
| 		// => remove vault field from meta
 | ||||
| 		account.meta = json::remove_vault_name_from_json_meta(&account.meta) | ||||
| 			.map_err(|err| Error::Custom(format!("{:?}", err)))?; | ||||
| 
 | ||||
| 		let key_file: json::KeyFile = account.into(); | ||||
| 		key_file.write(writer).map_err(|e| Error::Custom(format!("{:?}", e))) | ||||
| 	} | ||||
|  | ||||
| @ -72,6 +72,8 @@ pub trait VaultKeyDirectoryProvider { | ||||
| 	fn open(&self, name: &str, key: VaultKey) -> Result<Box<VaultKeyDirectory>, Error>; | ||||
| 	/// List all vaults
 | ||||
| 	fn list_vaults(&self) -> Result<Vec<String>, Error>; | ||||
| 	/// Get vault meta
 | ||||
| 	fn vault_meta(&self, name: &str) -> Result<String, Error>; | ||||
| } | ||||
| 
 | ||||
| /// Vault directory
 | ||||
|  | ||||
| @ -67,11 +67,23 @@ impl VaultDiskDirectory { | ||||
| 		} | ||||
| 
 | ||||
| 		// check that passed key matches vault file
 | ||||
| 		let meta = read_vault_file(&vault_dir_path, &key)?; | ||||
| 		let meta = read_vault_file(&vault_dir_path, Some(&key))?; | ||||
| 
 | ||||
| 		Ok(DiskDirectory::new(vault_dir_path, VaultKeyFileManager::new(name, key, &meta))) | ||||
| 	} | ||||
| 
 | ||||
| 	/// Read vault meta without actually opening the vault
 | ||||
| 	pub fn meta_at<P>(root: P, name: &str) -> Result<String, Error> where P: AsRef<Path> { | ||||
| 		// check that vault directory exists
 | ||||
| 		let vault_dir_path = make_vault_dir_path(root, name, true)?; | ||||
| 		if !vault_dir_path.is_dir() { | ||||
| 			return Err(Error::VaultNotFound); | ||||
| 		} | ||||
| 
 | ||||
| 		// check that passed key matches vault file
 | ||||
| 		read_vault_file(&vault_dir_path, None) | ||||
| 	} | ||||
| 
 | ||||
| 	fn create_temp_vault(&self, key: VaultKey) -> Result<VaultDiskDirectory, Error> { | ||||
| 		let original_path = self.path().expect("self is instance of DiskDirectory; DiskDirectory always returns path; qed"); | ||||
| 		let mut path: PathBuf = original_path.clone(); | ||||
| @ -241,7 +253,7 @@ fn create_vault_file<P>(vault_dir_path: P, key: &VaultKey, meta: &str) -> Result | ||||
| } | ||||
| 
 | ||||
| /// When vault is opened => we must check that password matches && read metadata
 | ||||
| fn read_vault_file<P>(vault_dir_path: P, key: &VaultKey) -> Result<String, Error> where P: AsRef<Path> { | ||||
| fn read_vault_file<P>(vault_dir_path: P, key: Option<&VaultKey>) -> Result<String, Error> where P: AsRef<Path> { | ||||
| 	let mut vault_file_path: PathBuf = vault_dir_path.as_ref().into(); | ||||
| 	vault_file_path.push(VAULT_FILE_NAME); | ||||
| 
 | ||||
| @ -250,10 +262,12 @@ fn read_vault_file<P>(vault_dir_path: P, key: &VaultKey) -> Result<String, Error | ||||
| 	let vault_file_meta = vault_file_contents.meta.unwrap_or("{}".to_owned()); | ||||
| 	let vault_file_crypto: Crypto = vault_file_contents.crypto.into(); | ||||
| 
 | ||||
| 	let password_bytes = vault_file_crypto.decrypt(&key.password)?; | ||||
| 	let password_hash = key.password.sha3(); | ||||
| 	if &*password_hash != password_bytes.as_slice() { | ||||
| 		return Err(Error::InvalidPassword); | ||||
| 	if let Some(key) = key { | ||||
| 		let password_bytes = vault_file_crypto.decrypt(&key.password)?; | ||||
| 		let password_hash = key.password.sha3(); | ||||
| 		if &*password_hash != password_bytes.as_slice() { | ||||
| 			return Err(Error::InvalidPassword); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	Ok(vault_file_meta) | ||||
| @ -264,7 +278,7 @@ mod test { | ||||
| 	use std::fs; | ||||
| 	use std::io::Write; | ||||
| 	use std::path::PathBuf; | ||||
| 	use dir::{VaultKey, VaultKeyDirectory}; | ||||
| 	use dir::VaultKey; | ||||
| 	use super::{VAULT_FILE_NAME, check_vault_name, make_vault_dir_path, create_vault_file, read_vault_file, VaultDiskDirectory}; | ||||
| 	use devtools::RandomTempPath; | ||||
| 
 | ||||
| @ -333,7 +347,7 @@ mod test { | ||||
| 		} | ||||
| 
 | ||||
| 		// when
 | ||||
| 		let result = read_vault_file(&dir, &key); | ||||
| 		let result = read_vault_file(&dir, Some(&key)); | ||||
| 
 | ||||
| 		// then
 | ||||
| 		assert!(result.is_ok()); | ||||
| @ -349,7 +363,7 @@ mod test { | ||||
| 		vault_file_path.push(VAULT_FILE_NAME); | ||||
| 
 | ||||
| 		// when
 | ||||
| 		let result = read_vault_file(&dir, &key); | ||||
| 		let result = read_vault_file(&dir, Some(&key)); | ||||
| 
 | ||||
| 		// then
 | ||||
| 		assert!(result.is_err()); | ||||
| @ -362,7 +376,7 @@ mod test { | ||||
| 		} | ||||
| 
 | ||||
| 		// when
 | ||||
| 		let result = read_vault_file(&dir, &key); | ||||
| 		let result = read_vault_file(&dir, Some(&key)); | ||||
| 
 | ||||
| 		// then
 | ||||
| 		assert!(result.is_err()); | ||||
| @ -418,22 +432,4 @@ mod test { | ||||
| 		// then
 | ||||
| 		assert!(vault.is_err()); | ||||
| 	} | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn vault_directory_can_preserve_meta() { | ||||
| 		// given
 | ||||
| 		let temp_path = RandomTempPath::new(); | ||||
| 		let key = VaultKey::new("password", 1024); | ||||
| 		let dir: PathBuf = temp_path.as_path().into(); | ||||
| 		let vault = VaultDiskDirectory::create(&dir, "vault", key.clone()).unwrap(); | ||||
| 
 | ||||
| 		// then
 | ||||
| 		assert_eq!(vault.meta(), "{}".to_owned()); | ||||
| 		assert!(vault.set_meta("Hello, world!!!").is_ok()); | ||||
| 		assert_eq!(vault.meta(), "Hello, world!!!".to_owned()); | ||||
| 
 | ||||
| 		// and when
 | ||||
| 		let vault = VaultDiskDirectory::at(&dir, "vault", key.clone()).unwrap(); | ||||
| 		assert_eq!(vault.meta(), "Hello, world!!!".to_owned()); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -501,10 +501,17 @@ impl SimpleSecretStore for EthMultiStore { | ||||
| 	} | ||||
| 
 | ||||
| 	fn get_vault_meta(&self, name: &str) -> Result<String, Error> { | ||||
| 		// vault meta contains password hint
 | ||||
| 		// => allow reading meta even if vault is not yet opened
 | ||||
| 		self.vaults.lock() | ||||
| 			.get(name) | ||||
| 			.and_then(|v| Some(v.meta())) | ||||
| 			.ok_or(Error::VaultNotFound) | ||||
| 			.and_then(|v| Ok(v.meta())) | ||||
| 			.or_else(|_| { | ||||
| 				let vault_provider = self.dir.as_vault_provider().ok_or(Error::VaultsAreNotSupported)?; | ||||
| 				vault_provider.vault_meta(name) | ||||
| 			}) | ||||
| 			
 | ||||
| 	} | ||||
| 
 | ||||
| 	fn set_vault_meta(&self, name: &str, meta: &str) -> Result<(), Error> { | ||||
| @ -861,4 +868,34 @@ mod tests { | ||||
| 		assert!(opened_vaults.iter().any(|v| &*v == name1)); | ||||
| 		assert!(opened_vaults.iter().any(|v| &*v == name3)); | ||||
| 	} | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn should_manage_vaults_meta() { | ||||
| 		// given
 | ||||
| 		let mut dir = RootDiskDirectoryGuard::new(); | ||||
| 		let store = EthStore::open(dir.key_dir.take().unwrap()).unwrap(); | ||||
| 		let name1 = "vault1"; let password1 = "password1"; | ||||
| 
 | ||||
| 		// when
 | ||||
| 		store.create_vault(name1, password1).unwrap(); | ||||
| 
 | ||||
| 		// then
 | ||||
| 		assert_eq!(store.get_vault_meta(name1).unwrap(), "{}".to_owned()); | ||||
| 		assert!(store.set_vault_meta(name1, "Hello, world!!!").is_ok()); | ||||
| 		assert_eq!(store.get_vault_meta(name1).unwrap(), "Hello, world!!!".to_owned()); | ||||
| 
 | ||||
| 		// and when
 | ||||
| 		store.close_vault(name1).unwrap(); | ||||
| 		store.open_vault(name1, password1).unwrap(); | ||||
| 
 | ||||
| 		// then
 | ||||
| 		assert_eq!(store.get_vault_meta(name1).unwrap(), "Hello, world!!!".to_owned()); | ||||
| 
 | ||||
| 		// and when
 | ||||
| 		store.close_vault(name1).unwrap(); | ||||
| 
 | ||||
| 		// then
 | ||||
| 		assert_eq!(store.get_vault_meta(name1).unwrap(), "Hello, world!!!".to_owned()); | ||||
| 		assert!(store.get_vault_meta("vault2").is_err()); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -81,7 +81,7 @@ impl Visitor for VaultFileVisitor { | ||||
| 		loop { | ||||
| 			match visitor.visit_key()? { | ||||
| 				Some(VaultFileField::Crypto) => { crypto = Some(visitor.visit_value()?); }, | ||||
| 				Some(VaultFileField::Meta) => { meta = Some(visitor.visit_value()?); } | ||||
| 				Some(VaultFileField::Meta) => { meta = visitor.visit_value().ok(); }, // meta is optional
 | ||||
| 				None => { break; }, | ||||
| 			} | ||||
| 		} | ||||
| @ -141,4 +141,29 @@ mod test { | ||||
| 
 | ||||
| 		assert_eq!(file, deserialized); | ||||
| 	} | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn to_and_from_json_no_meta() { | ||||
| 		let file = VaultFile { | ||||
| 			crypto: Crypto { | ||||
| 				cipher: Cipher::Aes128Ctr(Aes128Ctr { | ||||
| 					iv: "0155e3690be19fbfbecabcd440aa284b".into(), | ||||
| 				}), | ||||
| 				ciphertext: "4d6938a1f49b7782".into(), | ||||
| 				kdf: Kdf::Pbkdf2(Pbkdf2 { | ||||
| 					c: 1024, | ||||
| 					dklen: 32, | ||||
| 					prf: Prf::HmacSha256, | ||||
| 					salt: "b6a9338a7ccd39288a86dba73bfecd9101b4f3db9c9830e7c76afdbd4f6872e5".into(), | ||||
| 				}), | ||||
| 				mac: "16381463ea11c6eb2239a9f339c2e780516d29d234ce30ac5f166f9080b5a262".into(), | ||||
| 			}, | ||||
| 			meta: None, | ||||
| 		}; | ||||
| 
 | ||||
| 		let serialized = serde_json::to_string(&file).unwrap(); | ||||
| 		let deserialized = serde_json::from_str(&serialized).unwrap(); | ||||
| 
 | ||||
| 		assert_eq!(file, deserialized); | ||||
| 	} | ||||
| } | ||||
|  | ||||
							
								
								
									
										18
									
								
								hw/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								hw/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,18 @@ | ||||
| [package] | ||||
| description = "Hardware wallet support." | ||||
| homepage = "http://parity.io" | ||||
| license = "GPL-3.0" | ||||
| name = "hardware-wallet" | ||||
| version = "1.6.0" | ||||
| authors = ["Parity Technologies <admin@parity.io>"] | ||||
| 
 | ||||
| [dependencies] | ||||
| log = "0.3" | ||||
| parking_lot = "0.3" | ||||
| hidapi = { git = "https://github.com/ethcore/hidapi-rs" } | ||||
| libusb = { git = "https://github.com/ethcore/libusb-rs" } | ||||
| ethkey = { path = "../ethkey" } | ||||
| ethcore-bigint = { path = "../util/bigint" } | ||||
| 
 | ||||
| [dev-dependencies] | ||||
| rustc-serialize = "0.3" | ||||
							
								
								
									
										364
									
								
								hw/src/ledger.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										364
									
								
								hw/src/ledger.rs
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										183
									
								
								hw/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										183
									
								
								hw/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,183 @@ | ||||
| // Copyright 2015-2017 Parity Technologies (UK) Ltd.
 | ||||
| // This file is part of Parity.
 | ||||
| 
 | ||||
| // Parity is free software: you can redistribute it and/or modify
 | ||||
| // it under the terms of the GNU General Public License as published by
 | ||||
| // the Free Software Foundation, either version 3 of the License, or
 | ||||
| // (at your option) any later version.
 | ||||
| 
 | ||||
| // Parity is distributed in the hope that it will be useful,
 | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | ||||
| // GNU General Public License for more details.
 | ||||
| 
 | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| //! Hardware wallet management.
 | ||||
| 
 | ||||
| extern crate parking_lot; | ||||
| extern crate hidapi; | ||||
| extern crate libusb; | ||||
| extern crate ethkey; | ||||
| extern crate ethcore_bigint; | ||||
| #[macro_use] extern crate log; | ||||
| #[cfg(test)] extern crate rustc_serialize; | ||||
| 
 | ||||
| mod ledger; | ||||
| 
 | ||||
| use std::fmt; | ||||
| use std::thread; | ||||
| use std::sync::atomic; | ||||
| use std::sync::{Arc, Weak}; | ||||
| use std::sync::atomic::AtomicBool; | ||||
| use std::time::Duration; | ||||
| use parking_lot::Mutex; | ||||
| use ethkey::{Address, Signature}; | ||||
| 
 | ||||
| pub use ledger::KeyPath; | ||||
| 
 | ||||
| /// Hardware waller error.
 | ||||
| #[derive(Debug)] | ||||
| pub enum Error { | ||||
| 	/// Ledger device error.
 | ||||
| 	LedgerDevice(ledger::Error), | ||||
| 	/// USB error.
 | ||||
| 	Usb(libusb::Error), | ||||
| 	/// Hardware wallet not found for specified key.
 | ||||
| 	KeyNotFound, | ||||
| } | ||||
| 
 | ||||
| /// Hardware waller information.
 | ||||
| #[derive(Debug, Clone)] | ||||
| pub struct WalletInfo { | ||||
| 	/// Wallet device name.
 | ||||
| 	pub name: String, | ||||
| 	/// Wallet device manufacturer.
 | ||||
| 	pub manufacturer: String, | ||||
| 	/// Wallet device serial number.
 | ||||
| 	pub serial: String, | ||||
| 	/// Ethereum address.
 | ||||
| 	pub address: Address, | ||||
| } | ||||
| 
 | ||||
| impl fmt::Display for Error { | ||||
| 	fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { | ||||
| 		match *self { | ||||
| 			Error::KeyNotFound => write!(f, "Key not found for given address."), | ||||
| 			Error::LedgerDevice(ref e) => write!(f, "{}", e), | ||||
| 			Error::Usb(ref e) => write!(f, "{}", e), | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| impl From<ledger::Error> for Error { | ||||
| 	fn from(err: ledger::Error) -> Error { | ||||
| 		match err { | ||||
| 			ledger::Error::KeyNotFound => Error::KeyNotFound, | ||||
| 			_ => Error::LedgerDevice(err), | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| impl From<libusb::Error> for Error { | ||||
| 	fn from(err: libusb::Error) -> Error { | ||||
| 		Error::Usb(err) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| /// Hardware wallet management interface.
 | ||||
| pub struct HardwareWalletManager { | ||||
| 	update_thread: Option<thread::JoinHandle<()>>, | ||||
| 	exiting: Arc<AtomicBool>, | ||||
| 	ledger: Arc<Mutex<ledger::Manager>>, | ||||
| } | ||||
| 
 | ||||
| struct EventHandler { | ||||
| 	ledger: Weak<Mutex<ledger::Manager>>, | ||||
| } | ||||
| 
 | ||||
| impl libusb::Hotplug for EventHandler { | ||||
| 	fn device_arrived(&mut self, _device: libusb::Device) { | ||||
| 		debug!("USB Device arrived"); | ||||
| 		if let Some(l) = self.ledger.upgrade() { | ||||
| 			for _ in 0..10 { | ||||
| 				// The device might not be visible right away. Try a few times.
 | ||||
| 				if l.lock().update_devices().unwrap_or_else(|e| { | ||||
| 					debug!("Error enumerating Ledger devices: {}", e); | ||||
| 					0 | ||||
| 				}) > 0 { | ||||
| 					break; | ||||
| 				} | ||||
| 				thread::sleep(Duration::from_millis(200)); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	fn device_left(&mut self, _device: libusb::Device) { | ||||
| 		debug!("USB Device lost"); | ||||
| 		if let Some(l) = self.ledger.upgrade() { | ||||
| 			if let Err(e) = l.lock().update_devices() { | ||||
| 				debug!("Error enumerating Ledger devices: {}", e); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| impl HardwareWalletManager { | ||||
| 	pub fn new() -> Result<HardwareWalletManager, Error> { | ||||
| 		let usb_context = Arc::new(libusb::Context::new()?); | ||||
| 		let ledger = Arc::new(Mutex::new(ledger::Manager::new()?)); | ||||
| 		usb_context.register_callback(None, None, None, Box::new(EventHandler { ledger: Arc::downgrade(&ledger) }))?; | ||||
| 		let exiting = Arc::new(AtomicBool::new(false)); | ||||
| 		let thread_exiting = exiting.clone(); | ||||
| 		let l = ledger.clone(); | ||||
| 		let thread = thread::Builder::new().name("hw_wallet".to_string()).spawn(move || { | ||||
| 			if let Err(e) = l.lock().update_devices() { | ||||
| 				debug!("Error updating ledger devices: {}", e); | ||||
| 			} | ||||
| 			loop { | ||||
| 				usb_context.handle_events(Some(Duration::from_millis(500))).unwrap_or_else(|e| debug!("Error processing USB events: {}", e)); | ||||
| 				if thread_exiting.load(atomic::Ordering::Acquire) { | ||||
| 					break; | ||||
| 				} | ||||
| 			} | ||||
| 		}).ok(); | ||||
| 		Ok(HardwareWalletManager { | ||||
| 			update_thread: thread, | ||||
| 			exiting: exiting, | ||||
| 			ledger: ledger, | ||||
| 		}) | ||||
| 	} | ||||
| 
 | ||||
| 	/// Select key derivation path for a chain.
 | ||||
| 	pub fn set_key_path(&self, key_path: KeyPath) { | ||||
| 		self.ledger.lock().set_key_path(key_path); | ||||
| 	} | ||||
| 
 | ||||
| 
 | ||||
| 	/// List connected wallets. This only returns wallets that are ready to be used.
 | ||||
| 	pub fn list_wallets(&self) -> Vec<WalletInfo> { | ||||
| 		self.ledger.lock().list_devices() | ||||
| 	} | ||||
| 
 | ||||
| 	/// Get connected wallet info.
 | ||||
| 	pub fn wallet_info(&self, address: &Address) -> Option<WalletInfo> { | ||||
| 		self.ledger.lock().device_info(address) | ||||
| 	} | ||||
| 
 | ||||
| 	/// Sign transaction data with wallet managing `address`.
 | ||||
| 	pub fn sign_transaction(&self, address: &Address, data: &[u8]) -> Result<Signature, Error> { | ||||
| 		Ok(self.ledger.lock().sign_transaction(address, data)?) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| impl Drop for HardwareWalletManager { | ||||
| 	fn drop(&mut self) { | ||||
| 		self.exiting.store(true, atomic::Ordering::Release); | ||||
| 		if let Some(thread) = self.update_thread.take() { | ||||
| 			thread.thread().unpark(); | ||||
| 			thread.join().ok(); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @ -4,11 +4,11 @@ | ||||
|     "stage-0", "react" | ||||
|   ], | ||||
|   "plugins": [ | ||||
|     "transform-runtime", | ||||
|     "transform-decorators-legacy", | ||||
|     "transform-class-properties", | ||||
|     "transform-object-rest-spread", | ||||
|     "lodash" | ||||
|     "lodash", | ||||
|     "recharts" | ||||
|   ], | ||||
|   "retainLines": true, | ||||
|   "env": { | ||||
| @ -25,7 +25,8 @@ | ||||
|     }, | ||||
|     "test": { | ||||
|       "plugins": [ | ||||
|         ["babel-plugin-webpack-alias", { "config": "webpack/test.js" }] | ||||
|         "transform-runtime", | ||||
|         [ "babel-plugin-webpack-alias", { "config": "webpack/test.js" } ] | ||||
|       ] | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| { | ||||
|   "name": "parity.js", | ||||
|   "version": "0.3.72", | ||||
|   "version": "0.3.78", | ||||
|   "main": "release/index.js", | ||||
|   "jsnext:main": "src/index.js", | ||||
|   "author": "Parity Team <admin@parity.io>", | ||||
| @ -25,18 +25,20 @@ | ||||
|     "Promise" | ||||
|   ], | ||||
|   "scripts": { | ||||
|     "build": "npm run build:lib && npm run build:dll && npm run build:app", | ||||
|     "build": "npm run build:lib && npm run build:dll && npm run build:app && npm run build:embed", | ||||
|     "build:app": "webpack --config webpack/app", | ||||
|     "build:lib": "webpack --config webpack/libraries", | ||||
|     "build:dll": "webpack --config webpack/vendor", | ||||
|     "build:markdown": "babel-node ./scripts/build-rpc-markdown.js", | ||||
|     "build:json": "babel-node ./scripts/build-rpc-json.js", | ||||
|     "ci:build": "npm run ci:build:lib && npm run ci:build:dll && npm run ci:build:app", | ||||
|     "build:embed": "EMBED=1 node webpack/embed", | ||||
|     "ci:build": "npm run ci:build:lib && npm run ci:build:dll && npm run ci:build:app && npm run ci:build:embed", | ||||
|     "ci:build:app": "NODE_ENV=production webpack --config webpack/app", | ||||
|     "ci:build:lib": "NODE_ENV=production webpack --config webpack/libraries", | ||||
|     "ci:build:dll": "NODE_ENV=production webpack --config webpack/vendor", | ||||
|     "ci:build:npm": "NODE_ENV=production webpack --config webpack/npm", | ||||
|     "ci:build:jsonrpc": "babel-node ./scripts/build-rpc-json.js --output .npmjs/jsonrpc", | ||||
|     "ci:build:embed": "NODE_ENV=production EMBED=1 node webpack/embed", | ||||
|     "start": "npm install && npm run build:lib && npm run build:dll && npm run start:app", | ||||
|     "start:app": "node webpack/dev.server", | ||||
|     "clean": "rm -rf ./.build ./.coverage ./.happypack ./.npmjs ./build", | ||||
| @ -53,27 +55,28 @@ | ||||
|     "prepush": "npm run lint:cached" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "babel-cli": "6.18.0", | ||||
|     "babel-core": "6.21.0", | ||||
|     "babel-cli": "6.22.2", | ||||
|     "babel-core": "6.22.1", | ||||
|     "babel-eslint": "7.1.1", | ||||
|     "babel-loader": "6.2.10", | ||||
|     "babel-plugin-lodash": "3.2.11", | ||||
|     "babel-plugin-react-intl": "2.2.0", | ||||
|     "babel-plugin-transform-class-properties": "6.19.0", | ||||
|     "babel-plugin-react-intl": "2.3.1", | ||||
|     "babel-plugin-recharts": "1.1.0", | ||||
|     "babel-plugin-transform-class-properties": "6.22.0", | ||||
|     "babel-plugin-transform-decorators-legacy": "1.3.4", | ||||
|     "babel-plugin-transform-object-rest-spread": "6.20.2", | ||||
|     "babel-plugin-transform-react-remove-prop-types": "0.2.11", | ||||
|     "babel-plugin-transform-runtime": "6.15.0", | ||||
|     "babel-plugin-transform-object-rest-spread": "6.22.0", | ||||
|     "babel-plugin-transform-react-remove-prop-types": "0.3.0", | ||||
|     "babel-plugin-transform-runtime": "6.22.0", | ||||
|     "babel-plugin-webpack-alias": "2.1.2", | ||||
|     "babel-polyfill": "6.20.0", | ||||
|     "babel-preset-env": "1.1.4", | ||||
|     "babel-preset-es2015": "6.18.0", | ||||
|     "babel-preset-es2016": "6.16.0", | ||||
|     "babel-preset-es2017": "6.16.0", | ||||
|     "babel-preset-react": "6.16.0", | ||||
|     "babel-preset-stage-0": "6.16.0", | ||||
|     "babel-register": "6.18.0", | ||||
|     "babel-runtime": "6.20.0", | ||||
|     "babel-polyfill": "6.22.0", | ||||
|     "babel-preset-env": "1.1.8", | ||||
|     "babel-preset-es2015": "6.22.0", | ||||
|     "babel-preset-es2016": "6.22.0", | ||||
|     "babel-preset-es2017": "6.22.0", | ||||
|     "babel-preset-react": "6.22.0", | ||||
|     "babel-preset-stage-0": "6.22.0", | ||||
|     "babel-register": "6.22.0", | ||||
|     "babel-runtime": "6.22.0", | ||||
|     "chai": "3.5.0", | ||||
|     "chai-as-promised": "6.0.0", | ||||
|     "chai-enzyme": "0.6.1", | ||||
| @ -132,7 +135,7 @@ | ||||
|     "stylelint": "7.7.0", | ||||
|     "stylelint-config-standard": "15.0.1", | ||||
|     "url-loader": "0.5.7", | ||||
|     "webpack": "2.2.0-rc.2", | ||||
|     "webpack": "2.2.1", | ||||
|     "webpack-dev-middleware": "1.9.0", | ||||
|     "webpack-error-notification": "0.1.6", | ||||
|     "webpack-hot-middleware": "2.14.0", | ||||
|  | ||||
| @ -17,6 +17,7 @@ | ||||
| import BigNumber from 'bignumber.js'; | ||||
| 
 | ||||
| import { toChecksumAddress } from '../../abi/util/address'; | ||||
| import { isString } from '../util/types'; | ||||
| 
 | ||||
| export function outAccountInfo (infos) { | ||||
|   return Object | ||||
| @ -344,3 +345,17 @@ export function outTraceReplay (trace) { | ||||
| 
 | ||||
|   return trace; | ||||
| } | ||||
| 
 | ||||
| export function outVaultMeta (meta) { | ||||
|   if (isString(meta)) { | ||||
|     try { | ||||
|       const obj = JSON.parse(meta); | ||||
| 
 | ||||
|       return obj; | ||||
|     } catch (error) { | ||||
|       return {}; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   return meta || {}; | ||||
| } | ||||
|  | ||||
| @ -16,7 +16,7 @@ | ||||
| 
 | ||||
| import BigNumber from 'bignumber.js'; | ||||
| 
 | ||||
| import { outBlock, outAccountInfo, outAddress, outChainStatus, outDate, outHistogram, outNumber, outPeer, outPeers, outReceipt, outSyncing, outTransaction, outTrace } from './output'; | ||||
| import { outBlock, outAccountInfo, outAddress, outChainStatus, outDate, outHistogram, outNumber, outPeer, outPeers, outReceipt, outSyncing, outTransaction, outTrace, outVaultMeta } from './output'; | ||||
| import { isAddress, isBigNumber, isInstanceOf } from '../../../test/types'; | ||||
| 
 | ||||
| describe('api/format/output', () => { | ||||
| @ -455,4 +455,22 @@ describe('api/format/output', () => { | ||||
|       expect(formatted.transactionPosition.toNumber()).to.equal(11); | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|   describe('outVaultMeta', () => { | ||||
|     it('returns an exmpt object on null', () => { | ||||
|       expect(outVaultMeta(null)).to.deep.equal({}); | ||||
|     }); | ||||
| 
 | ||||
|     it('returns the original value if not string', () => { | ||||
|       expect(outVaultMeta({ test: 123 })).to.deep.equal({ test: 123 }); | ||||
|     }); | ||||
| 
 | ||||
|     it('returns an object from JSON string', () => { | ||||
|       expect(outVaultMeta('{"test":123}')).to.deep.equal({ test: 123 }); | ||||
|     }); | ||||
| 
 | ||||
|     it('returns an empty object on invalid JSON', () => { | ||||
|       expect(outVaultMeta('{"test"}')).to.deep.equal({}); | ||||
|     }); | ||||
|   }); | ||||
| }); | ||||
|  | ||||
| @ -15,7 +15,7 @@ | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| import { inAddress, inAddresses, inData, inHex, inNumber16, inOptions, inBlockNumber } from '../../format/input'; | ||||
| import { outAccountInfo, outAddress, outAddresses, outChainStatus, outHistogram, outNumber, outPeers, outTransaction } from '../../format/output'; | ||||
| import { outAccountInfo, outAddress, outAddresses, outChainStatus, outHistogram, outNumber, outPeers, outTransaction, outVaultMeta } from '../../format/output'; | ||||
| 
 | ||||
| export default class Parity { | ||||
|   constructor (transport) { | ||||
| @ -55,11 +55,26 @@ export default class Parity { | ||||
|       .execute('parity_changePassword', inAddress(account), password, newPassword); | ||||
|   } | ||||
| 
 | ||||
|   changeVault (account, vaultName) { | ||||
|     return this._transport | ||||
|       .execute('parity_changeVault', inAddress(account), vaultName); | ||||
|   } | ||||
| 
 | ||||
|   changeVaultPassword (vaultName, password) { | ||||
|     return this._transport | ||||
|       .execute('parity_changeVaultPassword', vaultName, password); | ||||
|   } | ||||
| 
 | ||||
|   checkRequest (requestId) { | ||||
|     return this._transport | ||||
|       .execute('parity_checkRequest', inNumber16(requestId)); | ||||
|   } | ||||
| 
 | ||||
|   closeVault (vaultName) { | ||||
|     return this._transport | ||||
|       .execute('parity_closeVault', vaultName); | ||||
|   } | ||||
| 
 | ||||
|   consensusCapability () { | ||||
|     return this._transport | ||||
|       .execute('parity_consensusCapability'); | ||||
| @ -167,6 +182,12 @@ export default class Parity { | ||||
|       .then((addresses) => addresses ? addresses.map(outAddress) : null); | ||||
|   } | ||||
| 
 | ||||
|   getVaultMeta (vaultName) { | ||||
|     return this._transport | ||||
|       .execute('parity_getVaultMeta', vaultName) | ||||
|       .then(outVaultMeta); | ||||
|   } | ||||
| 
 | ||||
|   hashContent (url) { | ||||
|     return this._transport | ||||
|       .execute('parity_hashContent', url); | ||||
| @ -189,6 +210,16 @@ export default class Parity { | ||||
|       .then((accounts) => (accounts || []).map(outAddress)); | ||||
|   } | ||||
| 
 | ||||
|   listOpenedVaults () { | ||||
|     return this._transport | ||||
|       .execute('parity_listOpenedVaults'); | ||||
|   } | ||||
| 
 | ||||
|   listVaults () { | ||||
|     return this._transport | ||||
|       .execute('parity_listVaults'); | ||||
|   } | ||||
| 
 | ||||
|   listRecentDapps () { | ||||
|     return this._transport | ||||
|       .execute('parity_listRecentDapps'); | ||||
| @ -275,6 +306,11 @@ export default class Parity { | ||||
|       .then(outAddress); | ||||
|   } | ||||
| 
 | ||||
|   newVault (vaultName, password) { | ||||
|     return this._transport | ||||
|       .execute('parity_newVault', vaultName, password); | ||||
|   } | ||||
| 
 | ||||
|   nextNonce (account) { | ||||
|     return this._transport | ||||
|       .execute('parity_nextNonce', inAddress(account)) | ||||
| @ -286,6 +322,11 @@ export default class Parity { | ||||
|       .execute('parity_nodeName'); | ||||
|   } | ||||
| 
 | ||||
|   openVault (vaultName, password) { | ||||
|     return this._transport | ||||
|       .execute('parity_openVault', vaultName, password); | ||||
|   } | ||||
| 
 | ||||
|   pendingTransactions () { | ||||
|     return this._transport | ||||
|       .execute('parity_pendingTransactions') | ||||
| @ -399,6 +440,11 @@ export default class Parity { | ||||
|       .execute('parity_setTransactionsLimit', inNumber16(quantity)); | ||||
|   } | ||||
| 
 | ||||
|   setVaultMeta (vaultName, meta) { | ||||
|     return this._transport | ||||
|       .execute('parity_setVaultMeta', vaultName, JSON.stringify(meta)); | ||||
|   } | ||||
| 
 | ||||
|   signerPort () { | ||||
|     return this._transport | ||||
|       .execute('parity_signerPort') | ||||
|  | ||||
| @ -104,11 +104,14 @@ export default class Personal { | ||||
|       } | ||||
| 
 | ||||
|       switch (data.method) { | ||||
|         case 'parity_closeVault': | ||||
|         case 'parity_openVault': | ||||
|         case 'parity_killAccount': | ||||
|         case 'parity_importGethAccounts': | ||||
|         case 'personal_newAccount': | ||||
|         case 'parity_newAccountFromPhrase': | ||||
|         case 'parity_newAccountFromWallet': | ||||
|         case 'personal_newAccount': | ||||
|           this._defaultAccount(true); | ||||
|           this._listAccounts(); | ||||
|           this._accountsInfo(); | ||||
|           return; | ||||
| @ -116,6 +119,7 @@ export default class Personal { | ||||
|         case 'parity_removeAddress': | ||||
|         case 'parity_setAccountName': | ||||
|         case 'parity_setAccountMeta': | ||||
|         case 'parity_changeVault': | ||||
|           this._accountsInfo(); | ||||
|           return; | ||||
| 
 | ||||
|  | ||||
| @ -259,7 +259,7 @@ export class LocalTransaction extends BaseTransaction { | ||||
|       to: transaction.to, | ||||
|       nonce: transaction.nonce, | ||||
|       value: transaction.value, | ||||
|       data: transaction.data, | ||||
|       data: transaction.input, | ||||
|       gasPrice, gas | ||||
|     }; | ||||
| 
 | ||||
|  | ||||
| @ -105,22 +105,22 @@ export default class InputText extends Component { | ||||
|     const { validationType, contract } = this.props; | ||||
|     const validation = validate(value, validationType, contract); | ||||
| 
 | ||||
|     if (validation instanceof Promise) { | ||||
|     const loadingTimeout = setTimeout(() => { | ||||
|       this.setState({ disabled: true, loading: true }); | ||||
|     }, 50); | ||||
| 
 | ||||
|       return validation | ||||
|         .then(validation => { | ||||
|           this.setValidation({ | ||||
|             ...validation, | ||||
|             disabled: false, | ||||
|             loading: false | ||||
|           }); | ||||
|     return Promise.resolve(validation) | ||||
|       .then((validation) => { | ||||
|         clearTimeout(loadingTimeout); | ||||
| 
 | ||||
|           event.target.focus(); | ||||
|         this.setValidation({ | ||||
|           ...validation, | ||||
|           disabled: false, | ||||
|           loading: false | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     this.setValidation(validation); | ||||
|         event.target.focus(); | ||||
|       }); | ||||
|   } | ||||
| 
 | ||||
|   onKeyDown = (event) => { | ||||
|  | ||||
| @ -49,7 +49,7 @@ | ||||
| } | ||||
| 
 | ||||
| .token-container { | ||||
|   flex: 1; | ||||
|   flex: 1 1 auto; | ||||
| } | ||||
| 
 | ||||
| .full-width .token-container { | ||||
|  | ||||
| @ -17,7 +17,7 @@ | ||||
| const DEFAULT_LOCALE = 'en'; | ||||
| const DEFAULT_LOCALES = process.env.NODE_ENV === 'production' | ||||
|   ? ['en'] | ||||
|   : ['en', 'de']; | ||||
|   : ['en', 'de', 'nl']; | ||||
| const LS_STORE_KEY = '_parity::locale'; | ||||
| 
 | ||||
| export { | ||||
|  | ||||
| @ -16,5 +16,6 @@ | ||||
| 
 | ||||
| export default { | ||||
|   de: 'Deutsch', | ||||
|   en: 'English' | ||||
|   en: 'English', | ||||
|   nl: 'Nederlands' | ||||
| }; | ||||
|  | ||||
							
								
								
									
										21
									
								
								js/src/i18n/nl/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								js/src/i18n/nl/index.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,21 @@ | ||||
| // Copyright 2015-2017 Parity Technologies (UK) Ltd.
 | ||||
| // This file is part of Parity.
 | ||||
| 
 | ||||
| // Parity is free software: you can redistribute it and/or modify
 | ||||
| // it under the terms of the GNU General Public License as published by
 | ||||
| // the Free Software Foundation, either version 3 of the License, or
 | ||||
| // (at your option) any later version.
 | ||||
| 
 | ||||
| // Parity is distributed in the hope that it will be useful,
 | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | ||||
| // GNU General Public License for more details.
 | ||||
| 
 | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| import settings from './settings'; | ||||
| 
 | ||||
| export default { | ||||
|   settings | ||||
| }; | ||||
							
								
								
									
										63
									
								
								js/src/i18n/nl/settings.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								js/src/i18n/nl/settings.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,63 @@ | ||||
| // Copyright 2015-2017 Parity Technologies (UK) Ltd.
 | ||||
| // This file is part of Parity.
 | ||||
| 
 | ||||
| // Parity is free software: you can redistribute it and/or modify
 | ||||
| // it under the terms of the GNU General Public License as published by
 | ||||
| // the Free Software Foundation, either version 3 of the License, or
 | ||||
| // (at your option) any later version.
 | ||||
| 
 | ||||
| // Parity is distributed in the hope that it will be useful,
 | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | ||||
| // GNU General Public License for more details.
 | ||||
| 
 | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| export default { | ||||
|   label: 'Instellingen', | ||||
| 
 | ||||
|   background: { | ||||
|     label: 'Achtergrond' | ||||
|   }, | ||||
| 
 | ||||
|   parity: { | ||||
|     label: 'Parity' | ||||
|   }, | ||||
| 
 | ||||
|   proxy: { | ||||
|     label: 'Proxy' | ||||
|   }, | ||||
| 
 | ||||
|   views: { | ||||
|     label: 'Weergaven', | ||||
| 
 | ||||
|     accounts: { | ||||
|       label: 'Accounts' | ||||
|     }, | ||||
| 
 | ||||
|     addresses: { | ||||
|       label: 'Adresboek' | ||||
|     }, | ||||
| 
 | ||||
|     apps: { | ||||
|       label: 'Applicaties' | ||||
|     }, | ||||
| 
 | ||||
|     contracts: { | ||||
|       label: 'Contracten' | ||||
|     }, | ||||
| 
 | ||||
|     status: { | ||||
|       label: 'Status' | ||||
|     }, | ||||
| 
 | ||||
|     signer: { | ||||
|       label: 'Signer' | ||||
|     }, | ||||
| 
 | ||||
|     settings: { | ||||
|       label: 'Instellingen' | ||||
|     } | ||||
|   } | ||||
| }; | ||||
| @ -17,11 +17,12 @@ | ||||
| import { Address, Data, Hash, Quantity, BlockNumber, TransactionRequest } from '../types'; | ||||
| import { fromDecimal, withComment, Dummy } from '../helpers'; | ||||
| 
 | ||||
| const SECTION_MINING = 'Block Authoring (aka "mining")'; | ||||
| const SECTION_DEV = 'Development'; | ||||
| const SECTION_NODE = 'Node Settings'; | ||||
| const SECTION_NET = 'Network Information'; | ||||
| const SECTION_ACCOUNTS = 'Accounts (read-only) and Signatures'; | ||||
| const SECTION_DEV = 'Development'; | ||||
| const SECTION_MINING = 'Block Authoring (aka "mining")'; | ||||
| const SECTION_NET = 'Network Information'; | ||||
| const SECTION_NODE = 'Node Settings'; | ||||
| const SECTION_VAULT = 'Account Vaults'; | ||||
| 
 | ||||
| const SUBDOC_SET = 'set'; | ||||
| const SUBDOC_ACCOUNTS = 'accounts'; | ||||
| @ -151,6 +152,67 @@ export default { | ||||
|     } | ||||
|   }, | ||||
| 
 | ||||
|   changeVault: { | ||||
|     section: SECTION_VAULT, | ||||
|     desc: 'Changes the current valut for the account', | ||||
|     params: [ | ||||
|       { | ||||
|         type: Address, | ||||
|         desc: 'Account address', | ||||
|         example: '0x63Cf90D3f0410092FC0fca41846f596223979195' | ||||
|       }, | ||||
|       { | ||||
|         type: String, | ||||
|         desc: 'Vault name', | ||||
|         example: 'StrongVault' | ||||
|       } | ||||
|     ], | ||||
|     returns: { | ||||
|       type: Boolean, | ||||
|       desc: 'True on success', | ||||
|       example: true | ||||
|     } | ||||
|   }, | ||||
| 
 | ||||
|   changeVaultPassword: { | ||||
|     section: SECTION_VAULT, | ||||
|     desc: 'Changes the password for any given vault', | ||||
|     params: [ | ||||
|       { | ||||
|         type: String, | ||||
|         desc: 'Vault name', | ||||
|         example: 'StrongVault' | ||||
|       }, | ||||
|       { | ||||
|         type: String, | ||||
|         desc: 'New Password', | ||||
|         example: 'p@55w0rd' | ||||
|       } | ||||
|     ], | ||||
|     returns: { | ||||
|       type: Boolean, | ||||
|       desc: 'True on success', | ||||
|       example: true | ||||
|     } | ||||
|   }, | ||||
| 
 | ||||
|   closeVault: { | ||||
|     section: SECTION_VAULT, | ||||
|     desc: 'Closes a vault with the given name', | ||||
|     params: [ | ||||
|       { | ||||
|         type: String, | ||||
|         desc: 'Vault name', | ||||
|         example: 'StrongVault' | ||||
|       } | ||||
|     ], | ||||
|     returns: { | ||||
|       type: Boolean, | ||||
|       desc: 'True on success', | ||||
|       example: true | ||||
|     } | ||||
|   }, | ||||
| 
 | ||||
|   consensusCapability: { | ||||
|     desc: 'Returns information on current consensus capability.', | ||||
|     params: [], | ||||
| @ -314,6 +376,43 @@ export default { | ||||
|     } | ||||
|   }, | ||||
| 
 | ||||
|   getVaultMeta: { | ||||
|     section: SECTION_VAULT, | ||||
|     desc: 'Returns the metadata for a specific vault', | ||||
|     params: [ | ||||
|       { | ||||
|         type: String, | ||||
|         desc: 'Vault name', | ||||
|         example: 'StrongVault' | ||||
|       } | ||||
|     ], | ||||
|     returns: { | ||||
|       type: String, | ||||
|       desc: 'The associated JSON metadata for this vault', | ||||
|       example: '{"passwordHint":"something"}' | ||||
|     } | ||||
|   }, | ||||
| 
 | ||||
|   listOpenedVaults: { | ||||
|     desc: 'Returns a list of all opened vaults', | ||||
|     params: [], | ||||
|     returns: { | ||||
|       type: Array, | ||||
|       desc: 'Names of all opened vaults', | ||||
|       example: "['Personal']" | ||||
|     } | ||||
|   }, | ||||
| 
 | ||||
|   listVaults: { | ||||
|     desc: 'Returns a list of all available vaults', | ||||
|     params: [], | ||||
|     returns: { | ||||
|       type: Array, | ||||
|       desc: 'Names of all available vaults', | ||||
|       example: "['Personal','Work']" | ||||
|     } | ||||
|   }, | ||||
| 
 | ||||
|   localTransactions: { | ||||
|     desc: 'Returns an object of current and past local transactions.', | ||||
|     params: [], | ||||
| @ -430,6 +529,28 @@ export default { | ||||
|     } | ||||
|   }, | ||||
| 
 | ||||
|   newVault: { | ||||
|     section: SECTION_VAULT, | ||||
|     desc: 'Creates a new vault with the given name & password', | ||||
|     params: [ | ||||
|       { | ||||
|         type: String, | ||||
|         desc: 'Vault name', | ||||
|         example: 'StrongVault' | ||||
|       }, | ||||
|       { | ||||
|         type: String, | ||||
|         desc: 'Password', | ||||
|         example: 'p@55w0rd' | ||||
|       } | ||||
|     ], | ||||
|     returns: { | ||||
|       type: Boolean, | ||||
|       desc: 'True on success', | ||||
|       example: true | ||||
|     } | ||||
|   }, | ||||
| 
 | ||||
|   nextNonce: { | ||||
|     section: SECTION_NET, | ||||
|     desc: 'Returns next available nonce for transaction from given account. Includes pending block and transaction queue.', | ||||
| @ -458,6 +579,28 @@ export default { | ||||
|     } | ||||
|   }, | ||||
| 
 | ||||
|   openVault: { | ||||
|     section: SECTION_VAULT, | ||||
|     desc: 'Opens a vault with the given name & password', | ||||
|     params: [ | ||||
|       { | ||||
|         type: String, | ||||
|         desc: 'Vault name', | ||||
|         example: 'StrongVault' | ||||
|       }, | ||||
|       { | ||||
|         type: String, | ||||
|         desc: 'Password', | ||||
|         example: 'p@55w0rd' | ||||
|       } | ||||
|     ], | ||||
|     returns: { | ||||
|       type: Boolean, | ||||
|       desc: 'True on success', | ||||
|       example: true | ||||
|     } | ||||
|   }, | ||||
| 
 | ||||
|   pendingTransactions: { | ||||
|     section: SECTION_NET, | ||||
|     desc: 'Returns a list of transactions currently in the queue.', | ||||
| @ -594,6 +737,28 @@ export default { | ||||
|     } | ||||
|   }, | ||||
| 
 | ||||
|   setVaultMeta: { | ||||
|     section: SECTION_VAULT, | ||||
|     desc: 'Sets the metadata for a specific vault', | ||||
|     params: [ | ||||
|       { | ||||
|         type: String, | ||||
|         desc: 'Vault name', | ||||
|         example: 'StrongVault' | ||||
|       }, | ||||
|       { | ||||
|         type: String, | ||||
|         desc: 'The metadata as a JSON string', | ||||
|         example: '{"passwordHint":"something"}' | ||||
|       } | ||||
|     ], | ||||
|     returns: { | ||||
|       type: Boolean, | ||||
|       desc: 'The boolean call result, true on success', | ||||
|       example: true | ||||
|     } | ||||
|   }, | ||||
| 
 | ||||
|   signerPort: { | ||||
|     section: SECTION_NODE, | ||||
|     desc: 'Returns the port the signer is running on, error if not enabled', | ||||
|  | ||||
| @ -31,4 +31,4 @@ if (isNode) { | ||||
| 
 | ||||
| import Etherscan from './3rdparty/etherscan'; | ||||
| 
 | ||||
| module.exports = Etherscan; | ||||
| export default Etherscan; | ||||
|  | ||||
| @ -16,4 +16,4 @@ | ||||
| 
 | ||||
| import JsonRpc from './jsonrpc'; | ||||
| 
 | ||||
| module.exports = JsonRpc; | ||||
| export default JsonRpc; | ||||
|  | ||||
| @ -32,4 +32,4 @@ if (isNode) { | ||||
| import Abi from './abi'; | ||||
| import Api from './api'; | ||||
| 
 | ||||
| module.exports = { Api, Abi }; | ||||
| export { Api, Abi }; | ||||
|  | ||||
| @ -31,4 +31,4 @@ if (isNode) { | ||||
| 
 | ||||
| import ShapeShift from './3rdparty/shapeshift'; | ||||
| 
 | ||||
| module.exports = ShapeShift; | ||||
| export default ShapeShift; | ||||
|  | ||||
| @ -20,7 +20,8 @@ import { FormattedMessage } from 'react-intl'; | ||||
| import { IconButton } from 'material-ui'; | ||||
| import { RadioButton, RadioButtonGroup } from 'material-ui/RadioButton'; | ||||
| 
 | ||||
| import { Form, Input, IdentityIcon, PasswordStrength } from '~/ui'; | ||||
| import { Form, Input, IdentityIcon } from '~/ui'; | ||||
| import PasswordStrength from '~/ui/Form/PasswordStrength'; | ||||
| import { RefreshIcon } from '~/ui/Icons'; | ||||
| 
 | ||||
| import styles from '../createAccount.css'; | ||||
|  | ||||
| @ -18,7 +18,8 @@ import { observer } from 'mobx-react'; | ||||
| import React, { Component, PropTypes } from 'react'; | ||||
| import { FormattedMessage } from 'react-intl'; | ||||
| 
 | ||||
| import { Form, Input, PasswordStrength } from '~/ui'; | ||||
| import { Form, Input } from '~/ui'; | ||||
| import PasswordStrength from '~/ui/Form/PasswordStrength'; | ||||
| 
 | ||||
| import styles from '../createAccount.css'; | ||||
| 
 | ||||
|  | ||||
| @ -19,7 +19,8 @@ import React, { Component, PropTypes } from 'react'; | ||||
| import { FormattedMessage } from 'react-intl'; | ||||
| import { Checkbox } from 'material-ui'; | ||||
| 
 | ||||
| import { Form, Input, PasswordStrength } from '~/ui'; | ||||
| import { Form, Input } from '~/ui'; | ||||
| import PasswordStrength from '~/ui/Form/PasswordStrength'; | ||||
| 
 | ||||
| import styles from '../createAccount.css'; | ||||
| 
 | ||||
|  | ||||
| @ -16,7 +16,6 @@ | ||||
| */ | ||||
| 
 | ||||
| .body { | ||||
|   height: 400px; | ||||
|   overflow-y: auto; | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -20,7 +20,8 @@ import moment from 'moment'; | ||||
| import React, { Component, PropTypes } from 'react'; | ||||
| import { FormattedMessage } from 'react-intl'; | ||||
| 
 | ||||
| import { Button, Modal, Editor } from '~/ui'; | ||||
| import { Button, Modal } from '~/ui'; | ||||
| import Editor from '~/ui/Editor'; | ||||
| import { CancelIcon, CheckIcon, DeleteIcon } from '~/ui/Icons'; | ||||
| 
 | ||||
| import styles from './loadContract.css'; | ||||
|  | ||||
| @ -23,7 +23,8 @@ import { connect } from 'react-redux'; | ||||
| import { bindActionCreators } from 'redux'; | ||||
| 
 | ||||
| import { newError, openSnackbar } from '~/redux/actions'; | ||||
| import { Button, Modal, IdentityName, IdentityIcon, PasswordStrength } from '~/ui'; | ||||
| import { Button, Modal, IdentityName, IdentityIcon } from '~/ui'; | ||||
| import PasswordStrength from '~/ui/Form/PasswordStrength'; | ||||
| import Form, { Input } from '~/ui/Form'; | ||||
| import { CancelIcon, CheckIcon, SendIcon } from '~/ui/Icons'; | ||||
| 
 | ||||
|  | ||||
| @ -19,7 +19,8 @@ import React, { Component, PropTypes } from 'react'; | ||||
| import SaveIcon from 'material-ui/svg-icons/content/save'; | ||||
| import ContentClear from 'material-ui/svg-icons/content/clear'; | ||||
| 
 | ||||
| import { Button, Modal, Editor, Form, Input } from '~/ui'; | ||||
| import { Button, Modal, Form, Input } from '~/ui'; | ||||
| import Editor from '~/ui/Editor'; | ||||
| import { ERRORS, validateName } from '~/util/validation'; | ||||
| 
 | ||||
| import styles from './saveContract.css'; | ||||
|  | ||||
| @ -14,15 +14,13 @@ | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| import BigNumber from 'bignumber.js'; | ||||
| import React, { Component, PropTypes } from 'react'; | ||||
| import { Checkbox, MenuItem } from 'material-ui'; | ||||
| import { isEqual } from 'lodash'; | ||||
| import { Checkbox } from 'material-ui'; | ||||
| 
 | ||||
| import Form, { Input, InputAddressSelect, AddressSelect, Select } from '~/ui/Form'; | ||||
| import Form, { Input, InputAddressSelect, AddressSelect } from '~/ui/Form'; | ||||
| import { nullableProptype } from '~/util/proptypes'; | ||||
| 
 | ||||
| import imageUnknown from '../../../../assets/images/contracts/unknown-64x64.png'; | ||||
| import TokenSelect from './tokenSelect'; | ||||
| import styles from '../transfer.css'; | ||||
| 
 | ||||
| const CHECK_STYLE = { | ||||
| @ -31,110 +29,12 @@ const CHECK_STYLE = { | ||||
|   left: '1em' | ||||
| }; | ||||
| 
 | ||||
| class TokenSelect extends Component { | ||||
|   static contextTypes = { | ||||
|     api: PropTypes.object | ||||
|   } | ||||
| 
 | ||||
|   static propTypes = { | ||||
|     onChange: PropTypes.func.isRequired, | ||||
|     balance: PropTypes.object.isRequired, | ||||
|     images: PropTypes.object.isRequired, | ||||
|     tag: PropTypes.string.isRequired | ||||
|   }; | ||||
| 
 | ||||
|   componentWillMount () { | ||||
|     this.computeTokens(); | ||||
|   } | ||||
| 
 | ||||
|   componentWillReceiveProps (nextProps) { | ||||
|     const prevTokens = this.props.balance.tokens.map((t) => `${t.token.tag}_${t.value.toNumber()}`); | ||||
|     const nextTokens = nextProps.balance.tokens.map((t) => `${t.token.tag}_${t.value.toNumber()}`); | ||||
| 
 | ||||
|     if (!isEqual(prevTokens, nextTokens)) { | ||||
|       this.computeTokens(nextProps); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   computeTokens (props = this.props) { | ||||
|     const { api } = this.context; | ||||
|     const { balance, images } = this.props; | ||||
| 
 | ||||
|     const items = balance.tokens | ||||
|       .filter((token, index) => !index || token.value.gt(0)) | ||||
|       .map((balance, index) => { | ||||
|         const token = balance.token; | ||||
|         const isEth = index === 0; | ||||
|         let imagesrc = token.image; | ||||
| 
 | ||||
|         if (!imagesrc) { | ||||
|           imagesrc = | ||||
|             images[token.address] | ||||
|               ? `${api.dappsUrl}${images[token.address]}` | ||||
|               : imageUnknown; | ||||
|         } | ||||
|         let value = 0; | ||||
| 
 | ||||
|         if (isEth) { | ||||
|           value = api.util.fromWei(balance.value).toFormat(3); | ||||
|         } else { | ||||
|           const format = balance.token.format || 1; | ||||
|           const decimals = format === 1 ? 0 : Math.min(3, Math.floor(format / 10)); | ||||
| 
 | ||||
|           value = new BigNumber(balance.value).div(format).toFormat(decimals); | ||||
|         } | ||||
| 
 | ||||
|         const label = ( | ||||
|           <div className={ styles.token }> | ||||
|             <img src={ imagesrc } /> | ||||
|             <div className={ styles.tokenname }> | ||||
|               { token.name } | ||||
|             </div> | ||||
|             <div className={ styles.tokenbalance }> | ||||
|               { value }<small> { token.tag }</small> | ||||
|             </div> | ||||
|           </div> | ||||
|         ); | ||||
| 
 | ||||
|         return ( | ||||
|           <MenuItem | ||||
|             key={ `${index}_${token.tag}` } | ||||
|             value={ token.tag } | ||||
|             label={ label } | ||||
|           > | ||||
|             { label } | ||||
|           </MenuItem> | ||||
|         ); | ||||
|       }); | ||||
| 
 | ||||
|     this.setState({ items }); | ||||
|   } | ||||
| 
 | ||||
|   render () { | ||||
|     const { tag, onChange } = this.props; | ||||
|     const { items } = this.state; | ||||
| 
 | ||||
|     return ( | ||||
|       <Select | ||||
|         className={ styles.tokenSelect } | ||||
|         label='type of token transfer' | ||||
|         hint='type of token to transfer' | ||||
|         value={ tag } | ||||
|         onChange={ onChange } | ||||
|       > | ||||
|         { items } | ||||
|       </Select> | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export default class Details extends Component { | ||||
|   static propTypes = { | ||||
|     address: PropTypes.string, | ||||
|     balance: PropTypes.object, | ||||
|     all: PropTypes.bool, | ||||
|     extras: PropTypes.bool, | ||||
|     images: PropTypes.object.isRequired, | ||||
|     sender: PropTypes.string, | ||||
|     senderError: PropTypes.string, | ||||
|     sendersBalances: PropTypes.object, | ||||
| @ -249,12 +149,11 @@ export default class Details extends Component { | ||||
|   } | ||||
| 
 | ||||
|   renderTokenSelect () { | ||||
|     const { balance, images, tag } = this.props; | ||||
|     const { balance, tag } = this.props; | ||||
| 
 | ||||
|     return ( | ||||
|       <TokenSelect | ||||
|         balance={ balance } | ||||
|         images={ images } | ||||
|         tag={ tag } | ||||
|         onChange={ this.onChangeToken } | ||||
|       /> | ||||
|  | ||||
							
								
								
									
										114
									
								
								js/src/modals/Transfer/Details/tokenSelect.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								js/src/modals/Transfer/Details/tokenSelect.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,114 @@ | ||||
| // Copyright 2015-2017 Parity Technologies (UK) Ltd.
 | ||||
| // This file is part of Parity.
 | ||||
| 
 | ||||
| // Parity is free software: you can redistribute it and/or modify
 | ||||
| // it under the terms of the GNU General Public License as published by
 | ||||
| // the Free Software Foundation, either version 3 of the License, or
 | ||||
| // (at your option) any later version.
 | ||||
| 
 | ||||
| // Parity is distributed in the hope that it will be useful,
 | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | ||||
| // GNU General Public License for more details.
 | ||||
| 
 | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| import BigNumber from 'bignumber.js'; | ||||
| import React, { Component, PropTypes } from 'react'; | ||||
| import { MenuItem } from 'material-ui'; | ||||
| import { isEqual } from 'lodash'; | ||||
| 
 | ||||
| import { Select } from '~/ui/Form'; | ||||
| import TokenImage from '~/ui/TokenImage'; | ||||
| 
 | ||||
| import styles from '../transfer.css'; | ||||
| 
 | ||||
| export default class TokenSelect extends Component { | ||||
|   static contextTypes = { | ||||
|     api: PropTypes.object | ||||
|   }; | ||||
| 
 | ||||
|   static propTypes = { | ||||
|     onChange: PropTypes.func.isRequired, | ||||
|     balance: PropTypes.object.isRequired, | ||||
|     tag: PropTypes.string.isRequired | ||||
|   }; | ||||
| 
 | ||||
|   componentWillMount () { | ||||
|     this.computeTokens(); | ||||
|   } | ||||
| 
 | ||||
|   componentWillReceiveProps (nextProps) { | ||||
|     const prevTokens = this.props.balance.tokens.map((t) => `${t.token.tag}_${t.value.toNumber()}`); | ||||
|     const nextTokens = nextProps.balance.tokens.map((t) => `${t.token.tag}_${t.value.toNumber()}`); | ||||
| 
 | ||||
|     if (!isEqual(prevTokens, nextTokens)) { | ||||
|       this.computeTokens(nextProps); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   computeTokens (props = this.props) { | ||||
|     const { api } = this.context; | ||||
|     const { balance } = this.props; | ||||
| 
 | ||||
|     const items = balance.tokens | ||||
|       .filter((token, index) => !index || token.value.gt(0)) | ||||
|       .map((balance, index) => { | ||||
|         const token = balance.token; | ||||
|         const isEth = index === 0; | ||||
| 
 | ||||
|         let value = 0; | ||||
| 
 | ||||
|         if (isEth) { | ||||
|           value = api.util.fromWei(balance.value).toFormat(3); | ||||
|         } else { | ||||
|           const format = balance.token.format || 1; | ||||
|           const decimals = format === 1 ? 0 : Math.min(3, Math.floor(format / 10)); | ||||
| 
 | ||||
|           value = new BigNumber(balance.value).div(format).toFormat(decimals); | ||||
|         } | ||||
| 
 | ||||
|         const label = ( | ||||
|           <div className={ styles.token }> | ||||
|             <TokenImage token={ token } /> | ||||
|             <div className={ styles.tokenname }> | ||||
|               { token.name } | ||||
|             </div> | ||||
|             <div className={ styles.tokenbalance }> | ||||
|               { value }<small> { token.tag }</small> | ||||
|             </div> | ||||
|           </div> | ||||
|         ); | ||||
| 
 | ||||
|         return ( | ||||
|           <MenuItem | ||||
|             key={ `${index}_${token.tag}` } | ||||
|             value={ token.tag } | ||||
|             label={ label } | ||||
|           > | ||||
|             { label } | ||||
|           </MenuItem> | ||||
|         ); | ||||
|       }); | ||||
| 
 | ||||
|     this.setState({ items }); | ||||
|   } | ||||
| 
 | ||||
|   render () { | ||||
|     const { tag, onChange } = this.props; | ||||
|     const { items } = this.state; | ||||
| 
 | ||||
|     return ( | ||||
|       <Select | ||||
|         className={ styles.tokenSelect } | ||||
|         label='type of token transfer' | ||||
|         hint='type of token to transfer' | ||||
|         value={ tag } | ||||
|         onChange={ onChange } | ||||
|       > | ||||
|         { items } | ||||
|       </Select> | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @ -44,7 +44,6 @@ class Transfer extends Component { | ||||
|   static propTypes = { | ||||
|     newError: PropTypes.func.isRequired, | ||||
|     gasLimit: PropTypes.object.isRequired, | ||||
|     images: PropTypes.object.isRequired, | ||||
| 
 | ||||
|     senders: nullableProptype(PropTypes.object), | ||||
|     sendersBalances: nullableProptype(PropTypes.object), | ||||
| @ -174,7 +173,7 @@ class Transfer extends Component { | ||||
|   } | ||||
| 
 | ||||
|   renderDetailsPage () { | ||||
|     const { account, balance, images, senders } = this.props; | ||||
|     const { account, balance, senders } = this.props; | ||||
|     const { recipient, recipientError, sender, senderError, sendersBalances } = this.store; | ||||
|     const { valueAll, extras, tag, total, totalError, value, valueError } = this.store; | ||||
| 
 | ||||
| @ -184,7 +183,6 @@ class Transfer extends Component { | ||||
|         all={ valueAll } | ||||
|         balance={ balance } | ||||
|         extras={ extras } | ||||
|         images={ images } | ||||
|         onChange={ this.store.onUpdateDetails } | ||||
|         recipient={ recipient } | ||||
|         recipientError={ recipientError } | ||||
|  | ||||
							
								
								
									
										23
									
								
								js/src/redux/providers/workerWrapper.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								js/src/redux/providers/workerWrapper.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,23 @@ | ||||
| // Copyright 2015-2017 Parity Technologies (UK) Ltd.
 | ||||
| // This file is part of Parity.
 | ||||
| 
 | ||||
| // Parity is free software: you can redistribute it and/or modify
 | ||||
| // it under the terms of the GNU General Public License as published by
 | ||||
| // the Free Software Foundation, either version 3 of the License, or
 | ||||
| // (at your option) any later version.
 | ||||
| 
 | ||||
| // Parity is distributed in the hope that it will be useful,
 | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | ||||
| // GNU General Public License for more details.
 | ||||
| 
 | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| if (!process.env.EMBED) { | ||||
|   const setupWorker = require('./worker').setupWorker; | ||||
| 
 | ||||
|   module.exports = { setupWorker }; | ||||
| } else { | ||||
|   module.exports = { setupWorker: () => {} }; | ||||
| } | ||||
| @ -20,7 +20,7 @@ import initMiddleware from './middleware'; | ||||
| import initReducers from './reducers'; | ||||
| 
 | ||||
| import { load as loadWallet } from './providers/walletActions'; | ||||
| import { setupWorker } from './providers/worker'; | ||||
| import { setupWorker } from './providers/workerWrapper'; | ||||
| 
 | ||||
| import { | ||||
|   Balances as BalancesProvider, | ||||
|  | ||||
| @ -65,7 +65,7 @@ describe('ui/AccountCard', () => { | ||||
|       let balance; | ||||
| 
 | ||||
|       beforeEach(() => { | ||||
|         balance = component.find('Connect(Balance)'); | ||||
|         balance = component.find('Balance'); | ||||
|       }); | ||||
| 
 | ||||
|       it('renders the balance', () => { | ||||
|  | ||||
| @ -14,4 +14,9 @@ | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| export Export from './Export'; | ||||
| export Import from './Import'; | ||||
| export Search from './Search'; | ||||
| export Sort from './Sort'; | ||||
| 
 | ||||
| export default from './actionbar'; | ||||
|  | ||||
| @ -17,13 +17,12 @@ | ||||
| import BigNumber from 'bignumber.js'; | ||||
| import React, { Component, PropTypes } from 'react'; | ||||
| import { FormattedMessage } from 'react-intl'; | ||||
| import { connect } from 'react-redux'; | ||||
| 
 | ||||
| import unknownImage from '~/../assets/images/contracts/unknown-64x64.png'; | ||||
| import TokenImage from '~/ui/TokenImage'; | ||||
| 
 | ||||
| import styles from './balance.css'; | ||||
| 
 | ||||
| class Balance extends Component { | ||||
| export default class Balance extends Component { | ||||
|   static contextTypes = { | ||||
|     api: PropTypes.object | ||||
|   }; | ||||
| @ -31,7 +30,6 @@ class Balance extends Component { | ||||
|   static propTypes = { | ||||
|     balance: PropTypes.object, | ||||
|     className: PropTypes.string, | ||||
|     images: PropTypes.object.isRequired, | ||||
|     showOnlyEth: PropTypes.bool, | ||||
|     showZeroValues: PropTypes.bool | ||||
|   }; | ||||
| @ -43,7 +41,7 @@ class Balance extends Component { | ||||
| 
 | ||||
|   render () { | ||||
|     const { api } = this.context; | ||||
|     const { balance, className, images, showZeroValues, showOnlyEth } = this.props; | ||||
|     const { balance, className, showZeroValues, showOnlyEth } = this.props; | ||||
| 
 | ||||
|     if (!balance || !balance.tokens) { | ||||
|       return null; | ||||
| @ -79,26 +77,12 @@ class Balance extends Component { | ||||
|           value = api.util.fromWei(balance.value).toFormat(3); | ||||
|         } | ||||
| 
 | ||||
|         const imageurl = token.image || images[token.address]; | ||||
|         let imagesrc = unknownImage; | ||||
| 
 | ||||
|         if (imageurl) { | ||||
|           const host = /^(\/)?api/.test(imageurl) | ||||
|             ? api.dappsUrl | ||||
|             : ''; | ||||
| 
 | ||||
|           imagesrc = `${host}${imageurl}`; | ||||
|         } | ||||
| 
 | ||||
|         return ( | ||||
|           <div | ||||
|             className={ styles.balance } | ||||
|             key={ `${index}_${token.tag}` } | ||||
|           > | ||||
|             <img | ||||
|               src={ imagesrc } | ||||
|               alt={ token.name } | ||||
|             /> | ||||
|             <TokenImage token={ token } /> | ||||
|             <div className={ styles.balanceValue }> | ||||
|               <span title={ value }> { value } </span> | ||||
|             </div> | ||||
| @ -125,14 +109,3 @@ class Balance extends Component { | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| function mapStateToProps (state) { | ||||
|   const { images } = state; | ||||
| 
 | ||||
|   return { images }; | ||||
| } | ||||
| 
 | ||||
| export default connect( | ||||
|   mapStateToProps, | ||||
|   null | ||||
| )(Balance); | ||||
|  | ||||
| @ -16,7 +16,6 @@ | ||||
| 
 | ||||
| import { shallow } from 'enzyme'; | ||||
| import React from 'react'; | ||||
| import sinon from 'sinon'; | ||||
| 
 | ||||
| import apiutil from '~/api/util'; | ||||
| 
 | ||||
| @ -32,7 +31,6 @@ const BALANCE = { | ||||
| 
 | ||||
| let api; | ||||
| let component; | ||||
| let store; | ||||
| 
 | ||||
| function createApi () { | ||||
|   api = { | ||||
| @ -43,36 +41,22 @@ function createApi () { | ||||
|   return api; | ||||
| } | ||||
| 
 | ||||
| function createStore () { | ||||
|   store = { | ||||
|     dispatch: sinon.stub(), | ||||
|     subscribe: sinon.stub(), | ||||
|     getState: () => { | ||||
|       return { | ||||
|         images: {} | ||||
|       }; | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   return store; | ||||
| } | ||||
| 
 | ||||
| function render (props = {}) { | ||||
|   if (!props.balance) { | ||||
|     props.balance = BALANCE; | ||||
|   } | ||||
| 
 | ||||
|   const api = createApi(); | ||||
| 
 | ||||
|   component = shallow( | ||||
|     <Balance | ||||
|       className='testClass' | ||||
|       { ...props } | ||||
|     />, | ||||
|     { | ||||
|       context: { | ||||
|         store: createStore() | ||||
|       } | ||||
|       context: { api } | ||||
|     } | ||||
|   ).find('Balance').shallow({ context: { api: createApi() } }); | ||||
|   ); | ||||
| 
 | ||||
|   return component; | ||||
| } | ||||
| @ -91,18 +75,18 @@ describe('ui/Balance', () => { | ||||
|   }); | ||||
| 
 | ||||
|   it('renders all the non-zero balances', () => { | ||||
|     expect(component.find('img')).to.have.length(2); | ||||
|     expect(component.find('Connect(TokenImage)')).to.have.length(2); | ||||
|   }); | ||||
| 
 | ||||
|   describe('render specifiers', () => { | ||||
|     it('renders only the single token with showOnlyEth', () => { | ||||
|       render({ showOnlyEth: true }); | ||||
|       expect(component.find('img')).to.have.length(1); | ||||
|       expect(component.find('Connect(TokenImage)')).to.have.length(1); | ||||
|     }); | ||||
| 
 | ||||
|     it('renders all the tokens with showZeroValues', () => { | ||||
|       render({ showZeroValues: true }); | ||||
|       expect(component.find('img')).to.have.length(3); | ||||
|       expect(component.find('Connect(TokenImage)')).to.have.length(3); | ||||
|     }); | ||||
| 
 | ||||
|     it('shows ETH with zero value with showOnlyEth & showZeroValues', () => { | ||||
| @ -116,7 +100,7 @@ describe('ui/Balance', () => { | ||||
|           ] | ||||
|         } | ||||
|       }); | ||||
|       expect(component.find('img')).to.have.length(1); | ||||
|       expect(component.find('Connect(TokenImage)')).to.have.length(1); | ||||
|     }); | ||||
|   }); | ||||
| }); | ||||
|  | ||||
| @ -14,35 +14,19 @@ | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| import AddressSelect from './AddressSelect'; | ||||
| import DappUrlInput from './DappUrlInput'; | ||||
| import FormWrap from './FormWrap'; | ||||
| import Input from './Input'; | ||||
| import InputAddress from './InputAddress'; | ||||
| import InputAddressSelect from './InputAddressSelect'; | ||||
| import InputChip from './InputChip'; | ||||
| import InputDate from './InputDate'; | ||||
| import InputInline from './InputInline'; | ||||
| import InputTime from './InputTime'; | ||||
| import Label from './Label'; | ||||
| import RadioButtons from './RadioButtons'; | ||||
| import Select from './Select'; | ||||
| import TypedInput from './TypedInput'; | ||||
| export AddressSelect from './AddressSelect'; | ||||
| export DappUrlInput from './DappUrlInput'; | ||||
| export FormWrap from './FormWrap'; | ||||
| export Input from './Input'; | ||||
| export InputAddress from './InputAddress'; | ||||
| export InputAddressSelect from './InputAddressSelect'; | ||||
| export InputChip from './InputChip'; | ||||
| export InputDate from './InputDate'; | ||||
| export InputInline from './InputInline'; | ||||
| export InputTime from './InputTime'; | ||||
| export Label from './Label'; | ||||
| export RadioButtons from './RadioButtons'; | ||||
| export Select from './Select'; | ||||
| export TypedInput from './TypedInput'; | ||||
| 
 | ||||
| export default from './form'; | ||||
| export { | ||||
|   AddressSelect, | ||||
|   DappUrlInput, | ||||
|   FormWrap, | ||||
|   Input, | ||||
|   InputAddress, | ||||
|   InputAddressSelect, | ||||
|   InputChip, | ||||
|   InputDate, | ||||
|   InputInline, | ||||
|   InputTime, | ||||
|   Label, | ||||
|   RadioButtons, | ||||
|   Select, | ||||
|   TypedInput | ||||
| }; | ||||
|  | ||||
							
								
								
									
										17
									
								
								js/src/ui/TokenImage/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								js/src/ui/TokenImage/index.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,17 @@ | ||||
| // Copyright 2015-2017 Parity Technologies (UK) Ltd.
 | ||||
| // This file is part of Parity.
 | ||||
| 
 | ||||
| // Parity is free software: you can redistribute it and/or modify
 | ||||
| // it under the terms of the GNU General Public License as published by
 | ||||
| // the Free Software Foundation, either version 3 of the License, or
 | ||||
| // (at your option) any later version.
 | ||||
| 
 | ||||
| // Parity is distributed in the hope that it will be useful,
 | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | ||||
| // GNU General Public License for more details.
 | ||||
| 
 | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| export default from './tokenImage'; | ||||
							
								
								
									
										72
									
								
								js/src/ui/TokenImage/tokenImage.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								js/src/ui/TokenImage/tokenImage.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,72 @@ | ||||
| // Copyright 2015-2017 Parity Technologies (UK) Ltd.
 | ||||
| // This file is part of Parity.
 | ||||
| 
 | ||||
| // Parity is free software: you can redistribute it and/or modify
 | ||||
| // it under the terms of the GNU General Public License as published by
 | ||||
| // the Free Software Foundation, either version 3 of the License, or
 | ||||
| // (at your option) any later version.
 | ||||
| 
 | ||||
| // Parity is distributed in the hope that it will be useful,
 | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | ||||
| // GNU General Public License for more details.
 | ||||
| 
 | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| import React, { Component, PropTypes } from 'react'; | ||||
| import { connect } from 'react-redux'; | ||||
| 
 | ||||
| import unknownImage from '~/../assets/images/contracts/unknown-64x64.png'; | ||||
| 
 | ||||
| class TokenImage extends Component { | ||||
|   static contextTypes = { | ||||
|     api: PropTypes.object | ||||
|   }; | ||||
| 
 | ||||
|   static propTypes = { | ||||
|     image: PropTypes.string, | ||||
|     token: PropTypes.shape({ | ||||
|       image: PropTypes.string, | ||||
|       address: PropTypes.string | ||||
|     }).isRequired | ||||
|   }; | ||||
| 
 | ||||
|   render () { | ||||
|     const { api } = this.context; | ||||
|     const { image, token } = this.props; | ||||
| 
 | ||||
|     const imageurl = token.image || image; | ||||
|     let imagesrc = unknownImage; | ||||
| 
 | ||||
|     if (imageurl) { | ||||
|       const host = /^(\/)?api/.test(imageurl) | ||||
|         ? api.dappsUrl | ||||
|         : ''; | ||||
| 
 | ||||
|       imagesrc = `${host}${imageurl}`; | ||||
|     } | ||||
| 
 | ||||
|     return ( | ||||
|       <img | ||||
|         src={ imagesrc } | ||||
|         alt={ token.name } | ||||
|       /> | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| function mapStateToProps (iniState) { | ||||
|   const { images } = iniState; | ||||
| 
 | ||||
|   return (_, props) => { | ||||
|     const { token } = props; | ||||
| 
 | ||||
|     return { image: images[token.address] }; | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| export default connect( | ||||
|   mapStateToProps, | ||||
|   null | ||||
| )(TokenImage); | ||||
| @ -16,8 +16,10 @@ | ||||
| 
 | ||||
| import moment from 'moment'; | ||||
| import React, { Component, PropTypes } from 'react'; | ||||
| import { connect } from 'react-redux'; | ||||
| import { Link } from 'react-router'; | ||||
| 
 | ||||
| import { txLink, addressLink } from '~/3rdparty/etherscan/links'; | ||||
| import { txLink } from '~/3rdparty/etherscan/links'; | ||||
| 
 | ||||
| import IdentityIcon from '../../IdentityIcon'; | ||||
| import IdentityName from '../../IdentityName'; | ||||
| @ -25,19 +27,20 @@ import MethodDecoding from '../../MethodDecoding'; | ||||
| 
 | ||||
| import styles from '../txList.css'; | ||||
| 
 | ||||
| export default class TxRow extends Component { | ||||
| class TxRow extends Component { | ||||
|   static contextTypes = { | ||||
|     api: PropTypes.object.isRequired | ||||
|   }; | ||||
| 
 | ||||
|   static propTypes = { | ||||
|     tx: PropTypes.object.isRequired, | ||||
|     accountAddresses: PropTypes.array.isRequired, | ||||
|     address: PropTypes.string.isRequired, | ||||
|     isTest: PropTypes.bool.isRequired, | ||||
|     tx: PropTypes.object.isRequired, | ||||
| 
 | ||||
|     block: PropTypes.object, | ||||
|     historic: PropTypes.bool, | ||||
|     className: PropTypes.string | ||||
|     className: PropTypes.string, | ||||
|     historic: PropTypes.bool | ||||
|   }; | ||||
| 
 | ||||
|   static defaultProps = { | ||||
| @ -77,22 +80,20 @@ export default class TxRow extends Component { | ||||
|   } | ||||
| 
 | ||||
|   renderAddress (address) { | ||||
|     const { isTest } = this.props; | ||||
| 
 | ||||
|     let esLink = null; | ||||
| 
 | ||||
|     if (address) { | ||||
|       esLink = ( | ||||
|         <a | ||||
|           href={ addressLink(address, isTest) } | ||||
|           target='_blank' | ||||
|         <Link | ||||
|           activeClassName={ styles.currentLink } | ||||
|           className={ styles.link } | ||||
|           to={ this.addressLink(address) } | ||||
|         > | ||||
|           <IdentityName | ||||
|             address={ address } | ||||
|             shorten | ||||
|           /> | ||||
|         </a> | ||||
|         </Link> | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
| @ -138,4 +139,30 @@ export default class TxRow extends Component { | ||||
|       </td> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   addressLink (address) { | ||||
|     const { accountAddresses } = this.props; | ||||
|     const isAccount = accountAddresses.includes(address); | ||||
| 
 | ||||
|     if (isAccount) { | ||||
|       return `/accounts/${address}`; | ||||
|     } | ||||
| 
 | ||||
|     return `/addresses/${address}`; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| function mapStateToProps (initState) { | ||||
|   const { accounts } = initState.personal; | ||||
|   const accountAddresses = Object.keys(accounts); | ||||
| 
 | ||||
|   return () => { | ||||
|     return { accountAddresses }; | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| export default connect( | ||||
|   mapStateToProps, | ||||
|   null | ||||
| )(TxRow); | ||||
| 
 | ||||
|  | ||||
| @ -25,13 +25,28 @@ import TxRow from './txRow'; | ||||
| 
 | ||||
| const api = new Api({ execute: sinon.stub() }); | ||||
| 
 | ||||
| const STORE = { | ||||
|   dispatch: sinon.stub(), | ||||
|   subscribe: sinon.stub(), | ||||
|   getState: () => { | ||||
|     return { | ||||
|       personal: { | ||||
|         accounts: { | ||||
|           '0x123': {} | ||||
|         } | ||||
|       } | ||||
|     }; | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| function render (props) { | ||||
|   return shallow( | ||||
|     <TxRow | ||||
|       store={ STORE } | ||||
|       { ...props } | ||||
|     />, | ||||
|     { context: { api } } | ||||
|   ); | ||||
|   ).find('TxRow').shallow({ context: { api } }); | ||||
| } | ||||
| 
 | ||||
| describe('ui/TxList/TxRow', () => { | ||||
| @ -48,5 +63,37 @@ describe('ui/TxList/TxRow', () => { | ||||
| 
 | ||||
|       expect(render({ address: '0x123', block, isTest: true, tx })).to.be.ok; | ||||
|     }); | ||||
| 
 | ||||
|     it('renders an account link', () => { | ||||
|       const block = { | ||||
|         timestamp: new Date() | ||||
|       }; | ||||
|       const tx = { | ||||
|         blockNumber: new BigNumber(123), | ||||
|         hash: '0x123456789abcdef0123456789abcdef0123456789abcdef', | ||||
|         to: '0x123', | ||||
|         value: new BigNumber(1) | ||||
|       }; | ||||
| 
 | ||||
|       const element = render({ address: '0x123', block, isTest: true, tx }); | ||||
| 
 | ||||
|       expect(element.find('Link').prop('to')).to.equal('/accounts/0x123'); | ||||
|     }); | ||||
| 
 | ||||
|     it('renders an address link', () => { | ||||
|       const block = { | ||||
|         timestamp: new Date() | ||||
|       }; | ||||
|       const tx = { | ||||
|         blockNumber: new BigNumber(123), | ||||
|         hash: '0x123456789abcdef0123456789abcdef0123456789abcdef', | ||||
|         to: '0x456', | ||||
|         value: new BigNumber(1) | ||||
|       }; | ||||
| 
 | ||||
|       const element = render({ address: '0x123', block, isTest: true, tx }); | ||||
| 
 | ||||
|       expect(element.find('Link').prop('to')).to.equal('/addresses/0x456'); | ||||
|     }); | ||||
|   }); | ||||
| }); | ||||
|  | ||||
| @ -65,6 +65,11 @@ | ||||
| 
 | ||||
|   .link { | ||||
|     vertical-align: top; | ||||
| 
 | ||||
|     &.currentLink { | ||||
|       color: white; | ||||
|       cursor: text; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .right { | ||||
|  | ||||
| @ -14,118 +14,43 @@ | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| import AccountCard from './AccountCard'; | ||||
| import Actionbar from './Actionbar'; | ||||
| import ActionbarExport from './Actionbar/Export'; | ||||
| import ActionbarImport from './Actionbar/Import'; | ||||
| import ActionbarSearch from './Actionbar/Search'; | ||||
| import ActionbarSort from './Actionbar/Sort'; | ||||
| import Badge from './Badge'; | ||||
| import Balance from './Balance'; | ||||
| import BlockStatus from './BlockStatus'; | ||||
| import Button from './Button'; | ||||
| import Certifications from './Certifications'; | ||||
| import ConfirmDialog from './ConfirmDialog'; | ||||
| import Container, { Title as ContainerTitle } from './Container'; | ||||
| import ContextProvider from './ContextProvider'; | ||||
| import CopyToClipboard from './CopyToClipboard'; | ||||
| import CurrencySymbol from './CurrencySymbol'; | ||||
| import DappCard from './DappCard'; | ||||
| import DappIcon from './DappIcon'; | ||||
| import Editor from './Editor'; | ||||
| import Errors from './Errors'; | ||||
| import Features, { FEATURES, FeaturesStore } from './Features'; | ||||
| import Form, { AddressSelect, DappUrlInput, FormWrap, Input, InputAddress, InputAddressSelect, InputChip, InputDate, InputInline, InputTime, Label, RadioButtons, Select, TypedInput } from './Form'; | ||||
| import GasPriceEditor from './GasPriceEditor'; | ||||
| import GasPriceSelector from './GasPriceSelector'; | ||||
| import Icons from './Icons'; | ||||
| import IdentityIcon from './IdentityIcon'; | ||||
| import IdentityName from './IdentityName'; | ||||
| import LanguageSelector from './LanguageSelector'; | ||||
| import Loading from './Loading'; | ||||
| import MethodDecoding from './MethodDecoding'; | ||||
| import Modal, { Busy as BusyStep, Completed as CompletedStep } from './Modal'; | ||||
| import muiTheme from './Theme'; | ||||
| import Page from './Page'; | ||||
| import ParityBackground from './ParityBackground'; | ||||
| import PasswordStrength from './Form/PasswordStrength'; | ||||
| import Portal from './Portal'; | ||||
| import QrCode from './QrCode'; | ||||
| import SectionList from './SectionList'; | ||||
| import ShortenedHash from './ShortenedHash'; | ||||
| import SignerIcon from './SignerIcon'; | ||||
| import Tags from './Tags'; | ||||
| import Title from './Title'; | ||||
| import Tooltips, { Tooltip } from './Tooltips'; | ||||
| import TxHash from './TxHash'; | ||||
| import TxList from './TxList'; | ||||
| import Warning from './Warning'; | ||||
| 
 | ||||
| export { | ||||
|   AccountCard, | ||||
|   Actionbar, | ||||
|   ActionbarExport, | ||||
|   ActionbarImport, | ||||
|   ActionbarSearch, | ||||
|   ActionbarSort, | ||||
|   AddressSelect, | ||||
|   Badge, | ||||
|   Balance, | ||||
|   BlockStatus, | ||||
|   Button, | ||||
|   Certifications, | ||||
|   ConfirmDialog, | ||||
|   Container, | ||||
|   ContainerTitle, | ||||
|   ContextProvider, | ||||
|   CopyToClipboard, | ||||
|   CurrencySymbol, | ||||
|   DappCard, | ||||
|   DappIcon, | ||||
|   DappUrlInput, | ||||
|   Editor, | ||||
|   Errors, | ||||
|   FEATURES, | ||||
|   Features, | ||||
|   FeaturesStore, | ||||
|   Form, | ||||
|   FormWrap, | ||||
|   GasPriceEditor, | ||||
|   GasPriceSelector, | ||||
|   Icons, | ||||
|   Input, | ||||
|   InputAddress, | ||||
|   InputAddressSelect, | ||||
|   InputChip, | ||||
|   InputDate, | ||||
|   InputInline, | ||||
|   InputTime, | ||||
|   IdentityIcon, | ||||
|   IdentityName, | ||||
|   Label, | ||||
|   LanguageSelector, | ||||
|   Loading, | ||||
|   MethodDecoding, | ||||
|   Modal, | ||||
|   BusyStep, | ||||
|   CompletedStep, | ||||
|   muiTheme, | ||||
|   Page, | ||||
|   ParityBackground, | ||||
|   PasswordStrength, | ||||
|   Portal, | ||||
|   QrCode, | ||||
|   RadioButtons, | ||||
|   Select, | ||||
|   ShortenedHash, | ||||
|   SectionList, | ||||
|   SignerIcon, | ||||
|   Tags, | ||||
|   Title, | ||||
|   Tooltip, | ||||
|   Tooltips, | ||||
|   TxHash, | ||||
|   TxList, | ||||
|   TypedInput, | ||||
|   Warning | ||||
| }; | ||||
| export AccountCard from './AccountCard'; | ||||
| export Actionbar, { Export as ActionbarExport, Import as ActionbarImport, Search as ActionbarSearch, Sort as ActionbarSort } from './Actionbar'; | ||||
| export Badge from './Badge'; | ||||
| export Balance from './Balance'; | ||||
| export BlockStatus from './BlockStatus'; | ||||
| export Button from './Button'; | ||||
| export Certifications from './Certifications'; | ||||
| export ConfirmDialog from './ConfirmDialog'; | ||||
| export Container, { Title as ContainerTitle } from './Container'; | ||||
| export ContextProvider from './ContextProvider'; | ||||
| export CopyToClipboard from './CopyToClipboard'; | ||||
| export CurrencySymbol from './CurrencySymbol'; | ||||
| export DappCard from './DappCard'; | ||||
| export DappIcon from './DappIcon'; | ||||
| export Errors from './Errors'; | ||||
| export Features, { FEATURES, FeaturesStore } from './Features'; | ||||
| export Form, { AddressSelect, DappUrlInput, FormWrap, Input, InputAddress, InputAddressSelect, InputChip, InputDate, InputInline, InputTime, Label, RadioButtons, Select, TypedInput } from './Form'; | ||||
| export GasPriceEditor from './GasPriceEditor'; | ||||
| export GasPriceSelector from './GasPriceSelector'; | ||||
| export Icons from './Icons'; | ||||
| export IdentityIcon from './IdentityIcon'; | ||||
| export IdentityName from './IdentityName'; | ||||
| export LanguageSelector from './LanguageSelector'; | ||||
| export Loading from './Loading'; | ||||
| export MethodDecoding from './MethodDecoding'; | ||||
| export Modal, { Busy as BusyStep, Completed as CompletedStep } from './Modal'; | ||||
| export muiTheme from './Theme'; | ||||
| export Page from './Page'; | ||||
| export ParityBackground from './ParityBackground'; | ||||
| export Portal from './Portal'; | ||||
| export QrCode from './QrCode'; | ||||
| export SectionList from './SectionList'; | ||||
| export ShortenedHash from './ShortenedHash'; | ||||
| export SignerIcon from './SignerIcon'; | ||||
| export Tags from './Tags'; | ||||
| export Title from './Title'; | ||||
| export Tooltips, { Tooltip } from './Tooltips'; | ||||
| export TxHash from './TxHash'; | ||||
| export TxList from './TxList'; | ||||
| export Warning from './Warning'; | ||||
|  | ||||
| @ -15,7 +15,7 @@ | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| import scrypt from 'scryptsy'; | ||||
| import * as Transaction from 'ethereumjs-tx'; | ||||
| import Transaction from 'ethereumjs-tx'; | ||||
| import { pbkdf2Sync } from 'crypto'; | ||||
| import { createDecipheriv } from 'browserify-aes'; | ||||
| 
 | ||||
|  | ||||
| @ -73,7 +73,7 @@ describe('views/Account/Header', () => { | ||||
| 
 | ||||
|         beforeEach(() => { | ||||
|           render({ balance: { balance: 'testing' } }); | ||||
|           balance = component.find('Connect(Balance)'); | ||||
|           balance = component.find('Balance'); | ||||
|         }); | ||||
| 
 | ||||
|         it('renders', () => { | ||||
|  | ||||
| @ -37,7 +37,6 @@ class Account extends Component { | ||||
|   static propTypes = { | ||||
|     fetchCertifiers: PropTypes.func.isRequired, | ||||
|     fetchCertifications: PropTypes.func.isRequired, | ||||
|     images: PropTypes.object.isRequired, | ||||
|     setVisibleAccounts: PropTypes.func.isRequired, | ||||
| 
 | ||||
|     accounts: PropTypes.object, | ||||
| @ -257,14 +256,13 @@ class Account extends Component { | ||||
|       return null; | ||||
|     } | ||||
| 
 | ||||
|     const { balances, images } = this.props; | ||||
|     const { balances } = this.props; | ||||
| 
 | ||||
|     return ( | ||||
|       <Transfer | ||||
|         account={ account } | ||||
|         balance={ balance } | ||||
|         balances={ balances } | ||||
|         images={ images } | ||||
|         onClose={ this.store.toggleTransferDialog } | ||||
|       /> | ||||
|     ); | ||||
| @ -289,12 +287,10 @@ class Account extends Component { | ||||
| function mapStateToProps (state) { | ||||
|   const { accounts } = state.personal; | ||||
|   const { balances } = state.balances; | ||||
|   const { images } = state; | ||||
| 
 | ||||
|   return { | ||||
|     accounts, | ||||
|     balances, | ||||
|     images | ||||
|     balances | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -30,7 +30,8 @@ import { newError } from '~/redux/actions'; | ||||
| import { setVisibleAccounts } from '~/redux/providers/personalActions'; | ||||
| 
 | ||||
| import { EditMeta, ExecuteContract } from '~/modals'; | ||||
| import { Actionbar, Button, Page, Modal, Editor } from '~/ui'; | ||||
| import { Actionbar, Button, Page, Modal } from '~/ui'; | ||||
| import Editor from '~/ui/Editor'; | ||||
| 
 | ||||
| import Header from '../Account/Header'; | ||||
| import Delete from '../Address/Delete'; | ||||
|  | ||||
| @ -285,6 +285,7 @@ class ParityBar extends Component { | ||||
|   } | ||||
| 
 | ||||
|   renderExpanded () { | ||||
|     const { externalLink } = this.props; | ||||
|     const { displayType } = this.state; | ||||
| 
 | ||||
|     return ( | ||||
| @ -333,7 +334,7 @@ class ParityBar extends Component { | ||||
|                 /> | ||||
|               ) | ||||
|               : ( | ||||
|                 <Signer /> | ||||
|                 <Signer externalLink={ externalLink } /> | ||||
|               ) | ||||
|           } | ||||
|         </div> | ||||
|  | ||||
| @ -15,16 +15,19 @@ | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| import React, { Component, PropTypes } from 'react'; | ||||
| import { connect } from 'react-redux'; | ||||
| import { Link } from 'react-router'; | ||||
| 
 | ||||
| import { addressLink } from '~/3rdparty/etherscan/links'; | ||||
| import styles from './accountLink.css'; | ||||
| 
 | ||||
| export default class AccountLink extends Component { | ||||
| class AccountLink extends Component { | ||||
|   static propTypes = { | ||||
|     isTest: PropTypes.bool.isRequired, | ||||
|     accountAddresses: PropTypes.array.isRequired, | ||||
|     address: PropTypes.string.isRequired, | ||||
|     className: PropTypes.string, | ||||
|     children: PropTypes.node | ||||
|     children: PropTypes.node, | ||||
|     externalLink: PropTypes.string.isRequired, | ||||
|     isTest: PropTypes.bool.isRequired | ||||
|   } | ||||
| 
 | ||||
|   state = { | ||||
| @ -32,36 +35,72 @@ export default class AccountLink extends Component { | ||||
|   }; | ||||
| 
 | ||||
|   componentWillMount () { | ||||
|     const { address, isTest } = this.props; | ||||
|     const { address, externalLink, isTest } = this.props; | ||||
| 
 | ||||
|     this.updateLink(address, isTest); | ||||
|     this.updateLink(address, externalLink, isTest); | ||||
|   } | ||||
| 
 | ||||
|   componentWillReceiveProps (nextProps) { | ||||
|     const { address, isTest } = nextProps; | ||||
|     const { address, externalLink, isTest } = nextProps; | ||||
| 
 | ||||
|     this.updateLink(address, isTest); | ||||
|     this.updateLink(address, externalLink, isTest); | ||||
|   } | ||||
| 
 | ||||
|   render () { | ||||
|     const { children, address, className } = this.props; | ||||
|     const { children, address, className, externalLink } = this.props; | ||||
| 
 | ||||
|     if (externalLink) { | ||||
|       return ( | ||||
|         <a | ||||
|           href={ this.state.link } | ||||
|           target='_blank' | ||||
|           className={ `${styles.container} ${className}` } | ||||
|         > | ||||
|           { children || address } | ||||
|         </a> | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     return ( | ||||
|       <a | ||||
|         href={ this.state.link } | ||||
|         target='_blank' | ||||
|       <Link | ||||
|         className={ `${styles.container} ${className}` } | ||||
|         to={ this.state.link } | ||||
|       > | ||||
|         { children || address } | ||||
|       </a> | ||||
|       </Link> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   updateLink (address, isTest) { | ||||
|     const link = addressLink(address, isTest); | ||||
|   updateLink (address, externalLink, isTest) { | ||||
|     const { accountAddresses } = this.props; | ||||
|     const isAccount = accountAddresses.includes(address); | ||||
| 
 | ||||
|     let link = isAccount | ||||
|       ? `/accounts/${address}` | ||||
|       : `/addresses/${address}`; | ||||
| 
 | ||||
|     if (externalLink) { | ||||
|       const path = externalLink.replace(/\/+$/, ''); | ||||
| 
 | ||||
|       link = `${path}/#${link}`; | ||||
|     } | ||||
| 
 | ||||
|     this.setState({ | ||||
|       link | ||||
|     }); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| function mapStateToProps (initState) { | ||||
|   const { accounts } = initState.personal; | ||||
|   const accountAddresses = Object.keys(accounts); | ||||
| 
 | ||||
|   return () => { | ||||
|     return { accountAddresses }; | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| export default connect( | ||||
|   mapStateToProps, | ||||
|   null | ||||
| )(AccountLink); | ||||
|  | ||||
| @ -25,6 +25,7 @@ export default class Account extends Component { | ||||
|   static propTypes = { | ||||
|     className: PropTypes.string, | ||||
|     address: PropTypes.string.isRequired, | ||||
|     externalLink: PropTypes.string.isRequired, | ||||
|     isTest: PropTypes.bool.isRequired, | ||||
|     balance: PropTypes.object // eth BigNumber, not required since it mght take time to fetch
 | ||||
|   }; | ||||
| @ -51,12 +52,13 @@ export default class Account extends Component { | ||||
|   } | ||||
| 
 | ||||
|   render () { | ||||
|     const { address, isTest, className } = this.props; | ||||
|     const { address, externalLink, isTest, className } = this.props; | ||||
| 
 | ||||
|     return ( | ||||
|       <div className={ `${styles.acc} ${className}` }> | ||||
|         <AccountLink | ||||
|           address={ address } | ||||
|           externalLink={ externalLink } | ||||
|           isTest={ isTest } | ||||
|         > | ||||
|           <IdentityIcon | ||||
| @ -79,13 +81,14 @@ export default class Account extends Component { | ||||
|   } | ||||
| 
 | ||||
|   renderName () { | ||||
|     const { address, isTest } = this.props; | ||||
|     const { address, externalLink, isTest } = this.props; | ||||
|     const name = <IdentityName address={ address } empty />; | ||||
| 
 | ||||
|     if (!name) { | ||||
|       return ( | ||||
|         <AccountLink | ||||
|           address={ address } | ||||
|           externalLink={ externalLink } | ||||
|           isTest={ isTest } | ||||
|         > | ||||
|           [{ this.shortAddress(address) }] | ||||
| @ -96,6 +99,7 @@ export default class Account extends Component { | ||||
|     return ( | ||||
|       <AccountLink | ||||
|         address={ address } | ||||
|         externalLink={ externalLink } | ||||
|         isTest={ isTest } | ||||
|       > | ||||
|         <span> | ||||
|  | ||||
| @ -93,7 +93,9 @@ export default class SignRequest extends Component { | ||||
|   renderDetails () { | ||||
|     const { api } = this.context; | ||||
|     const { address, isTest, store, data } = this.props; | ||||
|     const balance = store.balances[address]; | ||||
|     const { balances, externalLink } = store; | ||||
| 
 | ||||
|     const balance = balances[address]; | ||||
| 
 | ||||
|     if (!balance) { | ||||
|       return <div />; | ||||
| @ -105,6 +107,7 @@ export default class SignRequest extends Component { | ||||
|           <Account | ||||
|             address={ address } | ||||
|             balance={ balance } | ||||
|             externalLink={ externalLink } | ||||
|             isTest={ isTest } | ||||
|           /> | ||||
|         </div> | ||||
|  | ||||
| @ -27,6 +27,7 @@ import styles from './transactionMainDetails.css'; | ||||
| export default class TransactionMainDetails extends Component { | ||||
|   static propTypes = { | ||||
|     children: PropTypes.node, | ||||
|     externalLink: PropTypes.string.isRequired, | ||||
|     from: PropTypes.string.isRequired, | ||||
|     fromBalance: PropTypes.object, | ||||
|     gasStore: PropTypes.object, | ||||
| @ -50,7 +51,7 @@ export default class TransactionMainDetails extends Component { | ||||
|   } | ||||
| 
 | ||||
|   render () { | ||||
|     const { children, from, fromBalance, gasStore, isTest, transaction } = this.props; | ||||
|     const { children, externalLink, from, fromBalance, gasStore, isTest, transaction } = this.props; | ||||
| 
 | ||||
|     return ( | ||||
|       <div className={ styles.transaction }> | ||||
| @ -59,6 +60,7 @@ export default class TransactionMainDetails extends Component { | ||||
|             <Account | ||||
|               address={ from } | ||||
|               balance={ fromBalance } | ||||
|               externalLink={ externalLink } | ||||
|               isTest={ isTest } | ||||
|             /> | ||||
|           </div> | ||||
|  | ||||
| @ -89,14 +89,16 @@ export default class TransactionPending extends Component { | ||||
|   renderTransaction () { | ||||
|     const { className, focus, id, isSending, isTest, store, transaction } = this.props; | ||||
|     const { totalValue } = this.state; | ||||
|     const { balances, externalLink } = store; | ||||
|     const { from, value } = transaction; | ||||
| 
 | ||||
|     const fromBalance = store.balances[from]; | ||||
|     const fromBalance = balances[from]; | ||||
| 
 | ||||
|     return ( | ||||
|       <div className={ `${styles.container} ${className}` }> | ||||
|         <TransactionMainDetails | ||||
|           className={ styles.transactionDetails } | ||||
|           externalLink={ externalLink } | ||||
|           from={ from } | ||||
|           fromBalance={ fromBalance } | ||||
|           gasStore={ this.gasStore } | ||||
|  | ||||
| @ -37,6 +37,7 @@ class Embedded extends Component { | ||||
|       startConfirmRequest: PropTypes.func.isRequired, | ||||
|       startRejectRequest: PropTypes.func.isRequired | ||||
|     }).isRequired, | ||||
|     externalLink: PropTypes.string, | ||||
|     gasLimit: PropTypes.object.isRequired, | ||||
|     isTest: PropTypes.bool.isRequired, | ||||
|     signer: PropTypes.shape({ | ||||
| @ -45,7 +46,7 @@ class Embedded extends Component { | ||||
|     }).isRequired | ||||
|   }; | ||||
| 
 | ||||
|   store = new Store(this.context.api); | ||||
|   store = new Store(this.context.api, false, this.props.externalLink); | ||||
| 
 | ||||
|   render () { | ||||
|     return ( | ||||
|  | ||||
| @ -17,13 +17,16 @@ | ||||
| import { isEqual } from 'lodash'; | ||||
| import { action, observable } from 'mobx'; | ||||
| 
 | ||||
| export default class Store { | ||||
| export default class SignerStore { | ||||
|   @observable balances = {}; | ||||
|   @observable localHashes = []; | ||||
| 
 | ||||
|   constructor (api, withLocalTransactions = false) { | ||||
|   externalLink = ''; | ||||
| 
 | ||||
|   constructor (api, withLocalTransactions = false, externalLink = '') { | ||||
|     this._api = api; | ||||
|     this._timeoutId = 0; | ||||
|     this.externalLink = externalLink; | ||||
| 
 | ||||
|     if (withLocalTransactions) { | ||||
|       this.fetchLocalTransactions(); | ||||
|  | ||||
| @ -66,7 +66,6 @@ class Wallet extends Component { | ||||
|   static propTypes = { | ||||
|     address: PropTypes.string.isRequired, | ||||
|     balance: nullableProptype(PropTypes.object.isRequired), | ||||
|     images: PropTypes.object.isRequired, | ||||
|     isTest: PropTypes.bool.isRequired, | ||||
|     owned: PropTypes.bool.isRequired, | ||||
|     setVisibleAccounts: PropTypes.func.isRequired, | ||||
| @ -315,13 +314,12 @@ class Wallet extends Component { | ||||
|       return null; | ||||
|     } | ||||
| 
 | ||||
|     const { walletAccount, balance, images } = this.props; | ||||
|     const { walletAccount, balance } = this.props; | ||||
| 
 | ||||
|     return ( | ||||
|       <Transfer | ||||
|         account={ walletAccount } | ||||
|         balance={ balance } | ||||
|         images={ images } | ||||
|         onClose={ this.onTransferClose } | ||||
|       /> | ||||
|     ); | ||||
| @ -365,7 +363,6 @@ function mapStateToProps (_, initProps) { | ||||
|     const { isTest } = state.nodeStatus; | ||||
|     const { accountsInfo = {}, accounts = {} } = state.personal; | ||||
|     const { balances } = state.balances; | ||||
|     const { images } = state; | ||||
|     const walletAccount = accounts[address] || accountsInfo[address] || null; | ||||
| 
 | ||||
|     if (walletAccount) { | ||||
| @ -379,7 +376,6 @@ function mapStateToProps (_, initProps) { | ||||
|     return { | ||||
|       address, | ||||
|       balance, | ||||
|       images, | ||||
|       isTest, | ||||
|       owned, | ||||
|       wallet, | ||||
|  | ||||
| @ -28,7 +28,8 @@ import ListIcon from 'material-ui/svg-icons/action/view-list'; | ||||
| import SettingsIcon from 'material-ui/svg-icons/action/settings'; | ||||
| import SendIcon from 'material-ui/svg-icons/content/send'; | ||||
| 
 | ||||
| import { Actionbar, ActionbarExport, ActionbarImport, Button, Editor, Page, Select, Input } from '~/ui'; | ||||
| import { Actionbar, ActionbarExport, ActionbarImport, Button, Page, Select, Input } from '~/ui'; | ||||
| import Editor from '~/ui/Editor'; | ||||
| import { DeployContract, SaveContract, LoadContract } from '~/modals'; | ||||
| 
 | ||||
| import WriteContractStore from './writeContractStore'; | ||||
|  | ||||
| @ -14,44 +14,20 @@ | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| import Account from './Account'; | ||||
| import Accounts from './Accounts'; | ||||
| import Address from './Address'; | ||||
| import Addresses from './Addresses'; | ||||
| import Application from './Application'; | ||||
| import Contract from './Contract'; | ||||
| import Contracts from './Contracts'; | ||||
| import Dapp from './Dapp'; | ||||
| import Dapps from './Dapps'; | ||||
| import HistoryStore from './historyStore'; | ||||
| import ParityBar from './ParityBar'; | ||||
| import Settings, { SettingsBackground, SettingsParity, SettingsProxy, SettingsViews } from './Settings'; | ||||
| import Signer from './Signer'; | ||||
| import Status from './Status'; | ||||
| import Wallet from './Wallet'; | ||||
| import Web from './Web'; | ||||
| import WriteContract from './WriteContract'; | ||||
| 
 | ||||
| export { | ||||
|   Account, | ||||
|   Accounts, | ||||
|   Address, | ||||
|   Addresses, | ||||
|   Application, | ||||
|   Contract, | ||||
|   Contracts, | ||||
|   Dapp, | ||||
|   Dapps, | ||||
|   HistoryStore, | ||||
|   ParityBar, | ||||
|   Settings, | ||||
|   SettingsBackground, | ||||
|   SettingsParity, | ||||
|   SettingsProxy, | ||||
|   SettingsViews, | ||||
|   Signer, | ||||
|   Status, | ||||
|   Wallet, | ||||
|   Web, | ||||
|   WriteContract | ||||
| }; | ||||
| export Account from './Account'; | ||||
| export Accounts from './Accounts'; | ||||
| export Address from './Address'; | ||||
| export Addresses from './Addresses'; | ||||
| export Application from './Application'; | ||||
| export Contract from './Contract'; | ||||
| export Contracts from './Contracts'; | ||||
| export Dapp from './Dapp'; | ||||
| export Dapps from './Dapps'; | ||||
| export HistoryStore from './historyStore'; | ||||
| export ParityBar from './ParityBar'; | ||||
| export Settings, { SettingsBackground, SettingsParity, SettingsProxy, SettingsViews } from './Settings'; | ||||
| export Signer from './Signer'; | ||||
| export Status from './Status'; | ||||
| export Wallet from './Wallet'; | ||||
| export Web from './Web'; | ||||
| export WriteContract from './WriteContract'; | ||||
|  | ||||
| @ -15,7 +15,7 @@ | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| try { | ||||
|   var JsonRpc = require('../.npmjs/jsonRpc/library.js'); | ||||
|   var JsonRpc = require('../.npmjs/jsonrpc/library.js').default; | ||||
| 
 | ||||
|   if (typeof JsonRpc !== 'object') { | ||||
|     throw new Error('JsonRpc'); | ||||
|  | ||||
| @ -32,17 +32,25 @@ const FAVICON = path.resolve(__dirname, '../assets/images/parity-logo-black-no-t | ||||
| 
 | ||||
| const DEST = process.env.BUILD_DEST || '.build'; | ||||
| const ENV = process.env.NODE_ENV || 'development'; | ||||
| const EMBED = process.env.EMBED; | ||||
| 
 | ||||
| const isProd = ENV === 'production'; | ||||
| const isEmbed = EMBED === '1' || EMBED === 'true'; | ||||
| 
 | ||||
| const entry = isEmbed | ||||
|   ? { | ||||
|     embed: './embed.js' | ||||
|   } | ||||
|   : Object.assign({}, Shared.dappsEntry, { | ||||
|     index: './index.js' | ||||
|   }); | ||||
| 
 | ||||
| module.exports = { | ||||
|   cache: !isProd, | ||||
|   devtool: isProd ? '#hidden-source-map' : '#source-map', | ||||
| 
 | ||||
|   context: path.join(__dirname, '../src'), | ||||
|   entry: Object.assign({}, Shared.dappsEntry, { | ||||
|     index: './index.js', | ||||
|     embed: './embed.js' | ||||
|   }), | ||||
|   entry: entry, | ||||
|   output: { | ||||
|     // publicPath: '/',
 | ||||
|     path: path.join(__dirname, '../', DEST), | ||||
| @ -55,15 +63,12 @@ module.exports = { | ||||
|         test: /\.js$/, | ||||
|         exclude: /(node_modules)/, | ||||
|         // use: [ 'happypack/loader?id=js' ]
 | ||||
|         use: isProd ? ['babel-loader'] : [ | ||||
|           'babel-loader?cacheDirectory=true' | ||||
|         ], | ||||
|         options: Shared.getBabelrc() | ||||
|         use: isProd ? 'babel-loader' : 'babel-loader?cacheDirectory=true' | ||||
|       }, | ||||
|       { | ||||
|         test: /\.js$/, | ||||
|         include: /node_modules\/material-ui-chip-input/, | ||||
|         use: [ 'babel-loader' ] | ||||
|         include: /node_modules\/(material-chip-input|ethereumjs-tx)/, | ||||
|         use: 'babel-loader' | ||||
|       }, | ||||
|       { | ||||
|         test: /\.json$/, | ||||
| @ -91,13 +96,13 @@ module.exports = { | ||||
|         test: /\.css$/, | ||||
|         include: [ /src/ ], | ||||
|         // exclude: [ /src\/dapps/ ],
 | ||||
|         loader: isProd ? ExtractTextPlugin.extract([ | ||||
|         loader: (isProd && !isEmbed) ? ExtractTextPlugin.extract([ | ||||
|           // 'style-loader',
 | ||||
|           'css-loader?modules&sourceMap&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]', | ||||
|           'postcss-loader' | ||||
|         ]) : undefined, | ||||
|         // use: [ 'happypack/loader?id=css' ]
 | ||||
|         use: isProd ? undefined : [ | ||||
|         use: (isProd && !isEmbed) ? undefined : [ | ||||
|           'style-loader', | ||||
|           'css-loader?modules&sourceMap&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]', | ||||
|           'postcss-loader' | ||||
| @ -155,53 +160,63 @@ module.exports = { | ||||
|       }); | ||||
|     }); | ||||
| 
 | ||||
|     const plugins = Shared.getPlugins().concat( | ||||
|       new CopyWebpackPlugin([ | ||||
|         { from: './error_pages.css', to: 'styles.css' }, | ||||
|         { from: 'dapps/static' } | ||||
|       ], {}), | ||||
| 
 | ||||
|       new WebpackErrorNotificationPlugin(), | ||||
| 
 | ||||
|       new webpack.DllReferencePlugin({ | ||||
|         context: '.', | ||||
|         manifest: require(`../${DEST}/vendor-manifest.json`) | ||||
|       }), | ||||
| 
 | ||||
|       new HtmlWebpackPlugin({ | ||||
|         title: 'Parity', | ||||
|         filename: 'index.html', | ||||
|         template: './index.ejs', | ||||
|         favicon: FAVICON, | ||||
|         chunks: [ | ||||
|           isProd ? null : 'commons', | ||||
|           'index' | ||||
|         ] | ||||
|       }), | ||||
| 
 | ||||
|       new HtmlWebpackPlugin({ | ||||
|         title: 'Parity Bar', | ||||
|         filename: 'embed.html', | ||||
|         template: './index.ejs', | ||||
|         favicon: FAVICON, | ||||
|         chunks: [ | ||||
|           isProd ? null : 'commons', | ||||
|           'embed' | ||||
|         ] | ||||
|       }), | ||||
| 
 | ||||
|       new ScriptExtHtmlWebpackPlugin({ | ||||
|         sync: [ 'commons', 'vendor.js' ], | ||||
|         defaultAttribute: 'defer' | ||||
|       }), | ||||
| 
 | ||||
|       new ServiceWorkerWebpackPlugin({ | ||||
|         entry: path.join(__dirname, '../src/serviceWorker.js') | ||||
|       }), | ||||
| 
 | ||||
|       DappsHTMLInjection | ||||
|     let plugins = Shared.getPlugins().concat( | ||||
|       new WebpackErrorNotificationPlugin() | ||||
|     ); | ||||
| 
 | ||||
|     if (!isEmbed) { | ||||
|       plugins = [].concat( | ||||
|         plugins, | ||||
| 
 | ||||
|         new HtmlWebpackPlugin({ | ||||
|           title: 'Parity', | ||||
|           filename: 'index.html', | ||||
|           template: './index.ejs', | ||||
|           favicon: FAVICON, | ||||
|           chunks: [ | ||||
|             isProd ? null : 'commons', | ||||
|             'index' | ||||
|           ] | ||||
|         }), | ||||
| 
 | ||||
|         new ServiceWorkerWebpackPlugin({ | ||||
|           entry: path.join(__dirname, '../src/serviceWorker.js') | ||||
|         }), | ||||
| 
 | ||||
|         DappsHTMLInjection, | ||||
| 
 | ||||
|         new webpack.DllReferencePlugin({ | ||||
|           context: '.', | ||||
|           manifest: require(`../${DEST}/vendor-manifest.json`) | ||||
|         }), | ||||
| 
 | ||||
|         new ScriptExtHtmlWebpackPlugin({ | ||||
|           sync: [ 'commons', 'vendor.js' ], | ||||
|           defaultAttribute: 'defer' | ||||
|         }), | ||||
| 
 | ||||
|         new CopyWebpackPlugin([ | ||||
|           { from: './error_pages.css', to: 'styles.css' }, | ||||
|           { from: 'dapps/static' } | ||||
|         ], {}) | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     if (isEmbed) { | ||||
|       plugins.push( | ||||
|         new HtmlWebpackPlugin({ | ||||
|           title: 'Parity Bar', | ||||
|           filename: 'embed.html', | ||||
|           template: './index.ejs', | ||||
|           favicon: FAVICON, | ||||
|           chunks: [ | ||||
|             isProd ? null : 'commons', | ||||
|             'embed' | ||||
|           ] | ||||
|         }) | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     if (!isProd) { | ||||
|       const DEST_I18N = path.join(__dirname, '..', DEST, 'i18n'); | ||||
| 
 | ||||
|  | ||||
| @ -36,4 +36,5 @@ app.use(wsProxy); | ||||
| var server = app.listen(process.env.PORT || 3000, function () { | ||||
|   console.log('Listening on port', server.address().port); | ||||
| }); | ||||
| 
 | ||||
| server.on('upgrade', wsProxy.upgrade); | ||||
|  | ||||
							
								
								
									
										49
									
								
								js/webpack/embed.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								js/webpack/embed.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,49 @@ | ||||
| // Copyright 2015-2017 Parity Technologies (UK) Ltd.
 | ||||
| // This file is part of Parity.
 | ||||
| 
 | ||||
| // Parity is free software: you can redistribute it and/or modify
 | ||||
| // it under the terms of the GNU General Public License as published by
 | ||||
| // the Free Software Foundation, either version 3 of the License, or
 | ||||
| // (at your option) any later version.
 | ||||
| 
 | ||||
| // Parity is distributed in the hope that it will be useful,
 | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | ||||
| // GNU General Public License for more details.
 | ||||
| 
 | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| const webpack = require('webpack'); | ||||
| const WebpackStats = require('webpack/lib/Stats'); | ||||
| const path = require('path'); | ||||
| const fs = require('fs'); | ||||
| 
 | ||||
| const WebpackConfig = require('./app'); | ||||
| 
 | ||||
| const compiler = webpack(WebpackConfig); | ||||
| 
 | ||||
| compiler.run(function handler (err, stats) { | ||||
|   if (err) { | ||||
|     return console.error(err); | ||||
|   } | ||||
| 
 | ||||
|   // @see https://github.com/webpack/webpack/blob/324d309107f00cfc38ec727521563d309339b2ec/lib/Stats.js#L790
 | ||||
|   // Accepted values: none, errors-only, minimal, normal, verbose
 | ||||
|   const options = WebpackStats.presetToOptions('normal'); | ||||
| 
 | ||||
|   options.timings = true; | ||||
| 
 | ||||
|   const output = stats.toString(options); | ||||
|   const assets = Object.keys(stats.compilation.assets); | ||||
| 
 | ||||
|   const embedPath = path.resolve(WebpackConfig.output.path, './embed.json'); | ||||
|   const embedData = { assets: assets }; | ||||
| 
 | ||||
|   fs.writeFileSync(embedPath, JSON.stringify(embedData, null, 2)); | ||||
| 
 | ||||
|   process.stdout.write('\n'); | ||||
|   process.stdout.write(output); | ||||
|   process.stdout.write('\n\n'); | ||||
| }); | ||||
| 
 | ||||
| @ -26,6 +26,7 @@ const rucksack = require('rucksack-css'); | ||||
| const CircularDependencyPlugin = require('circular-dependency-plugin'); | ||||
| const ProgressBarPlugin = require('progress-bar-webpack-plugin'); | ||||
| 
 | ||||
| const EMBED = process.env.EMBED; | ||||
| const ENV = process.env.NODE_ENV || 'development'; | ||||
| const isProd = ENV === 'production'; | ||||
| 
 | ||||
| @ -113,6 +114,7 @@ function getPlugins (_isProd = isProd) { | ||||
| 
 | ||||
|     new webpack.DefinePlugin({ | ||||
|       'process.env': { | ||||
|         EMBED: JSON.stringify(EMBED), | ||||
|         NODE_ENV: JSON.stringify(ENV), | ||||
|         RPC_ADDRESS: JSON.stringify(process.env.RPC_ADDRESS), | ||||
|         PARITY_URL: JSON.stringify(process.env.PARITY_URL), | ||||
|  | ||||
| @ -37,7 +37,7 @@ | ||||
| 		0ACF9AC61E30FAB600D5C935 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; }; | ||||
| 		0ACF9AC81E30FAB600D5C935 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; | ||||
| 		0AE564F01E3CE42C00BD01F7 /* GetBSDProcessList.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetBSDProcessList.swift; sourceTree = "<group>"; }; | ||||
| 		0AED4D9F1E3E22F800BF87C0 /* ethstore */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; name = ethstore; path = ../target/release/deps/ethstore; sourceTree = "<group>"; }; | ||||
| 		0AED4D9F1E3E22F800BF87C0 /* ethstore */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; name = ethstore; path = ../target/release/ethstore; sourceTree = "<group>"; }; | ||||
| /* End PBXFileReference section */ | ||||
| 
 | ||||
| /* Begin PBXFrameworksBuildPhase section */ | ||||
|  | ||||
| @ -18,7 +18,7 @@ use std::path::PathBuf; | ||||
| use ethcore::ethstore::{EthStore, SecretStore, import_accounts, read_geth_accounts}; | ||||
| use ethcore::ethstore::dir::RootDiskDirectory; | ||||
| use ethcore::ethstore::SecretVaultRef; | ||||
| use ethcore::account_provider::AccountProvider; | ||||
| use ethcore::account_provider::{AccountProvider, AccountProviderSettings}; | ||||
| use helpers::{password_prompt, password_from_file}; | ||||
| use params::SpecType; | ||||
| 
 | ||||
| @ -92,7 +92,7 @@ fn new(n: NewAccount) -> Result<String, String> { | ||||
| 
 | ||||
| 	let dir = Box::new(keys_dir(n.path, n.spec)?); | ||||
| 	let secret_store = Box::new(secret_store(dir, Some(n.iterations))?); | ||||
| 	let acc_provider = AccountProvider::new(secret_store); | ||||
| 	let acc_provider = AccountProvider::new(secret_store, AccountProviderSettings::default()); | ||||
| 	let new_account = acc_provider.new_account(&password).map_err(|e| format!("Could not create new account: {}", e))?; | ||||
| 	Ok(format!("{:?}", new_account)) | ||||
| } | ||||
| @ -100,7 +100,7 @@ fn new(n: NewAccount) -> Result<String, String> { | ||||
| fn list(list_cmd: ListAccounts) -> Result<String, String> { | ||||
| 	let dir = Box::new(keys_dir(list_cmd.path, list_cmd.spec)?); | ||||
| 	let secret_store = Box::new(secret_store(dir, None)?); | ||||
| 	let acc_provider = AccountProvider::new(secret_store); | ||||
| 	let acc_provider = AccountProvider::new(secret_store, AccountProviderSettings::default()); | ||||
| 	let accounts = acc_provider.accounts(); | ||||
| 	let result = accounts.into_iter() | ||||
| 		.map(|a| format!("{:?}", a)) | ||||
|  | ||||
| @ -101,6 +101,9 @@ usage! { | ||||
| 			or |c: &Config| otry!(c.account).password.clone(), | ||||
| 		flag_keys_iterations: u32 = 10240u32, | ||||
| 			or |c: &Config| otry!(c.account).keys_iterations.clone(), | ||||
| 		flag_no_hardware_wallets: bool = false, | ||||
| 			or |c: &Config| otry!(c.account).disable_hardware.clone(), | ||||
| 
 | ||||
| 
 | ||||
| 		flag_force_ui: bool = false, | ||||
| 			or |c: &Config| otry!(c.ui).force.clone(), | ||||
| @ -347,6 +350,7 @@ struct Account { | ||||
| 	unlock: Option<Vec<String>>, | ||||
| 	password: Option<Vec<String>>, | ||||
| 	keys_iterations: Option<u32>, | ||||
| 	disable_hardware: Option<bool>, | ||||
| } | ||||
| 
 | ||||
| #[derive(Default, Debug, PartialEq, RustcDecodable)] | ||||
| @ -583,6 +587,7 @@ mod tests { | ||||
| 			flag_unlock: Some("0xdeadbeefcafe0000000000000000000000000000".into()), | ||||
| 			flag_password: vec!["~/.safe/password.file".into()], | ||||
| 			flag_keys_iterations: 10240u32, | ||||
| 			flag_no_hardware_wallets: false, | ||||
| 
 | ||||
| 			flag_force_ui: false, | ||||
| 			flag_no_ui: false, | ||||
| @ -769,6 +774,7 @@ mod tests { | ||||
| 				unlock: Some(vec!["0x1".into(), "0x2".into(), "0x3".into()]), | ||||
| 				password: Some(vec!["passwdfile path".into()]), | ||||
| 				keys_iterations: None, | ||||
| 				disable_hardware: None, | ||||
| 			}), | ||||
| 			ui: Some(Ui { | ||||
| 				force: None, | ||||
|  | ||||
| @ -78,6 +78,7 @@ Account Options: | ||||
|   --keys-iterations NUM          Specify the number of iterations to use when | ||||
|                                  deriving key from the password (bigger is more | ||||
|                                  secure) (default: {flag_keys_iterations}). | ||||
|   --no-hardware-wallets          Disables hardware wallet support. (default: {flag_no_hardware_wallets}) | ||||
| 
 | ||||
| UI Options: | ||||
|   --force-ui                     Enable Trusted UI WebSocket endpoint, | ||||
|  | ||||
| @ -461,6 +461,7 @@ impl Configuration { | ||||
| 			testnet: self.args.flag_testnet, | ||||
| 			password_files: self.args.flag_password.clone(), | ||||
| 			unlocked_accounts: to_addresses(&self.args.flag_unlock)?, | ||||
| 			enable_hardware_wallets: !self.args.flag_no_hardware_wallets, | ||||
| 		}; | ||||
| 
 | ||||
| 		Ok(cfg) | ||||
|  | ||||
| @ -175,6 +175,7 @@ pub struct AccountsConfig { | ||||
| 	pub testnet: bool, | ||||
| 	pub password_files: Vec<String>, | ||||
| 	pub unlocked_accounts: Vec<Address>, | ||||
| 	pub enable_hardware_wallets: bool, | ||||
| } | ||||
| 
 | ||||
| impl Default for AccountsConfig { | ||||
| @ -184,6 +185,7 @@ impl Default for AccountsConfig { | ||||
| 			testnet: false, | ||||
| 			password_files: Vec::new(), | ||||
| 			unlocked_accounts: Vec::new(), | ||||
| 			enable_hardware_wallets: true, | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -16,7 +16,7 @@ | ||||
| 
 | ||||
| use ethcore::ethstore::{PresaleWallet, EthStore}; | ||||
| use ethcore::ethstore::dir::RootDiskDirectory; | ||||
| use ethcore::account_provider::AccountProvider; | ||||
| use ethcore::account_provider::{AccountProvider, AccountProviderSettings}; | ||||
| use helpers::{password_prompt, password_from_file}; | ||||
| use params::SpecType; | ||||
| 
 | ||||
| @ -37,7 +37,7 @@ pub fn execute(cmd: ImportWallet) -> Result<String, String> { | ||||
| 
 | ||||
| 	let dir = Box::new(RootDiskDirectory::create(cmd.path).unwrap()); | ||||
| 	let secret_store = Box::new(EthStore::open_with_iterations(dir, cmd.iterations).unwrap()); | ||||
| 	let acc_provider = AccountProvider::new(secret_store); | ||||
| 	let acc_provider = AccountProvider::new(secret_store, AccountProviderSettings::default()); | ||||
| 	let wallet = PresaleWallet::open(cmd.wallet_path).map_err(|_| "Unable to open presale wallet.")?; | ||||
| 	let kp = wallet.decrypt(&password).map_err(|_| "Invalid password.")?; | ||||
| 	let address = acc_provider.insert_account(kp.secret().clone(), &password).unwrap(); | ||||
|  | ||||
| @ -26,7 +26,7 @@ use ethcore_logger::{Config as LogConfig}; | ||||
| use ethcore::miner::{StratumOptions, Stratum}; | ||||
| use ethcore::client::{Mode, DatabaseCompactionProfile, VMType, BlockChainClient}; | ||||
| use ethcore::service::ClientService; | ||||
| use ethcore::account_provider::AccountProvider; | ||||
| use ethcore::account_provider::{AccountProvider, AccountProviderSettings}; | ||||
| use ethcore::miner::{Miner, MinerService, ExternalMiner, MinerOptions}; | ||||
| use ethcore::snapshot; | ||||
| use ethcore::verification::queue::VerifierSettings; | ||||
| @ -516,9 +516,13 @@ fn prepare_account_provider(spec: &SpecType, dirs: &Directories, data_dir: &str, | ||||
| 	let path = dirs.keys_path(data_dir); | ||||
| 	upgrade_key_location(&dirs.legacy_keys_path(cfg.testnet), &path); | ||||
| 	let dir = Box::new(RootDiskDirectory::create(&path).map_err(|e| format!("Could not open keys directory: {}", e))?); | ||||
| 	let account_provider = AccountProvider::new(Box::new( | ||||
| 		EthStore::open_with_iterations(dir, cfg.iterations).map_err(|e| format!("Could not open keys directory: {}", e))? | ||||
| 	)); | ||||
| 	let account_settings = AccountProviderSettings { | ||||
| 		enable_hardware_wallets: cfg.enable_hardware_wallets, | ||||
| 		hardware_wallet_classic_key: spec == &SpecType::Classic, | ||||
| 	}; | ||||
| 	let account_provider = AccountProvider::new( | ||||
| 		Box::new(EthStore::open_with_iterations(dir, cfg.iterations).map_err(|e| format!("Could not open keys directory: {}", e))?), | ||||
| 		account_settings); | ||||
| 
 | ||||
| 	for a in cfg.unlocked_accounts { | ||||
| 		// Check if the account exists
 | ||||
|  | ||||
| @ -24,6 +24,7 @@ use futures::{future, Future, BoxFuture}; | ||||
| use light::client::LightChainClient; | ||||
| use light::on_demand::{request, OnDemand}; | ||||
| use light::TransactionQueue as LightTransactionQueue; | ||||
| use rlp::{self, Stream}; | ||||
| use util::{Address, H520, H256, U256, Uint, Bytes, RwLock}; | ||||
| use util::sha3::Hashable; | ||||
| 
 | ||||
| @ -118,7 +119,7 @@ impl<C: MiningBlockChainClient, M: MinerService> Dispatcher for FullDispatcher<C | ||||
| 		let (client, miner) = (take_weakf!(self.client), take_weakf!(self.miner)); | ||||
| 		let network_id = client.signing_network_id(); | ||||
| 		let address = filled.from; | ||||
| 		future::ok({ | ||||
| 		future::done({ | ||||
| 			let t = Transaction { | ||||
| 				nonce: filled.nonce | ||||
| 					.or_else(|| miner | ||||
| @ -133,13 +134,16 @@ impl<C: MiningBlockChainClient, M: MinerService> Dispatcher for FullDispatcher<C | ||||
| 				data: filled.data, | ||||
| 			}; | ||||
| 
 | ||||
| 			let hash = t.hash(network_id); | ||||
| 			let signature = try_bf!(signature(&accounts, address, hash, password)); | ||||
| 
 | ||||
| 			signature.map(|sig| { | ||||
| 				SignedTransaction::new(t.with_signature(sig, network_id)) | ||||
| 					.expect("Transaction was signed by AccountsProvider; it never produces invalid signatures; qed") | ||||
| 			}) | ||||
| 			if accounts.is_hardware_address(address) { | ||||
| 				hardware_signature(&*accounts, address, t, network_id).map(WithToken::No) | ||||
| 			} else { | ||||
| 				let hash = t.hash(network_id); | ||||
| 				let signature = try_bf!(signature(&*accounts, address, hash, password)); | ||||
| 				Ok(signature.map(|sig| { | ||||
| 					SignedTransaction::new(t.with_signature(sig, network_id)) | ||||
| 						.expect("Transaction was signed by AccountsProvider; it never produces invalid signatures; qed") | ||||
| 				})) | ||||
| 			} | ||||
| 		}).boxed() | ||||
| 	} | ||||
| 
 | ||||
| @ -219,8 +223,13 @@ impl Dispatcher for LightDispatcher { | ||||
| 				value: filled.value, | ||||
| 				data: filled.data, | ||||
| 			}; | ||||
| 
 | ||||
| 			if accounts.is_hardware_address(address) { | ||||
| 				return hardware_signature(&*accounts, address, t, network_id).map(WithToken::No) | ||||
| 			} | ||||
| 
 | ||||
| 			let hash = t.hash(network_id); | ||||
| 			let signature = signature(&accounts, address, hash, password)?; | ||||
| 			let signature = signature(&*accounts, address, hash, password)?; | ||||
| 
 | ||||
| 			Ok(signature.map(|sig| { | ||||
| 				SignedTransaction::new(t.with_signature(sig, network_id)) | ||||
| @ -411,6 +420,27 @@ fn signature(accounts: &AccountProvider, address: Address, hash: H256, password: | ||||
| 	}) | ||||
| } | ||||
| 
 | ||||
| // obtain a hardware signature from the given account.
 | ||||
| fn hardware_signature(accounts: &AccountProvider, address: Address, t: Transaction, network_id: Option<u64>) | ||||
| 	-> Result<SignedTransaction, Error> | ||||
| { | ||||
| 	debug_assert!(accounts.is_hardware_address(address)); | ||||
| 
 | ||||
| 	let mut stream = rlp::RlpStream::new(); | ||||
| 	t.rlp_append_unsigned_transaction(&mut stream, network_id); | ||||
| 	let signature = accounts.sign_with_hardware(address, &stream.as_raw()) | ||||
| 		.map_err(|e| { | ||||
| 			debug!(target: "miner", "Error signing transaction with hardware wallet: {}", e); | ||||
| 			errors::account("Error signing transaction with hardware wallet", e) | ||||
| 		})?; | ||||
| 
 | ||||
| 	SignedTransaction::new(t.with_signature(signature, network_id)) | ||||
| 		.map_err(|e| { | ||||
| 		  debug!(target: "miner", "Hardware wallet has produced invalid signature: {}", e); | ||||
| 		  errors::account("Invalid signature generated", e) | ||||
| 		}) | ||||
| } | ||||
| 
 | ||||
| fn decrypt(accounts: &AccountProvider, address: Address, msg: Bytes, password: SignWith) -> Result<WithToken<Bytes>, Error> { | ||||
| 	match password.clone() { | ||||
| 		SignWith::Nothing => accounts.decrypt(address, None, &DEFAULT_MAC, &msg).map(WithToken::No), | ||||
| @ -422,7 +452,7 @@ fn decrypt(accounts: &AccountProvider, address: Address, msg: Bytes, password: S | ||||
| 	}) | ||||
| } | ||||
| 
 | ||||
| /// Extract default gas price from a client and miner.
 | ||||
| /// Extract the default gas price from a client and miner.
 | ||||
| pub fn default_gas_price<C, M>(client: &C, miner: &M) -> U256 | ||||
| 	where C: MiningBlockChainClient, M: MinerService | ||||
| { | ||||
|  | ||||
| @ -45,6 +45,7 @@ use v1::types::{ | ||||
| 	TransactionStats, LocalTransactionStatus, | ||||
| 	BlockNumber, ConsensusCapability, VersionInfo, | ||||
| 	OperationsInfo, DappId, ChainStatus, | ||||
| 	AccountInfo, HwAccountInfo | ||||
| }; | ||||
| 
 | ||||
| /// Parity implementation.
 | ||||
| @ -111,7 +112,7 @@ impl<C, M, S: ?Sized, U> Parity for ParityClient<C, M, S, U> where | ||||
| { | ||||
| 	type Metadata = Metadata; | ||||
| 
 | ||||
| 	fn accounts_info(&self, dapp: Trailing<DappId>) -> Result<BTreeMap<String, BTreeMap<String, String>>, Error> { | ||||
| 	fn accounts_info(&self, dapp: Trailing<DappId>) -> Result<BTreeMap<H160, AccountInfo>, Error> { | ||||
| 		let dapp = dapp.0; | ||||
| 
 | ||||
| 		let store = take_weak!(self.accounts); | ||||
| @ -128,12 +129,17 @@ impl<C, M, S: ?Sized, U> Parity for ParityClient<C, M, S, U> where | ||||
| 			.into_iter() | ||||
| 			.chain(other.into_iter()) | ||||
| 			.filter(|&(ref a, _)| dapp_accounts.contains(a)) | ||||
| 			.map(|(a, v)| { | ||||
| 				let m = map![ | ||||
| 					"name".to_owned() => v.name | ||||
| 				]; | ||||
| 				(format!("0x{}", a.hex()), m) | ||||
| 			}) | ||||
| 			.map(|(a, v)| (H160::from(a), AccountInfo { name: v.name })) | ||||
| 			.collect() | ||||
| 		) | ||||
| 	} | ||||
| 
 | ||||
| 	fn hardware_accounts_info(&self) -> Result<BTreeMap<H160, HwAccountInfo>, Error> { | ||||
| 		let store = take_weak!(self.accounts); | ||||
| 		let info = store.hardware_accounts_info().map_err(|e| errors::account("Could not fetch account info.", e))?; | ||||
| 		Ok(info | ||||
| 			.into_iter() | ||||
| 			.map(|(a, v)| (H160::from(a), HwAccountInfo { name: v.name, manufacturer: v.meta })) | ||||
| 			.collect() | ||||
| 		) | ||||
| 	} | ||||
|  | ||||
| @ -16,7 +16,7 @@ | ||||
| 
 | ||||
| use std::sync::Arc; | ||||
| 
 | ||||
| use ethcore::account_provider::AccountProvider; | ||||
| use ethcore::account_provider::{AccountProvider, AccountProviderSettings}; | ||||
| use ethstore::EthStore; | ||||
| use ethstore::dir::RootDiskDirectory; | ||||
| use devtools::RandomTempPath; | ||||
| @ -36,7 +36,7 @@ fn accounts_provider() -> Arc<AccountProvider> { | ||||
| fn accounts_provider_with_vaults_support(temp_path: &str) -> Arc<AccountProvider> { | ||||
| 	let root_keys_dir = RootDiskDirectory::create(temp_path).unwrap(); | ||||
| 	let secret_store = EthStore::open(Box::new(root_keys_dir)).unwrap(); | ||||
| 	Arc::new(AccountProvider::new(Box::new(secret_store))) | ||||
| 	Arc::new(AccountProvider::new(Box::new(secret_store), AccountProviderSettings::default())) | ||||
| } | ||||
| 
 | ||||
| fn setup_with_accounts_provider(accounts_provider: Arc<AccountProvider>) -> ParityAccountsTester { | ||||
| @ -314,6 +314,14 @@ fn rpc_parity_vault_adds_vault_field_to_acount_meta() { | ||||
| 	let response = format!(r#"{{"jsonrpc":"2.0","result":{{"0x{}":{{"meta":"{{\"vault\":\"vault1\"}}","name":"","uuid":"{}"}}}},"id":1}}"#, address1.hex(), uuid1); | ||||
| 
 | ||||
| 	assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned())); | ||||
| 
 | ||||
| 	// and then
 | ||||
| 	assert!(tester.accounts.change_vault(address1, "").is_ok()); | ||||
| 
 | ||||
| 	let request = r#"{"jsonrpc": "2.0", "method": "parity_allAccountsInfo", "params":[], "id": 1}"#; | ||||
| 	let response = format!(r#"{{"jsonrpc":"2.0","result":{{"0x{}":{{"meta":"{{}}","name":"","uuid":"{}"}}}},"id":1}}"#, address1.hex(), uuid1); | ||||
| 
 | ||||
| 	assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned())); | ||||
| } | ||||
| 
 | ||||
| #[test] | ||||
| @ -358,6 +366,14 @@ fn rpc_parity_get_set_vault_meta() { | ||||
| 	let tester = setup_with_vaults_support(temp_path.as_str()); | ||||
| 
 | ||||
| 	assert!(tester.accounts.create_vault("vault1", "password1").is_ok()); | ||||
| 
 | ||||
| 	// when no meta set
 | ||||
| 	let request = r#"{"jsonrpc": "2.0", "method": "parity_getVaultMeta", "params":["vault1"], "id": 1}"#; | ||||
| 	let response = r#"{"jsonrpc":"2.0","result":"{}","id":1}"#; | ||||
| 
 | ||||
| 	assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned())); | ||||
| 
 | ||||
| 	// when meta set
 | ||||
| 	assert!(tester.accounts.set_vault_meta("vault1", "vault1_meta").is_ok()); | ||||
| 
 | ||||
| 	let request = r#"{"jsonrpc": "2.0", "method": "parity_getVaultMeta", "params":["vault1"], "id": 1}"#; | ||||
| @ -365,11 +381,13 @@ fn rpc_parity_get_set_vault_meta() { | ||||
| 
 | ||||
| 	assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned())); | ||||
| 
 | ||||
| 	// change meta
 | ||||
| 	let request = r#"{"jsonrpc": "2.0", "method": "parity_setVaultMeta", "params":["vault1", "updated_vault1_meta"], "id": 1}"#; | ||||
| 	let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#; | ||||
| 
 | ||||
| 	assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned())); | ||||
| 
 | ||||
| 	// query changed meta
 | ||||
| 	let request = r#"{"jsonrpc": "2.0", "method": "parity_getVaultMeta", "params":["vault1"], "id": 1}"#; | ||||
| 	let response = r#"{"jsonrpc":"2.0","result":"updated_vault1_meta","id":1}"#; | ||||
| 
 | ||||
|  | ||||
| @ -28,6 +28,7 @@ use v1::types::{ | ||||
| 	TransactionStats, LocalTransactionStatus, | ||||
| 	BlockNumber, ConsensusCapability, VersionInfo, | ||||
| 	OperationsInfo, DappId, ChainStatus, | ||||
| 	AccountInfo, HwAccountInfo, | ||||
| }; | ||||
| 
 | ||||
| build_rpc_trait! { | ||||
| @ -37,7 +38,11 @@ build_rpc_trait! { | ||||
| 
 | ||||
| 		/// Returns accounts information.
 | ||||
| 		#[rpc(name = "parity_accountsInfo")] | ||||
| 		fn accounts_info(&self, Trailing<DappId>) -> Result<BTreeMap<String, BTreeMap<String, String>>, Error>; | ||||
| 		fn accounts_info(&self, Trailing<DappId>) -> Result<BTreeMap<H160, AccountInfo>, Error>; | ||||
| 
 | ||||
| 		/// Returns hardware accounts information.
 | ||||
| 		#[rpc(name = "parity_hardwareAccountsInfo")] | ||||
| 		fn hardware_accounts_info(&self) -> Result<BTreeMap<H160, HwAccountInfo>, Error>; | ||||
| 
 | ||||
| 		/// Returns default account for dapp.
 | ||||
| 		#[rpc(meta, name = "parity_defaultAccount")] | ||||
|  | ||||
							
								
								
									
										31
									
								
								rpc/src/v1/types/account_info.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								rpc/src/v1/types/account_info.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,31 @@ | ||||
| // Copyright 2015-2017 Parity Technologies (UK) Ltd.
 | ||||
| // This file is part of Parity.
 | ||||
| 
 | ||||
| // Parity is free software: you can redistribute it and/or modify
 | ||||
| // it under the terms of the GNU General Public License as published by
 | ||||
| // the Free Software Foundation, either version 3 of the License, or
 | ||||
| // (at your option) any later version.
 | ||||
| 
 | ||||
| // Parity is distributed in the hope that it will be useful,
 | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | ||||
| // GNU General Public License for more details.
 | ||||
| 
 | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| /// Account information.
 | ||||
| #[derive(Debug, Default, Clone, PartialEq, Serialize)] | ||||
| pub struct AccountInfo { | ||||
| 	/// Account name
 | ||||
| 	pub name: String, | ||||
| } | ||||
| 
 | ||||
| /// Hardware wallet information.
 | ||||
| #[derive(Debug, Default, Clone, PartialEq, Serialize)] | ||||
| pub struct HwAccountInfo { | ||||
| 	/// Device name.
 | ||||
| 	pub name: String, | ||||
| 	/// Device manufacturer.
 | ||||
| 	pub manufacturer: String, | ||||
| } | ||||
| @ -14,6 +14,7 @@ | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| mod account_info; | ||||
| mod bytes; | ||||
| mod block; | ||||
| mod block_number; | ||||
| @ -65,3 +66,4 @@ pub use self::uint::{U128, U256}; | ||||
| pub use self::work::Work; | ||||
| pub use self::histogram::Histogram; | ||||
| pub use self::consensus_status::*; | ||||
| pub use self::account_info::{AccountInfo, HwAccountInfo}; | ||||
|  | ||||
| @ -23,7 +23,7 @@ const SNAPPY_OK: c_int = 0; | ||||
| const SNAPPY_INVALID_INPUT: c_int = 1; | ||||
| const SNAPPY_BUFFER_TOO_SMALL: c_int = 2; | ||||
| 
 | ||||
| #[link(name = "snappy")] | ||||
| #[link(name = "snappy", kind = "static")] | ||||
| extern { | ||||
| 	fn snappy_compress( | ||||
| 		input: *const c_char, | ||||
| @ -154,4 +154,4 @@ pub fn decompress_into(input: &[u8], output: &mut Vec<u8>) -> Result<usize, Inva | ||||
| pub fn validate_compressed_buffer(input: &[u8]) -> bool { | ||||
| 	let status = unsafe { snappy_validate_compressed_buffer(input.as_ptr() as *const c_char, input.len() as size_t )}; | ||||
| 	status == SNAPPY_OK | ||||
| } | ||||
| } | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user