[#442] Adopt SDK 0.17.0 (#496)

Fix _URLRouting error on project

Fix: Wallet account identifiers must be sequential on initialization

Adopt async API of SDK

Fixes after rebase

Fix tests
This commit is contained in:
Michal Fousek 2022-11-22 11:32:48 +01:00 committed by GitHub
parent 665c792808
commit a591ee93ce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 373 additions and 450 deletions

View File

@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 52;
objectVersion = 56;
objects = {
/* Begin PBXBuildFile section */
@ -23,6 +23,7 @@
0D535FDE271F4214009A9E3E /* Rubik-Italic-VariableFont_wght.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 0D535FDC271F4214009A9E3E /* Rubik-Italic-VariableFont_wght.ttf */; };
0D535FDF271F4214009A9E3E /* Rubik-VariableFont_wght.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 0D535FDD271F4214009A9E3E /* Rubik-VariableFont_wght.ttf */; };
0D535FE2271F9476009A9E3E /* EnumeratedChip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D535FE1271F9476009A9E3E /* EnumeratedChip.swift */; };
0D5D9B8F2914620700DBD03F /* URLRouting in Frameworks */ = {isa = PBXBuildFile; productRef = 0D5D9B8E2914620700DBD03F /* URLRouting */; };
0D6D628B276A528E002FB4CC /* DropDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D6D628A276A528D002FB4CC /* DropDelegate.swift */; };
0D7CE63427349B5D0020E050 /* View+WhenDraggable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D7CE63327349B5D0020E050 /* View+WhenDraggable.swift */; };
0D7DF08C271DCC0E00530046 /* ScreenBackground.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D7DF08B271DCC0E00530046 /* ScreenBackground.swift */; };
@ -74,6 +75,7 @@
346715A528E2027D0035F7C4 /* CheckCircleStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 346715A428E2027D0035F7C4 /* CheckCircleStore.swift */; };
346715A828E20FE40035F7C4 /* TransactionConfirmationSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 346715A728E20FE40035F7C4 /* TransactionConfirmationSnapshotTests.swift */; };
346D41E428DF0B8600963F36 /* CheckCircle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 346D41E328DF0B8600963F36 /* CheckCircle.swift */; };
34BF09092927C98000222134 /* Memo+toString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34BF09082927C98000222134 /* Memo+toString.swift */; };
34DA414728E4385800F8CC61 /* TransactionSendingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34DA414628E4385800F8CC61 /* TransactionSendingView.swift */; };
34DA414928E439CD00F8CC61 /* sendingTransaction.json in Resources */ = {isa = PBXBuildFile; fileRef = 34DA414828E439CD00F8CC61 /* sendingTransaction.json */; };
34E0AF0F28DEE4C70034CF37 /* HoldToSendButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34E0AF0E28DEE4C70034CF37 /* HoldToSendButton.swift */; };
@ -149,7 +151,6 @@
9E7225F6288AC71A00DF7F17 /* MultiLineTextFieldStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7225F5288AC71A00DF7F17 /* MultiLineTextFieldStore.swift */; };
9E7CB6122869882D00A02233 /* WalletEventsSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7CB6112869882D00A02233 /* WalletEventsSnapshotTests.swift */; };
9E7CB6152869E8C300A02233 /* CircularProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7CB6142869E8C300A02233 /* CircularProgress.swift */; };
9E7CB6182872D3DF00A02233 /* URLRouting in Frameworks */ = {isa = PBXBuildFile; productRef = 9E7CB6172872D3DF00A02233 /* URLRouting */; };
9E7CB61A287310EC00A02233 /* QRCodeGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7CB619287310EC00A02233 /* QRCodeGenerator.swift */; };
9E7CB6202874143800A02233 /* AddressDetailsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7CB61F2874143800A02233 /* AddressDetailsStore.swift */; };
9E7CB6212874143800A02233 /* AddressDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E7CB61E2874143800A02233 /* AddressDetailsView.swift */; };
@ -174,7 +175,6 @@
9E9ECC9B28589E150099D5A2 /* ImportWalletSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E9ECC9428589E150099D5A2 /* ImportWalletSnapshotTests.swift */; };
9E9ECC9C28589E150099D5A2 /* OnboardingSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E9ECC9628589E150099D5A2 /* OnboardingSnapshotTests.swift */; };
9EAB466D285A0468002904A0 /* Parsing in Frameworks */ = {isa = PBXBuildFile; productRef = 9EAB466C285A0468002904A0 /* Parsing */; };
9EAB466F285A0468002904A0 /* _URLRouting in Frameworks */ = {isa = PBXBuildFile; productRef = 9EAB466E285A0468002904A0 /* _URLRouting */; };
9EAB4671285A1C77002904A0 /* Deeplink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAB4670285A1C77002904A0 /* Deeplink.swift */; };
9EAB4676285B5C7C002904A0 /* DeeplinkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAB4675285B5C7C002904A0 /* DeeplinkTests.swift */; };
9EAB46782860A1D2002904A0 /* WalletEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAB46772860A1D2002904A0 /* WalletEvent.swift */; };
@ -368,6 +368,7 @@
346715A428E2027D0035F7C4 /* CheckCircleStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckCircleStore.swift; sourceTree = "<group>"; };
346715A728E20FE40035F7C4 /* TransactionConfirmationSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionConfirmationSnapshotTests.swift; sourceTree = "<group>"; };
346D41E328DF0B8600963F36 /* CheckCircle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckCircle.swift; sourceTree = "<group>"; };
34BF09082927C98000222134 /* Memo+toString.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Memo+toString.swift"; sourceTree = "<group>"; };
34DA414628E4385800F8CC61 /* TransactionSendingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionSendingView.swift; sourceTree = "<group>"; };
34DA414828E439CD00F8CC61 /* sendingTransaction.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = sendingTransaction.json; sourceTree = "<group>"; };
34E0AF0E28DEE4C70034CF37 /* HoldToSendButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HoldToSendButton.swift; sourceTree = "<group>"; };
@ -570,11 +571,10 @@
buildActionMask = 2147483647;
files = (
9E6612312878337F00C75B70 /* Lottie in Frameworks */,
9E7CB6182872D3DF00A02233 /* URLRouting in Frameworks */,
0D5D9B8F2914620700DBD03F /* URLRouting in Frameworks */,
0DB4E0B42881FD9100947B78 /* ZcashLightClientKit in Frameworks */,
9E2AC0FF27D8EC120042AA47 /* MnemonicSwift in Frameworks */,
6654C73A2715A38000901167 /* ComposableArchitecture in Frameworks */,
9EAB466F285A0468002904A0 /* _URLRouting in Frameworks */,
9EAB466D285A0468002904A0 /* Parsing in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -1200,22 +1200,23 @@
9E7FE0BB282D1DC200C374E8 /* Utils */ = {
isa = PBXGroup;
children = (
F96B41EA273B50520021B49A /* Strings.swift */,
9E7FE0D4282D281800C374E8 /* Array+Chunked.swift */,
0DACFA8027208D940039EEA5 /* UInt+SuperscriptText.swift */,
0D7CE63327349B5D0020E050 /* View+WhenDraggable.swift */,
9E7FE0D2282D274E00C374E8 /* Date+Readable.swift */,
0DACFA7E27208CE00039EEA5 /* Clamped.swift */,
F9322DBF273B555C00C105B5 /* NavigationLinks.swift */,
9E6612322878338C00C75B70 /* LottieAnimation.swift */,
F9C165B3274031F600592F76 /* Bindings.swift */,
F9EEB8152742C2210032EEB8 /* WithStateBinding.swift */,
F93673D52742CB840099C6AF /* Previews.swift */,
0D35CC45277A36E00074316A /* ScrollableWhenScaled.swift */,
0DACFA7E27208CE00039EEA5 /* Clamped.swift */,
9E6713F9289BE0E100A6796F /* ClearBackgroundView.swift */,
9E7FE0D2282D274E00C374E8 /* Date+Readable.swift */,
2EDA07A327EDE2A900D6F09B /* DebugFrame.swift */,
9E2F1C832809B606004E65FE /* DebugMenu.swift */,
9E6612322878338C00C75B70 /* LottieAnimation.swift */,
34BF09082927C98000222134 /* Memo+toString.swift */,
F9322DBF273B555C00C105B5 /* NavigationLinks.swift */,
F93673D52742CB840099C6AF /* Previews.swift */,
9E7CB619287310EC00A02233 /* QRCodeGenerator.swift */,
9E6713F9289BE0E100A6796F /* ClearBackgroundView.swift */,
0D35CC45277A36E00074316A /* ScrollableWhenScaled.swift */,
F96B41EA273B50520021B49A /* Strings.swift */,
0DACFA8027208D940039EEA5 /* UInt+SuperscriptText.swift */,
0D7CE63327349B5D0020E050 /* View+WhenDraggable.swift */,
F9EEB8152742C2210032EEB8 /* WithStateBinding.swift */,
);
path = Utils;
sourceTree = "<group>";
@ -1755,10 +1756,9 @@
6654C7392715A38000901167 /* ComposableArchitecture */,
9E2AC0FE27D8EC120042AA47 /* MnemonicSwift */,
9EAB466C285A0468002904A0 /* Parsing */,
9EAB466E285A0468002904A0 /* _URLRouting */,
9E7CB6172872D3DF00A02233 /* URLRouting */,
9E6612302878337F00C75B70 /* Lottie */,
0DB4E0B32881FD9100947B78 /* ZcashLightClientKit */,
0D5D9B8E2914620700DBD03F /* URLRouting */,
);
productName = secant;
productReference = 0D4E7A0526B364170058B01E /* secant-testnet.app */;
@ -1825,7 +1825,7 @@
};
};
buildConfigurationList = 0D4E7A0026B364170058B01E /* Build configuration list for PBXProject "secant" */;
compatibilityVersion = "Xcode 9.3";
compatibilityVersion = "Xcode 14.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
@ -1837,9 +1837,9 @@
6654C7382715A38000901167 /* XCRemoteSwiftPackageReference "swift-composable-architecture" */,
9E2AC0FD27D8EC120042AA47 /* XCRemoteSwiftPackageReference "MnemonicSwift" */,
9EAB466B285A0468002904A0 /* XCRemoteSwiftPackageReference "swift-parsing" */,
9E7CB6162872D3DF00A02233 /* XCRemoteSwiftPackageReference "swift-url-routing" */,
9E66122F2878337F00C75B70 /* XCRemoteSwiftPackageReference "lottie-ios" */,
0DB4E0B22881FD9100947B78 /* XCRemoteSwiftPackageReference "ZcashLightClientKit" */,
0D5D9B8D2914620700DBD03F /* XCRemoteSwiftPackageReference "swift-url-routing" */,
);
productRefGroup = 0D4E7A0626B364170058B01E /* Products */;
projectDirPath = "";
@ -2104,6 +2104,7 @@
66A0807B271993C500118B79 /* OnboardingProgressIndicator.swift in Sources */,
9EBDF96D291ECED4000A1A05 /* CaptureDeviceInterface.swift in Sources */,
9EB8638D2922CD4A003D0F8B /* FeedbackGeneratorLiveKey.swift in Sources */,
34BF09092927C98000222134 /* Memo+toString.swift in Sources */,
0D7DF08C271DCC0E00530046 /* ScreenBackground.swift in Sources */,
346715A528E2027D0035F7C4 /* CheckCircleStore.swift in Sources */,
F9C165C22740403600592F76 /* CreateTransactionView.swift in Sources */,
@ -2509,12 +2510,20 @@
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
0D5D9B8D2914620700DBD03F /* XCRemoteSwiftPackageReference "swift-url-routing" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/pointfreeco/swift-url-routing";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 0.3.1;
};
};
0DB4E0B22881FD9100947B78 /* XCRemoteSwiftPackageReference "ZcashLightClientKit" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/zcash/ZcashLightClientKit/";
requirement = {
kind = exactVersion;
version = "0.16.10-beta";
kind = upToNextMajorVersion;
minimumVersion = "0.17.0-beta";
};
};
6654C7382715A38000901167 /* XCRemoteSwiftPackageReference "swift-composable-architecture" */ = {
@ -2541,14 +2550,6 @@
minimumVersion = 3.0.0;
};
};
9E7CB6162872D3DF00A02233 /* XCRemoteSwiftPackageReference "swift-url-routing" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "http://github.com/pointfreeco/swift-url-routing";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 0.3.0;
};
};
9EAB466B285A0468002904A0 /* XCRemoteSwiftPackageReference "swift-parsing" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/pointfreeco/swift-parsing";
@ -2560,6 +2561,11 @@
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
0D5D9B8E2914620700DBD03F /* URLRouting */ = {
isa = XCSwiftPackageProductDependency;
package = 0D5D9B8D2914620700DBD03F /* XCRemoteSwiftPackageReference "swift-url-routing" */;
productName = URLRouting;
};
0DB4E0B32881FD9100947B78 /* ZcashLightClientKit */ = {
isa = XCSwiftPackageProductDependency;
package = 0DB4E0B22881FD9100947B78 /* XCRemoteSwiftPackageReference "ZcashLightClientKit" */;
@ -2580,21 +2586,11 @@
package = 9E66122F2878337F00C75B70 /* XCRemoteSwiftPackageReference "lottie-ios" */;
productName = Lottie;
};
9E7CB6172872D3DF00A02233 /* URLRouting */ = {
isa = XCSwiftPackageProductDependency;
package = 9E7CB6162872D3DF00A02233 /* XCRemoteSwiftPackageReference "swift-url-routing" */;
productName = URLRouting;
};
9EAB466C285A0468002904A0 /* Parsing */ = {
isa = XCSwiftPackageProductDependency;
package = 9EAB466B285A0468002904A0 /* XCRemoteSwiftPackageReference "swift-parsing" */;
productName = Parsing;
};
9EAB466E285A0468002904A0 /* _URLRouting */ = {
isa = XCSwiftPackageProductDependency;
package = 9EAB466B285A0468002904A0 /* XCRemoteSwiftPackageReference "swift-parsing" */;
productName = _URLRouting;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 0D4E79FD26B364170058B01E /* Project object */;

View File

@ -185,8 +185,8 @@
"kind" : "remoteSourceControl",
"location" : "http://github.com/pointfreeco/swift-url-routing",
"state" : {
"revision" : "5bf79bb370015e43842a61d558a9ee053171124e",
"version" : "0.3.0"
"revision" : "80e8a0257ccdd639e31f709954ceca6b690fdc67",
"version" : "0.3.1"
}
},
{
@ -201,10 +201,10 @@
{
"identity" : "zcash-light-client-ffi",
"kind" : "remoteSourceControl",
"location" : "https://github.com/zcash-hackworks/zcash-light-client-ffi.git",
"location" : "https://github.com/zcash-hackworks/zcash-light-client-ffi",
"state" : {
"revision" : "b7e8a2abab84c44046b4afe4ee4522a0fa2fcc7f",
"version" : "0.0.3"
"revision" : "fad9802b907822d5a1877584c91f3786824521b7",
"version" : "0.1.0"
}
},
{
@ -212,8 +212,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/zcash/ZcashLightClientKit",
"state" : {
"revision" : "9e41fb43757fd7b0eb8e817320537230774258cf",
"version" : "0.16.10-beta"
"revision" : "d9b85b40ad36ac5183f44b6db9805e44171ee988",
"version" : "0.17.0-beta"
}
}
],

View File

@ -15,125 +15,20 @@ extension DependencyValues {
}
}
// swiftlint:disable identifier_name
struct DerivationToolClient {
/**
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.
*/
let deriveViewingKeys: ([UInt8], 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.
*/
let deriveViewingKey: (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.
*/
var deriveSpendingKeys: ([UInt8], 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.
*/
let deriveShieldedAddress: ([UInt8], 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.
*/
let deriveShieldedAddressFromViewingKey: (String) throws -> String
/**
Given a viewing key string, return the associated address.
- Parameter seed: the seed from which to derive the address.
- Parameter account: the account to use for deriving the address.
- Parameter index: the index to use for deriving the address.
- Returns: the address that corresponds to the seed.
*/
let deriveTransparentAddress: ([UInt8], Int, Int) throws -> String
/**
Given a viewing key string, return the associated address.
- 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 address that corresponds to the seed.
*/
let deriveUnifiedViewingKeysFromSeed: ([UInt8], Int) throws -> [UnifiedViewingKey]
/**
Derives a Unified Address from a Unified Viewing Key
*/
let deriveUnifiedAddressFromUnifiedViewingKey: (UnifiedViewingKey) throws -> UnifiedAddress
/**
Derives a Transparent Address from a Public Key
*/
let deriveTransparentAddressFromPublicKey: (String) throws -> String
/**
Derives the transparent funds private key from the given seed
- Throws:
- KeyDerivationErrors.derivationError with the underlying error when it fails
- KeyDerivationErrors.unableToDerive when there's an unknown error
*/
let deriveTransparentPrivateKey: ([UInt8], Int, Int) throws -> String
/**
Derives the transparent address from a WIF Private Key
- Throws:
- KeyDerivationErrors.derivationError with the underlying error when it fails
- KeyDerivationErrors.unableToDerive when there's an unknown error
*/
let deriveTransparentAddressFromPrivateKey: (String) throws -> String
/**
Checks validity of the extended viewing key.
*/
let isValidExtendedViewingKey: (String) throws -> Bool
/**
Checks validity of the transparent address.
*/
let isValidTransparentAddress: (String) throws -> Bool
/**
Checks validity of the shielded address.
*/
let isValidShieldedAddress: (String) throws -> Bool
/**
Checks if given address is a valid zcash address.
*/
/// Given a seed and a number of accounts, return the associated spending keys.
/// - Parameter seed: the seed from which to derive spending keys.
/// - Parameter accountIndex: Index of account to use. Multiple accounts are not fully
/// supported so the default value of 0 is recommended.
/// - Returns: the spending keys that correspond to the seed, formatted as Strings.
var deriveSpendingKey: ([UInt8], Int) throws -> UnifiedSpendingKey
/// Checks validity of the transparent address.
var isValidTransparentAddress: (String) throws -> Bool
/// Checks validity of the shielded address.
var isValidSaplingAddress: (String) throws -> Bool
/// Checks if given address is a valid zcash address.
var isValidZcashAddress: (String) throws -> Bool
}

View File

@ -11,53 +11,25 @@ import ZcashLightClientKit
extension DerivationToolClient: DependencyKey {
static let liveValue = DerivationToolClient.live()
static func live(derivationTool: DerivationTool = DerivationTool(networkType: .testnet)) -> Self {
Self(
deriveViewingKeys: { seed, numberOfAccounts in
try derivationTool.deriveViewingKeys(seed: seed, numberOfAccounts: numberOfAccounts)
},
deriveViewingKey: { spendingKey in
try derivationTool.deriveViewingKey(spendingKey: spendingKey)
},
deriveSpendingKeys: { seed, numberOfAccounts in
try derivationTool.deriveSpendingKeys(seed: seed, numberOfAccounts: numberOfAccounts)
},
deriveShieldedAddress: { seed, accountIndex in
try derivationTool.deriveShieldedAddress(seed: seed, accountIndex: accountIndex)
},
deriveShieldedAddressFromViewingKey: { viewingKey in
try derivationTool.deriveShieldedAddress(viewingKey: viewingKey)
},
deriveTransparentAddress: { seed, account, index in
try derivationTool.deriveTransparentAddress(seed: seed, account: account, index: index)
},
deriveUnifiedViewingKeysFromSeed: { seed, numberOfAccounts in
try derivationTool.deriveUnifiedViewingKeysFromSeed(seed, numberOfAccounts: numberOfAccounts)
},
deriveUnifiedAddressFromUnifiedViewingKey: { uvk in
try derivationTool.deriveUnifiedAddressFromUnifiedViewingKey(uvk)
},
deriveTransparentAddressFromPublicKey: { pubkey in
try derivationTool.deriveTransparentAddressFromPublicKey(pubkey)
},
deriveTransparentPrivateKey: { seed, account, index in
try derivationTool.deriveTransparentPrivateKey(seed: seed, account: account, index: index)
},
deriveTransparentAddressFromPrivateKey: { tsk in
try derivationTool.deriveTransparentAddressFromPrivateKey(tsk)
},
isValidExtendedViewingKey: { extvk in
try derivationTool.isValidExtendedViewingKey(extvk)
static func live(networkType: NetworkType = .testnet) -> Self {
let derivationTool = DerivationTool(networkType: networkType)
return Self(
deriveSpendingKey: { seed, accountIndex in
try derivationTool.deriveUnifiedSpendingKey(seed: seed, accountIndex: accountIndex)
},
isValidTransparentAddress: { tAddress in
try derivationTool.isValidTransparentAddress(tAddress)
derivationTool.isValidTransparentAddress(tAddress)
},
isValidShieldedAddress: { zAddress in
try derivationTool.isValidShieldedAddress(zAddress)
isValidSaplingAddress: { zAddress in
derivationTool.isValidSaplingAddress(zAddress)
},
isValidZcashAddress: { address in
try derivationTool.isValidTransparentAddress(address) ? true :
try derivationTool.isValidShieldedAddress(address) ? true : false
do {
_ = try Recipient(address, network: networkType)
return true
} catch {
return false
}
}
)
}

View File

@ -11,53 +11,18 @@ import ZcashLightClientKit
extension DerivationToolClient: TestDependencyKey {
static let testValue = Self(
deriveViewingKeys: XCTUnimplemented("\(Self.self).deriveViewingKeys", placeholder: []),
deriveViewingKey: XCTUnimplemented("\(Self.self).deriveViewingKey", placeholder: ""),
deriveSpendingKeys: XCTUnimplemented("\(Self.self).deriveSpendingKeys", placeholder: []),
deriveShieldedAddress: XCTUnimplemented("\(Self.self).deriveShieldedAddress", placeholder: ""),
deriveShieldedAddressFromViewingKey: XCTUnimplemented("\(Self.self).deriveShieldedAddressFromViewingKey", placeholder: ""),
deriveTransparentAddress: XCTUnimplemented("\(Self.self).deriveTransparentAddress", placeholder: ""),
deriveUnifiedViewingKeysFromSeed: XCTUnimplemented("\(Self.self).deriveUnifiedViewingKeysFromSeed", placeholder: []),
deriveUnifiedAddressFromUnifiedViewingKey:
XCTUnimplemented("\(Self.self).deriveUnifiedAddressFromUnifiedViewingKey", placeholder: TestUnifiedAddress()),
deriveTransparentAddressFromPublicKey: XCTUnimplemented("\(Self.self).deriveTransparentAddressFromPublicKey", placeholder: ""),
deriveTransparentPrivateKey: XCTUnimplemented("\(Self.self).deriveTransparentPrivateKey", placeholder: ""),
deriveTransparentAddressFromPrivateKey: XCTUnimplemented("\(Self.self).deriveTransparentAddressFromPrivateKey", placeholder: ""),
isValidExtendedViewingKey: XCTUnimplemented("\(Self.self).isValidExtendedViewingKey", placeholder: false),
deriveSpendingKey: XCTUnimplemented("\(Self.self).deriveSpendingKey"),
isValidTransparentAddress: XCTUnimplemented("\(Self.self).isValidTransparentAddress", placeholder: false),
isValidShieldedAddress: XCTUnimplemented("\(Self.self).isValidShieldedAddress", placeholder: false),
isValidSaplingAddress: XCTUnimplemented("\(Self.self).isValidShieldedAddress", placeholder: false),
isValidZcashAddress: XCTUnimplemented("\(Self.self).isValidZcashAddress", placeholder: false)
)
}
extension DerivationToolClient {
struct TestUnifiedAddress: UnifiedAddress {
var tAddress: ZcashLightClientKit.TransparentAddress
var zAddress: ZcashLightClientKit.SaplingShieldedAddress
init(tAddress: ZcashLightClientKit.TransparentAddress = "", zAddress: ZcashLightClientKit.SaplingShieldedAddress = "") {
self.tAddress = tAddress
self.zAddress = zAddress
}
}
}
extension DerivationToolClient {
static let noOp = Self(
deriveViewingKeys: { _, _ in [] },
deriveViewingKey: { _ in "" },
deriveSpendingKeys: { _, _ in [] },
deriveShieldedAddress: { _, _ in "" },
deriveShieldedAddressFromViewingKey: { _ in "" },
deriveTransparentAddress: { _, _, _ in "" },
deriveUnifiedViewingKeysFromSeed: { _, _ in [] },
deriveUnifiedAddressFromUnifiedViewingKey: { _ in TestUnifiedAddress() },
deriveTransparentAddressFromPublicKey: { _ in "" },
deriveTransparentPrivateKey: { _, _, _ in "" },
deriveTransparentAddressFromPrivateKey: { _ in "" },
isValidExtendedViewingKey: { _ in false },
isValidTransparentAddress: { _ in false },
isValidShieldedAddress: { _ in false },
isValidZcashAddress: { _ in false }
deriveSpendingKey: { _, _ in throw "NotImplemented" },
isValidTransparentAddress: { _ in return false },
isValidSaplingAddress: { _ in return false },
isValidZcashAddress: { _ in return false }
)
}

View File

@ -38,6 +38,10 @@ enum SDKSynchronizerState: Equatable {
case connectionStateChanged
}
enum SDKSynchronizerClientError: Error {
case synchronizerNotInitialized
}
protocol SDKSynchronizerClient {
var notificationCenter: NotificationCenterClient { get }
var synchronizer: SDKSynchronizer? { get }
@ -45,14 +49,14 @@ protocol SDKSynchronizerClient {
var walletBirthday: BlockHeight? { get }
var latestScannedSynchronizerState: SDKSynchronizer.SynchronizerState? { get }
func prepareWith(initializer: Initializer) throws
func prepareWith(initializer: Initializer, seedBytes: [UInt8]) throws
func start(retry: Bool) throws
func stop()
func synchronizerSynced(_ synchronizerState: SDKSynchronizer.SynchronizerState?)
func statusSnapshot() -> SyncStatusSnapshot
func rewind(_ policy: RewindPolicy) throws
func rewind(_ policy: RewindPolicy) async throws
func getShieldedBalance() -> WalletBalance?
func getTransparentBalance() -> WalletBalance?
func getAllClearedTransactions() -> Effect<[WalletEvent], Never>
@ -60,14 +64,13 @@ protocol SDKSynchronizerClient {
func getAllTransactions() -> Effect<[WalletEvent], Never>
func getTransparentAddress(account: Int) -> TransparentAddress?
func getShieldedAddress(account: Int) -> SaplingShieldedAddress?
func getSaplingAddress(accountIndex: Int) async -> SaplingAddress?
func sendTransaction(
with spendingKey: String,
with spendingKey: UnifiedSpendingKey,
zatoshi: Zatoshi,
to recipientAddress: String,
memo: String?,
from account: Int
to recipientAddress: Recipient,
memo: Memo?
) -> Effect<Result<TransactionState, NSError>, Never>
}
@ -80,7 +83,7 @@ extension SDKSynchronizerClient {
getTransparentAddress(account: 0)
}
func getShieldedAddress() -> SaplingShieldedAddress? {
getShieldedAddress(account: 0)
func getSaplingAddress() async -> SaplingAddress? {
await getSaplingAddress(accountIndex: 0)
}
}

View File

@ -17,6 +17,9 @@ enum SDKSynchronizerDependency: DependencyKey {
class LiveSDKSynchronizerClient: SDKSynchronizerClient {
private var cancellables: [AnyCancellable] = []
private(set) var synchronizer: SDKSynchronizer?
/// TODO [#497]: Since 0.17.0-beta SDKSynchronizer has `lastState` property which does exactly the same as `stateChanged`. Problem is that we have
/// synchronizer as optional. And now it would be complicated to handle the situation when `lastState` isn't always available. Let's handle this
/// in future.
private(set) var stateChanged: CurrentValueSubject<SDKSynchronizerState, Never>
private(set) var notificationCenter: NotificationCenterClient
private(set) var walletBirthday: BlockHeight?
@ -31,8 +34,8 @@ class LiveSDKSynchronizerClient: SDKSynchronizerClient {
synchronizer?.stop()
}
func prepareWith(initializer: Initializer) throws {
synchronizer = try SDKSynchronizer(initializer: initializer)
func prepareWith(initializer: Initializer, seedBytes: [UInt8]) throws {
let synchronizer = try SDKSynchronizer(initializer: initializer)
notificationCenter.publisherFor(.synchronizerStarted)?
.receive(on: DispatchQueue.main)
@ -57,7 +60,11 @@ class LiveSDKSynchronizerClient: SDKSynchronizerClient {
.sink { [weak self] _ in self?.synchronizerStopped() }
.store(in: &cancellables)
try synchronizer?.prepare()
guard try synchronizer.prepare(with: seedBytes) == .success else {
throw SynchronizerError.initFailed(message: "")
}
self.synchronizer = synchronizer
walletBirthday = initializer.walletBirthday
}
@ -94,8 +101,8 @@ class LiveSDKSynchronizerClient: SDKSynchronizerClient {
return SyncStatusSnapshot.snapshotFor(state: synchronizer.status)
}
func rewind(_ policy: RewindPolicy) throws {
try synchronizer?.rewind(policy)
func rewind(_ policy: RewindPolicy) async throws {
try await synchronizer?.rewind(policy)
}
func getShieldedBalance() -> WalletBalance? {
@ -163,35 +170,34 @@ class LiveSDKSynchronizerClient: SDKSynchronizerClient {
synchronizer?.getTransparentAddress(accountIndex: account)
}
func getShieldedAddress(account: Int) -> SaplingShieldedAddress? {
synchronizer?.getShieldedAddress(accountIndex: account)
func getSaplingAddress(accountIndex: Int) async -> SaplingAddress? {
await synchronizer?.getSaplingAddress(accountIndex: accountIndex)
}
func sendTransaction(
with spendingKey: String,
with spendingKey: UnifiedSpendingKey,
zatoshi: Zatoshi,
to recipientAddress: String,
memo: String?,
from account: Int
to recipientAddress: Recipient,
memo: Memo?
) -> Effect<Result<TransactionState, NSError>, Never> {
Deferred {
Future { [weak self] promise in
self?.synchronizer?.sendToAddress(
return Effect.run { [weak self] send in
do {
guard let synchronizer = self?.synchronizer else {
await send(.failure(SDKSynchronizerClientError.synchronizerNotInitialized as NSError))
return
}
let pendingTransaction = try await synchronizer.sendToAddress(
spendingKey: spendingKey,
zatoshi: zatoshi,
toAddress: recipientAddress,
memo: memo,
from: account) { result in
switch result {
case .failure(let error as NSError):
promise(.failure(error))
case .success(let pendingTx):
promise(.success(TransactionState(pendingTransaction: pendingTx)))
}
}
memo: memo
)
await send(.success(TransactionState(pendingTransaction: pendingTransaction)))
} catch {
await send(.failure(error as NSError))
}
}
.mapError { $0 as NSError }
.catchToEffect()
}
}

View File

@ -27,7 +27,7 @@ class MockSDKSynchronizerClient: SDKSynchronizerClient {
self.stateChanged = CurrentValueSubject<SDKSynchronizerState, Never>(.unknown)
}
func prepareWith(initializer: Initializer) throws { }
func prepareWith(initializer: Initializer, seedBytes: [UInt8]) throws { }
func start(retry: Bool) throws { }
@ -113,14 +113,16 @@ class MockSDKSynchronizerClient: SDKSynchronizerClient {
func getTransparentAddress(account: Int) -> TransparentAddress? { nil }
func getShieldedAddress(account: Int) -> SaplingShieldedAddress? { "ff3927e1f83df9b1b0dc75540ddc59ee435eecebae914d2e6dfe8576fbedc9a8" }
func getSaplingAddress(accountIndex account: Int) -> SaplingAddress? {
// swiftlint:disable:next force_try
try! SaplingAddress(encoding: "ztestsapling1edm52k336nk70gxqxedd89slrrf5xwnnp5rt6gqnk0tgw4mynv6fcx42ym6x27yac5amvfvwypz", network: .testnet)
}
func sendTransaction(
with spendingKey: String,
with spendingKey: UnifiedSpendingKey,
zatoshi: Zatoshi,
to recipientAddress: String,
memo: String?,
from account: Int
to recipientAddress: Recipient,
memo: Memo?
) -> Effect<Result<TransactionState, NSError>, Never> {
let transactionState = TransactionState(
expirationHeight: 40,

View File

@ -26,7 +26,7 @@ class NoopSDKSynchronizer: SDKSynchronizerClient {
self.stateChanged = CurrentValueSubject<SDKSynchronizerState, Never>(.unknown)
}
func prepareWith(initializer: Initializer) throws { }
func prepareWith(initializer: Initializer, seedBytes: [UInt8]) throws { }
func start(retry: Bool) throws { }
@ -36,7 +36,7 @@ class NoopSDKSynchronizer: SDKSynchronizerClient {
func statusSnapshot() -> SyncStatusSnapshot { .default }
func rewind(_ policy: RewindPolicy) throws { }
func rewind(_ policy: RewindPolicy) async throws { }
func getShieldedBalance() -> WalletBalance? { nil }
@ -50,14 +50,13 @@ class NoopSDKSynchronizer: SDKSynchronizerClient {
func getTransparentAddress(account: Int) -> TransparentAddress? { nil }
func getShieldedAddress(account: Int) -> SaplingShieldedAddress? { nil }
func getSaplingAddress(accountIndex account: Int) -> SaplingAddress? { nil }
func sendTransaction(
with spendingKey: String,
with spendingKey: UnifiedSpendingKey,
zatoshi: Zatoshi,
to recipientAddress: String,
memo: String?,
from account: Int
to recipientAddress: Recipient,
memo: Memo?
) -> Effect<Result<TransactionState, NSError>, Never> {
Effect(value: Result.failure(SynchronizerError.criticalError as NSError))
}
@ -67,7 +66,7 @@ class NoopSDKSynchronizer: SDKSynchronizerClient {
}
}
class T2estSDKSynchronizerClient: SDKSynchronizerClient {
class TestSDKSynchronizerClient: SDKSynchronizerClient {
private(set) var blockProcessor: CompactBlockProcessor?
private(set) var notificationCenter: NotificationCenterClient
private(set) var synchronizer: SDKSynchronizer?
@ -80,7 +79,7 @@ class T2estSDKSynchronizerClient: SDKSynchronizerClient {
self.stateChanged = CurrentValueSubject<SDKSynchronizerState, Never>(.unknown)
}
func prepareWith(initializer: Initializer) throws { }
func prepareWith(initializer: Initializer, seedBytes: [UInt8]) throws { }
func start(retry: Bool) throws { }
@ -161,15 +160,17 @@ class T2estSDKSynchronizerClient: SDKSynchronizerClient {
}
func getTransparentAddress(account: Int) -> TransparentAddress? { nil }
func getShieldedAddress(account: Int) -> SaplingShieldedAddress? { "ff3927e1f83df9b1b0dc75540ddc59ee435eecebae914d2e6dfe8576fbedc9a8" }
func getSaplingAddress(accountIndex account: Int) -> SaplingAddress? {
// swiftlint:disable:next force_try
try! SaplingAddress(encoding: "ztestsapling1edm52k336nk70gxqxedd89slrrf5xwnnp5rt6gqnk0tgw4mynv6fcx42ym6x27yac5amvfvwypz", network: .testnet)
}
func sendTransaction(
with spendingKey: String,
with spendingKey: UnifiedSpendingKey,
zatoshi: Zatoshi,
to recipientAddress: String,
memo: String?,
from account: Int
to recipientAddress: Recipient,
memo: Memo?
) -> Effect<Result<TransactionState, NSError>, Never> {
return Effect(value: Result.failure(SynchronizerError.criticalError as NSError))
}

View File

@ -30,7 +30,7 @@ extension ZcashSDKEnvironment {
struct ZcashSDKEnvironment {
let defaultBirthday: BlockHeight
let endpoint: LightWalletEndpoint
let isMainnet: () -> Bool
var isMainnet: Bool { network.networkType == .mainnet }
let lightWalletService: LightWalletService
let memoCharLimit: Int
let mnemonicWordsMaxCount: Int

View File

@ -14,15 +14,14 @@ extension ZcashSDKEnvironment: DependencyKey {
static let liveValue = Self(
defaultBirthday: BlockHeight(ZcashSDKConstants.defaultBlockHeight),
endpoint: LightWalletEndpoint(
address: ZcashSDKConstants.endpointMainnetAddress,
address: ZcashSDKConstants.endpointTestnetAddress,
port: ZcashSDKConstants.endpointPort,
secure: true,
streamingCallTimeoutInMillis: ZcashSDKConstants.streamingCallTimeoutInMillis
),
isMainnet: { true },
lightWalletService: LightWalletGRPCService(
endpoint: LightWalletEndpoint(
address: ZcashSDKConstants.endpointMainnetAddress,
address: ZcashSDKConstants.endpointTestnetAddress,
port: ZcashSDKConstants.endpointPort,
secure: true,
streamingCallTimeoutInMillis: ZcashSDKConstants.streamingCallTimeoutInMillis
@ -30,8 +29,8 @@ extension ZcashSDKEnvironment: DependencyKey {
),
memoCharLimit: 512,
mnemonicWordsMaxCount: ZcashSDKConstants.mnemonicWordsMaxCount,
network: ZcashNetworkBuilder.network(for: .mainnet),
network: ZcashNetworkBuilder.network(for: .testnet),
requiredTransactionConfirmations: ZcashSDKConstants.requiredTransactionConfirmations,
sdkVersion: "0.16.5-beta"
sdkVersion: "0.17.0-beta"
)
}

View File

@ -20,7 +20,6 @@ extension ZcashSDKEnvironment: TestDependencyKey {
secure: true,
streamingCallTimeoutInMillis: ZcashSDKConstants.streamingCallTimeoutInMillis
),
isMainnet: { false },
lightWalletService: LightWalletGRPCService(
endpoint: LightWalletEndpoint(
address: ZcashSDKConstants.endpointTestnetAddress,
@ -33,6 +32,6 @@ extension ZcashSDKEnvironment: TestDependencyKey {
mnemonicWordsMaxCount: ZcashSDKConstants.mnemonicWordsMaxCount,
network: ZcashNetworkBuilder.network(for: .testnet),
requiredTransactionConfirmations: ZcashSDKConstants.requiredTransactionConfirmations,
sdkVersion: "0.16.5-beta"
sdkVersion: "0.17.0-beta"
)
}

View File

@ -227,9 +227,10 @@ struct AppReducer: ReducerProtocol {
}
try mnemonic.isValid(storedWallet.seedPhrase)
let seedBytes = try mnemonic.toSeed(storedWallet.seedPhrase)
let birthday = state.storedWallet?.birthday ?? zcashSDKEnvironment.defaultBirthday
let initializer = try AppReducer.prepareInitializer(
for: storedWallet.seedPhrase,
birthday: birthday,
@ -238,13 +239,16 @@ struct AppReducer: ReducerProtocol {
mnemonic: mnemonic,
zcashSDKEnvironment: zcashSDKEnvironment
)
try sdkSynchronizer.prepareWith(initializer: initializer)
try sdkSynchronizer.prepareWith(initializer: initializer, seedBytes: seedBytes)
try sdkSynchronizer.start()
return .none
} catch {
state.appInitializationState = .failed
// TODO [#221]: error we need to handle (https://github.com/zcash/secant-ios-wallet/issues/221)
state.appInitializationState = .failed
return .none
}
return .none
case .checkBackupPhraseValidation:
guard let storedWallet = state.storedWallet else {
@ -393,10 +397,10 @@ extension AppReducer {
) throws -> Initializer {
do {
let seedBytes = try mnemonic.toSeed(seedPhrase)
let viewingKeys = try derivationTool.deriveUnifiedViewingKeysFromSeed(seedBytes, 1)
let spendingKey = try derivationTool.deriveSpendingKey(seedBytes, 0)
let viewingKey = try spendingKey.deriveFullViewingKey()
let network = zcashSDKEnvironment.network
let initializer = Initializer(
cacheDbURL: try databaseFiles.cacheDbURLFor(network),
dataDbURL: try databaseFiles.dataDbURLFor(network),
@ -405,7 +409,7 @@ extension AppReducer {
network: zcashSDKEnvironment.network,
spendParamsURL: try databaseFiles.spendParamsURLFor(network),
outputParamsURL: try databaseFiles.outputParamsURLFor(network),
viewingKeys: viewingKeys,
viewingKeys: [viewingKey],
walletBirthday: birthday
)

View File

@ -39,7 +39,7 @@ struct HomeReducer: ReducerProtocol {
var totalCurrencyBalance: Zatoshi {
Zatoshi.from(decimal: shieldedBalance.total.decimalValue.decimalValue * zecPrice)
}
var isDownloading: Bool {
if case .downloading = synchronizerStatusSnapshot.syncStatus {
return true
@ -62,6 +62,7 @@ struct HomeReducer: ReducerProtocol {
case onDisappear
case profile(ProfileReducer.Action)
case request(RequestReducer.Action)
case rewindDone(Bool, SettingsReducer.Action)
case send(SendFlowReducer.Action)
case scan(ScanReducer.Action)
case synchronizerStateChanged(SDKSynchronizerState)
@ -109,7 +110,6 @@ struct HomeReducer: ReducerProtocol {
.map(HomeReducer.Action.synchronizerStateChanged)
.eraseToEffect()
.cancellable(id: CancelId.self, cancelInFlight: true)
return .concatenate(Effect(value: .updateRoute(nil)), syncEffect)
} else {
return Effect(value: .updateRoute(.notEnoughFreeDiskSpace))
@ -154,28 +154,36 @@ struct HomeReducer: ReducerProtocol {
return .none
case .profile(.settings(.quickRescan)):
do {
try sdkSynchronizer.rewind(.quick)
} catch {
// TODO [#221]: error we need to handle (https://github.com/zcash/secant-ios-wallet/issues/221)
}
state.route = nil
return .none
return Effect.task {
do {
try await sdkSynchronizer.rewind(.quick)
return .rewindDone(true, .quickRescan)
} catch {
return .rewindDone(false, .quickRescan)
}
}
case .profile(.settings(.fullRescan)):
do {
try sdkSynchronizer.rewind(.birthday)
} catch {
// TODO [#221]: error we need to handle (https://github.com/zcash/secant-ios-wallet/issues/221)
}
state.route = nil
return .none
return Effect.task {
do {
try await sdkSynchronizer.rewind(.birthday)
return .rewindDone(true, .fullRescan)
} catch {
return .rewindDone(false, .fullRescan)
}
}
case .profile:
return .none
case .request:
return .none
case .rewindDone:
// TODO [#221]: error we need to handle (https://github.com/zcash/secant-ios-wallet/issues/221)
return .none
case .walletEvents(.updateRoute(.all)):
return state.drawerOverlay != .full ? Effect(value: .updateDrawer(.full)) : .none

View File

@ -24,6 +24,7 @@ struct ProfileReducer: ReducerProtocol {
case addressDetails(AddressDetailsReducer.Action)
case back
case onAppear
case onAppearFinished(String)
case settings(SettingsReducer.Action)
case updateRoute(ProfileReducer.State.Route?)
}
@ -44,12 +45,18 @@ struct ProfileReducer: ReducerProtocol {
Reduce { state, action in
switch action {
case .onAppear:
state.address = sdkSynchronizer.getShieldedAddress() ?? ""
return Effect.task {
let saplingAddress = await self.sdkSynchronizer.getSaplingAddress()?.stringEncoded ?? ""
return .onAppearFinished(saplingAddress)
}
case let .onAppearFinished(saplingAddress):
state.address = saplingAddress
state.appBuild = appVersion.appBuild()
state.appVersion = appVersion.appVersion()
state.sdkVersion = zcashSDKEnvironment.sdkVersion
return .none
case .back:
return .none

View File

@ -146,18 +146,23 @@ struct SendFlowReducer: ReducerProtocol {
do {
let storedWallet = try walletStorage.exportWallet()
let seedBytes = try mnemonic.toSeed(storedWallet.seedPhrase)
guard let spendingKey = try derivationTool.deriveSpendingKeys(seedBytes, 1).first else {
return Effect(value: .updateRoute(.failure))
}
let spendingKey = try derivationTool.deriveSpendingKey(seedBytes, 0)
state.isSendingTransaction = true
let memo: Memo?
if let memoText = state.addMemoState ? state.memoState.text : nil {
memo = try Memo(string: memoText)
} else {
memo = nil
}
let recipient = try Recipient(state.address, network: zcashSDKEnvironment.network.networkType)
let sendTransActionEffect = sdkSynchronizer.sendTransaction(
with: spendingKey,
zatoshi: state.amount,
to: state.address,
memo: state.addMemoState ? state.memoState.text : nil,
from: 0
to: recipient,
memo: memo
)
.receive(on: mainQueue)
.map(SendFlowReducer.Action.sendTransactionResult)

View File

@ -25,26 +25,26 @@ struct TransactionDetailView: View {
plainText("fee \(transaction.fee.decimalString()) ZEC", mark: .inactive)
plainText("total amount \(transaction.totalAmount.decimalString()) ZEC", mark: .inactive)
address(mark: .inactive, viewStore: viewStore)
if let text = transaction.memo { memo(text, viewStore, mark: .highlight) }
if let text = transaction.memo?.toString() { memo(text, viewStore, mark: .highlight) }
confirmed(mark: .success, viewStore: viewStore)
case .pending:
plainText("You are sending \(transaction.zecAmount.decimalString()) ZEC")
plainText("Includes network fee \(transaction.fee.decimalString()) ZEC", mark: .inactive)
plainText("total amount \(transaction.totalAmount.decimalString()) ZEC", mark: .inactive)
if let text = transaction.memo { memo(text, viewStore, mark: .inactive) }
if let text = transaction.memo?.toString() { memo(text, viewStore, mark: .inactive) }
confirming(mark: .highlight, viewStore: viewStore)
case .received:
plainText("You received \(transaction.zecAmount.decimalString()) ZEC")
plainText("fee \(transaction.fee.decimalString()) ZEC")
plainText("total amount \(transaction.totalAmount.decimalString()) ZEC")
address(mark: .inactive, viewStore: viewStore)
if let text = transaction.memo { memo(text, viewStore, mark: .highlight) }
if let text = transaction.memo?.toString() { memo(text, viewStore, mark: .highlight) }
confirmed(mark: .success, viewStore: viewStore)
case .failed:
plainText("You DID NOT send \(transaction.zecAmount.decimalString()) ZEC", mark: .fail)
plainText("Includes network fee \(transaction.fee.decimalString()) ZEC", mark: .inactive)
plainText("total amount \(transaction.totalAmount.decimalString()) ZEC", mark: .inactive)
if let text = transaction.memo { memo(text, viewStore, mark: .inactive) }
if let text = transaction.memo?.toString() { memo(text, viewStore, mark: .inactive) }
if let errorMessage = transaction.errorMessage {
plainTwoColumnText(left: "Failed", right: errorMessage, mark: .fail)
}
@ -253,12 +253,12 @@ struct TransactionDetail_Previews: PreviewProvider {
transaction:
TransactionState(
errorMessage: "possible roll back",
memo:
memo: try? Memo(string:
"""
Testing some long memo so I can see many lines of text \
instead of just one. This can take some time and I'm \
bored to write all this stuff.
""",
"""),
minedHeight: 1_875_256,
zAddress: "t1gXqfSSQt6WfpwyuCU3Wi7sSVZ66DYQ3Po",
fee: Zatoshi(1_000_000),

View File

@ -22,7 +22,7 @@ struct StoredWallet: Codable, Equatable {
extension StoredWallet {
static let placeholder = Self(
language: .english,
seedPhrase: "",
seedPhrase: RecoveryPhrase.testPhrase.joined(separator: " "),
version: 0,
birthday: 0,
hasUserPassedPhraseBackupTest: false

View File

@ -19,7 +19,7 @@ struct TransactionState: Equatable, Identifiable {
var errorMessage: String?
var expirationHeight = -1
var memo: String?
var memo: Memo?
var minedHeight = -1
var shielded = true
var zAddress: String?
@ -55,8 +55,8 @@ extension TransactionState {
zAddress = confirmedTransaction.toAddress
zecAmount = sent ? Zatoshi(-confirmedTransaction.value.amount) : confirmedTransaction.value
fee = Zatoshi(10)
if let memo = confirmedTransaction.memo {
self.memo = memo.asZcashTransactionMemo()
if let memoData = confirmedTransaction.memo {
self.memo = try? Memo(bytes: Array(memoData))
}
minedHeight = confirmedTransaction.minedHeight
}
@ -70,14 +70,20 @@ extension TransactionState {
.paid(success: pendingTransaction.isSubmitSuccess) :
.pending
expirationHeight = pendingTransaction.expiryHeight
zAddress = pendingTransaction.toAddress
zecAmount = pendingTransaction.value
fee = Zatoshi(10)
if let memo = pendingTransaction.memo {
self.memo = memo.asZcashTransactionMemo()
if let memoData = pendingTransaction.memo {
self.memo = try? Memo(bytes: Array(memoData))
}
minedHeight = pendingTransaction.minedHeight
errorMessage = pendingTransaction.errorMessage
switch pendingTransaction.recipient {
case let .address(recipient):
zAddress = recipient.stringEncoded
case .internalAccount:
zAddress = nil
}
}
}

View File

@ -0,0 +1,24 @@
//
// Memo+toString.swift
// secant-testnet
//
// Created by Michal Fousek on 18.11.2022.
//
import Foundation
import ZcashLightClientKit
extension Memo {
func toString() -> String? {
switch self {
case .empty:
return nil
case .text(let text):
return text.string
case .future(let memoBytes):
return Data(memoBytes.bytes).asZcashTransactionMemo()
case .arbitrary(let bytes):
return Data(bytes).asZcashTransactionMemo()
}
}
}

View File

@ -9,6 +9,8 @@ extension String {
}
}
extension String: Error {}
extension String {
private static let emailRegex = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,6}"
private static let phoneRegex = "^^\\+(?:[0-9]?){6,14}[0-9]$"

View File

@ -16,12 +16,12 @@ class AppInitializationTests: XCTestCase {
/// 3. The .initializeSDK is triggered to set the state of the app and preparing the synchronizer.
/// 4. The .checkBackupPhraseValidation is triggered to check the validation state.
/// 5. The user hasn't finished the backup phrase test so the display phrase is presented.
func testDidFinishLaunching_to_InitializedWallet() throws {
@MainActor func testDidFinishLaunching_to_InitializedWallet() async throws {
// setup the store and environment to be fully mocked
let testScheduler = DispatchQueue.test
let recoveryPhrase = RecoveryPhrase(words: try MnemonicClient.mock.randomMnemonicWords())
let phraseValidationState = RecoveryPhraseValidationFlowReducer.State(
phrase: recoveryPhrase,
missingIndices: [2, 0, 3, 5],
@ -36,23 +36,23 @@ class AppInitializationTests: XCTestCase {
],
route: nil
)
let recoveryPhraseRandomizer = RecoveryPhraseRandomizerClient(
random: { _ in
let missingIndices = [2, 0, 3, 5]
let missingWordChipKind = [
PhraseChip.Kind.unassigned(
word: "voice",
color: Asset.Colors.Buttons.activeButton.color
word: "voice",
color: Asset.Colors.Buttons.activeButton.color
),
PhraseChip.Kind.empty,
PhraseChip.Kind.unassigned(
word: "survey",
color: Asset.Colors.Buttons.activeButton.color
word: "survey",
color: Asset.Colors.Buttons.activeButton.color
),
PhraseChip.Kind.unassigned(
word: "spread",
color: Asset.Colors.Buttons.activeButton.color
word: "spread",
color: Asset.Colors.Buttons.activeButton.color
)
]
@ -69,7 +69,7 @@ class AppInitializationTests: XCTestCase {
)
}
)
let appState = AppReducer.State(
homeState: .placeholder,
onboardingState: .init(
@ -82,14 +82,14 @@ class AppInitializationTests: XCTestCase {
sandboxState: .placeholder,
welcomeState: .placeholder
)
let store = TestStore(
initialState: appState,
reducer: AppReducer()
) { dependencies in
dependencies.databaseFiles = .noOp
dependencies.databaseFiles.areDbFilesPresentFor = { _ in true }
dependencies.derivationTool = .noOp
dependencies.derivationTool = .liveValue
dependencies.mainQueue = testScheduler.eraseToAnyScheduler()
dependencies.mnemonic = .mock
dependencies.randomRecoveryPhrase = recoveryPhraseRandomizer
@ -98,37 +98,31 @@ class AppInitializationTests: XCTestCase {
}
// Root of the test, the app finished the launch process and triggers the checks and initializations.
store.send(.appDelegate(.didFinishLaunching))
_ = await store.send(.appDelegate(.didFinishLaunching))
// the 0.02 delay ensures keychain is ready
testScheduler.advance(by: 0.02)
await testScheduler.advance(by: 0.02)
// ad 1.
store.receive(.checkWalletInitialization)
await store.receive(.checkWalletInitialization)
// ad 2.
store.receive(.respondToWalletInitializationState(.initialized))
await store.receive(.respondToWalletInitializationState(.initialized))
// ad 3.
store.receive(.initializeSDK) { state in
state.storedWallet = StoredWallet(
language: .english,
seedPhrase: "",
version: 0,
birthday: 0,
hasUserPassedPhraseBackupTest: false
)
await store.receive(.initializeSDK) { state in
state.storedWallet = .placeholder
}
// ad 4.
store.receive(.checkBackupPhraseValidation) { state in
await store.receive(.checkBackupPhraseValidation) { state in
state.appInitializationState = .initialized
}
// the 3.0 delay ensures the welcome screen is visible till the initialization is done
testScheduler.advance(by: 3.00)
await testScheduler.advance(by: 3.00)
// ad 5.
store.receive(.updateRoute(.phraseDisplay)) { state in
await store.receive(.updateRoute(.phraseDisplay)) { state in
state.prevRoute = .welcome
state.internalRoute = .phraseDisplay
}

View File

@ -99,7 +99,7 @@ class AppTests: XCTestCase {
store.send(.respondToWalletInitializationState(.filesMissing)) { state in
state.appInitializationState = .filesMissing
}
store.receive(.initializeSDK) { state in
// failed is expected because environment is throwing errors
state.appInitializationState = .failed
@ -107,7 +107,7 @@ class AppTests: XCTestCase {
store.receive(.checkBackupPhraseValidation)
}
func testRespondToWalletInitializationState_Initialized() throws {
let store = TestStore(
initialState: .placeholder,
@ -116,9 +116,9 @@ class AppTests: XCTestCase {
dependencies.walletStorage = .noOp
dependencies.walletStorage.exportWallet = { throw "export failed" }
}
store.send(.respondToWalletInitializationState(.initialized))
store.receive(.initializeSDK) { state in
// failed is expected because environment is throwing errors
state.appInitializationState = .failed

View File

@ -168,8 +168,8 @@ class HomeTests: XCTestCase {
// the .onDisappear action cancles the observer of the synchronizer status change.
store.send(.onDisappear)
}
func testQuickRescan_ResetToHomeScreen() throws {
@MainActor func testQuickRescan_ResetToHomeScreen() async throws {
let homeState = HomeReducer.State(
route: .profile,
balanceBreakdownState: .placeholder,
@ -182,18 +182,20 @@ class HomeTests: XCTestCase {
synchronizerStatusSnapshot: .default,
walletEventsState: .emptyPlaceHolder
)
let store = TestStore(
initialState: homeState,
reducer: HomeReducer()
)
store.send(.profile(.settings(.quickRescan))) { state in
_ = await store.send(.profile(.settings(.quickRescan))) { state in
state.route = nil
}
await store.receive(.rewindDone(true, .quickRescan))
}
func testFullRescan_ResetToHomeScreen() throws {
@MainActor func testFullRescan_ResetToHomeScreen() async throws {
let homeState = HomeReducer.State(
route: .profile,
balanceBreakdownState: .placeholder,
@ -206,14 +208,16 @@ class HomeTests: XCTestCase {
synchronizerStatusSnapshot: .default,
walletEventsState: .emptyPlaceHolder
)
let store = TestStore(
initialState: homeState,
reducer: HomeReducer()
)
store.send(.profile(.settings(.fullRescan))) { state in
_ = await store.send(.profile(.settings(.fullRescan))) { state in
state.route = nil
}
await store.receive(.rewindDone(true, .fullRescan))
}
}

View File

@ -10,7 +10,7 @@ import XCTest
import ComposableArchitecture
class ProfileTests: XCTestCase {
func testSynchronizerStateChanged_AnyButSynced() throws {
@MainActor func testSynchronizerStateChanged_AnyButSynced() async throws {
let store = TestStore(
initialState: .placeholder,
reducer: ProfileReducer()
@ -19,11 +19,13 @@ class ProfileTests: XCTestCase {
dependencies.sdkSynchronizer = SDKSynchronizerDependency.mock
}
store.send(.onAppear) { state in
state.address = "ff3927e1f83df9b1b0dc75540ddc59ee435eecebae914d2e6dfe8576fbedc9a8"
_ = await store.send(.onAppear)
await store.receive(.onAppearFinished("ztestsapling1edm52k336nk70gxqxedd89slrrf5xwnnp5rt6gqnk0tgw4mynv6fcx42ym6x27yac5amvfvwypz")) { state in
state.address = "ztestsapling1edm52k336nk70gxqxedd89slrrf5xwnnp5rt6gqnk0tgw4mynv6fcx42ym6x27yac5amvfvwypz"
state.appVersion = "0.0.1"
state.appBuild = "31"
state.sdkVersion = "0.16.5-beta"
state.sdkVersion = "0.17.0-beta"
}
}
}

View File

@ -27,34 +27,45 @@ class SendTests: XCTestCase {
usNumberFormatter.locale = Locale(identifier: "en_US")
}
func testSendSucceeded() throws {
@MainActor func testSendSucceeded() async throws {
// the test needs to pass the exportWallet() so we simulate some in the keychain
try storage.importWallet(bip39: "one two three", birthday: nil)
// setup the store and environment to be fully mocked
let testScheduler = DispatchQueue.test
var initialState = SendFlowReducer.State.placeholder
initialState.transactionAddressInputState = TransactionAddressTextFieldReducer.State(
textFieldState:
TCATextFieldReducer.State(
validationType: nil,
text: "ztestsapling1psqa06alcfj9t6s246hht3n7kcw5h900r6z40qnuu7l58qs55kzeqa98879z9hzy596dca4hmsr"
)
)
let store = TestStore(
initialState: .placeholder,
initialState: initialState,
reducer: SendFlowReducer()
) { dependencies in
dependencies.derivationTool = .noOp
dependencies.derivationTool.deriveSpendingKeys = { _, _ in [""] }
dependencies.derivationTool = .liveValue
dependencies.mainQueue = testScheduler.eraseToAnyScheduler()
dependencies.mnemonic = .mock
dependencies.mnemonic = .liveValue
dependencies.sdkSynchronizer = SDKSynchronizerDependency.mock
dependencies.walletStorage.exportWallet = { .placeholder }
dependencies.walletStorage = .noOp
}
// simulate the sending confirmation button to be pressed
store.send(.sendConfirmationPressed) { state in
_ = await store.send(.sendConfirmationPressed) { state in
// once sending is confirmed, the attemts to try to send again by pressing the button
// needs to be eliminated, indicated by the flag `isSendingTransaction`, need to be true
state.isSendingTransaction = true
}
testScheduler.advance(by: 0.01)
await testScheduler.advance(by: 0.01)
let transactionState = TransactionState(
expirationHeight: 40,
memo: "test",
memo: try? Memo(string: "test"),
minedHeight: 50,
shielded: true,
zAddress: "tteafadlamnelkqe",
@ -66,50 +77,59 @@ class SendTests: XCTestCase {
)
// first it's expected that progress screen is showed
store.receive(.updateRoute(.inProgress)) { state in
await store.receive(.updateRoute(.inProgress)) { state in
state.route = .inProgress
}
// check the success transaction to be received back
store.receive(.sendTransactionResult(Result.success(transactionState))) { state in
await store.receive(.sendTransactionResult(Result.success(transactionState))) { state in
// from this moment on the sending next transaction is allowed again
// the 'isSendingTransaction' needs to be false again
state.isSendingTransaction = false
}
// all went well, the success screen is triggered
store.receive(.updateRoute(.success)) { state in
await store.receive(.updateRoute(.success)) { state in
state.route = .success
}
}
func testSendSucceededWithoutMemo() throws {
@MainActor func testSendSucceededWithoutMemo() async throws {
// the test needs to pass the exportWallet() so we simulate some in the keychain
try storage.importWallet(bip39: "one two three", birthday: nil)
// setup the store and environment to be fully mocked
let testScheduler = DispatchQueue.test
var state = SendFlowReducer.State.placeholder
state.addMemoState = false
state.transactionAddressInputState = TransactionAddressTextFieldReducer.State(
textFieldState:
TCATextFieldReducer.State(
validationType: nil,
text: "ztestsapling1psqa06alcfj9t6s246hht3n7kcw5h900r6z40qnuu7l58qs55kzeqa98879z9hzy596dca4hmsr"
)
)
let store = TestStore(
initialState: state,
reducer: SendFlowReducer()
) { dependencies in
dependencies.derivationTool = .noOp
dependencies.derivationTool.deriveSpendingKeys = { _, _ in [""] }
dependencies.derivationTool = .liveValue
dependencies.mainQueue = testScheduler.eraseToAnyScheduler()
dependencies.mnemonic = .mock
dependencies.mnemonic = .liveValue
dependencies.sdkSynchronizer = SDKSynchronizerDependency.mock
dependencies.walletStorage.exportWallet = { .placeholder }
dependencies.walletStorage = .noOp
}
// simulate the sending confirmation button to be pressed
store.send(.sendConfirmationPressed) { state in
_ = await store.send(.sendConfirmationPressed) { state in
// once sending is confirmed, the attemts to try to send again by pressing the button
// needs to be eliminated, indicated by the flag `isSendingTransaction`, need to be true
state.isSendingTransaction = true
}
testScheduler.advance(by: 0.01)
await testScheduler.advance(by: 0.01)
let transactionState = TransactionState(
expirationHeight: 40,
@ -125,61 +145,72 @@ class SendTests: XCTestCase {
)
// first it's expected that progress screen is showed
store.receive(.updateRoute(.inProgress)) { state in
await store.receive(.updateRoute(.inProgress)) { state in
state.route = .inProgress
}
// check the success transaction to be received back
store.receive(.sendTransactionResult(Result.success(transactionState))) { state in
await store.receive(.sendTransactionResult(Result.success(transactionState))) { state in
// from this moment on the sending next transaction is allowed again
// the 'isSendingTransaction' needs to be false again
state.isSendingTransaction = false
}
// all went well, the success screen is triggered
store.receive(.updateRoute(.success)) { state in
await store.receive(.updateRoute(.success)) { state in
state.route = .success
}
}
func testSendFailed() throws {
@MainActor func testSendFailed() async throws {
// the test needs to pass the exportWallet() so we simulate some in the keychain
try storage.importWallet(bip39: "one two three", birthday: nil)
// setup the store and environment to be fully mocked
let testScheduler = DispatchQueue.test
var initialState = SendFlowReducer.State.placeholder
initialState.transactionAddressInputState = TransactionAddressTextFieldReducer.State(
textFieldState:
TCATextFieldReducer.State(
validationType: nil,
text: "ztestsapling1psqa06alcfj9t6s246hht3n7kcw5h900r6z40qnuu7l58qs55kzeqa98879z9hzy596dca4hmsr"
)
)
let store = TestStore(
initialState: .placeholder,
initialState: initialState,
reducer: SendFlowReducer()
) { dependencies in
dependencies.derivationTool = .noOp
dependencies.derivationTool.deriveSpendingKeys = { _, _ in [""] }
dependencies.derivationTool = .liveValue
dependencies.mainQueue = testScheduler.eraseToAnyScheduler()
dependencies.mnemonic = .mock
dependencies.walletStorage.exportWallet = { .placeholder }
dependencies.mnemonic = .liveValue
dependencies.walletStorage = .noOp
}
// simulate the sending confirmation button to be pressed
store.send(.sendConfirmationPressed) { state in
_ = await store.send(.sendConfirmationPressed) { state in
// once sending is confirmed, the attemts to try to send again by pressing the button
// needs to be eliminated, indicated by the flag `isSendingTransaction`, need to be true
state.isSendingTransaction = true
}
testScheduler.advance(by: 0.01)
await testScheduler.advance(by: 0.01)
// first it's expected that progress screen is showed
store.receive(.updateRoute(.inProgress)) { state in
await store.receive(.updateRoute(.inProgress)) { state in
state.route = .inProgress
}
// check the failure transaction to be received back
store.receive(.sendTransactionResult(Result.failure(SynchronizerError.criticalError as NSError))) { state in
await store.receive(.sendTransactionResult(Result.failure(SynchronizerError.criticalError as NSError))) { state in
// from this moment on the sending next transaction is allowed again
// the 'isSendingTransaction' needs to be false again
state.isSendingTransaction = false
}
// the failure screen is triggered as expected
store.receive(.updateRoute(.failure)) { state in
await store.receive(.updateRoute(.failure)) { state in
state.route = .failure
}
}

View File

@ -32,7 +32,7 @@ class TransactionConfirmationSnapshotTests: XCTestCase {
let store = Store(
initialState: state,
reducer: SendFlowReducer()
.dependency(\.derivationTool, .live(derivationTool: DerivationTool(networkType: .testnet)))
.dependency(\.derivationTool, .live(networkType: .testnet))
.dependency(\.mainQueue, DispatchQueue.main.eraseToAnyScheduler())
.dependency(\.numberFormatter, .live())
.dependency(\.walletStorage, .live())
@ -62,7 +62,7 @@ class TransactionConfirmationSnapshotTests: XCTestCase {
let store = Store(
initialState: state,
reducer: SendFlowReducer()
.dependency(\.derivationTool, .live(derivationTool: DerivationTool(networkType: .testnet)))
.dependency(\.derivationTool, .live(networkType: .testnet))
.dependency(\.mainQueue, DispatchQueue.main.eraseToAnyScheduler())
.dependency(\.numberFormatter, .live())
.dependency(\.walletStorage, .live())

View File

@ -32,7 +32,7 @@ class TransactionSendingTests: XCTestCase {
let store = Store(
initialState: state,
reducer: SendFlowReducer()
.dependency(\.derivationTool, .live(derivationTool: DerivationTool(networkType: .testnet)))
.dependency(\.derivationTool, .live(networkType: .testnet))
.dependency(\.mainQueue, DispatchQueue.main.eraseToAnyScheduler())
.dependency(\.numberFormatter, .live())
.dependency(\.walletStorage, .live())

View File

@ -64,12 +64,12 @@ class WalletEventsSnapshotTests: XCTestCase {
func testWalletEventDetailSnapshot_sent() throws {
let transaction = TransactionState(
memo:
memo: try? Memo(string:
"""
Testing some long memo so I can see many lines of text \
instead of just one. This can take some time and I'm \
bored to write all this stuff.
""",
"""),
minedHeight: 1_875_256,
zAddress: "t1gXqfSSQt6WfpwyuCU3Wi7sSVZ66DYQ3Po",
fee: Zatoshi(1_000_000),
@ -111,12 +111,12 @@ class WalletEventsSnapshotTests: XCTestCase {
func testWalletEventDetailSnapshot_received() throws {
let transaction = TransactionState(
memo:
memo: try? Memo(string:
"""
Testing some long memo so I can see many lines of text \
instead of just one. This can take some time and I'm \
bored to write all this stuff.
""",
"""),
minedHeight: 1_875_256,
zAddress: "t1gXqfSSQt6WfpwyuCU3Wi7sSVZ66DYQ3Po",
fee: Zatoshi(1_000_000),
@ -158,12 +158,12 @@ class WalletEventsSnapshotTests: XCTestCase {
func testWalletEventDetailSnapshot_pending() throws {
let transaction = TransactionState(
memo:
memo: try? Memo(string:
"""
Testing some long memo so I can see many lines of text \
instead of just one. This can take some time and I'm \
bored to write all this stuff.
""",
"""),
minedHeight: 1_875_256,
zAddress: "t1gXqfSSQt6WfpwyuCU3Wi7sSVZ66DYQ3Po",
fee: Zatoshi(1_000_000),
@ -211,12 +211,12 @@ class WalletEventsSnapshotTests: XCTestCase {
func testWalletEventDetailSnapshot_failed() throws {
let transaction = TransactionState(
errorMessage: "possible roll back",
memo:
memo: try? Memo(string:
"""
Testing some long memo so I can see many lines of text \
instead of just one. This can take some time and I'm \
bored to write all this stuff.
""",
"""),
minedHeight: 1_875_256,
zAddress: "t1gXqfSSQt6WfpwyuCU3Wi7sSVZ66DYQ3Po",
fee: Zatoshi(1_000_000),

View File

@ -9,8 +9,6 @@ import XCTest
import ZcashLightClientKit
@testable import secant_testnet
extension String: Error {}
extension DatabaseFiles.DatabaseFilesError {
var debugValue: String {
switch self {