Merge branch 'master' into auth-bft
This commit is contained in:
		
						commit
						c946ffebf3
					
				| @ -20,7 +20,7 @@ linux-stable: | ||||
|     - stable | ||||
|     - triggers | ||||
|   script: | ||||
|     - cargo build --release $CARGOFLAGS | ||||
|     - cargo build -j $(nproc) --release $CARGOFLAGS | ||||
|     - strip target/release/parity | ||||
|     - md5sum target/release/parity > parity.md5 | ||||
|     - sh scripts/deb-build.sh amd64 | ||||
| @ -52,7 +52,7 @@ linux-beta: | ||||
|     - stable | ||||
|     - triggers | ||||
|   script: | ||||
|     - cargo build --release $CARGOFLAGS | ||||
|     - cargo build -j $(nproc) --release $CARGOFLAGS | ||||
|     - strip target/release/parity | ||||
|   tags: | ||||
|     - rust | ||||
| @ -71,7 +71,7 @@ linux-nightly: | ||||
|     - stable | ||||
|     - triggers | ||||
|   script: | ||||
|     - cargo build --release $CARGOFLAGS | ||||
|     - cargo build -j $(nproc) --release $CARGOFLAGS | ||||
|     - strip target/release/parity | ||||
|   tags: | ||||
|     - rust | ||||
| @ -92,7 +92,7 @@ linux-centos: | ||||
|   script: | ||||
|     - export CXX="g++" | ||||
|     - export CC="gcc" | ||||
|     - cargo build --release $CARGOFLAGS | ||||
|     - cargo build -j $(nproc) --release $CARGOFLAGS | ||||
|     - strip target/release/parity | ||||
|     - md5sum target/release/parity > parity.md5 | ||||
|     - aws configure set aws_access_key_id $s3_key | ||||
| @ -119,7 +119,7 @@ linux-i686: | ||||
|   script: | ||||
|     - export HOST_CC=gcc | ||||
|     - export HOST_CXX=g++ | ||||
|     - cargo build --target i686-unknown-linux-gnu --release $CARGOFLAGS | ||||
|     - cargo build -j $(nproc) --target i686-unknown-linux-gnu --release $CARGOFLAGS | ||||
|     - strip target/i686-unknown-linux-gnu/release/parity | ||||
|     - md5sum target/i686-unknown-linux-gnu/release/parity > parity.md5 | ||||
|     - sh scripts/deb-build.sh i386 | ||||
| @ -161,7 +161,7 @@ linux-armv7: | ||||
|     - echo "[target.armv7-unknown-linux-gnueabihf]" >> .cargo/config | ||||
|     - echo "linker= \"arm-linux-gnueabihf-gcc\"" >> .cargo/config | ||||
|     - cat .cargo/config | ||||
|     - cargo build --target armv7-unknown-linux-gnueabihf --release $CARGOFLAGS | ||||
|     - cargo build -j $(nproc) --target armv7-unknown-linux-gnueabihf --release $CARGOFLAGS | ||||
|     - arm-linux-gnueabihf-strip target/armv7-unknown-linux-gnueabihf/release/parity | ||||
|     - md5sum target/armv7-unknown-linux-gnueabihf/release/parity > parity.md5 | ||||
|     - sh scripts/deb-build.sh armhf | ||||
| @ -203,7 +203,7 @@ linux-arm: | ||||
|     - echo "[target.arm-unknown-linux-gnueabihf]" >> .cargo/config | ||||
|     - echo "linker= \"arm-linux-gnueabihf-gcc\"" >> .cargo/config | ||||
|     - cat .cargo/config | ||||
|     - cargo build --target arm-unknown-linux-gnueabihf --release $CARGOFLAGS | ||||
|     - cargo build -j $(nproc) --target arm-unknown-linux-gnueabihf --release $CARGOFLAGS | ||||
|     - arm-linux-gnueabihf-strip target/arm-unknown-linux-gnueabihf/release/parity | ||||
|     - md5sum target/arm-unknown-linux-gnueabihf/release/parity > parity.md5 | ||||
|     - sh scripts/deb-build.sh armhf | ||||
| @ -245,7 +245,7 @@ linux-armv6: | ||||
|     - echo "[target.arm-unknown-linux-gnueabi]" >> .cargo/config | ||||
|     - echo "linker= \"arm-linux-gnueabi-gcc\"" >> .cargo/config | ||||
|     - cat .cargo/config | ||||
|     - cargo build --target arm-unknown-linux-gnueabi --release $CARGOFLAGS | ||||
|     - cargo build -j $(nproc) --target arm-unknown-linux-gnueabi --release $CARGOFLAGS | ||||
|     - arm-linux-gnueabi-strip target/arm-unknown-linux-gnueabi/release/parity | ||||
|     - md5sum target/arm-unknown-linux-gnueabi/release/parity > parity.md5 | ||||
|     - aws configure set aws_access_key_id $s3_key | ||||
| @ -280,7 +280,7 @@ linux-aarch64: | ||||
|     - echo "[target.aarch64-unknown-linux-gnu]" >> .cargo/config | ||||
|     - echo "linker= \"aarch64-linux-gnu-gcc\"" >> .cargo/config | ||||
|     - cat .cargo/config | ||||
|     - cargo build --target aarch64-unknown-linux-gnu --release $CARGOFLAGS | ||||
|     - cargo build -j $(nproc) --target aarch64-unknown-linux-gnu --release $CARGOFLAGS | ||||
|     - aarch64-linux-gnu-strip target/aarch64-unknown-linux-gnu/release/parity | ||||
|     - md5sum target/aarch64-unknown-linux-gnu/release/parity > parity.md5 | ||||
|     - sh scripts/deb-build.sh arm64 | ||||
| @ -312,8 +312,8 @@ darwin: | ||||
|     - stable | ||||
|     - triggers | ||||
|   script: | ||||
|     - cargo build --release -p ethstore $CARGOFLAGS | ||||
|     - cargo build --release $CARGOFLAGS | ||||
|     - cargo build -j 8 --release -p ethstore #$CARGOFLAGS | ||||
|     - cargo build -j 8 --release #$CARGOFLAGS | ||||
|     - rm -rf parity.md5 | ||||
|     - md5sum target/release/parity > parity.md5 | ||||
|     - packagesbuild -v mac/Parity.pkgproj | ||||
| @ -350,7 +350,7 @@ windows: | ||||
|     - set RUST_BACKTRACE=1 | ||||
|     - set RUSTFLAGS=%RUSTFLAGS%  | ||||
|     - rustup default stable-x86_64-pc-windows-msvc | ||||
|     - cargo build --release %CARGOFLAGS% | ||||
|     - cargo build -j 8 --release #%CARGOFLAGS% | ||||
|     - curl -sL --url "https://github.com/ethcore/win-build/raw/master/SimpleFC.dll" -o nsis\SimpleFC.dll | ||||
|     - curl -sL --url "https://github.com/ethcore/win-build/raw/master/vc_redist.x64.exe" -o nsis\vc_redist.x64.exe | ||||
|     - signtool sign /f %keyfile% /p %certpass% target\release\parity.exe | ||||
| @ -413,7 +413,7 @@ test-windows: | ||||
|     - git submodule update --init --recursive | ||||
|   script: | ||||
|     - set RUST_BACKTRACE=1 | ||||
|     - cargo test --features json-tests -p rlp -p ethash -p ethcore -p ethcore-bigint -p ethcore-dapps -p ethcore-rpc -p ethcore-signer -p ethcore-util -p ethcore-network -p ethcore-io -p ethkey -p ethstore -p ethsync -p ethcore-ipc -p ethcore-ipc-tests -p ethcore-ipc-nano -p parity %CARGOFLAGS% --verbose --release | ||||
|     - cargo -j 8 test --features json-tests -p rlp -p ethash -p ethcore -p ethcore-bigint -p ethcore-dapps -p ethcore-rpc -p ethcore-signer -p ethcore-util -p ethcore-network -p ethcore-io -p ethkey -p ethstore -p ethsync -p ethcore-ipc -p ethcore-ipc-tests -p ethcore-ipc-nano -p parity %CARGOFLAGS% --verbose --release | ||||
|   tags: | ||||
|     - rust-windows | ||||
|   allow_failure: true | ||||
|  | ||||
							
								
								
									
										159
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										159
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @ -27,7 +27,6 @@ dependencies = [ | ||||
|  "fdlimit 0.1.0", | ||||
|  "hyper 0.9.10 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "isatty 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "json-ipc-server 0.2.4 (git+https://github.com/ethcore/json-ipc-server.git)", | ||||
|  "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "num_cpus 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| @ -175,6 +174,15 @@ dependencies = [ | ||||
|  "url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "cookie" | ||||
| version = "0.3.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| dependencies = [ | ||||
|  "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "crossbeam" | ||||
| version = "0.2.9" | ||||
| @ -296,7 +304,7 @@ dependencies = [ | ||||
|  "ethstore 0.1.0", | ||||
|  "evmjit 1.4.0", | ||||
|  "heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "hyper 0.9.4 (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)", | ||||
|  "linked-hash-map 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| @ -340,9 +348,9 @@ dependencies = [ | ||||
|  "ethcore-rpc 1.5.0", | ||||
|  "ethcore-util 1.5.0", | ||||
|  "fetch 0.1.0", | ||||
|  "hyper 0.9.4 (git+https://github.com/ethcore/hyper)", | ||||
|  "jsonrpc-core 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "jsonrpc-http-server 6.1.1 (git+https://github.com/ethcore/jsonrpc-http-server.git)", | ||||
|  "hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)", | ||||
|  "jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git)", | ||||
|  "jsonrpc-http-server 6.1.1 (git+https://github.com/ethcore/jsonrpc.git)", | ||||
|  "linked-hash-map 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "mime 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| @ -503,9 +511,9 @@ dependencies = [ | ||||
|  "ethstore 0.1.0", | ||||
|  "ethsync 1.5.0", | ||||
|  "fetch 0.1.0", | ||||
|  "json-ipc-server 0.2.4 (git+https://github.com/ethcore/json-ipc-server.git)", | ||||
|  "jsonrpc-core 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "jsonrpc-http-server 6.1.1 (git+https://github.com/ethcore/jsonrpc-http-server.git)", | ||||
|  "jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git)", | ||||
|  "jsonrpc-http-server 6.1.1 (git+https://github.com/ethcore/jsonrpc.git)", | ||||
|  "jsonrpc-ipc-server 0.2.4 (git+https://github.com/ethcore/jsonrpc.git)", | ||||
|  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "rlp 0.1.0", | ||||
|  "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| @ -526,7 +534,7 @@ dependencies = [ | ||||
|  "ethcore-io 1.5.0", | ||||
|  "ethcore-rpc 1.5.0", | ||||
|  "ethcore-util 1.5.0", | ||||
|  "jsonrpc-core 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git)", | ||||
|  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "parity-ui 1.4.0", | ||||
| @ -545,8 +553,8 @@ dependencies = [ | ||||
|  "ethcore-ipc-codegen 1.4.0", | ||||
|  "ethcore-ipc-nano 1.4.0", | ||||
|  "ethcore-util 1.5.0", | ||||
|  "json-tcp-server 0.1.0 (git+https://github.com/ethcore/json-tcp-server)", | ||||
|  "jsonrpc-core 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git)", | ||||
|  "jsonrpc-tcp-server 0.1.0 (git+https://github.com/ethcore/jsonrpc.git)", | ||||
|  "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "mio 0.5.1 (git+https://github.com/ethcore/mio?branch=v0.5.x)", | ||||
| @ -686,7 +694,7 @@ name = "fetch" | ||||
| version = "0.1.0" | ||||
| dependencies = [ | ||||
|  "https-fetch 0.1.0", | ||||
|  "hyper 0.9.4 (git+https://github.com/ethcore/hyper)", | ||||
|  "hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)", | ||||
|  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| ] | ||||
| @ -748,27 +756,6 @@ dependencies = [ | ||||
|  "rustls 0.1.2 (git+https://github.com/ctz/rustls)", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "hyper" | ||||
| version = "0.9.4" | ||||
| source = "git+https://github.com/ethcore/hyper#9e346c1d4bc30cd4142dea9d8a0b117d30858ca4" | ||||
| dependencies = [ | ||||
|  "cookie 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "httparse 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "mime 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "rotor 0.6.3 (git+https://github.com/ethcore/rotor)", | ||||
|  "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "spmc 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "traitobject 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "vecio 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "hyper" | ||||
| version = "0.9.10" | ||||
| @ -789,6 +776,25 @@ dependencies = [ | ||||
|  "url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "hyper" | ||||
| version = "0.10.0-a.0" | ||||
| source = "git+https://github.com/ethcore/hyper#7d4f7fa0baddcb2b0c523f7c05855d67de94fe88" | ||||
| dependencies = [ | ||||
|  "cookie 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "httparse 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "mime 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "rotor 0.6.3 (git+https://github.com/ethcore/rotor)", | ||||
|  "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "spmc 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "vecio 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "idna" | ||||
| version = "0.1.0" | ||||
| @ -831,39 +837,10 @@ name = "itoa" | ||||
| version = "0.1.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "json-ipc-server" | ||||
| version = "0.2.4" | ||||
| source = "git+https://github.com/ethcore/json-ipc-server.git#4642cd03ec1d23db89df80d22d5a88e7364ab885" | ||||
| dependencies = [ | ||||
|  "bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "jsonrpc-core 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "mio 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "miow 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "slab 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "json-tcp-server" | ||||
| version = "0.1.0" | ||||
| source = "git+https://github.com/ethcore/json-tcp-server#c2858522274ae56042472bb5d22845a1b85e5338" | ||||
| dependencies = [ | ||||
|  "bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "jsonrpc-core 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "mio 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "slab 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "jsonrpc-core" | ||||
| version = "3.0.2" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| version = "4.0.0" | ||||
| source = "git+https://github.com/ethcore/jsonrpc.git#20c7e55b84d7fd62732f062dc3058e1b71133e4a" | ||||
| dependencies = [ | ||||
|  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "parking_lot 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| @ -875,14 +852,44 @@ dependencies = [ | ||||
| [[package]] | ||||
| name = "jsonrpc-http-server" | ||||
| version = "6.1.1" | ||||
| source = "git+https://github.com/ethcore/jsonrpc-http-server.git#cd6d4cb37d672cc3057aecd0692876f9e85f3ba5" | ||||
| source = "git+https://github.com/ethcore/jsonrpc.git#20c7e55b84d7fd62732f062dc3058e1b71133e4a" | ||||
| dependencies = [ | ||||
|  "hyper 0.9.4 (git+https://github.com/ethcore/hyper)", | ||||
|  "jsonrpc-core 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)", | ||||
|  "jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git)", | ||||
|  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "jsonrpc-ipc-server" | ||||
| version = "0.2.4" | ||||
| source = "git+https://github.com/ethcore/jsonrpc.git#20c7e55b84d7fd62732f062dc3058e1b71133e4a" | ||||
| dependencies = [ | ||||
|  "bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git)", | ||||
|  "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "mio 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "miow 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "slab 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "jsonrpc-tcp-server" | ||||
| version = "0.1.0" | ||||
| source = "git+https://github.com/ethcore/jsonrpc.git#20c7e55b84d7fd62732f062dc3058e1b71133e4a" | ||||
| dependencies = [ | ||||
|  "bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git)", | ||||
|  "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "mio 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "serde_json 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "slab 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "kernel32-sys" | ||||
| version = "0.2.2" | ||||
| @ -1263,7 +1270,7 @@ dependencies = [ | ||||
| [[package]] | ||||
| name = "parity-ui-precompiled" | ||||
| version = "1.4.0" | ||||
| source = "git+https://github.com/ethcore/js-precompiled.git#da35604af4259da3abd7a12a4c4925906c78f746" | ||||
| source = "git+https://github.com/ethcore/js-precompiled.git#d6232885df4b43c91291d31c769e48f107246d73" | ||||
| dependencies = [ | ||||
|  "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| ] | ||||
| @ -1503,11 +1510,12 @@ dependencies = [ | ||||
| [[package]] | ||||
| name = "rotor" | ||||
| version = "0.6.3" | ||||
| source = "git+https://github.com/ethcore/rotor#e63d45137b2eb66d1e085a7c6321a5db8b187576" | ||||
| source = "git+https://github.com/ethcore/rotor#c1a2dd0046c5ea2517a5b637fca8ee2e77021e82" | ||||
| dependencies = [ | ||||
|  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "mio 0.5.1 (git+https://github.com/ethcore/mio?branch=v0.5.x)", | ||||
|  "mio 0.6.1 (git+https://github.com/ethcore/mio)", | ||||
|  "quick-error 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| ] | ||||
| 
 | ||||
| @ -2008,6 +2016,7 @@ dependencies = [ | ||||
| "checksum clippy 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)" = "5b4fabf979ddf6419a313c1c0ada4a5b95cfd2049c56e8418d622d27b4b6ff32" | ||||
| "checksum clippy_lints 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)" = "ce96ec05bfe018a0d5d43da115e54850ea2217981ff0f2e462780ab9d594651a" | ||||
| "checksum cookie 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "90266f45846f14a1e986c77d1e9c2626b8c342ed806fe60241ec38cc8697b245" | ||||
| "checksum cookie 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d53b80dde876f47f03cda35303e368a79b91c70b0d65ecba5fd5280944a08591" | ||||
| "checksum crossbeam 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)" = "fb974f835e90390c5f9dfac00f05b06dc117299f5ea4e85fbc7bb443af4911cc" | ||||
| "checksum ctrlc 1.1.1 (git+https://github.com/ethcore/rust-ctrlc.git)" = "<none>" | ||||
| "checksum daemonize 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "271ec51b7e0bee92f0d04601422c73eb76ececf197026711c97ad25038a010cf" | ||||
| @ -2025,17 +2034,17 @@ dependencies = [ | ||||
| "checksum heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "abb306abb8d398e053cfb1b3e7b72c2f580be048b85745c52652954f8ad1439c" | ||||
| "checksum hpack 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d2da7d3a34cf6406d9d700111b8eafafe9a251de41ae71d8052748259343b58" | ||||
| "checksum httparse 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "46534074dbb80b070d60a5cb8ecadd8963a00a438ae1a95268850a7ef73b67ae" | ||||
| "checksum hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)" = "<none>" | ||||
| "checksum hyper 0.9.10 (registry+https://github.com/rust-lang/crates.io-index)" = "eb27e8a3e8f17ac43ffa41bbda9cf5ad3f9f13ef66fa4873409d4902310275f7" | ||||
| "checksum hyper 0.9.4 (git+https://github.com/ethcore/hyper)" = "<none>" | ||||
| "checksum idna 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1053236e00ce4f668aeca4a769a09b3bf5a682d802abd6f3cb39374f6b162c11" | ||||
| "checksum igd 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c8c12b1795b8b168f577c45fa10379b3814dcb11b7ab702406001f0d63f40484" | ||||
| "checksum isatty 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7408a548dc0e406b7912d9f84c261cc533c1866e047644a811c133c56041ac0c" | ||||
| "checksum itertools 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)" = "086e1fa5fe48840b1cfdef3a20c7e3115599f8d5c4c87ef32a794a7cdd184d76" | ||||
| "checksum itoa 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ae3088ea4baeceb0284ee9eea42f591226e6beaecf65373e41b38d95a1b8e7a1" | ||||
| "checksum json-ipc-server 0.2.4 (git+https://github.com/ethcore/json-ipc-server.git)" = "<none>" | ||||
| "checksum json-tcp-server 0.1.0 (git+https://github.com/ethcore/json-tcp-server)" = "<none>" | ||||
| "checksum jsonrpc-core 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3c5094610b07f28f3edaf3947b732dadb31dbba4941d4d0c1c7a8350208f4414" | ||||
| "checksum jsonrpc-http-server 6.1.1 (git+https://github.com/ethcore/jsonrpc-http-server.git)" = "<none>" | ||||
| "checksum jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git)" = "<none>" | ||||
| "checksum jsonrpc-http-server 6.1.1 (git+https://github.com/ethcore/jsonrpc.git)" = "<none>" | ||||
| "checksum jsonrpc-ipc-server 0.2.4 (git+https://github.com/ethcore/jsonrpc.git)" = "<none>" | ||||
| "checksum jsonrpc-tcp-server 0.1.0 (git+https://github.com/ethcore/jsonrpc.git)" = "<none>" | ||||
| "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" | ||||
| "checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" | ||||
| "checksum lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "49247ec2a285bb3dcb23cbd9c35193c025e7251bfce77c1d5da97e6362dffe7f" | ||||
|  | ||||
| @ -30,7 +30,6 @@ serde = "0.8.0" | ||||
| serde_json = "0.8.0" | ||||
| hyper = { version = "0.9", default-features = false } | ||||
| ctrlc = { git = "https://github.com/ethcore/rust-ctrlc.git" } | ||||
| json-ipc-server = { git = "https://github.com/ethcore/json-ipc-server.git" } | ||||
| fdlimit = { path = "util/fdlimit" } | ||||
| ethcore = { path = "ethcore" } | ||||
| ethcore-util = { path = "util" } | ||||
|  | ||||
| @ -12,8 +12,8 @@ build = "build.rs" | ||||
| rand = "0.3.14" | ||||
| log = "0.3" | ||||
| env_logger = "0.3" | ||||
| jsonrpc-core = "3.0" | ||||
| jsonrpc-http-server = { git = "https://github.com/ethcore/jsonrpc-http-server.git" } | ||||
| jsonrpc-core = { git = "https://github.com/ethcore/jsonrpc.git" } | ||||
| jsonrpc-http-server = { git = "https://github.com/ethcore/jsonrpc.git" } | ||||
| hyper = { default-features = false, git = "https://github.com/ethcore/hyper" } | ||||
| unicase = "1.3" | ||||
| url = "1.0" | ||||
|  | ||||
| @ -16,7 +16,6 @@ | ||||
| 
 | ||||
| //! Simple Content Handler
 | ||||
| 
 | ||||
| use std::io::Write; | ||||
| use hyper::{header, server, Decoder, Encoder, Next}; | ||||
| use hyper::net::HttpStream; | ||||
| use hyper::mime::Mime; | ||||
|  | ||||
| @ -58,7 +58,7 @@ pub fn extract_url(req: &server::Request<net::HttpStream>) -> Option<Url> { | ||||
| 				_ => None, | ||||
| 			} | ||||
| 		}, | ||||
| 		uri::RequestUri::AbsolutePath(ref path) => { | ||||
| 		uri::RequestUri::AbsolutePath { ref path, .. } => { | ||||
| 			// Attempt to prepend the Host header (mandatory in HTTP/1.1)
 | ||||
| 			let url_string = match req.headers().get::<header::Host>() { | ||||
| 				Some(ref host) => { | ||||
|  | ||||
| @ -266,7 +266,11 @@ impl Server { | ||||
| 	#[cfg(test)] | ||||
| 	/// Returns address that this server is bound to.
 | ||||
| 	pub fn addr(&self) -> &SocketAddr { | ||||
| 		self.server.as_ref().expect("server is always Some at the start; it's consumed only when object is dropped; qed").addr() | ||||
| 		self.server.as_ref() | ||||
| 			.expect("server is always Some at the start; it's consumed only when object is dropped; qed") | ||||
| 			.addrs() | ||||
| 			.first() | ||||
| 			.expect("You cannot start the server without binding to at least one address; qed") | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -14,7 +14,6 @@ | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| use std::io::Write; | ||||
| use time::{self, Duration}; | ||||
| 
 | ||||
| use hyper::header; | ||||
| @ -126,7 +125,7 @@ impl<T: Dapp> PageHandler<T> { | ||||
| impl<T: Dapp> server::Handler<HttpStream> for PageHandler<T> { | ||||
| 	fn on_request(&mut self, req: server::Request<HttpStream>) -> Next { | ||||
| 		self.file = match *req.uri() { | ||||
| 			RequestUri::AbsolutePath(ref path) => { | ||||
| 			RequestUri::AbsolutePath { ref path, .. } => { | ||||
| 				self.app.file(&self.extract_path(path)) | ||||
| 			}, | ||||
| 			RequestUri::AbsoluteUri(ref url) => { | ||||
|  | ||||
| @ -16,13 +16,14 @@ | ||||
| 
 | ||||
| use std::sync::{Arc, Mutex}; | ||||
| use hyper; | ||||
| use jsonrpc_core::IoHandler; | ||||
| use jsonrpc_http_server::{ServerHandler, PanicHandler, AccessControlAllowOrigin}; | ||||
| 
 | ||||
| use jsonrpc_core::{IoHandler, ResponseHandler, Request, Response}; | ||||
| use jsonrpc_http_server::{ServerHandler, PanicHandler, AccessControlAllowOrigin, RpcHandler}; | ||||
| use endpoint::{Endpoint, EndpointPath, Handler}; | ||||
| 
 | ||||
| pub fn rpc(handler: Arc<IoHandler>, panic_handler: Arc<Mutex<Option<Box<Fn() -> () + Send>>>>) -> Box<Endpoint> { | ||||
| 	Box::new(RpcEndpoint { | ||||
| 		handler: handler, | ||||
| 		handler: Arc::new(RpcMiddleware::new(handler)), | ||||
| 		panic_handler: panic_handler, | ||||
| 		cors_domain: None, | ||||
| 		// NOTE [ToDr] We don't need to do any hosts validation here. It's already done in router.
 | ||||
| @ -31,7 +32,7 @@ pub fn rpc(handler: Arc<IoHandler>, panic_handler: Arc<Mutex<Option<Box<Fn() -> | ||||
| } | ||||
| 
 | ||||
| struct RpcEndpoint { | ||||
| 	handler: Arc<IoHandler>, | ||||
| 	handler: Arc<RpcMiddleware>, | ||||
| 	panic_handler: Arc<Mutex<Option<Box<Fn() -> () + Send>>>>, | ||||
| 	cors_domain: Option<Vec<AccessControlAllowOrigin>>, | ||||
| 	allowed_hosts: Option<Vec<String>>, | ||||
| @ -49,3 +50,86 @@ impl Endpoint for RpcEndpoint { | ||||
| 		)) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| const MIDDLEWARE_METHOD: &'static str = "eth_accounts"; | ||||
| 
 | ||||
| struct RpcMiddleware { | ||||
| 	handler: Arc<IoHandler>, | ||||
| } | ||||
| 
 | ||||
| impl RpcMiddleware { | ||||
| 	fn new(handler: Arc<IoHandler>) -> Self { | ||||
| 		RpcMiddleware { | ||||
| 			handler: handler, | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	/// Appends additional parameter for specific calls.
 | ||||
| 	fn augment_request(&self, request: &mut Request, meta: Option<Meta>) { | ||||
| 		use jsonrpc_core::{Call, Params, to_value}; | ||||
| 
 | ||||
| 		fn augment_call(call: &mut Call, meta: Option<&Meta>) { | ||||
| 			match (call, meta) { | ||||
| 				(&mut Call::MethodCall(ref mut method_call), Some(meta)) if &method_call.method == MIDDLEWARE_METHOD => { | ||||
| 					let session = to_value(&meta.app_id); | ||||
| 
 | ||||
| 					let params = match method_call.params { | ||||
| 						Some(Params::Array(ref vec)) if vec.len() == 0 => Some(Params::Array(vec![session])), | ||||
| 						// invalid params otherwise
 | ||||
| 						_ => None, | ||||
| 					}; | ||||
| 
 | ||||
| 					method_call.params = params; | ||||
| 				}, | ||||
| 				_ => {} | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		match *request { | ||||
| 			Request::Single(ref mut call) => augment_call(call, meta.as_ref()), | ||||
| 			Request::Batch(ref mut vec) => { | ||||
| 				for mut call in vec { | ||||
| 					augment_call(call, meta.as_ref()) | ||||
| 				} | ||||
| 			}, | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug)] | ||||
| struct Meta { | ||||
| 	app_id: String, | ||||
| } | ||||
| 
 | ||||
| impl RpcHandler for RpcMiddleware { | ||||
| 	type Metadata = Meta; | ||||
| 
 | ||||
| 	fn read_metadata(&self, request: &hyper::server::Request<hyper::net::HttpStream>) -> Option<Self::Metadata> { | ||||
| 		request.headers().get::<hyper::header::Referer>() | ||||
| 			.and_then(|referer| hyper::Url::parse(referer).ok()) | ||||
| 			.and_then(|url| { | ||||
| 				url.path_segments() | ||||
| 					.and_then(|mut split| split.next()) | ||||
| 					.map(|app_id| Meta { | ||||
| 						app_id: app_id.to_owned(), | ||||
| 					}) | ||||
| 			}) | ||||
| 	} | ||||
| 
 | ||||
| 	fn handle_request<H>(&self, request_str: &str, response_handler: H, meta: Option<Self::Metadata>) where | ||||
| 		H: ResponseHandler<Option<String>, Option<String>> + 'static | ||||
| 	{ | ||||
| 		let handler = IoHandler::convert_handler(response_handler); | ||||
| 		let request = IoHandler::read_request(request_str); | ||||
| 		trace!(target: "rpc", "Request metadata: {:?}", meta); | ||||
| 
 | ||||
| 		match request { | ||||
| 			Ok(mut request) => { | ||||
| 				self.augment_request(&mut request, meta); | ||||
| 				self.handler.request_handler().handle_request(request, handler, None) | ||||
| 			}, | ||||
| 			Err(error) => handler.send(Some(Response::from(error))), | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -1 +1 @@ | ||||
| Subproject commit d509c75936ec6cbba683ee1916aa0bca436bc376 | ||||
| Subproject commit e8f4624b7f1a15c63674eecf577c7ab76c3b16be | ||||
| @ -16,9 +16,12 @@ | ||||
| 
 | ||||
| //! Account management.
 | ||||
| 
 | ||||
| use std::{fs, fmt}; | ||||
| mod stores; | ||||
| 
 | ||||
| use self::stores::{AddressBook, DappsSettingsStore}; | ||||
| 
 | ||||
| use std::fmt; | ||||
| use std::collections::HashMap; | ||||
| use std::path::PathBuf; | ||||
| use std::time::{Instant, Duration}; | ||||
| use util::{Mutex, RwLock}; | ||||
| use ethstore::{SecretStore, Error as SSError, SafeAccount, EthStore}; | ||||
| @ -91,84 +94,16 @@ impl KeyDirectory for NullDir { | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| /// Disk-backed map from Address to String. Uses JSON.
 | ||||
| struct AddressBook { | ||||
| 	path: PathBuf, | ||||
| 	cache: HashMap<Address, AccountMeta>, | ||||
| 	transient: bool, | ||||
| } | ||||
| 
 | ||||
| impl AddressBook { | ||||
| 	pub fn new(path: String) -> Self { | ||||
| 		trace!(target: "addressbook", "new({})", path); | ||||
| 		let mut path: PathBuf = path.into(); | ||||
| 		path.push("address_book.json"); | ||||
| 		trace!(target: "addressbook", "path={:?}", path); | ||||
| 		let mut r = AddressBook { | ||||
| 			path: path, | ||||
| 			cache: HashMap::new(), | ||||
| 			transient: false, | ||||
| 		}; | ||||
| 		r.revert(); | ||||
| 		r | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn transient() -> Self { | ||||
| 		let mut book = AddressBook::new(Default::default()); | ||||
| 		book.transient = true; | ||||
| 		book | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn get(&self) -> HashMap<Address, AccountMeta> { | ||||
| 		self.cache.clone() | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn set_name(&mut self, a: Address, name: String) { | ||||
| 		let mut x = self.cache.get(&a) | ||||
| 			.cloned() | ||||
| 			.unwrap_or_else(|| AccountMeta {name: Default::default(), meta: "{}".to_owned(), uuid: None}); | ||||
| 		x.name = name; | ||||
| 		self.cache.insert(a, x); | ||||
| 		self.save(); | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn set_meta(&mut self, a: Address, meta: String) { | ||||
| 		let mut x = self.cache.get(&a) | ||||
| 			.cloned() | ||||
| 			.unwrap_or_else(|| AccountMeta {name: "Anonymous".to_owned(), meta: Default::default(), uuid: None}); | ||||
| 		x.meta = meta; | ||||
| 		self.cache.insert(a, x); | ||||
| 		self.save(); | ||||
| 	} | ||||
| 
 | ||||
| 	fn revert(&mut self) { | ||||
| 		if self.transient { return; } | ||||
| 		trace!(target: "addressbook", "revert"); | ||||
| 		let _ = fs::File::open(self.path.clone()) | ||||
| 			.map_err(|e| trace!(target: "addressbook", "Couldn't open address book: {}", e)) | ||||
| 			.and_then(|f| AccountMeta::read_address_map(&f) | ||||
| 				.map_err(|e| warn!(target: "addressbook", "Couldn't read address book: {}", e)) | ||||
| 				.and_then(|m| { self.cache = m; Ok(()) }) | ||||
| 			); | ||||
| 	} | ||||
| 
 | ||||
| 	fn save(&mut self) { | ||||
| 		if self.transient { return; } | ||||
| 		trace!(target: "addressbook", "save"); | ||||
| 		let _ = fs::File::create(self.path.clone()) | ||||
| 			.map_err(|e| warn!(target: "addressbook", "Couldn't open address book for writing: {}", e)) | ||||
| 			.and_then(|mut f| AccountMeta::write_address_map(&self.cache, &mut f) | ||||
| 				.map_err(|e| warn!(target: "addressbook", "Couldn't write to address book: {}", e)) | ||||
| 			); | ||||
| 	} | ||||
| } | ||||
| /// Dapp identifier
 | ||||
| pub type DappId = String; | ||||
| 
 | ||||
| /// Account management.
 | ||||
| /// Responsible for unlocking accounts.
 | ||||
| pub struct AccountProvider { | ||||
| 	unlocked: Mutex<HashMap<Address, AccountData>>, | ||||
| 	sstore: Box<SecretStore>, | ||||
| 	address_book: Mutex<AddressBook>, | ||||
| 	address_book: RwLock<AddressBook>, | ||||
| 	dapps_settings: RwLock<DappsSettingsStore>, | ||||
| } | ||||
| 
 | ||||
| impl AccountProvider { | ||||
| @ -176,7 +111,8 @@ impl AccountProvider { | ||||
| 	pub fn new(sstore: Box<SecretStore>) -> Self { | ||||
| 		AccountProvider { | ||||
| 			unlocked: Mutex::new(HashMap::new()), | ||||
| 			address_book: Mutex::new(AddressBook::new(sstore.local_path().into())), | ||||
| 			address_book: RwLock::new(AddressBook::new(sstore.local_path().into())), | ||||
| 			dapps_settings: RwLock::new(DappsSettingsStore::new(sstore.local_path().into())), | ||||
| 			sstore: sstore, | ||||
| 		} | ||||
| 	} | ||||
| @ -185,7 +121,8 @@ impl AccountProvider { | ||||
| 	pub fn transient_provider() -> Self { | ||||
| 		AccountProvider { | ||||
| 			unlocked: Mutex::new(HashMap::new()), | ||||
| 			address_book: Mutex::new(AddressBook::transient()), | ||||
| 			address_book: RwLock::new(AddressBook::transient()), | ||||
| 			dapps_settings: RwLock::new(DappsSettingsStore::transient()), | ||||
| 			sstore: Box::new(EthStore::open(Box::new(NullDir::default())) | ||||
| 				.expect("NullDir load always succeeds; qed")) | ||||
| 		} | ||||
| @ -230,19 +167,31 @@ impl AccountProvider { | ||||
| 		Ok(accounts) | ||||
| 	} | ||||
| 
 | ||||
| 	/// Gets addresses visile for dapp.
 | ||||
| 	pub fn dapps_addresses(&self, dapp: DappId) -> Result<Vec<Address>, Error> { | ||||
| 		let accounts = self.dapps_settings.read().get(); | ||||
| 		Ok(accounts.get(&dapp).map(|settings| settings.accounts.clone()).unwrap_or_else(Vec::new)) | ||||
| 	} | ||||
| 
 | ||||
| 	/// Sets addresses visile for dapp.
 | ||||
| 	pub fn set_dapps_addresses(&self, dapp: DappId, addresses: Vec<Address>) -> Result<(), Error> { | ||||
| 		self.dapps_settings.write().set_accounts(dapp, addresses); | ||||
| 		Ok(()) | ||||
| 	} | ||||
| 
 | ||||
| 	/// Returns each address along with metadata.
 | ||||
| 	pub fn addresses_info(&self) -> Result<HashMap<Address, AccountMeta>, Error> { | ||||
| 		Ok(self.address_book.lock().get()) | ||||
| 		Ok(self.address_book.read().get()) | ||||
| 	} | ||||
| 
 | ||||
| 	/// Returns each address along with metadata.
 | ||||
| 	pub fn set_address_name(&self, account: Address, name: String) -> Result<(), Error> { | ||||
| 		Ok(self.address_book.lock().set_name(account, name)) | ||||
| 		Ok(self.address_book.write().set_name(account, name)) | ||||
| 	} | ||||
| 
 | ||||
| 	/// Returns each address along with metadata.
 | ||||
| 	pub fn set_address_meta(&self, account: Address, meta: String) -> Result<(), Error> { | ||||
| 		Ok(self.address_book.lock().set_meta(account, meta)) | ||||
| 		Ok(self.address_book.write().set_meta(account, meta)) | ||||
| 	} | ||||
| 
 | ||||
| 	/// Returns each account along with name and meta.
 | ||||
| @ -379,23 +328,9 @@ impl AccountProvider { | ||||
| 
 | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
| 	use super::{AccountProvider, AddressBook, Unlock}; | ||||
| 	use std::collections::HashMap; | ||||
| 	use super::{AccountProvider, Unlock}; | ||||
| 	use std::time::Instant; | ||||
| 	use ethjson::misc::AccountMeta; | ||||
| 	use ethstore::ethkey::{Generator, Random}; | ||||
| 	use devtools::RandomTempPath; | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn should_save_and_reload_address_book() { | ||||
| 		let temp = RandomTempPath::create_dir(); | ||||
| 		let path = temp.as_str().to_owned(); | ||||
| 		let mut b = AddressBook::new(path.clone()); | ||||
| 		b.set_name(1.into(), "One".to_owned()); | ||||
| 		b.set_meta(1.into(), "{1:1}".to_owned()); | ||||
| 		let b = AddressBook::new(path); | ||||
| 		assert_eq!(b.get(), hash_map![1.into() => AccountMeta{name: "One".to_owned(), meta: "{1:1}".to_owned(), uuid: None}]); | ||||
| 	} | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn unlock_account_temp() { | ||||
| @ -433,4 +368,17 @@ mod tests { | ||||
| 		ap.unlocked.lock().get_mut(&kp.address()).unwrap().unlock = Unlock::Timed(Instant::now()); | ||||
| 		assert!(ap.sign(kp.address(), None, Default::default()).is_err()); | ||||
| 	} | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn should_set_dapps_addresses() { | ||||
| 		// given
 | ||||
| 		let ap = AccountProvider::transient_provider(); | ||||
| 		let app = "app1".to_owned(); | ||||
| 
 | ||||
| 		// when
 | ||||
| 		ap.set_dapps_addresses(app.clone(), vec![1.into(), 2.into()]).unwrap(); | ||||
| 
 | ||||
| 		// then
 | ||||
| 		assert_eq!(ap.dapps_addresses(app.clone()).unwrap(), vec![1.into(), 2.into()]); | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										247
									
								
								ethcore/src/account_provider/stores.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										247
									
								
								ethcore/src/account_provider/stores.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,247 @@ | ||||
| // Copyright 2015, 2016 Ethcore (UK) Ltd.
 | ||||
| // This file is part of Parity.
 | ||||
| 
 | ||||
| // Parity is free software: you can redistribute it and/or modify
 | ||||
| // it under the terms of the GNU General Public License as published by
 | ||||
| // the Free Software Foundation, either version 3 of the License, or
 | ||||
| // (at your option) any later version.
 | ||||
| 
 | ||||
| // Parity is distributed in the hope that it will be useful,
 | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | ||||
| // GNU General Public License for more details.
 | ||||
| 
 | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| //! Address Book and Dapps Settings Store
 | ||||
| 
 | ||||
| use std::{fs, fmt, hash, ops}; | ||||
| use std::collections::HashMap; | ||||
| use std::path::PathBuf; | ||||
| 
 | ||||
| use ethstore::ethkey::Address; | ||||
| use ethjson::misc::{AccountMeta, DappsSettings as JsonSettings}; | ||||
| use account_provider::DappId; | ||||
| 
 | ||||
| /// Disk-backed map from Address to String. Uses JSON.
 | ||||
| pub struct AddressBook { | ||||
| 	cache: DiskMap<Address, AccountMeta>, | ||||
| } | ||||
| 
 | ||||
| impl AddressBook { | ||||
| 	/// Creates new address book at given directory.
 | ||||
| 	pub fn new(path: String) -> Self { | ||||
| 		let mut r = AddressBook { | ||||
| 			cache: DiskMap::new(path, "address_book.json".into()) | ||||
| 		}; | ||||
| 		r.cache.revert(AccountMeta::read_address_map); | ||||
| 		r | ||||
| 	} | ||||
| 
 | ||||
| 	/// Creates transient address book (no changes are saved to disk).
 | ||||
| 	pub fn transient() -> Self { | ||||
| 		AddressBook { | ||||
| 			cache: DiskMap::transient() | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	/// Get the address book.
 | ||||
| 	pub fn get(&self) -> HashMap<Address, AccountMeta> { | ||||
| 		self.cache.clone() | ||||
| 	} | ||||
| 
 | ||||
| 	fn save(&self) { | ||||
| 		self.cache.save(AccountMeta::write_address_map) | ||||
| 	} | ||||
| 
 | ||||
| 	/// Sets new name for given address.
 | ||||
| 	pub fn set_name(&mut self, a: Address, name: String) { | ||||
| 		{ | ||||
| 			let mut x = self.cache.entry(a) | ||||
| 				.or_insert_with(|| AccountMeta {name: Default::default(), meta: "{}".to_owned(), uuid: None}); | ||||
| 			x.name = name; | ||||
| 		} | ||||
| 		self.save(); | ||||
| 	} | ||||
| 
 | ||||
| 	/// Sets new meta for given address.
 | ||||
| 	pub fn set_meta(&mut self, a: Address, meta: String) { | ||||
| 		{ | ||||
| 			let mut x = self.cache.entry(a) | ||||
| 				.or_insert_with(|| AccountMeta {name: "Anonymous".to_owned(), meta: Default::default(), uuid: None}); | ||||
| 			x.meta = meta; | ||||
| 		} | ||||
| 		self.save(); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| /// Dapps user settings
 | ||||
| #[derive(Debug, Default, Clone, Eq, PartialEq)] | ||||
| pub struct DappsSettings { | ||||
| 	/// A list of visible accounts
 | ||||
| 	pub accounts: Vec<Address>, | ||||
| } | ||||
| 
 | ||||
| impl From<JsonSettings> for DappsSettings { | ||||
| 	fn from(s: JsonSettings) -> Self { | ||||
| 		DappsSettings { | ||||
| 			accounts: s.accounts.into_iter().map(Into::into).collect(), | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| impl From<DappsSettings> for JsonSettings { | ||||
| 	fn from(s: DappsSettings) -> Self { | ||||
| 		JsonSettings { | ||||
| 			accounts: s.accounts.into_iter().map(Into::into).collect(), | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| /// Disk-backed map from DappId to Settings. Uses JSON.
 | ||||
| pub struct DappsSettingsStore { | ||||
| 	cache: DiskMap<DappId, DappsSettings>, | ||||
| } | ||||
| 
 | ||||
| impl DappsSettingsStore { | ||||
| 	/// Creates new store at given directory path.
 | ||||
| 	pub fn new(path: String) -> Self { | ||||
| 		let mut r = DappsSettingsStore { | ||||
| 			cache: DiskMap::new(path, "dapps_accounts.json".into()) | ||||
| 		}; | ||||
| 		r.cache.revert(JsonSettings::read_dapps_settings); | ||||
| 		r | ||||
| 	} | ||||
| 
 | ||||
| 	/// Creates transient store (no changes are saved to disk).
 | ||||
| 	pub fn transient() -> Self { | ||||
| 		DappsSettingsStore { | ||||
| 			cache: DiskMap::transient() | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	/// Get copy of the dapps settings
 | ||||
| 	pub fn get(&self) -> HashMap<DappId, DappsSettings> { | ||||
| 		self.cache.clone() | ||||
| 	} | ||||
| 
 | ||||
| 	fn save(&self) { | ||||
| 		self.cache.save(JsonSettings::write_dapps_settings) | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn set_accounts(&mut self, id: DappId, accounts: Vec<Address>) { | ||||
| 		{ | ||||
| 			let mut settings = self.cache.entry(id).or_insert_with(DappsSettings::default); | ||||
| 			settings.accounts = accounts; | ||||
| 		} | ||||
| 		self.save(); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| /// Disk-serializable HashMap
 | ||||
| #[derive(Debug)] | ||||
| struct DiskMap<K: hash::Hash + Eq, V> { | ||||
| 	path: PathBuf, | ||||
| 	cache: HashMap<K, V>, | ||||
| 	transient: bool, | ||||
| } | ||||
| 
 | ||||
| impl<K: hash::Hash + Eq, V> ops::Deref for DiskMap<K, V> { | ||||
| 	type Target = HashMap<K, V>; | ||||
| 	fn deref(&self) -> &Self::Target { | ||||
| 		&self.cache | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| impl<K: hash::Hash + Eq, V> ops::DerefMut for DiskMap<K, V> { | ||||
| 	fn deref_mut(&mut self) -> &mut Self::Target { | ||||
| 		&mut self.cache | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| impl<K: hash::Hash + Eq, V> DiskMap<K, V> { | ||||
| 	pub fn new(path: String, file_name: String) -> Self { | ||||
| 		trace!(target: "diskmap", "new({})", path); | ||||
| 		let mut path: PathBuf = path.into(); | ||||
| 		path.push(file_name); | ||||
| 		trace!(target: "diskmap", "path={:?}", path); | ||||
| 		DiskMap { | ||||
| 			path: path, | ||||
| 			cache: HashMap::new(), | ||||
| 			transient: false, | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn transient() -> Self { | ||||
| 		let mut map = DiskMap::new(Default::default(), "diskmap.json".into()); | ||||
| 		map.transient = true; | ||||
| 		map | ||||
| 	} | ||||
| 
 | ||||
| 	fn revert<F, E>(&mut self, read: F) where | ||||
| 		F: Fn(fs::File) -> Result<HashMap<K, V>, E>, | ||||
| 		E: fmt::Display, | ||||
| 	{ | ||||
| 		if self.transient { return; } | ||||
| 		trace!(target: "diskmap", "revert {:?}", self.path); | ||||
| 		let _ = fs::File::open(self.path.clone()) | ||||
| 			.map_err(|e| trace!(target: "diskmap", "Couldn't open disk map: {}", e)) | ||||
| 			.and_then(|f| read(f).map_err(|e| warn!(target: "diskmap", "Couldn't read disk map: {}", e))) | ||||
| 			.and_then(|m| { | ||||
| 				self.cache = m; | ||||
| 				Ok(()) | ||||
| 			}); | ||||
| 	} | ||||
| 
 | ||||
| 	fn save<F, E>(&self, write: F) where | ||||
| 		F: Fn(&HashMap<K, V>, &mut fs::File) -> Result<(), E>, | ||||
| 		E: fmt::Display, | ||||
| 	{ | ||||
| 		if self.transient { return; } | ||||
| 		trace!(target: "diskmap", "save {:?}", self.path); | ||||
| 		let _ = fs::File::create(self.path.clone()) | ||||
| 			.map_err(|e| warn!(target: "diskmap", "Couldn't open disk map for writing: {}", e)) | ||||
| 			.and_then(|mut f| { | ||||
| 				write(&self.cache, &mut f).map_err(|e| warn!(target: "diskmap", "Couldn't write to disk map: {}", e)) | ||||
| 			}); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
| 	use super::{AddressBook, DappsSettingsStore, DappsSettings}; | ||||
| 	use std::collections::HashMap; | ||||
| 	use ethjson::misc::AccountMeta; | ||||
| 	use devtools::RandomTempPath; | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn should_save_and_reload_address_book() { | ||||
| 		let temp = RandomTempPath::create_dir(); | ||||
| 		let path = temp.as_str().to_owned(); | ||||
| 		let mut b = AddressBook::new(path.clone()); | ||||
| 		b.set_name(1.into(), "One".to_owned()); | ||||
| 		b.set_meta(1.into(), "{1:1}".to_owned()); | ||||
| 		let b = AddressBook::new(path); | ||||
| 		assert_eq!(b.get(), hash_map![1.into() => AccountMeta{name: "One".to_owned(), meta: "{1:1}".to_owned(), uuid: None}]); | ||||
| 	} | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn should_save_and_reload_dapps_settings() { | ||||
| 		// given
 | ||||
| 		let temp = RandomTempPath::create_dir(); | ||||
| 		let path = temp.as_str().to_owned(); | ||||
| 		let mut b = DappsSettingsStore::new(path.clone()); | ||||
| 
 | ||||
| 		// when
 | ||||
| 		b.set_accounts("dappOne".into(), vec![1.into(), 2.into()]); | ||||
| 
 | ||||
| 		// then
 | ||||
| 		let b = DappsSettingsStore::new(path); | ||||
| 		assert_eq!(b.get(), hash_map![ | ||||
| 			"dappOne".into() => DappsSettings { | ||||
| 				accounts: vec![1.into(), 2.into()], | ||||
| 			} | ||||
| 		]); | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										4
									
								
								js/assets/images/certifications/unknown.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								js/assets/images/certifications/unknown.svg
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,4 @@ | ||||
| <svg width="100" height="100" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"> | ||||
| 	<circle fill="#4A90E2" cx="50" cy="50" r="50"/> | ||||
| 	<path d="M20 45 L10 55 L35 85 L90 35 L80 25 L36 65 z" fill="#FFF"/> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 213 B | 
							
								
								
									
										233
									
								
								js/package.json
									
									
									
									
									
								
							
							
						
						
									
										233
									
								
								js/package.json
									
									
									
									
									
								
							| @ -1,6 +1,6 @@ | ||||
| { | ||||
|   "name": "parity.js", | ||||
|   "version": "0.2.79", | ||||
|   "version": "0.2.90", | ||||
|   "main": "release/index.js", | ||||
|   "jsnext:main": "src/index.js", | ||||
|   "author": "Parity Team <admin@parity.io>", | ||||
| @ -47,132 +47,127 @@ | ||||
|     "prepush": "npm run lint:cached" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "babel-cli": "~6.18.0", | ||||
|     "babel-core": "~6.18.2", | ||||
|     "babel-eslint": "~7.1.0", | ||||
|     "babel-loader": "~6.2.3", | ||||
|     "babel-plugin-lodash": "~3.2.2", | ||||
|     "babel-plugin-transform-class-properties": "~6.19.0", | ||||
|     "babel-plugin-transform-decorators-legacy": "~1.3.4", | ||||
|     "babel-plugin-transform-react-remove-prop-types": "~0.2.9", | ||||
|     "babel-plugin-transform-runtime": "~6.15.0", | ||||
|     "babel-polyfill": "~6.16.0", | ||||
|     "babel-preset-es2015": "~6.18.0", | ||||
|     "babel-preset-es2015-rollup": "~1.2.0", | ||||
|     "babel-preset-es2016": "~6.16.0", | ||||
|     "babel-preset-es2017": "~6.16.0", | ||||
|     "babel-preset-react": "~6.16.0", | ||||
|     "babel-preset-stage-0": "~6.16.0", | ||||
|     "babel-cli": "6.18.0", | ||||
|     "babel-core": "6.18.2", | ||||
|     "babel-eslint": "7.1.1", | ||||
|     "babel-loader": "6.2.8", | ||||
|     "babel-plugin-lodash": "3.2.10", | ||||
|     "babel-plugin-transform-class-properties": "6.19.0", | ||||
|     "babel-plugin-transform-decorators-legacy": "1.3.4", | ||||
|     "babel-plugin-transform-react-remove-prop-types": "0.2.11", | ||||
|     "babel-plugin-transform-runtime": "6.15.0", | ||||
|     "babel-polyfill": "6.16.0", | ||||
|     "babel-preset-es2015": "6.18.0", | ||||
|     "babel-preset-es2015-rollup": "1.2.0", | ||||
|     "babel-preset-es2016": "6.16.0", | ||||
|     "babel-preset-es2017": "6.16.0", | ||||
|     "babel-preset-react": "6.16.0", | ||||
|     "babel-preset-stage-0": "6.16.0", | ||||
|     "babel-register": "6.18.0", | ||||
|     "babel-runtime": "~6.18.0", | ||||
|     "chai": "~3.5.0", | ||||
|     "chai-enzyme": "0.4.2", | ||||
|     "cheerio": "0.20.0", | ||||
|     "copy-webpack-plugin": "~4.0.0", | ||||
|     "core-js": "~2.4.1", | ||||
|     "coveralls": "~2.11.11", | ||||
|     "css-loader": "~0.26.0", | ||||
|     "enzyme": "2.3.0", | ||||
|     "eslint": "~3.10.2", | ||||
|     "eslint-config-semistandard": "~7.0.0", | ||||
|     "eslint-config-standard": "~6.2.1", | ||||
|     "eslint-config-standard-react": "~4.2.0", | ||||
|     "eslint-plugin-promise": "~3.4.0", | ||||
|     "eslint-plugin-react": "~6.7.1", | ||||
|     "eslint-plugin-standard": "~2.0.0", | ||||
|     "express": "~4.14.0", | ||||
|     "babel-runtime": "6.18.0", | ||||
|     "chai": "3.5.0", | ||||
|     "chai-enzyme": "0.6.1", | ||||
|     "copy-webpack-plugin": "4.0.1", | ||||
|     "core-js": "2.4.1", | ||||
|     "coveralls": "2.11.15", | ||||
|     "css-loader": "0.26.1", | ||||
|     "ejs-loader": "0.3.0", | ||||
|     "enzyme": "2.6.0", | ||||
|     "eslint": "3.11.1", | ||||
|     "eslint-config-semistandard": "7.0.0", | ||||
|     "eslint-config-standard": "6.2.1", | ||||
|     "eslint-config-standard-react": "4.2.0", | ||||
|     "eslint-plugin-promise": "3.4.0", | ||||
|     "eslint-plugin-react": "6.7.1", | ||||
|     "eslint-plugin-standard": "2.0.1", | ||||
|     "express": "4.14.0", | ||||
|     "extract-loader": "0.1.0", | ||||
|     "extract-text-webpack-plugin": "~2.0.0-beta.4", | ||||
|     "file-loader": "~0.9.0", | ||||
|     "fs-extra": "~0.30.0", | ||||
|     "happypack": "~3.0.0", | ||||
|     "history": "~2.0.0", | ||||
|     "html-loader": "~0.4.4", | ||||
|     "html-webpack-plugin": "~2.24.1", | ||||
|     "http-proxy-middleware": "~0.17.2", | ||||
|     "husky": "~0.11.9", | ||||
|     "ignore-styles": "2.0.0", | ||||
|     "image-webpack-loader": "~3.0.0", | ||||
|     "istanbul": "~1.0.0-alpha.2", | ||||
|     "jsdom": "9.2.1", | ||||
|     "json-loader": "~0.5.4", | ||||
|     "mocha": "~3.0.0-1", | ||||
|     "extract-text-webpack-plugin": "2.0.0-beta.4", | ||||
|     "file-loader": "0.9.0", | ||||
|     "happypack": "3.0.0", | ||||
|     "html-loader": "0.4.4", | ||||
|     "html-webpack-plugin": "2.24.1", | ||||
|     "http-proxy-middleware": "0.17.2", | ||||
|     "husky": "0.11.9", | ||||
|     "ignore-styles": "5.0.1", | ||||
|     "image-webpack-loader": "3.0.0", | ||||
|     "istanbul": "1.0.0-alpha.2", | ||||
|     "jsdom": "9.8.3", | ||||
|     "json-loader": "0.5.4", | ||||
|     "mocha": "3.2.0", | ||||
|     "mock-local-storage": "1.0.2", | ||||
|     "mock-socket": "~3.0.1", | ||||
|     "nock": "~8.0.0", | ||||
|     "mock-socket": "6.0.3", | ||||
|     "nock": "9.0.2", | ||||
|     "postcss-import": "8.1.0", | ||||
|     "postcss-loader": "~1.1.1", | ||||
|     "postcss-nested": "~1.0.0", | ||||
|     "postcss-simple-vars": "~3.0.0", | ||||
|     "progress": "~1.1.8", | ||||
|     "raw-loader": "~0.5.1", | ||||
|     "react-addons-perf": "~15.3.2", | ||||
|     "react-addons-test-utils": "~15.3.2", | ||||
|     "react-dom": "~15.3.2", | ||||
|     "react-hot-loader": "~3.0.0-beta.6", | ||||
|     "rucksack-css": "~0.8.6", | ||||
|     "sinon": "~1.17.4", | ||||
|     "sinon-as-promised": "~4.0.2", | ||||
|     "sinon-chai": "~2.8.0", | ||||
|     "style-loader": "~0.13.0", | ||||
|     "url-loader": "~0.5.7", | ||||
|     "webpack": "~2.1.0-beta.27", | ||||
|     "webpack-dev-middleware": "~1.8.4", | ||||
|     "postcss-loader": "1.1.1", | ||||
|     "postcss-nested": "1.0.0", | ||||
|     "postcss-simple-vars": "3.0.0", | ||||
|     "progress": "1.1.8", | ||||
|     "raw-loader": "0.5.1", | ||||
|     "react-addons-perf": "15.4.1", | ||||
|     "react-addons-test-utils": "15.4.1", | ||||
|     "react-hot-loader": "3.0.0-beta.6", | ||||
|     "rucksack-css": "0.9.1", | ||||
|     "sinon": "1.17.6", | ||||
|     "sinon-as-promised": "4.0.2", | ||||
|     "sinon-chai": "2.8.0", | ||||
|     "style-loader": "0.13.1", | ||||
|     "url-loader": "0.5.7", | ||||
|     "webpack": "2.1.0-beta.27", | ||||
|     "webpack-dev-middleware": "1.8.4", | ||||
|     "webpack-error-notification": "0.1.6", | ||||
|     "webpack-hot-middleware": "~2.13.2", | ||||
|     "websocket": "~1.0.23" | ||||
|     "webpack-hot-middleware": "2.13.2", | ||||
|     "websocket": "1.0.23" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "bignumber.js": "~2.3.0", | ||||
|     "bignumber.js": "3.0.1", | ||||
|     "blockies": "0.0.2", | ||||
|     "brace": "~0.9.0", | ||||
|     "bytes": "~2.4.0", | ||||
|     "chart.js": "~2.3.0", | ||||
|     "es6-error": "~4.0.0", | ||||
|     "es6-promise": "~3.2.1", | ||||
|     "ethereumjs-tx": "~1.1.2", | ||||
|     "eventemitter3": "~2.0.2", | ||||
|     "file-saver": "~1.3.3", | ||||
|     "format-json": "~1.0.3", | ||||
|     "format-number": "~2.0.1", | ||||
|     "geopattern": "~1.2.3", | ||||
|     "isomorphic-fetch": "~2.2.1", | ||||
|     "js-sha3": "~0.5.2", | ||||
|     "lodash": "~4.11.1", | ||||
|     "marked": "~0.3.6", | ||||
|     "material-ui": "0.16.1", | ||||
|     "material-ui-chip-input": "~0.8.0", | ||||
|     "mobx": "~2.6.1", | ||||
|     "mobx-react": "~3.5.8", | ||||
|     "mobx-react-devtools": "~4.2.9", | ||||
|     "moment": "~2.14.1", | ||||
|     "phoneformat.js": "~1.0.3", | ||||
|     "qs": "~6.3.0", | ||||
|     "react": "~15.3.2", | ||||
|     "react-ace": "~4.0.0", | ||||
|     "react-addons-css-transition-group": "~15.3.2", | ||||
|     "react-chartjs-2": "~1.5.0", | ||||
|     "react-copy-to-clipboard": "~4.2.3", | ||||
|     "react-dom": "~15.3.2", | ||||
|     "react-dropzone": "~3.7.3", | ||||
|     "react-redux": "~4.4.5", | ||||
|     "react-router": "~2.6.1", | ||||
|     "react-router-redux": "~4.0.5", | ||||
|     "react-tap-event-plugin": "~1.0.0", | ||||
|     "react-tooltip": "~2.0.3", | ||||
|     "recharts": "~0.15.2", | ||||
|     "redux": "~3.5.2", | ||||
|     "redux-actions": "~0.10.1", | ||||
|     "redux-thunk": "~2.1.0", | ||||
|     "rlp": "~2.0.0", | ||||
|     "scryptsy": "~2.0.0", | ||||
|     "brace": "0.9.0", | ||||
|     "bytes": "2.4.0", | ||||
|     "es6-error": "4.0.0", | ||||
|     "es6-promise": "4.0.5", | ||||
|     "ethereumjs-tx": "1.1.4", | ||||
|     "eventemitter3": "2.0.2", | ||||
|     "file-saver": "1.3.3", | ||||
|     "format-json": "1.0.3", | ||||
|     "format-number": "2.0.1", | ||||
|     "geopattern": "1.2.3", | ||||
|     "isomorphic-fetch": "2.2.1", | ||||
|     "js-sha3": "0.5.5", | ||||
|     "lodash": "4.17.2", | ||||
|     "marked": "0.3.6", | ||||
|     "material-ui": "0.16.4", | ||||
|     "material-ui-chip-input": "0.11.1", | ||||
|     "mobx": "2.6.4", | ||||
|     "mobx-react": "4.0.3", | ||||
|     "mobx-react-devtools": "4.2.10", | ||||
|     "moment": "2.17.0", | ||||
|     "phoneformat.js": "1.0.3", | ||||
|     "qs": "6.3.0", | ||||
|     "react": "15.4.1", | ||||
|     "react-ace": "4.1.0", | ||||
|     "react-addons-css-transition-group": "15.4.1", | ||||
|     "react-copy-to-clipboard": "4.2.3", | ||||
|     "react-dom": "15.4.1", | ||||
|     "react-dropzone": "3.7.3", | ||||
|     "react-redux": "4.4.6", | ||||
|     "react-router": "3.0.0", | ||||
|     "react-router-redux": "4.0.7", | ||||
|     "react-tap-event-plugin": "2.0.1", | ||||
|     "react-tooltip": "3.2.2", | ||||
|     "recharts": "0.15.2", | ||||
|     "redux": "3.6.0", | ||||
|     "redux-actions": "1.1.0", | ||||
|     "redux-thunk": "2.1.0", | ||||
|     "rlp": "2.0.0", | ||||
|     "scryptsy": "2.0.0", | ||||
|     "solc": "ngotchac/solc-js", | ||||
|     "store": "~1.3.20", | ||||
|     "utf8": "~2.1.1", | ||||
|     "valid-url": "~1.0.9", | ||||
|     "validator": "~5.7.0", | ||||
|     "web3": "~0.17.0-beta", | ||||
|     "whatwg-fetch": "~1.0.0", | ||||
|     "worker-loader": "~0.7.1" | ||||
|     "store": "1.3.20", | ||||
|     "utf8": "2.1.2", | ||||
|     "valid-url": "1.0.9", | ||||
|     "validator": "6.2.0", | ||||
|     "web3": "0.17.0-beta", | ||||
|     "whatwg-fetch": "2.0.1", | ||||
|     "worker-loader": "0.7.1" | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -27,9 +27,5 @@ export function sliceData (_data) { | ||||
|     data = padAddress(''); | ||||
|   } | ||||
| 
 | ||||
|   if (data.length % 64) { | ||||
|     throw new Error(`Invalid data length (not mod 64) passed to sliceData, ${data}, % 64 == ${data.length % 64}`); | ||||
|   } | ||||
| 
 | ||||
|   return data.match(/.{1,64}/g); | ||||
| } | ||||
|  | ||||
| @ -21,10 +21,6 @@ describe('abi/util/slice', () => { | ||||
|     const slice1 = '131a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b'; | ||||
|     const slice2 = '2124768576358735263578356373526387638357635873563586353756358763'; | ||||
| 
 | ||||
|     it('throws an error on mod 64 != 0', () => { | ||||
|       expect(() => sliceData('123')).to.throw(/sliceData/); | ||||
|     }); | ||||
| 
 | ||||
|     it('returns an empty array when length === 0', () => { | ||||
|       expect(sliceData('')).to.deep.equal([]); | ||||
|     }); | ||||
|  | ||||
| @ -240,9 +240,29 @@ export default class Contract { | ||||
|       return this.unsubscribe(subscriptionId); | ||||
|     }; | ||||
| 
 | ||||
|     event.getAllLogs = (options = {}) => { | ||||
|       return this.getAllLogs(event); | ||||
|     }; | ||||
| 
 | ||||
|     return event; | ||||
|   } | ||||
| 
 | ||||
|   getAllLogs (event, _options) { | ||||
|     // Options as first parameter
 | ||||
|     if (!_options && event && event.topics) { | ||||
|       return this.getAllLogs(null, event); | ||||
|     } | ||||
| 
 | ||||
|     const options = this._getFilterOptions(event, _options); | ||||
|     return this._api.eth | ||||
|       .getLogs({ | ||||
|         fromBlock: 0, | ||||
|         toBlock: 'latest', | ||||
|         ...options | ||||
|       }) | ||||
|       .then((logs) => this.parseEventLogs(logs)); | ||||
|   } | ||||
| 
 | ||||
|   _findEvent (eventName = null) { | ||||
|     const event = eventName | ||||
|       ? this._events.find((evt) => evt.name === eventName) | ||||
| @ -256,7 +276,7 @@ export default class Contract { | ||||
|     return event; | ||||
|   } | ||||
| 
 | ||||
|   _createEthFilter (event = null, _options) { | ||||
|   _getFilterOptions (event = null, _options = {}) { | ||||
|     const optionTopics = _options.topics || []; | ||||
|     const signature = event && event.signature || null; | ||||
| 
 | ||||
| @ -271,6 +291,11 @@ export default class Contract { | ||||
|       topics | ||||
|     }); | ||||
| 
 | ||||
|     return options; | ||||
|   } | ||||
| 
 | ||||
|   _createEthFilter (event = null, _options) { | ||||
|     const options = this._getFilterOptions(event, _options); | ||||
|     return this._api.eth.newFilter(options); | ||||
|   } | ||||
| 
 | ||||
|  | ||||
| @ -144,7 +144,8 @@ export function outSignerRequest (request) { | ||||
|           break; | ||||
| 
 | ||||
|         case 'payload': | ||||
|           request[key].transaction = outTransaction(request[key].transaction); | ||||
|           request[key].signTransaction = outTransaction(request[key].signTransaction); | ||||
|           request[key].sendTransaction = outTransaction(request[key].sendTransaction); | ||||
|           break; | ||||
|       } | ||||
|     }); | ||||
|  | ||||
| @ -146,7 +146,8 @@ export default class Eth { | ||||
| 
 | ||||
|   getLogs (options) { | ||||
|     return this._transport | ||||
|       .execute('eth_getLogs', inFilter(options)); | ||||
|       .execute('eth_getLogs', inFilter(options)) | ||||
|       .then((logs) => logs.map(outLog)); | ||||
|   } | ||||
| 
 | ||||
|   getLogsEx (options) { | ||||
|  | ||||
| @ -32,6 +32,10 @@ export function hex2Ascii (_hex) { | ||||
|   return str; | ||||
| } | ||||
| 
 | ||||
| export function bytesToAscii (bytes) { | ||||
|   return bytes.map((b) => String.fromCharCode(b % 512)).join(''); | ||||
| } | ||||
| 
 | ||||
| export function asciiToHex (string) { | ||||
|   return '0x' + string.split('').map((s) => s.charCodeAt(0).toString(16)).join(''); | ||||
| } | ||||
|  | ||||
							
								
								
									
										1
									
								
								js/src/contracts/abi/badgereg.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								js/src/contracts/abi/badgereg.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | ||||
| [{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_addr","type":"address"},{"name":"_name","type":"bytes32"}],"name":"register","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"fromName","outputs":[{"name":"id","type":"uint256"},{"name":"addr","type":"address"},{"name":"owner","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"badgeCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_fee","type":"uint256"}],"name":"setFee","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_id","type":"uint256"},{"name":"_key","type":"bytes32"}],"name":"meta","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"drain","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_id","type":"uint256"}],"name":"unregister","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_addr","type":"address"}],"name":"fromAddress","outputs":[{"name":"id","type":"uint256"},{"name":"name","type":"bytes32"},{"name":"owner","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_id","type":"uint256"}],"name":"badge","outputs":[{"name":"addr","type":"address"},{"name":"name","type":"bytes32"},{"name":"owner","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_id","type":"uint256"},{"name":"_key","type":"bytes32"},{"name":"_value","type":"bytes32"}],"name":"setMeta","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_addr","type":"address"},{"name":"_name","type":"bytes32"},{"name":"_owner","type":"address"}],"name":"registerAs","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"fee","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"bytes32"},{"indexed":true,"name":"id","type":"uint256"},{"indexed":false,"name":"addr","type":"address"}],"name":"Registered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"bytes32"},{"indexed":true,"name":"id","type":"uint256"}],"name":"Unregistered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"id","type":"uint256"},{"indexed":true,"name":"key","type":"bytes32"},{"indexed":false,"name":"value","type":"bytes32"}],"name":"MetaChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"old","type":"address"},{"indexed":true,"name":"current","type":"address"}],"name":"NewOwner","type":"event"}] | ||||
							
								
								
									
										1
									
								
								js/src/contracts/abi/certifier.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								js/src/contracts/abi/certifier.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | ||||
| [{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"getAddress","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"getUint","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"}],"name":"certified","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"get","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"}],"name":"Confirmed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"}],"name":"Revoked","type":"event"}] | ||||
| @ -14,6 +14,7 @@ | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| import badgereg from './badgereg.json'; | ||||
| import basiccoin from './basiccoin.json'; | ||||
| import basiccoinmanager from './basiccoinmanager.json'; | ||||
| import dappreg from './dappreg.json'; | ||||
| @ -28,6 +29,7 @@ import tokenreg from './tokenreg.json'; | ||||
| import wallet from './wallet.json'; | ||||
| 
 | ||||
| export { | ||||
|   badgereg, | ||||
|   basiccoin, | ||||
|   basiccoinmanager, | ||||
|   dappreg, | ||||
|  | ||||
							
								
								
									
										66
									
								
								js/src/contracts/badgereg.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								js/src/contracts/badgereg.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,66 @@ | ||||
| // Copyright 2015, 2016 Ethcore (UK) Ltd.
 | ||||
| // This file is part of Parity.
 | ||||
| 
 | ||||
| // Parity is free software: you can redistribute it and/or modify
 | ||||
| // it under the terms of the GNU General Public License as published by
 | ||||
| // the Free Software Foundation, either version 3 of the License, or
 | ||||
| // (at your option) any later version.
 | ||||
| 
 | ||||
| // Parity is distributed in the hope that it will be useful,
 | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | ||||
| // GNU General Public License for more details.
 | ||||
| 
 | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| import { bytesToHex, hex2Ascii } from '~/api/util/format'; | ||||
| 
 | ||||
| import ABI from './abi/certifier.json'; | ||||
| 
 | ||||
| const ZERO = '0x0000000000000000000000000000000000000000000000000000000000000000'; | ||||
| 
 | ||||
| export default class BadgeReg { | ||||
|   constructor (api, registry) { | ||||
|     this._api = api; | ||||
|     this._registry = registry; | ||||
| 
 | ||||
|     registry.getContract('badgereg'); | ||||
|     this.certifiers = {}; // by name
 | ||||
|     this.contracts = {}; // by name
 | ||||
|   } | ||||
| 
 | ||||
|   fetchCertifier (name) { | ||||
|     if (this.certifiers[name]) { | ||||
|       return Promise.resolve(this.certifiers[name]); | ||||
|     } | ||||
|     return this._registry.getContract('badgereg') | ||||
|       .then((badgeReg) => { | ||||
|         return badgeReg.instance.fromName.call({}, [name]) | ||||
|         .then(([ id, address ]) => { | ||||
|           return Promise.all([ | ||||
|             badgeReg.instance.meta.call({}, [id, 'TITLE']), | ||||
|             badgeReg.instance.meta.call({}, [id, 'IMG']) | ||||
|           ]) | ||||
|             .then(([ title, img ]) => { | ||||
|               title = bytesToHex(title); | ||||
|               title = title === ZERO ? null : hex2Ascii(title); | ||||
|               if (bytesToHex(img) === ZERO) img = null; | ||||
| 
 | ||||
|               const data = { address, name, title, icon: img }; | ||||
|               this.certifiers[name] = data; | ||||
|               return data; | ||||
|             }); | ||||
|         }); | ||||
|       }); | ||||
|   } | ||||
| 
 | ||||
|   checkIfCertified (certifier, address) { | ||||
|     if (!this.contracts[certifier]) { | ||||
|       this.contracts[certifier] = this._api.newContract(ABI, certifier); | ||||
|     } | ||||
|     const contract = this.contracts[certifier]; | ||||
| 
 | ||||
|     return contract.instance.certified.call({}, [address]); | ||||
|   } | ||||
| } | ||||
| @ -20,6 +20,7 @@ import SignatureReg from './signaturereg'; | ||||
| import TokenReg from './tokenreg'; | ||||
| import GithubHint from './githubhint'; | ||||
| import * as smsVerification from './sms-verification'; | ||||
| import BadgeReg from './badgereg'; | ||||
| 
 | ||||
| let instance = null; | ||||
| 
 | ||||
| @ -33,6 +34,7 @@ export default class Contracts { | ||||
|     this._signaturereg = new SignatureReg(api, this._registry); | ||||
|     this._tokenreg = new TokenReg(api, this._registry); | ||||
|     this._githubhint = new GithubHint(api, this._registry); | ||||
|     this.badgeReg = new BadgeReg(api, this._registry); | ||||
|   } | ||||
| 
 | ||||
|   get registry () { | ||||
|  | ||||
| @ -16,8 +16,7 @@ | ||||
| 
 | ||||
| import ReactDOM from 'react-dom'; | ||||
| import React from 'react'; | ||||
| import { createHashHistory } from 'history'; | ||||
| import { Redirect, Router, Route, useRouterHistory } from 'react-router'; | ||||
| import { Redirect, Router, Route, hashHistory } from 'react-router'; | ||||
| 
 | ||||
| import injectTapEventPlugin from 'react-tap-event-plugin'; | ||||
| injectTapEventPlugin(); | ||||
| @ -27,14 +26,12 @@ import Application from './basiccoin/Application'; | ||||
| import Overview from './basiccoin/Overview'; | ||||
| import Transfer from './basiccoin/Transfer'; | ||||
| 
 | ||||
| const routerHistory = useRouterHistory(createHashHistory)({}); | ||||
| 
 | ||||
| import '../../assets/fonts/Roboto/font.css'; | ||||
| import '../../assets/fonts/RobotoMono/font.css'; | ||||
| import './style.css'; | ||||
| 
 | ||||
| ReactDOM.render( | ||||
|   <Router history={ routerHistory }> | ||||
|   <Router history={ hashHistory }> | ||||
|     <Redirect from='/' to='/overview' /> | ||||
|     <Route path='/' component={ Application }> | ||||
|       <Route path='deploy' component={ Deploy } /> | ||||
|  | ||||
| @ -17,7 +17,7 @@ | ||||
| import BigNumber from 'bignumber.js'; | ||||
| import React, { Component, PropTypes } from 'react'; | ||||
| 
 | ||||
| import { eip20 } from '../../../../contracts/abi'; | ||||
| import { eip20 } from '~/contracts/abi'; | ||||
| 
 | ||||
| import { api } from '../../parity'; | ||||
| import { loadBalances } from '../../services'; | ||||
|  | ||||
| @ -16,7 +16,7 @@ | ||||
| 
 | ||||
| import BigNumber from 'bignumber.js'; | ||||
| 
 | ||||
| import * as abis from '../../contracts/abi'; | ||||
| import * as abis from '~/contracts/abi'; | ||||
| import { api } from './parity'; | ||||
| 
 | ||||
| let managerInstance; | ||||
|  | ||||
| @ -17,8 +17,8 @@ | ||||
| import BigNumber from 'bignumber.js'; | ||||
| import { action, computed, observable, transaction } from 'mobx'; | ||||
| 
 | ||||
| import * as abis from '../../contracts/abi'; | ||||
| import builtins from '../../views/Dapps/builtin.json'; | ||||
| import * as abis from '~/contracts/abi'; | ||||
| import builtins from '~/views/Dapps/builtin.json'; | ||||
| 
 | ||||
| import { api } from './parity'; | ||||
| 
 | ||||
|  | ||||
| @ -14,7 +14,7 @@ | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| import * as abis from '../../contracts/abi'; | ||||
| import * as abis from '~/contracts/abi'; | ||||
| import { api } from './parity'; | ||||
| 
 | ||||
| export function attachInterface () { | ||||
|  | ||||
| @ -8,7 +8,7 @@ | ||||
|     <style> | ||||
|       html, body, #container { | ||||
|         width: 100%; | ||||
|         height: 100%; | ||||
|         min-height: 100%; | ||||
|         margin: 0; | ||||
|         padding: 0; | ||||
|         background: white; | ||||
|  | ||||
| @ -14,7 +14,7 @@ | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| import { registry as registryAbi } from '../../contracts/abi'; | ||||
| import { registry as registryAbi } from '~/contracts/abi'; | ||||
| 
 | ||||
| import { api } from './parity.js'; | ||||
| import * as addresses from './addresses/actions.js'; | ||||
|  | ||||
| @ -14,7 +14,7 @@ | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| import * as abis from '../../contracts/abi'; | ||||
| import * as abis from '~/contracts/abi'; | ||||
| import { api } from './parity'; | ||||
| 
 | ||||
| const sortEvents = (a, b) => b.blockNumber.cmp(a.blockNumber) || b.logIndex.cmp(a.logIndex); | ||||
|  | ||||
| @ -14,7 +14,7 @@ | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| import Contracts from '../../../contracts'; | ||||
| import Contracts from '~/contracts'; | ||||
| 
 | ||||
| import { loadToken, setTokenPending, deleteToken, setTokenData } from '../Tokens/actions'; | ||||
| 
 | ||||
|  | ||||
| @ -16,7 +16,7 @@ | ||||
| 
 | ||||
| import { api } from './parity'; | ||||
| 
 | ||||
| import { eip20 as eip20Abi } from '../../contracts/abi'; | ||||
| import { eip20 as eip20Abi } from '~/contracts/abi'; | ||||
| 
 | ||||
| export const getTokenTotalSupply = (tokenAddress) => { | ||||
|   return api | ||||
|  | ||||
| @ -25,19 +25,18 @@ import ReactDOM from 'react-dom'; | ||||
| import { AppContainer } from 'react-hot-loader'; | ||||
| 
 | ||||
| import injectTapEventPlugin from 'react-tap-event-plugin'; | ||||
| import { createHashHistory } from 'history'; | ||||
| import { useRouterHistory } from 'react-router'; | ||||
| import { hashHistory } from 'react-router'; | ||||
| import qs from 'querystring'; | ||||
| 
 | ||||
| import SecureApi from './secureApi'; | ||||
| import ContractInstances from './contracts'; | ||||
| import ContractInstances from '~/contracts'; | ||||
| 
 | ||||
| import { initStore } from './redux'; | ||||
| import ContextProvider from './ui/ContextProvider'; | ||||
| import muiTheme from './ui/Theme'; | ||||
| import ContextProvider from '~/ui/ContextProvider'; | ||||
| import muiTheme from '~/ui/Theme'; | ||||
| import MainApplication from './main'; | ||||
| 
 | ||||
| import { setApi } from './redux/providers/apiActions'; | ||||
| import { setApi } from '~/redux/providers/apiActions'; | ||||
| 
 | ||||
| import './environment'; | ||||
| 
 | ||||
| @ -74,13 +73,11 @@ store.dispatch(setApi(api)); | ||||
| 
 | ||||
| window.secureApi = api; | ||||
| 
 | ||||
| const routerHistory = useRouterHistory(createHashHistory)({}); | ||||
| 
 | ||||
| ReactDOM.render( | ||||
|   <AppContainer> | ||||
|     <ContextProvider api={ api } muiTheme={ muiTheme } store={ store }> | ||||
|       <MainApplication | ||||
|         routerHistory={ routerHistory } | ||||
|         routerHistory={ hashHistory } | ||||
|       /> | ||||
|     </ContextProvider> | ||||
|   </AppContainer>, | ||||
| @ -88,24 +85,6 @@ ReactDOM.render( | ||||
| ); | ||||
| 
 | ||||
| if (module.hot) { | ||||
|   // module.hot.accept('./redux', () => {
 | ||||
|   //   // redux store has a method replaceReducer
 | ||||
|   //   // const newStore = initStore(api);
 | ||||
|   //   console.warn('REDUX UPDATE');
 | ||||
|   //   // store.replaceReducer(appReducer);
 | ||||
| 
 | ||||
|   //   // ReactDOM.render(
 | ||||
|   //   //   <AppContainer>
 | ||||
|   //   //     <ContextProvider api={ api } muiTheme={ muiTheme } store={ newStore }>
 | ||||
|   //   //       <MainApplication
 | ||||
|   //   //         routerHistory={ routerHistory }
 | ||||
|   //   //       />
 | ||||
|   //   //     </ContextProvider>
 | ||||
|   //   //   </AppContainer>,
 | ||||
|   //   //   document.querySelector('#container')
 | ||||
|   //   // );
 | ||||
|   // });
 | ||||
| 
 | ||||
|   module.hot.accept('./main.js', () => { | ||||
|     require('./main.js'); | ||||
| 
 | ||||
| @ -113,7 +92,7 @@ if (module.hot) { | ||||
|       <AppContainer> | ||||
|         <ContextProvider api={ api } muiTheme={ muiTheme } store={ store }> | ||||
|           <MainApplication | ||||
|             routerHistory={ routerHistory } | ||||
|             routerHistory={ hashHistory } | ||||
|           /> | ||||
|         </ContextProvider> | ||||
|       </AppContainer>, | ||||
|  | ||||
| @ -17,7 +17,7 @@ | ||||
| import React, { Component, PropTypes } from 'react'; | ||||
| import { Redirect, Router, Route } from 'react-router'; | ||||
| 
 | ||||
| import { Accounts, Account, Addresses, Address, Application, Contract, Contracts, WriteContract, Dapp, Dapps, Settings, SettingsBackground, SettingsParity, SettingsProxy, SettingsViews, Signer, Status } from './views'; | ||||
| import { Accounts, Account, Addresses, Address, Application, Contract, Contracts, WriteContract, Dapp, Dapps, Settings, SettingsBackground, SettingsParity, SettingsProxy, SettingsViews, Signer, Status } from '~/views'; | ||||
| 
 | ||||
| import styles from './reset.css'; | ||||
| 
 | ||||
|  | ||||
| @ -18,7 +18,7 @@ import React, { Component, PropTypes } from 'react'; | ||||
| import ContentAdd from 'material-ui/svg-icons/content/add'; | ||||
| import ContentClear from 'material-ui/svg-icons/content/clear'; | ||||
| 
 | ||||
| import { Button, Modal, Form, Input, InputAddress } from '../../ui'; | ||||
| import { Button, Modal, Form, Input, InputAddress } from '~/ui'; | ||||
| import { ERRORS, validateAddress, validateName } from '../../util/validation'; | ||||
| 
 | ||||
| export default class AddAddress extends Component { | ||||
|  | ||||
| @ -20,10 +20,10 @@ import ContentClear from 'material-ui/svg-icons/content/clear'; | ||||
| import NavigationArrowForward from 'material-ui/svg-icons/navigation/arrow-forward'; | ||||
| import NavigationArrowBack from 'material-ui/svg-icons/navigation/arrow-back'; | ||||
| 
 | ||||
| import { Button, Modal, Form, Input, InputAddress, RadioButtons } from '../../ui'; | ||||
| import { Button, Modal, Form, Input, InputAddress, RadioButtons } from '~/ui'; | ||||
| import { ERRORS, validateAbi, validateAddress, validateName } from '../../util/validation'; | ||||
| 
 | ||||
| import { eip20, wallet } from '../../contracts/abi'; | ||||
| import { eip20, wallet } from '~/contracts/abi'; | ||||
| 
 | ||||
| const ABI_TYPES = [ | ||||
|   { | ||||
|  | ||||
| @ -16,7 +16,7 @@ | ||||
| 
 | ||||
| import React, { Component, PropTypes } from 'react'; | ||||
| 
 | ||||
| import { Form, Input, InputAddress } from '../../../ui'; | ||||
| import { Form, Input, InputAddress } from '~/ui'; | ||||
| 
 | ||||
| export default class AccountDetails extends Component { | ||||
|   static propTypes = { | ||||
|  | ||||
| @ -19,7 +19,7 @@ import IconButton from 'material-ui/IconButton'; | ||||
| import { RadioButton, RadioButtonGroup } from 'material-ui/RadioButton'; | ||||
| import ActionAutorenew from 'material-ui/svg-icons/action/autorenew'; | ||||
| 
 | ||||
| import { Form, Input, IdentityIcon } from '../../../ui'; | ||||
| import { Form, Input, IdentityIcon } from '~/ui'; | ||||
| 
 | ||||
| import styles from '../createAccount.css'; | ||||
| 
 | ||||
|  | ||||
| @ -17,7 +17,7 @@ | ||||
| import React, { Component, PropTypes } from 'react'; | ||||
| import { Checkbox } from 'material-ui'; | ||||
| 
 | ||||
| import { IdentityIcon } from '../../../ui'; | ||||
| import { IdentityIcon } from '~/ui'; | ||||
| 
 | ||||
| import styles from './newGeth.css'; | ||||
| 
 | ||||
|  | ||||
| @ -19,7 +19,7 @@ import ReactDOM from 'react-dom'; | ||||
| import { FloatingActionButton } from 'material-ui'; | ||||
| import EditorAttachFile from 'material-ui/svg-icons/editor/attach-file'; | ||||
| 
 | ||||
| import { Form, Input } from '../../../ui'; | ||||
| import { Form, Input } from '~/ui'; | ||||
| 
 | ||||
| import styles from '../createAccount.css'; | ||||
| 
 | ||||
|  | ||||
| @ -16,7 +16,7 @@ | ||||
| 
 | ||||
| import React, { Component, PropTypes } from 'react'; | ||||
| 
 | ||||
| import { Form, Input } from '../../../ui'; | ||||
| import { Form, Input } from '~/ui'; | ||||
| 
 | ||||
| import styles from '../createAccount.css'; | ||||
| 
 | ||||
|  | ||||
| @ -17,7 +17,7 @@ | ||||
| import React, { Component, PropTypes } from 'react'; | ||||
| import { Checkbox } from 'material-ui'; | ||||
| 
 | ||||
| import { Form, Input } from '../../../ui'; | ||||
| import { Form, Input } from '~/ui'; | ||||
| 
 | ||||
| import styles from '../createAccount.css'; | ||||
| 
 | ||||
|  | ||||
| @ -20,8 +20,9 @@ import ActionDoneAll from 'material-ui/svg-icons/action/done-all'; | ||||
| import ContentClear from 'material-ui/svg-icons/content/clear'; | ||||
| import NavigationArrowBack from 'material-ui/svg-icons/navigation/arrow-back'; | ||||
| import NavigationArrowForward from 'material-ui/svg-icons/navigation/arrow-forward'; | ||||
| import PrintIcon from 'material-ui/svg-icons/action/print'; | ||||
| 
 | ||||
| import { Button, Modal } from '../../ui'; | ||||
| import { Button, Modal } from '~/ui'; | ||||
| 
 | ||||
| import AccountDetails from './AccountDetails'; | ||||
| import AccountDetailsGeth from './AccountDetailsGeth'; | ||||
| @ -32,6 +33,11 @@ import NewImport from './NewImport'; | ||||
| import RawKey from './RawKey'; | ||||
| import RecoveryPhrase from './RecoveryPhrase'; | ||||
| 
 | ||||
| import { createIdentityImg } from '~/api/util/identity'; | ||||
| import print from './print'; | ||||
| import recoveryPage from './recovery-page.ejs'; | ||||
| import ParityLogo from '../../../assets/images/parity-logo-black-no-text.svg'; | ||||
| 
 | ||||
| const TITLES = { | ||||
|   type: 'creation type', | ||||
|   create: 'create account', | ||||
| @ -179,12 +185,18 @@ export default class CreateAccount extends Component { | ||||
|         ]; | ||||
| 
 | ||||
|       case 2: | ||||
|         return ( | ||||
|         return [ | ||||
|           createType === 'fromNew' || createType === 'fromPhrase' ? ( | ||||
|             <Button | ||||
|               icon={ <PrintIcon /> } | ||||
|               label='Print Phrase' | ||||
|               onClick={ this.printPhrase } /> | ||||
|           ) : null, | ||||
|           <Button | ||||
|             icon={ <ActionDoneAll /> } | ||||
|             label='Close' | ||||
|             onClick={ this.onClose } /> | ||||
|         ); | ||||
|         ]; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
| @ -377,4 +389,11 @@ export default class CreateAccount extends Component { | ||||
| 
 | ||||
|     store.dispatch({ type: 'newError', error }); | ||||
|   } | ||||
| 
 | ||||
|   printPhrase = () => { | ||||
|     const { address, phrase, name } = this.state; | ||||
|     const identity = createIdentityImg(address); | ||||
| 
 | ||||
|     print(recoveryPage({ phrase, name, identity, address, logo: ParityLogo })); | ||||
|   } | ||||
| } | ||||
|  | ||||
							
								
								
									
										63
									
								
								js/src/modals/CreateAccount/print.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								js/src/modals/CreateAccount/print.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,63 @@ | ||||
| // Copyright 2015, 2016 Ethcore (UK) Ltd.
 | ||||
| // This file is part of Parity.
 | ||||
| 
 | ||||
| // Parity is free software: you can redistribute it and/or modify
 | ||||
| // it under the terms of the GNU General Public License as published by
 | ||||
| // the Free Software Foundation, either version 3 of the License, or
 | ||||
| // (at your option) any later version.
 | ||||
| 
 | ||||
| // Parity is distributed in the hope that it will be useful,
 | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | ||||
| // GNU General Public License for more details.
 | ||||
| 
 | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| export const onPrint = (window, cb) => { | ||||
|   let called = false; let query, queryFn; | ||||
| 
 | ||||
|   const onPrint = () => { | ||||
|     if (queryFn) { | ||||
|       query.removeListener(queryFn); | ||||
|     } | ||||
|     window.removeEventListener('afterprint', onPrint, false); | ||||
|     if (!called) { | ||||
|       called = true; | ||||
|       cb(); | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   if (window.matchMedia) { | ||||
|     queryFn = (query) => { | ||||
|       if (!query.matches) { | ||||
|         onPrint(); | ||||
|       } | ||||
|     }; | ||||
|     query = window.matchMedia('print'); | ||||
|     query.addListener(queryFn); | ||||
|   } | ||||
|   window.addEventListener('afterprint', onPrint, false); | ||||
| }; | ||||
| 
 | ||||
| export default (html) => { | ||||
|   const iframe = document.createElement('iframe'); | ||||
|   iframe.setAttribute('sandbox', 'allow-modals allow-same-origin allow-scripts'); | ||||
|   iframe.setAttribute('src', '/'); | ||||
|   iframe.setAttribute('style', 'display: none'); | ||||
|   document.body.appendChild(iframe); | ||||
|   const teardown = () => { | ||||
|     // Safari crashes without a timeout.
 | ||||
|     setTimeout(() => document.body.removeChild(iframe), 0); | ||||
|   }; | ||||
| 
 | ||||
|   setTimeout(() => { | ||||
|     iframe.contentDocument.write(html); | ||||
| 
 | ||||
|     setTimeout(() => { | ||||
|       onPrint(iframe.contentWindow, teardown); | ||||
|       iframe.contentWindow.focus(); | ||||
|       iframe.contentWindow.print(); | ||||
|     }, 20); | ||||
|   }, 0); | ||||
| }; | ||||
							
								
								
									
										49
									
								
								js/src/modals/CreateAccount/recovery-page.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								js/src/modals/CreateAccount/recovery-page.ejs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,49 @@ | ||||
| <!DOCTYPE html> | ||||
| 
 | ||||
| <html lang="en"> | ||||
|   <head> | ||||
|     <title>Recovery phrase for <%= name %></title> | ||||
|     <meta charset="utf-8"> | ||||
|     <meta name="viewport" content="width=device-width"> | ||||
|     <style> | ||||
|       body { | ||||
|         margin: 2em auto; | ||||
|         max-width: 30em; | ||||
|         padding: 1em; | ||||
|         text-align: center; | ||||
|         font-size: 110%; | ||||
|         font-family: sans-serif; | ||||
|         font-weight: 300; | ||||
|       } | ||||
|       #logo { | ||||
|         max-width: 4rem; | ||||
|         margin-bottom: 3rem; | ||||
|       } | ||||
|       p { | ||||
|         margin-bottom: 1rem; | ||||
|       } | ||||
|       figure { | ||||
|         margin-bottom: 1rem; | ||||
|       } | ||||
|       figure.address img { | ||||
|         border-radius: 100%; | ||||
|       } | ||||
|       pre code { | ||||
|         display: inline-block; | ||||
|         text-align: center; | ||||
|         white-space: normal; | ||||
|         line-height: 1.3; | ||||
|       } | ||||
|     </style> | ||||
|   </head> | ||||
|   <body> | ||||
|     <img id="logo" src="<%= logo %>" alt="Parity Logo" /> | ||||
|     <p>This is your account <em><%= name %></em>:</p> | ||||
|     <figure class="address"> | ||||
|       <img src="<%= identity %>" alt="symbol for the address of the account" /> | ||||
|       <figcaption><code><%= address %></code></figcaption> | ||||
|     </figure> | ||||
|     <p>This is the recovery phrase:</p> | ||||
|     <pre><code><%= phrase %></code></pre> | ||||
|   </body> | ||||
| </html> | ||||
| @ -18,7 +18,7 @@ import React, { Component, PropTypes } from 'react'; | ||||
| import { connect } from 'react-redux'; | ||||
| import { bindActionCreators } from 'redux'; | ||||
| 
 | ||||
| import { ConfirmDialog, IdentityIcon, IdentityName, Input } from '../../ui'; | ||||
| import { ConfirmDialog, IdentityIcon, IdentityName, Input } from '~/ui'; | ||||
| import { newError } from '../../redux/actions'; | ||||
| 
 | ||||
| import styles from './deleteAccount.css'; | ||||
|  | ||||
| @ -17,7 +17,7 @@ | ||||
| import React, { Component, PropTypes } from 'react'; | ||||
| import { MenuItem } from 'material-ui'; | ||||
| 
 | ||||
| import { AddressSelect, Form, Input, Select } from '../../../ui'; | ||||
| import { AddressSelect, Form, Input, Select } from '~/ui'; | ||||
| import { validateAbi } from '../../../util/validation'; | ||||
| import { parseAbiType } from '../../../util/abi'; | ||||
| 
 | ||||
|  | ||||
| @ -31,7 +31,7 @@ | ||||
| 
 | ||||
| import React, { Component, PropTypes } from 'react'; | ||||
| 
 | ||||
| import { Form, TypedInput } from '../../../ui'; | ||||
| import { Form, TypedInput } from '~/ui'; | ||||
| import { parseAbiType } from '../../../util/abi'; | ||||
| 
 | ||||
| import styles from '../deployContract.css'; | ||||
|  | ||||
| @ -18,7 +18,7 @@ import React, { Component, PropTypes } from 'react'; | ||||
| import ActionDoneAll from 'material-ui/svg-icons/action/done-all'; | ||||
| import ContentClear from 'material-ui/svg-icons/content/clear'; | ||||
| 
 | ||||
| import { BusyStep, CompletedStep, CopyToClipboard, Button, IdentityIcon, Modal, TxHash } from '../../ui'; | ||||
| import { BusyStep, CompletedStep, CopyToClipboard, Button, IdentityIcon, Modal, TxHash } from '~/ui'; | ||||
| import { ERRORS, validateAbi, validateCode, validateName } from '../../util/validation'; | ||||
| 
 | ||||
| import DetailsStep from './DetailsStep'; | ||||
| @ -27,7 +27,7 @@ import ErrorStep from './ErrorStep'; | ||||
| 
 | ||||
| import styles from './deployContract.css'; | ||||
| 
 | ||||
| import { ERROR_CODES } from '../../api/transport/error'; | ||||
| import { ERROR_CODES } from '~/api/transport/error'; | ||||
| 
 | ||||
| const STEPS = { | ||||
|   CONTRACT_DETAILS: { title: 'contract details' }, | ||||
|  | ||||
| @ -18,7 +18,7 @@ import React, { Component, PropTypes } from 'react'; | ||||
| import ContentClear from 'material-ui/svg-icons/content/clear'; | ||||
| import ContentSave from 'material-ui/svg-icons/content/save'; | ||||
| 
 | ||||
| import { Button, Form, Input, InputChip, Modal } from '../../ui'; | ||||
| import { Button, Form, Input, InputChip, Modal } from '~/ui'; | ||||
| import { validateName } from '../../util/validation'; | ||||
| 
 | ||||
| export default class EditMeta extends Component { | ||||
|  | ||||
| @ -17,7 +17,7 @@ | ||||
| import React, { Component, PropTypes } from 'react'; | ||||
| import { MenuItem } from 'material-ui'; | ||||
| 
 | ||||
| import { AddressSelect, Form, Input, InputAddressSelect, Select } from '../../../ui'; | ||||
| import { AddressSelect, Form, Input, InputAddressSelect, Select } from '~/ui'; | ||||
| 
 | ||||
| import styles from '../executeContract.css'; | ||||
| 
 | ||||
|  | ||||
| @ -20,14 +20,14 @@ import { bindActionCreators } from 'redux'; | ||||
| import ActionDoneAll from 'material-ui/svg-icons/action/done-all'; | ||||
| import ContentClear from 'material-ui/svg-icons/content/clear'; | ||||
| 
 | ||||
| import { BusyStep, CompletedStep, Button, IdentityIcon, Modal, TxHash } from '../../ui'; | ||||
| import { BusyStep, CompletedStep, Button, IdentityIcon, Modal, TxHash } from '~/ui'; | ||||
| import { MAX_GAS_ESTIMATION } from '../../util/constants'; | ||||
| import { validateAddress, validateUint } from '../../util/validation'; | ||||
| 
 | ||||
| import DetailsStep from './DetailsStep'; | ||||
| 
 | ||||
| import ERRORS from '../Transfer/errors'; | ||||
| import { ERROR_CODES } from '../../api/transport/error'; | ||||
| import { ERROR_CODES } from '~/api/transport/error'; | ||||
| 
 | ||||
| class ExecuteContract extends Component { | ||||
|   static contextTypes = { | ||||
|  | ||||
| @ -18,8 +18,9 @@ import React, { Component, PropTypes } from 'react'; | ||||
| import ActionDone from 'material-ui/svg-icons/action/done'; | ||||
| import ActionDoneAll from 'material-ui/svg-icons/action/done-all'; | ||||
| import NavigationArrowForward from 'material-ui/svg-icons/navigation/arrow-forward'; | ||||
| import PrintIcon from 'material-ui/svg-icons/action/print'; | ||||
| 
 | ||||
| import { Button, Modal } from '../../ui'; | ||||
| import { Button, Modal } from '~/ui'; | ||||
| 
 | ||||
| import { NewAccount, AccountDetails } from '../CreateAccount'; | ||||
| 
 | ||||
| @ -27,6 +28,11 @@ import Completed from './Completed'; | ||||
| import TnC from './TnC'; | ||||
| import Welcome from './Welcome'; | ||||
| 
 | ||||
| import { createIdentityImg } from '~/api/util/identity'; | ||||
| import print from '../CreateAccount/print'; | ||||
| import recoveryPage from '../CreateAccount/recovery-page.ejs'; | ||||
| import ParityLogo from '../../../assets/images/parity-logo-black-no-text.svg'; | ||||
| 
 | ||||
| const STAGE_NAMES = ['welcome', 'terms', 'new account', 'recovery', 'completed']; | ||||
| 
 | ||||
| export default class FirstRun extends Component { | ||||
| @ -107,7 +113,6 @@ export default class FirstRun extends Component { | ||||
| 
 | ||||
|     switch (stage) { | ||||
|       case 0: | ||||
|       case 3: | ||||
|         return ( | ||||
|           <Button | ||||
|             icon={ <NavigationArrowForward /> } | ||||
| @ -133,6 +138,20 @@ export default class FirstRun extends Component { | ||||
|             onClick={ this.onCreate } /> | ||||
|         ); | ||||
| 
 | ||||
|       case 3: | ||||
|         return [ | ||||
|           <Button | ||||
|             icon={ <PrintIcon /> } | ||||
|             label='Print Phrase' | ||||
|             onClick={ this.printPhrase } | ||||
|           />, | ||||
|           <Button | ||||
|             icon={ <NavigationArrowForward /> } | ||||
|             label='Next' | ||||
|             onClick={ this.onNext } | ||||
|           /> | ||||
|         ]; | ||||
| 
 | ||||
|       case 4: | ||||
|         return ( | ||||
|           <Button | ||||
| @ -205,4 +224,11 @@ export default class FirstRun extends Component { | ||||
| 
 | ||||
|     store.dispatch({ type: 'newError', error }); | ||||
|   } | ||||
| 
 | ||||
|   printPhrase = () => { | ||||
|     const { address, phrase, name } = this.state; | ||||
|     const identity = createIdentityImg(address); | ||||
| 
 | ||||
|     print(recoveryPage({ phrase, name, identity, address, logo: ParityLogo })); | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -24,7 +24,7 @@ import { List, ListItem, makeSelectable } from 'material-ui/List'; | ||||
| import { Subheader, IconButton, Tabs, Tab } from 'material-ui'; | ||||
| import moment from 'moment'; | ||||
| 
 | ||||
| import { Button, Modal, Editor } from '../../ui'; | ||||
| import { Button, Modal, Editor } from '~/ui'; | ||||
| 
 | ||||
| import styles from './loadContract.css'; | ||||
| 
 | ||||
|  | ||||
| @ -24,10 +24,10 @@ import Paper from 'material-ui/Paper'; | ||||
| 
 | ||||
| import { connect } from 'react-redux'; | ||||
| import { bindActionCreators } from 'redux'; | ||||
| import { showSnackbar } from '../../redux/providers/snackbarActions'; | ||||
| import { showSnackbar } from '~/redux/providers/snackbarActions'; | ||||
| 
 | ||||
| import Form, { Input } from '../../ui/Form'; | ||||
| import { Button, Modal, IdentityName, IdentityIcon } from '../../ui'; | ||||
| import Form, { Input } from '~/ui/Form'; | ||||
| import { Button, Modal, IdentityName, IdentityIcon } from '~/ui'; | ||||
| 
 | ||||
| import styles from './passwordManager.css'; | ||||
| 
 | ||||
|  | ||||
| @ -22,8 +22,8 @@ import InfoIcon from 'material-ui/svg-icons/action/info-outline'; | ||||
| import SuccessIcon from 'material-ui/svg-icons/navigation/check'; | ||||
| import ErrorIcon from 'material-ui/svg-icons/navigation/close'; | ||||
| 
 | ||||
| import { fromWei } from '../../../api/util/wei'; | ||||
| import { Form, Input } from '../../../ui'; | ||||
| import { fromWei } from '~/api/util/wei'; | ||||
| import { Form, Input } from '~/ui'; | ||||
| 
 | ||||
| import { termsOfService } from '../../../3rdparty/sms-verification'; | ||||
| import styles from './gatherData.css'; | ||||
|  | ||||
| @ -16,7 +16,7 @@ | ||||
| 
 | ||||
| import React, { Component, PropTypes } from 'react'; | ||||
| 
 | ||||
| import { Form, Input } from '../../../ui'; | ||||
| import { Form, Input } from '~/ui'; | ||||
| 
 | ||||
| export default class QueryCode extends Component { | ||||
|   static propTypes = { | ||||
|  | ||||
| @ -19,7 +19,7 @@ import { observer } from 'mobx-react'; | ||||
| import DoneIcon from 'material-ui/svg-icons/action/done-all'; | ||||
| import CancelIcon from 'material-ui/svg-icons/content/clear'; | ||||
| 
 | ||||
| import { Button, IdentityIcon, Modal } from '../../ui'; | ||||
| import { Button, IdentityIcon, Modal } from '~/ui'; | ||||
| 
 | ||||
| import { | ||||
|   LOADING, | ||||
|  | ||||
| @ -17,7 +17,7 @@ | ||||
| import React, { Component, PropTypes } from 'react'; | ||||
| import nullable from '../../../util/nullable-proptype'; | ||||
| 
 | ||||
| import TxHash from '../../../ui/TxHash'; | ||||
| import TxHash from '~/ui/TxHash'; | ||||
| import { | ||||
|   POSTING_CONFIRMATION, POSTED_CONFIRMATION | ||||
| } from '../store'; | ||||
|  | ||||
| @ -17,7 +17,7 @@ | ||||
| import React, { Component, PropTypes } from 'react'; | ||||
| import nullable from '../../../util/nullable-proptype'; | ||||
| 
 | ||||
| import TxHash from '../../../ui/TxHash'; | ||||
| import TxHash from '~/ui/TxHash'; | ||||
| import { | ||||
|   POSTING_REQUEST, POSTED_REQUEST, REQUESTING_SMS | ||||
| } from '../store'; | ||||
|  | ||||
| @ -16,11 +16,11 @@ | ||||
| 
 | ||||
| import { observable, computed, autorun, action } from 'mobx'; | ||||
| import phone from 'phoneformat.js'; | ||||
| import { sha3 } from '../../api/util/sha3'; | ||||
| import { sha3 } from '~/api/util/sha3'; | ||||
| 
 | ||||
| import Contracts from '../../contracts'; | ||||
| import Contracts from '~/contracts'; | ||||
| 
 | ||||
| import { checkIfVerified, checkIfRequested, awaitPuzzle } from '../../contracts/sms-verification'; | ||||
| import { checkIfVerified, checkIfRequested, awaitPuzzle } from '~/contracts/sms-verification'; | ||||
| import { postToServer } from '../../3rdparty/sms-verification'; | ||||
| import checkIfTxFailed from '../../util/check-if-tx-failed'; | ||||
| import waitForConfirmations from '../../util/wait-for-block-confirmations'; | ||||
|  | ||||
| @ -19,7 +19,7 @@ import React, { Component, PropTypes } from 'react'; | ||||
| import SaveIcon from 'material-ui/svg-icons/content/save'; | ||||
| import ContentClear from 'material-ui/svg-icons/content/clear'; | ||||
| 
 | ||||
| import { Button, Modal, Editor, Form, Input } from '../../ui'; | ||||
| import { Button, Modal, Editor, Form, Input } from '~/ui'; | ||||
| import { ERRORS, validateName } from '../../util/validation'; | ||||
| 
 | ||||
| import styles from './saveContract.css'; | ||||
|  | ||||
| @ -17,7 +17,7 @@ | ||||
| import React, { Component, PropTypes } from 'react'; | ||||
| import { Checkbox, MenuItem } from 'material-ui'; | ||||
| 
 | ||||
| import { Form, Input, Select } from '../../../ui'; | ||||
| import { Form, Input, Select } from '~/ui'; | ||||
| 
 | ||||
| import Price from '../Price'; | ||||
| 
 | ||||
|  | ||||
| @ -18,7 +18,7 @@ import React, { Component, PropTypes } from 'react'; | ||||
| import ActionDoneAll from 'material-ui/svg-icons/action/done-all'; | ||||
| import ContentClear from 'material-ui/svg-icons/content/clear'; | ||||
| 
 | ||||
| import { Button, IdentityIcon, Modal } from '../../ui'; | ||||
| import { Button, IdentityIcon, Modal } from '~/ui'; | ||||
| import initShapeshift from '../../3rdparty/shapeshift'; | ||||
| import shapeshiftLogo from '../../../assets/images/shapeshift-logo.png'; | ||||
| 
 | ||||
|  | ||||
| @ -18,7 +18,9 @@ import BigNumber from 'bignumber.js'; | ||||
| import React, { Component, PropTypes } from 'react'; | ||||
| import { Checkbox, MenuItem } from 'material-ui'; | ||||
| 
 | ||||
| import Form, { Input, InputAddressSelect, Select } from '../../../ui/Form'; | ||||
| import { isEqual } from 'lodash'; | ||||
| 
 | ||||
| import Form, { Input, InputAddressSelect, Select } from '~/ui/Form'; | ||||
| 
 | ||||
| import imageUnknown from '../../../../assets/images/contracts/unknown-64x64.png'; | ||||
| import styles from '../transfer.css'; | ||||
| @ -29,11 +31,101 @@ const CHECK_STYLE = { | ||||
|   left: '1em' | ||||
| }; | ||||
| 
 | ||||
| export default class Details extends Component { | ||||
| 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={ token.tag } | ||||
|             value={ token.tag } | ||||
|             label={ label }> | ||||
|             { label } | ||||
|           </MenuItem> | ||||
|         ); | ||||
|       }); | ||||
| 
 | ||||
|     this.setState({ items }); | ||||
|   } | ||||
| 
 | ||||
|   render () { | ||||
|     const { tag, onChange } = this.props; | ||||
|     const { items } = this.state; | ||||
| 
 | ||||
|     return ( | ||||
|       <Select | ||||
|         className={ styles.tokenSelect } | ||||
|         label='type of token transfer' | ||||
|         hint='type of token to transfer' | ||||
|         value={ tag } | ||||
|         onChange={ onChange } | ||||
|       > | ||||
|         { items } | ||||
|       </Select> | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export default class Details extends Component { | ||||
|   static propTypes = { | ||||
|     address: PropTypes.string, | ||||
|     balance: PropTypes.object, | ||||
| @ -115,62 +207,15 @@ export default class Details extends Component { | ||||
|   } | ||||
| 
 | ||||
|   renderTokenSelect () { | ||||
|     const { api } = this.context; | ||||
|     const { balance, images, tag } = 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={ token.tag } | ||||
|             value={ token.tag } | ||||
|             label={ label }> | ||||
|             { label } | ||||
|           </MenuItem> | ||||
|         ); | ||||
|       }); | ||||
| 
 | ||||
|     return ( | ||||
|       <Select | ||||
|         className={ styles.tokenSelect } | ||||
|         label='type of token transfer' | ||||
|         hint='type of token to transfer' | ||||
|         value={ tag } | ||||
|         onChange={ this.onChangeToken }> | ||||
|         { items } | ||||
|       </Select> | ||||
|       <TokenSelect | ||||
|         balance={ balance } | ||||
|         images={ images } | ||||
|         tag={ tag } | ||||
|         onChange={ this.onChangeToken } | ||||
|       /> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|  | ||||
| @ -16,7 +16,7 @@ | ||||
| 
 | ||||
| import React, { Component, PropTypes } from 'react'; | ||||
| 
 | ||||
| import Form, { Input } from '../../../ui/Form'; | ||||
| import Form, { Input } from '~/ui/Form'; | ||||
| import GasPriceSelector from '../GasPriceSelector'; | ||||
| 
 | ||||
| import styles from '../transfer.css'; | ||||
|  | ||||
| @ -15,6 +15,7 @@ | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| const ERRORS = { | ||||
|   requireSender: 'a valid sender is required for the transaction', | ||||
|   requireRecipient: 'a recipient network address is required for the transaction', | ||||
|   invalidAddress: 'the supplied address is an invalid network address', | ||||
|   invalidAmount: 'the supplied amount should be a valid positive number', | ||||
|  | ||||
							
								
								
									
										463
									
								
								js/src/modals/Transfer/store.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										463
									
								
								js/src/modals/Transfer/store.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,463 @@ | ||||
| // Copyright 2015, 2016 Ethcore (UK) Ltd.
 | ||||
| // This file is part of Parity.
 | ||||
| 
 | ||||
| // Parity is free software: you can redistribute it and/or modify
 | ||||
| // it under the terms of the GNU General Public License as published by
 | ||||
| // the Free Software Foundation, either version 3 of the License, or
 | ||||
| // (at your option) any later version.
 | ||||
| 
 | ||||
| // Parity is distributed in the hope that it will be useful,
 | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | ||||
| // GNU General Public License for more details.
 | ||||
| 
 | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| import { observable, computed, action, transaction } from 'mobx'; | ||||
| import BigNumber from 'bignumber.js'; | ||||
| 
 | ||||
| import ERRORS from './errors'; | ||||
| import { ERROR_CODES } from '~/api/transport/error'; | ||||
| import { DEFAULT_GAS, DEFAULT_GASPRICE, MAX_GAS_ESTIMATION } from '../../util/constants'; | ||||
| 
 | ||||
| const TITLES = { | ||||
|   transfer: 'transfer details', | ||||
|   sending: 'sending', | ||||
|   complete: 'complete', | ||||
|   extras: 'extra information', | ||||
|   rejected: 'rejected' | ||||
| }; | ||||
| const STAGES_BASIC = [TITLES.transfer, TITLES.sending, TITLES.complete]; | ||||
| const STAGES_EXTRA = [TITLES.transfer, TITLES.extras, TITLES.sending, TITLES.complete]; | ||||
| 
 | ||||
| export default class TransferStore { | ||||
|   @observable stage = 0; | ||||
|   @observable data = ''; | ||||
|   @observable dataError = null; | ||||
|   @observable extras = false; | ||||
|   @observable gas = DEFAULT_GAS; | ||||
|   @observable gasEst = '0'; | ||||
|   @observable gasError = null; | ||||
|   @observable gasLimitError = null; | ||||
|   @observable gasPrice = DEFAULT_GASPRICE; | ||||
|   @observable gasPriceError = null; | ||||
|   @observable recipient = ''; | ||||
|   @observable recipientError = ERRORS.requireRecipient; | ||||
|   @observable sending = false; | ||||
|   @observable tag = 'ETH'; | ||||
|   @observable total = '0.0'; | ||||
|   @observable totalError = null; | ||||
|   @observable value = '0.0'; | ||||
|   @observable valueAll = false; | ||||
|   @observable valueError = null; | ||||
|   @observable isEth = true; | ||||
|   @observable busyState = null; | ||||
|   @observable rejected = false; | ||||
| 
 | ||||
|   gasPriceHistogram = {}; | ||||
| 
 | ||||
|   account = null; | ||||
|   balance = null; | ||||
|   gasLimit = null; | ||||
|   onClose = null; | ||||
| 
 | ||||
|   @computed get steps () { | ||||
|     const steps = [].concat(this.extras ? STAGES_EXTRA : STAGES_BASIC); | ||||
| 
 | ||||
|     if (this.rejected) { | ||||
|       steps[steps.length - 1] = TITLES.rejected; | ||||
|     } | ||||
| 
 | ||||
|     return steps; | ||||
|   } | ||||
| 
 | ||||
|   @computed get isValid () { | ||||
|     const detailsValid = !this.recipientError && !this.valueError && !this.totalError; | ||||
|     const extrasValid = !this.gasError && !this.gasPriceError && !this.totalError; | ||||
|     const verifyValid = !this.passwordError; | ||||
| 
 | ||||
|     switch (this.stage) { | ||||
|       case 0: | ||||
|         return detailsValid; | ||||
| 
 | ||||
|       case 1: | ||||
|         return this.extras ? extrasValid : verifyValid; | ||||
| 
 | ||||
|       case 2: | ||||
|         return verifyValid; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   constructor (api, props) { | ||||
|     this.api = api; | ||||
| 
 | ||||
|     const { account, balance, gasLimit, onClose } = props; | ||||
| 
 | ||||
|     this.account = account; | ||||
|     this.balance = balance; | ||||
|     this.gasLimit = gasLimit; | ||||
|     this.onClose = onClose; | ||||
|   } | ||||
| 
 | ||||
|   @action onNext = () => { | ||||
|     this.stage += 1; | ||||
|   } | ||||
| 
 | ||||
|   @action onPrev = () => { | ||||
|     this.stage -= 1; | ||||
|   } | ||||
| 
 | ||||
|   @action onClose = () => { | ||||
|     this.onClose && this.onClose(); | ||||
|     this.stage = 0; | ||||
|   } | ||||
| 
 | ||||
|   @action onUpdateDetails = (type, value) => { | ||||
|     switch (type) { | ||||
|       case 'all': | ||||
|         return this._onUpdateAll(value); | ||||
| 
 | ||||
|       case 'extras': | ||||
|         return this._onUpdateExtras(value); | ||||
| 
 | ||||
|       case 'data': | ||||
|         return this._onUpdateData(value); | ||||
| 
 | ||||
|       case 'gas': | ||||
|         return this._onUpdateGas(value); | ||||
| 
 | ||||
|       case 'gasPrice': | ||||
|         return this._onUpdateGasPrice(value); | ||||
| 
 | ||||
|       case 'recipient': | ||||
|         return this._onUpdateRecipient(value); | ||||
| 
 | ||||
|       case 'tag': | ||||
|         return this._onUpdateTag(value); | ||||
| 
 | ||||
|       case 'value': | ||||
|         return this._onUpdateValue(value); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @action getDefaults = () => { | ||||
|     Promise | ||||
|       .all([ | ||||
|         this.api.parity.gasPriceHistogram(), | ||||
|         this.api.eth.gasPrice() | ||||
|       ]) | ||||
|       .then(([gasPriceHistogram, gasPrice]) => { | ||||
|         transaction(() => { | ||||
|           this.gasPrice = gasPrice.toString(); | ||||
|           this.gasPriceDefault = gasPrice.toFormat(); | ||||
|           this.gasPriceHistogram = gasPriceHistogram; | ||||
| 
 | ||||
|           this.recalculate(); | ||||
|         }); | ||||
|       }) | ||||
|       .catch((error) => { | ||||
|         console.warn('getDefaults', error); | ||||
|       }); | ||||
|   } | ||||
| 
 | ||||
|   @action onSend = () => { | ||||
|     this.onNext(); | ||||
|     this.sending = true; | ||||
| 
 | ||||
|     const promise = this.isEth ? this._sendEth() : this._sendToken(); | ||||
| 
 | ||||
|     promise | ||||
|       .then((requestId) => { | ||||
|         this.busyState = 'Waiting for authorization in the Parity Signer'; | ||||
| 
 | ||||
|         return this.api | ||||
|           .pollMethod('parity_checkRequest', requestId) | ||||
|           .catch((e) => { | ||||
|             if (e.code === ERROR_CODES.REQUEST_REJECTED) { | ||||
|               this.rejected = true; | ||||
|               return false; | ||||
|             } | ||||
| 
 | ||||
|             throw e; | ||||
|           }); | ||||
|       }) | ||||
|       .then((txhash) => { | ||||
|         transaction(() => { | ||||
|           this.onNext(); | ||||
| 
 | ||||
|           this.sending = false; | ||||
|           this.txhash = txhash; | ||||
|           this.busyState = 'Your transaction has been posted to the network'; | ||||
|         }); | ||||
|       }) | ||||
|       .catch((error) => { | ||||
|         this.sending = false; | ||||
|         this.newError(error); | ||||
|       }); | ||||
|   } | ||||
| 
 | ||||
|   @action _onUpdateAll = (valueAll) => { | ||||
|     this.valueAll = valueAll; | ||||
|     this.recalculateGas(); | ||||
|   } | ||||
| 
 | ||||
|   @action _onUpdateExtras = (extras) => { | ||||
|     this.extras = extras; | ||||
|   } | ||||
| 
 | ||||
|   @action _onUpdateData = (data) => { | ||||
|     this.data = data; | ||||
|     this.recalculateGas(); | ||||
|   } | ||||
| 
 | ||||
|   @action _onUpdateGas = (gas) => { | ||||
|     const gasError = this._validatePositiveNumber(gas); | ||||
| 
 | ||||
|     transaction(() => { | ||||
|       this.gas = gas; | ||||
|       this.gasError = gasError; | ||||
| 
 | ||||
|       this.recalculate(); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   @action _onUpdateGasPrice = (gasPrice) => { | ||||
|     const gasPriceError = this._validatePositiveNumber(gasPrice); | ||||
| 
 | ||||
|     transaction(() => { | ||||
|       this.gasPrice = gasPrice; | ||||
|       this.gasPriceError = gasPriceError; | ||||
| 
 | ||||
|       this.recalculate(); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   @action _onUpdateRecipient = (recipient) => { | ||||
|     let recipientError = null; | ||||
| 
 | ||||
|     if (!recipient || !recipient.length) { | ||||
|       recipientError = ERRORS.requireRecipient; | ||||
|     } else if (!this.api.util.isAddressValid(recipient)) { | ||||
|       recipientError = ERRORS.invalidAddress; | ||||
|     } | ||||
| 
 | ||||
|     transaction(() => { | ||||
|       this.recipient = recipient; | ||||
|       this.recipientError = recipientError; | ||||
| 
 | ||||
|       this.recalculateGas(); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   @action _onUpdateTag = (tag) => { | ||||
|     transaction(() => { | ||||
|       this.tag = tag; | ||||
|       this.isEth = tag.toLowerCase().trim() === 'eth'; | ||||
| 
 | ||||
|       this.recalculateGas(); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   @action _onUpdateValue = (value) => { | ||||
|     let valueError = this._validatePositiveNumber(value); | ||||
| 
 | ||||
|     if (!valueError) { | ||||
|       valueError = this._validateDecimals(value); | ||||
|     } | ||||
| 
 | ||||
|     transaction(() => { | ||||
|       this.value = value; | ||||
|       this.valueError = valueError; | ||||
| 
 | ||||
|       this.recalculateGas(); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   @action recalculateGas = () => { | ||||
|     if (!this.isValid) { | ||||
|       this.gas = 0; | ||||
|       return this.recalculate(); | ||||
|     } | ||||
| 
 | ||||
|     const promise = this.isEth ? this._estimateGasEth() : this._estimateGasToken(); | ||||
| 
 | ||||
|     promise | ||||
|       .then((gasEst) => { | ||||
|         let gas = gasEst; | ||||
|         let gasLimitError = null; | ||||
| 
 | ||||
|         if (gas.gt(DEFAULT_GAS)) { | ||||
|           gas = gas.mul(1.2); | ||||
|         } | ||||
| 
 | ||||
|         if (gas.gte(MAX_GAS_ESTIMATION)) { | ||||
|           gasLimitError = ERRORS.gasException; | ||||
|         } else if (gas.gt(this.gasLimit)) { | ||||
|           gasLimitError = ERRORS.gasBlockLimit; | ||||
|         } | ||||
| 
 | ||||
|         transaction(() => { | ||||
|           this.gas = gas.toFixed(0); | ||||
|           this.gasEst = gasEst.toFormat(); | ||||
|           this.gasLimitError = gasLimitError; | ||||
| 
 | ||||
|           this.recalculate(); | ||||
|         }); | ||||
|       }) | ||||
|       .catch((error) => { | ||||
|         console.error('etimateGas', error); | ||||
|         this.recalculate(); | ||||
|       }); | ||||
|   } | ||||
| 
 | ||||
|   @action recalculate = () => { | ||||
|     const { account, balance } = this; | ||||
| 
 | ||||
|     if (!account || !balance) { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     const { gas, gasPrice, tag, valueAll, isEth } = this; | ||||
| 
 | ||||
|     const gasTotal = new BigNumber(gasPrice || 0).mul(new BigNumber(gas || 0)); | ||||
|     const balance_ = balance.tokens.find((b) => tag === b.token.tag); | ||||
|     const availableEth = new BigNumber(balance.tokens[0].value); | ||||
|     const available = new BigNumber(balance_.value); | ||||
|     const format = new BigNumber(balance_.token.format || 1); | ||||
| 
 | ||||
|     let { value, valueError } = this; | ||||
|     let totalEth = gasTotal; | ||||
|     let totalError = null; | ||||
| 
 | ||||
|     if (valueAll) { | ||||
|       if (isEth) { | ||||
|         const bn = this.api.util.fromWei(availableEth.minus(gasTotal)); | ||||
|         value = (bn.lt(0) ? new BigNumber(0.0) : bn).toString(); | ||||
|       } else { | ||||
|         value = available.div(format).toString(); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     if (isEth) { | ||||
|       totalEth = totalEth.plus(this.api.util.toWei(value || 0)); | ||||
|     } | ||||
| 
 | ||||
|     if (new BigNumber(value || 0).gt(available.div(format))) { | ||||
|       valueError = ERRORS.largeAmount; | ||||
|     } else if (valueError === ERRORS.largeAmount) { | ||||
|       valueError = null; | ||||
|     } | ||||
| 
 | ||||
|     if (totalEth.gt(availableEth)) { | ||||
|       totalError = ERRORS.largeAmount; | ||||
|     } | ||||
| 
 | ||||
|     transaction(() => { | ||||
|       this.total = this.api.util.fromWei(totalEth).toString(); | ||||
|       this.totalError = totalError; | ||||
|       this.value = value; | ||||
|       this.valueError = valueError; | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   _sendEth () { | ||||
|     const { account, data, gas, gasPrice, recipient, value } = this; | ||||
| 
 | ||||
|     const options = { | ||||
|       from: account.address, | ||||
|       to: recipient, | ||||
|       gas, | ||||
|       gasPrice, | ||||
|       value: this.api.util.toWei(value || 0) | ||||
|     }; | ||||
| 
 | ||||
|     if (data && data.length) { | ||||
|       options.data = data; | ||||
|     } | ||||
| 
 | ||||
|     return this.api.parity.postTransaction(options); | ||||
|   } | ||||
| 
 | ||||
|   _sendToken () { | ||||
|     const { account, balance } = this; | ||||
|     const { gas, gasPrice, recipient, value, tag } = this; | ||||
| 
 | ||||
|     const token = balance.tokens.find((balance) => balance.token.tag === tag).token; | ||||
| 
 | ||||
|     return token.contract.instance.transfer | ||||
|       .postTransaction({ | ||||
|         from: account.address, | ||||
|         to: token.address, | ||||
|         gas, | ||||
|         gasPrice | ||||
|       }, [ | ||||
|         recipient, | ||||
|         new BigNumber(value).mul(token.format).toFixed(0) | ||||
|       ]); | ||||
|   } | ||||
| 
 | ||||
|   _estimateGasToken () { | ||||
|     const { account, balance } = this; | ||||
|     const { recipient, value, tag } = this; | ||||
| 
 | ||||
|     const token = balance.tokens.find((balance) => balance.token.tag === tag).token; | ||||
| 
 | ||||
|     return token.contract.instance.transfer | ||||
|       .estimateGas({ | ||||
|         gas: MAX_GAS_ESTIMATION, | ||||
|         from: account.address, | ||||
|         to: token.address | ||||
|       }, [ | ||||
|         recipient, | ||||
|         new BigNumber(value || 0).mul(token.format).toFixed(0) | ||||
|       ]); | ||||
|   } | ||||
| 
 | ||||
|   _estimateGasEth () { | ||||
|     const { account, data, recipient, value } = this; | ||||
| 
 | ||||
|     const options = { | ||||
|       gas: MAX_GAS_ESTIMATION, | ||||
|       from: account.address, | ||||
|       to: recipient, | ||||
|       value: this.api.util.toWei(value || 0) | ||||
|     }; | ||||
| 
 | ||||
|     if (data && data.length) { | ||||
|       options.data = data; | ||||
|     } | ||||
| 
 | ||||
|     return this.api.eth.estimateGas(options); | ||||
|   } | ||||
| 
 | ||||
|   _validatePositiveNumber (num) { | ||||
|     try { | ||||
|       const v = new BigNumber(num); | ||||
|       if (v.lt(0)) { | ||||
|         return ERRORS.invalidAmount; | ||||
|       } | ||||
|     } catch (e) { | ||||
|       return ERRORS.invalidAmount; | ||||
|     } | ||||
| 
 | ||||
|     return null; | ||||
|   } | ||||
| 
 | ||||
|   _validateDecimals (num) { | ||||
|     const { balance } = this; | ||||
| 
 | ||||
|     if (this.tag === 'ETH') { | ||||
|       return null; | ||||
|     } | ||||
| 
 | ||||
|     const token = balance.tokens.find((balance) => balance.token.tag === this.tag).token; | ||||
|     const s = new BigNumber(num).mul(token.format || 1).toFixed(); | ||||
| 
 | ||||
|     if (s.indexOf('.') !== -1) { | ||||
|       return ERRORS.invalidDecimals; | ||||
|     } | ||||
| 
 | ||||
|     return null; | ||||
|   } | ||||
| } | ||||
| @ -14,88 +14,50 @@ | ||||
| // 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 { connect } from 'react-redux'; | ||||
| import { bindActionCreators } from 'redux'; | ||||
| import { observer } from 'mobx-react'; | ||||
| 
 | ||||
| import ActionDoneAll from 'material-ui/svg-icons/action/done-all'; | ||||
| import ContentClear from 'material-ui/svg-icons/content/clear'; | ||||
| import NavigationArrowBack from 'material-ui/svg-icons/navigation/arrow-back'; | ||||
| import NavigationArrowForward from 'material-ui/svg-icons/navigation/arrow-forward'; | ||||
| 
 | ||||
| import { BusyStep, CompletedStep, Button, IdentityIcon, Modal, TxHash } from '../../ui'; | ||||
| import { DEFAULT_GAS, DEFAULT_GASPRICE, MAX_GAS_ESTIMATION } from '../../util/constants'; | ||||
| import { newError } from '~/ui/Errors/actions'; | ||||
| import { BusyStep, CompletedStep, Button, IdentityIcon, Modal, TxHash } from '~/ui'; | ||||
| 
 | ||||
| import Details from './Details'; | ||||
| import Extras from './Extras'; | ||||
| import ERRORS from './errors'; | ||||
| 
 | ||||
| import TransferStore from './store'; | ||||
| import styles from './transfer.css'; | ||||
| 
 | ||||
| import { ERROR_CODES } from '../../api/transport/error'; | ||||
| 
 | ||||
| const TITLES = { | ||||
|   transfer: 'transfer details', | ||||
|   sending: 'sending', | ||||
|   complete: 'complete', | ||||
|   extras: 'extra information', | ||||
|   rejected: 'rejected' | ||||
| }; | ||||
| const STAGES_BASIC = [TITLES.transfer, TITLES.sending, TITLES.complete]; | ||||
| const STAGES_EXTRA = [TITLES.transfer, TITLES.extras, TITLES.sending, TITLES.complete]; | ||||
| 
 | ||||
| @observer | ||||
| class Transfer extends Component { | ||||
|   static contextTypes = { | ||||
|     api: PropTypes.object.isRequired, | ||||
|     store: PropTypes.object.isRequired | ||||
|     api: PropTypes.object.isRequired | ||||
|   } | ||||
| 
 | ||||
|   static propTypes = { | ||||
|     newError: PropTypes.func.isRequired, | ||||
|     gasLimit: PropTypes.object.isRequired, | ||||
|     images: PropTypes.object.isRequired, | ||||
| 
 | ||||
|     account: PropTypes.object, | ||||
|     balance: PropTypes.object, | ||||
|     balances: PropTypes.object, | ||||
|     gasLimit: PropTypes.object.isRequired, | ||||
|     images: PropTypes.object.isRequired, | ||||
|     onClose: PropTypes.func | ||||
|   } | ||||
| 
 | ||||
|   state = { | ||||
|     stage: 0, | ||||
|     data: '', | ||||
|     dataError: null, | ||||
|     extras: false, | ||||
|     gas: DEFAULT_GAS, | ||||
|     gasEst: '0', | ||||
|     gasError: null, | ||||
|     gasLimitError: null, | ||||
|     gasPrice: DEFAULT_GASPRICE, | ||||
|     gasPriceHistogram: {}, | ||||
|     gasPriceError: null, | ||||
|     recipient: '', | ||||
|     recipientError: ERRORS.requireRecipient, | ||||
|     sending: false, | ||||
|     tag: 'ETH', | ||||
|     total: '0.0', | ||||
|     totalError: null, | ||||
|     value: '0.0', | ||||
|     valueAll: false, | ||||
|     valueError: null, | ||||
|     isEth: true, | ||||
|     busyState: null, | ||||
|     rejected: false | ||||
|   } | ||||
|   store = new TransferStore(this.context.api, this.props); | ||||
| 
 | ||||
|   componentDidMount () { | ||||
|     this.getDefaults(); | ||||
|     this.store.getDefaults(); | ||||
|   } | ||||
| 
 | ||||
|   render () { | ||||
|     const { stage, extras, rejected } = this.state; | ||||
| 
 | ||||
|     const steps = [].concat(extras ? STAGES_EXTRA : STAGES_BASIC); | ||||
| 
 | ||||
|     if (rejected) { | ||||
|       steps[steps.length - 1] = TITLES.rejected; | ||||
|     } | ||||
|     const { stage, extras, steps } = this.store; | ||||
| 
 | ||||
|     return ( | ||||
|       <Modal | ||||
| @ -134,7 +96,7 @@ class Transfer extends Component { | ||||
|   } | ||||
| 
 | ||||
|   renderPage () { | ||||
|     const { extras, stage } = this.state; | ||||
|     const { extras, stage } = this.store; | ||||
| 
 | ||||
|     if (stage === 0) { | ||||
|       return this.renderDetailsPage(); | ||||
| @ -146,7 +108,7 @@ class Transfer extends Component { | ||||
|   } | ||||
| 
 | ||||
|   renderCompletePage () { | ||||
|     const { sending, txhash, busyState, rejected } = this.state; | ||||
|     const { sending, txhash, busyState, rejected } = this.store; | ||||
| 
 | ||||
|     if (rejected) { | ||||
|       return ( | ||||
| @ -174,83 +136,88 @@ class Transfer extends Component { | ||||
| 
 | ||||
|   renderDetailsPage () { | ||||
|     const { account, balance, images } = this.props; | ||||
|     const { valueAll, extras, recipient, recipientError, tag } = this.store; | ||||
|     const { total, totalError, value, valueError } = this.store; | ||||
| 
 | ||||
|     return ( | ||||
|       <Details | ||||
|         address={ account.address } | ||||
|         all={ this.state.valueAll } | ||||
|         all={ valueAll } | ||||
|         balance={ balance } | ||||
|         extras={ this.state.extras } | ||||
|         extras={ extras } | ||||
|         images={ images } | ||||
|         recipient={ this.state.recipient } | ||||
|         recipientError={ this.state.recipientError } | ||||
|         tag={ this.state.tag } | ||||
|         total={ this.state.total } | ||||
|         totalError={ this.state.totalError } | ||||
|         value={ this.state.value } | ||||
|         valueError={ this.state.valueError } | ||||
|         onChange={ this.onUpdateDetails } /> | ||||
|         recipient={ recipient } | ||||
|         recipientError={ recipientError } | ||||
|         tag={ tag } | ||||
|         total={ total } | ||||
|         totalError={ totalError } | ||||
|         value={ value } | ||||
|         valueError={ valueError } | ||||
|         onChange={ this.store.onUpdateDetails } /> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   renderExtrasPage () { | ||||
|     if (!this.state.gasPriceHistogram) { | ||||
|     if (!this.store.gasPriceHistogram) { | ||||
|       return null; | ||||
|     } | ||||
| 
 | ||||
|     const { isEth, data, dataError, gas, gasEst, gasError, gasPrice } = this.store; | ||||
|     const { gasPriceDefault, gasPriceError, gasPriceHistogram, total, totalError } = this.store; | ||||
| 
 | ||||
|     return ( | ||||
|       <Extras | ||||
|         isEth={ this.state.isEth } | ||||
|         data={ this.state.data } | ||||
|         dataError={ this.state.dataError } | ||||
|         gas={ this.state.gas } | ||||
|         gasEst={ this.state.gasEst } | ||||
|         gasError={ this.state.gasError } | ||||
|         gasPrice={ this.state.gasPrice } | ||||
|         gasPriceDefault={ this.state.gasPriceDefault } | ||||
|         gasPriceError={ this.state.gasPriceError } | ||||
|         gasPriceHistogram={ this.state.gasPriceHistogram } | ||||
|         total={ this.state.total } | ||||
|         totalError={ this.state.totalError } | ||||
|         onChange={ this.onUpdateDetails } /> | ||||
|         isEth={ isEth } | ||||
|         data={ data } | ||||
|         dataError={ dataError } | ||||
|         gas={ gas } | ||||
|         gasEst={ gasEst } | ||||
|         gasError={ gasError } | ||||
|         gasPrice={ gasPrice } | ||||
|         gasPriceDefault={ gasPriceDefault } | ||||
|         gasPriceError={ gasPriceError } | ||||
|         gasPriceHistogram={ gasPriceHistogram } | ||||
|         total={ total } | ||||
|         totalError={ totalError } | ||||
|         onChange={ this.store.onUpdateDetails } /> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   renderDialogActions () { | ||||
|     const { account } = this.props; | ||||
|     const { extras, sending, stage } = this.state; | ||||
|     const { extras, sending, stage } = this.store; | ||||
| 
 | ||||
|     const cancelBtn = ( | ||||
|       <Button | ||||
|         icon={ <ContentClear /> } | ||||
|         label='Cancel' | ||||
|         onClick={ this.onClose } /> | ||||
|         onClick={ this.store.onClose } /> | ||||
|     ); | ||||
|     const nextBtn = ( | ||||
|       <Button | ||||
|         disabled={ !this.isValid() } | ||||
|         disabled={ !this.store.isValid } | ||||
|         icon={ <NavigationArrowForward /> } | ||||
|         label='Next' | ||||
|         onClick={ this.onNext } /> | ||||
|         onClick={ this.store.onNext } /> | ||||
|     ); | ||||
|     const prevBtn = ( | ||||
|       <Button | ||||
|         icon={ <NavigationArrowBack /> } | ||||
|         label='Back' | ||||
|         onClick={ this.onPrev } /> | ||||
|         onClick={ this.store.onPrev } /> | ||||
|     ); | ||||
|     const sendBtn = ( | ||||
|       <Button | ||||
|         disabled={ !this.isValid() || sending } | ||||
|         disabled={ !this.store.isValid || sending } | ||||
|         icon={ <IdentityIcon address={ account.address } button /> } | ||||
|         label='Send' | ||||
|         onClick={ this.onSend } /> | ||||
|         onClick={ this.store.onSend } /> | ||||
|     ); | ||||
|     const doneBtn = ( | ||||
|       <Button | ||||
|         icon={ <ActionDoneAll /> } | ||||
|         label='Close' | ||||
|         onClick={ this.onClose } /> | ||||
|         onClick={ this.store.onClose } /> | ||||
|     ); | ||||
| 
 | ||||
|     switch (stage) { | ||||
| @ -268,7 +235,7 @@ class Transfer extends Component { | ||||
|   } | ||||
| 
 | ||||
|   renderWarning () { | ||||
|     const { gasLimitError } = this.state; | ||||
|     const { gasLimitError } = this.store; | ||||
| 
 | ||||
|     if (!gasLimitError) { | ||||
|       return null; | ||||
| @ -280,413 +247,17 @@ class Transfer extends Component { | ||||
|       </div> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   isValid () { | ||||
|     const detailsValid = !this.state.recipientError && !this.state.valueError && !this.state.totalError; | ||||
|     const extrasValid = !this.state.gasError && !this.state.gasPriceError && !this.state.totalError; | ||||
|     const verifyValid = !this.state.passwordError; | ||||
| 
 | ||||
|     switch (this.state.stage) { | ||||
|       case 0: | ||||
|         return detailsValid; | ||||
| 
 | ||||
|       case 1: | ||||
|         return this.state.extras ? extrasValid : verifyValid; | ||||
| 
 | ||||
|       case 2: | ||||
|         return verifyValid; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   onNext = () => { | ||||
|     this.setState({ | ||||
|       stage: this.state.stage + 1 | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   onPrev = () => { | ||||
|     this.setState({ | ||||
|       stage: this.state.stage - 1 | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   _onUpdateAll (valueAll) { | ||||
|     this.setState({ | ||||
|       valueAll | ||||
|     }, this.recalculateGas); | ||||
|   } | ||||
| 
 | ||||
|   _onUpdateExtras (extras) { | ||||
|     this.setState({ | ||||
|       extras | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   _onUpdateData (data) { | ||||
|     this.setState({ | ||||
|       data | ||||
|     }, this.recalculateGas); | ||||
|   } | ||||
| 
 | ||||
|   validatePositiveNumber (num) { | ||||
|     try { | ||||
|       const v = new BigNumber(num); | ||||
|       if (v.lt(0)) { | ||||
|         return ERRORS.invalidAmount; | ||||
|       } | ||||
|     } catch (e) { | ||||
|       return ERRORS.invalidAmount; | ||||
|     } | ||||
| 
 | ||||
|     return null; | ||||
|   } | ||||
| 
 | ||||
|   validateDecimals (num) { | ||||
|     const { balance } = this.props; | ||||
|     const { tag } = this.state; | ||||
| 
 | ||||
|     if (tag === 'ETH') { | ||||
|       return null; | ||||
|     } | ||||
| 
 | ||||
|     const token = balance.tokens.find((balance) => balance.token.tag === tag).token; | ||||
|     const s = new BigNumber(num).mul(token.format || 1).toFixed(); | ||||
| 
 | ||||
|     if (s.indexOf('.') !== -1) { | ||||
|       return ERRORS.invalidDecimals; | ||||
|     } | ||||
| 
 | ||||
|     return null; | ||||
|   } | ||||
| 
 | ||||
|   _onUpdateGas (gas) { | ||||
|     const gasError = this.validatePositiveNumber(gas); | ||||
| 
 | ||||
|     this.setState({ | ||||
|       gas, | ||||
|       gasError | ||||
|     }, this.recalculate); | ||||
|   } | ||||
| 
 | ||||
|   _onUpdateGasPrice (gasPrice) { | ||||
|     const gasPriceError = this.validatePositiveNumber(gasPrice); | ||||
| 
 | ||||
|     this.setState({ | ||||
|       gasPrice, | ||||
|       gasPriceError | ||||
|     }, this.recalculate); | ||||
|   } | ||||
| 
 | ||||
|   _onUpdateRecipient (recipient) { | ||||
|     const { api } = this.context; | ||||
|     let recipientError = null; | ||||
| 
 | ||||
|     if (!recipient || !recipient.length) { | ||||
|       recipientError = ERRORS.requireRecipient; | ||||
|     } else if (!api.util.isAddressValid(recipient)) { | ||||
|       recipientError = ERRORS.invalidAddress; | ||||
|     } | ||||
| 
 | ||||
|     this.setState({ | ||||
|       recipient, | ||||
|       recipientError | ||||
|     }, this.recalculateGas); | ||||
|   } | ||||
| 
 | ||||
|   _onUpdateTag (tag) { | ||||
|     const { balance } = this.props; | ||||
| 
 | ||||
|     this.setState({ | ||||
|       tag, | ||||
|       isEth: tag === balance.tokens[0].token.tag | ||||
|     }, this.recalculateGas); | ||||
|   } | ||||
| 
 | ||||
|   _onUpdateValue (value) { | ||||
|     let valueError = this.validatePositiveNumber(value); | ||||
| 
 | ||||
|     if (!valueError) { | ||||
|       valueError = this.validateDecimals(value); | ||||
|     } | ||||
| 
 | ||||
|     this.setState({ | ||||
|       value, | ||||
|       valueError | ||||
|     }, this.recalculateGas); | ||||
|   } | ||||
| 
 | ||||
|   onUpdateDetails = (type, value) => { | ||||
|     switch (type) { | ||||
|       case 'all': | ||||
|         return this._onUpdateAll(value); | ||||
| 
 | ||||
|       case 'extras': | ||||
|         return this._onUpdateExtras(value); | ||||
| 
 | ||||
|       case 'data': | ||||
|         return this._onUpdateData(value); | ||||
| 
 | ||||
|       case 'gas': | ||||
|         return this._onUpdateGas(value); | ||||
| 
 | ||||
|       case 'gasPrice': | ||||
|         return this._onUpdateGasPrice(value); | ||||
| 
 | ||||
|       case 'recipient': | ||||
|         return this._onUpdateRecipient(value); | ||||
| 
 | ||||
|       case 'tag': | ||||
|         return this._onUpdateTag(value); | ||||
| 
 | ||||
|       case 'value': | ||||
|         return this._onUpdateValue(value); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   _sendEth () { | ||||
|     const { api } = this.context; | ||||
|     const { account } = this.props; | ||||
|     const { data, gas, gasPrice, recipient, value } = this.state; | ||||
| 
 | ||||
|     const options = { | ||||
|       from: account.address, | ||||
|       to: recipient, | ||||
|       gas, | ||||
|       gasPrice, | ||||
|       value: api.util.toWei(value || 0) | ||||
|     }; | ||||
| 
 | ||||
|     if (data && data.length) { | ||||
|       options.data = data; | ||||
|     } | ||||
| 
 | ||||
|     return api.parity.postTransaction(options); | ||||
|   } | ||||
| 
 | ||||
|   _sendToken () { | ||||
|     const { account, balance } = this.props; | ||||
|     const { gas, gasPrice, recipient, value, tag } = this.state; | ||||
|     const token = balance.tokens.find((balance) => balance.token.tag === tag).token; | ||||
| 
 | ||||
|     return token.contract.instance.transfer | ||||
|       .postTransaction({ | ||||
|         from: account.address, | ||||
|         to: token.address, | ||||
|         gas, | ||||
|         gasPrice | ||||
|       }, [ | ||||
|         recipient, | ||||
|         new BigNumber(value).mul(token.format).toFixed(0) | ||||
|       ]); | ||||
|   } | ||||
| 
 | ||||
|   onSend = () => { | ||||
|     const { api } = this.context; | ||||
| 
 | ||||
|     this.onNext(); | ||||
| 
 | ||||
|     this.setState({ sending: true }, () => { | ||||
|       (this.state.isEth | ||||
|         ? this._sendEth() | ||||
|         : this._sendToken() | ||||
|       ).then((requestId) => { | ||||
|         this.setState({ busyState: 'Waiting for authorization in the Parity Signer' }); | ||||
| 
 | ||||
|         return api | ||||
|           .pollMethod('parity_checkRequest', requestId) | ||||
|           .catch((e) => { | ||||
|             if (e.code === ERROR_CODES.REQUEST_REJECTED) { | ||||
|               this.setState({ rejected: true }); | ||||
|               return false; | ||||
|             } | ||||
| 
 | ||||
|             throw e; | ||||
|           }); | ||||
|       }) | ||||
|       .then((txhash) => { | ||||
|         this.onNext(); | ||||
|         this.setState({ | ||||
|           sending: false, | ||||
|           txhash, | ||||
|           busyState: 'Your transaction has been posted to the network' | ||||
|         }); | ||||
|       }) | ||||
|       .catch((error) => { | ||||
|         console.log('send', error); | ||||
| 
 | ||||
|         this.setState({ | ||||
|           sending: false | ||||
|         }); | ||||
| 
 | ||||
|         this.newError(error); | ||||
|       }); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   onClose = () => { | ||||
|     this.setState({ stage: 0 }, () => { | ||||
|       this.props.onClose && this.props.onClose(); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   _estimateGasToken () { | ||||
|     const { account, balance } = this.props; | ||||
|     const { recipient, value, tag } = this.state; | ||||
|     const token = balance.tokens.find((balance) => balance.token.tag === tag).token; | ||||
| 
 | ||||
|     return token.contract.instance.transfer | ||||
|       .estimateGas({ | ||||
|         gas: MAX_GAS_ESTIMATION, | ||||
|         from: account.address, | ||||
|         to: token.address | ||||
|       }, [ | ||||
|         recipient, | ||||
|         new BigNumber(value || 0).mul(token.format).toFixed(0) | ||||
|       ]); | ||||
|   } | ||||
| 
 | ||||
|   _estimateGasEth () { | ||||
|     const { api } = this.context; | ||||
|     const { account } = this.props; | ||||
|     const { data, recipient, value } = this.state; | ||||
|     const options = { | ||||
|       gas: MAX_GAS_ESTIMATION, | ||||
|       from: account.address, | ||||
|       to: recipient, | ||||
|       value: api.util.toWei(value || 0) | ||||
|     }; | ||||
| 
 | ||||
|     if (data && data.length) { | ||||
|       options.data = data; | ||||
|     } | ||||
| 
 | ||||
|     return api.eth.estimateGas(options); | ||||
|   } | ||||
| 
 | ||||
|   recalculateGas = () => { | ||||
|     if (!this.isValid()) { | ||||
|       this.setState({ | ||||
|         gas: '0' | ||||
|       }, this.recalculate); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     const { gasLimit } = this.props; | ||||
| 
 | ||||
|     (this.state.isEth | ||||
|       ? this._estimateGasEth() | ||||
|       : this._estimateGasToken() | ||||
|     ).then((gasEst) => { | ||||
|       let gas = gasEst; | ||||
|       let gasLimitError = null; | ||||
| 
 | ||||
|       if (gas.gt(DEFAULT_GAS)) { | ||||
|         gas = gas.mul(1.2); | ||||
|       } | ||||
| 
 | ||||
|       if (gas.gte(MAX_GAS_ESTIMATION)) { | ||||
|         gasLimitError = ERRORS.gasException; | ||||
|       } else if (gas.gt(gasLimit)) { | ||||
|         gasLimitError = ERRORS.gasBlockLimit; | ||||
|       } | ||||
| 
 | ||||
|       this.setState({ | ||||
|         gas: gas.toFixed(0), | ||||
|         gasEst: gasEst.toFormat(), | ||||
|         gasLimitError | ||||
|       }, this.recalculate); | ||||
|     }) | ||||
|     .catch((error) => { | ||||
|       console.error('etimateGas', error); | ||||
|       this.recalculate(); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   recalculate = () => { | ||||
|     const { api } = this.context; | ||||
|     const { account, balance } = this.props; | ||||
| 
 | ||||
|     if (!account || !balance) { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     const { gas, gasPrice, tag, valueAll, isEth } = this.state; | ||||
|     const gasTotal = new BigNumber(gasPrice || 0).mul(new BigNumber(gas || 0)); | ||||
|     const balance_ = balance.tokens.find((b) => tag === b.token.tag); | ||||
|     const availableEth = new BigNumber(balance.tokens[0].value); | ||||
|     const available = new BigNumber(balance_.value); | ||||
|     const format = new BigNumber(balance_.token.format || 1); | ||||
| 
 | ||||
|     let { value, valueError } = this.state; | ||||
|     let totalEth = gasTotal; | ||||
|     let totalError = null; | ||||
| 
 | ||||
|     if (valueAll) { | ||||
|       if (isEth) { | ||||
|         const bn = api.util.fromWei(availableEth.minus(gasTotal)); | ||||
|         value = (bn.lt(0) ? new BigNumber(0.0) : bn).toString(); | ||||
|       } else { | ||||
|         value = available.div(format).toString(); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     if (isEth) { | ||||
|       totalEth = totalEth.plus(api.util.toWei(value || 0)); | ||||
|     } | ||||
| 
 | ||||
|     if (new BigNumber(value || 0).gt(available.div(format))) { | ||||
|       valueError = ERRORS.largeAmount; | ||||
|     } else if (valueError === ERRORS.largeAmount) { | ||||
|       valueError = null; | ||||
|     } | ||||
| 
 | ||||
|     if (totalEth.gt(availableEth)) { | ||||
|       totalError = ERRORS.largeAmount; | ||||
|     } | ||||
| 
 | ||||
|     this.setState({ | ||||
|       total: api.util.fromWei(totalEth).toString(), | ||||
|       totalError, | ||||
|       value, | ||||
|       valueError | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   getDefaults = () => { | ||||
|     const { api } = this.context; | ||||
| 
 | ||||
|     Promise | ||||
|       .all([ | ||||
|         api.parity.gasPriceHistogram(), | ||||
|         api.eth.gasPrice() | ||||
|       ]) | ||||
|       .then(([gasPriceHistogram, gasPrice]) => { | ||||
|         this.setState({ | ||||
|           gasPrice: gasPrice.toString(), | ||||
|           gasPriceDefault: gasPrice.toFormat(), | ||||
|           gasPriceHistogram | ||||
|         }, this.recalculate); | ||||
|       }) | ||||
|       .catch((error) => { | ||||
|         console.warn('getDefaults', error); | ||||
|       }); | ||||
|   } | ||||
| 
 | ||||
|   newError = (error) => { | ||||
|     const { store } = this.context; | ||||
| 
 | ||||
|     store.dispatch({ type: 'newError', error }); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| function mapStateToProps (state) { | ||||
|   const { gasLimit } = state.nodeStatus; | ||||
| 
 | ||||
|   return { gasLimit }; | ||||
| } | ||||
| 
 | ||||
| function mapDispatchToProps (dispatch) { | ||||
|   return bindActionCreators({}, dispatch); | ||||
|   return bindActionCreators({ | ||||
|     newError | ||||
|   }, dispatch); | ||||
| } | ||||
| 
 | ||||
| export default connect( | ||||
|  | ||||
| @ -14,10 +14,10 @@ | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| import { newError } from '../ui/Errors/actions'; | ||||
| import { newError } from '~/ui/Errors/actions'; | ||||
| import { setAddressImage } from './providers/imagesActions'; | ||||
| import { clearStatusLogs, toggleStatusLogs, toggleStatusRefresh } from './providers/statusActions'; | ||||
| import { toggleView } from '../views/Settings/actions'; | ||||
| import { toggleView } from '~/views/Settings/actions'; | ||||
| 
 | ||||
| export { | ||||
|   newError, | ||||
|  | ||||
| @ -15,22 +15,25 @@ | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| import thunk from 'redux-thunk'; | ||||
| 
 | ||||
| import ErrorsMiddleware from '../ui/Errors/middleware'; | ||||
| import SettingsMiddleware from '../views/Settings/middleware'; | ||||
| import ErrorsMiddleware from '~/ui/Errors/middleware'; | ||||
| import SettingsMiddleware from '~/views/Settings/middleware'; | ||||
| import SignerMiddleware from './providers/signerMiddleware'; | ||||
| 
 | ||||
| import statusMiddleware from '../views/Status/middleware'; | ||||
| import statusMiddleware from '~/views/Status/middleware'; | ||||
| import CertificationsMiddleware from './providers/certifications/middleware'; | ||||
| 
 | ||||
| export default function (api) { | ||||
|   const errors = new ErrorsMiddleware(); | ||||
|   const signer = new SignerMiddleware(api); | ||||
|   const settings = new SettingsMiddleware(); | ||||
|   const status = statusMiddleware(); | ||||
|   const certifications = new CertificationsMiddleware(); | ||||
| 
 | ||||
|   const middleware = [ | ||||
|     settings.toMiddleware(), | ||||
|     signer.toMiddleware(), | ||||
|     errors.toMiddleware() | ||||
|     errors.toMiddleware(), | ||||
|     certifications.toMiddleware() | ||||
|   ]; | ||||
| 
 | ||||
|   return middleware.concat(status, thunk); | ||||
|  | ||||
| @ -17,9 +17,9 @@ | ||||
| import { throttle } from 'lodash'; | ||||
| 
 | ||||
| import { loadTokens, setTokenReg, fetchBalances, fetchTokens, fetchTokensBalances } from './balancesActions'; | ||||
| import { padRight } from '../../api/util/format'; | ||||
| import { padRight } from '~/api/util/format'; | ||||
| 
 | ||||
| import Contracts from '../../contracts'; | ||||
| import Contracts from '~/contracts'; | ||||
| 
 | ||||
| export default class Balances { | ||||
|   constructor (store, api) { | ||||
|  | ||||
| @ -19,7 +19,7 @@ import { range, uniq, isEqual } from 'lodash'; | ||||
| import { hashToImageUrl } from './imagesReducer'; | ||||
| import { setAddressImage } from './imagesActions'; | ||||
| 
 | ||||
| import * as ABIS from '../../contracts/abi'; | ||||
| import * as ABIS from '~/contracts/abi'; | ||||
| import imagesEthereum from '../../../assets/images/contracts/ethereum-black-64x64.png'; | ||||
| 
 | ||||
| const ETH = { | ||||
| @ -256,7 +256,8 @@ export function queryTokensFilter (tokensFilter) { | ||||
|           return; | ||||
|         } | ||||
| 
 | ||||
|         const tokens = balances.tokens.filter((t) => tokenAddresses.includes(t.address)); | ||||
|         const tokens = Object.values(balances.tokens) | ||||
|           .filter((t) => tokenAddresses.includes(t.address)); | ||||
| 
 | ||||
|         fetchTokensBalances(uniq(addresses), tokens)(dispatch, getState); | ||||
|       }); | ||||
|  | ||||
| @ -14,7 +14,7 @@ | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| import Contracts from '../../contracts'; | ||||
| import Contracts from '~/contracts'; | ||||
| 
 | ||||
| export function setBlock (blockNumber, block) { | ||||
|   return { | ||||
|  | ||||
| @ -14,5 +14,10 @@ | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| export RequestFinished from './RequestFinished'; | ||||
| export RequestPending from './RequestPending'; | ||||
| export const fetchCertifications = (address) => ({ | ||||
|   type: 'fetchCertifications', address | ||||
| }); | ||||
| 
 | ||||
| export const addCertification = (address, name, title, icon) => ({ | ||||
|   type: 'addCertification', address, name, title, icon | ||||
| }); | ||||
							
								
								
									
										51
									
								
								js/src/redux/providers/certifications/middleware.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								js/src/redux/providers/certifications/middleware.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,51 @@ | ||||
| // Copyright 2015, 2016 Ethcore (UK) Ltd.
 | ||||
| // This file is part of Parity.
 | ||||
| 
 | ||||
| // Parity is free software: you can redistribute it and/or modify
 | ||||
| // it under the terms of the GNU General Public License as published by
 | ||||
| // the Free Software Foundation, either version 3 of the License, or
 | ||||
| // (at your option) any later version.
 | ||||
| 
 | ||||
| // Parity is distributed in the hope that it will be useful,
 | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | ||||
| // GNU General Public License for more details.
 | ||||
| 
 | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| import Contracts from '~/contracts'; | ||||
| import { addCertification } from './actions'; | ||||
| 
 | ||||
| const knownCertifiers = [ 'smsverification' ]; | ||||
| 
 | ||||
| export default class CertificationsMiddleware { | ||||
|   toMiddleware () { | ||||
|     return (store) => (next) => (action) => { | ||||
|       if (action.type !== 'fetchCertifications') { | ||||
|         return next(action); | ||||
|       } | ||||
| 
 | ||||
|       const { address } = action; | ||||
|       const badgeReg = Contracts.get().badgeReg; | ||||
| 
 | ||||
|       knownCertifiers.forEach((name) => { | ||||
|         badgeReg.fetchCertifier(name) | ||||
|           .then((cert) => { | ||||
|             return badgeReg.checkIfCertified(cert.address, address) | ||||
|               .then((isCertified) => { | ||||
|                 if (isCertified) { | ||||
|                   const { name, title, icon } = cert; | ||||
|                   store.dispatch(addCertification(address, name, title, icon)); | ||||
|                 } | ||||
|               }); | ||||
|           }) | ||||
|           .catch((err) => { | ||||
|             if (err) { | ||||
|               console.error(`Failed to check if ${address} certified by ${name}:`, err); | ||||
|             } | ||||
|           }); | ||||
|       }); | ||||
|     }; | ||||
|   } | ||||
| } | ||||
							
								
								
									
										33
									
								
								js/src/redux/providers/certifications/reducer.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								js/src/redux/providers/certifications/reducer.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,33 @@ | ||||
| // Copyright 2015, 2016 Ethcore (UK) Ltd.
 | ||||
| // This file is part of Parity.
 | ||||
| 
 | ||||
| // Parity is free software: you can redistribute it and/or modify
 | ||||
| // it under the terms of the GNU General Public License as published by
 | ||||
| // the Free Software Foundation, either version 3 of the License, or
 | ||||
| // (at your option) any later version.
 | ||||
| 
 | ||||
| // Parity is distributed in the hope that it will be useful,
 | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | ||||
| // GNU General Public License for more details.
 | ||||
| 
 | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| const initialState = {}; | ||||
| 
 | ||||
| export default (state = initialState, action) => { | ||||
|   if (action.type !== 'addCertification') { | ||||
|     return state; | ||||
|   } | ||||
| 
 | ||||
|   const { address, name, icon, title } = action; | ||||
|   const certifications = state[address] || []; | ||||
| 
 | ||||
|   if (certifications.some((c) => c.name === name)) { | ||||
|     return state; | ||||
|   } | ||||
|   const newCertifications = certifications.concat({ name, icon, title }); | ||||
| 
 | ||||
|   return { ...state, [address]: newCertifications }; | ||||
| }; | ||||
| @ -15,7 +15,7 @@ | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| import { handleActions } from 'redux-actions'; | ||||
| import { bytesToHex } from '../../api/util/format'; | ||||
| import { bytesToHex } from '~/api/util/format'; | ||||
| 
 | ||||
| const ZERO = '0x0000000000000000000000000000000000000000000000000000000000000000'; | ||||
| 
 | ||||
|  | ||||
| @ -16,7 +16,7 @@ | ||||
| 
 | ||||
| import * as actions from './signerActions'; | ||||
| 
 | ||||
| import { inHex } from '../../api/format/input'; | ||||
| import { inHex } from '~/api/format/input'; | ||||
| import { Wallet } from '../../util/wallet'; | ||||
| 
 | ||||
| export default class SignerMiddleware { | ||||
| @ -72,9 +72,8 @@ export default class SignerMiddleware { | ||||
|     }; | ||||
| 
 | ||||
|     // Sign request in-browser
 | ||||
|     if (wallet && payload.transaction) { | ||||
|       const { transaction } = payload; | ||||
| 
 | ||||
|     const transaction = payload.sendTransaction || payload.signTransaction; | ||||
|     if (wallet && transaction) { | ||||
|       (transaction.nonce.isZero() | ||||
|         ? this._api.parity.nextNonce(transaction.from) | ||||
|         : Promise.resolve(transaction.nonce) | ||||
|  | ||||
| @ -18,10 +18,11 @@ import { combineReducers } from 'redux'; | ||||
| import { routerReducer } from 'react-router-redux'; | ||||
| 
 | ||||
| import { apiReducer, balancesReducer, blockchainReducer, compilerReducer, imagesReducer, personalReducer, signerReducer, statusReducer as nodeStatusReducer, snackbarReducer } from './providers'; | ||||
| import certificationsReducer from './providers/certifications/reducer'; | ||||
| 
 | ||||
| import errorReducer from '../ui/Errors/reducers'; | ||||
| import settingsReducer from '../views/Settings/reducers'; | ||||
| import tooltipReducer from '../ui/Tooltips/reducers'; | ||||
| import errorReducer from '~/ui/Errors/reducers'; | ||||
| import settingsReducer from '~/views/Settings/reducers'; | ||||
| import tooltipReducer from '~/ui/Tooltips/reducers'; | ||||
| 
 | ||||
| export default function () { | ||||
|   return combineReducers({ | ||||
| @ -32,6 +33,7 @@ export default function () { | ||||
|     settings: settingsReducer, | ||||
| 
 | ||||
|     balances: balancesReducer, | ||||
|     certifications: certificationsReducer, | ||||
|     blockchain: blockchainReducer, | ||||
|     compiler: compilerReducer, | ||||
|     images: imagesReducer, | ||||
|  | ||||
| @ -15,7 +15,7 @@ | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| import { hashToImageUrl } from './providers/imagesReducer'; | ||||
| import { withError } from '../ui/Errors/middleware'; | ||||
| import { withError } from '~/ui/Errors/middleware'; | ||||
| 
 | ||||
| export { | ||||
|   hashToImageUrl, | ||||
|  | ||||
							
								
								
									
										46
									
								
								js/src/ui/Certifications/certifications.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								js/src/ui/Certifications/certifications.css
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,46 @@ | ||||
| /* Copyright 2015, 2016 Ethcore (UK) Ltd. | ||||
| /* This file is part of Parity. | ||||
| /* | ||||
| /* Parity is free software: you can redistribute it and/or modify | ||||
| /* it under the terms of the GNU General Public License as published by | ||||
| /* the Free Software Foundation, either version 3 of the License, or | ||||
| /* (at your option) any later version. | ||||
| /* | ||||
| /* Parity is distributed in the hope that it will be useful, | ||||
| /* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| /* GNU General Public License for more details. | ||||
| /* | ||||
| /* You should have received a copy of the GNU General Public License | ||||
| /* along with Parity.  If not, see <http://www.gnu.org/licenses/>. | ||||
| */ | ||||
| 
 | ||||
| .certifications { | ||||
|   margin-top: 1em; | ||||
| } | ||||
| 
 | ||||
| .certification { | ||||
|   display: inline-block; | ||||
|   position: relative; | ||||
|   margin-right: .5em; | ||||
|   padding: .3em .6em .2em 2.6em; | ||||
|   border-radius: 1em; | ||||
|   line-height: 1em; | ||||
|   text-transform: uppercase; | ||||
|   background-color: rgba(255, 255, 255, 0.07); | ||||
| } | ||||
| 
 | ||||
| .certification:last-child { | ||||
|   margin-right: 0; | ||||
| } | ||||
| 
 | ||||
| .icon { | ||||
|   position: absolute; | ||||
|   top: -.25em; | ||||
|   left: 0; | ||||
|   width: 2em; | ||||
|   height: 2em; | ||||
|   margin: 0; | ||||
|   padding: 0; | ||||
|   border-radius: 1em; | ||||
| } | ||||
							
								
								
									
										87
									
								
								js/src/ui/Certifications/certifications.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								js/src/ui/Certifications/certifications.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,87 @@ | ||||
| // Copyright 2015, 2016 Ethcore (UK) Ltd.
 | ||||
| // This file is part of Parity.
 | ||||
| 
 | ||||
| // Parity is free software: you can redistribute it and/or modify
 | ||||
| // it under the terms of the GNU General Public License as published by
 | ||||
| // the Free Software Foundation, either version 3 of the License, or
 | ||||
| // (at your option) any later version.
 | ||||
| 
 | ||||
| // Parity is distributed in the hope that it will be useful,
 | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | ||||
| // GNU General Public License for more details.
 | ||||
| 
 | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| import React, { Component, PropTypes } from 'react'; | ||||
| import { connect } from 'react-redux'; | ||||
| import { bindActionCreators } from 'redux'; | ||||
| 
 | ||||
| import { hashToImageUrl } from '~/redux/providers/imagesReducer'; | ||||
| import { fetchCertifications } from '~/redux/providers/certifications/actions'; | ||||
| 
 | ||||
| import defaultIcon from '../../../assets/images/certifications/unknown.svg'; | ||||
| 
 | ||||
| import styles from './certifications.css'; | ||||
| 
 | ||||
| class Certifications extends Component { | ||||
|   static propTypes = { | ||||
|     account: PropTypes.string.isRequired, | ||||
|     certifications: PropTypes.array.isRequired, | ||||
|     dappsUrl: PropTypes.string.isRequired, | ||||
| 
 | ||||
|     fetchCertifications: PropTypes.func.isRequired | ||||
|   } | ||||
| 
 | ||||
|   componentWillMount () { | ||||
|     const { account, fetchCertifications } = this.props; | ||||
|     fetchCertifications(account); | ||||
|   } | ||||
| 
 | ||||
|   render () { | ||||
|     const { certifications } = this.props; | ||||
| 
 | ||||
|     if (certifications.length === 0) { | ||||
|       return null; | ||||
|     } | ||||
| 
 | ||||
|     return ( | ||||
|       <div className={ styles.certifications }> | ||||
|         { certifications.map(this.renderCertification) } | ||||
|       </div> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   renderCertification = (certification) => { | ||||
|     const { name, title, icon } = certification; | ||||
|     const { dappsUrl } = this.props; | ||||
| 
 | ||||
|     const classNames = `${styles.certification} ${!icon ? styles.noIcon : ''}`; | ||||
|     const img = icon ? dappsUrl + hashToImageUrl(icon) : defaultIcon; | ||||
|     return ( | ||||
|       <div className={ classNames } key={ name }> | ||||
|         <img className={ styles.icon } src={ img } /> | ||||
|         <div className={ styles.text }>{ title || name }</div> | ||||
|       </div> | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| function mapStateToProps (_, initProps) { | ||||
|   const { account } = initProps; | ||||
| 
 | ||||
|   return (state) => { | ||||
|     const certifications = state.certifications[account] || []; | ||||
|     return { certifications }; | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| function mapDispatchToProps (dispatch) { | ||||
|   return bindActionCreators({ fetchCertifications }, dispatch); | ||||
| } | ||||
| 
 | ||||
| export default connect( | ||||
|   mapStateToProps, | ||||
|   mapDispatchToProps | ||||
| )(Certifications); | ||||
| @ -14,4 +14,4 @@ | ||||
| // 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 './requestFinished'; | ||||
| export default from './certifications'; | ||||
| @ -23,7 +23,7 @@ import Clipboard from 'react-copy-to-clipboard'; | ||||
| import CopyIcon from 'material-ui/svg-icons/content/content-copy'; | ||||
| import Theme from '../Theme'; | ||||
| 
 | ||||
| import { showSnackbar } from '../../redux/providers/snackbarActions'; | ||||
| import { showSnackbar } from '~/redux/providers/snackbarActions'; | ||||
| 
 | ||||
| const { textColor, disabledTextColor } = Theme.flatButton; | ||||
| 
 | ||||
|  | ||||
| @ -16,7 +16,6 @@ | ||||
| 
 | ||||
| import React, { Component, PropTypes } from 'react'; | ||||
| import { MenuItem } from 'material-ui'; | ||||
| import { isEqual } from 'lodash'; | ||||
| 
 | ||||
| import AutoComplete from '../AutoComplete'; | ||||
| import IdentityIcon from '../../IdentityIcon'; | ||||
| @ -64,13 +63,6 @@ export default class AddressSelect extends Component { | ||||
|   } | ||||
| 
 | ||||
|   componentWillReceiveProps (newProps) { | ||||
|     const entries = this.entriesFromProps(); | ||||
|     const addresses = Object.keys(entries).sort(); | ||||
| 
 | ||||
|     if (!isEqual(addresses, this.state.addresses)) { | ||||
|       this.setState({ entries, addresses }); | ||||
|     } | ||||
| 
 | ||||
|     if (newProps.value !== this.props.value) { | ||||
|       this.setState({ value: newProps.value }); | ||||
|     } | ||||
| @ -127,31 +119,33 @@ export default class AddressSelect extends Component { | ||||
|   } | ||||
| 
 | ||||
|   renderItem = (entry) => { | ||||
|     const { address, name } = entry; | ||||
| 
 | ||||
|     return { | ||||
|       text: entry.name && entry.name.toUpperCase() || entry.address, | ||||
|       value: this.renderSelectEntry(entry), | ||||
|       address: entry.address | ||||
|       text: name && name.toUpperCase() || address, | ||||
|       value: this.renderMenuItem(address), | ||||
|       address | ||||
|     }; | ||||
|   } | ||||
| 
 | ||||
|   renderSelectEntry = (entry) => { | ||||
|   renderMenuItem (address) { | ||||
|     const item = ( | ||||
|       <div className={ styles.account }> | ||||
|         <IdentityIcon | ||||
|           className={ styles.image } | ||||
|           inline center | ||||
|           address={ entry.address } /> | ||||
|           address={ address } /> | ||||
|         <IdentityName | ||||
|           className={ styles.name } | ||||
|           address={ entry.address } /> | ||||
|           address={ address } /> | ||||
|       </div> | ||||
|     ); | ||||
| 
 | ||||
|     return ( | ||||
|       <MenuItem | ||||
|         className={ styles.menuItem } | ||||
|         key={ entry.address } | ||||
|         value={ entry.address } | ||||
|         key={ address } | ||||
|         value={ address } | ||||
|         label={ item }> | ||||
|         { item } | ||||
|       </MenuItem> | ||||
|  | ||||
| @ -19,6 +19,8 @@ import keycode from 'keycode'; | ||||
| import { MenuItem, AutoComplete as MUIAutoComplete } from 'material-ui'; | ||||
| import { PopoverAnimationVertical } from 'material-ui/Popover'; | ||||
| 
 | ||||
| import { isEqual } from 'lodash'; | ||||
| 
 | ||||
| export default class AutoComplete extends Component { | ||||
|   static propTypes = { | ||||
|     onChange: PropTypes.func.isRequired, | ||||
| @ -42,12 +44,28 @@ export default class AutoComplete extends Component { | ||||
|     lastChangedValue: undefined, | ||||
|     entry: null, | ||||
|     open: false, | ||||
|     fakeBlur: false | ||||
|     fakeBlur: false, | ||||
|     dataSource: [] | ||||
|   } | ||||
| 
 | ||||
|   componentWillMount () { | ||||
|     const dataSource = this.getDataSource(); | ||||
|     this.setState({ dataSource }); | ||||
|   } | ||||
| 
 | ||||
|   componentWillReceiveProps (nextProps) { | ||||
|     const prevEntries = Object.keys(this.props.entries || {}).sort(); | ||||
|     const nextEntries = Object.keys(nextProps.entries || {}).sort(); | ||||
| 
 | ||||
|     if (!isEqual(prevEntries, nextEntries)) { | ||||
|       const dataSource = this.getDataSource(nextProps); | ||||
|       this.setState({ dataSource }); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   render () { | ||||
|     const { disabled, error, hint, label, value, className, filter, onUpdateInput } = this.props; | ||||
|     const { open } = this.state; | ||||
|     const { open, dataSource } = this.state; | ||||
| 
 | ||||
|     return ( | ||||
|       <MUIAutoComplete | ||||
| @ -68,7 +86,7 @@ export default class AutoComplete extends Component { | ||||
|         menuCloseDelay={ 0 } | ||||
|         fullWidth | ||||
|         floatingLabelFixed | ||||
|         dataSource={ this.getDataSource() } | ||||
|         dataSource={ dataSource } | ||||
|         menuProps={ { maxHeight: 400 } } | ||||
|         ref='muiAutocomplete' | ||||
|         onKeyDown={ this.onKeyDown } | ||||
| @ -76,8 +94,8 @@ export default class AutoComplete extends Component { | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   getDataSource () { | ||||
|     const { renderItem, entries } = this.props; | ||||
|   getDataSource (props = this.props) { | ||||
|     const { renderItem, entries } = props; | ||||
|     const entriesArray = (entries instanceof Array) | ||||
|       ? entries | ||||
|       : Object.values(entries); | ||||
|  | ||||
| @ -20,7 +20,7 @@ import { bindActionCreators } from 'redux'; | ||||
| 
 | ||||
| import Input from '../Input'; | ||||
| import IdentityIcon from '../../IdentityIcon'; | ||||
| import util from '../../../api/util'; | ||||
| import util from '~/api/util'; | ||||
| 
 | ||||
| import styles from './inputAddress.css'; | ||||
| 
 | ||||
|  | ||||
| @ -22,9 +22,9 @@ import IconButton from 'material-ui/IconButton'; | ||||
| import AddIcon from 'material-ui/svg-icons/content/add'; | ||||
| import RemoveIcon from 'material-ui/svg-icons/content/remove'; | ||||
| 
 | ||||
| import Input from '../../../ui/Form/Input'; | ||||
| import InputAddressSelect from '../../../ui/Form/InputAddressSelect'; | ||||
| import Select from '../../../ui/Form/Select'; | ||||
| import Input from '~/ui/Form/Input'; | ||||
| import InputAddressSelect from '~/ui/Form/InputAddressSelect'; | ||||
| import Select from '~/ui/Form/Select'; | ||||
| 
 | ||||
| import { ABI_TYPES } from '../../../util/abi'; | ||||
| 
 | ||||
|  | ||||
| @ -14,4 +14,4 @@ | ||||
| // 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 './TransactionFinished'; | ||||
| export default from './loading'; | ||||
| @ -15,40 +15,9 @@ | ||||
| /* along with Parity.  If not, see <http://www.gnu.org/licenses/>. | ||||
| */ | ||||
| 
 | ||||
| @import '../../_layout.css'; | ||||
| 
 | ||||
| .container { | ||||
| .loading { | ||||
|   flex: 1; | ||||
|   display: flex; | ||||
|   padding: 1.5em 0 1em; | ||||
| 
 | ||||
|   & > * { | ||||
|     vertical-align: middle; | ||||
|     min-height: $finishedHeight; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .statusContainer { | ||||
|   box-sizing: border-box; | ||||
|   float: right; | ||||
|   padding: 0 1em; | ||||
|   flex: 0 0 $statusWidth; | ||||
| } | ||||
| 
 | ||||
| .transactionDetails { | ||||
|   width: 100%; | ||||
|   box-sizing: border-box; | ||||
| } | ||||
| 
 | ||||
| .isConfirmed { | ||||
|   color: green; | ||||
| } | ||||
| 
 | ||||
| .isRejected { | ||||
|   opacity: 0.5; | ||||
|   padding-top: 2em; | ||||
| } | ||||
| 
 | ||||
| .txHash { | ||||
|   display: block; | ||||
|   word-break: break-all; | ||||
|   align-items: center; | ||||
|   justify-content: center; | ||||
| } | ||||
							
								
								
									
										36
									
								
								js/src/ui/Loading/loading.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								js/src/ui/Loading/loading.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,36 @@ | ||||
| // Copyright 2015, 2016 Ethcore (UK) Ltd.
 | ||||
| // This file is part of Parity.
 | ||||
| 
 | ||||
| // Parity is free software: you can redistribute it and/or modify
 | ||||
| // it under the terms of the GNU General Public License as published by
 | ||||
| // the Free Software Foundation, either version 3 of the License, or
 | ||||
| // (at your option) any later version.
 | ||||
| 
 | ||||
| // Parity is distributed in the hope that it will be useful,
 | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | ||||
| // GNU General Public License for more details.
 | ||||
| 
 | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| import React, { Component, PropTypes } from 'react'; | ||||
| import CircularProgress from 'material-ui/CircularProgress'; | ||||
| 
 | ||||
| import styles from './loading.css'; | ||||
| 
 | ||||
| export default class Loading extends Component { | ||||
|   static propTypes = { | ||||
|     size: PropTypes.number | ||||
|   }; | ||||
| 
 | ||||
|   render () { | ||||
|     const size = (this.props.size || 2) * 60; | ||||
| 
 | ||||
|     return ( | ||||
|       <div className={ styles.loading }> | ||||
|         <CircularProgress size={ size } /> | ||||
|       </div> | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @ -18,6 +18,20 @@ | ||||
| .container { | ||||
| } | ||||
| 
 | ||||
| .clickable { | ||||
|   border: 1px dashed rgba(255, 255, 255, 0.4); | ||||
|   padding: 0.1em 0.3em; | ||||
|   margin: 0.1em 0.1em; | ||||
| 
 | ||||
|   &:hover { | ||||
|     cursor: pointer; | ||||
|   } | ||||
| 
 | ||||
|   &.noSelect { | ||||
|     user-select: none; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .loading { | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
| @ -30,9 +44,9 @@ | ||||
| } | ||||
| 
 | ||||
| .gasDetails { | ||||
|   padding-top: 1em; | ||||
|   padding-top: 0.75em; | ||||
|   font-size: 0.75em; | ||||
|   line-height: 2em; | ||||
|   line-height: 1.5em; | ||||
|   opacity: 0.5; | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -20,7 +20,7 @@ import { connect } from 'react-redux'; | ||||
| import { bindActionCreators } from 'redux'; | ||||
| import CircularProgress from 'material-ui/CircularProgress'; | ||||
| 
 | ||||
| import Contracts from '../../contracts'; | ||||
| import Contracts from '~/contracts'; | ||||
| import { Input, InputAddress } from '../Form'; | ||||
| 
 | ||||
| import styles from './methodDecoding.css'; | ||||
| @ -54,7 +54,9 @@ class MethodDecoding extends Component { | ||||
|     isContract: false, | ||||
|     isDeploy: false, | ||||
|     isReceived: false, | ||||
|     isLoading: true | ||||
|     isLoading: true, | ||||
|     expandInput: false, | ||||
|     inputType: 'auto' | ||||
|   } | ||||
| 
 | ||||
|   componentWillMount () { | ||||
| @ -94,6 +96,11 @@ class MethodDecoding extends Component { | ||||
|   renderGas () { | ||||
|     const { historic, transaction } = this.props; | ||||
|     const { gas, gasPrice } = transaction; | ||||
| 
 | ||||
|     if (!gas || !gasPrice) { | ||||
|       return null; | ||||
|     } | ||||
| 
 | ||||
|     const gasValue = gas.mul(gasPrice); | ||||
| 
 | ||||
|     return ( | ||||
| @ -132,24 +139,55 @@ class MethodDecoding extends Component { | ||||
|       : this.renderValueTransfer(); | ||||
|   } | ||||
| 
 | ||||
|   renderInputValue () { | ||||
|   getAscii () { | ||||
|     const { api } = this.context; | ||||
|     const { transaction } = this.props; | ||||
|     const ascii = api.util.hex2Ascii(transaction.input || transaction.data); | ||||
| 
 | ||||
|     if (!/^(0x)?([0]*[1-9a-f]+[0]*)+$/.test(transaction.input)) { | ||||
|     return { value: ascii, valid: ASCII_INPUT.test(ascii) }; | ||||
|   } | ||||
| 
 | ||||
|   renderInputValue () { | ||||
|     const { transaction } = this.props; | ||||
|     const { expandInput, inputType } = this.state; | ||||
|     const input = transaction.input || transaction.data; | ||||
| 
 | ||||
|     if (!/^(0x)?([0]*[1-9a-f]+[0]*)+$/.test(input)) { | ||||
|       return null; | ||||
|     } | ||||
| 
 | ||||
|     const ascii = api.util.hex2Ascii(transaction.input); | ||||
|     const ascii = this.getAscii(); | ||||
|     const type = inputType === 'auto' | ||||
|       ? (ascii.valid ? 'ascii' : 'raw') | ||||
|       : inputType; | ||||
| 
 | ||||
|     const text = ASCII_INPUT.test(ascii) | ||||
|       ? ascii | ||||
|       : transaction.input; | ||||
|     const text = type === 'ascii' | ||||
|       ? ascii.value | ||||
|       : input; | ||||
| 
 | ||||
|     const expandable = text.length > 50; | ||||
|     const textToShow = expandInput || !expandable | ||||
|       ? text | ||||
|       : text.slice(0, 50) + '...'; | ||||
| 
 | ||||
|     return ( | ||||
|       <div> | ||||
|         <span>with the input  </span> | ||||
|         <code className={ styles.inputData }>{ text }</code> | ||||
|         <span>with the </span> | ||||
|         <span | ||||
|           onClick={ this.toggleInputType } | ||||
|           className={ [ styles.clickable, styles.noSelect ].join(' ') } | ||||
|         > | ||||
|           { type === 'ascii' ? 'input' : 'data' } | ||||
|         </span> | ||||
|         <span>   </span> | ||||
|         <span | ||||
|           onClick={ this.toggleInputExpand } | ||||
|           className={ expandable ? styles.clickable : '' } | ||||
|         > | ||||
|           <code className={ styles.inputData }> | ||||
|             { textToShow } | ||||
|           </code> | ||||
|         </span> | ||||
|       </div> | ||||
|     ); | ||||
|   } | ||||
| @ -373,6 +411,31 @@ class MethodDecoding extends Component { | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   toggleInputExpand = () => { | ||||
|     if (window.getSelection && window.getSelection().type === 'Range') { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     this.setState({ | ||||
|       expandInput: !this.state.expandInput | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   toggleInputType = () => { | ||||
|     const { inputType } = this.state; | ||||
| 
 | ||||
|     if (inputType !== 'auto') { | ||||
|       return this.setState({ | ||||
|         inputType: this.state.inputType === 'raw' ? 'ascii' : 'raw' | ||||
|       }); | ||||
|     } | ||||
| 
 | ||||
|     const ascii = this.getAscii(); | ||||
|     return this.setState({ | ||||
|       inputType: ascii.valid ? 'raw' : 'ascii' | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   lookup () { | ||||
|     const { transaction } = this.props; | ||||
| 
 | ||||
| @ -385,11 +448,12 @@ class MethodDecoding extends Component { | ||||
| 
 | ||||
|     const isReceived = transaction.to === address; | ||||
|     const contractAddress = isReceived ? transaction.from : transaction.to; | ||||
|     const input = transaction.input || transaction.data; | ||||
| 
 | ||||
|     const token = (tokens || {})[contractAddress]; | ||||
|     this.setState({ token, isReceived, contractAddress }); | ||||
| 
 | ||||
|     if (!transaction.input || transaction.input === '0x') { | ||||
|     if (!input || input === '0x') { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
| @ -408,7 +472,7 @@ class MethodDecoding extends Component { | ||||
|           return; | ||||
|         } | ||||
| 
 | ||||
|         const { signature, paramdata } = api.util.decodeCallData(transaction.input); | ||||
|         const { signature, paramdata } = api.util.decodeCallData(input); | ||||
|         this.setState({ methodSignature: signature, methodParams: paramdata }); | ||||
| 
 | ||||
|         if (!signature || signature === CONTRACT_CREATE || transaction.creates) { | ||||
|  | ||||
| @ -14,8 +14,9 @@ | ||||
| /* You should have received a copy of the GNU General Public License | ||||
| /* along with Parity.  If not, see <http://www.gnu.org/licenses/>. | ||||
| */ | ||||
| 
 | ||||
| .layout { | ||||
|   padding: 0.25em; | ||||
|   padding: 0.25em 0.25em 1em 0.25em; | ||||
| } | ||||
| 
 | ||||
| .layout>div { | ||||
|  | ||||
| @ -92,7 +92,9 @@ export default class Store { | ||||
| 
 | ||||
|     Promise | ||||
|       .all(txhashes.map((txhash) => this._api.eth.getTransactionByHash(txhash))) | ||||
|       .then((transactions) => { | ||||
|       .then((_transactions) => { | ||||
|         const transactions = _transactions.filter((tx) => tx); | ||||
| 
 | ||||
|         this.addTransactions( | ||||
|           transactions.reduce((transactions, tx, index) => { | ||||
|             transactions[txhashes[index]] = tx; | ||||
|  | ||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
		Reference in New Issue
	
	Block a user