@novastera-oss/nitro-metamask 0.6.3 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NitroMetamask.podspec +12 -3
- package/README.md +3 -1
- package/android/build.gradle +14 -32
- package/android/cargo-ecies.gradle +60 -88
- package/android/src/main/aidl/io/metamask/nativesdk/IMessegeService.aidl +8 -0
- package/android/src/main/aidl/io/metamask/nativesdk/IMessegeServiceCallback.aidl +8 -0
- package/android/src/main/java/com/margelo/nitro/nitrometamask/HybridNitroMetamask.kt +101 -3
- package/android/src/main/java/io/metamask/androidsdk/AnyRequest.kt +8 -0
- package/android/src/main/java/io/metamask/androidsdk/ClientMessageServiceCallback.kt +12 -0
- package/android/src/main/java/io/metamask/androidsdk/ClientServiceConnection.kt +42 -0
- package/android/src/main/java/io/metamask/androidsdk/CommunicationClient.kt +525 -0
- package/android/src/main/java/io/metamask/androidsdk/CommunicationClientModule.kt +47 -0
- package/android/src/main/java/io/metamask/androidsdk/CommunicationClientModuleInterface.kt +11 -0
- package/android/src/main/java/io/metamask/androidsdk/Constants.kt +5 -0
- package/android/src/main/java/io/metamask/androidsdk/Crypto.kt +35 -0
- package/android/src/main/java/io/metamask/androidsdk/DappMetadata.kt +36 -0
- package/android/src/main/java/io/metamask/androidsdk/Encryption.kt +9 -0
- package/android/src/main/java/io/metamask/androidsdk/ErrorType.kt +41 -0
- package/android/src/main/java/io/metamask/androidsdk/Ethereum.kt +328 -0
- package/android/src/main/java/io/metamask/androidsdk/EthereumEventCallback.kt +6 -0
- package/android/src/main/java/io/metamask/androidsdk/EthereumMethod.kt +80 -0
- package/android/src/main/java/io/metamask/androidsdk/EthereumRequest.kt +7 -0
- package/android/src/main/java/io/metamask/androidsdk/EthereumState.kt +7 -0
- package/android/src/main/java/io/metamask/androidsdk/KeyExchange.kt +77 -0
- package/android/src/main/java/io/metamask/androidsdk/KeyExchangeMessageType.kt +20 -0
- package/android/src/main/java/io/metamask/androidsdk/KeyStorage.kt +122 -0
- package/android/src/main/java/io/metamask/androidsdk/Logger.kt +18 -0
- package/android/src/main/java/io/metamask/androidsdk/Message.kt +3 -0
- package/android/src/main/java/io/metamask/androidsdk/MessageType.kt +11 -0
- package/android/src/main/java/io/metamask/androidsdk/OriginatorInfo.kt +12 -0
- package/android/src/main/java/io/metamask/androidsdk/RequestError.kt +8 -0
- package/android/src/main/java/io/metamask/androidsdk/RequestInfo.kt +9 -0
- package/android/src/main/java/io/metamask/androidsdk/Result.kt +11 -0
- package/android/src/main/java/io/metamask/androidsdk/RpcRequest.kt +7 -0
- package/android/src/main/java/io/metamask/androidsdk/SDKInfo.kt +6 -0
- package/android/src/main/java/io/metamask/androidsdk/SDKOptions.kt +6 -0
- package/android/src/main/java/io/metamask/androidsdk/SecureStorage.kt +9 -0
- package/android/src/main/java/io/metamask/androidsdk/SessionConfig.kt +10 -0
- package/android/src/main/java/io/metamask/androidsdk/SessionManager.kt +92 -0
- package/android/src/main/java/io/metamask/androidsdk/SubmittedRequest.kt +8 -0
- package/android/src/main/java/io/metamask/androidsdk/TimeStampGenerator.kt +7 -0
- package/android/src/main/jniLibs/arm64-v8a/libecies.so +0 -0
- package/android/src/main/jniLibs/armeabi-v7a/libecies.so +0 -0
- package/android/src/main/jniLibs/x86/libecies.so +0 -0
- package/android/src/main/jniLibs/x86_64/libecies.so +0 -0
- package/android/src/test/java/com/margelo/nitro/nitrometamask/CancellationStateMachineTest.kt +128 -0
- package/android/src/test/java/com/margelo/nitro/nitrometamask/ChainIdParsingTest.kt +65 -0
- package/android/src/test/java/com/margelo/nitro/nitrometamask/ConfigureStateMachineTest.kt +140 -0
- package/android/src/test/java/com/margelo/nitro/nitrometamask/ConnectSignJsonTest.kt +76 -0
- package/android/src/test/java/com/margelo/nitro/nitrometamask/MetaMaskInstallationCheckTest.kt +42 -0
- package/android/src/test/java/com/margelo/nitro/nitrometamask/PersonalSignParamsTest.kt +75 -0
- package/ios/Frameworks/Ecies.xcframework/Info.plist +47 -0
- package/ios/Frameworks/Ecies.xcframework/ios-arm64/Headers/ecies.h +20 -0
- package/ios/Frameworks/Ecies.xcframework/ios-arm64/Headers/module.modulemap +4 -0
- package/ios/Frameworks/Ecies.xcframework/ios-arm64/libecies.a +0 -0
- package/ios/Frameworks/Ecies.xcframework/ios-arm64-simulator/Headers/ecies.h +20 -0
- package/ios/Frameworks/Ecies.xcframework/ios-arm64-simulator/Headers/module.modulemap +4 -0
- package/ios/Frameworks/Ecies.xcframework/ios-arm64-simulator/libecies.a +0 -0
- package/ios/HybridNitroMetamask.swift +119 -54
- package/ios/NitroMetamaskTests/CancellationStateMachineTests.swift +150 -0
- package/ios/NitroMetamaskTests/ChainIdParsingTests.swift +117 -0
- package/ios/NitroMetamaskTests/ConfigureStateMachineTests.swift +174 -0
- package/ios/NitroMetamaskTests/ConnectSignJsonTests.swift +168 -0
- package/ios/NitroMetamaskTests/DefaultDappUrlTests.swift +80 -0
- package/ios/NitroMetamaskTests/PersonalSignParamsTests.swift +101 -0
- package/ios/metamask-ios-sdk/CommunicationLayer/CommClient.swift +43 -0
- package/ios/metamask-ios-sdk/CommunicationLayer/CommClientFactory.swift +17 -0
- package/ios/metamask-ios-sdk/CommunicationLayer/CommLayer.swift +36 -0
- package/ios/metamask-ios-sdk/CommunicationLayer/DeeplinkCommLayer/Deeplink.swift +26 -0
- package/ios/metamask-ios-sdk/CommunicationLayer/DeeplinkCommLayer/DeeplinkClient.swift +199 -0
- package/ios/metamask-ios-sdk/CommunicationLayer/DeeplinkCommLayer/DeeplinkManager.swift +83 -0
- package/ios/metamask-ios-sdk/CommunicationLayer/DeeplinkCommLayer/String.swift +48 -0
- package/ios/metamask-ios-sdk/CommunicationLayer/DeeplinkCommLayer/URLOpener.swift +19 -0
- package/ios/metamask-ios-sdk/CommunicationLayer/SocketClient.swift +27 -0
- package/ios/metamask-ios-sdk/Crypto/Crypto.swift +72 -0
- package/ios/metamask-ios-sdk/Crypto/Encoding.swift +15 -0
- package/ios/metamask-ios-sdk/Crypto/KeyExchange.swift +236 -0
- package/ios/metamask-ios-sdk/DeviceInfo/DeviceInfo.swift +11 -0
- package/ios/metamask-ios-sdk/Ethereum/AppMetadata.swift +28 -0
- package/ios/metamask-ios-sdk/Ethereum/ErrorType.swift +62 -0
- package/ios/metamask-ios-sdk/Ethereum/Ethereum.swift +810 -0
- package/ios/metamask-ios-sdk/Ethereum/EthereumMethod.swift +111 -0
- package/ios/metamask-ios-sdk/Ethereum/EthereumRequest.swift +40 -0
- package/ios/metamask-ios-sdk/Ethereum/EthereumWrapper.swift +10 -0
- package/ios/metamask-ios-sdk/Ethereum/RPCRequest.swift +14 -0
- package/ios/metamask-ios-sdk/Ethereum/RequestError.swift +88 -0
- package/ios/metamask-ios-sdk/Ethereum/ResponseMethod.swift +22 -0
- package/ios/metamask-ios-sdk/Ethereum/SubmitRequest.swift +26 -0
- package/ios/metamask-ios-sdk/Ethereum/TimestampGenerator.swift +16 -0
- package/ios/metamask-ios-sdk/Extensions/NSRecursiveLock.swift +14 -0
- package/ios/metamask-ios-sdk/Extensions/Notification.swift +10 -0
- package/ios/metamask-ios-sdk/Logger/Logging.swift +27 -0
- package/ios/metamask-ios-sdk/Models/AddChainParameters.swift +35 -0
- package/ios/metamask-ios-sdk/Models/Event.swift +19 -0
- package/ios/metamask-ios-sdk/Models/Mappable.swift +40 -0
- package/ios/metamask-ios-sdk/Models/NativeCurrency.swift +25 -0
- package/ios/metamask-ios-sdk/Models/OriginatorInfo.swift +26 -0
- package/ios/metamask-ios-sdk/Models/RequestInfo.swift +18 -0
- package/ios/metamask-ios-sdk/Models/SignContract.swift +48 -0
- package/ios/metamask-ios-sdk/Models/Typealiases.swift +9 -0
- package/ios/metamask-ios-sdk/Persistence/SecureStore.swift +134 -0
- package/ios/metamask-ios-sdk/Persistence/SessionConfig.swift +24 -0
- package/ios/metamask-ios-sdk/Persistence/SessionManager.swift +56 -0
- package/ios/metamask-ios-sdk/SDK/Dependencies.swift +35 -0
- package/ios/metamask-ios-sdk/SDK/MetaMaskSDK.swift +215 -0
- package/ios/metamask-ios-sdk/SDK/SDKInfo.swift +37 -0
- package/ios/metamask-ios-sdk/SDK/SDKOptions.swift +16 -0
- package/lib/commonjs/index.js +50 -3
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/index.js +49 -3
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/__tests__/parseNitroError.test.d.ts +2 -0
- package/lib/typescript/src/__tests__/parseNitroError.test.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +43 -3
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/specs/nitro-metamask.nitro.d.ts +29 -1
- package/lib/typescript/src/specs/nitro-metamask.nitro.d.ts.map +1 -1
- package/package.json +21 -12
- package/react-native.config.js +5 -0
- package/rust/ecies-jni/Cargo.lock +50 -86
- package/rust/ecies-jni/Cargo.toml +1 -1
- package/rust/ecies-jni/src/lib.rs +164 -100
- package/src/__tests__/parseNitroError.test.ts +35 -0
- package/src/index.ts +53 -5
- package/src/specs/nitro-metamask.nitro.ts +29 -1
- package/scripts/verify-16k-page-alignment.py +0 -117
- package/scripts/verify-16k-page-alignment.sh +0 -5
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
//
|
|
2
|
+
// DeeplinkClient.swift
|
|
3
|
+
// metamask-ios-sdk
|
|
4
|
+
//
|
|
5
|
+
|
|
6
|
+
import UIKit
|
|
7
|
+
import Foundation
|
|
8
|
+
|
|
9
|
+
public class DeeplinkClient: CommClient {
|
|
10
|
+
|
|
11
|
+
private let session: SessionManager
|
|
12
|
+
public var channelId: String = ""
|
|
13
|
+
let dappScheme: String
|
|
14
|
+
let urlOpener: URLOpener
|
|
15
|
+
|
|
16
|
+
public var appMetadata: AppMetadata?
|
|
17
|
+
public var trackEvent: ((Event, [String: Any]) -> Void)?
|
|
18
|
+
public var handleResponse: (([String: Any]) -> Void)?
|
|
19
|
+
public var onClientsTerminated: (() -> Void)?
|
|
20
|
+
|
|
21
|
+
let keyExchange: KeyExchange
|
|
22
|
+
let deeplinkManager: DeeplinkManager
|
|
23
|
+
private var isConnecting = false
|
|
24
|
+
|
|
25
|
+
public var sessionDuration: TimeInterval {
|
|
26
|
+
get {
|
|
27
|
+
session.sessionDuration
|
|
28
|
+
} set {
|
|
29
|
+
session.sessionDuration = newValue
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
public var requestJobs: [RequestJob] = []
|
|
34
|
+
|
|
35
|
+
public init(session: SessionManager,
|
|
36
|
+
keyExchange: KeyExchange,
|
|
37
|
+
deeplinkManager: DeeplinkManager,
|
|
38
|
+
dappScheme: String,
|
|
39
|
+
urlOpener: URLOpener = DefaultURLOpener()
|
|
40
|
+
) {
|
|
41
|
+
self.session = session
|
|
42
|
+
self.keyExchange = keyExchange
|
|
43
|
+
self.deeplinkManager = deeplinkManager
|
|
44
|
+
self.dappScheme = dappScheme
|
|
45
|
+
self.urlOpener = urlOpener
|
|
46
|
+
setupClient()
|
|
47
|
+
setupCallbacks()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private func setupCallbacks() {
|
|
51
|
+
self.deeplinkManager.onReceiveMessage = handleMessage
|
|
52
|
+
self.deeplinkManager.decryptMessage = keyExchange.decryptMessage
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private func setupClient() {
|
|
56
|
+
let sessionInfo = session.fetchSessionConfig()
|
|
57
|
+
channelId = sessionInfo.0.sessionId
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
public func clearSession() {
|
|
61
|
+
track(event: .disconnected)
|
|
62
|
+
session.clear()
|
|
63
|
+
setupClient()
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
public func requestAuthorisation() {
|
|
67
|
+
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
func sendMessage(_ message: String) {
|
|
71
|
+
let deeplink = "metamask://\(message)"
|
|
72
|
+
guard
|
|
73
|
+
let urlString = deeplink.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed),
|
|
74
|
+
let url = URL(string: urlString)
|
|
75
|
+
else { return }
|
|
76
|
+
|
|
77
|
+
urlOpener.open(url)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
public func handleUrl(_ url: URL) {
|
|
81
|
+
deeplinkManager.handleUrl(url)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
func sendMessage(_ deeplink: Deeplink, options: [String: String]) {
|
|
85
|
+
switch deeplink {
|
|
86
|
+
case .connect(_, let channelId, let request):
|
|
87
|
+
let originatorInfo = originatorInfo().toJsonString()?.base64Encode() ?? ""
|
|
88
|
+
var message = "connect?scheme=\(dappScheme)&channelId=\(channelId)&comm=deeplinking&originatorInfo=\(originatorInfo)"
|
|
89
|
+
if let request = request {
|
|
90
|
+
message.append("&request=\(request)")
|
|
91
|
+
}
|
|
92
|
+
sendMessage(message)
|
|
93
|
+
case .mmsdk(let message, _, let channelId):
|
|
94
|
+
let account = options["account"] ?? ""
|
|
95
|
+
let chainId = options["chainId"] ?? ""
|
|
96
|
+
let message = "mmsdk?scheme=\(dappScheme)&message=\(message)&channelId=\(channelId ?? "")&account=\(account)@\(chainId)"
|
|
97
|
+
sendMessage(message)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
public func connect(with request: String? = nil) {
|
|
102
|
+
track(event: .connectionRequest)
|
|
103
|
+
isConnecting = true
|
|
104
|
+
|
|
105
|
+
sendMessage(.connect(
|
|
106
|
+
pubkey: nil,
|
|
107
|
+
channelId: channelId,
|
|
108
|
+
request: request
|
|
109
|
+
), options: [:])
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
public func track(event: Event) {
|
|
113
|
+
let parameters: [String: Any] = [
|
|
114
|
+
"id": channelId,
|
|
115
|
+
"commLayer": "deeplinking",
|
|
116
|
+
"sdkVersion": SDKInfo.version,
|
|
117
|
+
"url": appMetadata?.url ?? "",
|
|
118
|
+
"dappId": SDKInfo.bundleIdentifier ?? "N/A",
|
|
119
|
+
"title": appMetadata?.name ?? "",
|
|
120
|
+
"platform": SDKInfo.platform
|
|
121
|
+
]
|
|
122
|
+
|
|
123
|
+
trackEvent?(event, parameters)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
public func disconnect() {
|
|
127
|
+
track(event: .disconnected)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
public func terminateConnection() {
|
|
131
|
+
track(event: .disconnected)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
public func addRequest(_ job: @escaping RequestJob) {
|
|
135
|
+
requestJobs.append(job)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
public func runQueuedJobs() {
|
|
139
|
+
while !requestJobs.isEmpty {
|
|
140
|
+
let job = requestJobs.popLast()
|
|
141
|
+
job?()
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
public func sendMessage<T>(_ message: T, encrypt: Bool, options: [String: String]) {
|
|
146
|
+
guard let message = message as? String else {
|
|
147
|
+
Logging.error("DeeplinkClient:: Expected message to be String, got \(type(of: message))")
|
|
148
|
+
return
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
let base64Encoded = message.base64Encode() ?? ""
|
|
152
|
+
|
|
153
|
+
let deeplink: Deeplink = .mmsdk(
|
|
154
|
+
message: base64Encoded,
|
|
155
|
+
pubkey: nil,
|
|
156
|
+
channelId: channelId
|
|
157
|
+
)
|
|
158
|
+
sendMessage(deeplink, options: options)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
public func handleMessage(_ message: String) {
|
|
162
|
+
do {
|
|
163
|
+
guard let data = message.data(using: .utf8) else {
|
|
164
|
+
Logging.error("DeeplinkClient:: Cannot convert message to data: \(message)")
|
|
165
|
+
return
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
let json: [String: Any] = try JSONSerialization.jsonObject(
|
|
169
|
+
with: data,
|
|
170
|
+
options: []
|
|
171
|
+
)
|
|
172
|
+
as? [String: Any] ?? [:]
|
|
173
|
+
|
|
174
|
+
if json["type"] as? String == "terminate" {
|
|
175
|
+
disconnect()
|
|
176
|
+
Logging.log("Connection terminated")
|
|
177
|
+
} else if json["type"] as? String == "ready" {
|
|
178
|
+
Logging.log("DeeplinkClient:: Connection is ready")
|
|
179
|
+
runQueuedJobs()
|
|
180
|
+
return
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
guard let data = json["data"] as? [String: Any] else {
|
|
184
|
+
Logging.log("DeeplinkClient:: Ignoring response \(json)")
|
|
185
|
+
return
|
|
186
|
+
}
|
|
187
|
+
if
|
|
188
|
+
isConnecting,
|
|
189
|
+
data["accounts"] != nil,
|
|
190
|
+
data["chainId"] != nil {
|
|
191
|
+
isConnecting = false
|
|
192
|
+
track(event: .connected)
|
|
193
|
+
}
|
|
194
|
+
handleResponse?(data)
|
|
195
|
+
} catch {
|
|
196
|
+
Logging.error("DeeplinkClient:: Could not convert message to json. Message: \(message)\nError: \(error)")
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
//
|
|
2
|
+
// DeeplinkManager.swift
|
|
3
|
+
// metamask-ios-sdk
|
|
4
|
+
//
|
|
5
|
+
|
|
6
|
+
import UIKit
|
|
7
|
+
import Foundation
|
|
8
|
+
|
|
9
|
+
public class DeeplinkManager {
|
|
10
|
+
public var onReceiveMessage: ((String) -> Void)?
|
|
11
|
+
var decryptMessage: ((String) throws -> String?)?
|
|
12
|
+
|
|
13
|
+
public init(onReceiveMessage: ( (String) -> Void)? = nil, decryptMessage: ( (String) -> String?)? = nil) {
|
|
14
|
+
self.onReceiveMessage = onReceiveMessage
|
|
15
|
+
self.decryptMessage = decryptMessage
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public func handleUrl(_ url: URL) {
|
|
19
|
+
handleUrl(url.absoluteString)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public func handleUrl(_ url: String) {
|
|
23
|
+
let deeplink = getDeeplink(url)
|
|
24
|
+
|
|
25
|
+
switch deeplink {
|
|
26
|
+
case .mmsdk(let message, _, _):
|
|
27
|
+
let base64Decoded = message.base64Decode() ?? ""
|
|
28
|
+
|
|
29
|
+
onReceiveMessage?(base64Decoded)
|
|
30
|
+
|
|
31
|
+
default:
|
|
32
|
+
Logging.error("DeeplinkManager:: ignoring url \(url)")
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
public func getDeeplink(_ link: String) -> Deeplink? {
|
|
37
|
+
|
|
38
|
+
guard let url = URL(string: link) else {
|
|
39
|
+
Logging.error("DeeplinkManager:: Deeplink has invalid url")
|
|
40
|
+
return nil
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
guard url.scheme != nil else {
|
|
44
|
+
Logging.error("DeeplinkManager:: Deeplink is missing scheme")
|
|
45
|
+
return nil
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: true) else {
|
|
49
|
+
Logging.error("DeeplinkManager:: Deeplink missing components")
|
|
50
|
+
return nil
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
guard let action = components.host else {
|
|
54
|
+
Logging.error("DeeplinkManager:: Deeplink missing action")
|
|
55
|
+
return nil
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let pubkey = components.queryItems?.first(where: { $0.name == "pubkey" })?.value
|
|
59
|
+
|
|
60
|
+
if action == Deeplink.connect {
|
|
61
|
+
guard let channelId: String = components.queryItems?.first(where: { $0.name == "channelId" })?.value else {
|
|
62
|
+
Logging.error("DeeplinkManager:: Connect step missing channelId")
|
|
63
|
+
return nil
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
let request = components.queryItems?.first(where: { $0.name == "request" })?.value
|
|
67
|
+
|
|
68
|
+
return .connect(pubkey: pubkey, channelId: channelId, request: request)
|
|
69
|
+
|
|
70
|
+
} else if action == Deeplink.mmsdk {
|
|
71
|
+
guard let message = components.queryItems?.first(where: { $0.name == "message" })?.value else {
|
|
72
|
+
Logging.error("DeeplinkManager:: Deeplink missing message")
|
|
73
|
+
return nil
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
let channelId = components.queryItems?.first(where: { $0.name == "channelId" })?.value
|
|
77
|
+
|
|
78
|
+
return .mmsdk(message: message, pubkey: pubkey, channelId: channelId)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return nil
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
//
|
|
2
|
+
// String.swift
|
|
3
|
+
// metamask-ios-sdk
|
|
4
|
+
|
|
5
|
+
import Foundation
|
|
6
|
+
|
|
7
|
+
extension String {
|
|
8
|
+
func trimEscapingChars() -> Self {
|
|
9
|
+
var unescapedString = replacingOccurrences(of: #"\""#, with: "\"")
|
|
10
|
+
if unescapedString.hasPrefix("\"") && unescapedString.hasSuffix("\"") {
|
|
11
|
+
unescapedString.removeFirst()
|
|
12
|
+
unescapedString.removeLast()
|
|
13
|
+
}
|
|
14
|
+
return unescapedString
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
func json(from value: Any) -> String? {
|
|
19
|
+
// Recursive function to decode nested JSON strings
|
|
20
|
+
func decodeNestedJson(_ value: Any) -> Any {
|
|
21
|
+
if let arrayValue = value as? [Any] {
|
|
22
|
+
// If it's an array, recursively decode each element
|
|
23
|
+
return arrayValue.map { decodeNestedJson($0) }
|
|
24
|
+
} else if let dictValue = value as? [String: Any] {
|
|
25
|
+
// If it's a dictionary, recursively decode each value
|
|
26
|
+
var decodedDict = [String: Any]()
|
|
27
|
+
for (key, value) in dictValue {
|
|
28
|
+
decodedDict[key] = decodeNestedJson(value)
|
|
29
|
+
}
|
|
30
|
+
return decodedDict
|
|
31
|
+
} else {
|
|
32
|
+
// If it's neither a string, array, nor dictionary, return the value as is
|
|
33
|
+
return value
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Decode any nested JSON strings recursively in the input dictionary
|
|
38
|
+
let decodedJsonObject = decodeNestedJson(value)
|
|
39
|
+
|
|
40
|
+
// Step 3: Convert the cleaned dictionary back to a JSON string
|
|
41
|
+
guard let cleanedJsonData = try? JSONSerialization.data(withJSONObject: decodedJsonObject, options: []),
|
|
42
|
+
let cleanedJsonString = String(data: cleanedJsonData, encoding: .utf8) else {
|
|
43
|
+
Logging.error("Failed to serialize cleaned JSON dictionary")
|
|
44
|
+
return nil
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return cleanedJsonString
|
|
48
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
//
|
|
2
|
+
// URLOpener.swift
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import UIKit
|
|
6
|
+
|
|
7
|
+
public protocol URLOpener {
|
|
8
|
+
func open(_ url: URL)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
public class DefaultURLOpener: URLOpener {
|
|
12
|
+
public init() {}
|
|
13
|
+
|
|
14
|
+
public func open(_ url: URL) {
|
|
15
|
+
DispatchQueue.main.async {
|
|
16
|
+
UIApplication.shared.open(url)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
//
|
|
2
|
+
// SocketClient.swift
|
|
3
|
+
// metamask-ios-sdk
|
|
4
|
+
//
|
|
5
|
+
// Stub — socket transport is not used. Exists only so `is SocketClient` type checks compile.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
import Foundation
|
|
9
|
+
|
|
10
|
+
/// Stub class. Socket transport is not supported; only deeplink transport is used.
|
|
11
|
+
public final class SocketClient: CommClient {
|
|
12
|
+
public var channelId: String = ""
|
|
13
|
+
public var handleResponse: (([String: Any]) -> Void)?
|
|
14
|
+
public var trackEvent: ((Event, [String: Any]) -> Void)?
|
|
15
|
+
public var onClientsTerminated: (() -> Void)?
|
|
16
|
+
public var appMetadata: AppMetadata?
|
|
17
|
+
public var sessionDuration: TimeInterval = 0
|
|
18
|
+
public var networkUrl: String = ""
|
|
19
|
+
public var useDeeplinks: Bool = false
|
|
20
|
+
|
|
21
|
+
public func connect(with request: String?) { fatalError("SocketClient not supported") }
|
|
22
|
+
public func disconnect() { fatalError("SocketClient not supported") }
|
|
23
|
+
public func clearSession() { fatalError("SocketClient not supported") }
|
|
24
|
+
public func requestAuthorisation() { fatalError("SocketClient not supported") }
|
|
25
|
+
public func addRequest(_ job: @escaping RequestJob) { fatalError("SocketClient not supported") }
|
|
26
|
+
public func sendMessage<T: Codable>(_ message: T, encrypt: Bool, options: [String: String]) { fatalError("SocketClient not supported") }
|
|
27
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import Ecies
|
|
2
|
+
import Foundation
|
|
3
|
+
|
|
4
|
+
public protocol Crypto {
|
|
5
|
+
/// Generates keypair and returns the asymmetric private key
|
|
6
|
+
/// - Returns: Asymmetric private key
|
|
7
|
+
static func generatePrivateKey() -> String
|
|
8
|
+
|
|
9
|
+
/// Computes public key from given private key
|
|
10
|
+
/// - Parameter privateKey: Sender's private key
|
|
11
|
+
/// - Returns: Public key
|
|
12
|
+
static func publicKey(from privateKey: String) throws -> String
|
|
13
|
+
|
|
14
|
+
/// Encrypts plain text using provided public key
|
|
15
|
+
/// - Parameters:
|
|
16
|
+
/// - message: Plain text to encrypt
|
|
17
|
+
/// - publicKey: Sender public key
|
|
18
|
+
/// - Returns: Encrypted text
|
|
19
|
+
static func encrypt(_ message: String, publicKey: String) throws -> String
|
|
20
|
+
|
|
21
|
+
/// Decrypts base64 encoded cipher text to plain text using provided private key
|
|
22
|
+
/// - Parameters:
|
|
23
|
+
/// - message: base64 encoded cipher text to decrypt
|
|
24
|
+
/// - privateKey: Receiever's private key
|
|
25
|
+
/// - Returns: Decryted plain text
|
|
26
|
+
static func decrypt(_ message: String, privateKey: String) throws -> String
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
public enum CryptoError: Error {
|
|
30
|
+
case encryptionFailure
|
|
31
|
+
case decryptionFailure
|
|
32
|
+
case publicKeyGenerationFailure
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/// Encryption module using ECIES encryption standard
|
|
36
|
+
public enum Ecies: Crypto {
|
|
37
|
+
public static func generatePrivateKey() -> String {
|
|
38
|
+
String(cString: ecies_generate_secret_key())
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
public static func publicKey(from privateKey: String) throws -> String {
|
|
42
|
+
let privateKey: NSString = privateKey as NSString
|
|
43
|
+
let privateKeyBytes = UnsafeMutablePointer(mutating: privateKey.utf8String)
|
|
44
|
+
guard let pubKeyCString = ecies_public_key_from(privateKeyBytes) else {
|
|
45
|
+
throw CryptoError.publicKeyGenerationFailure
|
|
46
|
+
}
|
|
47
|
+
return String(cString: pubKeyCString)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public static func encrypt(_ message: String, publicKey: String) throws -> String {
|
|
51
|
+
let message: NSString = message as NSString
|
|
52
|
+
let publicKey: NSString = publicKey as NSString
|
|
53
|
+
let messageBytes = UnsafeMutablePointer(mutating: message.utf8String)
|
|
54
|
+
let publicKeyBytes = UnsafeMutablePointer(mutating: publicKey.utf8String)
|
|
55
|
+
|
|
56
|
+
guard let encryptedText = ecies_encrypt(publicKeyBytes, messageBytes) else {
|
|
57
|
+
throw CryptoError.encryptionFailure
|
|
58
|
+
}
|
|
59
|
+
return String(cString: encryptedText)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
public static func decrypt(_ message: String, privateKey: String) throws -> String {
|
|
63
|
+
let message: NSString = message as NSString
|
|
64
|
+
let privateKey: NSString = privateKey as NSString
|
|
65
|
+
let messageBytes = UnsafeMutablePointer(mutating: message.utf8String)
|
|
66
|
+
let privateKeyBytes = UnsafeMutablePointer(mutating: privateKey.utf8String)
|
|
67
|
+
guard let decryptedText = ecies_decrypt(privateKeyBytes, messageBytes) else {
|
|
68
|
+
throw CryptoError.decryptionFailure
|
|
69
|
+
}
|
|
70
|
+
return String(cString: decryptedText)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
public extension String {
|
|
4
|
+
// Encode a string to base64
|
|
5
|
+
func base64Encode() -> String? {
|
|
6
|
+
guard let data = data(using: .utf8) else { return nil }
|
|
7
|
+
return data.base64EncodedString()
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// Decode a base64 string to original string
|
|
11
|
+
func base64Decode() -> String? {
|
|
12
|
+
guard let data = Data(base64Encoded: self) else { return nil }
|
|
13
|
+
return String(data: data, encoding: .utf8)
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
//
|
|
2
|
+
// KeyExchange.swift
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import Foundation
|
|
6
|
+
|
|
7
|
+
public enum KeyExchangeType: String, Mappable {
|
|
8
|
+
case start = "key_handshake_start"
|
|
9
|
+
case ack = "key_handshake_ACK"
|
|
10
|
+
case syn = "key_handshake_SYN"
|
|
11
|
+
case synack = "key_handshake_SYNACK"
|
|
12
|
+
|
|
13
|
+
public init(from decoder: Decoder) throws {
|
|
14
|
+
let container = try decoder.singleValueContainer()
|
|
15
|
+
let status = try? container.decode(String.self)
|
|
16
|
+
switch status {
|
|
17
|
+
case "key_handshake_start": self = .start
|
|
18
|
+
case "key_handshake_ACK": self = .ack
|
|
19
|
+
case "key_handshake_SYN": self = .syn
|
|
20
|
+
case "key_handshake_SYNACK": self = .synack
|
|
21
|
+
default:
|
|
22
|
+
self = .ack
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public enum KeyExchangeError: Error {
|
|
28
|
+
case keysNotExchanged
|
|
29
|
+
case encodingError
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public struct KeyExchangeMessage: CodableData, Mappable {
|
|
33
|
+
public let type: KeyExchangeType
|
|
34
|
+
public let pubkey: String?
|
|
35
|
+
public var v: Int?
|
|
36
|
+
public var clientType: String? = "dapp"
|
|
37
|
+
|
|
38
|
+
public init(type: KeyExchangeType, pubkey: String?, v: Int? = 2, clientType: String? = "dapp") {
|
|
39
|
+
self.type = type
|
|
40
|
+
self.pubkey = pubkey
|
|
41
|
+
self.clientType = clientType
|
|
42
|
+
self.v = v
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Custom initializer for decoding
|
|
46
|
+
public init(from decoder: Decoder) throws {
|
|
47
|
+
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
48
|
+
type = try container.decode(KeyExchangeType.self, forKey: .type)
|
|
49
|
+
pubkey = try container.decodeIfPresent(String.self, forKey: .pubkey)
|
|
50
|
+
v = try container.decodeIfPresent(Int.self, forKey: .v) ?? 2
|
|
51
|
+
clientType = try container.decodeIfPresent(String.self, forKey: .clientType) ?? "dapp"
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Custom method for encoding
|
|
55
|
+
public func encode(to encoder: Encoder) throws {
|
|
56
|
+
var container = encoder.container(keyedBy: CodingKeys.self)
|
|
57
|
+
try container.encode(type, forKey: .type)
|
|
58
|
+
try container.encode(pubkey, forKey: .pubkey)
|
|
59
|
+
try container.encode(v, forKey: .v)
|
|
60
|
+
try container.encode(clientType, forKey: .clientType)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private enum CodingKeys: String, CodingKey {
|
|
64
|
+
case type
|
|
65
|
+
case pubkey
|
|
66
|
+
case v
|
|
67
|
+
case clientType
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/*
|
|
71
|
+
A module for handling key exchange between client and server
|
|
72
|
+
The key exchange sequence is defined as:
|
|
73
|
+
syn -> synack -> ack
|
|
74
|
+
*/
|
|
75
|
+
|
|
76
|
+
public class KeyExchange {
|
|
77
|
+
private var privateKey: String
|
|
78
|
+
public var pubkey: String
|
|
79
|
+
public private(set) var theirPublicKey: String?
|
|
80
|
+
|
|
81
|
+
private let storage: SecureStore
|
|
82
|
+
private let encyption: Crypto.Type
|
|
83
|
+
var keysExchanged: Bool = false
|
|
84
|
+
var isKeysExchangedViaV2Protocol: Bool = false
|
|
85
|
+
private let privateKeyStorageKey = "MM_SDK_PRIV_KEY"
|
|
86
|
+
private let theirPubliKeyStorageKey = "MM_SDK_THEIR_PUB_KEY"
|
|
87
|
+
|
|
88
|
+
public init(encryption: Crypto.Type = Ecies.self, storage: SecureStore) {
|
|
89
|
+
self.storage = storage
|
|
90
|
+
self.encyption = encryption
|
|
91
|
+
|
|
92
|
+
if let storedPrivateKey = storage.string(for: privateKeyStorageKey) {
|
|
93
|
+
Logging.log("KeyExchange:: using stored private key")
|
|
94
|
+
self.privateKey = storedPrivateKey
|
|
95
|
+
|
|
96
|
+
if let theirPubKey = storage.string(for: theirPubliKeyStorageKey) {
|
|
97
|
+
self.theirPublicKey = theirPubKey
|
|
98
|
+
|
|
99
|
+
// wallet already has keys
|
|
100
|
+
keysExchanged = true
|
|
101
|
+
isKeysExchangedViaV2Protocol = true
|
|
102
|
+
}
|
|
103
|
+
} else {
|
|
104
|
+
Logging.log("KeyExchange:: generating new private key")
|
|
105
|
+
privateKey = encyption.generatePrivateKey()
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
do {
|
|
109
|
+
pubkey = try encyption.publicKey(from: privateKey)
|
|
110
|
+
} catch {
|
|
111
|
+
pubkey = ""
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
public func reset() {
|
|
116
|
+
keysExchanged = false
|
|
117
|
+
theirPublicKey = nil
|
|
118
|
+
privateKey = ""
|
|
119
|
+
isKeysExchangedViaV2Protocol = false
|
|
120
|
+
|
|
121
|
+
storage.deleteData(for: privateKeyStorageKey)
|
|
122
|
+
storage.deleteData(for: theirPubliKeyStorageKey)
|
|
123
|
+
privateKey = encyption.generatePrivateKey()
|
|
124
|
+
|
|
125
|
+
do {
|
|
126
|
+
pubkey = try encyption.publicKey(from: privateKey)
|
|
127
|
+
} catch {
|
|
128
|
+
pubkey = ""
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
public func nextMessage(_ message: KeyExchangeMessage) -> KeyExchangeMessage? {
|
|
133
|
+
if message.type == .start {
|
|
134
|
+
keysExchanged = false
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if
|
|
138
|
+
let publicKey = message.pubkey,
|
|
139
|
+
!publicKey.isEmpty {
|
|
140
|
+
// inline: store their public key
|
|
141
|
+
theirPublicKey = publicKey
|
|
142
|
+
keysExchanged = true
|
|
143
|
+
storage.save(string: publicKey, key: theirPubliKeyStorageKey)
|
|
144
|
+
storage.save(string: privateKey, key: privateKeyStorageKey)
|
|
145
|
+
|
|
146
|
+
if message.v == 2 {
|
|
147
|
+
self.storage.save(string: privateKey, key: privateKeyStorageKey)
|
|
148
|
+
self.storage.save(string: publicKey, key: theirPubliKeyStorageKey)
|
|
149
|
+
isKeysExchangedViaV2Protocol = true
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
guard let nextStep = nextStep(message.type) else {
|
|
154
|
+
return nil
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return KeyExchangeMessage(
|
|
158
|
+
type: nextStep,
|
|
159
|
+
pubkey: pubkey
|
|
160
|
+
)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
public func nextStep(_ step: KeyExchangeType) -> KeyExchangeType? {
|
|
164
|
+
switch step {
|
|
165
|
+
case .start: return .syn
|
|
166
|
+
case .syn: return .synack
|
|
167
|
+
case .synack: return .ack
|
|
168
|
+
case .ack: return nil
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
public func message(type: KeyExchangeType) -> KeyExchangeMessage {
|
|
173
|
+
KeyExchangeMessage(
|
|
174
|
+
type: type,
|
|
175
|
+
pubkey: pubkey
|
|
176
|
+
)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
public static func isHandshakeRestartMessage(_ message: [String: Any]) -> Bool {
|
|
180
|
+
guard
|
|
181
|
+
let message = message["message"] as? [String: Any],
|
|
182
|
+
let type = message["type"] as? String,
|
|
183
|
+
let exchangeType = KeyExchangeType(rawValue: type),
|
|
184
|
+
exchangeType == .start
|
|
185
|
+
else { return false }
|
|
186
|
+
return true
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
public func encryptMessage<T: Codable>(_ message: T) throws -> String {
|
|
190
|
+
guard let theirPublicKey = theirPublicKey else {
|
|
191
|
+
throw KeyExchangeError.keysNotExchanged
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
guard let encodedData = try? JSONEncoder().encode(message) else {
|
|
195
|
+
throw KeyExchangeError.encodingError
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
guard let jsonString = String(
|
|
199
|
+
data: encodedData,
|
|
200
|
+
encoding: .utf8
|
|
201
|
+
) else {
|
|
202
|
+
throw KeyExchangeError.encodingError
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return try encyption.encrypt(
|
|
206
|
+
jsonString,
|
|
207
|
+
publicKey: theirPublicKey
|
|
208
|
+
)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
public func encrypt(_ message: String) throws -> String {
|
|
212
|
+
guard let theirPublicKey = theirPublicKey else {
|
|
213
|
+
throw KeyExchangeError.keysNotExchanged
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return try encyption.encrypt(
|
|
217
|
+
message,
|
|
218
|
+
publicKey: theirPublicKey
|
|
219
|
+
)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
public func decryptMessage(_ message: String) throws -> String {
|
|
223
|
+
guard theirPublicKey != nil else {
|
|
224
|
+
throw KeyExchangeError.keysNotExchanged
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return try encyption.decrypt(
|
|
228
|
+
message,
|
|
229
|
+
privateKey: privateKey
|
|
230
|
+
).trimEscapingChars()
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
extension KeyExchange {
|
|
235
|
+
static let live = Dependencies.shared.keyExchange
|
|
236
|
+
}
|