Merge branch 'master' into lightrpc
This commit is contained in:
commit
6bf97de9d8
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)",
|
"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]]
|
[[package]]
|
||||||
name = "bit-set"
|
name = "bit-set"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
@ -381,6 +389,7 @@ dependencies = [
|
|||||||
"ethkey 0.2.0",
|
"ethkey 0.2.0",
|
||||||
"ethstore 0.1.0",
|
"ethstore 0.1.0",
|
||||||
"evmjit 1.6.0",
|
"evmjit 1.6.0",
|
||||||
|
"hardware-wallet 1.6.0",
|
||||||
"heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"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)",
|
"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)",
|
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
@ -863,6 +872,18 @@ name = "hamming"
|
|||||||
version = "0.1.3"
|
version = "0.1.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
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]]
|
[[package]]
|
||||||
name = "heapsize"
|
name = "heapsize"
|
||||||
version = "0.3.6"
|
version = "0.3.6"
|
||||||
@ -871,6 +892,15 @@ dependencies = [
|
|||||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"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]]
|
[[package]]
|
||||||
name = "hpack"
|
name = "hpack"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@ -1108,6 +1138,25 @@ name = "libc"
|
|||||||
version = "0.2.16"
|
version = "0.2.16"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
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#3c56a34bd040b0e60758824d839d9b700cfad2e0"
|
||||||
|
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]]
|
[[package]]
|
||||||
name = "linked-hash-map"
|
name = "linked-hash-map"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
@ -1575,7 +1624,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "parity-ui-precompiled"
|
name = "parity-ui-precompiled"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
source = "git+https://github.com/ethcore/js-precompiled.git#cb0dd77b70c552bb68288a94c7d5d37ecdd611c8"
|
source = "git+https://github.com/ethcore/js-precompiled.git#8d3275d4b227ea47558d6d8c423cc23b6bb504f3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"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 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 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 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-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 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"
|
"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 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 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 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 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 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>"
|
"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 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 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 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.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 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"
|
"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" }
|
ethcore-stratum = { path = "../stratum" }
|
||||||
lru-cache = "0.1.0"
|
lru-cache = "0.1.0"
|
||||||
ethcore-bloom-journal = { path = "../util/bloom" }
|
ethcore-bloom-journal = { path = "../util/bloom" }
|
||||||
|
hardware-wallet = { path = "../hw" }
|
||||||
ethabi = "0.2.2"
|
ethabi = "0.2.2"
|
||||||
|
|
||||||
[dependencies.hyper]
|
[dependencies.hyper]
|
||||||
|
@ -29,6 +29,7 @@ use ethstore::{SimpleSecretStore, SecretStore, Error as SSError, EthStore, EthMu
|
|||||||
use ethstore::dir::MemoryDirectory;
|
use ethstore::dir::MemoryDirectory;
|
||||||
use ethstore::ethkey::{Address, Message, Public, Secret, Random, Generator};
|
use ethstore::ethkey::{Address, Message, Public, Secret, Random, Generator};
|
||||||
use ethjson::misc::AccountMeta;
|
use ethjson::misc::AccountMeta;
|
||||||
|
use hardware_wallet::{Error as HardwareError, HardwareWalletManager, KeyPath};
|
||||||
pub use ethstore::ethkey::Signature;
|
pub use ethstore::ethkey::Signature;
|
||||||
|
|
||||||
/// Type of unlock.
|
/// Type of unlock.
|
||||||
@ -55,6 +56,10 @@ struct AccountData {
|
|||||||
pub enum SignError {
|
pub enum SignError {
|
||||||
/// Account is not unlocked
|
/// Account is not unlocked
|
||||||
NotUnlocked,
|
NotUnlocked,
|
||||||
|
/// Account does not exist.
|
||||||
|
NotFound,
|
||||||
|
/// Low-level hardware device error.
|
||||||
|
Hardware(HardwareError),
|
||||||
/// Low-level error from store
|
/// Low-level error from store
|
||||||
SStore(SSError)
|
SStore(SSError)
|
||||||
}
|
}
|
||||||
@ -63,11 +68,19 @@ impl fmt::Display for SignError {
|
|||||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
match *self {
|
match *self {
|
||||||
SignError::NotUnlocked => write!(f, "Account is locked"),
|
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),
|
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 {
|
impl From<SSError> for SignError {
|
||||||
fn from(e: SSError) -> Self {
|
fn from(e: SSError) -> Self {
|
||||||
SignError::SStore(e)
|
SignError::SStore(e)
|
||||||
@ -107,17 +120,47 @@ pub struct AccountProvider {
|
|||||||
sstore: Box<SecretStore>,
|
sstore: Box<SecretStore>,
|
||||||
/// Accounts unlocked with rolling tokens
|
/// Accounts unlocked with rolling tokens
|
||||||
transient_sstore: EthMultiStore,
|
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 {
|
impl AccountProvider {
|
||||||
/// Creates new account provider.
|
/// 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 {
|
AccountProvider {
|
||||||
unlocked: RwLock::new(HashMap::new()),
|
unlocked: RwLock::new(HashMap::new()),
|
||||||
address_book: RwLock::new(AddressBook::new(&sstore.local_path())),
|
address_book: RwLock::new(AddressBook::new(&sstore.local_path())),
|
||||||
dapps_settings: RwLock::new(DappsSettingsStore::new(&sstore.local_path())),
|
dapps_settings: RwLock::new(DappsSettingsStore::new(&sstore.local_path())),
|
||||||
sstore: sstore,
|
sstore: sstore,
|
||||||
transient_sstore: transient_sstore(),
|
transient_sstore: transient_sstore(),
|
||||||
|
hardware_store: hardware_store,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,6 +172,7 @@ impl AccountProvider {
|
|||||||
dapps_settings: RwLock::new(DappsSettingsStore::transient()),
|
dapps_settings: RwLock::new(DappsSettingsStore::transient()),
|
||||||
sstore: Box::new(EthStore::open(Box::new(MemoryDirectory::default())).expect("MemoryDirectory load always succeeds; qed")),
|
sstore: Box::new(EthStore::open(Box::new(MemoryDirectory::default())).expect("MemoryDirectory load always succeeds; qed")),
|
||||||
transient_sstore: transient_sstore(),
|
transient_sstore: transient_sstore(),
|
||||||
|
hardware_store: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,6 +220,12 @@ impl AccountProvider {
|
|||||||
Ok(accounts.into_iter().map(|a| a.address).collect())
|
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.
|
/// Sets a whitelist of accounts exposed for unknown dapps.
|
||||||
/// `None` means that all accounts will be visible.
|
/// `None` means that all accounts will be visible.
|
||||||
pub fn set_new_dapps_whitelist(&self, accounts: Option<Vec<Address>>) -> Result<(), Error> {
|
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.
|
/// Returns each account along with name and meta.
|
||||||
pub fn accounts_info(&self) -> Result<HashMap<Address, AccountMeta>, Error> {
|
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()
|
.into_iter()
|
||||||
.map(|a| (a.address.clone(), self.account_meta(a.address).ok().unwrap_or_default()))
|
.map(|a| (a.address.clone(), self.account_meta(a.address).ok().unwrap_or_default()))
|
||||||
.collect();
|
.collect();
|
||||||
Ok(r)
|
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.
|
/// Returns each account along with name and meta.
|
||||||
pub fn account_meta(&self, address: Address) -> Result<AccountMeta, Error> {
|
pub fn account_meta(&self, address: Address) -> Result<AccountMeta, Error> {
|
||||||
let account = self.sstore.account_ref(&address)?;
|
if let Some(info) = self.hardware_store.as_ref().and_then(|s| s.wallet_info(&address)) {
|
||||||
Ok(AccountMeta {
|
Ok(AccountMeta {
|
||||||
name: self.sstore.name(&account)?,
|
name: info.name,
|
||||||
meta: self.sstore.meta(&account)?,
|
meta: info.manufacturer,
|
||||||
uuid: self.sstore.uuid(&account).ok().map(Into::into), // allowed to not have a Uuid
|
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.
|
/// Returns each account along with name and meta.
|
||||||
@ -505,6 +577,15 @@ impl AccountProvider {
|
|||||||
self.sstore.set_vault_meta(name, meta)
|
self.sstore.set_vault_meta(name, meta)
|
||||||
.map_err(Into::into)
|
.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)]
|
#[cfg(test)]
|
||||||
|
@ -105,6 +105,7 @@ extern crate linked_hash_map;
|
|||||||
extern crate lru_cache;
|
extern crate lru_cache;
|
||||||
extern crate ethcore_stratum;
|
extern crate ethcore_stratum;
|
||||||
extern crate ethabi;
|
extern crate ethabi;
|
||||||
|
extern crate hardware_wallet;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
|
@ -154,7 +154,7 @@ impl Transaction {
|
|||||||
pub fn hash(&self, network_id: Option<u64>) -> H256 {
|
pub fn hash(&self, network_id: Option<u64>) -> H256 {
|
||||||
let mut stream = RlpStream::new();
|
let mut stream = RlpStream::new();
|
||||||
self.rlp_append_unsigned_transaction(&mut stream, network_id);
|
self.rlp_append_unsigned_transaction(&mut stream, network_id);
|
||||||
stream.out().sha3()
|
stream.as_raw().sha3()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Signs the transaction as coming from `sender`.
|
/// Signs the transaction as coming from `sender`.
|
||||||
|
@ -21,6 +21,53 @@ use Public;
|
|||||||
use bigint::hash::{H256, FixedHash};
|
use bigint::hash::{H256, FixedHash};
|
||||||
pub use self::derivation::Error as DerivationError;
|
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.
|
/// Extended secret key, allows deterministic derivation of subsequent keys.
|
||||||
pub struct ExtendedSecret {
|
pub struct ExtendedSecret {
|
||||||
secret: Secret,
|
secret: Secret,
|
||||||
@ -49,7 +96,7 @@ impl ExtendedSecret {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Derive new private key
|
/// 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_key, next_chain_code) = derivation::private(*self.secret, self.chain_code, index);
|
||||||
|
|
||||||
let derived_secret = Secret::from_slice(&*derived_key)
|
let derived_secret = Secret::from_slice(&*derived_key)
|
||||||
@ -88,7 +135,7 @@ impl ExtendedPublic {
|
|||||||
|
|
||||||
/// Derive new public key
|
/// Derive new public key
|
||||||
/// Operation is defined only for index belongs [0..2^31)
|
/// 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)?;
|
let (derived_key, next_chain_code) = derivation::public(self.public, self.chain_code, index)?;
|
||||||
Ok(ExtendedPublic::new(derived_key, next_chain_code))
|
Ok(ExtendedPublic::new(derived_key, next_chain_code))
|
||||||
}
|
}
|
||||||
@ -147,7 +194,7 @@ impl ExtendedKeyPair {
|
|||||||
&self.public
|
&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);
|
let derived = self.secret.derive(index);
|
||||||
|
|
||||||
Ok(ExtendedKeyPair {
|
Ok(ExtendedKeyPair {
|
||||||
@ -167,11 +214,11 @@ mod derivation {
|
|||||||
use rcrypto::sha2::Sha512;
|
use rcrypto::sha2::Sha512;
|
||||||
use bigint::hash::{H512, H256, FixedHash};
|
use bigint::hash::{H512, H256, FixedHash};
|
||||||
use bigint::prelude::{U256, U512, Uint};
|
use bigint::prelude::{U256, U512, Uint};
|
||||||
use byteorder::{BigEndian, ByteOrder};
|
|
||||||
use secp256k1;
|
use secp256k1;
|
||||||
use secp256k1::key::{SecretKey, PublicKey};
|
use secp256k1::key::{SecretKey, PublicKey};
|
||||||
use SECP256K1;
|
use SECP256K1;
|
||||||
use keccak;
|
use keccak;
|
||||||
|
use super::{Label, Derivation};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
@ -183,20 +230,18 @@ mod derivation {
|
|||||||
|
|
||||||
// Deterministic derivation of the key using secp256k1 elliptic curve.
|
// Deterministic derivation of the key using secp256k1 elliptic curve.
|
||||||
// Derivation can be either hardened or not.
|
// 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
|
// Can panic if passed `private_key` is not a valid secp256k1 private key
|
||||||
// (outside of (0..curve_n()]) field
|
// (outside of (0..curve_n()]) field
|
||||||
pub fn private(private_key: H256, chain_code: H256, index: u32) -> (H256, H256) {
|
pub fn private<T>(private_key: H256, chain_code: H256, index: Derivation<T>) -> (H256, H256) where T: Label {
|
||||||
if index < (2 << 30) {
|
match index {
|
||||||
private_soft(private_key, chain_code, index)
|
Derivation::Soft(index) => private_soft(private_key, chain_code, index),
|
||||||
}
|
Derivation::Hard(index) => private_hard(private_key, chain_code, index),
|
||||||
else {
|
|
||||||
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();
|
let private: U256 = private_key.into();
|
||||||
|
|
||||||
// produces 512-bit derived hmac (I)
|
// 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
|
// Can panic if passed `private_key` is not a valid secp256k1 private key
|
||||||
// (outside of (0..curve_n()]) field
|
// (outside of (0..curve_n()]) field
|
||||||
fn private_soft(private_key: H256, chain_code: H256, index: u32) -> (H256, H256) {
|
fn private_soft<T>(private_key: H256, chain_code: H256, index: T) -> (H256, H256) where T: Label {
|
||||||
let mut data = [0u8; 37];
|
let mut data = vec![0u8; 33 + T::len()];
|
||||||
|
|
||||||
let sec_private = SecretKey::from_slice(&SECP256K1, &*private_key)
|
let sec_private = SecretKey::from_slice(&SECP256K1, &*private_key)
|
||||||
.expect("Caller should provide valid private key");
|
.expect("Caller should provide valid private key");
|
||||||
@ -226,26 +271,26 @@ mod derivation {
|
|||||||
let public_serialized = sec_public.serialize_vec(&SECP256K1, true);
|
let public_serialized = sec_public.serialize_vec(&SECP256K1, true);
|
||||||
|
|
||||||
// curve point (compressed public key) -- index
|
// curve point (compressed public key) -- index
|
||||||
// 0.33 -- 33..37
|
// 0.33 -- 33..end
|
||||||
data[0..33].copy_from_slice(&public_serialized);
|
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
|
// Deterministic derivation of the key using secp256k1 elliptic curve
|
||||||
// This is hardened derivation and does not allow to associate
|
// This is hardened derivation and does not allow to associate
|
||||||
// corresponding public keys of the original and derived private keys
|
// corresponding public keys of the original and derived private keys
|
||||||
fn private_hard(private_key: H256, chain_code: H256, index: u32) -> (H256, H256) {
|
fn private_hard<T>(private_key: H256, chain_code: H256, index: T) -> (H256, H256) where T: Label {
|
||||||
let mut data = [0u8; 37];
|
let mut data: Vec<u8> = vec![0u8; 33 + T::len()];
|
||||||
let private: U256 = private_key.into();
|
let private: U256 = private_key.into();
|
||||||
|
|
||||||
// 0x00 (padding) -- private_key -- index
|
// 0x00 (padding) -- private_key -- index
|
||||||
// 0 -- 1..33 -- 33..37
|
// 0 -- 1..33 -- 33..end
|
||||||
private.to_big_endian(&mut data[1..33]);
|
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 {
|
fn private_add(k1: U256, k2: U256) -> U256 {
|
||||||
@ -266,11 +311,11 @@ mod derivation {
|
|||||||
H256::from_slice(&secp256k1::constants::CURVE_ORDER).into()
|
H256::from_slice(&secp256k1::constants::CURVE_ORDER).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn public(public_key: H512, chain_code: H256, index: u32) -> Result<(H512, H256), Error> {
|
pub fn public<T>(public_key: H512, chain_code: H256, derivation: Derivation<T>) -> Result<(H512, H256), Error> where T: Label {
|
||||||
if index >= (2 << 30) {
|
let index = match derivation {
|
||||||
// public derivation is only defined on 'soft' index space [0..2^31)
|
Derivation::Soft(index) => index,
|
||||||
return Err(Error::InvalidHardenedUse)
|
Derivation::Hard(_) => { return Err(Error::InvalidHardenedUse); }
|
||||||
}
|
};
|
||||||
|
|
||||||
let mut public_sec_raw = [0u8; 65];
|
let mut public_sec_raw = [0u8; 65];
|
||||||
public_sec_raw[0] = 4;
|
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_sec = PublicKey::from_slice(&SECP256K1, &public_sec_raw).map_err(|_| Error::InvalidPoint)?;
|
||||||
let public_serialized = public_sec.serialize_vec(&SECP256K1, true);
|
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
|
// curve point (compressed public key) -- index
|
||||||
// 0.33 -- 33..37
|
// 0.33 -- 33..end
|
||||||
data[0..33].copy_from_slice(&public_serialized);
|
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)]
|
// HMAC512SHA produces [derived private(256); new chain code(256)]
|
||||||
let mut hmac = Hmac::new(Sha512::new(), &*chain_code);
|
let mut hmac = Hmac::new(Sha512::new(), &*chain_code);
|
||||||
@ -351,7 +396,7 @@ mod tests {
|
|||||||
use secret::Secret;
|
use secret::Secret;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use bigint::hash::{H128, H256};
|
use bigint::hash::{H128, H256};
|
||||||
use super::derivation;
|
use super::{derivation, Derivation};
|
||||||
|
|
||||||
fn master_chain_basic() -> (H256, H256) {
|
fn master_chain_basic() -> (H256, H256) {
|
||||||
let seed = H128::from_str("000102030405060708090a0b0c0d0e0f")
|
let seed = H128::from_str("000102030405060708090a0b0c0d0e0f")
|
||||||
@ -375,23 +420,48 @@ mod tests {
|
|||||||
|
|
||||||
// hardened
|
// hardened
|
||||||
assert_eq!(&**extended_secret.secret(), &*secret);
|
assert_eq!(&**extended_secret.secret(), &*secret);
|
||||||
assert_eq!(&**extended_secret.derive(2147483648).secret(), &"0927453daed47839608e414a3738dfad10aed17c459bbd9ab53f89b026c834b6".into());
|
assert_eq!(&**extended_secret.derive(2147483648.into()).secret(), &"0927453daed47839608e414a3738dfad10aed17c459bbd9ab53f89b026c834b6".into());
|
||||||
assert_eq!(&**extended_secret.derive(2147483649).secret(), &"44238b6a29c6dcbe9b401364141ba11e2198c289a5fed243a1c11af35c19dc0f".into());
|
assert_eq!(&**extended_secret.derive(2147483649.into()).secret(), &"44238b6a29c6dcbe9b401364141ba11e2198c289a5fed243a1c11af35c19dc0f".into());
|
||||||
|
|
||||||
// normal
|
// normal
|
||||||
assert_eq!(&**extended_secret.derive(0).secret(), &"bf6a74e3f7b36fc4c96a1e12f31abc817f9f5904f5a8fc27713163d1f0b713f6".into());
|
assert_eq!(&**extended_secret.derive(0.into()).secret(), &"bf6a74e3f7b36fc4c96a1e12f31abc817f9f5904f5a8fc27713163d1f0b713f6".into());
|
||||||
assert_eq!(&**extended_secret.derive(1).secret(), &"bd4fca9eb1f9c201e9448c1eecd66e302d68d4d313ce895b8c134f512205c1bc".into());
|
assert_eq!(&**extended_secret.derive(1.into()).secret(), &"bd4fca9eb1f9c201e9448c1eecd66e302d68d4d313ce895b8c134f512205c1bc".into());
|
||||||
assert_eq!(&**extended_secret.derive(2).secret(), &"86932b542d6cab4d9c65490c7ef502d89ecc0e2a5f4852157649e3251e2a3268".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 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());
|
assert_eq!(&*derived_public.public(), &"f7b3244c96688f92372bfd4def26dc4151529747bab9f188a4ad34e141d47bd66522ff048bc6f19a0a4429b04318b1a8796c000265b4fa200dae5f6dda92dd94".into());
|
||||||
|
|
||||||
let keypair = ExtendedKeyPair::with_secret(
|
let keypair = ExtendedKeyPair::with_secret(
|
||||||
Secret::from_str("a100df7a048e50ed308ea696dc600215098141cb391e9527329df289f9383f65").unwrap(),
|
Secret::from_str("a100df7a048e50ed308ea696dc600215098141cb391e9527329df289f9383f65").unwrap(),
|
||||||
064.into(),
|
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]
|
#[test]
|
||||||
@ -400,8 +470,8 @@ mod tests {
|
|||||||
let extended_secret = ExtendedSecret::with_code(secret.clone(), 1.into());
|
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 extended_public = ExtendedPublic::from_secret(&extended_secret).expect("Extended public should be created");
|
||||||
|
|
||||||
let derived_secret0 = extended_secret.derive(0);
|
let derived_secret0 = extended_secret.derive(0.into());
|
||||||
let derived_public0 = extended_public.derive(0).expect("First derivation of public should succeed");
|
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");
|
let public_from_secret0 = ExtendedPublic::from_secret(&derived_secret0).expect("Extended public should be created");
|
||||||
|
|
||||||
@ -429,7 +499,7 @@ mod tests {
|
|||||||
/// xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7
|
/// xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7
|
||||||
/// H(0)
|
/// H(0)
|
||||||
test_extended(
|
test_extended(
|
||||||
|secret| secret.derive(2147483648),
|
|secret| secret.derive(2147483648.into()),
|
||||||
H256::from_str("edb2e14f9ee77d26dd93b4ecede8d16ed408ce149b6cd80b0715a2d911a0afea")
|
H256::from_str("edb2e14f9ee77d26dd93b4ecede8d16ed408ce149b6cd80b0715a2d911a0afea")
|
||||||
.expect("Private should be decoded ok")
|
.expect("Private should be decoded ok")
|
||||||
);
|
);
|
||||||
@ -440,7 +510,7 @@ mod tests {
|
|||||||
/// xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs
|
/// xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs
|
||||||
/// H(0)/1
|
/// H(0)/1
|
||||||
test_extended(
|
test_extended(
|
||||||
|secret| secret.derive(2147483648).derive(1),
|
|secret| secret.derive(2147483648.into()).derive(1.into()),
|
||||||
H256::from_str("3c6cb8d0f6a264c91ea8b5030fadaa8e538b020f0a387421a12de9319dc93368")
|
H256::from_str("3c6cb8d0f6a264c91ea8b5030fadaa8e538b020f0a387421a12de9319dc93368")
|
||||||
.expect("Private should be decoded ok")
|
.expect("Private should be decoded ok")
|
||||||
);
|
);
|
||||||
|
@ -22,8 +22,9 @@ use std::{env, process, fs};
|
|||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use docopt::Docopt;
|
use docopt::Docopt;
|
||||||
use ethstore::ethkey::Address;
|
use ethstore::ethkey::Address;
|
||||||
use ethstore::dir::{KeyDirectory, ParityDirectory, DiskDirectory, GethDirectory, DirectoryType};
|
use ethstore::dir::{KeyDirectory, ParityDirectory, RootDiskDirectory, GethDirectory, DirectoryType};
|
||||||
use ethstore::{EthStore, SecretStore, import_accounts, Error, PresaleWallet};
|
use ethstore::{EthStore, SimpleSecretStore, SecretStore, import_accounts, Error, PresaleWallet,
|
||||||
|
SecretVaultRef, StoreAccountRef};
|
||||||
|
|
||||||
pub const USAGE: &'static str = r#"
|
pub const USAGE: &'static str = r#"
|
||||||
Ethereum key management.
|
Ethereum key management.
|
||||||
@ -97,7 +98,7 @@ fn key_dir(location: &str) -> Result<Box<KeyDirectory>, Error> {
|
|||||||
"parity-test" => Box::new(ParityDirectory::create(DirectoryType::Testnet)?),
|
"parity-test" => Box::new(ParityDirectory::create(DirectoryType::Testnet)?),
|
||||||
"geth" => Box::new(GethDirectory::create(DirectoryType::Main)?),
|
"geth" => Box::new(GethDirectory::create(DirectoryType::Main)?),
|
||||||
"geth-test" => Box::new(GethDirectory::create(DirectoryType::Testnet)?),
|
"geth-test" => Box::new(GethDirectory::create(DirectoryType::Testnet)?),
|
||||||
path => Box::new(DiskDirectory::create(path)?),
|
path => Box::new(RootDiskDirectory::create(path)?),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(dir)
|
Ok(dir)
|
||||||
@ -130,16 +131,17 @@ fn execute<S, I>(command: I) -> Result<String, Error> where I: IntoIterator<Item
|
|||||||
return if args.cmd_insert {
|
return if args.cmd_insert {
|
||||||
let secret = args.arg_secret.parse().map_err(|_| Error::InvalidSecret)?;
|
let secret = args.arg_secret.parse().map_err(|_| Error::InvalidSecret)?;
|
||||||
let password = load_password(&args.arg_password)?;
|
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))
|
Ok(format!("0x{:?}", address))
|
||||||
} else if args.cmd_change_pwd {
|
} else if args.cmd_change_pwd {
|
||||||
let address = args.arg_address.parse().map_err(|_| Error::InvalidAccount)?;
|
let address = args.arg_address.parse().map_err(|_| Error::InvalidAccount)?;
|
||||||
let old_pwd = load_password(&args.arg_old_pwd)?;
|
let old_pwd = load_password(&args.arg_old_pwd)?;
|
||||||
let new_pwd = load_password(&args.arg_new_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))
|
Ok(format!("{}", ok))
|
||||||
} else if args.cmd_list {
|
} else if args.cmd_list {
|
||||||
let accounts = store.accounts()?;
|
let accounts = store.accounts()?;
|
||||||
|
let accounts: Vec<_> = accounts.into_iter().map(|a| a.address).collect();
|
||||||
Ok(format_accounts(&accounts))
|
Ok(format_accounts(&accounts))
|
||||||
} else if args.cmd_import {
|
} else if args.cmd_import {
|
||||||
let src = key_dir(&args.flag_src)?;
|
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 wallet = PresaleWallet::open(&args.arg_path)?;
|
||||||
let password = load_password(&args.arg_password)?;
|
let password = load_password(&args.arg_password)?;
|
||||||
let kp = wallet.decrypt(&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))
|
Ok(format!("0x{:?}", address))
|
||||||
} else if args.cmd_remove {
|
} else if args.cmd_remove {
|
||||||
let address = args.arg_address.parse().map_err(|_| Error::InvalidAccount)?;
|
let address = args.arg_address.parse().map_err(|_| Error::InvalidAccount)?;
|
||||||
let password = load_password(&args.arg_password)?;
|
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))
|
Ok(format!("{}", ok))
|
||||||
} else if args.cmd_sign {
|
} else if args.cmd_sign {
|
||||||
let address = args.arg_address.parse().map_err(|_| Error::InvalidAccount)?;
|
let address = args.arg_address.parse().map_err(|_| Error::InvalidAccount)?;
|
||||||
let message = args.arg_message.parse().map_err(|_| Error::InvalidMessage)?;
|
let message = args.arg_message.parse().map_err(|_| Error::InvalidMessage)?;
|
||||||
let password = load_password(&args.arg_password)?;
|
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))
|
Ok(format!("0x{:?}", signature))
|
||||||
} else if args.cmd_public {
|
} else if args.cmd_public {
|
||||||
let address = args.arg_address.parse().map_err(|_| Error::InvalidAccount)?;
|
let address = args.arg_address.parse().map_err(|_| Error::InvalidAccount)?;
|
||||||
let password = load_password(&args.arg_password)?;
|
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))
|
Ok(format!("0x{:?}", public))
|
||||||
} else {
|
} else {
|
||||||
Ok(format!("{}", USAGE))
|
Ok(format!("{}", USAGE))
|
||||||
|
@ -234,6 +234,10 @@ impl<T> VaultKeyDirectoryProvider for DiskDirectory<T> where T: KeyFileManager {
|
|||||||
})
|
})
|
||||||
.collect())
|
.collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn vault_meta(&self, name: &str) -> Result<String, Error> {
|
||||||
|
VaultDiskDirectory::meta_at(&self.path, name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KeyFileManager for DiskKeyFileManager {
|
impl KeyFileManager for DiskKeyFileManager {
|
||||||
@ -242,7 +246,12 @@ impl KeyFileManager for DiskKeyFileManager {
|
|||||||
Ok(SafeAccount::from_file(key_file, filename))
|
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();
|
let key_file: json::KeyFile = account.into();
|
||||||
key_file.write(writer).map_err(|e| Error::Custom(format!("{:?}", e)))
|
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>;
|
fn open(&self, name: &str, key: VaultKey) -> Result<Box<VaultKeyDirectory>, Error>;
|
||||||
/// List all vaults
|
/// List all vaults
|
||||||
fn list_vaults(&self) -> Result<Vec<String>, Error>;
|
fn list_vaults(&self) -> Result<Vec<String>, Error>;
|
||||||
|
/// Get vault meta
|
||||||
|
fn vault_meta(&self, name: &str) -> Result<String, Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Vault directory
|
/// Vault directory
|
||||||
|
@ -67,11 +67,23 @@ impl VaultDiskDirectory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// check that passed key matches vault file
|
// 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)))
|
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> {
|
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 original_path = self.path().expect("self is instance of DiskDirectory; DiskDirectory always returns path; qed");
|
||||||
let mut path: PathBuf = original_path.clone();
|
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
|
/// 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();
|
let mut vault_file_path: PathBuf = vault_dir_path.as_ref().into();
|
||||||
vault_file_path.push(VAULT_FILE_NAME);
|
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_meta = vault_file_contents.meta.unwrap_or("{}".to_owned());
|
||||||
let vault_file_crypto: Crypto = vault_file_contents.crypto.into();
|
let vault_file_crypto: Crypto = vault_file_contents.crypto.into();
|
||||||
|
|
||||||
let password_bytes = vault_file_crypto.decrypt(&key.password)?;
|
if let Some(key) = key {
|
||||||
let password_hash = key.password.sha3();
|
let password_bytes = vault_file_crypto.decrypt(&key.password)?;
|
||||||
if &*password_hash != password_bytes.as_slice() {
|
let password_hash = key.password.sha3();
|
||||||
return Err(Error::InvalidPassword);
|
if &*password_hash != password_bytes.as_slice() {
|
||||||
|
return Err(Error::InvalidPassword);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(vault_file_meta)
|
Ok(vault_file_meta)
|
||||||
@ -264,7 +278,7 @@ mod test {
|
|||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::path::PathBuf;
|
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 super::{VAULT_FILE_NAME, check_vault_name, make_vault_dir_path, create_vault_file, read_vault_file, VaultDiskDirectory};
|
||||||
use devtools::RandomTempPath;
|
use devtools::RandomTempPath;
|
||||||
|
|
||||||
@ -333,7 +347,7 @@ mod test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// when
|
// when
|
||||||
let result = read_vault_file(&dir, &key);
|
let result = read_vault_file(&dir, Some(&key));
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
@ -349,7 +363,7 @@ mod test {
|
|||||||
vault_file_path.push(VAULT_FILE_NAME);
|
vault_file_path.push(VAULT_FILE_NAME);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
let result = read_vault_file(&dir, &key);
|
let result = read_vault_file(&dir, Some(&key));
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
@ -362,7 +376,7 @@ mod test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// when
|
// when
|
||||||
let result = read_vault_file(&dir, &key);
|
let result = read_vault_file(&dir, Some(&key));
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
@ -418,22 +432,4 @@ mod test {
|
|||||||
// then
|
// then
|
||||||
assert!(vault.is_err());
|
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> {
|
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()
|
self.vaults.lock()
|
||||||
.get(name)
|
.get(name)
|
||||||
|
.and_then(|v| Some(v.meta()))
|
||||||
.ok_or(Error::VaultNotFound)
|
.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> {
|
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 == name1));
|
||||||
assert!(opened_vaults.iter().any(|v| &*v == name3));
|
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 {
|
loop {
|
||||||
match visitor.visit_key()? {
|
match visitor.visit_key()? {
|
||||||
Some(VaultFileField::Crypto) => { crypto = Some(visitor.visit_value()?); },
|
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; },
|
None => { break; },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -141,4 +141,29 @@ mod test {
|
|||||||
|
|
||||||
assert_eq!(file, deserialized);
|
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"
|
"stage-0", "react"
|
||||||
],
|
],
|
||||||
"plugins": [
|
"plugins": [
|
||||||
"transform-runtime",
|
|
||||||
"transform-decorators-legacy",
|
"transform-decorators-legacy",
|
||||||
"transform-class-properties",
|
"transform-class-properties",
|
||||||
"transform-object-rest-spread",
|
"transform-object-rest-spread",
|
||||||
"lodash"
|
"lodash",
|
||||||
|
"recharts"
|
||||||
],
|
],
|
||||||
"retainLines": true,
|
"retainLines": true,
|
||||||
"env": {
|
"env": {
|
||||||
@ -25,7 +25,8 @@
|
|||||||
},
|
},
|
||||||
"test": {
|
"test": {
|
||||||
"plugins": [
|
"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",
|
"name": "parity.js",
|
||||||
"version": "0.3.72",
|
"version": "0.3.77",
|
||||||
"main": "release/index.js",
|
"main": "release/index.js",
|
||||||
"jsnext:main": "src/index.js",
|
"jsnext:main": "src/index.js",
|
||||||
"author": "Parity Team <admin@parity.io>",
|
"author": "Parity Team <admin@parity.io>",
|
||||||
@ -25,18 +25,20 @@
|
|||||||
"Promise"
|
"Promise"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"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:app": "webpack --config webpack/app",
|
||||||
"build:lib": "webpack --config webpack/libraries",
|
"build:lib": "webpack --config webpack/libraries",
|
||||||
"build:dll": "webpack --config webpack/vendor",
|
"build:dll": "webpack --config webpack/vendor",
|
||||||
"build:markdown": "babel-node ./scripts/build-rpc-markdown.js",
|
"build:markdown": "babel-node ./scripts/build-rpc-markdown.js",
|
||||||
"build:json": "babel-node ./scripts/build-rpc-json.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:app": "NODE_ENV=production webpack --config webpack/app",
|
||||||
"ci:build:lib": "NODE_ENV=production webpack --config webpack/libraries",
|
"ci:build:lib": "NODE_ENV=production webpack --config webpack/libraries",
|
||||||
"ci:build:dll": "NODE_ENV=production webpack --config webpack/vendor",
|
"ci:build:dll": "NODE_ENV=production webpack --config webpack/vendor",
|
||||||
"ci:build:npm": "NODE_ENV=production webpack --config webpack/npm",
|
"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: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": "npm install && npm run build:lib && npm run build:dll && npm run start:app",
|
||||||
"start:app": "node webpack/dev.server",
|
"start:app": "node webpack/dev.server",
|
||||||
"clean": "rm -rf ./.build ./.coverage ./.happypack ./.npmjs ./build",
|
"clean": "rm -rf ./.build ./.coverage ./.happypack ./.npmjs ./build",
|
||||||
@ -53,27 +55,28 @@
|
|||||||
"prepush": "npm run lint:cached"
|
"prepush": "npm run lint:cached"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel-cli": "6.18.0",
|
"babel-cli": "6.22.2",
|
||||||
"babel-core": "6.21.0",
|
"babel-core": "6.22.1",
|
||||||
"babel-eslint": "7.1.1",
|
"babel-eslint": "7.1.1",
|
||||||
"babel-loader": "6.2.10",
|
"babel-loader": "6.2.10",
|
||||||
"babel-plugin-lodash": "3.2.11",
|
"babel-plugin-lodash": "3.2.11",
|
||||||
"babel-plugin-react-intl": "2.2.0",
|
"babel-plugin-react-intl": "2.3.1",
|
||||||
"babel-plugin-transform-class-properties": "6.19.0",
|
"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-decorators-legacy": "1.3.4",
|
||||||
"babel-plugin-transform-object-rest-spread": "6.20.2",
|
"babel-plugin-transform-object-rest-spread": "6.22.0",
|
||||||
"babel-plugin-transform-react-remove-prop-types": "0.2.11",
|
"babel-plugin-transform-react-remove-prop-types": "0.3.0",
|
||||||
"babel-plugin-transform-runtime": "6.15.0",
|
"babel-plugin-transform-runtime": "6.22.0",
|
||||||
"babel-plugin-webpack-alias": "2.1.2",
|
"babel-plugin-webpack-alias": "2.1.2",
|
||||||
"babel-polyfill": "6.20.0",
|
"babel-polyfill": "6.22.0",
|
||||||
"babel-preset-env": "1.1.4",
|
"babel-preset-env": "1.1.8",
|
||||||
"babel-preset-es2015": "6.18.0",
|
"babel-preset-es2015": "6.22.0",
|
||||||
"babel-preset-es2016": "6.16.0",
|
"babel-preset-es2016": "6.22.0",
|
||||||
"babel-preset-es2017": "6.16.0",
|
"babel-preset-es2017": "6.22.0",
|
||||||
"babel-preset-react": "6.16.0",
|
"babel-preset-react": "6.22.0",
|
||||||
"babel-preset-stage-0": "6.16.0",
|
"babel-preset-stage-0": "6.22.0",
|
||||||
"babel-register": "6.18.0",
|
"babel-register": "6.22.0",
|
||||||
"babel-runtime": "6.20.0",
|
"babel-runtime": "6.22.0",
|
||||||
"chai": "3.5.0",
|
"chai": "3.5.0",
|
||||||
"chai-as-promised": "6.0.0",
|
"chai-as-promised": "6.0.0",
|
||||||
"chai-enzyme": "0.6.1",
|
"chai-enzyme": "0.6.1",
|
||||||
@ -132,7 +135,7 @@
|
|||||||
"stylelint": "7.7.0",
|
"stylelint": "7.7.0",
|
||||||
"stylelint-config-standard": "15.0.1",
|
"stylelint-config-standard": "15.0.1",
|
||||||
"url-loader": "0.5.7",
|
"url-loader": "0.5.7",
|
||||||
"webpack": "2.2.0-rc.2",
|
"webpack": "2.2.1",
|
||||||
"webpack-dev-middleware": "1.9.0",
|
"webpack-dev-middleware": "1.9.0",
|
||||||
"webpack-error-notification": "0.1.6",
|
"webpack-error-notification": "0.1.6",
|
||||||
"webpack-hot-middleware": "2.14.0",
|
"webpack-hot-middleware": "2.14.0",
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
import BigNumber from 'bignumber.js';
|
import BigNumber from 'bignumber.js';
|
||||||
|
|
||||||
import { toChecksumAddress } from '../../abi/util/address';
|
import { toChecksumAddress } from '../../abi/util/address';
|
||||||
|
import { isString } from '../util/types';
|
||||||
|
|
||||||
export function outAccountInfo (infos) {
|
export function outAccountInfo (infos) {
|
||||||
return Object
|
return Object
|
||||||
@ -344,3 +345,17 @@ export function outTraceReplay (trace) {
|
|||||||
|
|
||||||
return 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 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';
|
import { isAddress, isBigNumber, isInstanceOf } from '../../../test/types';
|
||||||
|
|
||||||
describe('api/format/output', () => {
|
describe('api/format/output', () => {
|
||||||
@ -455,4 +455,22 @@ describe('api/format/output', () => {
|
|||||||
expect(formatted.transactionPosition.toNumber()).to.equal(11);
|
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/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import { inAddress, inAddresses, inData, inHex, inNumber16, inOptions, inBlockNumber } from '../../format/input';
|
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 {
|
export default class Parity {
|
||||||
constructor (transport) {
|
constructor (transport) {
|
||||||
@ -55,11 +55,26 @@ export default class Parity {
|
|||||||
.execute('parity_changePassword', inAddress(account), password, newPassword);
|
.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) {
|
checkRequest (requestId) {
|
||||||
return this._transport
|
return this._transport
|
||||||
.execute('parity_checkRequest', inNumber16(requestId));
|
.execute('parity_checkRequest', inNumber16(requestId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
closeVault (vaultName) {
|
||||||
|
return this._transport
|
||||||
|
.execute('parity_closeVault', vaultName);
|
||||||
|
}
|
||||||
|
|
||||||
consensusCapability () {
|
consensusCapability () {
|
||||||
return this._transport
|
return this._transport
|
||||||
.execute('parity_consensusCapability');
|
.execute('parity_consensusCapability');
|
||||||
@ -167,6 +182,12 @@ export default class Parity {
|
|||||||
.then((addresses) => addresses ? addresses.map(outAddress) : null);
|
.then((addresses) => addresses ? addresses.map(outAddress) : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getVaultMeta (vaultName) {
|
||||||
|
return this._transport
|
||||||
|
.execute('parity_getVaultMeta', vaultName)
|
||||||
|
.then(outVaultMeta);
|
||||||
|
}
|
||||||
|
|
||||||
hashContent (url) {
|
hashContent (url) {
|
||||||
return this._transport
|
return this._transport
|
||||||
.execute('parity_hashContent', url);
|
.execute('parity_hashContent', url);
|
||||||
@ -189,6 +210,16 @@ export default class Parity {
|
|||||||
.then((accounts) => (accounts || []).map(outAddress));
|
.then((accounts) => (accounts || []).map(outAddress));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
listOpenedVaults () {
|
||||||
|
return this._transport
|
||||||
|
.execute('parity_listOpenedVaults');
|
||||||
|
}
|
||||||
|
|
||||||
|
listVaults () {
|
||||||
|
return this._transport
|
||||||
|
.execute('parity_listVaults');
|
||||||
|
}
|
||||||
|
|
||||||
listRecentDapps () {
|
listRecentDapps () {
|
||||||
return this._transport
|
return this._transport
|
||||||
.execute('parity_listRecentDapps');
|
.execute('parity_listRecentDapps');
|
||||||
@ -275,6 +306,11 @@ export default class Parity {
|
|||||||
.then(outAddress);
|
.then(outAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
newVault (vaultName, password) {
|
||||||
|
return this._transport
|
||||||
|
.execute('parity_newVault', vaultName, password);
|
||||||
|
}
|
||||||
|
|
||||||
nextNonce (account) {
|
nextNonce (account) {
|
||||||
return this._transport
|
return this._transport
|
||||||
.execute('parity_nextNonce', inAddress(account))
|
.execute('parity_nextNonce', inAddress(account))
|
||||||
@ -286,6 +322,11 @@ export default class Parity {
|
|||||||
.execute('parity_nodeName');
|
.execute('parity_nodeName');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
openVault (vaultName, password) {
|
||||||
|
return this._transport
|
||||||
|
.execute('parity_openVault', vaultName, password);
|
||||||
|
}
|
||||||
|
|
||||||
pendingTransactions () {
|
pendingTransactions () {
|
||||||
return this._transport
|
return this._transport
|
||||||
.execute('parity_pendingTransactions')
|
.execute('parity_pendingTransactions')
|
||||||
@ -399,6 +440,11 @@ export default class Parity {
|
|||||||
.execute('parity_setTransactionsLimit', inNumber16(quantity));
|
.execute('parity_setTransactionsLimit', inNumber16(quantity));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setVaultMeta (vaultName, meta) {
|
||||||
|
return this._transport
|
||||||
|
.execute('parity_setVaultMeta', vaultName, JSON.stringify(meta));
|
||||||
|
}
|
||||||
|
|
||||||
signerPort () {
|
signerPort () {
|
||||||
return this._transport
|
return this._transport
|
||||||
.execute('parity_signerPort')
|
.execute('parity_signerPort')
|
||||||
|
@ -104,11 +104,14 @@ export default class Personal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (data.method) {
|
switch (data.method) {
|
||||||
|
case 'parity_closeVault':
|
||||||
|
case 'parity_openVault':
|
||||||
case 'parity_killAccount':
|
case 'parity_killAccount':
|
||||||
case 'parity_importGethAccounts':
|
case 'parity_importGethAccounts':
|
||||||
case 'personal_newAccount':
|
|
||||||
case 'parity_newAccountFromPhrase':
|
case 'parity_newAccountFromPhrase':
|
||||||
case 'parity_newAccountFromWallet':
|
case 'parity_newAccountFromWallet':
|
||||||
|
case 'personal_newAccount':
|
||||||
|
this._defaultAccount(true);
|
||||||
this._listAccounts();
|
this._listAccounts();
|
||||||
this._accountsInfo();
|
this._accountsInfo();
|
||||||
return;
|
return;
|
||||||
@ -116,6 +119,7 @@ export default class Personal {
|
|||||||
case 'parity_removeAddress':
|
case 'parity_removeAddress':
|
||||||
case 'parity_setAccountName':
|
case 'parity_setAccountName':
|
||||||
case 'parity_setAccountMeta':
|
case 'parity_setAccountMeta':
|
||||||
|
case 'parity_changeVault':
|
||||||
this._accountsInfo();
|
this._accountsInfo();
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -259,7 +259,7 @@ export class LocalTransaction extends BaseTransaction {
|
|||||||
to: transaction.to,
|
to: transaction.to,
|
||||||
nonce: transaction.nonce,
|
nonce: transaction.nonce,
|
||||||
value: transaction.value,
|
value: transaction.value,
|
||||||
data: transaction.data,
|
data: transaction.input,
|
||||||
gasPrice, gas
|
gasPrice, gas
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -105,22 +105,22 @@ export default class InputText extends Component {
|
|||||||
const { validationType, contract } = this.props;
|
const { validationType, contract } = this.props;
|
||||||
const validation = validate(value, validationType, contract);
|
const validation = validate(value, validationType, contract);
|
||||||
|
|
||||||
if (validation instanceof Promise) {
|
const loadingTimeout = setTimeout(() => {
|
||||||
this.setState({ disabled: true, loading: true });
|
this.setState({ disabled: true, loading: true });
|
||||||
|
}, 50);
|
||||||
|
|
||||||
return validation
|
return Promise.resolve(validation)
|
||||||
.then(validation => {
|
.then((validation) => {
|
||||||
this.setValidation({
|
clearTimeout(loadingTimeout);
|
||||||
...validation,
|
|
||||||
disabled: false,
|
|
||||||
loading: false
|
|
||||||
});
|
|
||||||
|
|
||||||
event.target.focus();
|
this.setValidation({
|
||||||
|
...validation,
|
||||||
|
disabled: false,
|
||||||
|
loading: false
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
this.setValidation(validation);
|
event.target.focus();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onKeyDown = (event) => {
|
onKeyDown = (event) => {
|
||||||
|
@ -49,7 +49,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.token-container {
|
.token-container {
|
||||||
flex: 1;
|
flex: 1 1 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.full-width .token-container {
|
.full-width .token-container {
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
const DEFAULT_LOCALE = 'en';
|
const DEFAULT_LOCALE = 'en';
|
||||||
const DEFAULT_LOCALES = process.env.NODE_ENV === 'production'
|
const DEFAULT_LOCALES = process.env.NODE_ENV === 'production'
|
||||||
? ['en']
|
? ['en']
|
||||||
: ['en', 'de'];
|
: ['en', 'de', 'nl'];
|
||||||
const LS_STORE_KEY = '_parity::locale';
|
const LS_STORE_KEY = '_parity::locale';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
@ -16,5 +16,6 @@
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
de: 'Deutsch',
|
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 { Address, Data, Hash, Quantity, BlockNumber, TransactionRequest } from '../types';
|
||||||
import { fromDecimal, withComment, Dummy } from '../helpers';
|
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_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_SET = 'set';
|
||||||
const SUBDOC_ACCOUNTS = 'accounts';
|
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: {
|
consensusCapability: {
|
||||||
desc: 'Returns information on current consensus capability.',
|
desc: 'Returns information on current consensus capability.',
|
||||||
params: [],
|
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: {
|
localTransactions: {
|
||||||
desc: 'Returns an object of current and past local transactions.',
|
desc: 'Returns an object of current and past local transactions.',
|
||||||
params: [],
|
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: {
|
nextNonce: {
|
||||||
section: SECTION_NET,
|
section: SECTION_NET,
|
||||||
desc: 'Returns next available nonce for transaction from given account. Includes pending block and transaction queue.',
|
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: {
|
pendingTransactions: {
|
||||||
section: SECTION_NET,
|
section: SECTION_NET,
|
||||||
desc: 'Returns a list of transactions currently in the queue.',
|
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: {
|
signerPort: {
|
||||||
section: SECTION_NODE,
|
section: SECTION_NODE,
|
||||||
desc: 'Returns the port the signer is running on, error if not enabled',
|
desc: 'Returns the port the signer is running on, error if not enabled',
|
||||||
|
@ -31,4 +31,4 @@ if (isNode) {
|
|||||||
|
|
||||||
import Etherscan from './3rdparty/etherscan';
|
import Etherscan from './3rdparty/etherscan';
|
||||||
|
|
||||||
module.exports = Etherscan;
|
export default Etherscan;
|
||||||
|
@ -16,4 +16,4 @@
|
|||||||
|
|
||||||
import JsonRpc from './jsonrpc';
|
import JsonRpc from './jsonrpc';
|
||||||
|
|
||||||
module.exports = JsonRpc;
|
export default JsonRpc;
|
||||||
|
@ -32,4 +32,4 @@ if (isNode) {
|
|||||||
import Abi from './abi';
|
import Abi from './abi';
|
||||||
import Api from './api';
|
import Api from './api';
|
||||||
|
|
||||||
module.exports = { Api, Abi };
|
export { Api, Abi };
|
||||||
|
@ -31,4 +31,4 @@ if (isNode) {
|
|||||||
|
|
||||||
import ShapeShift from './3rdparty/shapeshift';
|
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 { IconButton } from 'material-ui';
|
||||||
import { RadioButton, RadioButtonGroup } from 'material-ui/RadioButton';
|
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 { RefreshIcon } from '~/ui/Icons';
|
||||||
|
|
||||||
import styles from '../createAccount.css';
|
import styles from '../createAccount.css';
|
||||||
|
@ -18,7 +18,8 @@ import { observer } from 'mobx-react';
|
|||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
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';
|
import styles from '../createAccount.css';
|
||||||
|
|
||||||
|
@ -19,7 +19,8 @@ import React, { Component, PropTypes } from 'react';
|
|||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import { Checkbox } from 'material-ui';
|
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';
|
import styles from '../createAccount.css';
|
||||||
|
|
||||||
|
@ -20,7 +20,8 @@ import moment from 'moment';
|
|||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
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 { CancelIcon, CheckIcon, DeleteIcon } from '~/ui/Icons';
|
||||||
|
|
||||||
import styles from './loadContract.css';
|
import styles from './loadContract.css';
|
||||||
|
@ -23,7 +23,8 @@ import { connect } from 'react-redux';
|
|||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
|
|
||||||
import { newError, openSnackbar } from '~/redux/actions';
|
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 Form, { Input } from '~/ui/Form';
|
||||||
import { CancelIcon, CheckIcon, SendIcon } from '~/ui/Icons';
|
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 SaveIcon from 'material-ui/svg-icons/content/save';
|
||||||
import ContentClear from 'material-ui/svg-icons/content/clear';
|
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 { ERRORS, validateName } from '~/util/validation';
|
||||||
|
|
||||||
import styles from './saveContract.css';
|
import styles from './saveContract.css';
|
||||||
|
@ -14,15 +14,13 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import BigNumber from 'bignumber.js';
|
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import { Checkbox, MenuItem } from 'material-ui';
|
import { Checkbox } from 'material-ui';
|
||||||
import { isEqual } from 'lodash';
|
|
||||||
|
|
||||||
import Form, { Input, InputAddressSelect, AddressSelect, Select } from '~/ui/Form';
|
import Form, { Input, InputAddressSelect, AddressSelect } from '~/ui/Form';
|
||||||
import { nullableProptype } from '~/util/proptypes';
|
import { nullableProptype } from '~/util/proptypes';
|
||||||
|
|
||||||
import imageUnknown from '../../../../assets/images/contracts/unknown-64x64.png';
|
import TokenSelect from './tokenSelect';
|
||||||
import styles from '../transfer.css';
|
import styles from '../transfer.css';
|
||||||
|
|
||||||
const CHECK_STYLE = {
|
const CHECK_STYLE = {
|
||||||
@ -31,110 +29,12 @@ const CHECK_STYLE = {
|
|||||||
left: '1em'
|
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 {
|
export default class Details extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
address: PropTypes.string,
|
address: PropTypes.string,
|
||||||
balance: PropTypes.object,
|
balance: PropTypes.object,
|
||||||
all: PropTypes.bool,
|
all: PropTypes.bool,
|
||||||
extras: PropTypes.bool,
|
extras: PropTypes.bool,
|
||||||
images: PropTypes.object.isRequired,
|
|
||||||
sender: PropTypes.string,
|
sender: PropTypes.string,
|
||||||
senderError: PropTypes.string,
|
senderError: PropTypes.string,
|
||||||
sendersBalances: PropTypes.object,
|
sendersBalances: PropTypes.object,
|
||||||
@ -249,12 +149,11 @@ export default class Details extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderTokenSelect () {
|
renderTokenSelect () {
|
||||||
const { balance, images, tag } = this.props;
|
const { balance, tag } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TokenSelect
|
<TokenSelect
|
||||||
balance={ balance }
|
balance={ balance }
|
||||||
images={ images }
|
|
||||||
tag={ tag }
|
tag={ tag }
|
||||||
onChange={ this.onChangeToken }
|
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 = {
|
static propTypes = {
|
||||||
newError: PropTypes.func.isRequired,
|
newError: PropTypes.func.isRequired,
|
||||||
gasLimit: PropTypes.object.isRequired,
|
gasLimit: PropTypes.object.isRequired,
|
||||||
images: PropTypes.object.isRequired,
|
|
||||||
|
|
||||||
senders: nullableProptype(PropTypes.object),
|
senders: nullableProptype(PropTypes.object),
|
||||||
sendersBalances: nullableProptype(PropTypes.object),
|
sendersBalances: nullableProptype(PropTypes.object),
|
||||||
@ -174,7 +173,7 @@ class Transfer extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderDetailsPage () {
|
renderDetailsPage () {
|
||||||
const { account, balance, images, senders } = this.props;
|
const { account, balance, senders } = this.props;
|
||||||
const { recipient, recipientError, sender, senderError, sendersBalances } = this.store;
|
const { recipient, recipientError, sender, senderError, sendersBalances } = this.store;
|
||||||
const { valueAll, extras, tag, total, totalError, value, valueError } = this.store;
|
const { valueAll, extras, tag, total, totalError, value, valueError } = this.store;
|
||||||
|
|
||||||
@ -184,7 +183,6 @@ class Transfer extends Component {
|
|||||||
all={ valueAll }
|
all={ valueAll }
|
||||||
balance={ balance }
|
balance={ balance }
|
||||||
extras={ extras }
|
extras={ extras }
|
||||||
images={ images }
|
|
||||||
onChange={ this.store.onUpdateDetails }
|
onChange={ this.store.onUpdateDetails }
|
||||||
recipient={ recipient }
|
recipient={ recipient }
|
||||||
recipientError={ recipientError }
|
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 initReducers from './reducers';
|
||||||
|
|
||||||
import { load as loadWallet } from './providers/walletActions';
|
import { load as loadWallet } from './providers/walletActions';
|
||||||
import { setupWorker } from './providers/worker';
|
import { setupWorker } from './providers/workerWrapper';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Balances as BalancesProvider,
|
Balances as BalancesProvider,
|
||||||
|
@ -65,7 +65,7 @@ describe('ui/AccountCard', () => {
|
|||||||
let balance;
|
let balance;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
balance = component.find('Connect(Balance)');
|
balance = component.find('Balance');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders the balance', () => {
|
it('renders the balance', () => {
|
||||||
|
@ -14,4 +14,9 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// 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';
|
export default from './actionbar';
|
||||||
|
@ -17,13 +17,12 @@
|
|||||||
import BigNumber from 'bignumber.js';
|
import BigNumber from 'bignumber.js';
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
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';
|
import styles from './balance.css';
|
||||||
|
|
||||||
class Balance extends Component {
|
export default class Balance extends Component {
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
api: PropTypes.object
|
api: PropTypes.object
|
||||||
};
|
};
|
||||||
@ -31,7 +30,6 @@ class Balance extends Component {
|
|||||||
static propTypes = {
|
static propTypes = {
|
||||||
balance: PropTypes.object,
|
balance: PropTypes.object,
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
images: PropTypes.object.isRequired,
|
|
||||||
showOnlyEth: PropTypes.bool,
|
showOnlyEth: PropTypes.bool,
|
||||||
showZeroValues: PropTypes.bool
|
showZeroValues: PropTypes.bool
|
||||||
};
|
};
|
||||||
@ -43,7 +41,7 @@ class Balance extends Component {
|
|||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { api } = this.context;
|
const { api } = this.context;
|
||||||
const { balance, className, images, showZeroValues, showOnlyEth } = this.props;
|
const { balance, className, showZeroValues, showOnlyEth } = this.props;
|
||||||
|
|
||||||
if (!balance || !balance.tokens) {
|
if (!balance || !balance.tokens) {
|
||||||
return null;
|
return null;
|
||||||
@ -79,26 +77,12 @@ class Balance extends Component {
|
|||||||
value = api.util.fromWei(balance.value).toFormat(3);
|
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 (
|
return (
|
||||||
<div
|
<div
|
||||||
className={ styles.balance }
|
className={ styles.balance }
|
||||||
key={ `${index}_${token.tag}` }
|
key={ `${index}_${token.tag}` }
|
||||||
>
|
>
|
||||||
<img
|
<TokenImage token={ token } />
|
||||||
src={ imagesrc }
|
|
||||||
alt={ token.name }
|
|
||||||
/>
|
|
||||||
<div className={ styles.balanceValue }>
|
<div className={ styles.balanceValue }>
|
||||||
<span title={ value }> { value } </span>
|
<span title={ value }> { value } </span>
|
||||||
</div>
|
</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 { shallow } from 'enzyme';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import sinon from 'sinon';
|
|
||||||
|
|
||||||
import apiutil from '~/api/util';
|
import apiutil from '~/api/util';
|
||||||
|
|
||||||
@ -32,7 +31,6 @@ const BALANCE = {
|
|||||||
|
|
||||||
let api;
|
let api;
|
||||||
let component;
|
let component;
|
||||||
let store;
|
|
||||||
|
|
||||||
function createApi () {
|
function createApi () {
|
||||||
api = {
|
api = {
|
||||||
@ -43,36 +41,22 @@ function createApi () {
|
|||||||
return api;
|
return api;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createStore () {
|
|
||||||
store = {
|
|
||||||
dispatch: sinon.stub(),
|
|
||||||
subscribe: sinon.stub(),
|
|
||||||
getState: () => {
|
|
||||||
return {
|
|
||||||
images: {}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return store;
|
|
||||||
}
|
|
||||||
|
|
||||||
function render (props = {}) {
|
function render (props = {}) {
|
||||||
if (!props.balance) {
|
if (!props.balance) {
|
||||||
props.balance = BALANCE;
|
props.balance = BALANCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const api = createApi();
|
||||||
|
|
||||||
component = shallow(
|
component = shallow(
|
||||||
<Balance
|
<Balance
|
||||||
className='testClass'
|
className='testClass'
|
||||||
{ ...props }
|
{ ...props }
|
||||||
/>,
|
/>,
|
||||||
{
|
{
|
||||||
context: {
|
context: { api }
|
||||||
store: createStore()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
).find('Balance').shallow({ context: { api: createApi() } });
|
);
|
||||||
|
|
||||||
return component;
|
return component;
|
||||||
}
|
}
|
||||||
@ -91,18 +75,18 @@ describe('ui/Balance', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('renders all the non-zero balances', () => {
|
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', () => {
|
describe('render specifiers', () => {
|
||||||
it('renders only the single token with showOnlyEth', () => {
|
it('renders only the single token with showOnlyEth', () => {
|
||||||
render({ showOnlyEth: true });
|
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', () => {
|
it('renders all the tokens with showZeroValues', () => {
|
||||||
render({ showZeroValues: true });
|
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', () => {
|
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
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import AddressSelect from './AddressSelect';
|
export AddressSelect from './AddressSelect';
|
||||||
import DappUrlInput from './DappUrlInput';
|
export DappUrlInput from './DappUrlInput';
|
||||||
import FormWrap from './FormWrap';
|
export FormWrap from './FormWrap';
|
||||||
import Input from './Input';
|
export Input from './Input';
|
||||||
import InputAddress from './InputAddress';
|
export InputAddress from './InputAddress';
|
||||||
import InputAddressSelect from './InputAddressSelect';
|
export InputAddressSelect from './InputAddressSelect';
|
||||||
import InputChip from './InputChip';
|
export InputChip from './InputChip';
|
||||||
import InputDate from './InputDate';
|
export InputDate from './InputDate';
|
||||||
import InputInline from './InputInline';
|
export InputInline from './InputInline';
|
||||||
import InputTime from './InputTime';
|
export InputTime from './InputTime';
|
||||||
import Label from './Label';
|
export Label from './Label';
|
||||||
import RadioButtons from './RadioButtons';
|
export RadioButtons from './RadioButtons';
|
||||||
import Select from './Select';
|
export Select from './Select';
|
||||||
import TypedInput from './TypedInput';
|
export TypedInput from './TypedInput';
|
||||||
|
|
||||||
export default from './form';
|
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 moment from 'moment';
|
||||||
import React, { Component, PropTypes } from 'react';
|
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 IdentityIcon from '../../IdentityIcon';
|
||||||
import IdentityName from '../../IdentityName';
|
import IdentityName from '../../IdentityName';
|
||||||
@ -25,19 +27,20 @@ import MethodDecoding from '../../MethodDecoding';
|
|||||||
|
|
||||||
import styles from '../txList.css';
|
import styles from '../txList.css';
|
||||||
|
|
||||||
export default class TxRow extends Component {
|
class TxRow extends Component {
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
api: PropTypes.object.isRequired
|
api: PropTypes.object.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
tx: PropTypes.object.isRequired,
|
accountAddresses: PropTypes.array.isRequired,
|
||||||
address: PropTypes.string.isRequired,
|
address: PropTypes.string.isRequired,
|
||||||
isTest: PropTypes.bool.isRequired,
|
isTest: PropTypes.bool.isRequired,
|
||||||
|
tx: PropTypes.object.isRequired,
|
||||||
|
|
||||||
block: PropTypes.object,
|
block: PropTypes.object,
|
||||||
historic: PropTypes.bool,
|
className: PropTypes.string,
|
||||||
className: PropTypes.string
|
historic: PropTypes.bool
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
@ -77,22 +80,20 @@ export default class TxRow extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderAddress (address) {
|
renderAddress (address) {
|
||||||
const { isTest } = this.props;
|
|
||||||
|
|
||||||
let esLink = null;
|
let esLink = null;
|
||||||
|
|
||||||
if (address) {
|
if (address) {
|
||||||
esLink = (
|
esLink = (
|
||||||
<a
|
<Link
|
||||||
href={ addressLink(address, isTest) }
|
activeClassName={ styles.currentLink }
|
||||||
target='_blank'
|
|
||||||
className={ styles.link }
|
className={ styles.link }
|
||||||
|
to={ this.addressLink(address) }
|
||||||
>
|
>
|
||||||
<IdentityName
|
<IdentityName
|
||||||
address={ address }
|
address={ address }
|
||||||
shorten
|
shorten
|
||||||
/>
|
/>
|
||||||
</a>
|
</Link>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,4 +139,30 @@ export default class TxRow extends Component {
|
|||||||
</td>
|
</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 api = new Api({ execute: sinon.stub() });
|
||||||
|
|
||||||
|
const STORE = {
|
||||||
|
dispatch: sinon.stub(),
|
||||||
|
subscribe: sinon.stub(),
|
||||||
|
getState: () => {
|
||||||
|
return {
|
||||||
|
personal: {
|
||||||
|
accounts: {
|
||||||
|
'0x123': {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
function render (props) {
|
function render (props) {
|
||||||
return shallow(
|
return shallow(
|
||||||
<TxRow
|
<TxRow
|
||||||
|
store={ STORE }
|
||||||
{ ...props }
|
{ ...props }
|
||||||
/>,
|
/>,
|
||||||
{ context: { api } }
|
{ context: { api } }
|
||||||
);
|
).find('TxRow').shallow({ context: { api } });
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('ui/TxList/TxRow', () => {
|
describe('ui/TxList/TxRow', () => {
|
||||||
@ -48,5 +63,37 @@ describe('ui/TxList/TxRow', () => {
|
|||||||
|
|
||||||
expect(render({ address: '0x123', block, isTest: true, tx })).to.be.ok;
|
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 {
|
.link {
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
|
|
||||||
|
&.currentLink {
|
||||||
|
color: white;
|
||||||
|
cursor: text;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.right {
|
.right {
|
||||||
|
@ -14,118 +14,43 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import AccountCard from './AccountCard';
|
export AccountCard from './AccountCard';
|
||||||
import Actionbar from './Actionbar';
|
export Actionbar, { Export as ActionbarExport, Import as ActionbarImport, Search as ActionbarSearch, Sort as ActionbarSort } from './Actionbar';
|
||||||
import ActionbarExport from './Actionbar/Export';
|
export Badge from './Badge';
|
||||||
import ActionbarImport from './Actionbar/Import';
|
export Balance from './Balance';
|
||||||
import ActionbarSearch from './Actionbar/Search';
|
export BlockStatus from './BlockStatus';
|
||||||
import ActionbarSort from './Actionbar/Sort';
|
export Button from './Button';
|
||||||
import Badge from './Badge';
|
export Certifications from './Certifications';
|
||||||
import Balance from './Balance';
|
export ConfirmDialog from './ConfirmDialog';
|
||||||
import BlockStatus from './BlockStatus';
|
export Container, { Title as ContainerTitle } from './Container';
|
||||||
import Button from './Button';
|
export ContextProvider from './ContextProvider';
|
||||||
import Certifications from './Certifications';
|
export CopyToClipboard from './CopyToClipboard';
|
||||||
import ConfirmDialog from './ConfirmDialog';
|
export CurrencySymbol from './CurrencySymbol';
|
||||||
import Container, { Title as ContainerTitle } from './Container';
|
export DappCard from './DappCard';
|
||||||
import ContextProvider from './ContextProvider';
|
export DappIcon from './DappIcon';
|
||||||
import CopyToClipboard from './CopyToClipboard';
|
export Errors from './Errors';
|
||||||
import CurrencySymbol from './CurrencySymbol';
|
export Features, { FEATURES, FeaturesStore } from './Features';
|
||||||
import DappCard from './DappCard';
|
export Form, { AddressSelect, DappUrlInput, FormWrap, Input, InputAddress, InputAddressSelect, InputChip, InputDate, InputInline, InputTime, Label, RadioButtons, Select, TypedInput } from './Form';
|
||||||
import DappIcon from './DappIcon';
|
export GasPriceEditor from './GasPriceEditor';
|
||||||
import Editor from './Editor';
|
export GasPriceSelector from './GasPriceSelector';
|
||||||
import Errors from './Errors';
|
export Icons from './Icons';
|
||||||
import Features, { FEATURES, FeaturesStore } from './Features';
|
export IdentityIcon from './IdentityIcon';
|
||||||
import Form, { AddressSelect, DappUrlInput, FormWrap, Input, InputAddress, InputAddressSelect, InputChip, InputDate, InputInline, InputTime, Label, RadioButtons, Select, TypedInput } from './Form';
|
export IdentityName from './IdentityName';
|
||||||
import GasPriceEditor from './GasPriceEditor';
|
export LanguageSelector from './LanguageSelector';
|
||||||
import GasPriceSelector from './GasPriceSelector';
|
export Loading from './Loading';
|
||||||
import Icons from './Icons';
|
export MethodDecoding from './MethodDecoding';
|
||||||
import IdentityIcon from './IdentityIcon';
|
export Modal, { Busy as BusyStep, Completed as CompletedStep } from './Modal';
|
||||||
import IdentityName from './IdentityName';
|
export muiTheme from './Theme';
|
||||||
import LanguageSelector from './LanguageSelector';
|
export Page from './Page';
|
||||||
import Loading from './Loading';
|
export ParityBackground from './ParityBackground';
|
||||||
import MethodDecoding from './MethodDecoding';
|
export Portal from './Portal';
|
||||||
import Modal, { Busy as BusyStep, Completed as CompletedStep } from './Modal';
|
export QrCode from './QrCode';
|
||||||
import muiTheme from './Theme';
|
export SectionList from './SectionList';
|
||||||
import Page from './Page';
|
export ShortenedHash from './ShortenedHash';
|
||||||
import ParityBackground from './ParityBackground';
|
export SignerIcon from './SignerIcon';
|
||||||
import PasswordStrength from './Form/PasswordStrength';
|
export Tags from './Tags';
|
||||||
import Portal from './Portal';
|
export Title from './Title';
|
||||||
import QrCode from './QrCode';
|
export Tooltips, { Tooltip } from './Tooltips';
|
||||||
import SectionList from './SectionList';
|
export TxHash from './TxHash';
|
||||||
import ShortenedHash from './ShortenedHash';
|
export TxList from './TxList';
|
||||||
import SignerIcon from './SignerIcon';
|
export Warning from './Warning';
|
||||||
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
|
|
||||||
};
|
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import scrypt from 'scryptsy';
|
import scrypt from 'scryptsy';
|
||||||
import * as Transaction from 'ethereumjs-tx';
|
import Transaction from 'ethereumjs-tx';
|
||||||
import { pbkdf2Sync } from 'crypto';
|
import { pbkdf2Sync } from 'crypto';
|
||||||
import { createDecipheriv } from 'browserify-aes';
|
import { createDecipheriv } from 'browserify-aes';
|
||||||
|
|
||||||
|
@ -73,7 +73,7 @@ describe('views/Account/Header', () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
render({ balance: { balance: 'testing' } });
|
render({ balance: { balance: 'testing' } });
|
||||||
balance = component.find('Connect(Balance)');
|
balance = component.find('Balance');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders', () => {
|
it('renders', () => {
|
||||||
|
@ -37,7 +37,6 @@ class Account extends Component {
|
|||||||
static propTypes = {
|
static propTypes = {
|
||||||
fetchCertifiers: PropTypes.func.isRequired,
|
fetchCertifiers: PropTypes.func.isRequired,
|
||||||
fetchCertifications: PropTypes.func.isRequired,
|
fetchCertifications: PropTypes.func.isRequired,
|
||||||
images: PropTypes.object.isRequired,
|
|
||||||
setVisibleAccounts: PropTypes.func.isRequired,
|
setVisibleAccounts: PropTypes.func.isRequired,
|
||||||
|
|
||||||
accounts: PropTypes.object,
|
accounts: PropTypes.object,
|
||||||
@ -257,14 +256,13 @@ class Account extends Component {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { balances, images } = this.props;
|
const { balances } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Transfer
|
<Transfer
|
||||||
account={ account }
|
account={ account }
|
||||||
balance={ balance }
|
balance={ balance }
|
||||||
balances={ balances }
|
balances={ balances }
|
||||||
images={ images }
|
|
||||||
onClose={ this.store.toggleTransferDialog }
|
onClose={ this.store.toggleTransferDialog }
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@ -289,12 +287,10 @@ class Account extends Component {
|
|||||||
function mapStateToProps (state) {
|
function mapStateToProps (state) {
|
||||||
const { accounts } = state.personal;
|
const { accounts } = state.personal;
|
||||||
const { balances } = state.balances;
|
const { balances } = state.balances;
|
||||||
const { images } = state;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
accounts,
|
accounts,
|
||||||
balances,
|
balances
|
||||||
images
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,7 +30,8 @@ import { newError } from '~/redux/actions';
|
|||||||
import { setVisibleAccounts } from '~/redux/providers/personalActions';
|
import { setVisibleAccounts } from '~/redux/providers/personalActions';
|
||||||
|
|
||||||
import { EditMeta, ExecuteContract } from '~/modals';
|
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 Header from '../Account/Header';
|
||||||
import Delete from '../Address/Delete';
|
import Delete from '../Address/Delete';
|
||||||
|
@ -285,6 +285,7 @@ class ParityBar extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderExpanded () {
|
renderExpanded () {
|
||||||
|
const { externalLink } = this.props;
|
||||||
const { displayType } = this.state;
|
const { displayType } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -333,7 +334,7 @@ class ParityBar extends Component {
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
: (
|
: (
|
||||||
<Signer />
|
<Signer externalLink={ externalLink } />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
@ -15,16 +15,19 @@
|
|||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import React, { Component, PropTypes } from 'react';
|
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';
|
import styles from './accountLink.css';
|
||||||
|
|
||||||
export default class AccountLink extends Component {
|
class AccountLink extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
isTest: PropTypes.bool.isRequired,
|
accountAddresses: PropTypes.array.isRequired,
|
||||||
address: PropTypes.string.isRequired,
|
address: PropTypes.string.isRequired,
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
children: PropTypes.node
|
children: PropTypes.node,
|
||||||
|
externalLink: PropTypes.string.isRequired,
|
||||||
|
isTest: PropTypes.bool.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
@ -32,36 +35,72 @@ export default class AccountLink extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
componentWillMount () {
|
componentWillMount () {
|
||||||
const { address, isTest } = this.props;
|
const { address, externalLink, isTest } = this.props;
|
||||||
|
|
||||||
this.updateLink(address, isTest);
|
this.updateLink(address, externalLink, isTest);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps (nextProps) {
|
componentWillReceiveProps (nextProps) {
|
||||||
const { address, isTest } = nextProps;
|
const { address, externalLink, isTest } = nextProps;
|
||||||
|
|
||||||
this.updateLink(address, isTest);
|
this.updateLink(address, externalLink, isTest);
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
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 (
|
return (
|
||||||
<a
|
<Link
|
||||||
href={ this.state.link }
|
|
||||||
target='_blank'
|
|
||||||
className={ `${styles.container} ${className}` }
|
className={ `${styles.container} ${className}` }
|
||||||
|
to={ this.state.link }
|
||||||
>
|
>
|
||||||
{ children || address }
|
{ children || address }
|
||||||
</a>
|
</Link>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateLink (address, isTest) {
|
updateLink (address, externalLink, isTest) {
|
||||||
const link = addressLink(address, 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({
|
this.setState({
|
||||||
link
|
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 = {
|
static propTypes = {
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
address: PropTypes.string.isRequired,
|
address: PropTypes.string.isRequired,
|
||||||
|
externalLink: PropTypes.string.isRequired,
|
||||||
isTest: PropTypes.bool.isRequired,
|
isTest: PropTypes.bool.isRequired,
|
||||||
balance: PropTypes.object // eth BigNumber, not required since it mght take time to fetch
|
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 () {
|
render () {
|
||||||
const { address, isTest, className } = this.props;
|
const { address, externalLink, isTest, className } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ `${styles.acc} ${className}` }>
|
<div className={ `${styles.acc} ${className}` }>
|
||||||
<AccountLink
|
<AccountLink
|
||||||
address={ address }
|
address={ address }
|
||||||
|
externalLink={ externalLink }
|
||||||
isTest={ isTest }
|
isTest={ isTest }
|
||||||
>
|
>
|
||||||
<IdentityIcon
|
<IdentityIcon
|
||||||
@ -79,13 +81,14 @@ export default class Account extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderName () {
|
renderName () {
|
||||||
const { address, isTest } = this.props;
|
const { address, externalLink, isTest } = this.props;
|
||||||
const name = <IdentityName address={ address } empty />;
|
const name = <IdentityName address={ address } empty />;
|
||||||
|
|
||||||
if (!name) {
|
if (!name) {
|
||||||
return (
|
return (
|
||||||
<AccountLink
|
<AccountLink
|
||||||
address={ address }
|
address={ address }
|
||||||
|
externalLink={ externalLink }
|
||||||
isTest={ isTest }
|
isTest={ isTest }
|
||||||
>
|
>
|
||||||
[{ this.shortAddress(address) }]
|
[{ this.shortAddress(address) }]
|
||||||
@ -96,6 +99,7 @@ export default class Account extends Component {
|
|||||||
return (
|
return (
|
||||||
<AccountLink
|
<AccountLink
|
||||||
address={ address }
|
address={ address }
|
||||||
|
externalLink={ externalLink }
|
||||||
isTest={ isTest }
|
isTest={ isTest }
|
||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
|
@ -93,7 +93,9 @@ export default class SignRequest extends Component {
|
|||||||
renderDetails () {
|
renderDetails () {
|
||||||
const { api } = this.context;
|
const { api } = this.context;
|
||||||
const { address, isTest, store, data } = this.props;
|
const { address, isTest, store, data } = this.props;
|
||||||
const balance = store.balances[address];
|
const { balances, externalLink } = store;
|
||||||
|
|
||||||
|
const balance = balances[address];
|
||||||
|
|
||||||
if (!balance) {
|
if (!balance) {
|
||||||
return <div />;
|
return <div />;
|
||||||
@ -105,6 +107,7 @@ export default class SignRequest extends Component {
|
|||||||
<Account
|
<Account
|
||||||
address={ address }
|
address={ address }
|
||||||
balance={ balance }
|
balance={ balance }
|
||||||
|
externalLink={ externalLink }
|
||||||
isTest={ isTest }
|
isTest={ isTest }
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -27,6 +27,7 @@ import styles from './transactionMainDetails.css';
|
|||||||
export default class TransactionMainDetails extends Component {
|
export default class TransactionMainDetails extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
|
externalLink: PropTypes.string.isRequired,
|
||||||
from: PropTypes.string.isRequired,
|
from: PropTypes.string.isRequired,
|
||||||
fromBalance: PropTypes.object,
|
fromBalance: PropTypes.object,
|
||||||
gasStore: PropTypes.object,
|
gasStore: PropTypes.object,
|
||||||
@ -50,7 +51,7 @@ export default class TransactionMainDetails extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { children, from, fromBalance, gasStore, isTest, transaction } = this.props;
|
const { children, externalLink, from, fromBalance, gasStore, isTest, transaction } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ styles.transaction }>
|
<div className={ styles.transaction }>
|
||||||
@ -59,6 +60,7 @@ export default class TransactionMainDetails extends Component {
|
|||||||
<Account
|
<Account
|
||||||
address={ from }
|
address={ from }
|
||||||
balance={ fromBalance }
|
balance={ fromBalance }
|
||||||
|
externalLink={ externalLink }
|
||||||
isTest={ isTest }
|
isTest={ isTest }
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -89,14 +89,16 @@ export default class TransactionPending extends Component {
|
|||||||
renderTransaction () {
|
renderTransaction () {
|
||||||
const { className, focus, id, isSending, isTest, store, transaction } = this.props;
|
const { className, focus, id, isSending, isTest, store, transaction } = this.props;
|
||||||
const { totalValue } = this.state;
|
const { totalValue } = this.state;
|
||||||
|
const { balances, externalLink } = store;
|
||||||
const { from, value } = transaction;
|
const { from, value } = transaction;
|
||||||
|
|
||||||
const fromBalance = store.balances[from];
|
const fromBalance = balances[from];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ `${styles.container} ${className}` }>
|
<div className={ `${styles.container} ${className}` }>
|
||||||
<TransactionMainDetails
|
<TransactionMainDetails
|
||||||
className={ styles.transactionDetails }
|
className={ styles.transactionDetails }
|
||||||
|
externalLink={ externalLink }
|
||||||
from={ from }
|
from={ from }
|
||||||
fromBalance={ fromBalance }
|
fromBalance={ fromBalance }
|
||||||
gasStore={ this.gasStore }
|
gasStore={ this.gasStore }
|
||||||
|
@ -37,6 +37,7 @@ class Embedded extends Component {
|
|||||||
startConfirmRequest: PropTypes.func.isRequired,
|
startConfirmRequest: PropTypes.func.isRequired,
|
||||||
startRejectRequest: PropTypes.func.isRequired
|
startRejectRequest: PropTypes.func.isRequired
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
|
externalLink: PropTypes.string,
|
||||||
gasLimit: PropTypes.object.isRequired,
|
gasLimit: PropTypes.object.isRequired,
|
||||||
isTest: PropTypes.bool.isRequired,
|
isTest: PropTypes.bool.isRequired,
|
||||||
signer: PropTypes.shape({
|
signer: PropTypes.shape({
|
||||||
@ -45,7 +46,7 @@ class Embedded extends Component {
|
|||||||
}).isRequired
|
}).isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
store = new Store(this.context.api);
|
store = new Store(this.context.api, false, this.props.externalLink);
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
return (
|
return (
|
||||||
|
@ -17,13 +17,16 @@
|
|||||||
import { isEqual } from 'lodash';
|
import { isEqual } from 'lodash';
|
||||||
import { action, observable } from 'mobx';
|
import { action, observable } from 'mobx';
|
||||||
|
|
||||||
export default class Store {
|
export default class SignerStore {
|
||||||
@observable balances = {};
|
@observable balances = {};
|
||||||
@observable localHashes = [];
|
@observable localHashes = [];
|
||||||
|
|
||||||
constructor (api, withLocalTransactions = false) {
|
externalLink = '';
|
||||||
|
|
||||||
|
constructor (api, withLocalTransactions = false, externalLink = '') {
|
||||||
this._api = api;
|
this._api = api;
|
||||||
this._timeoutId = 0;
|
this._timeoutId = 0;
|
||||||
|
this.externalLink = externalLink;
|
||||||
|
|
||||||
if (withLocalTransactions) {
|
if (withLocalTransactions) {
|
||||||
this.fetchLocalTransactions();
|
this.fetchLocalTransactions();
|
||||||
|
@ -66,7 +66,6 @@ class Wallet extends Component {
|
|||||||
static propTypes = {
|
static propTypes = {
|
||||||
address: PropTypes.string.isRequired,
|
address: PropTypes.string.isRequired,
|
||||||
balance: nullableProptype(PropTypes.object.isRequired),
|
balance: nullableProptype(PropTypes.object.isRequired),
|
||||||
images: PropTypes.object.isRequired,
|
|
||||||
isTest: PropTypes.bool.isRequired,
|
isTest: PropTypes.bool.isRequired,
|
||||||
owned: PropTypes.bool.isRequired,
|
owned: PropTypes.bool.isRequired,
|
||||||
setVisibleAccounts: PropTypes.func.isRequired,
|
setVisibleAccounts: PropTypes.func.isRequired,
|
||||||
@ -315,13 +314,12 @@ class Wallet extends Component {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { walletAccount, balance, images } = this.props;
|
const { walletAccount, balance } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Transfer
|
<Transfer
|
||||||
account={ walletAccount }
|
account={ walletAccount }
|
||||||
balance={ balance }
|
balance={ balance }
|
||||||
images={ images }
|
|
||||||
onClose={ this.onTransferClose }
|
onClose={ this.onTransferClose }
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@ -365,7 +363,6 @@ function mapStateToProps (_, initProps) {
|
|||||||
const { isTest } = state.nodeStatus;
|
const { isTest } = state.nodeStatus;
|
||||||
const { accountsInfo = {}, accounts = {} } = state.personal;
|
const { accountsInfo = {}, accounts = {} } = state.personal;
|
||||||
const { balances } = state.balances;
|
const { balances } = state.balances;
|
||||||
const { images } = state;
|
|
||||||
const walletAccount = accounts[address] || accountsInfo[address] || null;
|
const walletAccount = accounts[address] || accountsInfo[address] || null;
|
||||||
|
|
||||||
if (walletAccount) {
|
if (walletAccount) {
|
||||||
@ -379,7 +376,6 @@ function mapStateToProps (_, initProps) {
|
|||||||
return {
|
return {
|
||||||
address,
|
address,
|
||||||
balance,
|
balance,
|
||||||
images,
|
|
||||||
isTest,
|
isTest,
|
||||||
owned,
|
owned,
|
||||||
wallet,
|
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 SettingsIcon from 'material-ui/svg-icons/action/settings';
|
||||||
import SendIcon from 'material-ui/svg-icons/content/send';
|
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 { DeployContract, SaveContract, LoadContract } from '~/modals';
|
||||||
|
|
||||||
import WriteContractStore from './writeContractStore';
|
import WriteContractStore from './writeContractStore';
|
||||||
|
@ -14,44 +14,20 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import Account from './Account';
|
export Account from './Account';
|
||||||
import Accounts from './Accounts';
|
export Accounts from './Accounts';
|
||||||
import Address from './Address';
|
export Address from './Address';
|
||||||
import Addresses from './Addresses';
|
export Addresses from './Addresses';
|
||||||
import Application from './Application';
|
export Application from './Application';
|
||||||
import Contract from './Contract';
|
export Contract from './Contract';
|
||||||
import Contracts from './Contracts';
|
export Contracts from './Contracts';
|
||||||
import Dapp from './Dapp';
|
export Dapp from './Dapp';
|
||||||
import Dapps from './Dapps';
|
export Dapps from './Dapps';
|
||||||
import HistoryStore from './historyStore';
|
export HistoryStore from './historyStore';
|
||||||
import ParityBar from './ParityBar';
|
export ParityBar from './ParityBar';
|
||||||
import Settings, { SettingsBackground, SettingsParity, SettingsProxy, SettingsViews } from './Settings';
|
export Settings, { SettingsBackground, SettingsParity, SettingsProxy, SettingsViews } from './Settings';
|
||||||
import Signer from './Signer';
|
export Signer from './Signer';
|
||||||
import Status from './Status';
|
export Status from './Status';
|
||||||
import Wallet from './Wallet';
|
export Wallet from './Wallet';
|
||||||
import Web from './Web';
|
export Web from './Web';
|
||||||
import WriteContract from './WriteContract';
|
export 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
|
|
||||||
};
|
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var JsonRpc = require('../.npmjs/jsonRpc/library.js');
|
var JsonRpc = require('../.npmjs/jsonrpc/library.js').default;
|
||||||
|
|
||||||
if (typeof JsonRpc !== 'object') {
|
if (typeof JsonRpc !== 'object') {
|
||||||
throw new Error('JsonRpc');
|
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 DEST = process.env.BUILD_DEST || '.build';
|
||||||
const ENV = process.env.NODE_ENV || 'development';
|
const ENV = process.env.NODE_ENV || 'development';
|
||||||
|
const EMBED = process.env.EMBED;
|
||||||
|
|
||||||
const isProd = ENV === 'production';
|
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 = {
|
module.exports = {
|
||||||
cache: !isProd,
|
cache: !isProd,
|
||||||
devtool: isProd ? '#hidden-source-map' : '#source-map',
|
devtool: isProd ? '#hidden-source-map' : '#source-map',
|
||||||
|
|
||||||
context: path.join(__dirname, '../src'),
|
context: path.join(__dirname, '../src'),
|
||||||
entry: Object.assign({}, Shared.dappsEntry, {
|
entry: entry,
|
||||||
index: './index.js',
|
|
||||||
embed: './embed.js'
|
|
||||||
}),
|
|
||||||
output: {
|
output: {
|
||||||
// publicPath: '/',
|
// publicPath: '/',
|
||||||
path: path.join(__dirname, '../', DEST),
|
path: path.join(__dirname, '../', DEST),
|
||||||
@ -55,15 +63,12 @@ module.exports = {
|
|||||||
test: /\.js$/,
|
test: /\.js$/,
|
||||||
exclude: /(node_modules)/,
|
exclude: /(node_modules)/,
|
||||||
// use: [ 'happypack/loader?id=js' ]
|
// use: [ 'happypack/loader?id=js' ]
|
||||||
use: isProd ? ['babel-loader'] : [
|
use: isProd ? 'babel-loader' : 'babel-loader?cacheDirectory=true'
|
||||||
'babel-loader?cacheDirectory=true'
|
|
||||||
],
|
|
||||||
options: Shared.getBabelrc()
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.js$/,
|
test: /\.js$/,
|
||||||
include: /node_modules\/material-ui-chip-input/,
|
include: /node_modules\/(material-chip-input|ethereumjs-tx)/,
|
||||||
use: [ 'babel-loader' ]
|
use: 'babel-loader'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.json$/,
|
test: /\.json$/,
|
||||||
@ -91,13 +96,13 @@ module.exports = {
|
|||||||
test: /\.css$/,
|
test: /\.css$/,
|
||||||
include: [ /src/ ],
|
include: [ /src/ ],
|
||||||
// exclude: [ /src\/dapps/ ],
|
// exclude: [ /src\/dapps/ ],
|
||||||
loader: isProd ? ExtractTextPlugin.extract([
|
loader: (isProd && !isEmbed) ? ExtractTextPlugin.extract([
|
||||||
// 'style-loader',
|
// 'style-loader',
|
||||||
'css-loader?modules&sourceMap&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]',
|
'css-loader?modules&sourceMap&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]',
|
||||||
'postcss-loader'
|
'postcss-loader'
|
||||||
]) : undefined,
|
]) : undefined,
|
||||||
// use: [ 'happypack/loader?id=css' ]
|
// use: [ 'happypack/loader?id=css' ]
|
||||||
use: isProd ? undefined : [
|
use: (isProd && !isEmbed) ? undefined : [
|
||||||
'style-loader',
|
'style-loader',
|
||||||
'css-loader?modules&sourceMap&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]',
|
'css-loader?modules&sourceMap&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]',
|
||||||
'postcss-loader'
|
'postcss-loader'
|
||||||
@ -155,53 +160,63 @@ module.exports = {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const plugins = Shared.getPlugins().concat(
|
let plugins = Shared.getPlugins().concat(
|
||||||
new CopyWebpackPlugin([
|
new WebpackErrorNotificationPlugin()
|
||||||
{ 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
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
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) {
|
if (!isProd) {
|
||||||
const DEST_I18N = path.join(__dirname, '..', DEST, 'i18n');
|
const DEST_I18N = path.join(__dirname, '..', DEST, 'i18n');
|
||||||
|
|
||||||
|
@ -36,4 +36,5 @@ app.use(wsProxy);
|
|||||||
var server = app.listen(process.env.PORT || 3000, function () {
|
var server = app.listen(process.env.PORT || 3000, function () {
|
||||||
console.log('Listening on port', server.address().port);
|
console.log('Listening on port', server.address().port);
|
||||||
});
|
});
|
||||||
|
|
||||||
server.on('upgrade', wsProxy.upgrade);
|
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 CircularDependencyPlugin = require('circular-dependency-plugin');
|
||||||
const ProgressBarPlugin = require('progress-bar-webpack-plugin');
|
const ProgressBarPlugin = require('progress-bar-webpack-plugin');
|
||||||
|
|
||||||
|
const EMBED = process.env.EMBED;
|
||||||
const ENV = process.env.NODE_ENV || 'development';
|
const ENV = process.env.NODE_ENV || 'development';
|
||||||
const isProd = ENV === 'production';
|
const isProd = ENV === 'production';
|
||||||
|
|
||||||
@ -113,6 +114,7 @@ function getPlugins (_isProd = isProd) {
|
|||||||
|
|
||||||
new webpack.DefinePlugin({
|
new webpack.DefinePlugin({
|
||||||
'process.env': {
|
'process.env': {
|
||||||
|
EMBED: JSON.stringify(EMBED),
|
||||||
NODE_ENV: JSON.stringify(ENV),
|
NODE_ENV: JSON.stringify(ENV),
|
||||||
RPC_ADDRESS: JSON.stringify(process.env.RPC_ADDRESS),
|
RPC_ADDRESS: JSON.stringify(process.env.RPC_ADDRESS),
|
||||||
PARITY_URL: JSON.stringify(process.env.PARITY_URL),
|
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>"; };
|
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>"; };
|
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>"; };
|
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 */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase 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::{EthStore, SecretStore, import_accounts, read_geth_accounts};
|
||||||
use ethcore::ethstore::dir::RootDiskDirectory;
|
use ethcore::ethstore::dir::RootDiskDirectory;
|
||||||
use ethcore::ethstore::SecretVaultRef;
|
use ethcore::ethstore::SecretVaultRef;
|
||||||
use ethcore::account_provider::AccountProvider;
|
use ethcore::account_provider::{AccountProvider, AccountProviderSettings};
|
||||||
use helpers::{password_prompt, password_from_file};
|
use helpers::{password_prompt, password_from_file};
|
||||||
use params::SpecType;
|
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 dir = Box::new(keys_dir(n.path, n.spec)?);
|
||||||
let secret_store = Box::new(secret_store(dir, Some(n.iterations))?);
|
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))?;
|
let new_account = acc_provider.new_account(&password).map_err(|e| format!("Could not create new account: {}", e))?;
|
||||||
Ok(format!("{:?}", new_account))
|
Ok(format!("{:?}", new_account))
|
||||||
}
|
}
|
||||||
@ -100,7 +100,7 @@ fn new(n: NewAccount) -> Result<String, String> {
|
|||||||
fn list(list_cmd: ListAccounts) -> Result<String, String> {
|
fn list(list_cmd: ListAccounts) -> Result<String, String> {
|
||||||
let dir = Box::new(keys_dir(list_cmd.path, list_cmd.spec)?);
|
let dir = Box::new(keys_dir(list_cmd.path, list_cmd.spec)?);
|
||||||
let secret_store = Box::new(secret_store(dir, None)?);
|
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 accounts = acc_provider.accounts();
|
||||||
let result = accounts.into_iter()
|
let result = accounts.into_iter()
|
||||||
.map(|a| format!("{:?}", a))
|
.map(|a| format!("{:?}", a))
|
||||||
|
@ -101,6 +101,9 @@ usage! {
|
|||||||
or |c: &Config| otry!(c.account).password.clone(),
|
or |c: &Config| otry!(c.account).password.clone(),
|
||||||
flag_keys_iterations: u32 = 10240u32,
|
flag_keys_iterations: u32 = 10240u32,
|
||||||
or |c: &Config| otry!(c.account).keys_iterations.clone(),
|
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,
|
flag_force_ui: bool = false,
|
||||||
or |c: &Config| otry!(c.ui).force.clone(),
|
or |c: &Config| otry!(c.ui).force.clone(),
|
||||||
@ -347,6 +350,7 @@ struct Account {
|
|||||||
unlock: Option<Vec<String>>,
|
unlock: Option<Vec<String>>,
|
||||||
password: Option<Vec<String>>,
|
password: Option<Vec<String>>,
|
||||||
keys_iterations: Option<u32>,
|
keys_iterations: Option<u32>,
|
||||||
|
disable_hardware: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, PartialEq, RustcDecodable)]
|
#[derive(Default, Debug, PartialEq, RustcDecodable)]
|
||||||
@ -583,6 +587,7 @@ mod tests {
|
|||||||
flag_unlock: Some("0xdeadbeefcafe0000000000000000000000000000".into()),
|
flag_unlock: Some("0xdeadbeefcafe0000000000000000000000000000".into()),
|
||||||
flag_password: vec!["~/.safe/password.file".into()],
|
flag_password: vec!["~/.safe/password.file".into()],
|
||||||
flag_keys_iterations: 10240u32,
|
flag_keys_iterations: 10240u32,
|
||||||
|
flag_no_hardware_wallets: false,
|
||||||
|
|
||||||
flag_force_ui: false,
|
flag_force_ui: false,
|
||||||
flag_no_ui: false,
|
flag_no_ui: false,
|
||||||
@ -769,6 +774,7 @@ mod tests {
|
|||||||
unlock: Some(vec!["0x1".into(), "0x2".into(), "0x3".into()]),
|
unlock: Some(vec!["0x1".into(), "0x2".into(), "0x3".into()]),
|
||||||
password: Some(vec!["passwdfile path".into()]),
|
password: Some(vec!["passwdfile path".into()]),
|
||||||
keys_iterations: None,
|
keys_iterations: None,
|
||||||
|
disable_hardware: None,
|
||||||
}),
|
}),
|
||||||
ui: Some(Ui {
|
ui: Some(Ui {
|
||||||
force: None,
|
force: None,
|
||||||
|
@ -78,6 +78,7 @@ Account Options:
|
|||||||
--keys-iterations NUM Specify the number of iterations to use when
|
--keys-iterations NUM Specify the number of iterations to use when
|
||||||
deriving key from the password (bigger is more
|
deriving key from the password (bigger is more
|
||||||
secure) (default: {flag_keys_iterations}).
|
secure) (default: {flag_keys_iterations}).
|
||||||
|
--no-hardware-wallets Disables hardware wallet support. (default: {flag_no_hardware_wallets})
|
||||||
|
|
||||||
UI Options:
|
UI Options:
|
||||||
--force-ui Enable Trusted UI WebSocket endpoint,
|
--force-ui Enable Trusted UI WebSocket endpoint,
|
||||||
|
@ -461,6 +461,7 @@ impl Configuration {
|
|||||||
testnet: self.args.flag_testnet,
|
testnet: self.args.flag_testnet,
|
||||||
password_files: self.args.flag_password.clone(),
|
password_files: self.args.flag_password.clone(),
|
||||||
unlocked_accounts: to_addresses(&self.args.flag_unlock)?,
|
unlocked_accounts: to_addresses(&self.args.flag_unlock)?,
|
||||||
|
enable_hardware_wallets: !self.args.flag_no_hardware_wallets,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(cfg)
|
Ok(cfg)
|
||||||
|
@ -175,6 +175,7 @@ pub struct AccountsConfig {
|
|||||||
pub testnet: bool,
|
pub testnet: bool,
|
||||||
pub password_files: Vec<String>,
|
pub password_files: Vec<String>,
|
||||||
pub unlocked_accounts: Vec<Address>,
|
pub unlocked_accounts: Vec<Address>,
|
||||||
|
pub enable_hardware_wallets: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for AccountsConfig {
|
impl Default for AccountsConfig {
|
||||||
@ -184,6 +185,7 @@ impl Default for AccountsConfig {
|
|||||||
testnet: false,
|
testnet: false,
|
||||||
password_files: Vec::new(),
|
password_files: Vec::new(),
|
||||||
unlocked_accounts: Vec::new(),
|
unlocked_accounts: Vec::new(),
|
||||||
|
enable_hardware_wallets: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
use ethcore::ethstore::{PresaleWallet, EthStore};
|
use ethcore::ethstore::{PresaleWallet, EthStore};
|
||||||
use ethcore::ethstore::dir::RootDiskDirectory;
|
use ethcore::ethstore::dir::RootDiskDirectory;
|
||||||
use ethcore::account_provider::AccountProvider;
|
use ethcore::account_provider::{AccountProvider, AccountProviderSettings};
|
||||||
use helpers::{password_prompt, password_from_file};
|
use helpers::{password_prompt, password_from_file};
|
||||||
use params::SpecType;
|
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 dir = Box::new(RootDiskDirectory::create(cmd.path).unwrap());
|
||||||
let secret_store = Box::new(EthStore::open_with_iterations(dir, cmd.iterations).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 wallet = PresaleWallet::open(cmd.wallet_path).map_err(|_| "Unable to open presale wallet.")?;
|
||||||
let kp = wallet.decrypt(&password).map_err(|_| "Invalid password.")?;
|
let kp = wallet.decrypt(&password).map_err(|_| "Invalid password.")?;
|
||||||
let address = acc_provider.insert_account(kp.secret().clone(), &password).unwrap();
|
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::miner::{StratumOptions, Stratum};
|
||||||
use ethcore::client::{Mode, DatabaseCompactionProfile, VMType, BlockChainClient};
|
use ethcore::client::{Mode, DatabaseCompactionProfile, VMType, BlockChainClient};
|
||||||
use ethcore::service::ClientService;
|
use ethcore::service::ClientService;
|
||||||
use ethcore::account_provider::AccountProvider;
|
use ethcore::account_provider::{AccountProvider, AccountProviderSettings};
|
||||||
use ethcore::miner::{Miner, MinerService, ExternalMiner, MinerOptions};
|
use ethcore::miner::{Miner, MinerService, ExternalMiner, MinerOptions};
|
||||||
use ethcore::snapshot;
|
use ethcore::snapshot;
|
||||||
use ethcore::verification::queue::VerifierSettings;
|
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);
|
let path = dirs.keys_path(data_dir);
|
||||||
upgrade_key_location(&dirs.legacy_keys_path(cfg.testnet), &path);
|
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 dir = Box::new(RootDiskDirectory::create(&path).map_err(|e| format!("Could not open keys directory: {}", e))?);
|
||||||
let account_provider = AccountProvider::new(Box::new(
|
let account_settings = AccountProviderSettings {
|
||||||
EthStore::open_with_iterations(dir, cfg.iterations).map_err(|e| format!("Could not open keys directory: {}", e))?
|
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 {
|
for a in cfg.unlocked_accounts {
|
||||||
// Check if the account exists
|
// Check if the account exists
|
||||||
|
@ -21,6 +21,7 @@ use std::ops::Deref;
|
|||||||
use std::sync::Weak;
|
use std::sync::Weak;
|
||||||
|
|
||||||
use futures::{future, Future, BoxFuture};
|
use futures::{future, Future, BoxFuture};
|
||||||
|
use rlp::{self, Stream};
|
||||||
use util::{Address, H520, H256, U256, Uint, Bytes};
|
use util::{Address, H520, H256, U256, Uint, Bytes};
|
||||||
use util::sha3::Hashable;
|
use util::sha3::Hashable;
|
||||||
|
|
||||||
@ -129,12 +130,31 @@ impl<C: MiningBlockChainClient, M: MinerService> Dispatcher for FullDispatcher<C
|
|||||||
};
|
};
|
||||||
|
|
||||||
let hash = t.hash(network_id);
|
let hash = t.hash(network_id);
|
||||||
let signature = try_bf!(signature(accounts, address, hash, password));
|
if accounts.is_hardware_address(address) {
|
||||||
|
let mut stream = rlp::RlpStream::new();
|
||||||
signature.map(|sig| {
|
t.rlp_append_unsigned_transaction(&mut stream, network_id);
|
||||||
SignedTransaction::new(t.with_signature(sig, network_id))
|
let signature = try_bf!(
|
||||||
.expect("Transaction was signed by AccountsProvider; it never produces invalid signatures; qed")
|
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)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
let signed = try_bf!(
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
WithToken::No(signed)
|
||||||
|
} else {
|
||||||
|
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")
|
||||||
|
})
|
||||||
|
}
|
||||||
}).boxed()
|
}).boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -309,7 +329,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
|
pub fn default_gas_price<C, M>(client: &C, miner: &M) -> U256
|
||||||
where C: MiningBlockChainClient, M: MinerService
|
where C: MiningBlockChainClient, M: MinerService
|
||||||
{
|
{
|
||||||
|
@ -45,6 +45,7 @@ use v1::types::{
|
|||||||
TransactionStats, LocalTransactionStatus,
|
TransactionStats, LocalTransactionStatus,
|
||||||
BlockNumber, ConsensusCapability, VersionInfo,
|
BlockNumber, ConsensusCapability, VersionInfo,
|
||||||
OperationsInfo, DappId, ChainStatus,
|
OperationsInfo, DappId, ChainStatus,
|
||||||
|
AccountInfo, HwAccountInfo
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Parity implementation.
|
/// Parity implementation.
|
||||||
@ -111,7 +112,7 @@ impl<C, M, S: ?Sized, U> Parity for ParityClient<C, M, S, U> where
|
|||||||
{
|
{
|
||||||
type Metadata = Metadata;
|
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 dapp = dapp.0;
|
||||||
|
|
||||||
let store = take_weak!(self.accounts);
|
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()
|
.into_iter()
|
||||||
.chain(other.into_iter())
|
.chain(other.into_iter())
|
||||||
.filter(|&(ref a, _)| dapp_accounts.contains(a))
|
.filter(|&(ref a, _)| dapp_accounts.contains(a))
|
||||||
.map(|(a, v)| {
|
.map(|(a, v)| (H160::from(a), AccountInfo { name: v.name }))
|
||||||
let m = map![
|
.collect()
|
||||||
"name".to_owned() => v.name
|
)
|
||||||
];
|
}
|
||||||
(format!("0x{}", a.hex()), m)
|
|
||||||
})
|
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()
|
.collect()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use ethcore::account_provider::AccountProvider;
|
use ethcore::account_provider::{AccountProvider, AccountProviderSettings};
|
||||||
use ethstore::EthStore;
|
use ethstore::EthStore;
|
||||||
use ethstore::dir::RootDiskDirectory;
|
use ethstore::dir::RootDiskDirectory;
|
||||||
use devtools::RandomTempPath;
|
use devtools::RandomTempPath;
|
||||||
@ -36,7 +36,7 @@ fn accounts_provider() -> Arc<AccountProvider> {
|
|||||||
fn accounts_provider_with_vaults_support(temp_path: &str) -> Arc<AccountProvider> {
|
fn accounts_provider_with_vaults_support(temp_path: &str) -> Arc<AccountProvider> {
|
||||||
let root_keys_dir = RootDiskDirectory::create(temp_path).unwrap();
|
let root_keys_dir = RootDiskDirectory::create(temp_path).unwrap();
|
||||||
let secret_store = EthStore::open(Box::new(root_keys_dir)).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 {
|
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);
|
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()));
|
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]
|
#[test]
|
||||||
@ -358,6 +366,14 @@ fn rpc_parity_get_set_vault_meta() {
|
|||||||
let tester = setup_with_vaults_support(temp_path.as_str());
|
let tester = setup_with_vaults_support(temp_path.as_str());
|
||||||
|
|
||||||
assert!(tester.accounts.create_vault("vault1", "password1").is_ok());
|
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());
|
assert!(tester.accounts.set_vault_meta("vault1", "vault1_meta").is_ok());
|
||||||
|
|
||||||
let request = r#"{"jsonrpc": "2.0", "method": "parity_getVaultMeta", "params":["vault1"], "id": 1}"#;
|
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()));
|
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 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}"#;
|
let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#;
|
||||||
|
|
||||||
assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned()));
|
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 request = r#"{"jsonrpc": "2.0", "method": "parity_getVaultMeta", "params":["vault1"], "id": 1}"#;
|
||||||
let response = r#"{"jsonrpc":"2.0","result":"updated_vault1_meta","id":1}"#;
|
let response = r#"{"jsonrpc":"2.0","result":"updated_vault1_meta","id":1}"#;
|
||||||
|
|
||||||
|
@ -28,6 +28,7 @@ use v1::types::{
|
|||||||
TransactionStats, LocalTransactionStatus,
|
TransactionStats, LocalTransactionStatus,
|
||||||
BlockNumber, ConsensusCapability, VersionInfo,
|
BlockNumber, ConsensusCapability, VersionInfo,
|
||||||
OperationsInfo, DappId, ChainStatus,
|
OperationsInfo, DappId, ChainStatus,
|
||||||
|
AccountInfo, HwAccountInfo,
|
||||||
};
|
};
|
||||||
|
|
||||||
build_rpc_trait! {
|
build_rpc_trait! {
|
||||||
@ -37,7 +38,11 @@ build_rpc_trait! {
|
|||||||
|
|
||||||
/// Returns accounts information.
|
/// Returns accounts information.
|
||||||
#[rpc(name = "parity_accountsInfo")]
|
#[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.
|
/// Returns default account for dapp.
|
||||||
#[rpc(meta, name = "parity_defaultAccount")]
|
#[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
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
mod account_info;
|
||||||
mod bytes;
|
mod bytes;
|
||||||
mod block;
|
mod block;
|
||||||
mod block_number;
|
mod block_number;
|
||||||
@ -65,3 +66,4 @@ pub use self::uint::{U128, U256};
|
|||||||
pub use self::work::Work;
|
pub use self::work::Work;
|
||||||
pub use self::histogram::Histogram;
|
pub use self::histogram::Histogram;
|
||||||
pub use self::consensus_status::*;
|
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_INVALID_INPUT: c_int = 1;
|
||||||
const SNAPPY_BUFFER_TOO_SMALL: c_int = 2;
|
const SNAPPY_BUFFER_TOO_SMALL: c_int = 2;
|
||||||
|
|
||||||
#[link(name = "snappy")]
|
#[link(name = "snappy", kind = "static")]
|
||||||
extern {
|
extern {
|
||||||
fn snappy_compress(
|
fn snappy_compress(
|
||||||
input: *const c_char,
|
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 {
|
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 )};
|
let status = unsafe { snappy_validate_compressed_buffer(input.as_ptr() as *const c_char, input.len() as size_t )};
|
||||||
status == SNAPPY_OK
|
status == SNAPPY_OK
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user