mirror of
https://github.com/lil-org/tokenary.git
synced 2024-12-15 15:05:15 +03:00
Merge branch 'main' into feature/resolve-todo
This commit is contained in:
commit
90ae7b92a0
@ -7,20 +7,6 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
0DB729122674E2DB0011F7A1 /* EIP712ParameterEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB729022674E2DB0011F7A1 /* EIP712ParameterEncoder.swift */; };
|
||||
0DB729142674E2DB0011F7A1 /* EIP712Parameter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB729042674E2DB0011F7A1 /* EIP712Parameter.swift */; };
|
||||
0DB729152674E2DB0011F7A1 /* EIP712Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB729052674E2DB0011F7A1 /* EIP712Error.swift */; };
|
||||
0DB729162674E2DB0011F7A1 /* EIP712SimpleValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB729062674E2DB0011F7A1 /* EIP712SimpleValue.swift */; };
|
||||
0DB729172674E2DB0011F7A1 /* EIP712StructType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB729072674E2DB0011F7A1 /* EIP712StructType.swift */; };
|
||||
0DB729182674E2DB0011F7A1 /* EIP712Signer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB729082674E2DB0011F7A1 /* EIP712Signer.swift */; };
|
||||
0DB729192674E2DB0011F7A1 /* EIP712TypedData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB729092674E2DB0011F7A1 /* EIP712TypedData.swift */; };
|
||||
0DB7291A2674E2DB0011F7A1 /* EIP712Type.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB7290A2674E2DB0011F7A1 /* EIP712Type.swift */; };
|
||||
0DB7291B2674E2DB0011F7A1 /* EIP712Hash.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB7290B2674E2DB0011F7A1 /* EIP712Hash.swift */; };
|
||||
0DB7291C2674E2DB0011F7A1 /* EIP712Value.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB7290D2674E2DB0011F7A1 /* EIP712Value.swift */; };
|
||||
0DB7291D2674E2DB0011F7A1 /* EIP712Hashable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB7290E2674E2DB0011F7A1 /* EIP712Hashable.swift */; };
|
||||
0DB7291E2674E2DB0011F7A1 /* EIP712Signable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB7290F2674E2DB0011F7A1 /* EIP712Signable.swift */; };
|
||||
0DB7291F2674E2DB0011F7A1 /* EIP712Representable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB729102674E2DB0011F7A1 /* EIP712Representable.swift */; };
|
||||
0DB729202674E2DB0011F7A1 /* EIP712Domain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DB729112674E2DB0011F7A1 /* EIP712Domain.swift */; };
|
||||
0DC850E726B73A5900809E82 /* AuthenticationReason.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DC850E626B73A5900809E82 /* AuthenticationReason.swift */; };
|
||||
28BDF30EBC80362870C988B6 /* Pods_Encrypted_Ink.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3E2A642C960E4952955E6E82 /* Pods_Encrypted_Ink.framework */; };
|
||||
2C03D1D2269B407900EF10EA /* NetworkMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C03D1D1269B407900EF10EA /* NetworkMonitor.swift */; };
|
||||
@ -32,10 +18,12 @@
|
||||
2C1995562674D0F300A8E370 /* Ethereum.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C1995552674D0F300A8E370 /* Ethereum.swift */; };
|
||||
2C208A9F26813408005BA500 /* Secrets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C208A9E26813408005BA500 /* Secrets.swift */; };
|
||||
2C528A16267FA8EB00CA3ADD /* Defaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C528A15267FA8EB00CA3ADD /* Defaults.swift */; };
|
||||
2C603D0226B6E13F00956955 /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C603D0126B6E13F00956955 /* String.swift */; };
|
||||
2C6706A5267A6BFE006AAEF2 /* Bundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C6706A4267A6BFE006AAEF2 /* Bundle.swift */; };
|
||||
2C6B964C26B9D92500D2C819 /* NSColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C6B964B26B9D92500D2C819 /* NSColor.swift */; };
|
||||
2C6B964F26B9D98C00D2C819 /* Colors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2C6B964E26B9D98C00D2C819 /* Colors.xcassets */; };
|
||||
2C78F8282683BDCC00C10670 /* Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C78F8272683BDCC00C10670 /* Alert.swift */; };
|
||||
2C797E7E267BB88800F2CE2D /* WelcomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C797E7D267BB88800F2CE2D /* WelcomeViewController.swift */; };
|
||||
2C8A09B52675101300993638 /* AccountsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C8A09B42675101300993638 /* AccountsService.swift */; };
|
||||
2C8A09C6267513FC00993638 /* Agent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C8A09C5267513FC00993638 /* Agent.swift */; };
|
||||
2C8A09D42675184700993638 /* Window.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C8A09D32675184700993638 /* Window.swift */; };
|
||||
2C8A09D726751A0C00993638 /* WalletConnect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C8A09D626751A0C00993638 /* WalletConnect.swift */; };
|
||||
@ -52,28 +40,16 @@
|
||||
2CC0CDBE2692027E0072922A /* PriceService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CC0CDBD2692027E0072922A /* PriceService.swift */; };
|
||||
2CC8946F269A2E8C00879245 /* SessionStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CC8946E269A2E8C00879245 /* SessionStorage.swift */; };
|
||||
2CC89471269A334A00879245 /* UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CC89470269A334A00879245 /* UserDefaults.swift */; };
|
||||
2CD0669126B5537B00728C20 /* InkWallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD0668B26B2142000728C20 /* InkWallet.swift */; };
|
||||
2CD0669226B5537B00728C20 /* WalletsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD0668926B213E500728C20 /* WalletsManager.swift */; };
|
||||
2CD0B3F526A0DAA900488D92 /* NSPasteboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD0B3F426A0DAA900488D92 /* NSPasteboard.swift */; };
|
||||
2CD0B3F726AC619900488D92 /* AddAccountOptionCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD0B3F626AC619900488D92 /* AddAccountOptionCellView.swift */; };
|
||||
2CDAB3722675B3F0009F8B97 /* PasswordViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CDAB3712675B3F0009F8B97 /* PasswordViewController.swift */; };
|
||||
2CE3D012267F73C00032A62E /* Transaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CE3D011267F73C00032A62E /* Transaction.swift */; };
|
||||
2CE3D015267F73E80032A62E /* AccountWithKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CE3D014267F73E80032A62E /* AccountWithKey.swift */; };
|
||||
2CE3D015267F73E80032A62E /* LegacyAccountWithKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CE3D014267F73E80032A62E /* LegacyAccountWithKey.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
0DB729022674E2DB0011F7A1 /* EIP712ParameterEncoder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EIP712ParameterEncoder.swift; sourceTree = "<group>"; };
|
||||
0DB729042674E2DB0011F7A1 /* EIP712Parameter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EIP712Parameter.swift; sourceTree = "<group>"; };
|
||||
0DB729052674E2DB0011F7A1 /* EIP712Error.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EIP712Error.swift; sourceTree = "<group>"; };
|
||||
0DB729062674E2DB0011F7A1 /* EIP712SimpleValue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EIP712SimpleValue.swift; sourceTree = "<group>"; };
|
||||
0DB729072674E2DB0011F7A1 /* EIP712StructType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EIP712StructType.swift; sourceTree = "<group>"; };
|
||||
0DB729082674E2DB0011F7A1 /* EIP712Signer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EIP712Signer.swift; sourceTree = "<group>"; };
|
||||
0DB729092674E2DB0011F7A1 /* EIP712TypedData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EIP712TypedData.swift; sourceTree = "<group>"; };
|
||||
0DB7290A2674E2DB0011F7A1 /* EIP712Type.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EIP712Type.swift; sourceTree = "<group>"; };
|
||||
0DB7290B2674E2DB0011F7A1 /* EIP712Hash.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EIP712Hash.swift; sourceTree = "<group>"; };
|
||||
0DB7290D2674E2DB0011F7A1 /* EIP712Value.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EIP712Value.swift; sourceTree = "<group>"; };
|
||||
0DB7290E2674E2DB0011F7A1 /* EIP712Hashable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EIP712Hashable.swift; sourceTree = "<group>"; };
|
||||
0DB7290F2674E2DB0011F7A1 /* EIP712Signable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EIP712Signable.swift; sourceTree = "<group>"; };
|
||||
0DB729102674E2DB0011F7A1 /* EIP712Representable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EIP712Representable.swift; sourceTree = "<group>"; };
|
||||
0DB729112674E2DB0011F7A1 /* EIP712Domain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EIP712Domain.swift; sourceTree = "<group>"; };
|
||||
0DC850E626B73A5900809E82 /* AuthenticationReason.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationReason.swift; sourceTree = "<group>"; };
|
||||
2C03D1D1269B407900EF10EA /* NetworkMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkMonitor.swift; sourceTree = "<group>"; };
|
||||
2C03D1D4269B428C00EF10EA /* Notification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notification.swift; sourceTree = "<group>"; };
|
||||
@ -87,10 +63,12 @@
|
||||
2C1995552674D0F300A8E370 /* Ethereum.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Ethereum.swift; sourceTree = "<group>"; };
|
||||
2C208A9E26813408005BA500 /* Secrets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Secrets.swift; sourceTree = "<group>"; };
|
||||
2C528A15267FA8EB00CA3ADD /* Defaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Defaults.swift; sourceTree = "<group>"; };
|
||||
2C603D0126B6E13F00956955 /* String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = String.swift; sourceTree = "<group>"; };
|
||||
2C6706A4267A6BFE006AAEF2 /* Bundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bundle.swift; sourceTree = "<group>"; };
|
||||
2C6B964B26B9D92500D2C819 /* NSColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSColor.swift; sourceTree = "<group>"; };
|
||||
2C6B964E26B9D98C00D2C819 /* Colors.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Colors.xcassets; sourceTree = "<group>"; };
|
||||
2C78F8272683BDCC00C10670 /* Alert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Alert.swift; sourceTree = "<group>"; };
|
||||
2C797E7D267BB88800F2CE2D /* WelcomeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeViewController.swift; sourceTree = "<group>"; };
|
||||
2C8A09B42675101300993638 /* AccountsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsService.swift; sourceTree = "<group>"; };
|
||||
2C8A09C5267513FC00993638 /* Agent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Agent.swift; sourceTree = "<group>"; };
|
||||
2C8A09D32675184700993638 /* Window.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Window.swift; sourceTree = "<group>"; };
|
||||
2C8A09D626751A0C00993638 /* WalletConnect.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletConnect.swift; sourceTree = "<group>"; };
|
||||
@ -113,7 +91,7 @@
|
||||
2CD0B3F626AC619900488D92 /* AddAccountOptionCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddAccountOptionCellView.swift; sourceTree = "<group>"; };
|
||||
2CDAB3712675B3F0009F8B97 /* PasswordViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordViewController.swift; sourceTree = "<group>"; };
|
||||
2CE3D011267F73C00032A62E /* Transaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Transaction.swift; sourceTree = "<group>"; };
|
||||
2CE3D014267F73E80032A62E /* AccountWithKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountWithKey.swift; sourceTree = "<group>"; };
|
||||
2CE3D014267F73E80032A62E /* LegacyAccountWithKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyAccountWithKey.swift; sourceTree = "<group>"; };
|
||||
35AD6E3AC630C8A9B4EC16D9 /* Pods-Encrypted Ink.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Encrypted Ink.release.xcconfig"; path = "Target Support Files/Pods-Encrypted Ink/Pods-Encrypted Ink.release.xcconfig"; sourceTree = "<group>"; };
|
||||
3E2A642C960E4952955E6E82 /* Pods_Encrypted_Ink.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Encrypted_Ink.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
A858B0F7D88913EAB1FA50B0 /* Pods-Encrypted Ink.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Encrypted Ink.debug.xcconfig"; path = "Target Support Files/Pods-Encrypted Ink/Pods-Encrypted Ink.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
@ -131,35 +109,6 @@
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
0DB729012674E2DB0011F7A1 /* EIP712 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0DB729022674E2DB0011F7A1 /* EIP712ParameterEncoder.swift */,
|
||||
0DB729042674E2DB0011F7A1 /* EIP712Parameter.swift */,
|
||||
0DB729052674E2DB0011F7A1 /* EIP712Error.swift */,
|
||||
0DB729062674E2DB0011F7A1 /* EIP712SimpleValue.swift */,
|
||||
0DB729072674E2DB0011F7A1 /* EIP712StructType.swift */,
|
||||
0DB729082674E2DB0011F7A1 /* EIP712Signer.swift */,
|
||||
0DB729092674E2DB0011F7A1 /* EIP712TypedData.swift */,
|
||||
0DB7290A2674E2DB0011F7A1 /* EIP712Type.swift */,
|
||||
0DB7290B2674E2DB0011F7A1 /* EIP712Hash.swift */,
|
||||
0DB729112674E2DB0011F7A1 /* EIP712Domain.swift */,
|
||||
0DB7290C2674E2DB0011F7A1 /* Protocols */,
|
||||
);
|
||||
path = EIP712;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
0DB7290C2674E2DB0011F7A1 /* Protocols */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0DB7290D2674E2DB0011F7A1 /* EIP712Value.swift */,
|
||||
0DB7290E2674E2DB0011F7A1 /* EIP712Hashable.swift */,
|
||||
0DB7290F2674E2DB0011F7A1 /* EIP712Signable.swift */,
|
||||
0DB729102674E2DB0011F7A1 /* EIP712Representable.swift */,
|
||||
);
|
||||
path = Protocols;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
0DC850E926B73A8200809E82 /* Models */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -203,6 +152,7 @@
|
||||
2C8A09C2267513A700993638 /* Ethereum */,
|
||||
2C8A09E5267595C200993638 /* Views */,
|
||||
2C1995432674C4BA00A8E370 /* Assets.xcassets */,
|
||||
2C6B964E26B9D98C00D2C819 /* Colors.xcassets */,
|
||||
2C1995452674C4BA00A8E370 /* Main.storyboard */,
|
||||
2C208AA126813497005BA500 /* Supporting Files */,
|
||||
);
|
||||
@ -224,6 +174,8 @@
|
||||
children = (
|
||||
2C6706A4267A6BFE006AAEF2 /* Bundle.swift */,
|
||||
2C03D1D4269B428C00EF10EA /* Notification.swift */,
|
||||
2C603D0126B6E13F00956955 /* String.swift */,
|
||||
2C6B964B26B9D92500D2C819 /* NSColor.swift */,
|
||||
2CD0B3F426A0DAA900488D92 /* NSPasteboard.swift */,
|
||||
2CC89470269A334A00879245 /* UserDefaults.swift */,
|
||||
);
|
||||
@ -234,9 +186,8 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
2C1995552674D0F300A8E370 /* Ethereum.swift */,
|
||||
2CE3D014267F73E80032A62E /* AccountWithKey.swift */,
|
||||
2CE3D014267F73E80032A62E /* LegacyAccountWithKey.swift */,
|
||||
2CE3D011267F73C00032A62E /* Transaction.swift */,
|
||||
0DB729012674E2DB0011F7A1 /* EIP712 */,
|
||||
);
|
||||
path = Ethereum;
|
||||
sourceTree = "<group>";
|
||||
@ -270,7 +221,6 @@
|
||||
2C91742B267D2A7900049075 /* Services */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
2C8A09B42675101300993638 /* AccountsService.swift */,
|
||||
2C03D1D1269B407900EF10EA /* NetworkMonitor.swift */,
|
||||
2C901C4C268A033100D0926A /* GasService.swift */,
|
||||
2CC0CDBD2692027E0072922A /* PriceService.swift */,
|
||||
@ -366,6 +316,7 @@
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
2C6B964F26B9D98C00D2C819 /* Colors.xcassets in Resources */,
|
||||
2C1995442674C4BA00A8E370 /* Assets.xcassets in Resources */,
|
||||
2C1995472674C4BA00A8E370 /* Main.storyboard in Resources */,
|
||||
);
|
||||
@ -438,47 +389,36 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
2C901C4A2689F01700D0926A /* Strings.swift in Sources */,
|
||||
0DB729152674E2DB0011F7A1 /* EIP712Error.swift in Sources */,
|
||||
2C6706A5267A6BFE006AAEF2 /* Bundle.swift in Sources */,
|
||||
2CC0CDBE2692027E0072922A /* PriceService.swift in Sources */,
|
||||
2C8A09C6267513FC00993638 /* Agent.swift in Sources */,
|
||||
2C8A09D42675184700993638 /* Window.swift in Sources */,
|
||||
0DB7291A2674E2DB0011F7A1 /* EIP712Type.swift in Sources */,
|
||||
2C208A9F26813408005BA500 /* Secrets.swift in Sources */,
|
||||
2CC8946F269A2E8C00879245 /* SessionStorage.swift in Sources */,
|
||||
0DB7291D2674E2DB0011F7A1 /* EIP712Hashable.swift in Sources */,
|
||||
0DB729162674E2DB0011F7A1 /* EIP712SimpleValue.swift in Sources */,
|
||||
0DB729122674E2DB0011F7A1 /* EIP712ParameterEncoder.swift in Sources */,
|
||||
0DC850E726B73A5900809E82 /* AuthenticationReason.swift in Sources */,
|
||||
2CD0669126B5537B00728C20 /* InkWallet.swift in Sources */,
|
||||
2C8A09D726751A0C00993638 /* WalletConnect.swift in Sources */,
|
||||
2C03D1D2269B407900EF10EA /* NetworkMonitor.swift in Sources */,
|
||||
2C8A09E326757FC000993638 /* AccountCellView.swift in Sources */,
|
||||
0DB729202674E2DB0011F7A1 /* EIP712Domain.swift in Sources */,
|
||||
0DB729182674E2DB0011F7A1 /* EIP712Signer.swift in Sources */,
|
||||
2C6B964C26B9D92500D2C819 /* NSColor.swift in Sources */,
|
||||
2C603D0226B6E13F00956955 /* String.swift in Sources */,
|
||||
2CC89471269A334A00879245 /* UserDefaults.swift in Sources */,
|
||||
2C78F8282683BDCC00C10670 /* Alert.swift in Sources */,
|
||||
2C8A09EE2675965F00993638 /* WaitingViewController.swift in Sources */,
|
||||
2C797E7E267BB88800F2CE2D /* WelcomeViewController.swift in Sources */,
|
||||
0DB729192674E2DB0011F7A1 /* EIP712TypedData.swift in Sources */,
|
||||
2CD0669226B5537B00728C20 /* WalletsManager.swift in Sources */,
|
||||
2CD0B3F726AC619900488D92 /* AddAccountOptionCellView.swift in Sources */,
|
||||
2C8A09E82675960D00993638 /* ErrorViewController.swift in Sources */,
|
||||
2C1995422674C4B900A8E370 /* ImportViewController.swift in Sources */,
|
||||
2C8E47A326A322E8007B8354 /* RightClickTableView.swift in Sources */,
|
||||
2C901C472689E6D400D0926A /* ApproveTransactionViewController.swift in Sources */,
|
||||
2C8A09B52675101300993638 /* AccountsService.swift in Sources */,
|
||||
2CDAB3722675B3F0009F8B97 /* PasswordViewController.swift in Sources */,
|
||||
2C1995402674C4B900A8E370 /* AppDelegate.swift in Sources */,
|
||||
0DB729142674E2DB0011F7A1 /* EIP712Parameter.swift in Sources */,
|
||||
2C901C4D268A033100D0926A /* GasService.swift in Sources */,
|
||||
2C528A16267FA8EB00CA3ADD /* Defaults.swift in Sources */,
|
||||
2CD0B3F526A0DAA900488D92 /* NSPasteboard.swift in Sources */,
|
||||
2CE3D015267F73E80032A62E /* AccountWithKey.swift in Sources */,
|
||||
2CE3D015267F73E80032A62E /* LegacyAccountWithKey.swift in Sources */,
|
||||
2CE3D012267F73C00032A62E /* Transaction.swift in Sources */,
|
||||
0DB7291B2674E2DB0011F7A1 /* EIP712Hash.swift in Sources */,
|
||||
0DB729172674E2DB0011F7A1 /* EIP712StructType.swift in Sources */,
|
||||
0DB7291F2674E2DB0011F7A1 /* EIP712Representable.swift in Sources */,
|
||||
0DB7291E2674E2DB0011F7A1 /* EIP712Signable.swift in Sources */,
|
||||
0DB7291C2674E2DB0011F7A1 /* EIP712Value.swift in Sources */,
|
||||
2C8A09EB2675964700993638 /* ApproveViewController.swift in Sources */,
|
||||
2C03D1D5269B428C00EF10EA /* Notification.swift in Sources */,
|
||||
2C1995562674D0F300A8E370 /* Ethereum.swift in Sources */,
|
||||
@ -626,7 +566,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 22;
|
||||
CURRENT_PROJECT_VERSION = 23;
|
||||
DEVELOPMENT_TEAM = XWNXDSM6BU;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
INFOPLIST_FILE = "$(SRCROOT)/Encrypted Ink/Supporting Files/Info.plist";
|
||||
@ -653,7 +593,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 22;
|
||||
CURRENT_PROJECT_VERSION = 23;
|
||||
DEVELOPMENT_TEAM = XWNXDSM6BU;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
INFOPLIST_FILE = "$(SRCROOT)/Encrypted Ink/Supporting Files/Info.plist";
|
||||
|
@ -11,7 +11,6 @@ class Agent: NSObject {
|
||||
|
||||
private override init() { super.init() }
|
||||
private var statusBarItem: NSStatusItem!
|
||||
private let accountsService = AccountsService.shared
|
||||
private var hasPassword = Keychain.shared.password != nil
|
||||
private var didEnterPasswordOnStart = false
|
||||
|
||||
@ -72,9 +71,9 @@ class Agent: NSObject {
|
||||
}
|
||||
|
||||
let windowController = Window.showNew()
|
||||
let completion = onSelectedAccount(session: session)
|
||||
let completion = onSelectedWallet(session: session)
|
||||
let accountsList = instantiate(AccountsListViewController.self)
|
||||
accountsList.onSelectedAccount = completion
|
||||
accountsList.onSelectedWallet = completion
|
||||
windowController.contentViewController = accountsList
|
||||
}
|
||||
|
||||
@ -120,9 +119,9 @@ class Agent: NSObject {
|
||||
showInitialScreen(wcSession: session)
|
||||
}
|
||||
|
||||
func getAccountSelectionCompletionIfShouldSelect() -> ((AccountWithKey) -> Void)? {
|
||||
func getWalletSelectionCompletionIfShouldSelect() -> ((InkWallet) -> Void)? {
|
||||
let session = getSessionFromPasteboard()
|
||||
return onSelectedAccount(session: session)
|
||||
return onSelectedWallet(session: session)
|
||||
}
|
||||
|
||||
lazy private var statusBarMenu: NSMenu = {
|
||||
@ -223,10 +222,10 @@ class Agent: NSObject {
|
||||
}
|
||||
}
|
||||
|
||||
private func onSelectedAccount(session: WCSession?) -> ((AccountWithKey) -> Void)? {
|
||||
private func onSelectedWallet(session: WCSession?) -> ((InkWallet) -> Void)? {
|
||||
guard let session = session else { return nil }
|
||||
return { [weak self] account in
|
||||
self?.connectWallet(session: session, account: account)
|
||||
return { [weak self] wallet in
|
||||
self?.connectWallet(session: session, wallet: wallet)
|
||||
}
|
||||
}
|
||||
|
||||
@ -285,12 +284,12 @@ class Agent: NSObject {
|
||||
}
|
||||
}
|
||||
|
||||
private func connectWallet(session: WCSession, account: AccountWithKey) {
|
||||
private func connectWallet(session: WCSession, wallet: InkWallet) {
|
||||
let windowController = Window.showNew()
|
||||
let window = windowController.window
|
||||
windowController.contentViewController = WaitingViewController.withReason("Connecting")
|
||||
|
||||
WalletConnect.shared.connect(session: session, address: account.address) { [weak window] _ in
|
||||
WalletConnect.shared.connect(session: session, walletId: wallet.id) { [weak window] _ in
|
||||
if window?.isVisible == true {
|
||||
Window.closeAllAndActivateBrowser()
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
private let gasService = GasService.shared
|
||||
private let priceService = PriceService.shared
|
||||
private let networkMonitor = NetworkMonitor.shared
|
||||
private let walletsManager = WalletsManager.shared
|
||||
|
||||
private var didFinishLaunching = false
|
||||
private var initialInputLink: String?
|
||||
@ -34,6 +35,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
gasService.start()
|
||||
priceService.start()
|
||||
networkMonitor.start()
|
||||
walletsManager.start()
|
||||
|
||||
didFinishLaunching = true
|
||||
if let link = initialInputLink {
|
||||
|
@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="18122" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="17156" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="18122"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="17156"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
@ -724,7 +724,7 @@
|
||||
<subviews>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="feM-MO-Msf">
|
||||
<rect key="frame" x="14" y="258" width="222" height="68"/>
|
||||
<textFieldCell key="cell" controlSize="large" alignment="center" title="Import Account" id="bI7-hT-bS8">
|
||||
<textFieldCell key="cell" alignment="center" title="Import Account" id="bI7-hT-bS8">
|
||||
<font key="font" metaFont="systemHeavy" size="29"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
@ -736,18 +736,18 @@
|
||||
<constraint firstAttribute="width" constant="190" id="Pi7-Hn-MhX"/>
|
||||
<constraint firstAttribute="height" constant="150" id="WsU-g6-Y2a"/>
|
||||
</constraints>
|
||||
<textFieldCell key="cell" controlSize="large" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" state="on" borderStyle="border" focusRingType="none" alignment="left" placeholderString="Private Key or Secret Words or Keystore" drawsBackground="YES" id="qLb-bZ-IWI">
|
||||
<textFieldCell key="cell" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" state="on" borderStyle="border" focusRingType="none" alignment="left" placeholderString="Private Key or Secret Words or Keystore" drawsBackground="YES" id="qLb-bZ-IWI">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<stackView distribution="fill" orientation="horizontal" alignment="top" spacing="12" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="OMb-fe-Srm">
|
||||
<rect key="frame" x="69" y="32" width="113" height="28"/>
|
||||
<rect key="frame" x="61" y="32" width="129" height="21"/>
|
||||
<subviews>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="h7j-aQ-ZXs">
|
||||
<rect key="frame" x="-6" y="-6" width="74" height="40"/>
|
||||
<buttonCell key="cell" type="push" title="Cancel" bezelStyle="rounded" alignment="center" controlSize="large" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="Nwp-G5-xI6">
|
||||
<rect key="frame" x="-6" y="-7" width="82" height="32"/>
|
||||
<buttonCell key="cell" type="push" title="Cancel" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="Nwp-G5-xI6">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<string key="keyEquivalent" base64-UTF8="YES">
|
||||
@ -759,8 +759,8 @@ Gw
|
||||
</connections>
|
||||
</button>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="t6z-sy-JB9">
|
||||
<rect key="frame" x="68" y="-6" width="51" height="40"/>
|
||||
<buttonCell key="cell" type="push" title="OK" bezelStyle="rounded" alignment="center" controlSize="large" enabled="NO" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="WW9-Lc-I9e">
|
||||
<rect key="frame" x="76" y="-7" width="59" height="32"/>
|
||||
<buttonCell key="cell" type="push" title="OK" bezelStyle="rounded" alignment="center" enabled="NO" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="WW9-Lc-I9e">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<string key="keyEquivalent" base64-UTF8="YES">
|
||||
@ -810,8 +810,8 @@ DQ
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" textCompletion="NO" translatesAutoresizingMaskIntoConstraints="NO" id="dkh-kG-EFj">
|
||||
<rect key="frame" x="53" y="292" width="144" height="34"/>
|
||||
<textFieldCell key="cell" controlSize="large" enabled="NO" allowsUndo="NO" alignment="center" title="Accounts" id="9No-vQ-vBK">
|
||||
<rect key="frame" x="54" y="292" width="143" height="34"/>
|
||||
<textFieldCell key="cell" enabled="NO" allowsUndo="NO" alignment="center" title="Accounts" id="9No-vQ-vBK">
|
||||
<font key="font" metaFont="systemHeavy" size="29"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
@ -842,11 +842,11 @@ DQ
|
||||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||
<prototypeCellViews>
|
||||
<tableCellView identifier="AccountCellView" id="Lp1-Zy-70c" customClass="AccountCellView" customModule="Encrypted_Ink" customModuleProvider="target">
|
||||
<rect key="frame" x="10" y="0.0" width="205" height="40"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="193" height="40"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="pdB-jS-gKE">
|
||||
<rect key="frame" x="51" y="5" width="30" height="30"/>
|
||||
<rect key="frame" x="45" y="5" width="30" height="30"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="30" id="Dmm-fG-F82"/>
|
||||
<constraint firstAttribute="height" constant="30" id="MNb-Zm-mjg"/>
|
||||
@ -859,7 +859,7 @@ DQ
|
||||
</userDefinedRuntimeAttributes>
|
||||
</imageView>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="sef-hl-iPV">
|
||||
<rect key="frame" x="85" y="8" width="62" height="25"/>
|
||||
<rect key="frame" x="79" y="8" width="62" height="24"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" alignment="center" title="Label" id="2a6-9c-AzV">
|
||||
<font key="font" metaFont="systemBold" size="21"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
@ -879,11 +879,11 @@ DQ
|
||||
</connections>
|
||||
</tableCellView>
|
||||
<tableCellView identifier="AddAccountOptionCellView" id="1KM-PS-nEo" customClass="AddAccountOptionCellView" customModule="Encrypted_Ink" customModuleProvider="target">
|
||||
<rect key="frame" x="10" y="40" width="205" height="40"/>
|
||||
<rect key="frame" x="0.0" y="40" width="193" height="40"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="BpB-tc-JdP">
|
||||
<rect key="frame" x="18" y="8" width="181" height="25"/>
|
||||
<rect key="frame" x="18" y="8" width="169" height="24"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" alignment="left" title="Label" id="FAR-3U-VR0">
|
||||
<font key="font" metaFont="systemBold" size="21"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
@ -919,7 +919,7 @@ DQ
|
||||
</scroller>
|
||||
</scrollView>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ngQ-Bn-Kwd">
|
||||
<rect key="frame" x="205" y="292" width="33" height="34"/>
|
||||
<rect key="frame" x="205" y="291" width="33" height="34"/>
|
||||
<buttonCell key="cell" type="inline" title="+" bezelStyle="inline" imagePosition="overlaps" alignment="center" lineBreakMode="truncatingTail" state="on" imageScaling="proportionallyDown" inset="2" id="JVh-da-a0h">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="systemBold" size="29"/>
|
||||
@ -961,18 +961,18 @@ DQ
|
||||
<subviews>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="GMd-25-PjD">
|
||||
<rect key="frame" x="14" y="292" width="222" height="34"/>
|
||||
<textFieldCell key="cell" controlSize="large" alignment="center" title="Waiting" id="Ab0-0l-hhQ">
|
||||
<textFieldCell key="cell" alignment="center" title="Waiting" id="Ab0-0l-hhQ">
|
||||
<font key="font" metaFont="systemHeavy" size="29"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<stackView distribution="fill" orientation="horizontal" alignment="top" spacing="12" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="PTt-zv-8aY">
|
||||
<rect key="frame" x="106" y="32" width="39" height="28"/>
|
||||
<rect key="frame" x="102" y="32" width="47" height="21"/>
|
||||
<subviews>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="R7c-Oj-Adu">
|
||||
<rect key="frame" x="-6" y="-6" width="51" height="40"/>
|
||||
<buttonCell key="cell" type="push" title="OK" bezelStyle="rounded" alignment="center" controlSize="large" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="aLn-BL-hyz">
|
||||
<rect key="frame" x="-6" y="-7" width="59" height="32"/>
|
||||
<buttonCell key="cell" type="push" title="OK" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="aLn-BL-hyz">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<connections>
|
||||
@ -1021,18 +1021,18 @@ DQ
|
||||
<subviews>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="PdV-Nr-oOY">
|
||||
<rect key="frame" x="14" y="292" width="222" height="34"/>
|
||||
<textFieldCell key="cell" controlSize="large" alignment="center" title="Approve" id="wVk-Ux-kpU">
|
||||
<textFieldCell key="cell" alignment="center" title="Approve" id="wVk-Ux-kpU">
|
||||
<font key="font" metaFont="systemHeavy" size="29"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<stackView distribution="fill" orientation="horizontal" alignment="top" spacing="12" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Oc8-WJ-ABp">
|
||||
<rect key="frame" x="69" y="32" width="113" height="28"/>
|
||||
<rect key="frame" x="61" y="32" width="129" height="21"/>
|
||||
<subviews>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="orP-ex-jdo">
|
||||
<rect key="frame" x="-6" y="-6" width="74" height="40"/>
|
||||
<buttonCell key="cell" type="push" title="Cancel" bezelStyle="rounded" alignment="center" controlSize="large" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="DGh-ds-l2H">
|
||||
<rect key="frame" x="-6" y="-7" width="82" height="32"/>
|
||||
<buttonCell key="cell" type="push" title="Cancel" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="DGh-ds-l2H">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<string key="keyEquivalent" base64-UTF8="YES">
|
||||
@ -1044,8 +1044,8 @@ Gw
|
||||
</connections>
|
||||
</button>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="xdD-3r-3YP">
|
||||
<rect key="frame" x="68" y="-6" width="51" height="40"/>
|
||||
<buttonCell key="cell" type="push" title="OK" bezelStyle="rounded" alignment="center" controlSize="large" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="O71-BW-3Sb">
|
||||
<rect key="frame" x="76" y="-7" width="59" height="32"/>
|
||||
<buttonCell key="cell" type="push" title="OK" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="O71-BW-3Sb">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<string key="keyEquivalent" base64-UTF8="YES">
|
||||
@ -1067,17 +1067,17 @@ DQ
|
||||
</customSpacing>
|
||||
</stackView>
|
||||
<scrollView focusRingType="none" borderType="line" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" horizontalScrollElasticity="none" translatesAutoresizingMaskIntoConstraints="NO" id="Ra3-Vm-T8F">
|
||||
<rect key="frame" x="30" y="90" width="190" height="184"/>
|
||||
<rect key="frame" x="30" y="83" width="190" height="191"/>
|
||||
<clipView key="contentView" id="3HR-u1-79b">
|
||||
<rect key="frame" x="1" y="1" width="188" height="182"/>
|
||||
<rect key="frame" x="1" y="1" width="188" height="189"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textView editable="NO" selectable="NO" importsGraphics="NO" richText="NO" verticallyResizable="YES" spellingCorrection="YES" smartInsertDelete="YES" id="nek-dW-Y7v">
|
||||
<rect key="frame" x="0.0" y="0.0" width="188" height="182"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="188" height="189"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
<size key="minSize" width="188" height="182"/>
|
||||
<size key="minSize" width="188" height="189"/>
|
||||
<size key="maxSize" width="240" height="10000000"/>
|
||||
<attributedString key="textStorage">
|
||||
<fragment content="Meta">
|
||||
@ -1100,7 +1100,7 @@ DQ
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
<scroller key="verticalScroller" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="GFp-c5-pAF">
|
||||
<rect key="frame" x="173" y="1" width="16" height="182"/>
|
||||
<rect key="frame" x="173" y="1" width="16" height="189"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
</scrollView>
|
||||
@ -1170,18 +1170,18 @@ DQ
|
||||
<subviews>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="T7q-cN-jjc">
|
||||
<rect key="frame" x="14" y="292" width="222" height="34"/>
|
||||
<textFieldCell key="cell" controlSize="large" alignment="center" title="Error" id="t7e-cO-oeN">
|
||||
<textFieldCell key="cell" alignment="center" title="Error" id="t7e-cO-oeN">
|
||||
<font key="font" metaFont="systemHeavy" size="29"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<stackView distribution="fill" orientation="horizontal" alignment="top" spacing="12" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="owz-dg-x2C">
|
||||
<rect key="frame" x="106" y="32" width="39" height="28"/>
|
||||
<rect key="frame" x="102" y="32" width="47" height="21"/>
|
||||
<subviews>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="bRK-Tb-oKu">
|
||||
<rect key="frame" x="-6" y="-6" width="51" height="40"/>
|
||||
<buttonCell key="cell" type="push" title="OK" bezelStyle="rounded" alignment="center" controlSize="large" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="xDk-iz-zcw">
|
||||
<rect key="frame" x="-6" y="-7" width="59" height="32"/>
|
||||
<buttonCell key="cell" type="push" title="OK" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="xDk-iz-zcw">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<string key="keyEquivalent" base64-UTF8="YES">
|
||||
@ -1201,8 +1201,8 @@ DQ
|
||||
</customSpacing>
|
||||
</stackView>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="M1Q-Iu-4dD">
|
||||
<rect key="frame" x="34" y="175" width="182" height="25"/>
|
||||
<textFieldCell key="cell" controlSize="large" selectable="YES" alignment="center" title="Failed to connect" id="qy3-O0-p56">
|
||||
<rect key="frame" x="34" y="175" width="182" height="24"/>
|
||||
<textFieldCell key="cell" selectable="YES" alignment="center" title="Failed to connect" id="qy3-O0-p56">
|
||||
<font key="font" metaFont="systemBold" size="21"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
@ -1239,18 +1239,18 @@ DQ
|
||||
<subviews>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="Gdw-Q4-bo0">
|
||||
<rect key="frame" x="14" y="258" width="222" height="68"/>
|
||||
<textFieldCell key="cell" controlSize="large" alignment="center" title="Create Password" id="8sq-L4-gvS">
|
||||
<textFieldCell key="cell" alignment="center" title="Create Password" id="8sq-L4-gvS">
|
||||
<font key="font" metaFont="systemHeavy" size="29"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<stackView distribution="fill" orientation="horizontal" alignment="top" spacing="12" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="uA2-QK-zWz">
|
||||
<rect key="frame" x="69" y="32" width="113" height="28"/>
|
||||
<rect key="frame" x="61" y="32" width="129" height="21"/>
|
||||
<subviews>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="cWf-at-SKV">
|
||||
<rect key="frame" x="-6" y="-6" width="74" height="40"/>
|
||||
<buttonCell key="cell" type="push" title="Cancel" bezelStyle="rounded" alignment="center" controlSize="large" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="r4y-I1-eGI">
|
||||
<rect key="frame" x="-6" y="-7" width="82" height="32"/>
|
||||
<buttonCell key="cell" type="push" title="Cancel" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="r4y-I1-eGI">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<string key="keyEquivalent" base64-UTF8="YES">
|
||||
@ -1262,8 +1262,8 @@ Gw
|
||||
</connections>
|
||||
</button>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="N8k-Bq-UNz">
|
||||
<rect key="frame" x="68" y="-6" width="51" height="40"/>
|
||||
<buttonCell key="cell" type="push" title="OK" bezelStyle="rounded" alignment="center" controlSize="large" enabled="NO" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="SbB-to-6AA">
|
||||
<rect key="frame" x="76" y="-7" width="59" height="32"/>
|
||||
<buttonCell key="cell" type="push" title="OK" bezelStyle="rounded" alignment="center" enabled="NO" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="SbB-to-6AA">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<string key="keyEquivalent" base64-UTF8="YES">
|
||||
@ -1289,7 +1289,7 @@ DQ
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="160" id="PLH-bk-GPE"/>
|
||||
</constraints>
|
||||
<secureTextFieldCell key="cell" controlSize="large" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="border" alignment="center" placeholderString="Password" drawsBackground="YES" usesSingleLineMode="YES" id="Szl-cI-dPv">
|
||||
<secureTextFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="border" alignment="center" placeholderString="Password" drawsBackground="YES" usesSingleLineMode="YES" id="Szl-cI-dPv">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
@ -1300,7 +1300,7 @@ DQ
|
||||
</secureTextField>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="H35-O5-AZi">
|
||||
<rect key="frame" x="18" y="234" width="214" height="16"/>
|
||||
<textFieldCell key="cell" controlSize="large" selectable="YES" alignment="center" title="Multiline Label" id="5g8-hW-z0n">
|
||||
<textFieldCell key="cell" selectable="YES" alignment="center" title="Multiline Label" id="5g8-hW-z0n">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="secondaryLabelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
@ -1342,18 +1342,18 @@ DQ
|
||||
<subviews>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="vxh-AZ-Ll4">
|
||||
<rect key="frame" x="14" y="292" width="222" height="34"/>
|
||||
<textFieldCell key="cell" controlSize="large" alignment="center" title="Encrypted Ink" id="dja-pE-CfV">
|
||||
<textFieldCell key="cell" alignment="center" title="Encrypted Ink" id="dja-pE-CfV">
|
||||
<font key="font" metaFont="systemHeavy" size="29"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<stackView distribution="fill" orientation="horizontal" alignment="top" spacing="12" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="zQX-EK-b8k">
|
||||
<rect key="frame" x="80" y="32" width="91" height="28"/>
|
||||
<rect key="frame" x="76" y="32" width="99" height="21"/>
|
||||
<subviews>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ADG-H0-CZa">
|
||||
<rect key="frame" x="-6" y="-6" width="103" height="40"/>
|
||||
<buttonCell key="cell" type="push" title="Get Started" bezelStyle="rounded" alignment="center" controlSize="large" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="jgC-3I-6bj">
|
||||
<rect key="frame" x="-6" y="-7" width="111" height="32"/>
|
||||
<buttonCell key="cell" type="push" title="Get Started" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="jgC-3I-6bj">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<string key="keyEquivalent" base64-UTF8="YES">
|
||||
@ -1373,8 +1373,8 @@ DQ
|
||||
</customSpacing>
|
||||
</stackView>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="BvK-xX-gxC">
|
||||
<rect key="frame" x="26" y="162" width="198" height="50"/>
|
||||
<textFieldCell key="cell" controlSize="large" alignment="center" title="Sign crypto transactions." id="4kF-cy-CbK">
|
||||
<rect key="frame" x="26" y="163" width="198" height="48"/>
|
||||
<textFieldCell key="cell" alignment="center" title="Sign crypto transactions." id="4kF-cy-CbK">
|
||||
<font key="font" metaFont="systemBold" size="21"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
@ -1411,18 +1411,18 @@ DQ
|
||||
<subviews>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="g2G-lh-X1F">
|
||||
<rect key="frame" x="14" y="310" width="222" height="34"/>
|
||||
<textFieldCell key="cell" controlSize="large" alignment="center" title="Approve" id="t64-oP-3Nm">
|
||||
<textFieldCell key="cell" alignment="center" title="Approve" id="t64-oP-3Nm">
|
||||
<font key="font" metaFont="systemHeavy" size="29"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<stackView distribution="fill" orientation="horizontal" alignment="top" spacing="12" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="MlF-7E-1Jg">
|
||||
<rect key="frame" x="69" y="32" width="113" height="28"/>
|
||||
<rect key="frame" x="61" y="32" width="129" height="21"/>
|
||||
<subviews>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="sUu-V8-fLA">
|
||||
<rect key="frame" x="-6" y="-6" width="74" height="40"/>
|
||||
<buttonCell key="cell" type="push" title="Cancel" bezelStyle="rounded" alignment="center" controlSize="large" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="i8C-YK-VwR">
|
||||
<rect key="frame" x="-6" y="-7" width="82" height="32"/>
|
||||
<buttonCell key="cell" type="push" title="Cancel" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="i8C-YK-VwR">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<string key="keyEquivalent" base64-UTF8="YES">
|
||||
@ -1434,8 +1434,8 @@ Gw
|
||||
</connections>
|
||||
</button>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="JBO-6Q-5eC">
|
||||
<rect key="frame" x="68" y="-6" width="51" height="40"/>
|
||||
<buttonCell key="cell" type="push" title="OK" bezelStyle="rounded" alignment="center" controlSize="large" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="U44-9K-bLF">
|
||||
<rect key="frame" x="76" y="-7" width="59" height="32"/>
|
||||
<buttonCell key="cell" type="push" title="OK" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="U44-9K-bLF">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<string key="keyEquivalent" base64-UTF8="YES">
|
||||
@ -1457,17 +1457,17 @@ DQ
|
||||
</customSpacing>
|
||||
</stackView>
|
||||
<scrollView focusRingType="none" borderType="line" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" horizontalScrollElasticity="none" translatesAutoresizingMaskIntoConstraints="NO" id="7mi-1e-Qtu">
|
||||
<rect key="frame" x="30" y="138" width="190" height="154"/>
|
||||
<rect key="frame" x="30" y="131" width="190" height="161"/>
|
||||
<clipView key="contentView" id="lLN-8O-Blv">
|
||||
<rect key="frame" x="1" y="1" width="188" height="152"/>
|
||||
<rect key="frame" x="1" y="1" width="188" height="159"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textView editable="NO" selectable="NO" importsGraphics="NO" richText="NO" verticallyResizable="YES" spellingCorrection="YES" smartInsertDelete="YES" id="EuZ-5b-EGk">
|
||||
<rect key="frame" x="0.0" y="0.0" width="188" height="152"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="188" height="159"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
<size key="minSize" width="188" height="152"/>
|
||||
<size key="minSize" width="188" height="159"/>
|
||||
<size key="maxSize" width="240" height="10000000"/>
|
||||
<attributedString key="textStorage">
|
||||
<fragment content="Meta">
|
||||
@ -1490,26 +1490,26 @@ DQ
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
<scroller key="verticalScroller" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="bKL-hk-a9Y">
|
||||
<rect key="frame" x="173" y="1" width="16" height="152"/>
|
||||
<rect key="frame" x="173" y="1" width="16" height="159"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
</scrollView>
|
||||
<stackView distribution="fillProportionally" orientation="horizontal" alignment="centerY" spacing="4" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="pLD-2z-PFP">
|
||||
<rect key="frame" x="45" y="86" width="160" height="16"/>
|
||||
<rect key="frame" x="45" y="79" width="160" height="16"/>
|
||||
<subviews>
|
||||
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Jcz-Um-efA">
|
||||
<rect key="frame" x="-2" y="0.0" width="24" height="16"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="20" id="Vdv-zQ-pVQ"/>
|
||||
</constraints>
|
||||
<textFieldCell key="cell" controlSize="large" lineBreakMode="clipping" alignment="left" title="🐢" id="QsW-fn-Al5">
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" alignment="left" title="🐢" id="QsW-fn-Al5">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<slider horizontalHuggingPriority="500" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="oWb-y6-1VO">
|
||||
<rect key="frame" x="22" y="0.0" width="116" height="17"/>
|
||||
<rect key="frame" x="24" y="1" width="112" height="15"/>
|
||||
<sliderCell key="cell" controlSize="mini" enabled="NO" state="on" alignment="left" maxValue="100" doubleValue="33.333333333333336" tickMarkPosition="below" numberOfTickMarks="4" sliderType="linear" id="8pg-D3-7EG"/>
|
||||
<connections>
|
||||
<action selector="sliderValueChanged:" target="eky-J7-eog" id="iz9-9b-wNa"/>
|
||||
@ -1517,7 +1517,7 @@ DQ
|
||||
</slider>
|
||||
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="WJV-V3-GWm">
|
||||
<rect key="frame" x="138" y="0.0" width="24" height="16"/>
|
||||
<textFieldCell key="cell" controlSize="large" lineBreakMode="clipping" alignment="right" title="🐇" id="aKB-6P-OIS">
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" alignment="right" title="🐇" id="aKB-6P-OIS">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
@ -1569,7 +1569,7 @@ DQ
|
||||
</customSpacing>
|
||||
</stackView>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="xby-TM-Wec">
|
||||
<rect key="frame" x="28" y="111" width="194" height="14"/>
|
||||
<rect key="frame" x="28" y="105" width="194" height="14"/>
|
||||
<textFieldCell key="cell" lineBreakMode="truncatingTail" alignment="center" id="606-Ge-1Pk">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="secondaryLabelColor" catalog="System" colorSpace="catalog"/>
|
||||
|
6
Encrypted Ink/Colors.xcassets/Contents.json
Normal file
6
Encrypted Ink/Colors.xcassets/Contents.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
20
Encrypted Ink/Colors.xcassets/InkBlue.colorset/Contents.json
Normal file
20
Encrypted Ink/Colors.xcassets/InkBlue.colorset/Contents.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0xB0",
|
||||
"green" : "0x17",
|
||||
"red" : "0x1B"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0x5A",
|
||||
"green" : "0x5D",
|
||||
"red" : "0x00"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
20
Encrypted Ink/Colors.xcassets/InkRed.colorset/Contents.json
Normal file
20
Encrypted Ink/Colors.xcassets/InkRed.colorset/Contents.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0x14",
|
||||
"green" : "0x00",
|
||||
"red" : "0xFA"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
@ -1,66 +0,0 @@
|
||||
//
|
||||
// This source file is part of the 0x.swift open source project
|
||||
// Copyright 2019 The 0x.swift Authors
|
||||
// Licensed under Apache License v2.0
|
||||
//
|
||||
// EIP712Domain.swift
|
||||
//
|
||||
// Created by Igor Shmakov on 09/04/2019
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct EIP712Domain: EIP712Representable {
|
||||
|
||||
public let name: String?
|
||||
public let version: String?
|
||||
public let chainID: Int?
|
||||
public let verifyingContract: String?
|
||||
public let salt: Data?
|
||||
|
||||
public var typeName: String {
|
||||
return "EIP712Domain"
|
||||
}
|
||||
|
||||
public func values() throws -> [EIP712Value] {
|
||||
|
||||
var data = [EIP712Value]()
|
||||
|
||||
if let name = name {
|
||||
data.append(EIP712SimpleValue(
|
||||
parameter: EIP712Parameter(name: "name", type: .string),
|
||||
value: name)
|
||||
)
|
||||
}
|
||||
|
||||
if let version = version {
|
||||
data.append(EIP712SimpleValue(
|
||||
parameter: EIP712Parameter(name: "version", type: .string),
|
||||
value: version)
|
||||
)
|
||||
}
|
||||
|
||||
if let chainID = chainID {
|
||||
data.append(EIP712SimpleValue(
|
||||
parameter: EIP712Parameter(name: "chainId", type: .uint(len: 256)),
|
||||
value: chainID)
|
||||
)
|
||||
}
|
||||
|
||||
if let verifyingContract = verifyingContract {
|
||||
data.append(EIP712SimpleValue(
|
||||
parameter: EIP712Parameter(name: "verifyingContract", type: .address),
|
||||
value: verifyingContract)
|
||||
)
|
||||
}
|
||||
|
||||
if let salt = salt {
|
||||
data.append(EIP712SimpleValue(
|
||||
parameter: EIP712Parameter(name: "salt", type: .fixedBytes(len: 32)),
|
||||
value: salt)
|
||||
)
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
//
|
||||
// This source file is part of the 0x.swift open source project
|
||||
// Copyright 2019 The 0x.swift Authors
|
||||
// Licensed under Apache License v2.0
|
||||
//
|
||||
// EIP712Error.swift
|
||||
//
|
||||
// Created by Igor Shmakov on 09/04/2019
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public enum EIP712Error: Error {
|
||||
|
||||
case notImplemented
|
||||
case invalidInput
|
||||
case invalidMessage
|
||||
case invalidParameter(name: String)
|
||||
case invalidType(name: String)
|
||||
|
||||
case invalidTypedData
|
||||
case invalidTypedDataPrimaryType
|
||||
case invalidTypedDataDomain
|
||||
case invalidTypedDataMessage
|
||||
case invalidTypedDataType
|
||||
case invalidTypedDataValue
|
||||
case integerOverflow
|
||||
|
||||
case signatureVerificationError
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
//
|
||||
// This source file is part of the 0x.swift open source project
|
||||
// Copyright 2019 The 0x.swift Authors
|
||||
// Licensed under Apache License v2.0
|
||||
//
|
||||
// Hash:.swift
|
||||
//
|
||||
// Created by Igor Shmakov on 09/04/2019
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public class EIP712Hash: EIP712Hashable {
|
||||
|
||||
private let typedData: EIP712Hashable
|
||||
private let domain: EIP712Hashable
|
||||
|
||||
public init(domain: EIP712Hashable, typedData: EIP712Hashable) {
|
||||
|
||||
self.domain = domain
|
||||
self.typedData = typedData
|
||||
}
|
||||
|
||||
public func hash() throws -> Data {
|
||||
guard
|
||||
let domainData = try? domain.hash(),
|
||||
let structData = try? typedData.hash()
|
||||
else {
|
||||
throw EIP712Error.invalidMessage
|
||||
}
|
||||
return (Data(hex: "0x1901") + domainData + structData).sha3(.keccak256)
|
||||
}
|
||||
}
|
@ -1,111 +0,0 @@
|
||||
//
|
||||
// This source file is part of the 0x.swift open source project
|
||||
// Copyright 2019 The 0x.swift Authors
|
||||
// Licensed under Apache License v2.0
|
||||
//
|
||||
// EIP712Parameter.swift
|
||||
//
|
||||
// Created by Igor Shmakov on 15/04/2019
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public enum EIP712ParameterType {
|
||||
|
||||
case bool
|
||||
case address
|
||||
case string
|
||||
case bytes
|
||||
case fixedBytes(len: Int)
|
||||
case uint(len: Int)
|
||||
case int(len: Int)
|
||||
case object(name: String)
|
||||
|
||||
private static func parseBytesSize(type: String, prefix: String) throws -> Int {
|
||||
|
||||
guard type.starts(with: prefix) else {
|
||||
throw EIP712Error.invalidType(name: type)
|
||||
}
|
||||
guard let size = Int(type.dropFirst(prefix.count)) else {
|
||||
throw EIP712Error.invalidType(name: type)
|
||||
}
|
||||
if size < 1 || size > 32 {
|
||||
throw EIP712Error.invalidType(name: type)
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
private static func parseIntSize(type: String, prefix: String) throws -> Int {
|
||||
|
||||
guard type.starts(with: prefix) else {
|
||||
throw EIP712Error.invalidType(name: type)
|
||||
}
|
||||
guard let size = Int(type.dropFirst(prefix.count)) else {
|
||||
if type == prefix {
|
||||
return 256
|
||||
}
|
||||
throw EIP712Error.invalidType(name: type)
|
||||
}
|
||||
if size < 8 || size > 256 || size % 8 != 0 {
|
||||
throw EIP712Error.invalidType(name: type)
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
public static func parse(type: String) throws -> EIP712ParameterType {
|
||||
|
||||
if type == "bool" {
|
||||
return .bool
|
||||
}
|
||||
|
||||
if type == "address" {
|
||||
return .address
|
||||
}
|
||||
|
||||
if type == "string" {
|
||||
return .string
|
||||
}
|
||||
|
||||
if type == "bytes" {
|
||||
return .bytes
|
||||
}
|
||||
|
||||
if type.hasPrefix("uint") {
|
||||
return try .uint(len: parseIntSize(type: type, prefix: "uint"))
|
||||
}
|
||||
|
||||
if type.hasPrefix("int") {
|
||||
return try .int(len: parseIntSize(type: type, prefix: "int"))
|
||||
}
|
||||
|
||||
if type.hasPrefix("bytes") {
|
||||
return try .fixedBytes(len: parseBytesSize(type: type, prefix: "bytes"))
|
||||
}
|
||||
|
||||
return object(name: type)
|
||||
}
|
||||
|
||||
public func raw() -> String {
|
||||
|
||||
switch self {
|
||||
case .bool: return "bool"
|
||||
case .address: return "address"
|
||||
case .string: return "string"
|
||||
case let .fixedBytes(len): return "bytes\(len)"
|
||||
case let .uint(len): return "uint\(len)"
|
||||
case let .int(len): return "int\(len)"
|
||||
case .bytes: return "bytes"
|
||||
case let .object(name): return name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct EIP712Parameter {
|
||||
|
||||
public let name: String
|
||||
public let type: EIP712ParameterType
|
||||
|
||||
public func encode() -> String {
|
||||
return "\(type.raw()) \(name)"
|
||||
}
|
||||
}
|
@ -1,119 +0,0 @@
|
||||
//
|
||||
// This source file is part of the 0x.swift open source project
|
||||
// Copyright 2019 The 0x.swift Authors
|
||||
// Licensed under Apache License v2.0
|
||||
//
|
||||
// EIP712ValueEncoder.swift
|
||||
//
|
||||
// Created by Igor Shmakov on 15/04/2019
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import BigInt
|
||||
import Web3Swift
|
||||
|
||||
public final class EIP712ValueEncoder {
|
||||
|
||||
private let type: EIP712ParameterType
|
||||
private let value: Any
|
||||
|
||||
public init(type: EIP712ParameterType, value: Any) {
|
||||
|
||||
self.type = type
|
||||
self.value = value
|
||||
}
|
||||
|
||||
public func makeABIEncodedParameter() throws -> ABIEncodedParameter {
|
||||
|
||||
switch type {
|
||||
case .bool:
|
||||
return try encodeBool()
|
||||
case .address:
|
||||
return try encodeAddress()
|
||||
case .string:
|
||||
return try encodeString()
|
||||
case .fixedBytes:
|
||||
return try encodeFixedBytes()
|
||||
case .uint, .int:
|
||||
return try encodeInt()
|
||||
case .bytes:
|
||||
return try encodeBytes()
|
||||
case .object:
|
||||
return try encodeObject()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension EIP712ValueEncoder {
|
||||
|
||||
private func encodeBool() throws -> ABIEncodedParameter {
|
||||
|
||||
guard let bool = value as? Bool else {
|
||||
throw EIP712Error.invalidTypedDataValue
|
||||
}
|
||||
return ABIBoolean(origin: bool)
|
||||
}
|
||||
|
||||
private func encodeAddress() throws -> ABIEncodedParameter {
|
||||
|
||||
guard let value = value as? String else {
|
||||
throw EIP712Error.invalidTypedDataValue
|
||||
}
|
||||
return ABIAddress(address: EthAddress(hex: value))
|
||||
}
|
||||
|
||||
private func encodeString() throws -> ABIEncodedParameter {
|
||||
|
||||
guard let value = value as? String, let data = value.data(using: .utf8) else {
|
||||
throw EIP712Error.invalidTypedDataValue
|
||||
}
|
||||
return ABIFixedBytes(origin: SimpleBytes(bytes: data.sha3(.keccak256)))
|
||||
}
|
||||
|
||||
private func encodeFixedBytes() throws -> ABIEncodedParameter {
|
||||
|
||||
let data: Data
|
||||
if let value = value as? String {
|
||||
data = Data(hex: value)
|
||||
} else if let value = value as? Data {
|
||||
data = value
|
||||
} else {
|
||||
throw EIP712Error.invalidTypedDataValue
|
||||
}
|
||||
return ABIFixedBytes(origin: SimpleBytes(bytes: data))
|
||||
}
|
||||
|
||||
private func encodeInt() throws -> ABIEncodedParameter {
|
||||
|
||||
let number: Int
|
||||
if let value = value as? Int {
|
||||
number = value
|
||||
} else if let str = value as? String {
|
||||
return ABIUnsignedNumber(origin: EthNumber(hex: str))
|
||||
} else {
|
||||
throw EIP712Error.invalidTypedDataValue
|
||||
}
|
||||
return ABIUnsignedNumber(origin: EthNumber(value: number))
|
||||
}
|
||||
|
||||
private func encodeBytes() throws -> ABIEncodedParameter {
|
||||
|
||||
let data: Data
|
||||
if let value = value as? String {
|
||||
data = Data(hex: value)
|
||||
} else if let value = value as? Data {
|
||||
data = value
|
||||
} else {
|
||||
throw EIP712Error.invalidTypedDataValue
|
||||
}
|
||||
return ABIFixedBytes(origin: SimpleBytes(bytes: data.sha3(.keccak256)))
|
||||
}
|
||||
|
||||
private func encodeObject() throws -> ABIEncodedParameter {
|
||||
|
||||
guard let value = value as? EIP712Representable else {
|
||||
throw EIP712Error.invalidTypedDataValue
|
||||
}
|
||||
return ABIFixedBytes(origin: SimpleBytes(bytes: try value.hash()))
|
||||
}
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
//
|
||||
// This source file is part of the 0x.swift open source project
|
||||
// Copyright 2019 The 0x.swift Authors
|
||||
// Licensed under Apache License v2.0
|
||||
//
|
||||
// EIP712Signer.swift
|
||||
//
|
||||
// Created by Igor Shmakov on 19/04/2019
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Web3Swift
|
||||
import CryptoSwift
|
||||
|
||||
public final class EIP712Signer: EIP712Signable {
|
||||
|
||||
private let privateKey: EthPrivateKey
|
||||
|
||||
public init(privateKey: EthPrivateKey) {
|
||||
|
||||
self.privateKey = privateKey
|
||||
}
|
||||
|
||||
public func sign(hash: EIP712Hashable) throws -> SECP256k1Signature {
|
||||
|
||||
let signature = SECP256k1Signature(
|
||||
digest: SimpleBytes(bytes: try hash.hash()),
|
||||
privateKey: privateKey
|
||||
)
|
||||
|
||||
return signature
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
//
|
||||
// This source file is part of the 0x.swift open source project
|
||||
// Copyright 2019 The 0x.swift Authors
|
||||
// Licensed under Apache License v2.0
|
||||
//
|
||||
// EIP712SimpleValue.swift
|
||||
//
|
||||
// Created by Igor Shmakov on 15/04/2019
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Web3Swift
|
||||
|
||||
public struct EIP712SimpleValue: EIP712Value {
|
||||
|
||||
public let parameter: EIP712Parameter
|
||||
public let value: Any
|
||||
|
||||
public func makeABIEncodedParameter() throws -> ABIEncodedParameter {
|
||||
|
||||
let encoder = EIP712ValueEncoder(type: parameter.type, value: value)
|
||||
return try encoder.makeABIEncodedParameter()
|
||||
}
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
//
|
||||
// This source file is part of the 0x.swift open source project
|
||||
// Copyright 2019 The 0x.swift Authors
|
||||
// Licensed under Apache License v2.0
|
||||
//
|
||||
// EIP712StructType.swift
|
||||
//
|
||||
// Created by Igor Shmakov on 15/04/2019
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct EIP712StructType {
|
||||
|
||||
public let types: [EIP712Type]
|
||||
|
||||
public var primaryType: EIP712Type? {
|
||||
return types.first
|
||||
}
|
||||
|
||||
public init(primary: EIP712Type, referenced: [EIP712Type] = []) {
|
||||
types = [primary] + referenced.sorted { $0.name < $1.name }
|
||||
}
|
||||
|
||||
public func encode() -> String {
|
||||
return types.reduce("") { $0 + $1.encode()}
|
||||
}
|
||||
|
||||
public func hashType() throws -> Data {
|
||||
return encode().data(using: .utf8)!.sha3(.keccak256)
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
//
|
||||
// This source file is part of the 0x.swift open source project
|
||||
// Copyright 2019 The 0x.swift Authors
|
||||
// Licensed under Apache License v2.0
|
||||
//
|
||||
// EIP712Type.swift
|
||||
//
|
||||
// Created by Igor Shmakov on 15/04/2019
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct EIP712Type {
|
||||
|
||||
public let name: String
|
||||
public let parameters: [EIP712Parameter]
|
||||
|
||||
public func encode() -> String {
|
||||
let encodedParameters = parameters.map { $0.encode() }.joined(separator: ",")
|
||||
return "\(name)(\(encodedParameters))"
|
||||
}
|
||||
}
|
@ -1,160 +0,0 @@
|
||||
//
|
||||
// This source file is part of the 0x.swift open source project
|
||||
// Copyright 2019 The 0x.swift Authors
|
||||
// Licensed under Apache License v2.0
|
||||
//
|
||||
// EIP712TypedData.swift
|
||||
//
|
||||
// Created by Igor Shmakov on 12/04/2019
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftyJSON
|
||||
import Web3Swift
|
||||
import BigInt
|
||||
|
||||
public class EIP712TypedData {
|
||||
|
||||
public private(set) var type: EIP712StructType!
|
||||
public private(set) var domain: EIP712Domain!
|
||||
public private(set) var encodedData: Data!
|
||||
|
||||
private let domainType = "EIP712Domain"
|
||||
|
||||
public convenience init(jsonData: Data) throws {
|
||||
try self.init(json: try JSON(data: jsonData))
|
||||
}
|
||||
|
||||
public convenience init(jsonString: String) throws {
|
||||
try self.init(json: JSON(parseJSON: jsonString))
|
||||
}
|
||||
|
||||
public convenience init(jsonObject: Any) throws {
|
||||
try self.init(json: JSON(jsonObject))
|
||||
}
|
||||
|
||||
public init(json: JSON) throws {
|
||||
|
||||
let jsonDomain = json["domain"]
|
||||
let jsonMessage = json["message"]
|
||||
|
||||
guard
|
||||
let jsonTypes = json["types"].dictionary,
|
||||
let primaryTypeName = json["primaryType"].string
|
||||
else {
|
||||
throw EIP712Error.invalidTypedData
|
||||
}
|
||||
|
||||
let types = try parseTypes(jsonTypes: jsonTypes)
|
||||
|
||||
guard let primaryType = types[primaryTypeName] else {
|
||||
throw EIP712Error.invalidTypedDataPrimaryType
|
||||
}
|
||||
|
||||
guard let domainType = types[domainType] else {
|
||||
throw EIP712Error.invalidTypedDataDomain
|
||||
}
|
||||
|
||||
let dependencies = try findTypeDependencies(primaryType: primaryType, types: types).filter { $0.name != primaryType.name }
|
||||
let type = EIP712StructType(primary: primaryType, referenced: dependencies)
|
||||
let domain = try parseDomain(jsonDomain: jsonDomain, type: domainType)
|
||||
let data = try encodeData(data: jsonMessage, primaryType: primaryType, types: types)
|
||||
|
||||
self.type = type
|
||||
self.encodedData = data
|
||||
self.domain = domain
|
||||
}
|
||||
|
||||
private func parseDomain(jsonDomain: JSON, type: EIP712Type) throws -> EIP712Domain {
|
||||
|
||||
return EIP712Domain(name: jsonDomain["name"].string,
|
||||
version: jsonDomain["version"].string,
|
||||
chainID: jsonDomain["chainId"].int,
|
||||
verifyingContract: jsonDomain["verifyingContract"].string,
|
||||
salt: jsonDomain["salt"].string?.data(using: .utf8))
|
||||
}
|
||||
|
||||
private func parseTypes(jsonTypes: [String: JSON]) throws -> [String: EIP712Type] {
|
||||
|
||||
var types = [String: EIP712Type]()
|
||||
|
||||
for (name, jsonParameters) in jsonTypes {
|
||||
var parameters = [EIP712Parameter]()
|
||||
for (_, jsonParameter) in jsonParameters {
|
||||
guard
|
||||
let name = jsonParameter["name"].string,
|
||||
let type = jsonParameter["type"].string
|
||||
else {
|
||||
throw EIP712Error.invalidTypedDataType
|
||||
}
|
||||
parameters.append(EIP712Parameter(name: name, type: try EIP712ParameterType.parse(type: type)))
|
||||
}
|
||||
let type = EIP712Type(name: name, parameters: parameters)
|
||||
types[name] = type
|
||||
}
|
||||
|
||||
return types
|
||||
}
|
||||
|
||||
private func findTypeDependencies(primaryType: EIP712Type, types: [String: EIP712Type], results: [EIP712Type] = []) throws -> [EIP712Type] {
|
||||
|
||||
var results = results
|
||||
if (results.contains(where: { $0.name == primaryType.name }) || types[primaryType.name] == nil) {
|
||||
return results
|
||||
}
|
||||
|
||||
results.append(primaryType)
|
||||
|
||||
for parameter in primaryType.parameters {
|
||||
if let type = types[parameter.type.raw()] {
|
||||
let dependencies = try findTypeDependencies(primaryType: type, types: types, results: results)
|
||||
results += dependencies.filter { dep in !results.contains(where: { res in res.name == dep.name }) }
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
private func encodeType(primaryType: EIP712Type, types: [String: EIP712Type]) throws -> EIP712StructType {
|
||||
|
||||
let dependencies = try findTypeDependencies(primaryType: primaryType, types: types).filter { $0.name != primaryType.name }
|
||||
return EIP712StructType(primary: primaryType, referenced: dependencies)
|
||||
}
|
||||
|
||||
private func hashType(primaryType: EIP712Type, types: [String: EIP712Type]) throws -> Data {
|
||||
|
||||
let type = try encodeType(primaryType: primaryType, types: types)
|
||||
return try type.hashType()
|
||||
}
|
||||
|
||||
private func encodeData(data: JSON, primaryType: EIP712Type, types: [String: EIP712Type]) throws -> Data {
|
||||
|
||||
var encodedTypes: [EIP712ParameterType] = [.fixedBytes(len: 32)]
|
||||
var encodedValues: [Any] = [try self.hashType(primaryType: primaryType, types: types)]
|
||||
|
||||
for parameter in primaryType.parameters {
|
||||
let value = data[parameter.name]
|
||||
if let type = types[parameter.type.raw()] {
|
||||
encodedTypes.append(.fixedBytes(len: 32))
|
||||
let data = try encodeData(data: value, primaryType: type, types: types)
|
||||
encodedValues.append(data.sha3(.keccak256))
|
||||
} else {
|
||||
encodedTypes.append(parameter.type)
|
||||
encodedValues.append(value.object)
|
||||
}
|
||||
}
|
||||
|
||||
let parameters = try zip(encodedTypes, encodedValues).map {
|
||||
try EIP712ValueEncoder(type: $0.0, value: $0.1).makeABIEncodedParameter()
|
||||
}
|
||||
|
||||
return try EncodedABITuple(parameters: parameters).value()
|
||||
}
|
||||
}
|
||||
|
||||
extension EIP712TypedData: EIP712Hashable {
|
||||
|
||||
public func hash() throws -> Data {
|
||||
|
||||
return encodedData.sha3(.keccak256)
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
//
|
||||
// This source file is part of the 0x.swift open source project
|
||||
// Copyright 2019 The 0x.swift Authors
|
||||
// Licensed under Apache License v2.0
|
||||
//
|
||||
// EIP712Hashable.swift
|
||||
//
|
||||
// Created by Igor Shmakov on 17/04/2019
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public protocol EIP712Hashable {
|
||||
|
||||
func hash() throws -> Data
|
||||
}
|
@ -1,64 +0,0 @@
|
||||
//
|
||||
// This source file is part of the 0x.swift open source project
|
||||
// Copyright 2019 The 0x.swift Authors
|
||||
// Licensed under Apache License v2.0
|
||||
//
|
||||
// EIP712Representable.swift
|
||||
//
|
||||
// Created by Igor Shmakov on 15/04/2019
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Web3Swift
|
||||
|
||||
public protocol EIP712Representable: EIP712Hashable {
|
||||
|
||||
var typeName: String { get }
|
||||
func values() throws -> [EIP712Value]
|
||||
}
|
||||
|
||||
public extension EIP712Representable {
|
||||
|
||||
func typeDependencies() throws -> [EIP712Type] {
|
||||
|
||||
let types = try values()
|
||||
.compactMap { $0.value as? EIP712Representable }
|
||||
.flatMap { [try $0.type()] + (try $0.typeDependencies()) }
|
||||
|
||||
var uniqueTypes = [EIP712Type]()
|
||||
for type in types {
|
||||
if !uniqueTypes.contains(where: { $0.name == type.name }) {
|
||||
uniqueTypes.append(type)
|
||||
}
|
||||
}
|
||||
return uniqueTypes
|
||||
}
|
||||
|
||||
func type() throws -> EIP712Type {
|
||||
return EIP712Type(name: typeName, parameters: try values().map { $0.parameter })
|
||||
}
|
||||
|
||||
func encodeType() throws -> EIP712StructType {
|
||||
|
||||
let primary = try type()
|
||||
let referenced = try typeDependencies().filter { $0.name != primary.name }
|
||||
return EIP712StructType(primary: primary, referenced: referenced)
|
||||
}
|
||||
|
||||
func hashType() throws -> Data {
|
||||
|
||||
return try encodeType().hashType()
|
||||
}
|
||||
|
||||
func encodeData() throws -> Data {
|
||||
|
||||
var parameters: [ABIEncodedParameter] = [ABIFixedBytes(origin: SimpleBytes(bytes: try hashType()))]
|
||||
parameters += try values().map { try $0.makeABIEncodedParameter() }
|
||||
return try EncodedABITuple(parameters: parameters).value()
|
||||
}
|
||||
|
||||
func hash() throws -> Data {
|
||||
|
||||
return try encodeData().sha3(.keccak256)
|
||||
}
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
//
|
||||
// This source file is part of the 0x.swift open source project
|
||||
// Copyright 2019 The 0x.swift Authors
|
||||
// Licensed under Apache License v2.0
|
||||
//
|
||||
// EIP712Signable.swift
|
||||
//
|
||||
// Created by Igor Shmakov on 19/04/2019
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Web3Swift
|
||||
|
||||
public protocol EIP712Signable {
|
||||
|
||||
func sign(hash: EIP712Hashable) throws -> SECP256k1Signature
|
||||
}
|
||||
|
||||
public extension EIP712Signable {
|
||||
|
||||
func signatureData(hash: EIP712Hashable) throws -> Data {
|
||||
|
||||
let signature = try sign(hash: hash)
|
||||
|
||||
let message = ConcatenatedBytes(
|
||||
bytes: [
|
||||
try signature.r(),
|
||||
try signature.s(),
|
||||
SimpleBytes(bytes: [UInt8(try signature.recoverID().value() + 27)])
|
||||
]
|
||||
)
|
||||
|
||||
return try message.value()
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
//
|
||||
// This source file is part of the 0x.swift open source project
|
||||
// Copyright 2019 The 0x.swift Authors
|
||||
// Licensed under Apache License v2.0
|
||||
//
|
||||
// EIP712Value.swift
|
||||
//
|
||||
// Created by Igor Shmakov on 15/04/2019
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Web3Swift
|
||||
|
||||
public protocol EIP712Value {
|
||||
|
||||
var parameter: EIP712Parameter { get }
|
||||
var value: Any { get }
|
||||
|
||||
func makeABIEncodedParameter() throws -> ABIEncodedParameter
|
||||
}
|
@ -1,14 +1,17 @@
|
||||
// Copyright © 2021 Encrypted Ink. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import WalletCore
|
||||
import Web3Swift
|
||||
import CryptoSwift
|
||||
|
||||
struct Ethereum {
|
||||
|
||||
enum Errors: Error {
|
||||
enum Error: Swift.Error {
|
||||
case invalidInputData
|
||||
case failedToSendTransaction
|
||||
case failedToSign
|
||||
case keyNotFound
|
||||
}
|
||||
|
||||
private let queue = DispatchQueue(label: "Ethereum", qos: .default)
|
||||
@ -21,8 +24,9 @@ struct Ethereum {
|
||||
apiKey: Secrets.alchemy
|
||||
)
|
||||
|
||||
func sign(message: String, account: AccountWithKey) throws -> String {
|
||||
let ethPrivateKey = EthPrivateKey(hex: account.privateKey)
|
||||
func sign(message: String, wallet: InkWallet) throws -> String {
|
||||
guard let privateKeyString = wallet.ethereumPrivateKeyString else { throw Error.keyNotFound }
|
||||
let ethPrivateKey = EthPrivateKey(hex: privateKeyString)
|
||||
|
||||
let signature = SECP256k1Signature(
|
||||
privateKey: ethPrivateKey,
|
||||
@ -39,32 +43,33 @@ struct Ethereum {
|
||||
return data.toPrefixedHexString()
|
||||
}
|
||||
|
||||
func signPersonal(message: String, account: AccountWithKey) throws -> String {
|
||||
let ethPrivateKey = EthPrivateKey(hex: account.privateKey)
|
||||
func signPersonal(message: String, wallet: InkWallet) throws -> String {
|
||||
guard let privateKeyString = wallet.ethereumPrivateKeyString else { throw Error.keyNotFound }
|
||||
let ethPrivateKey = EthPrivateKey(hex: privateKeyString)
|
||||
let signed = SignedPersonalMessageBytes(message: message, signerKey: ethPrivateKey)
|
||||
let data = try signed.value().toPrefixedHexString()
|
||||
return data
|
||||
}
|
||||
|
||||
func sign(typedData: String, account: AccountWithKey) throws -> String {
|
||||
let data = try EIP712TypedData(jsonString: typedData)
|
||||
let hash = EIP712Hash(domain: data.domain, typedData: data)
|
||||
let privateKey = EthPrivateKey(hex: account.privateKey)
|
||||
let signer = EIP712Signer(privateKey: privateKey)
|
||||
return try signer.signatureData(hash: hash).toPrefixedHexString()
|
||||
func sign(typedData: String, wallet: InkWallet) throws -> String {
|
||||
guard let ethereumPrivateKey = wallet.ethereumPrivateKey else { throw Error.keyNotFound }
|
||||
let digest = EthereumAbi.encodeTyped(messageJson: typedData)
|
||||
guard let signed = ethereumPrivateKey.sign(digest: digest, curve: CoinType.ethereum.curve)?.toPrefixedHexString() else { throw Error.failedToSign }
|
||||
return signed
|
||||
}
|
||||
|
||||
func send(transaction: Transaction, account: AccountWithKey) throws -> String {
|
||||
let bytes = signedTransactionBytes(transaction: transaction, account: account)
|
||||
func send(transaction: Transaction, wallet: InkWallet) throws -> String {
|
||||
let bytes = try signedTransactionBytes(transaction: transaction, wallet: wallet)
|
||||
let response = try SendRawTransactionProcedure(network: network, transactionBytes: bytes).call()
|
||||
guard let hash = response["result"].string else {
|
||||
throw Errors.failedToSendTransaction
|
||||
throw Error.failedToSendTransaction
|
||||
}
|
||||
return hash
|
||||
}
|
||||
|
||||
private func signedTransactionBytes(transaction: Transaction, account: AccountWithKey) -> EthContractCallBytes {
|
||||
let senderKey = EthPrivateKey(hex: account.privateKey)
|
||||
private func signedTransactionBytes(transaction: Transaction, wallet: InkWallet) throws -> EthContractCallBytes {
|
||||
guard let privateKeyString = wallet.ethereumPrivateKeyString else { throw Error.keyNotFound }
|
||||
let senderKey = EthPrivateKey(hex: privateKeyString)
|
||||
let contractAddress = EthAddress(hex: transaction.to)
|
||||
let functionCall = BytesFromHexString(hex: transaction.data)
|
||||
let bytes: EthContractCallBytes
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
struct AccountWithKey: Codable {
|
||||
struct LegacyAccountWithKey: Codable {
|
||||
let privateKey: String
|
||||
let address: String
|
||||
}
|
9
Encrypted Ink/Extensions/NSColor.swift
Normal file
9
Encrypted Ink/Extensions/NSColor.swift
Normal file
@ -0,0 +1,9 @@
|
||||
// Copyright © 2021 Encrypted Ink. All rights reserved.
|
||||
|
||||
import Cocoa
|
||||
|
||||
extension NSColor {
|
||||
|
||||
static let inkGreen = NSColor(named: "InkGreen")!
|
||||
|
||||
}
|
11
Encrypted Ink/Extensions/String.swift
Normal file
11
Encrypted Ink/Extensions/String.swift
Normal file
@ -0,0 +1,11 @@
|
||||
// Copyright © 2021 Encrypted Ink. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
|
||||
extension String {
|
||||
|
||||
var maybeJSON: Bool {
|
||||
return hasPrefix("{") && hasSuffix("}") && count > 3
|
||||
}
|
||||
|
||||
}
|
@ -5,6 +5,7 @@ enum AuthenticationReason {
|
||||
case sendTransaction
|
||||
case removeAccount
|
||||
case showPrivateKey
|
||||
case showSecretWords
|
||||
case signAction(title: String)
|
||||
|
||||
var title: String {
|
||||
@ -17,6 +18,8 @@ enum AuthenticationReason {
|
||||
return "Remove account"
|
||||
case .showPrivateKey:
|
||||
return "Show private key"
|
||||
case .showSecretWords:
|
||||
return "Show secret words"
|
||||
case .signAction(let title):
|
||||
return title
|
||||
}
|
||||
|
@ -5,14 +5,14 @@ import Cocoa
|
||||
class AccountsListViewController: NSViewController {
|
||||
|
||||
private let agent = Agent.shared
|
||||
private let accountsService = AccountsService.shared
|
||||
private var accounts = [AccountWithKey]()
|
||||
private let walletsManager = WalletsManager.shared
|
||||
private var cellModels = [CellModel]()
|
||||
|
||||
var onSelectedAccount: ((AccountWithKey) -> Void)?
|
||||
var onSelectedWallet: ((InkWallet) -> Void)?
|
||||
var newWalletId: String?
|
||||
|
||||
enum CellModel {
|
||||
case account(AccountWithKey)
|
||||
case wallet
|
||||
case addAccountOption(AddAccountOption)
|
||||
}
|
||||
|
||||
@ -45,42 +45,46 @@ class AccountsListViewController: NSViewController {
|
||||
}
|
||||
}
|
||||
|
||||
private var wallets: [InkWallet] {
|
||||
return walletsManager.wallets
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
setupAccountsMenu()
|
||||
reloadAccounts()
|
||||
reloadTitle()
|
||||
updateCellModels()
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(didBecomeActive), name: NSApplication.didBecomeActiveNotification, object: nil)
|
||||
}
|
||||
|
||||
override func viewDidAppear() {
|
||||
super.viewDidAppear()
|
||||
blinkNewWalletCellIfNeeded()
|
||||
}
|
||||
|
||||
private func setupAccountsMenu() {
|
||||
let menu = NSMenu()
|
||||
menu.delegate = self
|
||||
menu.addItem(NSMenuItem(title: "Copy address", action: #selector(didClickCopyAddress(_:)), keyEquivalent: ""))
|
||||
menu.addItem(NSMenuItem(title: "View on Zerion", action: #selector(didClickViewOnZerion(_:)), keyEquivalent: ""))
|
||||
menu.addItem(.separator())
|
||||
menu.addItem(NSMenuItem(title: "Show private key", action: #selector(didClickExportAccount(_:)), keyEquivalent: "")) // TODO: show different texts for secret words export
|
||||
menu.addItem(NSMenuItem(title: "Show account key", action: #selector(didClickExportAccount(_:)), keyEquivalent: ""))
|
||||
menu.addItem(NSMenuItem(title: "Remove account", action: #selector(didClickRemoveAccount(_:)), keyEquivalent: ""))
|
||||
menu.addItem(.separator())
|
||||
menu.addItem(NSMenuItem(title: "How to WalletConnect?", action: #selector(showInstructionsAlert), keyEquivalent: ""))
|
||||
tableView.menu = menu
|
||||
}
|
||||
|
||||
private func reloadAccounts() {
|
||||
accounts = accountsService.getAccounts()
|
||||
}
|
||||
|
||||
private func reloadTitle() {
|
||||
titleLabel.stringValue = onSelectedAccount != nil && !accounts.isEmpty ? "Select\nAccount" : "Accounts"
|
||||
addButton.isHidden = accounts.isEmpty
|
||||
titleLabel.stringValue = onSelectedWallet != nil && !wallets.isEmpty ? "Select\nAccount" : "Accounts"
|
||||
addButton.isHidden = wallets.isEmpty
|
||||
}
|
||||
|
||||
@objc private func didBecomeActive() {
|
||||
guard view.window?.isVisible == true else { return }
|
||||
if let completion = agent.getAccountSelectionCompletionIfShouldSelect() {
|
||||
onSelectedAccount = completion
|
||||
if let completion = agent.getWalletSelectionCompletionIfShouldSelect() {
|
||||
onSelectedWallet = completion
|
||||
}
|
||||
reloadTitle()
|
||||
}
|
||||
@ -103,24 +107,32 @@ class AccountsListViewController: NSViewController {
|
||||
}
|
||||
|
||||
@objc private func didClickCreateAccount() {
|
||||
accountsService.createAccount()
|
||||
reloadAccounts()
|
||||
let wallet = try? walletsManager.createWallet()
|
||||
newWalletId = wallet?.id
|
||||
reloadTitle()
|
||||
updateCellModels()
|
||||
tableView.reloadData()
|
||||
blinkNewWalletCellIfNeeded()
|
||||
// TODO: show backup phrase
|
||||
}
|
||||
|
||||
private func blinkNewWalletCellIfNeeded() {
|
||||
guard let id = newWalletId else { return }
|
||||
newWalletId = nil
|
||||
guard let row = wallets.firstIndex(where: { $0.id == id }), row < cellModels.count else { return }
|
||||
tableView.scrollRowToVisible(row)
|
||||
(tableView.rowView(atRow: row, makeIfNecessary: true) as? AccountCellView)?.blink()
|
||||
}
|
||||
|
||||
@objc private func didClickImportAccount() {
|
||||
let importViewController = instantiate(ImportViewController.self)
|
||||
importViewController.onSelectedAccount = onSelectedAccount
|
||||
importViewController.onSelectedWallet = onSelectedWallet
|
||||
view.window?.contentViewController = importViewController
|
||||
}
|
||||
|
||||
@objc private func didClickViewOnZerion(_ sender: AnyObject) {
|
||||
let row = tableView.deselectedRow
|
||||
guard row >= 0 else { return }
|
||||
let address = accounts[row].address
|
||||
guard row >= 0, let address = wallets[row].ethereumAddress else { return }
|
||||
if let url = URL(string: "https://app.zerion.io/\(address)/overview") {
|
||||
NSWorkspace.shared.open(url)
|
||||
}
|
||||
@ -128,8 +140,8 @@ class AccountsListViewController: NSViewController {
|
||||
|
||||
@objc private func didClickCopyAddress(_ sender: AnyObject) {
|
||||
let row = tableView.deselectedRow
|
||||
guard row >= 0 else { return }
|
||||
NSPasteboard.general.clearAndSetString(accounts[row].address)
|
||||
guard row >= 0, let address = wallets[row].ethereumAddress else { return }
|
||||
NSPasteboard.general.clearAndSetString(address)
|
||||
}
|
||||
|
||||
@objc private func didClickRemoveAccount(_ sender: AnyObject) {
|
||||
@ -151,34 +163,46 @@ class AccountsListViewController: NSViewController {
|
||||
}
|
||||
|
||||
@objc private func didClickExportAccount(_ sender: AnyObject) {
|
||||
// TODO: show different texts for secret words export
|
||||
let row = tableView.deselectedRow
|
||||
guard row >= 0 else { return }
|
||||
let isMnemonic = wallets[row].isMnemonic
|
||||
let alert = Alert()
|
||||
alert.messageText = "Private key gives full access to your funds."
|
||||
|
||||
alert.messageText = "\(isMnemonic ? "Secret words give" : "Private key gives") full access to your funds."
|
||||
alert.alertStyle = .critical
|
||||
alert.addButton(withTitle: "I understand the risks")
|
||||
alert.addButton(withTitle: "Cancel")
|
||||
if alert.runModal() == .alertFirstButtonReturn {
|
||||
agent.askAuthentication(on: view.window, getBackTo: self, onStart: false, reason: .showPrivateKey) { [weak self] allowed in
|
||||
let reason: AuthenticationReason = isMnemonic ? .showSecretWords : .showPrivateKey
|
||||
agent.askAuthentication(on: view.window, getBackTo: self, onStart: false, reason: reason) { [weak self] allowed in
|
||||
Window.activateWindow(self?.view.window)
|
||||
if allowed {
|
||||
self?.showPrivateKey(index: row)
|
||||
self?.showKey(index: row, mnemonic: isMnemonic)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func showPrivateKey(index: Int) {
|
||||
let privateKey = accounts[index].privateKey
|
||||
private func showKey(index: Int, mnemonic: Bool) {
|
||||
let wallet = wallets[index]
|
||||
|
||||
let secret: String
|
||||
if mnemonic, let mnemonicString = try? walletsManager.exportMnemonic(wallet: wallet) {
|
||||
secret = mnemonicString
|
||||
} else if let data = try? walletsManager.exportPrivateKey(wallet: wallet) {
|
||||
secret = data.hexString
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
||||
let alert = Alert()
|
||||
alert.messageText = "Private key"
|
||||
alert.informativeText = privateKey
|
||||
alert.messageText = mnemonic ? "Secret words" : "Private key"
|
||||
alert.informativeText = secret
|
||||
alert.alertStyle = .informational
|
||||
alert.addButton(withTitle: "OK")
|
||||
alert.addButton(withTitle: "Copy")
|
||||
if alert.runModal() != .alertFirstButtonReturn {
|
||||
NSPasteboard.general.clearAndSetString(privateKey)
|
||||
NSPasteboard.general.clearAndSetString(secret)
|
||||
}
|
||||
}
|
||||
|
||||
@ -187,19 +211,19 @@ class AccountsListViewController: NSViewController {
|
||||
}
|
||||
|
||||
private func removeAccountAtIndex(_ index: Int) {
|
||||
accountsService.removeAccount(accounts[index])
|
||||
accounts.remove(at: index)
|
||||
let wallet = wallets[index]
|
||||
try? walletsManager.delete(wallet: wallet)
|
||||
reloadTitle()
|
||||
updateCellModels()
|
||||
tableView.reloadData()
|
||||
}
|
||||
|
||||
private func updateCellModels() {
|
||||
if accounts.isEmpty {
|
||||
if wallets.isEmpty {
|
||||
cellModels = [.addAccountOption(.createNew), .addAccountOption(.importExisting)]
|
||||
tableView.shouldShowRightClickMenu = false
|
||||
} else {
|
||||
cellModels = accounts.map { .account($0) }
|
||||
cellModels = Array(repeating: CellModel.wallet, count: wallets.count)
|
||||
tableView.shouldShowRightClickMenu = true
|
||||
}
|
||||
}
|
||||
@ -213,9 +237,10 @@ extension AccountsListViewController: NSTableViewDelegate {
|
||||
let model = cellModels[row]
|
||||
|
||||
switch model {
|
||||
case let .account(account):
|
||||
if let onSelectedAccount = onSelectedAccount {
|
||||
onSelectedAccount(account)
|
||||
case .wallet:
|
||||
let wallet = wallets[row]
|
||||
if let onSelectedWallet = onSelectedWallet {
|
||||
onSelectedWallet(wallet)
|
||||
} else {
|
||||
Timer.scheduledTimer(withTimeInterval: 0.01, repeats: false) { [weak self] _ in
|
||||
var point = NSEvent.mouseLocation
|
||||
@ -242,9 +267,10 @@ extension AccountsListViewController: NSTableViewDataSource {
|
||||
func tableView(_ tableView: NSTableView, rowViewForRow row: Int) -> NSTableRowView? {
|
||||
let model = cellModels[row]
|
||||
switch model {
|
||||
case let .account(account):
|
||||
case .wallet:
|
||||
let wallet = wallets[row]
|
||||
let rowView = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier("AccountCellView"), owner: self) as? AccountCellView
|
||||
rowView?.setup(address: account.address)
|
||||
rowView?.setup(address: wallet.ethereumAddress ?? "")
|
||||
return rowView
|
||||
case let .addAccountOption(addAccountOption):
|
||||
let rowView = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier("AddAccountOptionCellView"), owner: self) as? AddAccountOptionCellView
|
||||
@ -254,7 +280,7 @@ extension AccountsListViewController: NSTableViewDataSource {
|
||||
}
|
||||
|
||||
func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat {
|
||||
if case .account = cellModels[row] {
|
||||
if case .wallet = cellModels[row] {
|
||||
return 50
|
||||
} else {
|
||||
return 44
|
||||
|
@ -4,9 +4,9 @@ import Cocoa
|
||||
|
||||
class ImportViewController: NSViewController {
|
||||
|
||||
private let accountsService = AccountsService.shared
|
||||
var onSelectedAccount: ((AccountWithKey) -> Void)?
|
||||
private var inputValidationResult = AccountsService.InputValidationResult.invalid
|
||||
private let walletsManager = WalletsManager.shared
|
||||
var onSelectedWallet: ((InkWallet) -> Void)?
|
||||
private var inputValidationResult = WalletsManager.InputValidationResult.invalid
|
||||
|
||||
@IBOutlet weak var textField: NSTextField! {
|
||||
didSet {
|
||||
@ -51,21 +51,23 @@ class ImportViewController: NSViewController {
|
||||
}
|
||||
|
||||
private func importWith(input: String, password: String?) {
|
||||
if accountsService.addAccount(input: input, password: password) != nil {
|
||||
showAccountsList()
|
||||
} else {
|
||||
do {
|
||||
let wallet = try walletsManager.addWallet(input: input, inputPassword: password)
|
||||
showAccountsList(newWalletId: wallet.id)
|
||||
} catch {
|
||||
Alert.showWithMessage("Failed to import account", style: .critical)
|
||||
}
|
||||
}
|
||||
|
||||
private func showAccountsList() {
|
||||
private func showAccountsList(newWalletId: String?) {
|
||||
let accountsListViewController = instantiate(AccountsListViewController.self)
|
||||
accountsListViewController.onSelectedAccount = onSelectedAccount
|
||||
accountsListViewController.onSelectedWallet = onSelectedWallet
|
||||
accountsListViewController.newWalletId = newWalletId
|
||||
view.window?.contentViewController = accountsListViewController
|
||||
}
|
||||
|
||||
@IBAction func cancelButtonTapped(_ sender: NSButton) {
|
||||
showAccountsList()
|
||||
showAccountsList(newWalletId: nil)
|
||||
}
|
||||
|
||||
}
|
||||
@ -73,7 +75,7 @@ class ImportViewController: NSViewController {
|
||||
extension ImportViewController: NSTextFieldDelegate {
|
||||
|
||||
func controlTextDidChange(_ obj: Notification) {
|
||||
inputValidationResult = accountsService.validateAccountInput(textField.stringValue)
|
||||
inputValidationResult = walletsManager.validateWalletInput(textField.stringValue)
|
||||
okButton.isEnabled = inputValidationResult != .invalid
|
||||
}
|
||||
|
||||
|
@ -39,7 +39,7 @@ class PasswordViewController: NSViewController {
|
||||
switch reason {
|
||||
case .none, .start:
|
||||
reasonLabel.stringValue = ""
|
||||
case .sendTransaction, .removeAccount, .showPrivateKey, .signAction:
|
||||
case .sendTransaction, .removeAccount, .showPrivateKey, .showSecretWords, .signAction:
|
||||
reasonLabel.stringValue = "to " + (reason?.title.lowercased() ?? "")
|
||||
}
|
||||
}
|
||||
|
@ -1,89 +0,0 @@
|
||||
// Copyright © 2021 Encrypted Ink. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import WalletCore
|
||||
|
||||
struct AccountsService {
|
||||
|
||||
private init() {}
|
||||
private let keychain = Keychain.shared
|
||||
|
||||
static let shared = AccountsService()
|
||||
|
||||
enum InputValidationResult {
|
||||
case valid, invalid, requiresPassword
|
||||
}
|
||||
|
||||
func validateAccountInput(_ input: String) -> InputValidationResult {
|
||||
if Mnemonic.isValid(mnemonic: input) {
|
||||
return .valid
|
||||
} else if let data = Data(hexString: input) {
|
||||
return PrivateKey.isValid(data: data, curve: CoinType.ethereum.curve) ? .valid : .invalid
|
||||
} else {
|
||||
return input.maybeJSON ? .requiresPassword : .invalid
|
||||
}
|
||||
}
|
||||
|
||||
func createAccount() {
|
||||
guard let password = keychain.password?.data(using: .utf8) else { return }
|
||||
let key = StoredKey(name: "", password: password)
|
||||
guard let privateKey = key.wallet(password: password)?.getKeyForCoin(coin: .ethereum) else { return }
|
||||
_ = saveAccount(privateKey: privateKey)
|
||||
}
|
||||
|
||||
func addAccount(input: String, password: String?) -> AccountWithKey? {
|
||||
let key: PrivateKey
|
||||
if Mnemonic.isValid(mnemonic: input) {
|
||||
key = HDWallet(mnemonic: input, passphrase: "").getKeyForCoin(coin: .ethereum)
|
||||
} else if let data = Data(hexString: input), let privateKey = PrivateKey(data: data) {
|
||||
key = privateKey
|
||||
} else if input.maybeJSON,
|
||||
let password = password,
|
||||
let json = input.data(using: .utf8),
|
||||
let jsonKey = StoredKey.importJSON(json: json),
|
||||
let data = jsonKey.decryptPrivateKey(password: Data(password.utf8)),
|
||||
let privateKey = PrivateKey(data: data) {
|
||||
key = privateKey
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let account = saveAccount(privateKey: key)
|
||||
return account
|
||||
}
|
||||
|
||||
private func saveAccount(privateKey: PrivateKey) -> AccountWithKey? {
|
||||
let address = CoinType.ethereum.deriveAddress(privateKey: privateKey).lowercased()
|
||||
// TODO: use checksum address
|
||||
let account = AccountWithKey(privateKey: privateKey.data.hexString, address: address)
|
||||
var accounts = getAccounts()
|
||||
guard !accounts.contains(where: { $0.address == address }) else { return nil }
|
||||
accounts.append(account)
|
||||
keychain.save(accounts: accounts)
|
||||
return account
|
||||
}
|
||||
|
||||
func removeAccount(_ account: AccountWithKey) {
|
||||
var accounts = getAccounts()
|
||||
accounts.removeAll(where: {$0.address == account.address })
|
||||
keychain.save(accounts: accounts)
|
||||
}
|
||||
|
||||
func getAccounts() -> [AccountWithKey] {
|
||||
return keychain.accounts
|
||||
}
|
||||
|
||||
func getAccountForAddress(_ address: String) -> AccountWithKey? {
|
||||
let allAccounts = getAccounts()
|
||||
return allAccounts.first(where: { $0.address == address.lowercased() })
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private extension String {
|
||||
|
||||
var maybeJSON: Bool {
|
||||
return hasPrefix("{") && hasSuffix("}") && count > 3
|
||||
}
|
||||
|
||||
}
|
@ -4,15 +4,38 @@ import Foundation
|
||||
|
||||
struct Keychain {
|
||||
|
||||
private let prefix = "ink.encrypted.macos."
|
||||
|
||||
private init() {}
|
||||
|
||||
static let shared = Keychain()
|
||||
|
||||
private enum Key: String {
|
||||
case accounts = "ethereum.keys"
|
||||
case password = "password"
|
||||
private enum ItemKey {
|
||||
case legacyAccounts
|
||||
case password
|
||||
case wallet(id: String)
|
||||
|
||||
private static let commonPrefix = "ink.encrypted.macos."
|
||||
private static let walletPrefix = "wallet."
|
||||
private static let fullWalletPrefix = commonPrefix + walletPrefix
|
||||
private static let fullWalletPrefixCount = fullWalletPrefix.count
|
||||
|
||||
var stringValue: String {
|
||||
let key: String
|
||||
switch self {
|
||||
case .legacyAccounts:
|
||||
key = "ethereum.keys"
|
||||
case .password:
|
||||
key = "password"
|
||||
case let .wallet(id: id):
|
||||
key = ItemKey.walletPrefix + id
|
||||
}
|
||||
return ItemKey.commonPrefix + key
|
||||
}
|
||||
|
||||
static func walletId(key: String) -> String? {
|
||||
guard key.hasPrefix(fullWalletPrefix) else { return nil }
|
||||
return String(key.dropFirst(fullWalletPrefixCount))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var password: String? {
|
||||
@ -28,63 +51,84 @@ struct Keychain {
|
||||
save(data: data, key: .password)
|
||||
}
|
||||
|
||||
var accounts: [AccountWithKey] {
|
||||
if let data = get(key: .accounts), let accounts = try? JSONDecoder().decode([AccountWithKey].self, from: data) {
|
||||
// MARK: - WalletCore
|
||||
|
||||
func getAllWalletsIds() -> [String] {
|
||||
let allKeys = allStoredItemsKeys()
|
||||
let ids = allKeys.compactMap { ItemKey.walletId(key: $0) }
|
||||
return ids
|
||||
}
|
||||
|
||||
func getWalletData(id: String) -> Data? {
|
||||
return get(key: .wallet(id: id))
|
||||
}
|
||||
|
||||
func saveWallet(id: String, data: Data) throws {
|
||||
save(data: data, key: .wallet(id: id))
|
||||
}
|
||||
|
||||
func removeWallet(id: String) throws {
|
||||
removeData(forKey: .wallet(id: id))
|
||||
}
|
||||
|
||||
func removeAllWallets() throws {
|
||||
for id in getAllWalletsIds() {
|
||||
removeData(forKey: .wallet(id: id))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Legacy
|
||||
|
||||
func getLegacyAccounts() throws -> [LegacyAccountWithKey] {
|
||||
if let data = get(key: .legacyAccounts), let accounts = try? JSONDecoder().decode([LegacyAccountWithKey].self, from: data) {
|
||||
return accounts
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
func save(accounts: [AccountWithKey]) {
|
||||
guard let data = try? JSONEncoder().encode(accounts) else { return }
|
||||
save(data: data, key: .accounts)
|
||||
}
|
||||
|
||||
// MARK: - WalletCore
|
||||
|
||||
func getAllWalletsIds() -> [String] {
|
||||
// TODO: implement
|
||||
return []
|
||||
}
|
||||
|
||||
func getWalletData(id: String) -> Data? {
|
||||
// TODO: implement
|
||||
return nil
|
||||
}
|
||||
|
||||
func saveWallet(id: String, data: Data) throws {
|
||||
// TODO: implement
|
||||
}
|
||||
|
||||
func removeWallet(id: String) throws {
|
||||
// TODO: implement
|
||||
}
|
||||
|
||||
func removeAllWallets() throws {
|
||||
// TODO: implement
|
||||
func removeLegacyAccounts() throws {
|
||||
removeData(forKey: .legacyAccounts)
|
||||
}
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private func save(data: Data, key: Key) {
|
||||
let query = [kSecClass as String: kSecClassGenericPassword as String,
|
||||
kSecAttrAccount as String: prefix + key.rawValue,
|
||||
kSecValueData as String: data] as [String: Any]
|
||||
private func save(data: Data, key: ItemKey) {
|
||||
let query: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
|
||||
kSecAttrAccount as String: key.stringValue,
|
||||
kSecValueData as String: data]
|
||||
SecItemDelete(query as CFDictionary)
|
||||
SecItemAdd(query as CFDictionary, nil)
|
||||
}
|
||||
|
||||
private func get(key: Key) -> Data? {
|
||||
guard let returnDataQueryValue = kCFBooleanTrue else { return nil }
|
||||
let query = [kSecClass as String: kSecClassGenericPassword,
|
||||
kSecAttrAccount as String: prefix + key.rawValue,
|
||||
kSecReturnData as String: returnDataQueryValue,
|
||||
kSecMatchLimit as String: kSecMatchLimitOne] as [String: Any]
|
||||
|
||||
var dataTypeRef: AnyObject?
|
||||
let status: OSStatus = SecItemCopyMatching(query as CFDictionary, &dataTypeRef)
|
||||
if status == noErr, let data = dataTypeRef as? Data {
|
||||
private func allStoredItemsKeys() -> [String] {
|
||||
let query: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
|
||||
kSecReturnData as String: false,
|
||||
kSecReturnAttributes as String: true,
|
||||
kSecMatchLimit as String: kSecMatchLimitAll]
|
||||
var items: CFTypeRef?
|
||||
let status: OSStatus = SecItemCopyMatching(query as CFDictionary, &items)
|
||||
if status == noErr, let items = items as? [[String: Any]], !items.isEmpty {
|
||||
return items.compactMap { $0[kSecAttrAccount as String] as? String }
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
private func removeData(forKey key: ItemKey) {
|
||||
let query: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
|
||||
kSecAttrAccount as String: key.stringValue]
|
||||
SecItemDelete(query as CFDictionary)
|
||||
}
|
||||
|
||||
private func get(key: ItemKey) -> Data? {
|
||||
let query: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
|
||||
kSecAttrAccount as String: key.stringValue,
|
||||
kSecReturnData as String: true,
|
||||
kSecMatchLimit as String: kSecMatchLimitOne]
|
||||
var item: CFTypeRef?
|
||||
let status: OSStatus = SecItemCopyMatching(query as CFDictionary, &item)
|
||||
if status == noErr, let data = item as? Data {
|
||||
return data
|
||||
} else {
|
||||
return nil
|
||||
|
@ -7,7 +7,7 @@ class SessionStorage {
|
||||
|
||||
struct Item: Codable {
|
||||
let session: WCSession
|
||||
let address: String
|
||||
let walletId: String
|
||||
let clientId: String
|
||||
let sessionDetails: WCSessionRequestParam
|
||||
}
|
||||
@ -51,8 +51,8 @@ class SessionStorage {
|
||||
}
|
||||
}
|
||||
|
||||
func add(interactor: WCInteractor, address: String, sessionDetails: WCSessionRequestParam) {
|
||||
let item = Item(session: interactor.session, address: address, clientId: interactor.clientId, sessionDetails: sessionDetails)
|
||||
func add(interactor: WCInteractor, walletId: String, sessionDetails: WCSessionRequestParam) {
|
||||
let item = Item(session: interactor.session, walletId: walletId, clientId: interactor.clientId, sessionDetails: sessionDetails)
|
||||
WCSessionStore.store(interactor.session, peerId: sessionDetails.peerId, peerMeta: sessionDetails.peerMeta)
|
||||
Defaults.storedSessions[interactor.clientId] = item
|
||||
didInteractWith(clientId: interactor.clientId)
|
||||
|
@ -15,9 +15,19 @@ class AccountCellView: NSTableRowView {
|
||||
@IBOutlet weak var addressTextField: NSTextField!
|
||||
|
||||
func setup(address: String) {
|
||||
addressImageView.image = Blockies(seed: address).createImage()
|
||||
addressImageView.image = Blockies(seed: address.lowercased()).createImage()
|
||||
let without0x = address.dropFirst(2)
|
||||
addressTextField.stringValue = without0x.prefix(4) + "..." + without0x.suffix(4)
|
||||
}
|
||||
|
||||
func blink() {
|
||||
let initialBackgroundColor = backgroundColor
|
||||
backgroundColor = .inkGreen
|
||||
NSAnimationContext.runAnimationGroup { [weak self] context in
|
||||
context.duration = 1.2
|
||||
context.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut)
|
||||
self?.animator().backgroundColor = initialBackgroundColor
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ class WalletConnect {
|
||||
private let sessionStorage = SessionStorage.shared
|
||||
private let networkMonitor = NetworkMonitor.shared
|
||||
private let ethereum = Ethereum.shared
|
||||
private let accountsService = AccountsService.shared
|
||||
private let walletsManager = WalletsManager.shared
|
||||
|
||||
static let shared = WalletConnect()
|
||||
|
||||
@ -24,10 +24,10 @@ class WalletConnect {
|
||||
return WCSession.from(string: link)
|
||||
}
|
||||
|
||||
func connect(session: WCSession, address: String, uuid: UUID = UUID(), completion: @escaping ((Bool) -> Void)) {
|
||||
func connect(session: WCSession, walletId: String, uuid: UUID = UUID(), completion: @escaping ((Bool) -> Void)) {
|
||||
let clientMeta = WCPeerMeta(name: "Encrypted Ink", url: "https://encrypted.ink", description: "Ethereum agent for macOS", icons: ["https://encrypted.ink/icon.png"])
|
||||
let interactor = WCInteractor(session: session, meta: clientMeta, uuid: uuid)
|
||||
configure(interactor: interactor, address: address)
|
||||
configure(interactor: interactor, walletId: walletId)
|
||||
|
||||
interactor.connect().done { connected in
|
||||
completion(connected)
|
||||
@ -45,7 +45,7 @@ class WalletConnect {
|
||||
|
||||
for item in items {
|
||||
guard let uuid = UUID(uuidString: item.clientId) else { continue }
|
||||
connect(session: item.session, address: item.address, uuid: uuid) { _ in }
|
||||
connect(session: item.session, walletId: item.walletId, uuid: uuid) { _ in }
|
||||
peers[item.clientId] = item.sessionDetails.peerMeta
|
||||
}
|
||||
}
|
||||
@ -83,7 +83,8 @@ class WalletConnect {
|
||||
return peers[id]
|
||||
}
|
||||
|
||||
private func configure(interactor: WCInteractor, address: String) {
|
||||
private func configure(interactor: WCInteractor, walletId: String) {
|
||||
guard let address = walletsManager.getWallet(id: walletId)?.ethereumAddress else { return }
|
||||
let accounts = [address]
|
||||
let chainId = 1
|
||||
|
||||
@ -92,7 +93,7 @@ class WalletConnect {
|
||||
interactor.onSessionRequest = { [weak self, weak interactor] (id, peerParam) in
|
||||
guard let interactor = interactor else { return }
|
||||
self?.peers[interactor.clientId] = peerParam.peerMeta
|
||||
self?.sessionStorage.add(interactor: interactor, address: address, sessionDetails: peerParam)
|
||||
self?.sessionStorage.add(interactor: interactor, walletId: walletId, sessionDetails: peerParam)
|
||||
interactor.approveSession(accounts: accounts, chainId: chainId).cauterize()
|
||||
}
|
||||
|
||||
@ -103,12 +104,12 @@ class WalletConnect {
|
||||
}
|
||||
|
||||
interactor.eth.onSign = { [weak self, weak interactor] (id, payload) in
|
||||
self?.approveSign(id: id, payload: payload, address: address, interactor: interactor)
|
||||
self?.approveSign(id: id, payload: payload, walletId: walletId, interactor: interactor)
|
||||
self?.sessionStorage.didInteractWith(clientId: interactor?.clientId)
|
||||
}
|
||||
|
||||
interactor.eth.onTransaction = { [weak self, weak interactor] (id, event, transaction) in
|
||||
self?.approveTransaction(id: id, wct: transaction, address: address, interactor: interactor)
|
||||
self?.approveTransaction(id: id, wct: transaction, walletId: walletId, interactor: interactor)
|
||||
self?.sessionStorage.didInteractWith(clientId: interactor?.clientId)
|
||||
}
|
||||
}
|
||||
@ -124,7 +125,7 @@ class WalletConnect {
|
||||
}
|
||||
}
|
||||
|
||||
private func approveTransaction(id: Int64, wct: WCEthereumTransaction, address: String, interactor: WCInteractor?) {
|
||||
private func approveTransaction(id: Int64, wct: WCEthereumTransaction, walletId: String, interactor: WCInteractor?) {
|
||||
guard let to = wct.to else {
|
||||
rejectRequest(id: id, interactor: interactor, message: "Something went wrong.")
|
||||
return
|
||||
@ -134,14 +135,14 @@ class WalletConnect {
|
||||
let transaction = Transaction(from: wct.from, to: to, nonce: wct.nonce, gasPrice: wct.gasPrice, gas: wct.gas, value: wct.value, data: wct.data)
|
||||
Agent.shared.showApprove(transaction: transaction, peerMeta: peer) { [weak self, weak interactor] transaction in
|
||||
if let transaction = transaction {
|
||||
self?.sendTransaction(transaction, address: address, requestId: id, interactor: interactor)
|
||||
self?.sendTransaction(transaction, walletId: walletId, requestId: id, interactor: interactor)
|
||||
} else {
|
||||
self?.rejectRequest(id: id, interactor: interactor, message: "Cancelled")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func approveSign(id: Int64, payload: WCEthereumSignPayload, address: String, interactor: WCInteractor?) {
|
||||
private func approveSign(id: Int64, payload: WCEthereumSignPayload, walletId: String, interactor: WCInteractor?) {
|
||||
var message: String?
|
||||
let title: String
|
||||
switch payload {
|
||||
@ -161,7 +162,7 @@ class WalletConnect {
|
||||
let peer = getPeerOfInteractor(interactor)
|
||||
Agent.shared.showApprove(title: title, meta: message ?? "", peerMeta: peer) { [weak self, weak interactor] approved in
|
||||
if approved {
|
||||
self?.sign(id: id, message: message, payload: payload, address: address, interactor: interactor)
|
||||
self?.sign(id: id, message: message, payload: payload, walletId: walletId, interactor: interactor)
|
||||
} else {
|
||||
self?.rejectRequest(id: id, interactor: interactor, message: "Cancelled")
|
||||
}
|
||||
@ -172,31 +173,31 @@ class WalletConnect {
|
||||
interactor?.rejectRequest(id: id, message: message).cauterize()
|
||||
}
|
||||
|
||||
private func sendTransaction(_ transaction: Transaction, address: String, requestId: Int64, interactor: WCInteractor?) {
|
||||
guard let account = accountsService.getAccountForAddress(address) else {
|
||||
private func sendTransaction(_ transaction: Transaction, walletId: String, requestId: Int64, interactor: WCInteractor?) {
|
||||
guard let wallet = walletsManager.getWallet(id: walletId) else {
|
||||
rejectRequest(id: requestId, interactor: interactor, message: "Something went wrong.")
|
||||
return
|
||||
}
|
||||
guard let hash = try? ethereum.send(transaction: transaction, account: account) else {
|
||||
guard let hash = try? ethereum.send(transaction: transaction, wallet: wallet) else {
|
||||
rejectRequest(id: requestId, interactor: interactor, message: "Failed to send")
|
||||
return
|
||||
}
|
||||
interactor?.approveRequest(id: requestId, result: hash).cauterize()
|
||||
}
|
||||
|
||||
private func sign(id: Int64, message: String?, payload: WCEthereumSignPayload, address: String, interactor: WCInteractor?) {
|
||||
guard let message = message, let account = accountsService.getAccountForAddress(address) else {
|
||||
private func sign(id: Int64, message: String?, payload: WCEthereumSignPayload, walletId: String, interactor: WCInteractor?) {
|
||||
guard let message = message, let wallet = walletsManager.getWallet(id: walletId) else {
|
||||
rejectRequest(id: id, interactor: interactor, message: "Something went wrong.")
|
||||
return
|
||||
}
|
||||
var signed: String?
|
||||
switch payload {
|
||||
case .personalSign:
|
||||
signed = try? ethereum.signPersonal(message: message, account: account)
|
||||
signed = try? ethereum.signPersonal(message: message, wallet: wallet)
|
||||
case .signTypeData:
|
||||
signed = try? ethereum.sign(typedData: message, account: account)
|
||||
signed = try? ethereum.sign(typedData: message, wallet: wallet)
|
||||
case .sign:
|
||||
signed = try? ethereum.sign(message: message, account: account)
|
||||
signed = try? ethereum.sign(message: message, wallet: wallet)
|
||||
}
|
||||
guard let result = signed else {
|
||||
rejectRequest(id: id, interactor: interactor, message: "Something went wrong.")
|
||||
|
66
Encrypted Ink/Wallets/InkWallet.swift
Normal file
66
Encrypted Ink/Wallets/InkWallet.swift
Normal file
@ -0,0 +1,66 @@
|
||||
// Copyright © 2021 Encrypted Ink. All rights reserved.
|
||||
// Rewrite of Wallet.swift from Trust Wallet Core.
|
||||
|
||||
import Foundation
|
||||
import WalletCore
|
||||
|
||||
final class InkWallet: Hashable, Equatable {
|
||||
|
||||
let id: String
|
||||
var key: StoredKey
|
||||
|
||||
var accounts: [Account] {
|
||||
return (0..<key.accountCount).compactMap({ key.account(index: $0) })
|
||||
}
|
||||
|
||||
init(id: String, key: StoredKey) {
|
||||
self.id = id
|
||||
self.key = key
|
||||
}
|
||||
|
||||
func getAccount(password: String, coin: CoinType) throws -> Account {
|
||||
let wallet = key.wallet(password: Data(password.utf8))
|
||||
guard let account = key.accountForCoin(coin: coin, wallet: wallet) else { throw KeyStore.Error.invalidPassword }
|
||||
return account
|
||||
}
|
||||
|
||||
func getAccounts(password: String, coins: [CoinType]) throws -> [Account] {
|
||||
guard let wallet = key.wallet(password: Data(password.utf8)) else { throw KeyStore.Error.invalidPassword }
|
||||
return coins.compactMap({ key.accountForCoin(coin: $0, wallet: wallet) })
|
||||
}
|
||||
|
||||
func privateKey(password: String, coin: CoinType) throws -> PrivateKey {
|
||||
guard let privateKey = key.privateKey(coin: coin, password: Data(password.utf8)) else { throw KeyStore.Error.invalidPassword }
|
||||
return privateKey
|
||||
}
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(id)
|
||||
}
|
||||
|
||||
static func == (lhs: InkWallet, rhs: InkWallet) -> Bool {
|
||||
return lhs.id == rhs.id
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension InkWallet {
|
||||
|
||||
var ethereumAddress: String? {
|
||||
return accounts.first(where: { $0.coin == .ethereum })?.address
|
||||
}
|
||||
|
||||
var ethereumPrivateKey: PrivateKey? {
|
||||
guard let password = Keychain.shared.password else { return nil }
|
||||
return try? privateKey(password: password, coin: .ethereum)
|
||||
}
|
||||
|
||||
var ethereumPrivateKeyString: String? {
|
||||
return ethereumPrivateKey?.data.hexString
|
||||
}
|
||||
|
||||
var isMnemonic: Bool {
|
||||
return key.isMnemonic
|
||||
}
|
||||
|
||||
}
|
195
Encrypted Ink/Wallets/WalletsManager.swift
Normal file
195
Encrypted Ink/Wallets/WalletsManager.swift
Normal file
@ -0,0 +1,195 @@
|
||||
// Copyright © 2021 Encrypted Ink. All rights reserved.
|
||||
// Rewrite of KeyStore.swift from Trust Wallet Core.
|
||||
|
||||
import Foundation
|
||||
import WalletCore
|
||||
|
||||
final class WalletsManager {
|
||||
|
||||
enum Error: Swift.Error {
|
||||
case keychainAccessFailure
|
||||
case invalidInput
|
||||
}
|
||||
|
||||
enum InputValidationResult {
|
||||
case valid, invalid, requiresPassword
|
||||
}
|
||||
|
||||
static let shared = WalletsManager()
|
||||
private let keychain = Keychain.shared
|
||||
private(set) var wallets = [InkWallet]()
|
||||
|
||||
private init() {}
|
||||
|
||||
func start() {
|
||||
try? load()
|
||||
try? migrateFromLegacyIfNeeded()
|
||||
}
|
||||
|
||||
func validateWalletInput(_ input: String) -> InputValidationResult {
|
||||
if Mnemonic.isValid(mnemonic: input) {
|
||||
return .valid
|
||||
} else if let data = Data(hexString: input) {
|
||||
return PrivateKey.isValid(data: data, curve: CoinType.ethereum.curve) ? .valid : .invalid
|
||||
} else {
|
||||
return input.maybeJSON ? .requiresPassword : .invalid
|
||||
}
|
||||
}
|
||||
|
||||
func createWallet() throws -> InkWallet {
|
||||
guard let password = keychain.password else { throw Error.keychainAccessFailure }
|
||||
return try createWallet(name: defaultWalletName, password: password, coin: .ethereum)
|
||||
}
|
||||
|
||||
func getWallet(id: String) -> InkWallet? {
|
||||
return wallets.first(where: { $0.id == id })
|
||||
}
|
||||
|
||||
func addWallet(input: String, inputPassword: String?) throws -> InkWallet {
|
||||
guard let password = keychain.password else { throw Error.keychainAccessFailure }
|
||||
let name = defaultWalletName
|
||||
let coin = CoinType.ethereum
|
||||
if Mnemonic.isValid(mnemonic: input) {
|
||||
return try importMnemonic(input, name: name, encryptPassword: password, coin: coin)
|
||||
} else if let data = Data(hexString: input), PrivateKey.isValid(data: data, curve: coin.curve), let privateKey = PrivateKey(data: data) {
|
||||
return try importPrivateKey(privateKey, name: name, password: password, coin: coin)
|
||||
} else if input.maybeJSON, let inputPassword = inputPassword, let json = input.data(using: .utf8) {
|
||||
return try importJSON(json, name: name, password: inputPassword, newPassword: password, coin: coin)
|
||||
} else {
|
||||
throw Error.invalidInput
|
||||
}
|
||||
}
|
||||
|
||||
private func createWallet(name: String, password: String, coin: CoinType) throws -> InkWallet {
|
||||
let key = StoredKey(name: name, password: Data(password.utf8))
|
||||
let id = makeNewWalletId()
|
||||
let wallet = InkWallet(id: id, key: key)
|
||||
_ = try wallet.getAccount(password: password, coin: coin)
|
||||
wallets.append(wallet)
|
||||
try save(wallet: wallet)
|
||||
return wallet
|
||||
}
|
||||
|
||||
private func importJSON(_ json: Data, name: String, password: String, newPassword: String, coin: CoinType) throws -> InkWallet {
|
||||
guard let key = StoredKey.importJSON(json: json) else { throw KeyStore.Error.invalidKey }
|
||||
guard let data = key.decryptPrivateKey(password: Data(password.utf8)) else { throw KeyStore.Error.invalidPassword }
|
||||
if let mnemonic = checkMnemonic(data) { return try self.importMnemonic(mnemonic, name: name, encryptPassword: newPassword, coin: coin) }
|
||||
guard let privateKey = PrivateKey(data: data) else { throw KeyStore.Error.invalidKey }
|
||||
return try self.importPrivateKey(privateKey, name: name, password: newPassword, coin: coin)
|
||||
}
|
||||
|
||||
private func checkMnemonic(_ data: Data) -> String? {
|
||||
guard let mnemonic = String(data: data, encoding: .ascii), Mnemonic.isValid(mnemonic: mnemonic) else { return nil }
|
||||
return mnemonic
|
||||
}
|
||||
|
||||
private func importPrivateKey(_ privateKey: PrivateKey, name: String, password: String, coin: CoinType) throws -> InkWallet {
|
||||
guard let newKey = StoredKey.importPrivateKey(privateKey: privateKey.data, name: name, password: Data(password.utf8), coin: coin) else { throw KeyStore.Error.invalidKey }
|
||||
let id = makeNewWalletId()
|
||||
let wallet = InkWallet(id: id, key: newKey)
|
||||
_ = try wallet.getAccount(password: password, coin: coin)
|
||||
wallets.append(wallet)
|
||||
try save(wallet: wallet)
|
||||
return wallet
|
||||
}
|
||||
|
||||
private func importMnemonic(_ mnemonic: String, name: String, encryptPassword: String, coin: CoinType) throws -> InkWallet {
|
||||
guard let key = StoredKey.importHDWallet(mnemonic: mnemonic, name: name, password: Data(encryptPassword.utf8), coin: coin) else { throw KeyStore.Error.invalidMnemonic }
|
||||
let id = makeNewWalletId()
|
||||
let wallet = InkWallet(id: id, key: key)
|
||||
_ = try wallet.getAccount(password: encryptPassword, coin: coin)
|
||||
wallets.append(wallet)
|
||||
try save(wallet: wallet)
|
||||
return wallet
|
||||
}
|
||||
|
||||
func exportPrivateKey(wallet: InkWallet) throws -> Data {
|
||||
guard let password = keychain.password else { throw Error.keychainAccessFailure }
|
||||
guard let key = wallet.key.decryptPrivateKey(password: Data(password.utf8)) else { throw KeyStore.Error.invalidPassword }
|
||||
return key
|
||||
}
|
||||
|
||||
func exportMnemonic(wallet: InkWallet) throws -> String {
|
||||
guard let password = keychain.password else { throw Error.keychainAccessFailure }
|
||||
guard let mnemonic = wallet.key.decryptMnemonic(password: Data(password.utf8)) else { throw KeyStore.Error.invalidPassword }
|
||||
return mnemonic
|
||||
}
|
||||
|
||||
func update(wallet: InkWallet, password: String, newPassword: String) throws {
|
||||
try update(wallet: wallet, password: password, newPassword: newPassword, newName: wallet.key.name)
|
||||
}
|
||||
|
||||
func update(wallet: InkWallet, password: String, newName: String) throws {
|
||||
try update(wallet: wallet, password: password, newPassword: password, newName: newName)
|
||||
}
|
||||
|
||||
func delete(wallet: InkWallet) throws {
|
||||
guard let password = keychain.password else { throw Error.keychainAccessFailure }
|
||||
guard let index = wallets.firstIndex(of: wallet) else { throw KeyStore.Error.accountNotFound }
|
||||
guard var privateKey = wallet.key.decryptPrivateKey(password: Data(password.utf8)) else { throw KeyStore.Error.invalidKey }
|
||||
defer { privateKey.resetBytes(in: 0..<privateKey.count) }
|
||||
wallets.remove(at: index)
|
||||
try keychain.removeWallet(id: wallet.id)
|
||||
}
|
||||
|
||||
func destroy() throws {
|
||||
wallets.removeAll(keepingCapacity: false)
|
||||
try keychain.removeAllWallets()
|
||||
}
|
||||
|
||||
private func load() throws {
|
||||
let ids = keychain.getAllWalletsIds()
|
||||
for id in ids {
|
||||
guard let data = keychain.getWalletData(id: id), let key = StoredKey.importJSON(json: data) else { continue }
|
||||
let wallet = InkWallet(id: id, key: key)
|
||||
wallets.append(wallet)
|
||||
}
|
||||
}
|
||||
|
||||
private func migrateFromLegacyIfNeeded() throws {
|
||||
let legacyAccountsWithKeys = try keychain.getLegacyAccounts()
|
||||
guard !legacyAccountsWithKeys.isEmpty, let password = keychain.password else { return }
|
||||
for legacyAccount in legacyAccountsWithKeys {
|
||||
if let data = Data(hexString: legacyAccount.privateKey), let privateKey = PrivateKey(data: data) {
|
||||
_ = try importPrivateKey(privateKey, name: defaultWalletName, password: password, coin: .ethereum)
|
||||
}
|
||||
}
|
||||
try keychain.removeLegacyAccounts()
|
||||
}
|
||||
|
||||
private func update(wallet: InkWallet, password: String, newPassword: String, newName: String) throws {
|
||||
guard let index = wallets.firstIndex(of: wallet) else { throw KeyStore.Error.accountNotFound }
|
||||
guard var privateKeyData = wallet.key.decryptPrivateKey(password: Data(password.utf8)) else { throw KeyStore.Error.invalidPassword }
|
||||
defer { privateKeyData.resetBytes(in: 0 ..< privateKeyData.count) }
|
||||
let coins = wallet.accounts.map({ $0.coin })
|
||||
guard !coins.isEmpty else { throw KeyStore.Error.accountNotFound }
|
||||
|
||||
if let mnemonic = checkMnemonic(privateKeyData),
|
||||
let key = StoredKey.importHDWallet(mnemonic: mnemonic, name: newName, password: Data(newPassword.utf8), coin: coins[0]) {
|
||||
wallets[index].key = key
|
||||
} else if let key = StoredKey.importPrivateKey(
|
||||
privateKey: privateKeyData, name: newName, password: Data(newPassword.utf8), coin: coins[0]) {
|
||||
wallets[index].key = key
|
||||
} else {
|
||||
throw KeyStore.Error.invalidKey
|
||||
}
|
||||
|
||||
_ = try wallets[index].getAccounts(password: newPassword, coins: coins)
|
||||
try save(wallet: wallets[index])
|
||||
}
|
||||
|
||||
private func save(wallet: InkWallet) throws {
|
||||
guard let data = wallet.key.exportJSON() else { throw KeyStore.Error.invalidPassword }
|
||||
try keychain.saveWallet(id: wallet.id, data: data)
|
||||
}
|
||||
|
||||
private var defaultWalletName = ""
|
||||
|
||||
private func makeNewWalletId() -> String {
|
||||
let uuid = UUID().uuidString
|
||||
let date = Date().timeIntervalSince1970
|
||||
let walletId = "\(uuid)-\(date)"
|
||||
return walletId
|
||||
}
|
||||
|
||||
}
|
2
Podfile
2
Podfile
@ -5,7 +5,7 @@ target 'Encrypted Ink' do
|
||||
use_frameworks!
|
||||
pod 'Web3Swift.io', :git => 'https://github.com/zeriontech/Web3Swift.git', :branch => 'develop'
|
||||
pod 'BlockiesSwift'
|
||||
pod 'Kingfisher'
|
||||
pod 'Kingfisher', '~> 5.0'
|
||||
pod 'WalletConnect', git: 'https://github.com/grachyov/wallet-connect-swift', branch: 'master'
|
||||
pod 'TrustWalletCore'
|
||||
end
|
||||
|
12
Podfile.lock
12
Podfile.lock
@ -2,7 +2,9 @@ PODS:
|
||||
- BigInt (5.2.0)
|
||||
- BlockiesSwift (0.1.2)
|
||||
- CryptoSwift (1.4.1)
|
||||
- Kingfisher (6.3.0)
|
||||
- Kingfisher (5.15.8):
|
||||
- Kingfisher/Core (= 5.15.8)
|
||||
- Kingfisher/Core (5.15.8)
|
||||
- PromiseKit (6.15.3):
|
||||
- PromiseKit/CorePromise (= 6.15.3)
|
||||
- PromiseKit/Foundation (= 6.15.3)
|
||||
@ -32,7 +34,7 @@ PODS:
|
||||
|
||||
DEPENDENCIES:
|
||||
- BlockiesSwift
|
||||
- Kingfisher
|
||||
- Kingfisher (~> 5.0)
|
||||
- TrustWalletCore
|
||||
- WalletConnect (from `https://github.com/grachyov/wallet-connect-swift`, branch `master`)
|
||||
- Web3Swift.io (from `https://github.com/zeriontech/Web3Swift.git`, branch `develop`)
|
||||
@ -70,7 +72,7 @@ SPEC CHECKSUMS:
|
||||
BigInt: f668a80089607f521586bbe29513d708491ef2f7
|
||||
BlockiesSwift: 22d8d56dd187e6bfd16cb8c8fbd4fd4896c3e65d
|
||||
CryptoSwift: 0bc800a7e6a24c4fc9ebeab97d44b0d5f73a78bd
|
||||
Kingfisher: 6c3df386db71d82c0817a429d2c9421a77396529
|
||||
Kingfisher: a3c03d702433fa6cfedabb2bddbe076fb8f2e902
|
||||
PromiseKit: 3b2b6995e51a954c46dbc550ce3da44fbfb563c5
|
||||
secp256k1.swift: a7e7a214f6db6ce5db32cc6b2b45e5c4dd633634
|
||||
Starscream: 4bb2f9942274833f7b4d296a55504dcfc7edb7b0
|
||||
@ -80,6 +82,6 @@ SPEC CHECKSUMS:
|
||||
WalletConnect: 1df75d4355b1cacfc27d7ef2416fae43862d0eb4
|
||||
Web3Swift.io: 18fd06aed9d56df9c704f9c6f87b06675bb05b53
|
||||
|
||||
PODFILE CHECKSUM: 435285424c0821db925918dfd84843c031999350
|
||||
PODFILE CHECKSUM: c3841a90049661c002c630eeb332566c663ce315
|
||||
|
||||
COCOAPODS: 1.10.1
|
||||
COCOAPODS: 1.10.0
|
||||
|
Loading…
Reference in New Issue
Block a user