Merge pull request #202 from zcash/enhancements

Enhancements
This commit is contained in:
Francisco Gindre 2020-10-16 15:14:55 -03:00 committed by GitHub
commit 5d84755615
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 1443 additions and 240 deletions

103
Cargo.lock generated
View File

@ -111,6 +111,12 @@ version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b20b618342cf9891c292c4f5ac2cde7287cc5c87e87e9c769d617793607dec1" checksum = "1b20b618342cf9891c292c4f5ac2cde7287cc5c87e87e9c769d617793607dec1"
[[package]]
name = "base58"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5024ee8015f02155eee35c711107ddd9a9bf3cb689cf2a9089c97e79b6e1ae83"
[[package]] [[package]]
name = "base64" name = "base64"
version = "0.12.3" version = "0.12.3"
@ -295,9 +301,9 @@ dependencies = [
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.60" version = "1.0.41"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef611cc68ff783f18535d77ddd080185275713d852c4f5cbb6122c462a7a825c" checksum = "8dae9c4b8fedcae85592ba623c4fd08cfdab3e3b72d6df780c6ead964a69bfff"
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
@ -571,6 +577,18 @@ dependencies = [
"hashbrown", "hashbrown",
] ]
[[package]]
name = "hdwallet"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35b15cc3c181a2aace485d56c784568a4ae6e34322287a2499549d4cda7af3e1"
dependencies = [
"lazy_static",
"rand",
"ring",
"secp256k1",
]
[[package]] [[package]]
name = "heck" name = "heck"
version = "0.3.1" version = "0.3.1"
@ -601,6 +619,15 @@ version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6"
[[package]]
name = "js-sys"
version = "0.3.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca059e81d9486668f12d455a4ea6daa600bd408134cd17e3d3fb5a32d1f016f8"
dependencies = [
"wasm-bindgen",
]
[[package]] [[package]]
name = "jubjub" name = "jubjub"
version = "0.5.1" version = "0.5.1"
@ -642,10 +669,16 @@ dependencies = [
name = "libzcashlc" name = "libzcashlc"
version = "0.0.5" version = "0.0.5"
dependencies = [ dependencies = [
"base58",
"bs58",
"cbindgen", "cbindgen",
"failure", "failure",
"ffi_helpers", "ffi_helpers",
"hdwallet",
"hex", "hex",
"ripemd160",
"secp256k1",
"sha2 0.9.1",
"zcash_client_backend", "zcash_client_backend",
"zcash_client_sqlite", "zcash_client_sqlite",
"zcash_primitives", "zcash_primitives",
@ -869,6 +902,32 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "ring"
version = "0.16.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ba5a8ec64ee89a76c98c549af81ff14813df09c3e6dc4766c3856da48597a0c"
dependencies = [
"cc",
"lazy_static",
"libc",
"spin",
"untrusted",
"web-sys",
"winapi",
]
[[package]]
name = "ripemd160"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2eca4ecc81b7f313189bf73ce724400a07da2a6dac19588b03c8bd76a2dcc251"
dependencies = [
"block-buffer 0.9.0",
"digest 0.9.0",
"opaque-debug 0.3.0",
]
[[package]] [[package]]
name = "rusqlite" name = "rusqlite"
version = "0.24.1" version = "0.24.1"
@ -918,6 +977,24 @@ version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
[[package]]
name = "secp256k1"
version = "0.17.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2932dc07acd2066ff2e3921a4419606b220ba6cd03a9935123856cc534877056"
dependencies = [
"secp256k1-sys",
]
[[package]]
name = "secp256k1-sys"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ab2c26f0d3552a0f12e639ae8a64afc2e3db9c52fe32f5fc6c289d38519f220"
dependencies = [
"cc",
]
[[package]] [[package]]
name = "semver" name = "semver"
version = "0.9.0" version = "0.9.0"
@ -1001,6 +1078,12 @@ version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbee7696b84bbf3d89a1c2eccff0850e3047ed46bfcd2e92c29a2d074d57e252" checksum = "fbee7696b84bbf3d89a1c2eccff0850e3047ed46bfcd2e92c29a2d074d57e252"
[[package]]
name = "spin"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]] [[package]]
name = "standback" name = "standback"
version = "0.2.11" version = "0.2.11"
@ -1188,6 +1271,12 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
[[package]]
name = "untrusted"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]] [[package]]
name = "vcpkg" name = "vcpkg"
version = "0.2.10" version = "0.2.10"
@ -1266,6 +1355,16 @@ version = "0.2.68"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d649a3145108d7d3fbcde896a468d1bd636791823c9921135218ad89be08307" checksum = "1d649a3145108d7d3fbcde896a468d1bd636791823c9921135218ad89be08307"
[[package]]
name = "web-sys"
version = "0.3.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bf6ef87ad7ae8008e15a355ce696bed26012b7caa21605188cfd8214ab51e2d"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]] [[package]]
name = "winapi" name = "winapi"
version = "0.3.9" version = "0.3.9"

View File

@ -15,6 +15,15 @@ zcash_client_backend = "0.4"
zcash_client_sqlite = "0.2" zcash_client_sqlite = "0.2"
zcash_primitives = "0.4" zcash_primitives = "0.4"
#### Temporary additions: ####################################
base58 = "0.1.0"
sha2 = "0.9"
bs58 = { version = "0.3", features = ["check"] }
hdwallet = "0.2.2"
ripemd160 = "0.9"
secp256k1 = "0.17.2"
##############################################################
[dependencies.zcash_proofs] [dependencies.zcash_proofs]
version = "0.4" version = "0.4"
default-features = false default-features = false

View File

@ -18,7 +18,6 @@ target 'ZcashLightClientSample' do
end end
target 'ZcashLightClientSampleUITests' do target 'ZcashLightClientSampleUITests' do
use_frameworks!
inherit! :search_paths inherit! :search_paths
# Pods for testing # Pods for testing
end end

View File

@ -66,10 +66,10 @@ PODS:
- SwiftNIOFoundationCompat (< 3, >= 2.22.0) - SwiftNIOFoundationCompat (< 3, >= 2.22.0)
- SwiftNIOTLS (< 3, >= 2.22.0) - SwiftNIOTLS (< 3, >= 2.22.0)
- SwiftProtobuf (1.12.0) - SwiftProtobuf (1.12.0)
- ZcashLightClientKit (0.6.6): - ZcashLightClientKit (0.7.0):
- gRPC-Swift (= 1.0.0-alpha.19) - gRPC-Swift (= 1.0.0-alpha.19)
- SQLite.swift (~> 0.12.2) - SQLite.swift (~> 0.12.2)
- ZcashLightClientKit/Tests (0.6.6): - ZcashLightClientKit/Tests (0.7.0):
- gRPC-Swift (= 1.0.0-alpha.19) - gRPC-Swift (= 1.0.0-alpha.19)
- SQLite.swift (~> 0.12.2) - SQLite.swift (~> 0.12.2)
@ -145,8 +145,8 @@ SPEC CHECKSUMS:
SwiftNIOTLS: 46bb3a0ff37d6b52ae6baf5207ec3cd411da327c SwiftNIOTLS: 46bb3a0ff37d6b52ae6baf5207ec3cd411da327c
SwiftNIOTransportServices: 801923921fbecdcde1e1c1ff38e812167d01ead1 SwiftNIOTransportServices: 801923921fbecdcde1e1c1ff38e812167d01ead1
SwiftProtobuf: 4ef85479c18ca85b5482b343df9c319c62bda699 SwiftProtobuf: 4ef85479c18ca85b5482b343df9c319c62bda699
ZcashLightClientKit: aa2bfedb1cd48385d35113672b521a4bb47fbbf4 ZcashLightClientKit: 6a12b800a373f3330d4bc177480e56b499c23a8f
PODFILE CHECKSUM: db5f48346d4a54b2e0a33adb8890ffba9289ec63 PODFILE CHECKSUM: 7d5095283dc02470f40ab06564d94076ba16d570
COCOAPODS: 1.9.3 COCOAPODS: 1.9.3

View File

@ -7,10 +7,9 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
0D2343EE238C91B900606F71 /* sapling-output.params in Resources */ = {isa = PBXBuildFile; fileRef = 0D2343EC238C91B900606F71 /* sapling-output.params */; };
0D2343EF238C91B900606F71 /* sapling-spend.params in Resources */ = {isa = PBXBuildFile; fileRef = 0D2343ED238C91B900606F71 /* sapling-spend.params */; };
0D49A18C241698A800CC0649 /* SampleLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D49A18B241698A800CC0649 /* SampleLogger.swift */; }; 0D49A18C241698A800CC0649 /* SampleLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D49A18B241698A800CC0649 /* SampleLogger.swift */; };
0D4EBA312396CFD70041B507 /* SendViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D4EBA302396CFD70041B507 /* SendViewController.swift */; }; 0D4EBA312396CFD70041B507 /* SendViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D4EBA302396CFD70041B507 /* SendViewController.swift */; };
0D6CE8BD252E3C4A0005D707 /* SaplingParametersViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D6CE8BC252E3C4A0005D707 /* SaplingParametersViewController.swift */; };
0D756A94236C761E009B041B /* GetAddressViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D756A93236C761E009B041B /* GetAddressViewController.swift */; }; 0D756A94236C761E009B041B /* GetAddressViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D756A93236C761E009B041B /* GetAddressViewController.swift */; };
0D7A4A83236CCD88001F4DD8 /* SyncBlocksViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D7A4A82236CCD88001F4DD8 /* SyncBlocksViewController.swift */; }; 0D7A4A83236CCD88001F4DD8 /* SyncBlocksViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D7A4A82236CCD88001F4DD8 /* SyncBlocksViewController.swift */; };
0D7C85E523AD5A9B006878FC /* SampleStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D7C85E423AD5A9B006878FC /* SampleStorage.swift */; }; 0D7C85E523AD5A9B006878FC /* SampleStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D7C85E423AD5A9B006878FC /* SampleStorage.swift */; };
@ -30,8 +29,6 @@
0D8BB46223B1DA0700D5E2A1 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0D907F1E2322CC5B00D641FE /* LaunchScreen.storyboard */; }; 0D8BB46223B1DA0700D5E2A1 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0D907F1E2322CC5B00D641FE /* LaunchScreen.storyboard */; };
0D8BB46323B1DA0700D5E2A1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0D907F1C2322CC5B00D641FE /* Assets.xcassets */; }; 0D8BB46323B1DA0700D5E2A1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0D907F1C2322CC5B00D641FE /* Assets.xcassets */; };
0D8BB46423B1DA0700D5E2A1 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0D907F192322CC5900D641FE /* Main.storyboard */; }; 0D8BB46423B1DA0700D5E2A1 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0D907F192322CC5900D641FE /* Main.storyboard */; };
0D8BB46623B1DA0700D5E2A1 /* sapling-output.params in Resources */ = {isa = PBXBuildFile; fileRef = 0D2343EC238C91B900606F71 /* sapling-output.params */; };
0D8BB46723B1DA0700D5E2A1 /* sapling-spend.params in Resources */ = {isa = PBXBuildFile; fileRef = 0D2343ED238C91B900606F71 /* sapling-spend.params */; };
0D907F162322CC5900D641FE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D907F152322CC5900D641FE /* AppDelegate.swift */; }; 0D907F162322CC5900D641FE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D907F152322CC5900D641FE /* AppDelegate.swift */; };
0D907F182322CC5900D641FE /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D907F172322CC5900D641FE /* ViewController.swift */; }; 0D907F182322CC5900D641FE /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D907F172322CC5900D641FE /* ViewController.swift */; };
0D907F1B2322CC5900D641FE /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0D907F192322CC5900D641FE /* Main.storyboard */; }; 0D907F1B2322CC5900D641FE /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0D907F192322CC5900D641FE /* Main.storyboard */; };
@ -46,9 +43,9 @@
0DDFB33C236B743000AED892 /* LatestHeightViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDFB33B236B743000AED892 /* LatestHeightViewController.swift */; }; 0DDFB33C236B743000AED892 /* LatestHeightViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDFB33B236B743000AED892 /* LatestHeightViewController.swift */; };
0DDFB33E236B844900AED892 /* DemoAppConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDFB33D236B844900AED892 /* DemoAppConfig.swift */; }; 0DDFB33E236B844900AED892 /* DemoAppConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DDFB33D236B844900AED892 /* DemoAppConfig.swift */; };
0DF53E6723A438F100D7249C /* PaginatedTransactionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DF53E6623A438F100D7249C /* PaginatedTransactionsViewController.swift */; }; 0DF53E6723A438F100D7249C /* PaginatedTransactionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DF53E6623A438F100D7249C /* PaginatedTransactionsViewController.swift */; };
4B7E6D953A7CC9204FDE510C /* Pods_ZcashLightClientSample.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6CB06C3C92417F3584991C0B /* Pods_ZcashLightClientSample.framework */; };
6355F11E6389A7F4FD9F21F7 /* Pods_ZcashLightClientSample_Mainnet.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 49540782BAF7E579947E23C4 /* Pods_ZcashLightClientSample_Mainnet.framework */; }; 6355F11E6389A7F4FD9F21F7 /* Pods_ZcashLightClientSample_Mainnet.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 49540782BAF7E579947E23C4 /* Pods_ZcashLightClientSample_Mainnet.framework */; };
742DCA7AFF9FBEA217FD3EED /* Pods_ZcashLightClientSampleUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 33F67E6BA5A27016288E77EB /* Pods_ZcashLightClientSampleUITests.framework */; }; 742DCA7AFF9FBEA217FD3EED /* Pods_ZcashLightClientSampleUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 33F67E6BA5A27016288E77EB /* Pods_ZcashLightClientSampleUITests.framework */; };
A337DEF9CF408DD9F8C200DD /* Pods_ZcashLightClientSample.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CCCFED26DDACC4FBD997470C /* Pods_ZcashLightClientSample.framework */; };
A459F9A413C2AB70A02FE894 /* Pods_ZcashLightClientSampleTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C89C3AE14EA6993FECC643BC /* Pods_ZcashLightClientSampleTests.framework */; }; A459F9A413C2AB70A02FE894 /* Pods_ZcashLightClientSampleTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C89C3AE14EA6993FECC643BC /* Pods_ZcashLightClientSampleTests.framework */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
@ -70,10 +67,9 @@
/* End PBXContainerItemProxy section */ /* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
0D2343EC238C91B900606F71 /* sapling-output.params */ = {isa = PBXFileReference; lastKnownFileType = file; name = "sapling-output.params"; path = "../../../ZcashLightClientKitTests/sapling-output.params"; sourceTree = "<group>"; };
0D2343ED238C91B900606F71 /* sapling-spend.params */ = {isa = PBXFileReference; lastKnownFileType = file; name = "sapling-spend.params"; path = "../../../ZcashLightClientKitTests/sapling-spend.params"; sourceTree = "<group>"; };
0D49A18B241698A800CC0649 /* SampleLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SampleLogger.swift; sourceTree = "<group>"; }; 0D49A18B241698A800CC0649 /* SampleLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SampleLogger.swift; sourceTree = "<group>"; };
0D4EBA302396CFD70041B507 /* SendViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendViewController.swift; sourceTree = "<group>"; }; 0D4EBA302396CFD70041B507 /* SendViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendViewController.swift; sourceTree = "<group>"; };
0D6CE8BC252E3C4A0005D707 /* SaplingParametersViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SaplingParametersViewController.swift; sourceTree = "<group>"; };
0D756A93236C761E009B041B /* GetAddressViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetAddressViewController.swift; sourceTree = "<group>"; }; 0D756A93236C761E009B041B /* GetAddressViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetAddressViewController.swift; sourceTree = "<group>"; };
0D7A4A82236CCD88001F4DD8 /* SyncBlocksViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncBlocksViewController.swift; sourceTree = "<group>"; }; 0D7A4A82236CCD88001F4DD8 /* SyncBlocksViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncBlocksViewController.swift; sourceTree = "<group>"; };
0D7C85E423AD5A9B006878FC /* SampleStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SampleStorage.swift; sourceTree = "<group>"; }; 0D7C85E423AD5A9B006878FC /* SampleStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SampleStorage.swift; sourceTree = "<group>"; };
@ -105,12 +101,12 @@
47991618469ABD2A7FE69E73 /* Pods-ZcashLightClientSample-Mainnet.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ZcashLightClientSample-Mainnet.release.xcconfig"; path = "Target Support Files/Pods-ZcashLightClientSample-Mainnet/Pods-ZcashLightClientSample-Mainnet.release.xcconfig"; sourceTree = "<group>"; }; 47991618469ABD2A7FE69E73 /* Pods-ZcashLightClientSample-Mainnet.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ZcashLightClientSample-Mainnet.release.xcconfig"; path = "Target Support Files/Pods-ZcashLightClientSample-Mainnet/Pods-ZcashLightClientSample-Mainnet.release.xcconfig"; sourceTree = "<group>"; };
49540782BAF7E579947E23C4 /* Pods_ZcashLightClientSample_Mainnet.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ZcashLightClientSample_Mainnet.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 49540782BAF7E579947E23C4 /* Pods_ZcashLightClientSample_Mainnet.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ZcashLightClientSample_Mainnet.framework; sourceTree = BUILT_PRODUCTS_DIR; };
501B4B56030ED65B9C170CE2 /* Pods-ZcashLightClientSampleTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ZcashLightClientSampleTests.debug.xcconfig"; path = "Target Support Files/Pods-ZcashLightClientSampleTests/Pods-ZcashLightClientSampleTests.debug.xcconfig"; sourceTree = "<group>"; }; 501B4B56030ED65B9C170CE2 /* Pods-ZcashLightClientSampleTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ZcashLightClientSampleTests.debug.xcconfig"; path = "Target Support Files/Pods-ZcashLightClientSampleTests/Pods-ZcashLightClientSampleTests.debug.xcconfig"; sourceTree = "<group>"; };
6CB06C3C92417F3584991C0B /* Pods_ZcashLightClientSample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ZcashLightClientSample.framework; sourceTree = BUILT_PRODUCTS_DIR; };
81B2AC726EA5CC3FEF521F84 /* Pods-ZcashLightClientSampleUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ZcashLightClientSampleUITests.debug.xcconfig"; path = "Target Support Files/Pods-ZcashLightClientSampleUITests/Pods-ZcashLightClientSampleUITests.debug.xcconfig"; sourceTree = "<group>"; }; 81B2AC726EA5CC3FEF521F84 /* Pods-ZcashLightClientSampleUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ZcashLightClientSampleUITests.debug.xcconfig"; path = "Target Support Files/Pods-ZcashLightClientSampleUITests/Pods-ZcashLightClientSampleUITests.debug.xcconfig"; sourceTree = "<group>"; };
90F55027AEE5406E35644223 /* Pods-ZcashLightClientSample-Mainnet.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ZcashLightClientSample-Mainnet.debug.xcconfig"; path = "Target Support Files/Pods-ZcashLightClientSample-Mainnet/Pods-ZcashLightClientSample-Mainnet.debug.xcconfig"; sourceTree = "<group>"; }; 90F55027AEE5406E35644223 /* Pods-ZcashLightClientSample-Mainnet.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ZcashLightClientSample-Mainnet.debug.xcconfig"; path = "Target Support Files/Pods-ZcashLightClientSample-Mainnet/Pods-ZcashLightClientSample-Mainnet.debug.xcconfig"; sourceTree = "<group>"; };
93F435FD5C95BEE3BC7BBC28 /* Pods-ZcashLightClientSample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ZcashLightClientSample.debug.xcconfig"; path = "Target Support Files/Pods-ZcashLightClientSample/Pods-ZcashLightClientSample.debug.xcconfig"; sourceTree = "<group>"; }; 93F435FD5C95BEE3BC7BBC28 /* Pods-ZcashLightClientSample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ZcashLightClientSample.debug.xcconfig"; path = "Target Support Files/Pods-ZcashLightClientSample/Pods-ZcashLightClientSample.debug.xcconfig"; sourceTree = "<group>"; };
BEA142AE2F3C339269CBA120 /* Pods-ZcashLightClientSampleTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ZcashLightClientSampleTests.release.xcconfig"; path = "Target Support Files/Pods-ZcashLightClientSampleTests/Pods-ZcashLightClientSampleTests.release.xcconfig"; sourceTree = "<group>"; }; BEA142AE2F3C339269CBA120 /* Pods-ZcashLightClientSampleTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ZcashLightClientSampleTests.release.xcconfig"; path = "Target Support Files/Pods-ZcashLightClientSampleTests/Pods-ZcashLightClientSampleTests.release.xcconfig"; sourceTree = "<group>"; };
C89C3AE14EA6993FECC643BC /* Pods_ZcashLightClientSampleTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ZcashLightClientSampleTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; C89C3AE14EA6993FECC643BC /* Pods_ZcashLightClientSampleTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ZcashLightClientSampleTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
CCCFED26DDACC4FBD997470C /* Pods_ZcashLightClientSample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ZcashLightClientSample.framework; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
@ -126,7 +122,7 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
4B7E6D953A7CC9204FDE510C /* Pods_ZcashLightClientSample.framework in Frameworks */, A337DEF9CF408DD9F8C200DD /* Pods_ZcashLightClientSample.framework in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -157,6 +153,14 @@
path = Send; path = Send;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
0D6CE8BB252E3C1A0005D707 /* Sapling Parameters */ = {
isa = PBXGroup;
children = (
0D6CE8BC252E3C4A0005D707 /* SaplingParametersViewController.swift */,
);
path = "Sapling Parameters";
sourceTree = "<group>";
};
0D756A92236C75FE009B041B /* Get Address */ = { 0D756A92236C75FE009B041B /* Get Address */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -200,13 +204,12 @@
0D907F142322CC5900D641FE /* ZcashLightClientSample */ = { 0D907F142322CC5900D641FE /* ZcashLightClientSample */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
0D6CE8BB252E3C1A0005D707 /* Sapling Parameters */,
0DBF8F9323A80F0E0010B85F /* Transaction Detail */, 0DBF8F9323A80F0E0010B85F /* Transaction Detail */,
0DF53E6523A438BA00D7249C /* Paginated Transactions */, 0DF53E6523A438BA00D7249C /* Paginated Transactions */,
0DA58B922397DDBC004596EA /* List Transactions */, 0DA58B922397DDBC004596EA /* List Transactions */,
0D4EBA2F2396CFBE0041B507 /* Send */, 0D4EBA2F2396CFBE0041B507 /* Send */,
0DCD3DC5238D888B00DD3EC4 /* Get Balance */, 0DCD3DC5238D888B00DD3EC4 /* Get Balance */,
0D2343EC238C91B900606F71 /* sapling-output.params */,
0D2343ED238C91B900606F71 /* sapling-spend.params */,
0D7A4A81236CCCDB001F4DD8 /* Sync Blocks */, 0D7A4A81236CCCDB001F4DD8 /* Sync Blocks */,
0D756A92236C75FE009B041B /* Get Address */, 0D756A92236C75FE009B041B /* Get Address */,
0DDFB33A236B733700AED892 /* Latest Block Height */, 0DDFB33A236B733700AED892 /* Latest Block Height */,
@ -303,7 +306,7 @@
C89C3AE14EA6993FECC643BC /* Pods_ZcashLightClientSampleTests.framework */, C89C3AE14EA6993FECC643BC /* Pods_ZcashLightClientSampleTests.framework */,
33F67E6BA5A27016288E77EB /* Pods_ZcashLightClientSampleUITests.framework */, 33F67E6BA5A27016288E77EB /* Pods_ZcashLightClientSampleUITests.framework */,
49540782BAF7E579947E23C4 /* Pods_ZcashLightClientSample_Mainnet.framework */, 49540782BAF7E579947E23C4 /* Pods_ZcashLightClientSample_Mainnet.framework */,
6CB06C3C92417F3584991C0B /* Pods_ZcashLightClientSample.framework */, CCCFED26DDACC4FBD997470C /* Pods_ZcashLightClientSample.framework */,
); );
name = Frameworks; name = Frameworks;
sourceTree = "<group>"; sourceTree = "<group>";
@ -340,7 +343,7 @@
0D907F0E2322CC5900D641FE /* Sources */, 0D907F0E2322CC5900D641FE /* Sources */,
0D907F0F2322CC5900D641FE /* Frameworks */, 0D907F0F2322CC5900D641FE /* Frameworks */,
0D907F102322CC5900D641FE /* Resources */, 0D907F102322CC5900D641FE /* Resources */,
45EC872B9ED4F87BF9B92B1C /* [CP] Embed Pods Frameworks */, AD38BD608A7D4F64E720F342 /* [CP] Embed Pods Frameworks */,
); );
buildRules = ( buildRules = (
); );
@ -441,8 +444,6 @@
0D8BB46223B1DA0700D5E2A1 /* LaunchScreen.storyboard in Resources */, 0D8BB46223B1DA0700D5E2A1 /* LaunchScreen.storyboard in Resources */,
0D8BB46323B1DA0700D5E2A1 /* Assets.xcassets in Resources */, 0D8BB46323B1DA0700D5E2A1 /* Assets.xcassets in Resources */,
0D8BB46423B1DA0700D5E2A1 /* Main.storyboard in Resources */, 0D8BB46423B1DA0700D5E2A1 /* Main.storyboard in Resources */,
0D8BB46623B1DA0700D5E2A1 /* sapling-output.params in Resources */,
0D8BB46723B1DA0700D5E2A1 /* sapling-spend.params in Resources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -453,8 +454,6 @@
0D907F202322CC5B00D641FE /* LaunchScreen.storyboard in Resources */, 0D907F202322CC5B00D641FE /* LaunchScreen.storyboard in Resources */,
0D907F1D2322CC5B00D641FE /* Assets.xcassets in Resources */, 0D907F1D2322CC5B00D641FE /* Assets.xcassets in Resources */,
0D907F1B2322CC5900D641FE /* Main.storyboard in Resources */, 0D907F1B2322CC5900D641FE /* Main.storyboard in Resources */,
0D2343EE238C91B900606F71 /* sapling-output.params in Resources */,
0D2343EF238C91B900606F71 /* sapling-spend.params in Resources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -553,23 +552,6 @@
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0; showEnvVarsInLog = 0;
}; };
45EC872B9ED4F87BF9B92B1C /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-ZcashLightClientSample/Pods-ZcashLightClientSample-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-ZcashLightClientSample/Pods-ZcashLightClientSample-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ZcashLightClientSample/Pods-ZcashLightClientSample-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
A18B8A4D55F1DF8B808ABB67 /* [CP] Embed Pods Frameworks */ = { A18B8A4D55F1DF8B808ABB67 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
@ -631,6 +613,23 @@
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0; showEnvVarsInLog = 0;
}; };
AD38BD608A7D4F64E720F342 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-ZcashLightClientSample/Pods-ZcashLightClientSample-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-ZcashLightClientSample/Pods-ZcashLightClientSample-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ZcashLightClientSample/Pods-ZcashLightClientSample-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */ /* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */
@ -668,6 +667,7 @@
0D7A4A83236CCD88001F4DD8 /* SyncBlocksViewController.swift in Sources */, 0D7A4A83236CCD88001F4DD8 /* SyncBlocksViewController.swift in Sources */,
0DDFB33E236B844900AED892 /* DemoAppConfig.swift in Sources */, 0DDFB33E236B844900AED892 /* DemoAppConfig.swift in Sources */,
0D907F162322CC5900D641FE /* AppDelegate.swift in Sources */, 0D907F162322CC5900D641FE /* AppDelegate.swift in Sources */,
0D6CE8BD252E3C4A0005D707 /* SaplingParametersViewController.swift in Sources */,
0D7C85E523AD5A9B006878FC /* SampleStorage.swift in Sources */, 0D7C85E523AD5A9B006878FC /* SampleStorage.swift in Sources */,
0D49A18C241698A800CC0649 /* SampleLogger.swift in Sources */, 0D49A18C241698A800CC0649 /* SampleLogger.swift in Sources */,
0DA58B962397F2CB004596EA /* TransactionsDataSource.swift in Sources */, 0DA58B962397F2CB004596EA /* TransactionsDataSource.swift in Sources */,

View File

@ -40,12 +40,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
spendParamsURL: try! __spendParamsURL(), spendParamsURL: try! __spendParamsURL(),
outputParamsURL: try! __outputParamsURL(), outputParamsURL: try! __outputParamsURL(),
loggerProxy: loggerProxy) loggerProxy: loggerProxy)
let addresses = try! wallet.initialize(seedProvider: DemoAppConfig(), try! wallet.initialize(viewingKeys: try DerivationTool.default.deriveViewingKeys(seed: DemoAppConfig.seed, numberOfAccounts: 1),
walletBirthdayHeight: BlockHeight(DemoAppConfig.birthdayHeight)) // Init or DIE walletBirthday: BlockHeight(DemoAppConfig.birthdayHeight)) // Init or DIE
var storage = SampleStorage.shared var storage = SampleStorage.shared
storage!.seed = Data(DemoAppConfig().seed()) storage!.seed = Data(try! DemoAppConfig().seed())
storage!.privateKey = addresses?[0] storage!.privateKey = try! DerivationTool.default.deriveSpendingKeys(seed: DemoAppConfig.seed, numberOfAccounts: 1)[0]
self.wallet = wallet self.wallet = wallet
return wallet return wallet
} }
@ -132,12 +132,6 @@ extension AppDelegate {
} }
} }
extension DemoAppConfig: SeedProvider {
func seed() -> [UInt8] {
DemoAppConfig.seed
}
}
extension Initializer { extension Initializer {
static var shared: Initializer { static var shared: Initializer {
AppDelegate.shared.sharedWallet // AppDelegate or DIE. AppDelegate.shared.sharedWallet // AppDelegate or DIE.
@ -167,14 +161,15 @@ func __pendingDbURL() throws -> URL {
} }
func __spendParamsURL() throws -> URL { func __spendParamsURL() throws -> URL {
Bundle.main.url(forResource: "sapling-spend", withExtension: ".params")! try __documentsDirectory().appendingPathComponent("sapling-spend.params")
} }
func __outputParamsURL() throws -> URL { func __outputParamsURL() throws -> URL {
Bundle.main.url(forResource: "sapling-output", withExtension: ".params")! try __documentsDirectory().appendingPathComponent("sapling-output.params")
} }
public extension NotificationBubble { public extension NotificationBubble {
static func sucessOptions(animation: NotificationBubble.Animation) -> [NotificationBubble.Style] { static func sucessOptions(animation: NotificationBubble.Animation) -> [NotificationBubble.Style] {
return [ NotificationBubble.Style.animation(animation), return [ NotificationBubble.Style.animation(animation),

View File

@ -1,9 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="16097" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="Ewq-Xy-xHb"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17156" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="Ewq-Xy-xHb">
<device id="retina6_1" orientation="portrait" appearance="light"/> <device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies> <dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/> <deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17125"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/> <capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
<scenes> <scenes>
@ -14,7 +16,7 @@
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="EVy-1I-VtG"> <tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="EVy-1I-VtG">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/> <rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/> <color key="backgroundColor" systemColor="systemBackgroundColor"/>
<sections> <sections>
<tableViewSection id="HAA-1f-le3"> <tableViewSection id="HAA-1f-le3">
<cells> <cells>
@ -238,6 +240,26 @@
<segue destination="6wc-2b-HvC" kind="show" identifier="Paginated" id="tXC-6F-YcU"/> <segue destination="6wc-2b-HvC" kind="show" identifier="Paginated" id="tXC-6F-YcU"/>
</connections> </connections>
</tableViewCell> </tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="XCC-pZ-eel" style="IBUITableViewCellStyleDefault" id="dgB-c9-cv9">
<rect key="frame" x="0.0" y="506.5" width="414" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="dgB-c9-cv9" id="C46-42-3yU">
<rect key="frame" x="0.0" y="0.0" width="414" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Sapling Parameters" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="XCC-pZ-eel">
<rect key="frame" x="20" y="0.0" width="374" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</tableViewCellContentView>
<connections>
<segue destination="Nsd-EZ-dgy" kind="show" id="Xvb-pF-KgB"/>
</connections>
</tableViewCell>
</cells> </cells>
</tableViewSection> </tableViewSection>
</sections> </sections>
@ -250,7 +272,7 @@
<simulatedNavigationBarMetrics key="simulatedTopBarMetrics" prompted="NO"/> <simulatedNavigationBarMetrics key="simulatedTopBarMetrics" prompted="NO"/>
</tableViewController> </tableViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="u9B-ne-NE7" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/> <placeholder placeholderIdentifier="IBFirstResponder" id="u9B-ne-NE7" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" id="vYi-by-eJM"> <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" id="vYi-by-eJM">
<rect key="frame" x="0.0" y="0.0" width="46" height="30"/> <rect key="frame" x="0.0" y="0.0" width="46" height="30"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<state key="normal" title="Button"/> <state key="normal" title="Button"/>
@ -268,7 +290,7 @@
<subviews> <subviews>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="7kA-7f-dpe" customClass="PaginatedTableView" customModule="PaginatedTableView"> <tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="7kA-7f-dpe" customClass="PaginatedTableView" customModule="PaginatedTableView">
<rect key="frame" x="0.0" y="88" width="414" height="774"/> <rect key="frame" x="0.0" y="88" width="414" height="774"/>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/> <color key="backgroundColor" systemColor="systemBackgroundColor"/>
<prototypes> <prototypes>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="TransactionCell" textLabel="U7p-Y7-W1b" detailTextLabel="ngx-lY-C9z" style="IBUITableViewCellStyleSubtitle" id="0Hj-GS-f83"> <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="TransactionCell" textLabel="U7p-Y7-W1b" detailTextLabel="ngx-lY-C9z" style="IBUITableViewCellStyleSubtitle" id="0Hj-GS-f83">
<rect key="frame" x="0.0" y="28" width="414" height="55.5"/> <rect key="frame" x="0.0" y="28" width="414" height="55.5"/>
@ -278,7 +300,7 @@
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="U7p-Y7-W1b"> <label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="U7p-Y7-W1b">
<rect key="frame" x="20" y="10" width="33.5" height="20.5"/> <rect key="frame" x="20" y="10" width="33" height="20.5"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/> <fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/> <nil key="textColor"/>
@ -300,7 +322,8 @@
</prototypes> </prototypes>
</tableView> </tableView>
</subviews> </subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/> <viewLayoutGuide key="safeArea" id="uPE-9c-zlh"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints> <constraints>
<constraint firstItem="uPE-9c-zlh" firstAttribute="bottom" secondItem="7kA-7f-dpe" secondAttribute="bottom" id="NTg-7T-3FT"/> <constraint firstItem="uPE-9c-zlh" firstAttribute="bottom" secondItem="7kA-7f-dpe" secondAttribute="bottom" id="NTg-7T-3FT"/>
<constraint firstItem="7kA-7f-dpe" firstAttribute="centerX" secondItem="uPE-9c-zlh" secondAttribute="centerX" id="WgJ-Jn-EWn"/> <constraint firstItem="7kA-7f-dpe" firstAttribute="centerX" secondItem="uPE-9c-zlh" secondAttribute="centerX" id="WgJ-Jn-EWn"/>
@ -309,7 +332,6 @@
<constraint firstItem="uPE-9c-zlh" firstAttribute="trailing" secondItem="7kA-7f-dpe" secondAttribute="trailing" id="np6-8l-i7Y"/> <constraint firstItem="uPE-9c-zlh" firstAttribute="trailing" secondItem="7kA-7f-dpe" secondAttribute="trailing" id="np6-8l-i7Y"/>
<constraint firstItem="7kA-7f-dpe" firstAttribute="top" secondItem="uPE-9c-zlh" secondAttribute="top" id="vWa-3a-llZ"/> <constraint firstItem="7kA-7f-dpe" firstAttribute="top" secondItem="uPE-9c-zlh" secondAttribute="top" id="vWa-3a-llZ"/>
</constraints> </constraints>
<viewLayoutGuide key="safeArea" id="uPE-9c-zlh"/>
</view> </view>
<navigationItem key="navigationItem" id="Dpm-6S-CDh"/> <navigationItem key="navigationItem" id="Dpm-6S-CDh"/>
<connections> <connections>
@ -327,7 +349,7 @@
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="Btf-Ps-SgQ"> <tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="Btf-Ps-SgQ">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/> <rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/> <color key="backgroundColor" systemColor="systemBackgroundColor"/>
<prototypes> <prototypes>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="TransactionCell" textLabel="ZgQ-vD-hBa" detailTextLabel="hDe-Pg-Ygb" style="IBUITableViewCellStyleSubtitle" id="GMs-Mo-bHf"> <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="TransactionCell" textLabel="ZgQ-vD-hBa" detailTextLabel="hDe-Pg-Ygb" style="IBUITableViewCellStyleSubtitle" id="GMs-Mo-bHf">
<rect key="frame" x="0.0" y="28" width="414" height="55.5"/> <rect key="frame" x="0.0" y="28" width="414" height="55.5"/>
@ -337,7 +359,7 @@
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="ZgQ-vD-hBa"> <label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="ZgQ-vD-hBa">
<rect key="frame" x="20" y="10" width="33.5" height="20.5"/> <rect key="frame" x="20" y="10" width="33" height="20.5"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/> <fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/> <nil key="textColor"/>
@ -375,7 +397,7 @@
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="GZf-SE-Mi3"> <tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="GZf-SE-Mi3">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/> <rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/> <color key="backgroundColor" systemColor="systemBackgroundColor"/>
<sections> <sections>
<tableViewSection id="Imd-sb-3mE"> <tableViewSection id="Imd-sb-3mE">
<cells> <cells>
@ -387,7 +409,7 @@
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="id:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="MpE-wV-n7T"> <label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="id:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="MpE-wV-n7T">
<rect key="frame" x="20" y="12" width="19" height="20.5"/> <rect key="frame" x="20" y="12" width="18.5" height="20.5"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/> <fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/> <nil key="textColor"/>
@ -411,14 +433,14 @@
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Mined Height" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Lz1-6q-dQ6"> <label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Mined Height" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Lz1-6q-dQ6">
<rect key="frame" x="20" y="12" width="102.5" height="20.5"/> <rect key="frame" x="20" y="12" width="102" height="20.5"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/> <fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/> <nil key="textColor"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Height" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="7dw-1w-ESS"> <label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Height" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="7dw-1w-ESS">
<rect key="frame" x="343" y="12" width="51" height="20.5"/> <rect key="frame" x="343.5" y="12" width="50.5" height="20.5"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/> <fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/> <nil key="textColor"/>
@ -442,7 +464,7 @@
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="date" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="3it-Im-6mo"> <label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="date" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="3it-Im-6mo">
<rect key="frame" x="359.5" y="12" width="34.5" height="20.5"/> <rect key="frame" x="360" y="12" width="34" height="20.5"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/> <fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/> <nil key="textColor"/>
@ -459,14 +481,14 @@
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="expiry height" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="dPr-pp-SCj"> <label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="expiry height" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="dPr-pp-SCj">
<rect key="frame" x="20" y="12" width="99" height="20.5"/> <rect key="frame" x="20" y="12" width="98.5" height="20.5"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/> <fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/> <nil key="textColor"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="expires at" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="K4H-bj-ib8"> <label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="expires at" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="K4H-bj-ib8">
<rect key="frame" x="319.5" y="12" width="74.5" height="20.5"/> <rect key="frame" x="320" y="12" width="74" height="20.5"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/> <fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/> <nil key="textColor"/>
@ -483,7 +505,7 @@
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="zatoshi" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Wfw-cd-nlP"> <label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="zatoshi" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Wfw-cd-nlP">
<rect key="frame" x="20" y="12" width="55.5" height="20.5"/> <rect key="frame" x="20" y="12" width="55" height="20.5"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/> <fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/> <nil key="textColor"/>
@ -557,7 +579,7 @@
<rect key="frame" x="20" y="143" width="374" height="32"/> <rect key="frame" x="20" y="143" width="374" height="32"/>
<subviews> <subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" verticalHuggingPriority="251" text="Balance:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="AFU-aL-aZ0"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" verticalHuggingPriority="251" text="Balance:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="AFU-aL-aZ0">
<rect key="frame" x="0.0" y="0.0" width="254" height="32"/> <rect key="frame" x="0.0" y="0.0" width="255.5" height="32"/>
<constraints> <constraints>
<constraint firstAttribute="height" constant="32" id="c5n-hg-sIV"/> <constraint firstAttribute="height" constant="32" id="c5n-hg-sIV"/>
</constraints> </constraints>
@ -566,7 +588,7 @@
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Zec 0.0" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="8RU-tm-VOE"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Zec 0.0" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="8RU-tm-VOE">
<rect key="frame" x="286" y="0.0" width="88" height="32"/> <rect key="frame" x="287.5" y="0.0" width="86.5" height="32"/>
<constraints> <constraints>
<constraint firstAttribute="height" constant="32" id="KuU-va-m1a"/> <constraint firstAttribute="height" constant="32" id="KuU-va-m1a"/>
</constraints> </constraints>
@ -585,20 +607,20 @@
<constraint firstAttribute="height" constant="28" id="LJY-DX-BrR"/> <constraint firstAttribute="height" constant="28" id="LJY-DX-BrR"/>
</constraints> </constraints>
<fontDescription key="fontDescription" type="italicSystem" pointSize="23"/> <fontDescription key="fontDescription" type="italicSystem" pointSize="23"/>
<color key="textColor" cocoaTouchSystemColor="scrollViewTexturedBackgroundColor"/> <color key="textColor" systemColor="scrollViewTexturedBackgroundColor"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<stackView opaque="NO" contentMode="scaleToFill" spacing="19" translatesAutoresizingMaskIntoConstraints="NO" id="q7P-t6-O88"> <stackView opaque="NO" contentMode="scaleToFill" spacing="19" translatesAutoresizingMaskIntoConstraints="NO" id="q7P-t6-O88">
<rect key="frame" x="19" y="252" width="375" height="34"/> <rect key="frame" x="19" y="252" width="375" height="34"/>
<subviews> <subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Amount:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="0JH-Y9-k10"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Amount:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="0JH-Y9-k10">
<rect key="frame" x="0.0" y="0.0" width="95.5" height="34"/> <rect key="frame" x="0.0" y="0.0" width="95" height="34"/>
<fontDescription key="fontDescription" type="system" pointSize="26"/> <fontDescription key="fontDescription" type="system" pointSize="26"/>
<nil key="textColor"/> <nil key="textColor"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="0.0" textAlignment="natural" minimumFontSize="17" clearButtonMode="whileEditing" translatesAutoresizingMaskIntoConstraints="NO" id="QPl-ho-Vb5"> <textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="0.0" textAlignment="natural" minimumFontSize="17" clearButtonMode="whileEditing" translatesAutoresizingMaskIntoConstraints="NO" id="QPl-ho-Vb5">
<rect key="frame" x="114.5" y="0.0" width="260.5" height="34"/> <rect key="frame" x="114" y="0.0" width="261" height="34"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/> <fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits" autocorrectionType="no" spellCheckingType="no" keyboardType="decimalPad" returnKeyType="next" enablesReturnKeyAutomatically="YES" smartDashesType="no" smartInsertDeleteType="no" smartQuotesType="no"/> <textInputTraits key="textInputTraits" autocorrectionType="no" spellCheckingType="no" keyboardType="decimalPad" returnKeyType="next" enablesReturnKeyAutomatically="YES" smartDashesType="no" smartInsertDeleteType="no" smartQuotesType="no"/>
<connections> <connections>
@ -614,13 +636,13 @@
<rect key="frame" x="20" y="353" width="374" height="34"/> <rect key="frame" x="20" y="353" width="374" height="34"/>
<subviews> <subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="to:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="uPw-SX-zmP"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="to:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="uPw-SX-zmP">
<rect key="frame" x="0.0" y="0.0" width="29" height="34"/> <rect key="frame" x="0.0" y="0.0" width="28" height="34"/>
<fontDescription key="fontDescription" type="system" pointSize="26"/> <fontDescription key="fontDescription" type="system" pointSize="26"/>
<nil key="textColor"/> <nil key="textColor"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="zAddress" textAlignment="natural" minimumFontSize="17" clearButtonMode="always" translatesAutoresizingMaskIntoConstraints="NO" id="pMR-dt-N4H"> <textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="zAddress" textAlignment="natural" minimumFontSize="17" clearButtonMode="always" translatesAutoresizingMaskIntoConstraints="NO" id="pMR-dt-N4H">
<rect key="frame" x="50" y="0.0" width="324" height="34"/> <rect key="frame" x="49" y="0.0" width="325" height="34"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/> <fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits" autocorrectionType="no" spellCheckingType="no" keyboardType="alphabet" returnKeyType="done" enablesReturnKeyAutomatically="YES" smartDashesType="no" smartInsertDeleteType="no" smartQuotesType="no"/> <textInputTraits key="textInputTraits" autocorrectionType="no" spellCheckingType="no" keyboardType="alphabet" returnKeyType="done" enablesReturnKeyAutomatically="YES" smartDashesType="no" smartInsertDeleteType="no" smartQuotesType="no"/>
<connections> <connections>
@ -630,16 +652,16 @@
</subviews> </subviews>
</stackView> </stackView>
<stackView opaque="NO" contentMode="scaleToFill" spacing="16" translatesAutoresizingMaskIntoConstraints="NO" id="06w-ih-1vw"> <stackView opaque="NO" contentMode="scaleToFill" spacing="16" translatesAutoresizingMaskIntoConstraints="NO" id="06w-ih-1vw">
<rect key="frame" x="249" y="311" width="145" height="31"/> <rect key="frame" x="249.5" y="311" width="144.5" height="31"/>
<subviews> <subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Max funds" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="qUm-uc-jqe"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Max funds" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="qUm-uc-jqe">
<rect key="frame" x="0.0" y="0.0" width="80" height="31"/> <rect key="frame" x="0.0" y="0.0" width="79.5" height="31"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/> <fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/> <nil key="textColor"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="xns-dw-RD9"> <switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="xns-dw-RD9">
<rect key="frame" x="96" y="0.0" width="51" height="31"/> <rect key="frame" x="95.5" y="0.0" width="51" height="31"/>
<connections> <connections>
<action selector="maxFundsValueChanged:" destination="6mH-Rv-HBn" eventType="valueChanged" id="Fbp-vd-U5h"/> <action selector="maxFundsValueChanged:" destination="6mH-Rv-HBn" eventType="valueChanged" id="Fbp-vd-U5h"/>
</connections> </connections>
@ -650,15 +672,15 @@
<rect key="frame" x="21" y="183" width="373" height="32"/> <rect key="frame" x="21" y="183" width="373" height="32"/>
<subviews> <subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="verified" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="rBT-Aw-Qe1"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="verified" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="rBT-Aw-Qe1">
<rect key="frame" x="0.0" y="0.0" width="61" height="32"/> <rect key="frame" x="0.0" y="0.0" width="60.5" height="32"/>
<fontDescription key="fontDescription" type="italicSystem" pointSize="18"/> <fontDescription key="fontDescription" type="italicSystem" pointSize="18"/>
<color key="textColor" cocoaTouchSystemColor="scrollViewTexturedBackgroundColor"/> <color key="textColor" systemColor="scrollViewTexturedBackgroundColor"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" verticalHuggingPriority="251" text="Zec 0.0" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="io7-QX-p59"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" verticalHuggingPriority="251" text="Zec 0.0" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="io7-QX-p59">
<rect key="frame" x="93" y="0.0" width="280" height="32"/> <rect key="frame" x="92.5" y="0.0" width="280.5" height="32"/>
<fontDescription key="fontDescription" type="italicSystem" pointSize="18"/> <fontDescription key="fontDescription" type="italicSystem" pointSize="18"/>
<color key="textColor" cocoaTouchSystemColor="scrollViewTexturedBackgroundColor"/> <color key="textColor" systemColor="scrollViewTexturedBackgroundColor"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
</subviews> </subviews>
@ -672,7 +694,7 @@
<constraint firstAttribute="height" constant="141" id="J6k-T9-MPS"/> <constraint firstAttribute="height" constant="141" id="J6k-T9-MPS"/>
</constraints> </constraints>
<string key="text">Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda.</string> <string key="text">Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda.</string>
<color key="textColor" systemColor="labelColor" cocoaTouchSystemColor="darkTextColor"/> <color key="textColor" systemColor="labelColor"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/> <fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits" autocorrectionType="no" spellCheckingType="no" enablesReturnKeyAutomatically="YES" smartDashesType="no" smartInsertDeleteType="no" smartQuotesType="no"/> <textInputTraits key="textInputTraits" autocorrectionType="no" spellCheckingType="no" enablesReturnKeyAutomatically="YES" smartDashesType="no" smartInsertDeleteType="no" smartQuotesType="no"/>
<connections> <connections>
@ -685,10 +707,10 @@
<constraint firstAttribute="height" constant="21" id="0Yw-Vi-MSw"/> <constraint firstAttribute="height" constant="21" id="0Yw-Vi-MSw"/>
</constraints> </constraints>
<fontDescription key="fontDescription" type="system" pointSize="17"/> <fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" systemColor="systemGray2Color" red="0.68235294120000001" green="0.68235294120000001" blue="0.69803921570000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="textColor" systemColor="systemGray2Color"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Kti-hs-oWv"> <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Kti-hs-oWv">
<rect key="frame" x="172" y="620" width="70" height="49"/> <rect key="frame" x="172" y="620" width="70" height="49"/>
<constraints> <constraints>
<constraint firstAttribute="height" constant="49" id="7G1-ut-7KK"/> <constraint firstAttribute="height" constant="49" id="7G1-ut-7KK"/>
@ -700,7 +722,8 @@
</connections> </connections>
</button> </button>
</subviews> </subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/> <viewLayoutGuide key="safeArea" id="jlC-eJ-vHy"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints> <constraints>
<constraint firstItem="mCk-Iy-qub" firstAttribute="leading" secondItem="jlC-eJ-vHy" secondAttribute="leading" constant="20" id="58F-y0-DKl"/> <constraint firstItem="mCk-Iy-qub" firstAttribute="leading" secondItem="jlC-eJ-vHy" secondAttribute="leading" constant="20" id="58F-y0-DKl"/>
<constraint firstItem="06w-ih-1vw" firstAttribute="leading" secondItem="jlC-eJ-vHy" secondAttribute="leading" priority="250" constant="249" id="5hm-3u-gqf"/> <constraint firstItem="06w-ih-1vw" firstAttribute="leading" secondItem="jlC-eJ-vHy" secondAttribute="leading" priority="250" constant="249" id="5hm-3u-gqf"/>
@ -729,7 +752,6 @@
<constraint firstItem="BEY-nq-CrN" firstAttribute="top" secondItem="mCk-Iy-qub" secondAttribute="bottom" constant="26" id="ob2-RM-fjc"/> <constraint firstItem="BEY-nq-CrN" firstAttribute="top" secondItem="mCk-Iy-qub" secondAttribute="bottom" constant="26" id="ob2-RM-fjc"/>
<constraint firstItem="8Pq-pM-WWH" firstAttribute="top" secondItem="TpC-jA-EZ0" secondAttribute="bottom" constant="8" id="r1c-df-j7W"/> <constraint firstItem="8Pq-pM-WWH" firstAttribute="top" secondItem="TpC-jA-EZ0" secondAttribute="bottom" constant="8" id="r1c-df-j7W"/>
</constraints> </constraints>
<viewLayoutGuide key="safeArea" id="jlC-eJ-vHy"/>
</view> </view>
<navigationItem key="navigationItem" title="Send Funds" id="BMZ-tq-ThY"/> <navigationItem key="navigationItem" title="Send Funds" id="BMZ-tq-ThY"/>
<connections> <connections>
@ -762,7 +784,7 @@
<rect key="frame" x="20" y="88" width="374" height="774"/> <rect key="frame" x="20" y="88" width="374" height="774"/>
<subviews> <subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="center" lineBreakMode="characterWrap" numberOfLines="0" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="X0E-Ba-xxX"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="center" lineBreakMode="characterWrap" numberOfLines="0" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="X0E-Ba-xxX">
<rect key="frame" x="166" y="0.0" width="42" height="387"/> <rect key="frame" x="166.5" y="0.0" width="41.5" height="387"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/> <fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/> <nil key="textColor"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
@ -780,14 +802,14 @@
</constraints> </constraints>
</stackView> </stackView>
</subviews> </subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/> <viewLayoutGuide key="safeArea" id="jVy-LO-XKe"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints> <constraints>
<constraint firstAttribute="trailingMargin" secondItem="TGE-Nt-Y1I" secondAttribute="trailing" id="MdM-tW-ZXD"/> <constraint firstAttribute="trailingMargin" secondItem="TGE-Nt-Y1I" secondAttribute="trailing" id="MdM-tW-ZXD"/>
<constraint firstItem="jVy-LO-XKe" firstAttribute="bottom" secondItem="TGE-Nt-Y1I" secondAttribute="bottom" id="aeY-PS-JRQ"/> <constraint firstItem="jVy-LO-XKe" firstAttribute="bottom" secondItem="TGE-Nt-Y1I" secondAttribute="bottom" id="aeY-PS-JRQ"/>
<constraint firstItem="TGE-Nt-Y1I" firstAttribute="top" secondItem="5WU-Pu-1e9" secondAttribute="topMargin" id="dp4-xM-chZ"/> <constraint firstItem="TGE-Nt-Y1I" firstAttribute="top" secondItem="5WU-Pu-1e9" secondAttribute="topMargin" id="dp4-xM-chZ"/>
<constraint firstItem="TGE-Nt-Y1I" firstAttribute="leading" secondItem="5WU-Pu-1e9" secondAttribute="leadingMargin" id="p2L-c4-Chf"/> <constraint firstItem="TGE-Nt-Y1I" firstAttribute="leading" secondItem="5WU-Pu-1e9" secondAttribute="leadingMargin" id="p2L-c4-Chf"/>
</constraints> </constraints>
<viewLayoutGuide key="safeArea" id="jVy-LO-XKe"/>
</view> </view>
<navigationItem key="navigationItem" title="Get Address" largeTitleDisplayMode="always" id="Uvy-EM-bSo"/> <navigationItem key="navigationItem" title="Get Address" largeTitleDisplayMode="always" id="Uvy-EM-bSo"/>
<connections> <connections>
@ -799,6 +821,81 @@
</objects> </objects>
<point key="canvasLocation" x="2317" y="-1829"/> <point key="canvasLocation" x="2317" y="-1829"/>
</scene> </scene>
<!--Sapling Parameters View Controller-->
<scene sceneID="A7L-pt-VGf">
<objects>
<viewController id="Nsd-EZ-dgy" customClass="SaplingParametersViewController" customModule="ZcashLightClientSample" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="cqf-Dv-Buo">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="equalCentering" translatesAutoresizingMaskIntoConstraints="NO" id="mvO-Hh-h9H">
<rect key="frame" x="0.0" y="88" width="414" height="774"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Spend Parameter path:" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="d2f-dy-eY1">
<rect key="frame" x="0.0" y="0.0" width="414" height="23"/>
<fontDescription key="fontDescription" type="system" pointSize="19"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="path" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="leL-2C-dpj">
<rect key="frame" x="0.0" y="151" width="414" height="20.5"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Output Parameter path:" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ou4-Wo-H6t">
<rect key="frame" x="0.0" y="299" width="414" height="23"/>
<fontDescription key="fontDescription" type="system" pointSize="19"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="path" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="0HT-EL-sbN">
<rect key="frame" x="0.0" y="450" width="414" height="20.5"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="zSx-wA-Nuf">
<rect key="frame" x="0.0" y="594.5" width="414" height="30"/>
<state key="normal" title="Download"/>
<connections>
<action selector="download:" destination="Nsd-EZ-dgy" eventType="touchUpInside" id="KRw-e7-LtM"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="5ix-Pz-cnR">
<rect key="frame" x="0.0" y="744" width="414" height="30"/>
<state key="normal" title="Delete">
<color key="titleColor" systemColor="systemRedColor"/>
</state>
<connections>
<action selector="deleteFiles:" destination="Nsd-EZ-dgy" eventType="touchUpInside" id="QEQ-ou-Z3F"/>
</connections>
</button>
</subviews>
</stackView>
</subviews>
<viewLayoutGuide key="safeArea" id="37y-Pc-bYm"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstItem="37y-Pc-bYm" firstAttribute="bottom" secondItem="mvO-Hh-h9H" secondAttribute="bottom" id="T9S-l6-MNr"/>
<constraint firstItem="mvO-Hh-h9H" firstAttribute="leading" secondItem="37y-Pc-bYm" secondAttribute="leading" id="aF3-Vr-xzJ"/>
<constraint firstItem="37y-Pc-bYm" firstAttribute="trailing" secondItem="mvO-Hh-h9H" secondAttribute="trailing" id="k8T-hf-CJM"/>
<constraint firstItem="mvO-Hh-h9H" firstAttribute="top" secondItem="37y-Pc-bYm" secondAttribute="top" id="yZ2-S9-t24"/>
</constraints>
</view>
<navigationItem key="navigationItem" id="zEi-Kz-Ejw"/>
<connections>
<outlet property="deleteButton" destination="5ix-Pz-cnR" id="on6-U7-Tb1"/>
<outlet property="downloadButton" destination="zSx-wA-Nuf" id="wbq-LF-0W8"/>
<outlet property="outputPath" destination="0HT-EL-sbN" id="Pb1-d0-pkT"/>
<outlet property="spendPath" destination="leL-2C-dpj" id="rch-Co-nFz"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="vQP-aT-BeF" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="889" y="983"/>
</scene>
<!--Sync Blocks--> <!--Sync Blocks-->
<scene sceneID="mqi-cb-0xH"> <scene sceneID="mqi-cb-0xH">
<objects> <objects>
@ -812,7 +909,7 @@
<subviews> <subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="TdB-z4-xLJ"> <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="TdB-z4-xLJ">
<rect key="frame" x="0.0" y="0.0" width="398" height="128"/> <rect key="frame" x="0.0" y="0.0" width="398" height="128"/>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/> <color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints> <constraints>
<constraint firstAttribute="height" constant="128" id="enj-s3-Sct"/> <constraint firstAttribute="height" constant="128" id="enj-s3-Sct"/>
</constraints> </constraints>
@ -821,15 +918,15 @@
<rect key="frame" x="0.0" y="152" width="398" height="112.5"/> <rect key="frame" x="0.0" y="152" width="398" height="112.5"/>
<subviews> <subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="252" text="Status:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="3t3-Tr-UlI"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="252" text="Status:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="3t3-Tr-UlI">
<rect key="frame" x="0.0" y="37" width="97" height="38.5"/> <rect key="frame" x="0.0" y="37" width="95.5" height="38.5"/>
<fontDescription key="fontDescription" type="system" pointSize="32"/> <fontDescription key="fontDescription" type="system" pointSize="32"/>
<nil key="textColor"/> <nil key="textColor"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Jj9-7r-s2Y"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Jj9-7r-s2Y">
<rect key="frame" x="110" y="42.5" width="288" height="27.5"/> <rect key="frame" x="108.5" y="42.5" width="289.5" height="27.5"/>
<fontDescription key="fontDescription" type="italicSystem" pointSize="23"/> <fontDescription key="fontDescription" type="italicSystem" pointSize="23"/>
<color key="textColor" cocoaTouchSystemColor="scrollViewTexturedBackgroundColor"/> <color key="textColor" systemColor="scrollViewTexturedBackgroundColor"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
</subviews> </subviews>
@ -852,7 +949,7 @@
<nil key="textColor"/> <nil key="textColor"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="G5M-gm-1ux"> <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="G5M-gm-1ux">
<rect key="frame" x="0.0" y="444.5" width="398" height="45"/> <rect key="frame" x="0.0" y="444.5" width="398" height="45"/>
<fontDescription key="fontDescription" type="system" pointSize="27"/> <fontDescription key="fontDescription" type="system" pointSize="27"/>
<state key="normal" title="Start"/> <state key="normal" title="Start"/>
@ -862,7 +959,7 @@
</button> </button>
<view contentMode="scaleToFill" verticalHuggingPriority="750" verticalCompressionResistancePriority="737" translatesAutoresizingMaskIntoConstraints="NO" id="fB9-xh-4fl" userLabel="Trailing View"> <view contentMode="scaleToFill" verticalHuggingPriority="750" verticalCompressionResistancePriority="737" translatesAutoresizingMaskIntoConstraints="NO" id="fB9-xh-4fl" userLabel="Trailing View">
<rect key="frame" x="0.0" y="513.5" width="398" height="260.5"/> <rect key="frame" x="0.0" y="513.5" width="398" height="260.5"/>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/> <color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints> <constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" priority="750" constant="200" id="Bpt-XM-IZA"/> <constraint firstAttribute="height" relation="greaterThanOrEqual" priority="750" constant="200" id="Bpt-XM-IZA"/>
</constraints> </constraints>
@ -883,14 +980,14 @@
</constraints> </constraints>
</stackView> </stackView>
</subviews> </subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/> <viewLayoutGuide key="safeArea" id="K0A-3E-XB9"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints> <constraints>
<constraint firstItem="K0A-3E-XB9" firstAttribute="bottom" secondItem="dp8-P5-xEk" secondAttribute="bottom" id="Eqf-9d-bM8"/> <constraint firstItem="K0A-3E-XB9" firstAttribute="bottom" secondItem="dp8-P5-xEk" secondAttribute="bottom" id="Eqf-9d-bM8"/>
<constraint firstItem="dp8-P5-xEk" firstAttribute="leading" secondItem="K0A-3E-XB9" secondAttribute="leading" constant="8" id="Mkg-Rm-u7R"/> <constraint firstItem="dp8-P5-xEk" firstAttribute="leading" secondItem="K0A-3E-XB9" secondAttribute="leading" constant="8" id="Mkg-Rm-u7R"/>
<constraint firstItem="K0A-3E-XB9" firstAttribute="trailing" secondItem="dp8-P5-xEk" secondAttribute="trailing" constant="8" id="VM8-iL-n3m"/> <constraint firstItem="K0A-3E-XB9" firstAttribute="trailing" secondItem="dp8-P5-xEk" secondAttribute="trailing" constant="8" id="VM8-iL-n3m"/>
<constraint firstItem="dp8-P5-xEk" firstAttribute="top" secondItem="K0A-3E-XB9" secondAttribute="top" id="iOn-jh-hj0"/> <constraint firstItem="dp8-P5-xEk" firstAttribute="top" secondItem="K0A-3E-XB9" secondAttribute="top" id="iOn-jh-hj0"/>
</constraints> </constraints>
<viewLayoutGuide key="safeArea" id="K0A-3E-XB9"/>
</view> </view>
<navigationItem key="navigationItem" title="Sync Blocks" id="Acf-Sj-aA6"/> <navigationItem key="navigationItem" title="Sync Blocks" id="Acf-Sj-aA6"/>
<connections> <connections>
@ -928,7 +1025,8 @@
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
</subviews> </subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/> <viewLayoutGuide key="safeArea" id="fg7-R9-l4X"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints> <constraints>
<constraint firstItem="0Hj-Jx-J3i" firstAttribute="centerY" secondItem="tie-BL-1Ip" secondAttribute="centerY" id="6HS-U8-JHx"/> <constraint firstItem="0Hj-Jx-J3i" firstAttribute="centerY" secondItem="tie-BL-1Ip" secondAttribute="centerY" id="6HS-U8-JHx"/>
<constraint firstItem="WKf-j9-SjN" firstAttribute="centerX" secondItem="0Hj-Jx-J3i" secondAttribute="centerX" id="UOE-Jf-r6N"/> <constraint firstItem="WKf-j9-SjN" firstAttribute="centerX" secondItem="0Hj-Jx-J3i" secondAttribute="centerX" id="UOE-Jf-r6N"/>
@ -937,7 +1035,6 @@
<constraint firstItem="0Hj-Jx-J3i" firstAttribute="leading" secondItem="fg7-R9-l4X" secondAttribute="leading" id="kAe-yR-Nr8"/> <constraint firstItem="0Hj-Jx-J3i" firstAttribute="leading" secondItem="fg7-R9-l4X" secondAttribute="leading" id="kAe-yR-Nr8"/>
<constraint firstItem="0Hj-Jx-J3i" firstAttribute="centerX" secondItem="tie-BL-1Ip" secondAttribute="centerX" id="ruf-Mw-pLN"/> <constraint firstItem="0Hj-Jx-J3i" firstAttribute="centerX" secondItem="tie-BL-1Ip" secondAttribute="centerX" id="ruf-Mw-pLN"/>
</constraints> </constraints>
<viewLayoutGuide key="safeArea" id="fg7-R9-l4X"/>
</view> </view>
<connections> <connections>
<outlet property="blockHeightLabel" destination="0Hj-Jx-J3i" id="P2U-am-RMw"/> <outlet property="blockHeightLabel" destination="0Hj-Jx-J3i" id="P2U-am-RMw"/>
@ -1011,14 +1108,14 @@
</subviews> </subviews>
</stackView> </stackView>
</subviews> </subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/> <viewLayoutGuide key="safeArea" id="5sH-IW-gs5"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints> <constraints>
<constraint firstItem="5sH-IW-gs5" firstAttribute="bottom" secondItem="Cep-qY-yP1" secondAttribute="bottom" id="1wA-22-MWW"/> <constraint firstItem="5sH-IW-gs5" firstAttribute="bottom" secondItem="Cep-qY-yP1" secondAttribute="bottom" id="1wA-22-MWW"/>
<constraint firstItem="5sH-IW-gs5" firstAttribute="trailing" secondItem="Cep-qY-yP1" secondAttribute="trailing" id="5rX-6x-hht"/> <constraint firstItem="5sH-IW-gs5" firstAttribute="trailing" secondItem="Cep-qY-yP1" secondAttribute="trailing" id="5rX-6x-hht"/>
<constraint firstItem="Cep-qY-yP1" firstAttribute="leading" secondItem="5sH-IW-gs5" secondAttribute="leading" id="6mk-cr-RT1"/> <constraint firstItem="Cep-qY-yP1" firstAttribute="leading" secondItem="5sH-IW-gs5" secondAttribute="leading" id="6mk-cr-RT1"/>
<constraint firstItem="Cep-qY-yP1" firstAttribute="top" secondItem="5sH-IW-gs5" secondAttribute="top" id="MhI-qH-RAz"/> <constraint firstItem="Cep-qY-yP1" firstAttribute="top" secondItem="5sH-IW-gs5" secondAttribute="top" id="MhI-qH-RAz"/>
</constraints> </constraints>
<viewLayoutGuide key="safeArea" id="5sH-IW-gs5"/>
</view> </view>
<navigationItem key="navigationItem" id="HmT-B1-R92"/> <navigationItem key="navigationItem" id="HmT-B1-R92"/>
<connections> <connections>
@ -1035,4 +1132,30 @@
<segue reference="oxP-eV-1Z2"/> <segue reference="oxP-eV-1Z2"/>
<segue reference="snP-Bc-obL"/> <segue reference="snP-Bc-obL"/>
</inferredMetricsTieBreakers> </inferredMetricsTieBreakers>
<resources>
<systemColor name="labelColor">
<color white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
<systemColor name="scrollViewTexturedBackgroundColor">
<color red="0.43529411764705878" green="0.44313725490196082" blue="0.47450980392156861" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
<systemColor name="scrollViewTexturedBackgroundColor">
<color red="0.43529411764705878" green="0.44313725490196082" blue="0.47450980392156861" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
<systemColor name="scrollViewTexturedBackgroundColor">
<color red="0.43529411764705878" green="0.44313725490196082" blue="0.47450980392156861" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
<systemColor name="scrollViewTexturedBackgroundColor">
<color red="0.43529411764705878" green="0.44313725490196082" blue="0.47450980392156861" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
<systemColor name="systemGray2Color">
<color red="0.68235294117647061" green="0.68235294117647061" blue="0.69803921568627447" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
<systemColor name="systemRedColor">
<color red="1" green="0.23137254901960785" blue="0.18823529411764706" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
</resources>
</document> </document>

View File

@ -10,7 +10,7 @@ import Foundation
import ZcashLightClientKit import ZcashLightClientKit
import MnemonicSwift import MnemonicSwift
struct DemoAppConfig { struct DemoAppConfig {
static var host = ZcashSDK.isMainnet ? "lightwalletd.electriccoin.co" : "localhost" static var host = ZcashSDK.isMainnet ? "localhost" : "localhost"
static var port: Int = 9067 static var port: Int = 9067
static var birthdayHeight: BlockHeight = ZcashSDK.isMainnet ? 663174 : 620_000 static var birthdayHeight: BlockHeight = ZcashSDK.isMainnet ? 663174 : 620_000
static var network = ZcashSDK.isMainnet ? ZcashNetwork.mainNet : ZcashNetwork.testNet static var network = ZcashSDK.isMainnet ? ZcashNetwork.mainNet : ZcashNetwork.testNet

View File

@ -0,0 +1,98 @@
//
// SaplingParametersViewController.swift
// ZcashLightClientSample
//
// Created by Francisco Gindre on 10/7/20.
// Copyright © 2020 Electric Coin Company. All rights reserved.
//
import UIKit
import ZcashLightClientKit
class SaplingParametersViewController: UIViewController {
@IBOutlet weak var outputPath: UILabel!
@IBOutlet weak var spendPath: UILabel!
@IBOutlet weak var downloadButton: UIButton!
@IBOutlet weak var deleteButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
let spendParamPath = try! __spendParamsURL().path
let outputParamPath = try! __outputParamsURL().path
// Do any additional setup after loading the view.
self.spendPath.text = spendParamPath
self.outputPath.text = outputParamPath
self.updateColor()
self.spendPath.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(spendPathTapped(_:))))
self.outputPath.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(outputPathTapped(_:))))
self.outputPath.isUserInteractionEnabled = true
self.spendPath.isUserInteractionEnabled = true
self.updateButtons()
}
func updateButtons() {
let spendParamPath = try! __spendParamsURL().path
let outputParamPath = try! __outputParamsURL().path
self.downloadButton.isHidden = fileExists(outputParamPath) && fileExists(spendParamPath)
self.deleteButton.isHidden = !(fileExists(outputParamPath) || fileExists(spendParamPath))
}
func updateColor() {
let spendParamPath = try! __spendParamsURL().path
let outputParamPath = try! __outputParamsURL().path
self.spendPath.textColor = fileExists(spendParamPath) ? UIColor.green : UIColor.red
self.outputPath.textColor = fileExists(outputParamPath) ? UIColor.green : UIColor.red
}
@objc func spendPathTapped(_ gesture: UIGestureRecognizer) {
loggerProxy.event("copied to clipboard:\(self.spendPath.text ?? "")")
UIPasteboard.general.string = self.spendPath.text
let alert = UIAlertController(title: "", message: "Path Copied to clipboard", preferredStyle: UIAlertController.Style.alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertAction.Style.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
@objc func outputPathTapped(_ gesture: UIGestureRecognizer) {
loggerProxy.event("copied to clipboard:\(self.outputPath.text ?? "")")
UIPasteboard.general.string = self.outputPath.text
let alert = UIAlertController(title: "", message: "Path Copied to clipboard", preferredStyle: UIAlertController.Style.alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertAction.Style.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
@IBAction func download(_ sender: Any) {
let outputParameter = try! __outputParamsURL()
let spendParameter = try! __spendParamsURL()
SaplingParameterDownloader.downloadParamsIfnotPresent(spendURL: spendParameter, outputURL: outputParameter) { (result) in
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
switch result {
case .success(let urls):
self.spendPath.text = urls.spend.path
self.outputPath.text = urls.output.path
self.updateColor()
self.updateButtons()
case .failure(let error):
self.showError(error)
}
}
}
}
func fileExists(_ path: String) -> Bool {
(try? FileManager.default.attributesOfItem(atPath: path)) != nil
}
func showError(_ error: Error) {
let alert = UIAlertController(title: "Download Failed", message: "\(error)", preferredStyle: UIAlertController.Style.alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertAction.Style.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
@IBAction func deleteFiles(_ sender: Any) {
let spendParamURL = try! __spendParamsURL()
let outputParamURL = try! __outputParamsURL()
try? FileManager.default.removeItem(at: spendParamURL)
try? FileManager.default.removeItem(at: outputParamURL)
self.updateColor()
self.updateButtons()
}
}

View File

@ -1,6 +1,6 @@
Pod::Spec.new do |s| Pod::Spec.new do |s|
s.name = 'ZcashLightClientKit' s.name = 'ZcashLightClientKit'
s.version = '0.6.6' s.version = '0.7.0'
s.summary = 'Zcash Light Client wallet SDK for iOS' s.summary = 'Zcash Light Client wallet SDK for iOS'
s.description = <<-DESC s.description = <<-DESC

View File

@ -121,4 +121,18 @@ public protocol ConfirmedTransactionEntity: MinedTransactionEntity, SignedTransa
expiration height for this transaction expiration height for this transaction
*/ */
var expiryHeight: BlockHeight? { get set } var expiryHeight: BlockHeight? { get set }
}
public extension ConfirmedTransactionEntity {
var isOutbound: Bool {
self.toAddress != nil
}
var isInbound: Bool {
self.toAddress == nil
}
var blockTimeInMilliseconds: Double {
self.blockTimeInSeconds * 1000
}
} }

View File

@ -17,6 +17,7 @@ public enum InitializerError: Error {
case dataDbInitFailed case dataDbInitFailed
case accountInitFailed case accountInitFailed
case falseStart case falseStart
case invalidViewingKey(key: String)
} }
/** /**
@ -50,13 +51,15 @@ public struct LightWalletEndpoint {
public class Initializer { public class Initializer {
private(set) var rustBackend: ZcashRustBackendWelding.Type private(set) var rustBackend: ZcashRustBackendWelding.Type
private(set) var alias: String
private(set) var endpoint: LightWalletEndpoint
private var lowerBoundHeight: BlockHeight private var lowerBoundHeight: BlockHeight
private(set) var cacheDbURL: URL private(set) var cacheDbURL: URL
private(set) var dataDbURL: URL private(set) var dataDbURL: URL
private(set) var pendingDbURL: URL private(set) var pendingDbURL: URL
private(set) var spendParamsURL: URL private(set) var spendParamsURL: URL
private(set) var outputParamsURL: URL private(set) var outputParamsURL: URL
private var walletBirthday: WalletBirthday?
private(set) var lightWalletService: LightWalletService private(set) var lightWalletService: LightWalletService
private(set) var transactionRepository: TransactionRepository private(set) var transactionRepository: TransactionRepository
private(set) var downloader: CompactBlockDownloader private(set) var downloader: CompactBlockDownloader
@ -78,6 +81,7 @@ public class Initializer {
endpoint: LightWalletEndpoint, endpoint: LightWalletEndpoint,
spendParamsURL: URL, spendParamsURL: URL,
outputParamsURL: URL, outputParamsURL: URL,
alias: String = "",
loggerProxy: Logger? = nil) { loggerProxy: Logger? = nil) {
let storage = CompactBlockStorage(url: cacheDbURL, readonly: false) let storage = CompactBlockStorage(url: cacheDbURL, readonly: false)
@ -90,11 +94,13 @@ public class Initializer {
cacheDbURL: cacheDbURL, cacheDbURL: cacheDbURL,
dataDbURL: dataDbURL, dataDbURL: dataDbURL,
pendingDbURL: pendingDbURL, pendingDbURL: pendingDbURL,
endpoint: endpoint,
service: lwdService, service: lwdService,
repository: TransactionRepositoryBuilder.build(dataDbURL: dataDbURL), repository: TransactionRepositoryBuilder.build(dataDbURL: dataDbURL),
downloader: CompactBlockDownloader(service: lwdService, storage: storage), downloader: CompactBlockDownloader(service: lwdService, storage: storage),
spendParamsURL: spendParamsURL, spendParamsURL: spendParamsURL,
outputParamsURL: outputParamsURL, outputParamsURL: outputParamsURL,
alias: alias,
loggerProxy: loggerProxy loggerProxy: loggerProxy
) )
} }
@ -107,12 +113,15 @@ public class Initializer {
cacheDbURL: URL, cacheDbURL: URL,
dataDbURL: URL, dataDbURL: URL,
pendingDbURL: URL, pendingDbURL: URL,
endpoint: LightWalletEndpoint,
service: LightWalletService, service: LightWalletService,
repository: TransactionRepository, repository: TransactionRepository,
downloader: CompactBlockDownloader, downloader: CompactBlockDownloader,
spendParamsURL: URL, spendParamsURL: URL,
outputParamsURL: URL, outputParamsURL: URL,
alias: String = "",
loggerProxy: Logger? = nil loggerProxy: Logger? = nil
) { ) {
logger = loggerProxy logger = loggerProxy
self.rustBackend = rustBackend self.rustBackend = rustBackend
@ -120,8 +129,10 @@ public class Initializer {
self.cacheDbURL = cacheDbURL self.cacheDbURL = cacheDbURL
self.dataDbURL = dataDbURL self.dataDbURL = dataDbURL
self.pendingDbURL = pendingDbURL self.pendingDbURL = pendingDbURL
self.endpoint = endpoint
self.spendParamsURL = spendParamsURL self.spendParamsURL = spendParamsURL
self.outputParamsURL = outputParamsURL self.outputParamsURL = outputParamsURL
self.alias = alias
self.lightWalletService = service self.lightWalletService = service
self.transactionRepository = repository self.transactionRepository = repository
self.downloader = downloader self.downloader = downloader
@ -139,12 +150,18 @@ public class Initializer {
do not already exist). These files can be given a prefix for scenarios where multiple wallets do not already exist). These files can be given a prefix for scenarios where multiple wallets
operate in one app--for instance, when sweeping funds from another wallet seed. operate in one app--for instance, when sweeping funds from another wallet seed.
- Parameters: - Parameters:
- seedProvider: the seed to use for initializing this wallet. - viewingKeys: Extended Full Viewing Keys to initialize the DBs with
- walletBirthdayHeight: the height corresponding to when the wallet seed was created. If null, this signals that the wallet is being born.
- numberOfAccounts: the number of accounts to create from this seed.
*/ */
public func initialize(seedProvider: SeedProvider, walletBirthdayHeight: BlockHeight, numberOfAccounts: Int = 1) throws -> [String]? { public func initialize(viewingKeys: [String], walletBirthday: BlockHeight) throws {
let derivationTool = DerivationTool()
for vk in viewingKeys {
do {
try derivationTool.validateViewingKey(viewingKey: vk)
} catch {
throw InitializerError.invalidViewingKey(key: vk)
}
}
do { do {
try rustBackend.initDataDb(dbData: dataDbURL) try rustBackend.initDataDb(dbData: dataDbURL)
@ -154,10 +171,7 @@ public class Initializer {
throw InitializerError.dataDbInitFailed throw InitializerError.dataDbInitFailed
} }
self.walletBirthday = WalletBirthday.birthday(with: walletBirthdayHeight) let birthday = WalletBirthday.birthday(with: walletBirthday)
guard let birthday = self.walletBirthday else {
throw InitializerError.falseStart
}
do { do {
try rustBackend.initBlocksTable(dbData: dataDbURL, height: Int32(birthday.height), hash: birthday.hash, time: birthday.time, saplingTree: birthday.tree) try rustBackend.initBlocksTable(dbData: dataDbURL, height: Int32(birthday.height), hash: birthday.hash, time: birthday.time, saplingTree: birthday.tree)
@ -167,17 +181,24 @@ public class Initializer {
throw InitializerError.dataDbInitFailed throw InitializerError.dataDbInitFailed
} }
let lastDownloaded = (try? downloader.storage.latestHeight()) ?? self.walletBirthday?.height ?? ZcashSDK.SAPLING_ACTIVATION_HEIGHT let lastDownloaded = (try? downloader.storage.latestHeight()) ?? birthday.height
// resume from last downloaded block // resume from last downloaded block
lowerBoundHeight = max(birthday.height, lastDownloaded) lowerBoundHeight = max(birthday.height, lastDownloaded)
self.processor = CompactBlockProcessorBuilder.buildProcessor(configuration: CompactBlockProcessor.Configuration(cacheDb: cacheDbURL, dataDb: dataDbURL, walletBirthday: walletBirthday?.height ?? self.lowerBoundHeight), downloader: self.downloader, transactionRepository: transactionRepository, backend: rustBackend) let config = CompactBlockProcessor.Configuration(cacheDb: cacheDbURL,
dataDb: dataDbURL,
walletBirthday: birthday.height)
guard let accounts = rustBackend.initAccountsTable(dbData: dataDbURL, seed: seedProvider.seed(), accounts: Int32(numberOfAccounts)) else { self.processor = CompactBlockProcessorBuilder.buildProcessor(configuration: config,
downloader: self.downloader,
transactionRepository: transactionRepository,
backend: rustBackend)
guard try rustBackend.initAccountsTable(dbData: dataDbURL, exfvks: viewingKeys) else {
throw rustBackend.lastError() ?? InitializerError.accountInitFailed throw rustBackend.lastError() ?? InitializerError.accountInitFailed
} }
return accounts
} }
/** /**
@ -225,11 +246,69 @@ public class Initializer {
public func blockProcessor() -> CompactBlockProcessor? { public func blockProcessor() -> CompactBlockProcessor? {
self.processor self.processor
} }
func isSpendParameterPresent() -> Bool {
FileManager.default.isReadableFile(atPath: self.spendParamsURL.path)
}
func isOutputParameterPresent() -> Bool {
FileManager.default.isReadableFile(atPath: self.outputParamsURL.path)
}
func downloadParametersIfNeeded(result: @escaping (Result<Bool,Error>) -> Void) {
let spendParameterPresent = isSpendParameterPresent()
let outputParameterPresent = isOutputParameterPresent()
if spendParameterPresent && outputParameterPresent {
result(.success(true))
return
}
let outputURL = self.outputParamsURL
let spendURL = self.spendParamsURL
if !outputParameterPresent {
SaplingParameterDownloader.downloadOutputParameter(outputURL) { outputResult in
switch outputResult {
case .failure(let e):
result(.failure(e))
case .success:
guard !spendParameterPresent else {
result(.success(false))
return
}
SaplingParameterDownloader.downloadSpendParameter(spendURL) { (spendResult) in
switch spendResult {
case .failure(let e):
result(.failure(e))
case .success:
result(.success(false))
}
}
}
}
} else if !spendParameterPresent {
SaplingParameterDownloader.downloadSpendParameter(spendURL) { (spendResult) in
switch spendResult {
case .failure(let e):
result(.failure(e))
case .success:
result(.success(false))
}
}
}
}
} }
class CompactBlockProcessorBuilder { class CompactBlockProcessorBuilder {
static func buildProcessor(configuration: CompactBlockProcessor.Configuration, downloader: CompactBlockDownloader, transactionRepository: TransactionRepository, backend: ZcashRustBackendWelding.Type) -> CompactBlockProcessor { static func buildProcessor(configuration: CompactBlockProcessor.Configuration,
return CompactBlockProcessor(downloader: downloader, backend: backend, config: configuration, repository: transactionRepository) downloader: CompactBlockDownloader,
transactionRepository: TransactionRepository,
backend: ZcashRustBackendWelding.Type) -> CompactBlockProcessor {
return CompactBlockProcessor(downloader: downloader,
backend: backend,
config: configuration,
repository: transactionRepository)
} }
} }

View File

@ -1,15 +0,0 @@
//
// SeedProvider.swift
// ZcashLightClientKit
//
// Created by Francisco Gindre on 13/09/2019.
// Copyright © 2019 Electric Coin Company. All rights reserved.
//
import Foundation
/**
Describes an interface for an entity that provides seed bytes
*/
public protocol SeedProvider {
func seed() -> [UInt8]
}

View File

@ -90,6 +90,28 @@ class ZcashRustBackend: ZcashRustBackendWelding {
return extsks return extsks
} }
static func initAccountsTable(dbData: URL, exfvks: [String]) throws -> Bool {
let dbData = dbData.osStr()
let viewingKeys = exfvks.map { UnsafePointer(strdup($0)) }
guard exfvks.count > 0 else {
throw RustWeldingError.malformedStringInput
}
let res = zcashlc_init_accounts_table_with_keys(dbData.0, dbData.1, viewingKeys, UInt(viewingKeys.count));
viewingKeys.compactMap({UnsafeMutablePointer(mutating: $0)}).forEach({ free($0) })
guard res else {
if let error = lastError() {
throw error
}
return false
}
return res
}
static func initBlocksTable(dbData: URL, height: Int32, hash: String, time: UInt32, saplingTree: String) throws { static func initBlocksTable(dbData: URL, height: Int32, hash: String, time: UInt32, saplingTree: String) throws {
let dbData = dbData.osStr() let dbData = dbData.osStr()
@ -208,9 +230,9 @@ class ZcashRustBackend: ZcashRustBackendWelding {
return derived return derived
} }
static func deriveExtendedFullViewingKeys(seed: String, accounts: Int32) throws -> [String]? { static func deriveExtendedFullViewingKeys(seed: [UInt8], accounts: Int32) throws -> [String]? {
var capacity = UInt(0); var capacity = UInt(0);
guard let extsksCStr = zcashlc_derive_extended_full_viewing_keys(seed, UInt(seed.lengthOfBytes(using: .utf8)), accounts, &capacity) else { guard let extsksCStr = zcashlc_derive_extended_full_viewing_keys(seed, UInt(seed.count), accounts, &capacity) else {
if let error = lastError() { if let error = lastError() {
throw error throw error
} }
@ -225,9 +247,9 @@ class ZcashRustBackend: ZcashRustBackendWelding {
return extsks return extsks
} }
static func deriveExtendedSpendingKeys(seed: String, accounts: Int32) throws -> [String]? { static func deriveExtendedSpendingKeys(seed: [UInt8], accounts: Int32) throws -> [String]? {
var capacity = UInt(0); var capacity = UInt(0);
guard let extsksCStr = zcashlc_derive_extended_spending_keys(seed, UInt(seed.lengthOfBytes(using: .utf8)), accounts, &capacity) else { guard let extsksCStr = zcashlc_derive_extended_spending_keys(seed, UInt(seed.count), accounts, &capacity) else {
if let error = lastError() { if let error = lastError() {
throw error throw error
} }
@ -242,6 +264,52 @@ class ZcashRustBackend: ZcashRustBackendWelding {
return extsks return extsks
} }
static func deriveShieldedAddressFromSeed(seed: [UInt8], accountIndex: Int32) throws -> String? {
guard let zaddrCStr = zcashlc_derive_shielded_address_from_seed(seed, UInt(seed.count), accountIndex) else {
if let error = lastError() {
throw error
}
return nil
}
let zAddr = String(validatingUTF8: zaddrCStr)
zcashlc_string_free(zaddrCStr)
return zAddr
}
static func deriveShieldedAddressFromViewingKey(_ extfvk: String) throws -> String? {
guard !extfvk.containsCStringNullBytesBeforeStringEnding() else {
throw RustWeldingError.malformedStringInput
}
guard let zaddrCStr = zcashlc_derive_shielded_address_from_viewing_key([CChar](extfvk.utf8CString)) else {
if let error = lastError() {
throw error
}
return nil
}
let zAddr = String(validatingUTF8: zaddrCStr)
zcashlc_string_free(zaddrCStr)
return zAddr
}
static func deriveTransparentAddressFromSeed(seed: [UInt8]) throws -> String? {
guard let tAddrCStr = zcashlc_derive_transparent_address_from_seed(seed, UInt(seed.count)) else {
if let error = lastError() {
throw error
}
return nil
}
let tAddr = String(validatingUTF8: tAddrCStr)
return tAddr
}
static func consensusBranchIdFor(height: Int32) throws -> Int32 { static func consensusBranchIdFor(height: Int32) throws -> Int32 {
let branchId = zcashlc_branch_id_for_height(height) let branchId = zcashlc_branch_id_for_height(height)

View File

@ -49,7 +49,7 @@ public protocol ZcashRustBackendWelding {
static func isValidTransparentAddress(_ address: String) throws -> Bool static func isValidTransparentAddress(_ address: String) throws -> Bool
/** /**
initialize the blocks table from a given checkpoint (birthday) initialize the accounts table from a given seed and a number of accounts
- Parameters: - Parameters:
- dbData: location of the data db - dbData: location of the data db
- seed: byte array of the zip32 seed - seed: byte array of the zip32 seed
@ -57,6 +57,16 @@ public protocol ZcashRustBackendWelding {
*/ */
static func initAccountsTable(dbData: URL, seed: [UInt8], accounts: Int32) -> [String]? static func initAccountsTable(dbData: URL, seed: [UInt8], accounts: Int32) -> [String]?
/**
initialize the accounts table from a given seed and a number of accounts
- Parameters:
- dbData: location of the data db
- exfvks: byte array of the zip32 seed
- Returns: a boolean indicating if the database was initialized or an error
*/
static func initAccountsTable(dbData: URL, exfvks: [String]) throws -> Bool
/** /**
initialize the blocks table from a given checkpoint (birthday) initialize the blocks table from a given checkpoint (birthday)
- Parameters: - Parameters:
@ -190,7 +200,7 @@ public protocol ZcashRustBackendWelding {
- Returns: an array containing the derived keys - Returns: an array containing the derived keys
- Throws: RustBackendError if fatal error occurs - Throws: RustBackendError if fatal error occurs
*/ */
static func deriveExtendedFullViewingKeys(seed: String, accounts: Int32) throws -> [String]? static func deriveExtendedFullViewingKeys(seed: [UInt8], accounts: Int32) throws -> [String]?
/** /**
Derives a set of full viewing keys from a seed Derives a set of full viewing keys from a seed
@ -199,7 +209,32 @@ public protocol ZcashRustBackendWelding {
- Returns: an array containing the spending keys - Returns: an array containing the spending keys
- Throws: RustBackendError if fatal error occurs - Throws: RustBackendError if fatal error occurs
*/ */
static func deriveExtendedSpendingKeys(seed: String, accounts: Int32) throws -> [String]? static func deriveExtendedSpendingKeys(seed: [UInt8], accounts: Int32) throws -> [String]?
/**
Derives a shielded address from a seed
- Parameter seed: an array of bytes of the seed
- Parameter accountIndex: the index of the account you want the address for
- Returns: an optional String containing the Shielded address
- Throws: RustBackendError if fatal error occurs
*/
static func deriveShieldedAddressFromSeed(seed: [UInt8], accountIndex: Int32) throws -> String?
/**
Derives a shielded address from an Extended Full Viewing Key
- Parameter extfvk: a string containing the extended full viewing key
- Returns: an optional String containing the Shielded address
- Throws: RustBackendError if fatal error occurs
*/
static func deriveShieldedAddressFromViewingKey(_ extfvk: String) throws -> String?
/**
Derives a shielded address from an Extended Full Viewing Key
- Parameter seed: an array of bytes of the seed
- Returns: an optional String containing the transparent address
- Throws: RustBackendError if fatal error occurs
*/
static func deriveTransparentAddressFromSeed(seed: [UInt8]) throws -> String?
/** /**
Gets the consensus branch id for the given height Gets the consensus branch id for the given height

View File

@ -229,13 +229,20 @@ public extension WalletBirthday {
time: 1599946198, time: 1599946198,
tree: "01d9e6147caab719ae68cb20d976c78437634e2c999ef3a09c6ba35086d443703d00120001019d135be7b1db088c68bd76703ec2b45066bb1761619745362e61dcf55f644601d0c8f296479a73722c2e2a260ab7017b9a9e6d084b651289cfe6d3c7a00ff54e01f853ab39dbfc81e2aabefd231d3374ff794028168c725ad465e61205692fef4b014f13b6e4475cbd004b4d95aa8205ed7338224e13627ecbb19afd1937dcbc0818000185b4f1ddee3199cd1f7913b223c01c4623cd9d0e1b47df4e36aaca7717b6331f011d6c8ec914cc312ef0962d52240308b22a647f4cbd2d7c2fd420ad5fbcae5619011e43cbb05b8efc885531367e5f611fe7ce7514131be892cce3adad02e151f72b01f0d7e0d589c7e5f8fff0bdd5037aeb5d5d818d413262758c9915ded705e40f70000101d26ff60e77e23fb86a52da565c22d76f81df7f25d543ec0e58a0d692d4be2700000110b2bfd32a99e0b982a41a6dbaebf783bdb9d6af795f5f20056ce7317d15ce1101f1c57245fff8dbc2d3efe5a0953eafdedeb06e18a3ad4f1e4042ee76623f803200011323ddf890bfd7b94fc609b0d191982cb426b8bf4d900d04709a8b9cb1a27625" tree: "01d9e6147caab719ae68cb20d976c78437634e2c999ef3a09c6ba35086d443703d00120001019d135be7b1db088c68bd76703ec2b45066bb1761619745362e61dcf55f644601d0c8f296479a73722c2e2a260ab7017b9a9e6d084b651289cfe6d3c7a00ff54e01f853ab39dbfc81e2aabefd231d3374ff794028168c725ad465e61205692fef4b014f13b6e4475cbd004b4d95aa8205ed7338224e13627ecbb19afd1937dcbc0818000185b4f1ddee3199cd1f7913b223c01c4623cd9d0e1b47df4e36aaca7717b6331f011d6c8ec914cc312ef0962d52240308b22a647f4cbd2d7c2fd420ad5fbcae5619011e43cbb05b8efc885531367e5f611fe7ce7514131be892cce3adad02e151f72b01f0d7e0d589c7e5f8fff0bdd5037aeb5d5d818d413262758c9915ded705e40f70000101d26ff60e77e23fb86a52da565c22d76f81df7f25d543ec0e58a0d692d4be2700000110b2bfd32a99e0b982a41a6dbaebf783bdb9d6af795f5f20056ce7317d15ce1101f1c57245fff8dbc2d3efe5a0953eafdedeb06e18a3ad4f1e4042ee76623f803200011323ddf890bfd7b94fc609b0d191982cb426b8bf4d900d04709a8b9cb1a27625"
) )
default: case 980000 ..< 998500:
return WalletBirthday( return WalletBirthday(
height: 980000, height: 980000,
hash: "00000000005de60d31b653cdf1637f1bad62af844c6c51f38557a4e8bb74e2d7", hash: "00000000005de60d31b653cdf1637f1bad62af844c6c51f38557a4e8bb74e2d7",
time: 1600700163, time: 1600700163,
tree: "0184330bda72e9596256847a9597d7d9476aa3d69d9dad2149314751e708da206601cd8a1a61df1e80514cd2c0a7faac8b8d7ce27d6d96bb63cb7c61c1f33e7c654312014098788b75f26108d93f0429202ffffb6cf9ffffd7278383e4d8ad2af642264600011c035ed934a11c1b48e24b6be9b2d483e7747dd082f06abf75f9a092b34cd33c01be00394a99bd33304fc4343cd928857ae7c09c176452f40d9815f9ff3ba3865d013bd7218072e1588aea0568198d37e0d83ba75f155c863355974c4eb864de0103019ed27335bc5452e320ec22a30cfe61508929016157ff2a555181a9a0623e725801328208bea2c5c83487effab780cdb36b4b82e6e7290b06d98817a160f3d79d2800019ed6779a1724a107807baf4dda9481fb940f50d85db701dda43a0989c2d62535000001d1e806194dbe171d4ad1ef8c73c1a469130caced0e24b04b8acef91c42be7a56000107771e04f7d6371bfda40ef9e04419a25c6563dcd359c85bd501de28c3c7f3250110b2bfd32a99e0b982a41a6dbaebf783bdb9d6af795f5f20056ce7317d15ce1101f1c57245fff8dbc2d3efe5a0953eafdedeb06e18a3ad4f1e4042ee76623f803200011323ddf890bfd7b94fc609b0d191982cb426b8bf4d900d04709a8b9cb1a27625" tree: "0184330bda72e9596256847a9597d7d9476aa3d69d9dad2149314751e708da206601cd8a1a61df1e80514cd2c0a7faac8b8d7ce27d6d96bb63cb7c61c1f33e7c654312014098788b75f26108d93f0429202ffffb6cf9ffffd7278383e4d8ad2af642264600011c035ed934a11c1b48e24b6be9b2d483e7747dd082f06abf75f9a092b34cd33c01be00394a99bd33304fc4343cd928857ae7c09c176452f40d9815f9ff3ba3865d013bd7218072e1588aea0568198d37e0d83ba75f155c863355974c4eb864de0103019ed27335bc5452e320ec22a30cfe61508929016157ff2a555181a9a0623e725801328208bea2c5c83487effab780cdb36b4b82e6e7290b06d98817a160f3d79d2800019ed6779a1724a107807baf4dda9481fb940f50d85db701dda43a0989c2d62535000001d1e806194dbe171d4ad1ef8c73c1a469130caced0e24b04b8acef91c42be7a56000107771e04f7d6371bfda40ef9e04419a25c6563dcd359c85bd501de28c3c7f3250110b2bfd32a99e0b982a41a6dbaebf783bdb9d6af795f5f20056ce7317d15ce1101f1c57245fff8dbc2d3efe5a0953eafdedeb06e18a3ad4f1e4042ee76623f803200011323ddf890bfd7b94fc609b0d191982cb426b8bf4d900d04709a8b9cb1a27625"
) )
default:
return WalletBirthday(
height: 998500,
hash: "00000000014cf0915c4f105140e846c62ca6f2e321f4f717cf6762a35c6e8eb4",
time: 1602093677,
tree: "01b86ad8964b68a9a316a3038af14ac9e557a6eed614ee5d337c9fdb8887e9dc6001b71fbc815cc014a7f68648fcad5b9b72920b3d21cce406b5f593a0eab6d91f1212000001aede58c0641825b7531a8b296b9fdc7393090b87588893e8dd5160bd59b2e16300000000014fa30e43b641cf67c84cef81083b81bec3bd677f55def5c7bd948298e64690560134ac55ded091faef2ae8a1043704dfec41b1a95c4cedf1818574752bc40f2033000103ee02ae59c6688dcaadf1c4ff95e7b1a902837e4989a4c4994dce7dac6ecb20014ff8c0fe6bce02ac4ad684996bfa931d61c724015d797642819361d611ebd61201c7ae83949d9502b0eff10618124d335f046e4aae52c19ccad5567feceb342a5200000001b7fc5791e3650729b7e1e38ee8c4ea9da612a07b4bf412cefaffbab7ac74c547011323ddf890bfd7b94fc609b0d191982cb426b8bf4d900d04709a8b9cb1a27625"
)
} }
} }
} }

View File

@ -21,6 +21,7 @@ public enum SynchronizerError: Error {
case networkTimeout case networkTimeout
case uncategorized(underlyingError: Error) case uncategorized(underlyingError: Error)
case criticalError case criticalError
case parameterMissing(underlyingError: Error)
} }
/** /**
@ -104,6 +105,21 @@ public protocol Synchronizer {
*/ */
func paginatedTransactions(of kind: TransactionKind) -> PaginatedTransactionRepository func paginatedTransactions(of kind: TransactionKind) -> PaginatedTransactionRepository
/**
gets the latest downloaded height from the compact block cache
*/
func latestDownloadedHeight() throws -> BlockHeight
/**
Gets the latest block height from the provided Lightwallet endpoint
*/
func latestHeight(result: @escaping (Result<BlockHeight, Error>) -> Void)
/**
Gets the latest block height from the provided Lightwallet endpoint
Blocking
*/
func latestHeight() throws -> BlockHeight
} }
/** /**

View File

@ -0,0 +1,207 @@
//
// DerivationTool.swift
// Pods
//
// Created by Francisco Gindre on 10/8/20.
//
import Foundation
public protocol KeyDeriving {
/**
Given a seed and a number of accounts, return the associated viewing keys.
- Parameter seed: the seed from which to derive viewing keys.
- Parameter numberOfAccounts: the number of accounts to use. Multiple accounts are not fully
supported so the default value of 1 is recommended.
- Returns: the viewing keys that correspond to the seed, formatted as Strings.
*/
func deriveViewingKeys(seed: [UInt8], numberOfAccounts: Int) throws -> [String]
/**
Given a spending key, return the associated viewing key.
- Parameter spendingKey: the key from which to derive the viewing key.
- Returns: the viewing key that corresponds to the spending key.
*/
func deriveViewingKey(spendingKey: String) throws -> String
/**
Given a seed and a number of accounts, return the associated spending keys.
- Parameter seed: the seed from which to derive spending keys.
- Parameter numberOfAccounts: the number of accounts to use. Multiple accounts are not fully
supported so the default value of 1 is recommended.
- Returns: the spending keys that correspond to the seed, formatted as Strings.
*/
func deriveSpendingKeys(seed: [UInt8], numberOfAccounts: Int) throws -> [String]
/**
Given a seed and account index, return the associated address.
- Parameter seed: the seed from which to derive the address.
- Parameter accountIndex: the index of the account to use for deriving the address. Multiple
accounts are not fully supported so the default value of 1 is recommended.
- Returns: the address that corresponds to the seed and account index.
*/
func deriveShieldedAddress(seed: [UInt8], accountIndex: Int) throws -> String
/**
Given a viewing key string, return the associated address.
- Parameter viewingKey: the viewing key to use for deriving the address. The viewing key is tied to
a specific account so no account index is required.
- Returns: the address that corresponds to the viewing key.
*/
func deriveShieldedAddress(viewingKey: String) throws -> String
// WIP probably shouldn't be used just yet. Why?
// - because we need the private key associated with this seed and this function doesn't return it.
// - the underlying implementation needs to be split out into a few lower-level calls
func deriveTransparentAddress(seed: [UInt8]) throws -> String
func validateViewingKey(viewingKey: String) throws
}
public enum KeyDerivationErrors: Error {
case derivationError(underlyingError: Error)
case unableToDerive
case invalidInput
}
public class DerivationTool: KeyDeriving {
var rustwelding: ZcashRustBackendWelding.Type = ZcashRustBackend.self
public static let `default` = DerivationTool()
/**
Given a seed and a number of accounts, return the associated viewing keys.
- Parameter seed: the seed from which to derive viewing keys.
- Parameter numberOfAccounts: the number of accounts to use. Multiple accounts are not fully
supported so the default value of 1 is recommended.
- Returns: the viewing keys that correspond to the seed, formatted as Strings.
*/
public func deriveViewingKeys(seed: [UInt8], numberOfAccounts: Int) throws -> [String] {
guard numberOfAccounts > 0, let numberOfAccounts = Int32(exactly: numberOfAccounts) else {
throw KeyDerivationErrors.invalidInput
}
do {
guard let keys = try rustwelding.deriveExtendedFullViewingKeys(seed: seed, accounts: numberOfAccounts) else {
throw KeyDerivationErrors.unableToDerive
}
return keys
} catch {
throw KeyDerivationErrors.derivationError(underlyingError: error)
}
}
/**
Given a spending key, return the associated viewing key.
- Parameter spendingKey: the key from which to derive the viewing key.
- Returns: the viewing key that corresponds to the spending key.
*/
public func deriveViewingKey(spendingKey: String) throws -> String {
do {
guard let key = try rustwelding.deriveExtendedFullViewingKey(spendingKey) else {
throw KeyDerivationErrors.unableToDerive
}
return key
} catch {
throw KeyDerivationErrors.derivationError(underlyingError: error)
}
}
/**
Given a seed and a number of accounts, return the associated spending keys.
- Parameter seed: the seed from which to derive spending keys.
- Parameter numberOfAccounts: the number of accounts to use. Multiple accounts are not fully
supported so the default value of 1 is recommended.
- Returns: the spending keys that correspond to the seed, formatted as Strings.
*/
public func deriveSpendingKeys(seed: [UInt8], numberOfAccounts: Int) throws -> [String] {
guard numberOfAccounts > 0, let numberOfAccounts = Int32(exactly: numberOfAccounts) else {
throw KeyDerivationErrors.invalidInput
}
do {
guard let keys = try rustwelding.deriveExtendedSpendingKeys(seed: seed, accounts: numberOfAccounts) else {
throw KeyDerivationErrors.unableToDerive
}
return keys
} catch {
throw KeyDerivationErrors.derivationError(underlyingError: error)
}
}
/**
Given a seed and account index, return the associated address.
- Parameter seed: the seed from which to derive the address.
- Parameter accountIndex: the index of the account to use for deriving the address. Multiple
accounts are not fully supported so the default value of 1 is recommended.
- Returns: the address that corresponds to the seed and account index.
*/
public func deriveShieldedAddress(seed: [UInt8], accountIndex: Int) throws -> String {
guard accountIndex >= 0, let accountIndex = Int32(exactly: accountIndex) else {
throw KeyDerivationErrors.invalidInput
}
do {
guard let address = try rustwelding.deriveShieldedAddressFromSeed(seed: seed, accountIndex: accountIndex) else {
throw KeyDerivationErrors.unableToDerive
}
return address
} catch {
throw KeyDerivationErrors.derivationError(underlyingError: error)
}
}
/**
Given a viewing key string, return the associated address.
- Parameter viewingKey: the viewing key to use for deriving the address. The viewing key is tied to
a specific account so no account index is required.
- Returns: the address that corresponds to the viewing key.
*/
public func deriveShieldedAddress(viewingKey: String) throws -> String {
do {
guard let zaddr = try rustwelding.deriveShieldedAddressFromViewingKey(viewingKey) else {
throw KeyDerivationErrors.unableToDerive
}
return zaddr
} catch {
throw KeyDerivationErrors.derivationError(underlyingError: error)
}
}
// WIP probably shouldn't be used just yet. Why?
// - because we need the private key associated with this seed and this function doesn't return it.
// - the underlying implementation needs to be split out into a few lower-level calls
public func deriveTransparentAddress(seed: [UInt8]) throws -> String {
do {
guard let zaddr = try rustwelding.deriveTransparentAddressFromSeed(seed: seed) else {
throw KeyDerivationErrors.unableToDerive
}
return zaddr
} catch {
throw KeyDerivationErrors.derivationError(underlyingError: error)
}
}
public func validateViewingKey(viewingKey: String) throws {
// TODO
}
}

View File

@ -392,12 +392,6 @@ public class SDKSynchronizer: Synchronizer {
@objc func applicationWillResignActive(_ notification: Notification) { @objc func applicationWillResignActive(_ notification: Notification) {
registerBackgroundActivity() registerBackgroundActivity()
LoggerProxy.debug("applicationWillResignActive") LoggerProxy.debug("applicationWillResignActive")
// do {
//
// try stop()
// } catch {
// LoggerProxy.debug("stop failed with error: \(error)")
// }
} }
@objc func applicationWillTerminate(_ notification: Notification) { @objc func applicationWillTerminate(_ notification: Notification) {
@ -408,6 +402,20 @@ public class SDKSynchronizer: Synchronizer {
public func sendToAddress(spendingKey: String, zatoshi: Int64, toAddress: String, memo: String?, from accountIndex: Int, resultBlock: @escaping (Result<PendingTransactionEntity, Error>) -> Void) { public func sendToAddress(spendingKey: String, zatoshi: Int64, toAddress: String, memo: String?, from accountIndex: Int, resultBlock: @escaping (Result<PendingTransactionEntity, Error>) -> Void) {
initializer.downloadParametersIfNeeded { (downloadResult) in
DispatchQueue.main.async { [weak self] in
switch downloadResult {
case .success:
self?.createToAddress(spendingKey: spendingKey, zatoshi: zatoshi, toAddress: toAddress, memo: memo, from: accountIndex, resultBlock: resultBlock)
case .failure(let error):
resultBlock(.failure(SynchronizerError.parameterMissing(underlyingError: error)))
}
}
}
}
func createToAddress(spendingKey: String, zatoshi: Int64, toAddress: String, memo: String?, from accountIndex: Int, resultBlock: @escaping (Result<PendingTransactionEntity, Error>) -> Void) {
do { do {
let spend = try transactionManager.initSpend(zatoshi: Int(zatoshi), toAddress: toAddress, memo: memo, from: accountIndex) let spend = try transactionManager.initSpend(zatoshi: Int(zatoshi), toAddress: toAddress, memo: memo, from: accountIndex)
@ -435,7 +443,6 @@ public class SDKSynchronizer: Synchronizer {
resultBlock(.failure(error)) resultBlock(.failure(error))
} }
} }
public func getAddress(accountIndex: Int) -> String { public func getAddress(accountIndex: Int) -> String {
initializer.getAddress(index: accountIndex) ?? "" initializer.getAddress(index: accountIndex) ?? ""
} }
@ -464,6 +471,18 @@ public class SDKSynchronizer: Synchronizer {
PagedTransactionRepositoryBuilder.build(initializer: initializer, kind: .all) PagedTransactionRepositoryBuilder.build(initializer: initializer, kind: .all)
} }
public func latestDownloadedHeight() throws -> BlockHeight {
try initializer.downloader.lastDownloadedBlockHeight()
}
public func latestHeight(result: @escaping (Result<BlockHeight, Error>) -> Void) {
initializer.downloader.latestBlockHeight(result: result)
}
public func latestHeight() throws -> BlockHeight {
try initializer.downloader.latestBlockHeight()
}
// MARK: notify state // MARK: notify state
private func notify(progress: Float, height: BlockHeight) { private func notify(progress: Float, height: BlockHeight) {
NotificationCenter.default.post(name: Notification.Name.synchronizerProgressUpdated, object: self, userInfo: [ NotificationCenter.default.post(name: Notification.Name.synchronizerProgressUpdated, object: self, userInfo: [

View File

@ -0,0 +1,119 @@
//
// SaplingParameterDownloader.swift
// ZcashLightClientKit
//
// Created by Francisco Gindre on 10/7/20.
//
import Foundation
/**
Helper class to handle the download of Sapling parameters
*/
public class SaplingParameterDownloader {
public enum Errors: Error {
case invalidURL(url: String)
case failed(error: Error)
}
/**
Download a Spend parameter from default host and stores it at given URL
- Parameters:
- at: The destination URL for the download
- result: block to handle the download success or error
*/
public static func downloadSpendParameter(_ at: URL, result: @escaping (Result<URL, Error>) -> Void) {
guard let url = URL(string: spendParamsURLString) else {
result(.failure(Errors.invalidURL(url: spendParamsURLString)))
return
}
downloadFileWithRequest(URLRequest(url: url), at: at, result: result)
}
/**
Download an Output parameter from default host and stores it at given URL
- Parameters:
- at: The destination URL for the download
- result: block to handle the download success or error
*/
public static func downloadOutputParameter(_ at: URL, result: @escaping (Result<URL, Error>) -> Void) {
guard let url = URL(string: outputParamsURLString) else {
result(.failure(Errors.invalidURL(url: outputParamsURLString)))
return
}
downloadFileWithRequest(URLRequest(url: url), at: at, result: result)
}
private static func downloadFileWithRequest(_ request: URLRequest, at destination: URL, result: @escaping (Result<URL,Error>) -> Void) {
let task = URLSession.shared.downloadTask(with: request) { (url, _, error) in
if let e = error {
result(.failure(Errors.failed(error: e)))
return
} else if let localUrl = url {
do {
try FileManager.default.moveItem(at: localUrl, to: destination)
result(.success(destination))
} catch {
result(.failure(error))
}
}
}
task.resume()
}
/**
Downloads the parameters if not present and provides the resulting URLs for both parameters
- Parameters:
- spendURL: URL to check whether the parameter is already downloaded
- outputURL: URL to check whether the parameter is already downloaded
- result: block to handle success or error
*/
public static func downloadParamsIfnotPresent(spendURL: URL, outputURL: URL, result: @escaping (Result<(spend: URL, output: URL),Error>) -> Void) {
ensureSpendParameter(at: spendURL) { (spendResult) in
switch spendResult {
case .success(let spendResultURL):
ensureOutputParameter(at: outputURL) { (outputResult) in
switch outputResult {
case .success(let outputResultURL):
result(.success((spendResultURL,outputResultURL)))
case .failure(let outputResultError):
result(.failure(Errors.failed(error: outputResultError)))
}
}
case .failure(let spendResultError):
result(.failure(Errors.failed(error: spendResultError)))
}
}
}
static func ensureSpendParameter(at url: URL, result: @escaping (Result<URL,Error>) -> Void) {
if isFilePresent(url: url) {
DispatchQueue.global().async {
result(.success(url))
}
} else {
downloadSpendParameter(url, result: result)
}
}
static func ensureOutputParameter(at url: URL, result: @escaping (Result<URL,Error>) -> Void) {
if isFilePresent(url: url) {
DispatchQueue.global().async {
result(.success(url))
}
} else {
downloadOutputParameter(url, result: result)
}
}
static func isFilePresent(url: URL) -> Bool {
(try? FileManager.default.attributesOfItem(atPath: url.path)) != nil
}
static var spendParamsURLString: String {
return ZcashSDK.CLOUD_PARAM_DIR_URL + ZcashSDK.SPEND_PARAM_FILE_NAME
}
static var outputParamsURLString: String {
return ZcashSDK.CLOUD_PARAM_DIR_URL + ZcashSDK.OUTPUT_PARAM_FILE_NAME
}
}

View File

@ -38,18 +38,56 @@ int32_t zcashlc_decrypt_and_store_transaction(const uint8_t *db_data,
const uint8_t *tx, const uint8_t *tx,
uintptr_t tx_len); uintptr_t tx_len);
/**
* derives a shielded address from the given extended full viewing key.
* call zcashlc_string_free with the returned pointer when done using it
*/
char *zcashlc_derive_extended_full_viewing_key(const char *extsk); char *zcashlc_derive_extended_full_viewing_key(const char *extsk);
/**
* Derives Extended Full Viewing Keys from the given seed into 'accounts' number of accounts.
* Returns the Extended Full Viewing Keys for the accounts. The caller should store these
* securely
*
* Call `zcashlc_vec_string_free` on the returned pointer when you are finished with it.
*/
char **zcashlc_derive_extended_full_viewing_keys(const uint8_t *seed, char **zcashlc_derive_extended_full_viewing_keys(const uint8_t *seed,
uintptr_t seed_len, uintptr_t seed_len,
int32_t accounts, int32_t accounts,
uintptr_t *capacity_ret); uintptr_t *capacity_ret);
/**
* Derives Extended Spending Keys from the given seed into 'accounts' number of accounts.
* Returns the ExtendedSpendingKeys for the accounts. The caller should store these
* securely for use while spending.
*
* Call `zcashlc_vec_string_free` on the returned pointer when you are finished with it.
*/
char **zcashlc_derive_extended_spending_keys(const uint8_t *seed, char **zcashlc_derive_extended_spending_keys(const uint8_t *seed,
uintptr_t seed_len, uintptr_t seed_len,
int32_t accounts, int32_t accounts,
uintptr_t *capacity_ret); uintptr_t *capacity_ret);
/**
* derives a shielded address from the given seed.
* call zcashlc_string_free with the returned pointer when done using it
*/
char *zcashlc_derive_shielded_address_from_seed(const uint8_t *seed,
uintptr_t seed_len,
int32_t account_index);
/**
* derives a shielded address from the given viewing key.
* call zcashlc_string_free with the returned pointer when done using it
*/
char *zcashlc_derive_shielded_address_from_viewing_key(const char *extfvk);
/**
* TEST TEST 123 TEST
* Derives a transparent address from the given seed
*/
char *zcashlc_derive_transparent_address_from_seed(const uint8_t *seed, uintptr_t seed_len);
/** /**
* Copies the last error message into the provided allocated buffer. * Copies the last error message into the provided allocated buffer.
*/ */
@ -112,6 +150,15 @@ char **zcashlc_init_accounts_table(const uint8_t *db_data,
int32_t accounts, int32_t accounts,
uintptr_t *capacity_ret); uintptr_t *capacity_ret);
/**
* Initialises the data database with the given extended full viewing keys
* Call `zcashlc_vec_string_free` on the returned pointer when you are finished with it.
*/
bool zcashlc_init_accounts_table_with_keys(const uint8_t *db_data,
uintptr_t db_data_len,
const char *const *extfvks,
uintptr_t extfvks_len);
/** /**
* Initialises the data database with the given block. * Initialises the data database with the given block.
* *

View File

@ -688,7 +688,6 @@ class AdvancedReOrgTests: XCTestCase {
sleep(1) sleep(1)
let initialTotalBalance = coordinator.synchronizer.initializer.getBalance() let initialTotalBalance = coordinator.synchronizer.initializer.getBalance()
let initialVerifiedBalance = coordinator.synchronizer.initializer.getVerifiedBalance()
let sendExpectation = XCTestExpectation(description: "send expectation") let sendExpectation = XCTestExpectation(description: "send expectation")
var p: PendingTransactionEntity? = nil var p: PendingTransactionEntity? = nil

View File

@ -0,0 +1,70 @@
//
// DerivationToolTests.swift
// ZcashLightClientKit-Unit-Tests
//
// Created by Francisco Gindre on 10/9/20.
//
import XCTest
import ZcashLightClientKit
class DerivationToolTests: XCTestCase {
var seedPhrase = "still champion voice habit trend flight survey between bitter process artefact blind carbon truly provide dizzy crush flush breeze blouse charge solid fish spread" //TODO: Parameterize this from environment?
var seedData: Data = Data(base64Encoded: "9VDVOZZZOWWHpZtq1Ebridp3Qeux5C+HwiRR0g7Oi7HgnMs8Gfln83+/Q1NnvClcaSwM4ADFL1uZHxypEWlWXg==")!
let testRecipientAddress = "zs1vp7kvlqr4n9gpehztr76lcn6skkss9p8keqs3nv8avkdtjrcctrvmk9a7u494kluv756jeee5k0" //TODO: Parameterize this from environment
let expectedSpendingKey = "secret-extended-key-main1qw28psv0qqqqpqr2ru0kss5equx6h0xjsuk5299xrsgdqnhe0cknkl8uqff34prwkyuegyhh5d4rdr8025nl7e0hm8r2txx3fuea5mquy3wnsr9tlajsg4wwvw0xcfk8357k4h850rgj72kt4rx3fjdz99zs9f4neda35cq8tn3848yyvlg4w38gx75cyv9jdpve77x9eq6rtl6d9qyh8det4edevlnc70tg5kse670x50764gzhy60dta0yv3wsd4fsuaz686lgszc7nc9vv"
let expectedViewingKey = "zxviews1qw28psv0qqqqpqr2ru0kss5equx6h0xjsuk5299xrsgdqnhe0cknkl8uqff34prwkysswfhjk79n8l99f2grd26dqg6dy3jcmxsaypxfsu6ara6vsk3x8l544uaksstx9zre879mdg7s9a7zurrx6pf5qg2n323js2s3zlu8tn3848yyvlg4w38gx75cyv9jdpve77x9eq6rtl6d9qyh8det4edevlnc70tg5kse670x50764gzhy60dta0yv3wsd4fsuaz686lgszcq7kwxy"
let expectedTransparentAddress = "t1dRJRY7GmyeykJnMH38mdQoaZtFhn1QmGz"
func testDeriveViewingKeysFromSeed() throws {
let accounts: Int = 1
let seedBytes = [UInt8](seedData)
let viewingKeys = try DerivationTool.default.deriveViewingKeys(seed: seedBytes, numberOfAccounts: accounts)
XCTAssertEqual(viewingKeys.count, accounts, "the number of viewing keys have to match the number of account requested to derive")
guard let viewingKey = viewingKeys.first else {
XCTFail("no viewing key generated")
return
}
XCTAssertEqual(expectedViewingKey, viewingKey)
}
func testDeriveViewingKeyFromSpendingKeys() throws {
XCTAssertEqual(expectedViewingKey, try DerivationTool.default.deriveViewingKey(spendingKey: expectedSpendingKey))
}
func testDeriveSpendingKeysFromSeed() throws {
let accounts: Int = 1
let seedBytes = [UInt8](seedData)
let spendingKeys = try DerivationTool.default.deriveSpendingKeys(seed: seedBytes, numberOfAccounts: accounts)
XCTAssertEqual(spendingKeys.count, accounts, "the number of viewing keys have to match the number of account requested to derive")
guard let spendingKey = spendingKeys.first else {
XCTFail("no viewing key generated")
return
}
XCTAssertEqual(expectedSpendingKey, spendingKey)
}
func testDeriveShieldedAddressFromSeed() throws {
let seedBytes = [UInt8](seedData)
let shieldedAddress = try DerivationTool.default.deriveShieldedAddress(seed: seedBytes, accountIndex: 0)
XCTAssertEqual(shieldedAddress, testRecipientAddress)
}
func testDeriveShieldedAddressFromViewingKey() throws {
XCTAssertEqual(try DerivationTool.default.deriveShieldedAddress(viewingKey: expectedViewingKey), testRecipientAddress)
}
func testDeriveTransparentAddressFromSeed() throws {
XCTAssertEqual(try DerivationTool.default.deriveTransparentAddress(seed: [UInt8](seedData)), expectedTransparentAddress)
}
}

View File

@ -29,7 +29,12 @@ class OutboundTransactionManagerTests: XCTestCase {
try! pendingRespository.createrTableIfNeeded() try! pendingRespository.createrTableIfNeeded()
initializer = Initializer(cacheDbURL: cacheDbHandle.readWriteDb, dataDbURL: dataDbHandle.readWriteDb, pendingDbURL: try! TestDbBuilder.pendingTransactionsDbURL(), endpoint: LightWalletEndpointBuilder.default, spendParamsURL: try! __spendParamsURL(), outputParamsURL: try! __outputParamsURL()) initializer = Initializer(cacheDbURL: cacheDbHandle.readWriteDb,
dataDbURL: dataDbHandle.readWriteDb,
pendingDbURL: try! TestDbBuilder.pendingTransactionsDbURL(),
endpoint: LightWalletEndpointBuilder.default,
spendParamsURL: try! __spendParamsURL(),
outputParamsURL: try! __outputParamsURL())
encoder = WalletTransactionEncoder(initializer: initializer) encoder = WalletTransactionEncoder(initializer: initializer)
transactionManager = PersistentTransactionManager(encoder: encoder, service: MockLightWalletService(latestBlockHeight: 620999), repository: pendingRespository) transactionManager = PersistentTransactionManager(encoder: encoder, service: MockLightWalletService(latestBlockHeight: 620999), repository: pendingRespository)

View File

@ -78,8 +78,6 @@ class PendingTransactionUpdatesTest: XCTestCase {
wait(for: [firstSyncExpectation], timeout: 5) wait(for: [firstSyncExpectation], timeout: 5)
sleep(1) sleep(1)
let initialTotalBalance = coordinator.synchronizer.initializer.getBalance()
let initialVerifiedBalance = coordinator.synchronizer.initializer.getVerifiedBalance()
let sendExpectation = XCTestExpectation(description: "send expectation") let sendExpectation = XCTestExpectation(description: "send expectation")
var p: PendingTransactionEntity? = nil var p: PendingTransactionEntity? = nil

View File

@ -63,6 +63,7 @@ class TestCoordinator {
cacheDbURL: databases.cacheDB, cacheDbURL: databases.cacheDB,
dataDbURL: databases.dataDB, dataDbURL: databases.dataDB,
pendingDbURL: databases.pendingDB, pendingDbURL: databases.pendingDB,
endpoint: LightWalletEndpointBuilder.default,
service: self.service, service: self.service,
repository: TransactionSQLDAO(dbProvider: SimpleConnectionProvider(path: databases.dataDB.absoluteString)), repository: TransactionSQLDAO(dbProvider: SimpleConnectionProvider(path: databases.dataDB.absoluteString)),
downloader: downloader, downloader: downloader,
@ -78,7 +79,7 @@ class TestCoordinator {
} }
func stop() throws { func stop() throws {
try synchronizer.stop() synchronizer.stop()
self.completionHandler = nil self.completionHandler = nil
self.errorHandler = nil self.errorHandler = nil
@ -216,6 +217,7 @@ class TestSynchronizerBuilder {
cacheDbURL: URL, cacheDbURL: URL,
dataDbURL: URL, dataDbURL: URL,
pendingDbURL: URL, pendingDbURL: URL,
endpoint: LightWalletEndpoint,
service: LightWalletService, service: LightWalletService,
repository: TransactionRepository, repository: TransactionRepository,
downloader: CompactBlockDownloader, downloader: CompactBlockDownloader,
@ -232,6 +234,7 @@ class TestSynchronizerBuilder {
cacheDbURL: cacheDbURL, cacheDbURL: cacheDbURL,
dataDbURL: dataDbURL, dataDbURL: dataDbURL,
pendingDbURL: pendingDbURL, pendingDbURL: pendingDbURL,
endpoint: endpoint,
service: service, service: service,
repository: repository, repository: repository,
downloader: downloader, downloader: downloader,
@ -239,19 +242,10 @@ class TestSynchronizerBuilder {
outputParamsURL: outputParamsURL, outputParamsURL: outputParamsURL,
loggerProxy: loggerProxy loggerProxy: loggerProxy
) )
let credentials = try initializer.initialize(seedProvider: StubSeedProvider(bytes: seedBytes), walletBirthdayHeight: walletBirthday.height) try initializer.initialize(viewingKeys: try DerivationTool().deriveViewingKeys(seed: seedBytes, numberOfAccounts: 1), walletBirthday: walletBirthday.height)
let credentials = try DerivationTool().deriveSpendingKeys(seed: seedBytes, numberOfAccounts: 1)
return (credentials, try SDKSynchronizer(initializer: initializer) return (credentials, try SDKSynchronizer(initializer: initializer)
) )
} }
} }
class StubSeedProvider: SeedProvider {
let bytes: [UInt8]
init(bytes: [UInt8]) {
self.bytes = bytes
}
func seed() -> [UInt8] {
self.bytes
}
}

View File

@ -31,9 +31,15 @@ class WalletTests: XCTestCase {
func testWalletInitialization() { func testWalletInitialization() {
let wallet = Initializer(cacheDbURL: cacheData, dataDbURL: dbData, pendingDbURL: try! TestDbBuilder.pendingTransactionsDbURL(), endpoint: LightWalletEndpointBuilder.default, spendParamsURL: try! __spendParamsURL(), outputParamsURL: try! __outputParamsURL()) let wallet = Initializer(cacheDbURL: cacheData,
dataDbURL: dbData,
pendingDbURL: try! TestDbBuilder.pendingTransactionsDbURL(),
endpoint: LightWalletEndpointBuilder.default,
spendParamsURL: try! __spendParamsURL(),
outputParamsURL: try! __outputParamsURL()
)
XCTAssertNoThrow(try wallet.initialize(seedProvider: SampleSeedProvider(), walletBirthdayHeight: ZcashSDK.SAPLING_ACTIVATION_HEIGHT)) XCTAssertNoThrow(try wallet.initialize(viewingKeys: ["zxviewtestsapling1qwxyzvdmqqqqpqy3knx32fpja779wzg76kmglgguvr74g773f3aw3gy37rar6y9d37knvskz6thnea55s05cz3a7q38835hq4w58yevn763cn2wf7k2mpj247ynxpt9qm0nn39slkz5dk572hxr43pxqtg5kz3pqcj8z8uhz0l2vx8gxe90uf4pgw7ks23f0hz2hm47k9ym42cmns3tenhxzlyur2nvx68h4fmk9nrs44ymcqz434zsuxpvhklrjzn00gc43fdghn5szc5x2w"], walletBirthday: 663194))
// fileExists actually sucks, so attempting to delete the file and checking what happens is far better :) // fileExists actually sucks, so attempting to delete the file and checking what happens is far better :)
XCTAssertNoThrow( try FileManager.default.removeItem(at: dbData!) ) XCTAssertNoThrow( try FileManager.default.removeItem(at: dbData!) )
@ -42,12 +48,6 @@ class WalletTests: XCTestCase {
} }
} }
struct SampleSeedProvider: SeedProvider {
func seed() -> [UInt8] {
Array("testreferencealicetestreferencealice".utf8)
}
}
struct WalletBirthdayProvider { struct WalletBirthdayProvider {
static var testBirthday: WalletBirthday { static var testBirthday: WalletBirthday {
WalletBirthday() WalletBirthday()

View File

@ -28,7 +28,12 @@ class WalletTransactionEncoderTests: XCTestCase {
queue.maxConcurrentOperationCount = 1 queue.maxConcurrentOperationCount = 1
queue.qualityOfService = .userInitiated queue.qualityOfService = .userInitiated
initializer = Initializer(cacheDbURL: cacheDbHandle.readWriteDb, dataDbURL: dataDbHandle.readWriteDb, pendingDbURL: try! TestDbBuilder.pendingTransactionsDbURL(), endpoint: LightWalletEndpointBuilder.default, spendParamsURL: try! __spendParamsURL(), outputParamsURL: try! __outputParamsURL()) initializer = Initializer(cacheDbURL: cacheDbHandle.readWriteDb,
dataDbURL: dataDbHandle.readWriteDb,
pendingDbURL: try! TestDbBuilder.pendingTransactionsDbURL(),
endpoint: LightWalletEndpointBuilder.default,
spendParamsURL: try! __spendParamsURL(),
outputParamsURL: try! __outputParamsURL())
repository = TransactionSQLDAO(dbProvider: dataDbHandle.connectionProvider(readwrite: false)) repository = TransactionSQLDAO(dbProvider: dataDbHandle.connectionProvider(readwrite: false))
transactionEncoder = WalletTransactionEncoder(initializer: initializer) transactionEncoder = WalletTransactionEncoder(initializer: initializer)

View File

@ -37,8 +37,20 @@ class ZcashRustBackendTests: XCTestCase {
} }
func testInitWithViewingKeys() throws {
let viewingKey = "zxviews1qw28psv0qqqqpqr2ru0kss5equx6h0xjsuk5299xrsgdqnhe0cknkl8uqff34prwkysswfhjk79n8l99f2grd26dqg6dy3jcmxsaypxfsu6ara6vsk3x8l544uaksstx9zre879mdg7s9a7zurrx6pf5qg2n323js2s3zlu8tn3848yyvlg4w38gx75cyv9jdpve77x9eq6rtl6d9qyh8det4edevlnc70tg5kse670x50764gzhy60dta0yv3wsd4fsuaz686lgszcq7kwxy"
XCTAssertNoThrow(try ZcashRustBackend.initDataDb(dbData: dbData!))
let _ = try ZcashRustBackend.initAccountsTable(dbData: dbData!, exfvks: [viewingKey])
XCTAssertNil(ZcashRustBackend.getLastError())
XCTAssertEqual(ZcashRustBackend.getAddress(dbData: dbData!, account: 0),"zs1vp7kvlqr4n9gpehztr76lcn6skkss9p8keqs3nv8avkdtjrcctrvmk9a7u494kluv756jeee5k0")
}
func testDeriveExtendedSpendingKeys() { func testDeriveExtendedSpendingKeys() {
let seed = "testreferencealicetestreferencealice" let seed = Array("testreferencealicetestreferencealice".utf8)
var spendingKeys: [String]? = nil var spendingKeys: [String]? = nil
XCTAssertNoThrow(try { spendingKeys = try ZcashRustBackend.deriveExtendedSpendingKeys(seed: seed, accounts: 1) }()) XCTAssertNoThrow(try { spendingKeys = try ZcashRustBackend.deriveExtendedSpendingKeys(seed: seed, accounts: 1) }())
@ -49,7 +61,7 @@ class ZcashRustBackendTests: XCTestCase {
} }
func testDeriveExtendedFullViewingKeys() { func testDeriveExtendedFullViewingKeys() {
let seed = "testreferencealicetestreferencealice" let seed = Array("testreferencealicetestreferencealice".utf8)
var fullViewingKeys: [String]? = nil var fullViewingKeys: [String]? = nil
XCTAssertNoThrow(try { fullViewingKeys = try ZcashRustBackend.deriveExtendedFullViewingKeys(seed: seed, accounts: 1) }()) XCTAssertNoThrow(try { fullViewingKeys = try ZcashRustBackend.deriveExtendedFullViewingKeys(seed: seed, accounts: 1) }())
@ -59,7 +71,7 @@ class ZcashRustBackendTests: XCTestCase {
} }
func testDeriveExtendedFullViewingKey() { func testDeriveExtendedFullViewingKey() {
let seed = "testreferencealicetestreferencealice" let seed = Array("testreferencealicetestreferencealice".utf8)
var fullViewingKey: String? = nil var fullViewingKey: String? = nil

View File

@ -204,14 +204,3 @@ internal final class DarksideStreamerClient: GRPCClient, DarksideStreamerClientP
} }
// Provides conformance to `GRPCPayload` for request and response messages
extension DarksideMetaState: GRPCProtobufPayload {}
//extension Empty: GRPCProtobufPayload {}
extension DarksideBlock: GRPCProtobufPayload {}
extension DarksideBlocksURL: GRPCProtobufPayload {}
extension DarksideEmptyBlocks: GRPCProtobufPayload {}
//extension RawTransaction: GRPCProtobufPayload {}
extension DarksideTransactionsURL: GRPCProtobufPayload {}
extension DarksideHeight: GRPCProtobufPayload {}

View File

@ -77,6 +77,30 @@ extension LightWalletServiceMockResponse {
} }
class MockRustBackend: ZcashRustBackendWelding { class MockRustBackend: ZcashRustBackendWelding {
static func initAccountsTable(dbData: URL, exfvks: [String]) throws -> Bool {
false
}
static func deriveTransparentAddressFromSeed(seed: [UInt8]) throws -> String? {
nil
}
static func deriveExtendedFullViewingKeys(seed: [UInt8], accounts: Int32) throws -> [String]? {
nil
}
static func deriveExtendedSpendingKeys(seed: [UInt8], accounts: Int32) throws -> [String]? {
nil
}
static func deriveShieldedAddressFromSeed(seed: [UInt8], accountIndex: Int32) throws -> String? {
nil
}
static func deriveShieldedAddressFromViewingKey(_ extfvk: String) throws -> String? {
nil
}
static func consensusBranchIdFor(height: Int32) throws -> Int32 { static func consensusBranchIdFor(height: Int32) throws -> Int32 {
-1 -1

View File

@ -67,11 +67,11 @@ func __dataDbURL() throws -> URL {
} }
func __spendParamsURL() throws -> URL { func __spendParamsURL() throws -> URL {
URL(string: Bundle.testBundle.url(forResource: "sapling-spend", withExtension: "params")!.path)! try __documentsDirectory().appendingPathComponent("sapling-spend.params")
} }
func __outputParamsURL() throws -> URL { func __outputParamsURL() throws -> URL {
URL(string: Bundle.testBundle.url(forResource: "sapling-output", withExtension: "params")!.path)! try __documentsDirectory().appendingPathComponent("sapling-output.params")
} }
func copyParametersToDocuments() throws -> (spend: URL, output: URL) { func copyParametersToDocuments() throws -> (spend: URL, output: URL) {
@ -112,7 +112,7 @@ extension Bundle {
} }
class TestSeed: SeedProvider { class TestSeed {
/** /**
test account: "still champion voice habit trend flight survey between bitter process artefact blind carbon truly provide dizzy crush flush breeze blouse charge solid fish spread" test account: "still champion voice habit trend flight survey between bitter process artefact blind carbon truly provide dizzy crush flush breeze blouse charge solid fish spread"

View File

@ -8,8 +8,8 @@ use std::slice;
use std::str::FromStr; use std::str::FromStr;
use zcash_client_backend::{ use zcash_client_backend::{
encoding::{ encoding::{
decode_extended_spending_key, encode_extended_full_viewing_key, decode_extended_full_viewing_key, decode_extended_spending_key,
encode_extended_spending_key, encode_extended_full_viewing_key, encode_extended_spending_key, encode_payment_address,
}, },
keys::spending_key, keys::spending_key,
}; };
@ -43,14 +43,29 @@ use zcash_proofs::prover::LocalTxProver;
#[cfg(feature = "mainnet")] #[cfg(feature = "mainnet")]
use zcash_client_backend::constants::mainnet::{ use zcash_client_backend::constants::mainnet::{
COIN_TYPE, HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY, HRP_SAPLING_EXTENDED_SPENDING_KEY, COIN_TYPE, HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY, HRP_SAPLING_EXTENDED_SPENDING_KEY,
HRP_SAPLING_PAYMENT_ADDRESS,
}; };
#[cfg(not(feature = "mainnet"))] #[cfg(not(feature = "mainnet"))]
use zcash_client_backend::constants::testnet::{ use zcash_client_backend::constants::testnet::{
COIN_TYPE, HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY, HRP_SAPLING_EXTENDED_SPENDING_KEY, COIN_TYPE, HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY, HRP_SAPLING_EXTENDED_SPENDING_KEY,
HRP_SAPLING_PAYMENT_ADDRESS,
}; };
use std::convert::TryFrom; use std::convert::TryFrom;
// /////////////////////////////////////////////////////////////////////////////////////////////////
// Temporary Imports
use base58::ToBase58;
use sha2::{Digest, Sha256};
// use zcash_primitives::legacy::TransparentAddress;
use hdwallet::{ExtendedPrivKey, KeyIndex};
use secp256k1::{PublicKey, Secp256k1};
use zcash_client_backend::constants::mainnet::B58_PUBKEY_ADDRESS_PREFIX;
// use crate::extended_key::{key_index::KeyIndex, ExtendedPrivKey, ExtendedPubKey, KeySeed};
// /////////////////////////////////////////////////////////////////////////////////////////////////
fn unwrap_exc_or<T>(exc: Result<T, ()>, def: T) -> T { fn unwrap_exc_or<T>(exc: Result<T, ()>, def: T) -> T {
match exc { match exc {
Ok(value) => value, Ok(value) => value,
@ -160,39 +175,49 @@ pub extern "C" fn zcashlc_init_accounts_table(
unwrap_exc_or_null(res) unwrap_exc_or_null(res)
} }
/// Initialises the data database with the given block. /// Initialises the data database with the given extended full viewing keys
/// /// Call `zcashlc_vec_string_free` on the returned pointer when you are finished with it.
/// This enables a newly-created database to be immediately-usable, without needing to
/// synchronise historic blocks.
#[no_mangle] #[no_mangle]
pub extern "C" fn zcashlc_init_blocks_table( pub extern "C" fn zcashlc_init_accounts_table_with_keys(
db_data: *const u8, db_data: *const u8,
db_data_len: usize, db_data_len: usize,
height: i32, extfvks: *const *const c_char,
hash_hex: *const c_char, extfvks_len: usize,
time: u32, ) -> bool {
sapling_tree_hex: *const c_char,
) -> i32 {
let res = catch_panic(|| { let res = catch_panic(|| {
let db_data = Path::new(OsStr::from_bytes(unsafe { let db_data = Path::new(OsStr::from_bytes(unsafe {
slice::from_raw_parts(db_data, db_data_len) slice::from_raw_parts(db_data, db_data_len)
})); }));
let hash = {
let mut hash = hex::decode(unsafe { CStr::from_ptr(hash_hex) }.to_str()?).unwrap();
hash.reverse();
BlockHash::from_slice(&hash)
};
let sapling_tree =
hex::decode(unsafe { CStr::from_ptr(sapling_tree_hex) }.to_str()?).unwrap();
match init_blocks_table(&db_data, height, hash, time, &sapling_tree) { let extfvks = unsafe { std::slice::from_raw_parts(extfvks, extfvks_len)
Ok(()) => Ok(1), .into_iter()
Err(e) => Err(format_err!("Error while initializing blocks table: {}", e)), .map(|s| CStr::from_ptr(*s).to_str().unwrap())
.map( |vkstr|
decode_extended_full_viewing_key(HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY, &vkstr)
.unwrap()
.unwrap()
).collect::<Vec<_>>() };
match init_accounts_table(&db_data,&extfvks) {
Ok(()) => Ok(true),
Err(e) => match e.kind() {
ErrorKind::TableNotEmpty => {
// Ignore this error.
Ok(true)
}
_ => return Err(format_err!("Error while initializing accounts: {}", e)),
},
} }
}); });
unwrap_exc_or_null(res) unwrap_exc_or(res, false)
} }
/// Derives Extended Spending Keys from the given seed into 'accounts' number of accounts.
/// Returns the ExtendedSpendingKeys for the accounts. The caller should store these
/// securely for use while spending.
///
/// Call `zcashlc_vec_string_free` on the returned pointer when you are finished with it.
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn zcashlc_derive_extended_spending_keys( pub unsafe extern "C" fn zcashlc_derive_extended_spending_keys(
seed: *const u8, seed: *const u8,
@ -229,7 +254,11 @@ pub unsafe extern "C" fn zcashlc_derive_extended_spending_keys(
}); });
unwrap_exc_or_null(res) unwrap_exc_or_null(res)
} }
/// Derives Extended Full Viewing Keys from the given seed into 'accounts' number of accounts.
/// Returns the Extended Full Viewing Keys for the accounts. The caller should store these
/// securely
///
/// Call `zcashlc_vec_string_free` on the returned pointer when you are finished with it.
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn zcashlc_derive_extended_full_viewing_keys( pub unsafe extern "C" fn zcashlc_derive_extended_full_viewing_keys(
seed: *const u8, seed: *const u8,
@ -266,7 +295,63 @@ pub unsafe extern "C" fn zcashlc_derive_extended_full_viewing_keys(
}); });
unwrap_exc_or_null(res) unwrap_exc_or_null(res)
} }
/// derives a shielded address from the given seed.
/// call zcashlc_string_free with the returned pointer when done using it
#[no_mangle]
pub unsafe extern "C" fn zcashlc_derive_shielded_address_from_seed(
seed: *const u8,
seed_len: usize,
account_index: i32,
) -> *mut c_char {
let res = catch_panic(|| {
let seed = slice::from_raw_parts(seed, seed_len);
let account_index = if account_index >= 0 {
account_index as u32
} else {
return Err(format_err!("accounts argument must be greater than zero"));
};
let address = spending_key(&seed, COIN_TYPE, account_index)
.default_address()
.unwrap()
.1;
let address_str = encode_payment_address(HRP_SAPLING_PAYMENT_ADDRESS, &address);
Ok(CString::new(address_str).unwrap().into_raw())
});
unwrap_exc_or_null(res)
}
/// derives a shielded address from the given viewing key.
/// call zcashlc_string_free with the returned pointer when done using it
#[no_mangle]
pub unsafe extern "C" fn zcashlc_derive_shielded_address_from_viewing_key(
extfvk: *const c_char,
) -> *mut c_char {
let res = catch_panic(|| {
let extfvk_string = CStr::from_ptr(extfvk).to_str()?;
let extfvk = match decode_extended_full_viewing_key(
HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY,
&extfvk_string,
) {
Ok(Some(extfvk)) => extfvk,
Ok(None) => {
return Err(format_err!("Failed to parse viewing key string in order to derive the address. Deriving a viewing key from the string returned no results. Encoding was valid but type was incorrect."));
}
Err(e) => {
return Err(format_err!(
"Error while deriving viewing key from string input: {}",
e
));
}
};
let address = extfvk.default_address().unwrap().1;
let address_str = encode_payment_address(HRP_SAPLING_PAYMENT_ADDRESS, &address);
Ok(CString::new(address_str).unwrap().into_raw())
});
unwrap_exc_or_null(res)
}
/// derives a shielded address from the given extended full viewing key.
/// call zcashlc_string_free with the returned pointer when done using it
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn zcashlc_derive_extended_full_viewing_key( pub unsafe extern "C" fn zcashlc_derive_extended_full_viewing_key(
extsk: *const c_char, extsk: *const c_char,
@ -294,6 +379,39 @@ pub unsafe extern "C" fn zcashlc_derive_extended_full_viewing_key(
unwrap_exc_or_null(res) unwrap_exc_or_null(res)
} }
/// Initialises the data database with the given block.
///
/// This enables a newly-created database to be immediately-usable, without needing to
/// synchronise historic blocks.
#[no_mangle]
pub extern "C" fn zcashlc_init_blocks_table(
db_data: *const u8,
db_data_len: usize,
height: i32,
hash_hex: *const c_char,
time: u32,
sapling_tree_hex: *const c_char,
) -> i32 {
let res = catch_panic(|| {
let db_data = Path::new(OsStr::from_bytes(unsafe {
slice::from_raw_parts(db_data, db_data_len)
}));
let hash = {
let mut hash = hex::decode(unsafe { CStr::from_ptr(hash_hex) }.to_str()?).unwrap();
hash.reverse();
BlockHash::from_slice(&hash)
};
let sapling_tree =
hex::decode(unsafe { CStr::from_ptr(sapling_tree_hex) }.to_str()?).unwrap();
match init_blocks_table(&db_data, height, hash, time, &sapling_tree) {
Ok(()) => Ok(1),
Err(e) => Err(format_err!("Error while initializing blocks table: {}", e)),
}
});
unwrap_exc_or_null(res)
}
/// Returns the address for the account. /// Returns the address for the account.
/// ///
/// Call `zcashlc_string_free` on the returned pointer when you are finished with it. /// Call `zcashlc_string_free` on the returned pointer when you are finished with it.
@ -324,27 +442,6 @@ pub extern "C" fn zcashlc_get_address(
unwrap_exc_or_null(res) unwrap_exc_or_null(res)
} }
/// Returns the balance for the account, including all unspent notes that we know about.
#[no_mangle]
pub extern "C" fn zcashlc_get_balance(db_data: *const u8, db_data_len: usize, account: i32) -> i64 {
let res = catch_panic(|| {
let db_data = Path::new(OsStr::from_bytes(unsafe {
slice::from_raw_parts(db_data, db_data_len)
}));
let account = if account >= 0 {
account as u32
} else {
return Err(format_err!("account argument must be positive"));
};
match get_balance(&db_data, account) {
Ok(balance) => Ok(balance.into()),
Err(e) => Err(format_err!("Error while fetching balance: {}", e)),
}
});
unwrap_exc_or(res, -1)
}
/// Returns true when the address is valid and shielded. /// Returns true when the address is valid and shielded.
/// Returns false in any other case /// Returns false in any other case
/// Errors when the provided address belongs to another network /// Errors when the provided address belongs to another network
@ -382,6 +479,27 @@ pub unsafe extern "C" fn zcashlc_is_valid_transparent_address(address: *const c_
unwrap_exc_or(res, false) unwrap_exc_or(res, false)
} }
/// Returns the balance for the account, including all unspent notes that we know about.
#[no_mangle]
pub extern "C" fn zcashlc_get_balance(db_data: *const u8, db_data_len: usize, account: i32) -> i64 {
let res = catch_panic(|| {
let db_data = Path::new(OsStr::from_bytes(unsafe {
slice::from_raw_parts(db_data, db_data_len)
}));
let account = if account >= 0 {
account as u32
} else {
return Err(format_err!("account argument must be positive"));
};
match get_balance(&db_data, account) {
Ok(balance) => Ok(balance.into()),
Err(e) => Err(format_err!("Error while fetching balance: {}", e)),
}
});
unwrap_exc_or(res, -1)
}
/// Returns the verified balance for the account, which ignores notes that have been /// Returns the verified balance for the account, which ignores notes that have been
/// received too recently and are not yet deemed spendable. /// received too recently and are not yet deemed spendable.
#[no_mangle] #[no_mangle]
@ -715,3 +833,73 @@ pub extern "C" fn zcashlc_vec_string_free(v: *mut *mut c_char, len: usize, capac
v.into_iter().map(|s| CString::from_raw(s)).for_each(drop); v.into_iter().map(|s| CString::from_raw(s)).for_each(drop);
}; };
} }
/// TEST TEST 123 TEST
/// Derives a transparent address from the given seed
#[no_mangle]
pub unsafe extern "C" fn zcashlc_derive_transparent_address_from_seed(
seed: *const u8,
seed_len: usize,
) -> *mut c_char {
let res = catch_panic(|| {
let seed = slice::from_raw_parts(seed, seed_len);
// modified from: https://github.com/adityapk00/zecwallet-light-cli/blob/master/lib/src/lightwallet.rs
let ext_t_key = ExtendedPrivKey::with_seed(&seed).unwrap();
let address_sk = ext_t_key
.derive_private_key(KeyIndex::hardened_from_normalize_index(44).unwrap())
.unwrap()
.derive_private_key(KeyIndex::hardened_from_normalize_index(COIN_TYPE).unwrap())
.unwrap()
.derive_private_key(KeyIndex::hardened_from_normalize_index(0).unwrap())
.unwrap()
.derive_private_key(KeyIndex::Normal(0))
.unwrap()
.derive_private_key(KeyIndex::Normal(0))
.unwrap()
.private_key;
let secp = Secp256k1::new();
let pk = PublicKey::from_secret_key(&secp, &address_sk);
let mut hash160 = ripemd160::Ripemd160::new();
hash160.update(Sha256::digest(&pk.serialize()[..].to_vec()));
let address_string = hash160
.finalize()
.to_base58check(&B58_PUBKEY_ADDRESS_PREFIX, &[]);
Ok(CString::new(address_string).unwrap().into_raw())
});
unwrap_exc_or_null(res)
}
//
// Helper code from: https://github.com/adityapk00/zecwallet-light-cli/blob/master/lib/src/lightwallet.rs
//
/// A trait for converting a [u8] to base58 encoded string.
pub trait ToBase58Check {
/// Converts a value of `self` to a base58 value, returning the owned string.
/// The version is a coin-specific prefix that is added.
/// The suffix is any bytes that we want to add at the end (like the "iscompressed" flag for
/// Secret key encoding)
fn to_base58check(&self, version: &[u8], suffix: &[u8]) -> String;
}
impl ToBase58Check for [u8] {
fn to_base58check(&self, version: &[u8], suffix: &[u8]) -> String {
let mut payload: Vec<u8> = Vec::new();
payload.extend_from_slice(version);
payload.extend_from_slice(self);
payload.extend_from_slice(suffix);
let checksum = double_sha256(&payload);
payload.append(&mut checksum[..4].to_vec());
payload.to_base58()
}
}
pub fn double_sha256(payload: &[u8]) -> Vec<u8> {
let h1 = Sha256::digest(&payload);
let h2 = Sha256::digest(&h1);
h2.to_vec()
}