@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,525 @@
|
|
|
1
|
+
package io.metamask.androidsdk
|
|
2
|
+
|
|
3
|
+
import android.content.ComponentName
|
|
4
|
+
import android.content.Context
|
|
5
|
+
import android.content.Intent
|
|
6
|
+
import android.content.pm.PackageManager
|
|
7
|
+
import android.os.Build
|
|
8
|
+
import android.os.Bundle
|
|
9
|
+
import com.google.gson.Gson
|
|
10
|
+
import com.google.gson.JsonSyntaxException
|
|
11
|
+
import com.google.gson.reflect.TypeToken
|
|
12
|
+
import kotlinx.serialization.Serializable
|
|
13
|
+
import org.json.JSONObject
|
|
14
|
+
import java.lang.ref.WeakReference
|
|
15
|
+
|
|
16
|
+
class CommunicationClient(
|
|
17
|
+
context: Context,
|
|
18
|
+
callback: EthereumEventCallback?,
|
|
19
|
+
private val sessionManager: SessionManager,
|
|
20
|
+
private val keyExchange: KeyExchange,
|
|
21
|
+
private val serviceConnection: ClientServiceConnection,
|
|
22
|
+
private val messageServiceCallback: ClientMessageServiceCallback,
|
|
23
|
+
private val logger: Logger = DefaultLogger) {
|
|
24
|
+
|
|
25
|
+
var sessionId: String = ""
|
|
26
|
+
|
|
27
|
+
var dappMetadata: DappMetadata? = null
|
|
28
|
+
var isServiceConnected = false
|
|
29
|
+
private set
|
|
30
|
+
|
|
31
|
+
private val appContextRef: WeakReference<Context> = WeakReference(context)
|
|
32
|
+
var ethereumEventCallbackRef: WeakReference<EthereumEventCallback> = WeakReference(callback)
|
|
33
|
+
|
|
34
|
+
var requestJobs: MutableList<() -> Unit> = mutableListOf()
|
|
35
|
+
private set
|
|
36
|
+
|
|
37
|
+
var submittedRequests: MutableMap<String, SubmittedRequest> = mutableMapOf()
|
|
38
|
+
private set
|
|
39
|
+
|
|
40
|
+
var queuedRequests: MutableMap<String, SubmittedRequest> = mutableMapOf()
|
|
41
|
+
private set
|
|
42
|
+
|
|
43
|
+
private var isMetaMaskReady = false
|
|
44
|
+
private var sentOriginatorInfo = false
|
|
45
|
+
|
|
46
|
+
var requestedBindService = false
|
|
47
|
+
private set
|
|
48
|
+
|
|
49
|
+
var enableDebug: Boolean = false
|
|
50
|
+
|
|
51
|
+
init {
|
|
52
|
+
sessionId = sessionManager.sessionId
|
|
53
|
+
// in case not yet initialised in SessionManager
|
|
54
|
+
sessionManager.onInitialized = {
|
|
55
|
+
sessionId = sessionManager.sessionId
|
|
56
|
+
}
|
|
57
|
+
setupServiceConnection()
|
|
58
|
+
setupMessageServiceCallback()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
fun resetState() {
|
|
62
|
+
sentOriginatorInfo = false
|
|
63
|
+
submittedRequests.clear()
|
|
64
|
+
queuedRequests.clear()
|
|
65
|
+
requestJobs.clear()
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private fun setupServiceConnection() {
|
|
69
|
+
serviceConnection.onConnected = {
|
|
70
|
+
logger.log("CommunicationClient:: Service connected")
|
|
71
|
+
isServiceConnected = true
|
|
72
|
+
serviceConnection.registerCallback(messageServiceCallback)
|
|
73
|
+
initiateKeyExchange()
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
serviceConnection.onDisconnected = { name ->
|
|
77
|
+
isServiceConnected = false
|
|
78
|
+
logger.error("CommunicationClient:: Service disconnected $name")
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
serviceConnection.onBindingDied = { name ->
|
|
82
|
+
logger.error("CommunicationClient:: binding died: $name")
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
serviceConnection.onNullBinding = { name ->
|
|
86
|
+
logger.error("CommunicationClient:: null binding: $name")
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
private fun setupMessageServiceCallback() {
|
|
91
|
+
messageServiceCallback.onMessage = { bundle ->
|
|
92
|
+
val keyExchange = bundle.getString(KEY_EXCHANGE)
|
|
93
|
+
val message = bundle.getString(MESSAGE)
|
|
94
|
+
|
|
95
|
+
if (keyExchange != null) {
|
|
96
|
+
handleKeyExchange(keyExchange)
|
|
97
|
+
} else if (message != null) {
|
|
98
|
+
handleMessage(message)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
fun updateSessionDuration(duration: Long) {
|
|
104
|
+
sessionManager.updateSessionDuration(duration)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
fun clearSession(onComplete: () -> Unit) {
|
|
108
|
+
sessionManager.clearSession {
|
|
109
|
+
sessionId = sessionManager.sessionId
|
|
110
|
+
keyExchange.reset()
|
|
111
|
+
onComplete()
|
|
112
|
+
}
|
|
113
|
+
sentOriginatorInfo = false
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
fun handleMessage(message: String) {
|
|
117
|
+
val jsonString = keyExchange.decrypt(message)
|
|
118
|
+
val json = JSONObject(jsonString)
|
|
119
|
+
|
|
120
|
+
when (json.optString(MessageType.TYPE.value)) {
|
|
121
|
+
MessageType.TERMINATE.value -> {
|
|
122
|
+
logger.log("CommunicationClient:: Connection terminated by MetaMask")
|
|
123
|
+
unbindService()
|
|
124
|
+
keyExchange.reset()
|
|
125
|
+
}
|
|
126
|
+
MessageType.KEYS_EXCHANGED.value -> {
|
|
127
|
+
logger.log("CommunicationClient:: Keys exchanged")
|
|
128
|
+
keyExchange.complete()
|
|
129
|
+
sendOriginatorInfo()
|
|
130
|
+
}
|
|
131
|
+
MessageType.READY.value -> {
|
|
132
|
+
logger.log("CommunicationClient:: Connection ready")
|
|
133
|
+
isMetaMaskReady = true
|
|
134
|
+
resumeRequestJobs()
|
|
135
|
+
}
|
|
136
|
+
else -> {
|
|
137
|
+
val data = json.optString(MessageType.DATA.value)
|
|
138
|
+
|
|
139
|
+
if (data.isNotEmpty()) {
|
|
140
|
+
val dataJson = JSONObject(data)
|
|
141
|
+
val id = dataJson.optString(MessageType.ID.value)
|
|
142
|
+
|
|
143
|
+
if (id.isNotEmpty()) {
|
|
144
|
+
handleResponse(id, dataJson)
|
|
145
|
+
} else if (dataJson.optString(MessageType.ERROR.value).isNotEmpty()) {
|
|
146
|
+
handleError(dataJson.optString(MessageType.ERROR.value), "")
|
|
147
|
+
sentOriginatorInfo = false // connection request rejected
|
|
148
|
+
} else {
|
|
149
|
+
handleEvent(dataJson)
|
|
150
|
+
}
|
|
151
|
+
} else {
|
|
152
|
+
logger.log("CommunicationClient:: Received error $json")
|
|
153
|
+
val id = json.optString("id")
|
|
154
|
+
val error = json.optString(MessageType.ERROR.value)
|
|
155
|
+
handleError(error, id)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
fun resumeRequestJobs() {
|
|
162
|
+
logger.log("CommunicationClient:: Resuming jobs")
|
|
163
|
+
|
|
164
|
+
while (requestJobs.isNotEmpty()) {
|
|
165
|
+
val job = requestJobs.removeFirstOrNull()
|
|
166
|
+
job?.invoke()
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
fun queueRequestJob(job: () -> Unit) {
|
|
171
|
+
requestJobs.add(job)
|
|
172
|
+
logger.log("CommunicationClient:: Queued job")
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
fun clearPendingRequests() {
|
|
176
|
+
queuedRequests = mutableMapOf()
|
|
177
|
+
requestJobs = mutableListOf()
|
|
178
|
+
submittedRequests = mutableMapOf()
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
fun handleResponse(id: String, data: JSONObject) {
|
|
182
|
+
val submittedRequest = submittedRequests[id]?.request ?: return
|
|
183
|
+
|
|
184
|
+
val error = data.optString("error")
|
|
185
|
+
|
|
186
|
+
if (handleError(error, id)) {
|
|
187
|
+
return
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
val isResultMethod = EthereumMethod.isResultMethod(submittedRequest.method)
|
|
191
|
+
|
|
192
|
+
if (!isResultMethod) {
|
|
193
|
+
val resultJson = data.optString("result")
|
|
194
|
+
|
|
195
|
+
if (resultJson.isNotEmpty()) {
|
|
196
|
+
val resultMap: Map<String, Any?>? = try {
|
|
197
|
+
Gson().fromJson(resultJson, object : TypeToken<Map<String, Any?>>() {}.type)
|
|
198
|
+
} catch (e: JsonSyntaxException) {
|
|
199
|
+
null
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (resultMap != null) {
|
|
203
|
+
submittedRequests[id]?.callback?.invoke(Result.Success.ItemMap(resultMap))
|
|
204
|
+
completeRequest(id, Result.Success.ItemMap(resultMap))
|
|
205
|
+
} else {
|
|
206
|
+
val accounts: List<String>? = try {
|
|
207
|
+
Gson().fromJson(resultJson, object : TypeToken<List<String>>() {}.type)
|
|
208
|
+
} catch (e: JsonSyntaxException) {
|
|
209
|
+
null
|
|
210
|
+
}
|
|
211
|
+
val account = accounts?.firstOrNull()
|
|
212
|
+
if (account != null) {
|
|
213
|
+
submittedRequests[id]?.callback?.invoke(Result.Success.Item(account))
|
|
214
|
+
completeRequest(id, Result.Success.Item(account))
|
|
215
|
+
} else {
|
|
216
|
+
submittedRequests[id]?.callback?.invoke(Result.Success.Item(resultJson))
|
|
217
|
+
completeRequest(id, Result.Success.Item(resultJson))
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
} else {
|
|
221
|
+
val result: Map<String, Serializable> = Gson().fromJson(data.toString(), object : TypeToken<Map<String, Serializable>>() {}.type)
|
|
222
|
+
completeRequest(id, Result.Success.ItemMap(result))
|
|
223
|
+
}
|
|
224
|
+
return
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
when(submittedRequest.method) {
|
|
228
|
+
EthereumMethod.GET_METAMASK_PROVIDER_STATE.value -> {
|
|
229
|
+
val result = data.optString("result")
|
|
230
|
+
val resultJson = JSONObject(result)
|
|
231
|
+
val accountsJson = resultJson.optString("accounts")
|
|
232
|
+
val accounts: List<String> = Gson().fromJson(accountsJson, object : TypeToken<List<String>>() {}.type)
|
|
233
|
+
|
|
234
|
+
val account = accounts.firstOrNull()
|
|
235
|
+
|
|
236
|
+
if (account != null) {
|
|
237
|
+
updateAccount(account)
|
|
238
|
+
completeRequest(id, Result.Success.Item(account))
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
val chainId = resultJson.optString("chainId")
|
|
242
|
+
|
|
243
|
+
if (chainId.isNotEmpty()) {
|
|
244
|
+
updateChainId(chainId)
|
|
245
|
+
completeRequest(id, Result.Success.Item(chainId))
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
EthereumMethod.ETH_REQUEST_ACCOUNTS.value -> {
|
|
249
|
+
val result = data.optString("result")
|
|
250
|
+
val accounts: List<String> = Gson().fromJson(result, object : TypeToken<List<String>>() {}.type)
|
|
251
|
+
val selectedAccount = accounts.getOrNull(0)
|
|
252
|
+
|
|
253
|
+
if (selectedAccount != null) {
|
|
254
|
+
updateAccount(selectedAccount)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
completeRequest(id, Result.Success.Items(accounts))
|
|
258
|
+
}
|
|
259
|
+
EthereumMethod.ETH_CHAIN_ID.value -> {
|
|
260
|
+
val chainId = data.optString("result")
|
|
261
|
+
|
|
262
|
+
if (chainId.isNotEmpty()) {
|
|
263
|
+
updateChainId(chainId)
|
|
264
|
+
completeRequest(id, Result.Success.Item(chainId))
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
EthereumMethod.ETH_SIGN_TYPED_DATA_V3.value,
|
|
268
|
+
EthereumMethod.ETH_SIGN_TYPED_DATA_V4.value,
|
|
269
|
+
EthereumMethod.ETH_SEND_TRANSACTION.value -> {
|
|
270
|
+
val result = data.optString("result")
|
|
271
|
+
|
|
272
|
+
if (result.isNotEmpty()) {
|
|
273
|
+
completeRequest(id, Result.Success.Item(result))
|
|
274
|
+
} else {
|
|
275
|
+
logger.error("CommunicationClient:: Unexpected response: $data")
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
EthereumMethod.METAMASK_BATCH.value -> {
|
|
279
|
+
val result = data.optString("result")
|
|
280
|
+
val results: List<String?> = Gson().fromJson(result, object : TypeToken<List<String?>>() {}.type)
|
|
281
|
+
val sanitisedResults = results.filterNotNull()
|
|
282
|
+
completeRequest(id, Result.Success.Items(sanitisedResults))
|
|
283
|
+
}
|
|
284
|
+
else -> {
|
|
285
|
+
val result = data.optString("result")
|
|
286
|
+
completeRequest(id, Result.Success.Item(result))
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
fun handleError(error: String, id: String): Boolean {
|
|
292
|
+
if (error.isEmpty()) {
|
|
293
|
+
return false
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
val requestId: String = id.ifEmpty {
|
|
297
|
+
queuedRequests.entries.find { it.value.request.method == EthereumMethod.ETH_REQUEST_ACCOUNTS.value }?.key ?: ""
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
val errorMap: Map<String, Any?> = Gson().fromJson(error, object : TypeToken<Map<String, Any?>>() {}.type)
|
|
301
|
+
val errorCode = errorMap["code"] as? Double ?: -1
|
|
302
|
+
val code = errorCode.toInt()
|
|
303
|
+
val message = errorMap["message"] as? String ?: ErrorType.message(code)
|
|
304
|
+
logger.error("CommunicationClient:: Got error $error")
|
|
305
|
+
completeRequest(requestId, Result.Error(RequestError(code, message)))
|
|
306
|
+
return true
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
fun completeRequest(id: String, result: Result) {
|
|
310
|
+
if (queuedRequests[id] != null) {
|
|
311
|
+
queuedRequests[id]?.callback?.invoke(result)
|
|
312
|
+
queuedRequests.remove(id)
|
|
313
|
+
}
|
|
314
|
+
submittedRequests[id]?.callback?.invoke(result)
|
|
315
|
+
submittedRequests.remove(id)
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
fun handleEvent(event: JSONObject) {
|
|
319
|
+
when (event.optString("method")) {
|
|
320
|
+
EthereumMethod.METAMASK_ACCOUNTS_CHANGED.value -> {
|
|
321
|
+
val accountsJson = event.optString("params")
|
|
322
|
+
val accounts: List<String> = Gson().fromJson(accountsJson, object : TypeToken<List<String>>() {}.type)
|
|
323
|
+
accounts.getOrNull(0)?.let { account ->
|
|
324
|
+
logger.log("CommunicationClient:: Event Updated to account $account")
|
|
325
|
+
updateAccount(account)
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
EthereumMethod.METAMASK_CHAIN_CHANGED.value -> {
|
|
329
|
+
val paramsJson = event.optJSONObject("params")
|
|
330
|
+
val chainId = paramsJson?.optString("chainId")
|
|
331
|
+
|
|
332
|
+
if (!chainId.isNullOrEmpty()) {
|
|
333
|
+
updateChainId(chainId)
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
else -> {
|
|
337
|
+
logger.error("CommunicationClient:: Unexpected event: $event")
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
fun updateAccount(account: String) {
|
|
343
|
+
val callback = ethereumEventCallbackRef.get()
|
|
344
|
+
callback?.updateAccount(account)
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
fun updateChainId(chainId: String) {
|
|
348
|
+
val callback = ethereumEventCallbackRef.get()
|
|
349
|
+
callback?.updateChainId(chainId)
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
fun handleKeyExchange(message: String) {
|
|
353
|
+
val json = JSONObject(message)
|
|
354
|
+
|
|
355
|
+
val keyExchangeStep = json.optString(KeyExchange.TYPE, KeyExchangeMessageType.KEY_HANDSHAKE_SYN.name)
|
|
356
|
+
val type = KeyExchangeMessageType.valueOf(keyExchangeStep)
|
|
357
|
+
val theirPublicKey = json.optString(KeyExchange.PUBLIC_KEY)
|
|
358
|
+
val keyExchangeMessage = KeyExchangeMessage(type.name, theirPublicKey)
|
|
359
|
+
val nextStep = keyExchange.nextKeyExchangeMessage(keyExchangeMessage)
|
|
360
|
+
|
|
361
|
+
if (type == KeyExchangeMessageType.KEY_HANDSHAKE_ACK) {
|
|
362
|
+
keyExchange.complete()
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (nextStep != null) {
|
|
366
|
+
val exchangeMessage = JSONObject().apply {
|
|
367
|
+
put(KeyExchange.PUBLIC_KEY, nextStep.publicKey)
|
|
368
|
+
put(KeyExchange.TYPE, nextStep.type)
|
|
369
|
+
}.toString()
|
|
370
|
+
|
|
371
|
+
logger.log("Sending key exchange ${nextStep.type}")
|
|
372
|
+
sendKeyExchangeMesage(exchangeMessage)
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
fun sendMessage(message: String) {
|
|
377
|
+
val bundle = Bundle().apply {
|
|
378
|
+
putString(MESSAGE, message)
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (keyExchange.keysExchanged()) {
|
|
382
|
+
serviceConnection.sendMessage(bundle)
|
|
383
|
+
} else {
|
|
384
|
+
logger.log("CommunicationClient::sendMessage keys not exchanged, queueing job")
|
|
385
|
+
queueRequestJob { serviceConnection.sendMessage(bundle) }
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
fun sendRequest(request: RpcRequest, callback: (Result) -> Unit) {
|
|
390
|
+
if (request.method == EthereumMethod.GET_METAMASK_PROVIDER_STATE.value) {
|
|
391
|
+
clearPendingRequests()
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (!isServiceConnected) {
|
|
395
|
+
queuedRequests[request.id] = SubmittedRequest(request, callback)
|
|
396
|
+
queueRequestJob { processRequest(request, callback) }
|
|
397
|
+
if (!requestedBindService) {
|
|
398
|
+
logger.log("CommunicationClient:: sendRequest - not yet connected to metamask, binding service first")
|
|
399
|
+
bindService()
|
|
400
|
+
} else {
|
|
401
|
+
logger.log("CommunicationClient:: sendRequest - not yet connected to metamask, waiting for service to bind")
|
|
402
|
+
}
|
|
403
|
+
} else if (!keyExchange.keysExchanged()) {
|
|
404
|
+
logger.log("CommunicationClient:: sendRequest - keys not yet exchanged")
|
|
405
|
+
queuedRequests[request.id] = SubmittedRequest(request, callback)
|
|
406
|
+
queueRequestJob { processRequest(request, callback) }
|
|
407
|
+
initiateKeyExchange()
|
|
408
|
+
} else {
|
|
409
|
+
if (isMetaMaskReady) {
|
|
410
|
+
processRequest(request, callback)
|
|
411
|
+
} else {
|
|
412
|
+
logger.log("CommunicationClient::sendRequest - wallet is not ready, queueing request")
|
|
413
|
+
queueRequestJob { processRequest(request, callback) }
|
|
414
|
+
sendOriginatorInfo()
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
fun processRequest(request: RpcRequest, callback: (Result) -> Unit) {
|
|
420
|
+
logger.log("CommunicationClient:: sending request $request")
|
|
421
|
+
if (queuedRequests[request.id] != null) {
|
|
422
|
+
queuedRequests.remove(request.id)
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
val requestJson = Gson().toJson(request)
|
|
426
|
+
|
|
427
|
+
val payload = keyExchange.encrypt(requestJson)
|
|
428
|
+
val message = Message(sessionId, payload)
|
|
429
|
+
val messageJson = Gson().toJson(message)
|
|
430
|
+
|
|
431
|
+
submittedRequests[request.id] = SubmittedRequest(request, callback)
|
|
432
|
+
sendMessage(messageJson)
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
fun sendOriginatorInfo() {
|
|
436
|
+
if (sentOriginatorInfo) { return }
|
|
437
|
+
sentOriginatorInfo = true
|
|
438
|
+
|
|
439
|
+
val originatorInfo = OriginatorInfo(
|
|
440
|
+
title = dappMetadata?.name,
|
|
441
|
+
url = dappMetadata?.url,
|
|
442
|
+
icon = dappMetadata?.iconUrl ?: dappMetadata?.base64Icon,
|
|
443
|
+
dappId = appContextRef.get()?.packageName,
|
|
444
|
+
platform = SDKInfo.PLATFORM,
|
|
445
|
+
apiVersion = SDKInfo.VERSION)
|
|
446
|
+
val requestInfo = RequestInfo("originator_info", originatorInfo)
|
|
447
|
+
val requestInfoJson = Gson().toJson(requestInfo)
|
|
448
|
+
|
|
449
|
+
logger.log("CommunicationClient:: Sending originator info: $requestInfoJson")
|
|
450
|
+
logger.log("CommunicationClient:: SessionId $sessionId")
|
|
451
|
+
|
|
452
|
+
val payload = keyExchange.encrypt(requestInfoJson)
|
|
453
|
+
|
|
454
|
+
val message = Message(sessionId, payload)
|
|
455
|
+
val messageJson = Gson().toJson(message)
|
|
456
|
+
|
|
457
|
+
sendMessage(messageJson)
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
fun isQA(): Boolean {
|
|
461
|
+
if (Build.VERSION.SDK_INT < 33 ) { // i.e Build.VERSION_CODES.TIRAMISU
|
|
462
|
+
return false
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
val packageManager = appContextRef.get()?.packageManager
|
|
466
|
+
|
|
467
|
+
return try {
|
|
468
|
+
packageManager?.getPackageInfo("io.metamask.qa", PackageManager.PackageInfoFlags.of(0))
|
|
469
|
+
true
|
|
470
|
+
} catch (e: PackageManager.NameNotFoundException) {
|
|
471
|
+
false
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
fun bindService() {
|
|
476
|
+
logger.log("CommunicationClient:: Binding service")
|
|
477
|
+
requestedBindService = true
|
|
478
|
+
|
|
479
|
+
val serviceIntent = Intent()
|
|
480
|
+
.setComponent(
|
|
481
|
+
ComponentName(
|
|
482
|
+
if (isQA()) "io.metamask.qa" else "io.metamask",
|
|
483
|
+
"io.metamask.nativesdk.MessageService"
|
|
484
|
+
)
|
|
485
|
+
)
|
|
486
|
+
|
|
487
|
+
if (appContextRef.get() != null) {
|
|
488
|
+
appContextRef.get()?.bindService(
|
|
489
|
+
serviceIntent,
|
|
490
|
+
serviceConnection,
|
|
491
|
+
Context.BIND_AUTO_CREATE)
|
|
492
|
+
} else {
|
|
493
|
+
logger.error("App context null")
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
fun unbindService() {
|
|
498
|
+
requestedBindService = false
|
|
499
|
+
|
|
500
|
+
if (isServiceConnected) {
|
|
501
|
+
logger.log("CommunicationClient:: unbindService")
|
|
502
|
+
appContextRef.get()?.unbindService(serviceConnection)
|
|
503
|
+
isServiceConnected = false
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
fun initiateKeyExchange() {
|
|
508
|
+
logger.log("CommunicationClient:: Initiating key exchange")
|
|
509
|
+
|
|
510
|
+
val keyExchange = JSONObject().apply {
|
|
511
|
+
put(KeyExchange.PUBLIC_KEY, keyExchange.publicKey)
|
|
512
|
+
put(KeyExchange.TYPE, KeyExchangeMessageType.KEY_HANDSHAKE_SYN.name)
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
logger.log("Sending key exchange ${KeyExchangeMessageType.KEY_HANDSHAKE_SYN}")
|
|
516
|
+
sendKeyExchangeMesage(keyExchange.toString())
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
fun sendKeyExchangeMesage(message: String) {
|
|
520
|
+
val bundle = Bundle().apply {
|
|
521
|
+
putString(KEY_EXCHANGE, message)
|
|
522
|
+
}
|
|
523
|
+
serviceConnection.sendMessage(bundle)
|
|
524
|
+
}
|
|
525
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
package io.metamask.androidsdk
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
|
|
5
|
+
open class CommunicationClientModule(private val context: Context): CommunicationClientModuleInterface {
|
|
6
|
+
override fun provideKeyStorage(): SecureStorage {
|
|
7
|
+
return KeyStorage(context)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
override fun provideSessionManager(keyStorage: SecureStorage): SessionManager {
|
|
11
|
+
return SessionManager(keyStorage)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
override fun provideKeyExchange(): KeyExchange {
|
|
15
|
+
return KeyExchange()
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
override fun provideLogger(): Logger {
|
|
19
|
+
return DefaultLogger
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
override fun provideClientServiceConnection(): ClientServiceConnection {
|
|
23
|
+
return ClientServiceConnection()
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
override fun provideClientMessageServiceCallback(): ClientMessageServiceCallback {
|
|
27
|
+
return ClientMessageServiceCallback()
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
override fun provideCommunicationClient(callback: EthereumEventCallback?): CommunicationClient {
|
|
31
|
+
val keyStorage = provideKeyStorage()
|
|
32
|
+
val sessionManager = provideSessionManager(keyStorage)
|
|
33
|
+
val keyExchange = provideKeyExchange()
|
|
34
|
+
val serviceConnection = provideClientServiceConnection()
|
|
35
|
+
val messageServiceCallback = provideClientMessageServiceCallback()
|
|
36
|
+
val logger = provideLogger()
|
|
37
|
+
|
|
38
|
+
return CommunicationClient(
|
|
39
|
+
context,
|
|
40
|
+
callback,
|
|
41
|
+
sessionManager,
|
|
42
|
+
keyExchange,
|
|
43
|
+
serviceConnection,
|
|
44
|
+
messageServiceCallback,
|
|
45
|
+
logger)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
package io.metamask.androidsdk
|
|
2
|
+
|
|
3
|
+
interface CommunicationClientModuleInterface {
|
|
4
|
+
fun provideKeyStorage(): SecureStorage
|
|
5
|
+
fun provideSessionManager(keyStorage: SecureStorage): SessionManager
|
|
6
|
+
fun provideKeyExchange(): KeyExchange
|
|
7
|
+
fun provideLogger(): Logger
|
|
8
|
+
fun provideClientServiceConnection(): ClientServiceConnection
|
|
9
|
+
fun provideClientMessageServiceCallback(): ClientMessageServiceCallback
|
|
10
|
+
fun provideCommunicationClient(callback: EthereumEventCallback?): CommunicationClient
|
|
11
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
package io.metamask.androidsdk
|
|
2
|
+
|
|
3
|
+
import io.metamask.ecies.Ecies
|
|
4
|
+
import kotlinx.coroutines.CoroutineScope
|
|
5
|
+
import kotlinx.coroutines.Dispatchers
|
|
6
|
+
import kotlinx.coroutines.launch
|
|
7
|
+
|
|
8
|
+
class Crypto : Encryption {
|
|
9
|
+
private var ecies: Ecies? = null
|
|
10
|
+
override var onInitialized: () -> Unit = {}
|
|
11
|
+
private val coroutineScope = CoroutineScope(Dispatchers.IO)
|
|
12
|
+
|
|
13
|
+
init {
|
|
14
|
+
coroutineScope.launch {
|
|
15
|
+
ecies = Ecies()
|
|
16
|
+
onInitialized()
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
override fun generatePrivateKey(): String {
|
|
21
|
+
return ecies?.privateKey() ?: ""
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
override fun publicKey(privateKey: String): String {
|
|
25
|
+
return ecies?.publicKeyFrom(privateKey) ?: ""
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
override fun encrypt(publicKey: String, message: String): String {
|
|
29
|
+
return ecies?.encrypt(publicKey, message) ?: ""
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
override fun decrypt(privateKey: String, message: String): String {
|
|
33
|
+
return ecies?.decrypt(privateKey, message) ?: ""
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
package io.metamask.androidsdk
|
|
2
|
+
import kotlinx.serialization.Serializable
|
|
3
|
+
import java.net.MalformedURLException
|
|
4
|
+
import java.net.URL
|
|
5
|
+
|
|
6
|
+
@Serializable
|
|
7
|
+
data class DappMetadata(
|
|
8
|
+
val name: String,
|
|
9
|
+
val url: String,
|
|
10
|
+
val iconUrl: String? = null,
|
|
11
|
+
val base64Icon: String? = null
|
|
12
|
+
) {
|
|
13
|
+
fun hasValidUrl(): Boolean {
|
|
14
|
+
return try {
|
|
15
|
+
val url = URL(url)
|
|
16
|
+
url.protocol != null && url.host != null
|
|
17
|
+
} catch (e: MalformedURLException) {
|
|
18
|
+
false
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
fun hasValidName(): Boolean {
|
|
23
|
+
return name.isNotEmpty()
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
val validationError: RequestError?
|
|
27
|
+
get() {
|
|
28
|
+
if (!hasValidUrl()) {
|
|
29
|
+
return RequestError(-101, "Please use a valid Dapp url")
|
|
30
|
+
}
|
|
31
|
+
if (!hasValidName()) {
|
|
32
|
+
return RequestError(-102, "Please use a valid Dapp name")
|
|
33
|
+
}
|
|
34
|
+
return null
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
package io.metamask.androidsdk
|
|
2
|
+
|
|
3
|
+
interface Encryption {
|
|
4
|
+
var onInitialized: () -> Unit
|
|
5
|
+
fun generatePrivateKey(): String
|
|
6
|
+
fun publicKey(privateKey: String): String
|
|
7
|
+
fun encrypt(publicKey: String, message: String): String
|
|
8
|
+
fun decrypt(privateKey: String, message: String): String
|
|
9
|
+
}
|