@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.
Files changed (127) hide show
  1. package/NitroMetamask.podspec +12 -3
  2. package/README.md +3 -1
  3. package/android/build.gradle +14 -32
  4. package/android/cargo-ecies.gradle +60 -88
  5. package/android/src/main/aidl/io/metamask/nativesdk/IMessegeService.aidl +8 -0
  6. package/android/src/main/aidl/io/metamask/nativesdk/IMessegeServiceCallback.aidl +8 -0
  7. package/android/src/main/java/com/margelo/nitro/nitrometamask/HybridNitroMetamask.kt +101 -3
  8. package/android/src/main/java/io/metamask/androidsdk/AnyRequest.kt +8 -0
  9. package/android/src/main/java/io/metamask/androidsdk/ClientMessageServiceCallback.kt +12 -0
  10. package/android/src/main/java/io/metamask/androidsdk/ClientServiceConnection.kt +42 -0
  11. package/android/src/main/java/io/metamask/androidsdk/CommunicationClient.kt +525 -0
  12. package/android/src/main/java/io/metamask/androidsdk/CommunicationClientModule.kt +47 -0
  13. package/android/src/main/java/io/metamask/androidsdk/CommunicationClientModuleInterface.kt +11 -0
  14. package/android/src/main/java/io/metamask/androidsdk/Constants.kt +5 -0
  15. package/android/src/main/java/io/metamask/androidsdk/Crypto.kt +35 -0
  16. package/android/src/main/java/io/metamask/androidsdk/DappMetadata.kt +36 -0
  17. package/android/src/main/java/io/metamask/androidsdk/Encryption.kt +9 -0
  18. package/android/src/main/java/io/metamask/androidsdk/ErrorType.kt +41 -0
  19. package/android/src/main/java/io/metamask/androidsdk/Ethereum.kt +328 -0
  20. package/android/src/main/java/io/metamask/androidsdk/EthereumEventCallback.kt +6 -0
  21. package/android/src/main/java/io/metamask/androidsdk/EthereumMethod.kt +80 -0
  22. package/android/src/main/java/io/metamask/androidsdk/EthereumRequest.kt +7 -0
  23. package/android/src/main/java/io/metamask/androidsdk/EthereumState.kt +7 -0
  24. package/android/src/main/java/io/metamask/androidsdk/KeyExchange.kt +77 -0
  25. package/android/src/main/java/io/metamask/androidsdk/KeyExchangeMessageType.kt +20 -0
  26. package/android/src/main/java/io/metamask/androidsdk/KeyStorage.kt +122 -0
  27. package/android/src/main/java/io/metamask/androidsdk/Logger.kt +18 -0
  28. package/android/src/main/java/io/metamask/androidsdk/Message.kt +3 -0
  29. package/android/src/main/java/io/metamask/androidsdk/MessageType.kt +11 -0
  30. package/android/src/main/java/io/metamask/androidsdk/OriginatorInfo.kt +12 -0
  31. package/android/src/main/java/io/metamask/androidsdk/RequestError.kt +8 -0
  32. package/android/src/main/java/io/metamask/androidsdk/RequestInfo.kt +9 -0
  33. package/android/src/main/java/io/metamask/androidsdk/Result.kt +11 -0
  34. package/android/src/main/java/io/metamask/androidsdk/RpcRequest.kt +7 -0
  35. package/android/src/main/java/io/metamask/androidsdk/SDKInfo.kt +6 -0
  36. package/android/src/main/java/io/metamask/androidsdk/SDKOptions.kt +6 -0
  37. package/android/src/main/java/io/metamask/androidsdk/SecureStorage.kt +9 -0
  38. package/android/src/main/java/io/metamask/androidsdk/SessionConfig.kt +10 -0
  39. package/android/src/main/java/io/metamask/androidsdk/SessionManager.kt +92 -0
  40. package/android/src/main/java/io/metamask/androidsdk/SubmittedRequest.kt +8 -0
  41. package/android/src/main/java/io/metamask/androidsdk/TimeStampGenerator.kt +7 -0
  42. package/android/src/main/jniLibs/arm64-v8a/libecies.so +0 -0
  43. package/android/src/main/jniLibs/armeabi-v7a/libecies.so +0 -0
  44. package/android/src/main/jniLibs/x86/libecies.so +0 -0
  45. package/android/src/main/jniLibs/x86_64/libecies.so +0 -0
  46. package/android/src/test/java/com/margelo/nitro/nitrometamask/CancellationStateMachineTest.kt +128 -0
  47. package/android/src/test/java/com/margelo/nitro/nitrometamask/ChainIdParsingTest.kt +65 -0
  48. package/android/src/test/java/com/margelo/nitro/nitrometamask/ConfigureStateMachineTest.kt +140 -0
  49. package/android/src/test/java/com/margelo/nitro/nitrometamask/ConnectSignJsonTest.kt +76 -0
  50. package/android/src/test/java/com/margelo/nitro/nitrometamask/MetaMaskInstallationCheckTest.kt +42 -0
  51. package/android/src/test/java/com/margelo/nitro/nitrometamask/PersonalSignParamsTest.kt +75 -0
  52. package/ios/Frameworks/Ecies.xcframework/Info.plist +47 -0
  53. package/ios/Frameworks/Ecies.xcframework/ios-arm64/Headers/ecies.h +20 -0
  54. package/ios/Frameworks/Ecies.xcframework/ios-arm64/Headers/module.modulemap +4 -0
  55. package/ios/Frameworks/Ecies.xcframework/ios-arm64/libecies.a +0 -0
  56. package/ios/Frameworks/Ecies.xcframework/ios-arm64-simulator/Headers/ecies.h +20 -0
  57. package/ios/Frameworks/Ecies.xcframework/ios-arm64-simulator/Headers/module.modulemap +4 -0
  58. package/ios/Frameworks/Ecies.xcframework/ios-arm64-simulator/libecies.a +0 -0
  59. package/ios/HybridNitroMetamask.swift +119 -54
  60. package/ios/NitroMetamaskTests/CancellationStateMachineTests.swift +150 -0
  61. package/ios/NitroMetamaskTests/ChainIdParsingTests.swift +117 -0
  62. package/ios/NitroMetamaskTests/ConfigureStateMachineTests.swift +174 -0
  63. package/ios/NitroMetamaskTests/ConnectSignJsonTests.swift +168 -0
  64. package/ios/NitroMetamaskTests/DefaultDappUrlTests.swift +80 -0
  65. package/ios/NitroMetamaskTests/PersonalSignParamsTests.swift +101 -0
  66. package/ios/metamask-ios-sdk/CommunicationLayer/CommClient.swift +43 -0
  67. package/ios/metamask-ios-sdk/CommunicationLayer/CommClientFactory.swift +17 -0
  68. package/ios/metamask-ios-sdk/CommunicationLayer/CommLayer.swift +36 -0
  69. package/ios/metamask-ios-sdk/CommunicationLayer/DeeplinkCommLayer/Deeplink.swift +26 -0
  70. package/ios/metamask-ios-sdk/CommunicationLayer/DeeplinkCommLayer/DeeplinkClient.swift +199 -0
  71. package/ios/metamask-ios-sdk/CommunicationLayer/DeeplinkCommLayer/DeeplinkManager.swift +83 -0
  72. package/ios/metamask-ios-sdk/CommunicationLayer/DeeplinkCommLayer/String.swift +48 -0
  73. package/ios/metamask-ios-sdk/CommunicationLayer/DeeplinkCommLayer/URLOpener.swift +19 -0
  74. package/ios/metamask-ios-sdk/CommunicationLayer/SocketClient.swift +27 -0
  75. package/ios/metamask-ios-sdk/Crypto/Crypto.swift +72 -0
  76. package/ios/metamask-ios-sdk/Crypto/Encoding.swift +15 -0
  77. package/ios/metamask-ios-sdk/Crypto/KeyExchange.swift +236 -0
  78. package/ios/metamask-ios-sdk/DeviceInfo/DeviceInfo.swift +11 -0
  79. package/ios/metamask-ios-sdk/Ethereum/AppMetadata.swift +28 -0
  80. package/ios/metamask-ios-sdk/Ethereum/ErrorType.swift +62 -0
  81. package/ios/metamask-ios-sdk/Ethereum/Ethereum.swift +810 -0
  82. package/ios/metamask-ios-sdk/Ethereum/EthereumMethod.swift +111 -0
  83. package/ios/metamask-ios-sdk/Ethereum/EthereumRequest.swift +40 -0
  84. package/ios/metamask-ios-sdk/Ethereum/EthereumWrapper.swift +10 -0
  85. package/ios/metamask-ios-sdk/Ethereum/RPCRequest.swift +14 -0
  86. package/ios/metamask-ios-sdk/Ethereum/RequestError.swift +88 -0
  87. package/ios/metamask-ios-sdk/Ethereum/ResponseMethod.swift +22 -0
  88. package/ios/metamask-ios-sdk/Ethereum/SubmitRequest.swift +26 -0
  89. package/ios/metamask-ios-sdk/Ethereum/TimestampGenerator.swift +16 -0
  90. package/ios/metamask-ios-sdk/Extensions/NSRecursiveLock.swift +14 -0
  91. package/ios/metamask-ios-sdk/Extensions/Notification.swift +10 -0
  92. package/ios/metamask-ios-sdk/Logger/Logging.swift +27 -0
  93. package/ios/metamask-ios-sdk/Models/AddChainParameters.swift +35 -0
  94. package/ios/metamask-ios-sdk/Models/Event.swift +19 -0
  95. package/ios/metamask-ios-sdk/Models/Mappable.swift +40 -0
  96. package/ios/metamask-ios-sdk/Models/NativeCurrency.swift +25 -0
  97. package/ios/metamask-ios-sdk/Models/OriginatorInfo.swift +26 -0
  98. package/ios/metamask-ios-sdk/Models/RequestInfo.swift +18 -0
  99. package/ios/metamask-ios-sdk/Models/SignContract.swift +48 -0
  100. package/ios/metamask-ios-sdk/Models/Typealiases.swift +9 -0
  101. package/ios/metamask-ios-sdk/Persistence/SecureStore.swift +134 -0
  102. package/ios/metamask-ios-sdk/Persistence/SessionConfig.swift +24 -0
  103. package/ios/metamask-ios-sdk/Persistence/SessionManager.swift +56 -0
  104. package/ios/metamask-ios-sdk/SDK/Dependencies.swift +35 -0
  105. package/ios/metamask-ios-sdk/SDK/MetaMaskSDK.swift +215 -0
  106. package/ios/metamask-ios-sdk/SDK/SDKInfo.swift +37 -0
  107. package/ios/metamask-ios-sdk/SDK/SDKOptions.swift +16 -0
  108. package/lib/commonjs/index.js +50 -3
  109. package/lib/commonjs/index.js.map +1 -1
  110. package/lib/module/index.js +49 -3
  111. package/lib/module/index.js.map +1 -1
  112. package/lib/typescript/src/__tests__/parseNitroError.test.d.ts +2 -0
  113. package/lib/typescript/src/__tests__/parseNitroError.test.d.ts.map +1 -0
  114. package/lib/typescript/src/index.d.ts +43 -3
  115. package/lib/typescript/src/index.d.ts.map +1 -1
  116. package/lib/typescript/src/specs/nitro-metamask.nitro.d.ts +29 -1
  117. package/lib/typescript/src/specs/nitro-metamask.nitro.d.ts.map +1 -1
  118. package/package.json +21 -12
  119. package/react-native.config.js +5 -0
  120. package/rust/ecies-jni/Cargo.lock +50 -86
  121. package/rust/ecies-jni/Cargo.toml +1 -1
  122. package/rust/ecies-jni/src/lib.rs +164 -100
  123. package/src/__tests__/parseNitroError.test.ts +35 -0
  124. package/src/index.ts +53 -5
  125. package/src/specs/nitro-metamask.nitro.ts +29 -1
  126. package/scripts/verify-16k-page-alignment.py +0 -117
  127. package/scripts/verify-16k-page-alignment.sh +0 -5
@@ -0,0 +1,41 @@
1
+ package io.metamask.androidsdk
2
+
3
+ enum class ErrorType(val code: Int) {
4
+ // Ethereum Provider
5
+ USER_REJECTED_REQUEST(4001), // Ethereum Provider User Rejected Request
6
+ UNAUTHORISED_REQUEST(4100), // Ethereum Provider User Rejected Request
7
+ UNSUPPORTED_METHOD(4200), // Ethereum Provider Unsupported Method
8
+ DISCONNECTED(4900), // Ethereum Provider Not Connected
9
+ CHAIN_DISCONNECTED(4901), // Ethereum Provider Chain Not Connected
10
+ UNRECOGNIZED_CHAIN_ID(4902), // Unrecognized chain ID. Try adding the chain using wallet_ADD_ETHEREUM_CHAIN first
11
+
12
+ // Ethereum RPC
13
+ INVALID_INPUT(-32000), // JSON RPC 2.0 Server error
14
+ TRANSACTION_REJECTED(-32003), // Ethereum JSON RPC Transaction Rejected
15
+ INVALID_REQUEST(-32600), // JSON RPC 2.0 Invalid Request
16
+ INVALID_METHOD_PARAMETERS(-32602), // JSON RPC 2.0 Invalid Parameters
17
+ SERVER_ERROR(-32603), // Could be one of many outcomes
18
+ PARSE_ERROR(-32700), // JSON RPC 2.0 Parse error
19
+ UNKNOWN_ERROR(-1); // Check RequestError.code instead
20
+
21
+ companion object {
22
+ fun message(code: Int): String {
23
+
24
+ return when(values().firstOrNull { it.code == code }) {
25
+ USER_REJECTED_REQUEST -> "User rejected request"
26
+ UNAUTHORISED_REQUEST -> "Request not authorised"
27
+ UNSUPPORTED_METHOD -> "Ethereum provider unsupported method"
28
+ DISCONNECTED -> "Ethereum provider not connected"
29
+ CHAIN_DISCONNECTED -> "Ethereum provider chain not connected"
30
+ UNRECOGNIZED_CHAIN_ID -> "Unrecognized chain ID. Try adding the chain using ADD_ETHEREUM_CHAIN first"
31
+ INVALID_INPUT -> "JSON RPC 2.0 Server error"
32
+ TRANSACTION_REJECTED -> "Ethereum transaction rejected"
33
+ INVALID_METHOD_PARAMETERS -> "JSON RPC 2.0 invalid parameters"
34
+ INVALID_REQUEST -> "Invalid request"
35
+ SERVER_ERROR -> "Server error"
36
+ PARSE_ERROR -> "Parse error"
37
+ else -> "The request failed"
38
+ }
39
+ }
40
+ }
41
+ }
@@ -0,0 +1,328 @@
1
+ package io.metamask.androidsdk
2
+
3
+ import android.content.Context
4
+ import android.content.Intent
5
+ import android.net.Uri
6
+ import androidx.lifecycle.LiveData
7
+ import androidx.lifecycle.MutableLiveData
8
+ import kotlinx.coroutines.Dispatchers
9
+ import kotlinx.coroutines.runBlocking
10
+ import kotlinx.coroutines.withContext
11
+ import java.lang.ref.WeakReference
12
+
13
+ private const val METAMASK_DEEPLINK = "https://metamask.app.link"
14
+ private const val METAMASK_BIND_DEEPLINK = "$METAMASK_DEEPLINK/bind"
15
+
16
+ class Ethereum(
17
+ private val context: Context,
18
+ private val dappMetadata: DappMetadata,
19
+ sdkOptions: SDKOptions? = null,
20
+ private val logger: Logger = DefaultLogger,
21
+ private val communicationClientModule: CommunicationClientModuleInterface = CommunicationClientModule(context)
22
+ ) : EthereumEventCallback {
23
+
24
+ private var connectRequestSent = false
25
+
26
+ val communicationClient: CommunicationClient? by lazy {
27
+ communicationClientModule.provideCommunicationClient(this)
28
+ }
29
+
30
+ private val storage = communicationClientModule.provideKeyStorage()
31
+
32
+ // Ethereum LiveData
33
+ private val _ethereumState = MutableLiveData(EthereumState("", "", ""))
34
+ private val currentEthereumState: EthereumState
35
+ get() = checkNotNull(ethereumState.value)
36
+ val ethereumState: LiveData<EthereumState> get() = _ethereumState
37
+
38
+ private var cachedChainId = ""
39
+ private var cachedAccount = ""
40
+
41
+ var selectedAddress: String = ethereumState.value?.selectedAddress.takeIf { !it.isNullOrEmpty() } ?: cachedAccount
42
+ var chainId: String = ethereumState.value?.chainId.takeIf { !it.isNullOrEmpty() } ?: cachedChainId
43
+
44
+ var enableDebug: Boolean = true
45
+ set(value) {
46
+ field = value
47
+ communicationClient?.enableDebug = value
48
+ }
49
+
50
+ init {
51
+ runBlocking { fetchCachedSession() }
52
+ }
53
+
54
+ private suspend fun fetchCachedSession() {
55
+ withContext(Dispatchers.IO) {
56
+ try {
57
+ val account = storage.getValue(key = SessionManager.SESSION_ACCOUNT_KEY, file = SessionManager.SESSION_CONFIG_FILE)
58
+ val chainId = storage.getValue(key = SessionManager.SESSION_CHAIN_ID_KEY, file = SessionManager.SESSION_CONFIG_FILE)
59
+ if (account != null && chainId != null) {
60
+ cachedChainId = chainId
61
+ cachedAccount = account
62
+ _ethereumState.postValue(
63
+ currentEthereumState.copy(
64
+ selectedAddress = account,
65
+ chainId = chainId,
66
+ sessionId = communicationClient?.sessionId ?: ""
67
+ )
68
+ )
69
+ }
70
+ } catch (e: Exception) {
71
+ e.localizedMessage?.let { logger.error(it) }
72
+ }
73
+ }
74
+ }
75
+
76
+ fun enableDebug(enable: Boolean) = apply {
77
+ this.enableDebug = enable
78
+ }
79
+
80
+ private var sessionDuration: Long = SessionManager.DEFAULT_SESSION_DURATION
81
+
82
+ override fun updateAccount(account: String) {
83
+ logger.log("Ethereum:: Selected account changed: $account")
84
+ _ethereumState.postValue(
85
+ currentEthereumState.copy(
86
+ selectedAddress = account,
87
+ sessionId = communicationClient?.sessionId ?: ""
88
+ )
89
+ )
90
+ if (account.isNotEmpty()) {
91
+ selectedAddress = account
92
+ storage.putValue(account, key = SessionManager.SESSION_ACCOUNT_KEY, SessionManager.SESSION_CONFIG_FILE)
93
+ }
94
+ }
95
+
96
+ override fun updateChainId(newChainId: String) {
97
+ logger.log("Ethereum:: ChainId changed: $newChainId")
98
+ _ethereumState.postValue(
99
+ currentEthereumState.copy(
100
+ chainId = newChainId,
101
+ sessionId = communicationClient?.sessionId ?: ""
102
+ )
103
+ )
104
+ if (newChainId.isNotEmpty()) {
105
+ chainId = newChainId
106
+ storage.putValue(newChainId, key = SessionManager.SESSION_CHAIN_ID_KEY, SessionManager.SESSION_CONFIG_FILE)
107
+ }
108
+ }
109
+
110
+ fun updateSessionDuration(duration: Long = SessionManager.DEFAULT_SESSION_DURATION) = apply {
111
+ sessionDuration = duration
112
+ communicationClient?.updateSessionDuration(duration)
113
+ }
114
+
115
+ fun clearSession() {
116
+ disconnect(true)
117
+ storage.clear(SessionManager.SESSION_CONFIG_FILE)
118
+ }
119
+
120
+ fun connect(callback: ((Result) -> Unit)? = null) {
121
+ connectRequestSent = true
122
+
123
+ val error = dappMetadata.validationError
124
+ if (error != null) {
125
+ callback?.invoke(Result.Error(error))
126
+ return
127
+ }
128
+
129
+ logger.log("Ethereum:: connecting...")
130
+ communicationClient?.dappMetadata = dappMetadata
131
+ communicationClient?.ethereumEventCallbackRef = WeakReference(this)
132
+ communicationClient?.updateSessionDuration(sessionDuration)
133
+
134
+ _ethereumState.postValue(
135
+ currentEthereumState.copy(
136
+ selectedAddress = "",
137
+ chainId = ""
138
+ )
139
+ )
140
+ requestAccounts(callback)
141
+ }
142
+
143
+ fun connectWith(request: EthereumRequest, callback: ((Result) -> Unit)? = null) {
144
+ logger.log("Ethereum:: connecting with ${request.method}...")
145
+ connectRequestSent = true
146
+ communicationClient?.dappMetadata = dappMetadata
147
+ communicationClient?.ethereumEventCallbackRef = WeakReference(this)
148
+ communicationClient?.updateSessionDuration(sessionDuration)
149
+
150
+ _ethereumState.postValue(
151
+ currentEthereumState.copy(
152
+ selectedAddress = "",
153
+ chainId = ""
154
+ )
155
+ )
156
+ val sendRequest: EthereumRequest = if (request.method == EthereumMethod.METAMASK_CONNECT_WITH.value) {
157
+ request
158
+ } else {
159
+ EthereumRequest(
160
+ method = EthereumMethod.METAMASK_CONNECT_WITH.value,
161
+ params = listOf(request)
162
+ )
163
+ }
164
+ sendConnectRequest(sendRequest, callback)
165
+ }
166
+
167
+ fun connectSign(message: String, callback: ((Result) -> Unit)? = null) {
168
+ connectRequestSent = true
169
+ communicationClient?.dappMetadata = dappMetadata
170
+ communicationClient?.ethereumEventCallbackRef = WeakReference(this)
171
+ communicationClient?.updateSessionDuration(sessionDuration)
172
+
173
+ _ethereumState.postValue(
174
+ currentEthereumState.copy(
175
+ selectedAddress = "",
176
+ chainId = ""
177
+ )
178
+ )
179
+ val connectSignRequest = EthereumRequest(
180
+ method = EthereumMethod.METAMASK_CONNECT_SIGN.value,
181
+ params = listOf(message)
182
+ )
183
+ sendConnectRequest(connectSignRequest, callback)
184
+ }
185
+
186
+ private fun ethereumRequest(method: EthereumMethod, params: Any?, callback: ((Result) -> Unit)?) {
187
+ sendRequest(EthereumRequest(method = method.value, params = params), callback)
188
+ }
189
+
190
+ fun getChainId(callback: ((Result) -> Unit)?) {
191
+ if (connectRequestSent) {
192
+ ethereumRequest(method = EthereumMethod.ETH_CHAIN_ID, params = null, callback)
193
+ } else {
194
+ callback?.invoke(Result.Success.Item(cachedChainId))
195
+ }
196
+ }
197
+
198
+ fun getEthAccounts(callback: ((Result) -> Unit)?) {
199
+ if (connectRequestSent) {
200
+ ethereumRequest(method = EthereumMethod.ETH_ACCOUNTS, params = null, callback)
201
+ } else {
202
+ callback?.invoke(Result.Success.Item(cachedAccount))
203
+ }
204
+ }
205
+
206
+ fun getEthBalance(address: String, block: String, callback: ((Result) -> Unit)? = null) {
207
+ ethereumRequest(EthereumMethod.ETH_GET_BALANCE, params = listOf(address, block), callback)
208
+ }
209
+
210
+ fun getEthBlockNumber(callback: ((Result) -> Unit)?) {
211
+ ethereumRequest(method = EthereumMethod.ETH_BLOCK_NUMBER, params = null, callback)
212
+ }
213
+
214
+ fun getEthEstimateGas(callback: ((Result) -> Unit)?) {
215
+ ethereumRequest(method = EthereumMethod.ETH_ESTIMATE_GAS, params = null, callback)
216
+ }
217
+
218
+ fun getWeb3ClientVersion(callback: ((Result) -> Unit)?) {
219
+ ethereumRequest(method = EthereumMethod.WEB3_CLIENT_VERSION, params = listOf<String>(), callback)
220
+ }
221
+
222
+ fun personalSign(message: String, address: String, callback: ((Result) -> Unit)?) {
223
+ ethereumRequest(method = EthereumMethod.PERSONAL_SIGN, params = listOf(address, message), callback)
224
+ }
225
+
226
+ fun ethSignTypedDataV4(typedData: Any, address: String, callback: ((Result) -> Unit)?) {
227
+ ethereumRequest(method = EthereumMethod.ETH_SIGN_TYPED_DATA_V4, params = listOf(address, typedData), callback)
228
+ }
229
+
230
+ fun sendTransaction(from: String, to: String, value: String, callback: ((Result) -> Unit)?) {
231
+ ethereumRequest(
232
+ method = EthereumMethod.ETH_SEND_TRANSACTION,
233
+ params = listOf(mutableMapOf("from" to from, "to" to to, "value" to value)),
234
+ callback
235
+ )
236
+ }
237
+
238
+ fun sendRawTransaction(signedTransaction: String, callback: ((Result) -> Unit)?) {
239
+ ethereumRequest(method = EthereumMethod.ETH_SEND_RAW_TRANSACTION, params = listOf(signedTransaction), callback)
240
+ }
241
+
242
+ fun getBlockTransactionCountByNumber(blockNumber: String, callback: ((Result) -> Unit)?) {
243
+ ethereumRequest(method = EthereumMethod.ETH_GET_BLOCK_TRANSACTION_COUNT_BY_NUMBER, params = listOf(blockNumber), callback)
244
+ }
245
+
246
+ fun getBlockTransactionCountByHash(blockHash: String, callback: ((Result) -> Unit)?) {
247
+ ethereumRequest(method = EthereumMethod.ETH_GET_BLOCK_TRANSACTION_COUNT_BY_HASH, params = listOf(blockHash), callback)
248
+ }
249
+
250
+ fun getTransactionCount(address: String, tagOrblockNumber: String, callback: ((Result) -> Unit)?) {
251
+ ethereumRequest(method = EthereumMethod.ETH_GET_TRANSACTION_COUNT, params = listOf(address, tagOrblockNumber), callback)
252
+ }
253
+
254
+ fun switchEthereumChain(targetChainId: String, callback: ((Result) -> Unit)?) {
255
+ ethereumRequest(method = EthereumMethod.SWITCH_ETHEREUM_CHAIN, params = listOf(mapOf("chainId" to targetChainId)), callback)
256
+ }
257
+
258
+ private fun sendConnectRequest(request: EthereumRequest, callback: ((Result) -> Unit)?) {
259
+ sendRequest(request, callback)
260
+ }
261
+
262
+ fun disconnect(clearSession: Boolean = false) {
263
+ logger.log("Ethereum:: disconnecting...")
264
+ connectRequestSent = false
265
+ communicationClient?.resetState()
266
+ communicationClient?.unbindService()
267
+
268
+ if (clearSession) {
269
+ communicationClient?.clearSession {
270
+ resetEthereumState()
271
+ }
272
+ } else {
273
+ resetEthereumState()
274
+ }
275
+ }
276
+
277
+ private fun resetEthereumState() {
278
+ _ethereumState.postValue(
279
+ currentEthereumState.copy(
280
+ selectedAddress = "",
281
+ sessionId = communicationClient?.sessionId ?: "",
282
+ chainId = ""
283
+ )
284
+ )
285
+ }
286
+
287
+ private fun requestChainId() {
288
+ val chainIdRequest = EthereumRequest(method = EthereumMethod.ETH_CHAIN_ID.value)
289
+ sendRequest(chainIdRequest)
290
+ }
291
+
292
+ private fun requestAccounts(callback: ((Result) -> Unit)? = null) {
293
+ logger.log("Ethereum:: Requesting ethereum accounts")
294
+ connectRequestSent = true
295
+ val accountsRequest = EthereumRequest(method = EthereumMethod.ETH_REQUEST_ACCOUNTS.value)
296
+ sendConnectRequest(accountsRequest, callback)
297
+ requestChainId()
298
+ }
299
+
300
+ fun sendRequest(request: RpcRequest, callback: ((Result) -> Unit)? = null) {
301
+ logger.log("Ethereum:: Sending request $request")
302
+
303
+ if (!connectRequestSent && selectedAddress.isEmpty()) {
304
+ requestAccounts {
305
+ sendRequest(request, callback)
306
+ }
307
+ return
308
+ }
309
+
310
+ communicationClient?.sendRequest(request) { response ->
311
+ callback?.invoke(response)
312
+ }
313
+ if (EthereumMethod.requiresAuthorisation(request.method)) {
314
+ openMetaMask()
315
+ }
316
+ }
317
+
318
+ fun sendRequestBatch(requests: List<EthereumRequest>, callback: ((Result) -> Unit)? = null) {
319
+ val batchRequest = AnyRequest(method = EthereumMethod.METAMASK_BATCH.value, params = requests)
320
+ sendRequest(batchRequest, callback)
321
+ }
322
+
323
+ private fun openMetaMask() {
324
+ val intent = Intent(Intent.ACTION_VIEW, Uri.parse(METAMASK_BIND_DEEPLINK))
325
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
326
+ context.startActivity(intent)
327
+ }
328
+ }
@@ -0,0 +1,6 @@
1
+ package io.metamask.androidsdk
2
+
3
+ interface EthereumEventCallback {
4
+ fun updateAccount(account: String)
5
+ fun updateChainId(newChainId: String)
6
+ }
@@ -0,0 +1,80 @@
1
+ package io.metamask.androidsdk
2
+
3
+ enum class EthereumMethod(val value: String) {
4
+ ETH_SIGN("eth_sign"),
5
+ WEB3_SHA("web3_sha3"),
6
+ ETH_CALL("eth_call"),
7
+ ETH_CHAIN_ID("eth_chainId"),
8
+ ETH_GET_CODE("eth_getCode"),
9
+ ETH_ACCOUNTS("eth_accounts"),
10
+ ETH_GAS_PRICE("eth_gasPrice"),
11
+ PERSONAL_SIGN("personal_sign"),
12
+ ETH_GET_BALANCE("eth_getBalance"),
13
+ WATCH_ASSET("wallet_watchAsset"),
14
+ ETH_BLOCK_NUMBER("eth_blockNumber"),
15
+ ETH_ESTIMATE_GAS("eth_estimateGas"),
16
+ ETH_GET_STORAGE_AT("eth_getStorageAt"),
17
+ ETH_SIGN_TYPED_DATA("eth_signTypedData"),
18
+ ETH_GET_BLOCK_BY_HASH("eth_getBlockByHash"),
19
+ WEB3_CLIENT_VERSION("web3_clientVersion"),
20
+ ETH_REQUEST_ACCOUNTS("eth_requestAccounts"),
21
+ ETH_SIGN_TRANSACTION("eth_signTransaction"),
22
+ ETH_SEND_TRANSACTION("eth_sendTransaction"),
23
+ ETH_SIGN_TYPED_DATA_V3("eth_signTypedData_v3"),
24
+ ETH_SIGN_TYPED_DATA_V4("eth_signTypedData_v4"),
25
+ ADD_ETHEREUM_CHAIN("wallet_addEthereumChain"),
26
+ METAMASK_BATCH("metamask_batch"),
27
+ METAMASK_OPEN("metamask_open"),
28
+ PERSONAL_EC_RECOVER("personal_ecRecover"),
29
+ WALLET_REVOKE_PERMISSIONS("wallet_revokePermissions"),
30
+ WALLET_REQUEST_PERMISSIONS("wallet_requestPermissions"),
31
+ WALLET_GET_PERMISSIONS("wallet_getPermissions"),
32
+ METAMASK_CONNECT_WITH("metamask_connectwith"),
33
+ METAMASK_CONNECT_SIGN("metamask_connectSign"),
34
+ METAMASK_CHAIN_CHANGED("metamask_chainChanged"),
35
+ ETH_SEND_RAW_TRANSACTION("eth_sendRawTransaction"),
36
+ SWITCH_ETHEREUM_CHAIN("wallet_switchEthereumChain"),
37
+ ETH_GET_TRANSACTION_COUNT("eth_getTransactionCount"),
38
+ METAMASK_ACCOUNTS_CHANGED("metamask_accountsChanged"),
39
+ ETH_GET_TRANSACTION_BY_HASH("eth_getTransactionByHash"),
40
+ ETH_GET_TRANSACTION_RECEIPT("eth_getTransactionReceipt"),
41
+ GET_METAMASK_PROVIDER_STATE("metamask_getProviderState"),
42
+ ETH_GET_BLOCK_TRANSACTION_COUNT_BY_HASH("eth_getBlockTransactionCountByHash"),
43
+ ETH_GET_BLOCK_TRANSACTION_COUNT_BY_NUMBER("eth_getBlockTransactionCountByNumber"),
44
+ UNKNOWN("unknown");
45
+
46
+ companion object {
47
+ fun hasMethod(method: String): Boolean {
48
+ return enumValues<EthereumMethod>()
49
+ .toList()
50
+ .map { it.value }
51
+ .contains(method)
52
+ }
53
+
54
+ fun requiresAuthorisation(method: String): Boolean {
55
+ val authorisationMethods: List<String> = listOf(
56
+ ETH_SIGN, WATCH_ASSET, PERSONAL_SIGN, METAMASK_BATCH, WALLET_GET_PERMISSIONS, WALLET_REVOKE_PERMISSIONS,
57
+ ADD_ETHEREUM_CHAIN, SWITCH_ETHEREUM_CHAIN, METAMASK_CONNECT_WITH, WALLET_REQUEST_PERMISSIONS,
58
+ ETH_SEND_TRANSACTION, ETH_SIGN_TRANSACTION, ETH_REQUEST_ACCOUNTS, METAMASK_CONNECT_SIGN, PERSONAL_EC_RECOVER,
59
+ ETH_SIGN_TYPED_DATA, ETH_SIGN_TYPED_DATA_V3, ETH_SIGN_TYPED_DATA_V4, ETH_ACCOUNTS, METAMASK_OPEN
60
+ ).map { it.value }
61
+
62
+ return authorisationMethods.contains(method)
63
+ }
64
+
65
+ fun isReadOnly(method: String): Boolean {
66
+ return !requiresAuthorisation(method)
67
+ }
68
+
69
+ fun isResultMethod(method: String): Boolean {
70
+ val resultMethods: List<String> = listOf(
71
+ ETH_SIGN, ETH_CHAIN_ID, PERSONAL_SIGN, METAMASK_CONNECT_WITH, WALLET_GET_PERMISSIONS,
72
+ ADD_ETHEREUM_CHAIN, SWITCH_ETHEREUM_CHAIN, METAMASK_BATCH, WALLET_REQUEST_PERMISSIONS,
73
+ ETH_SIGN_TRANSACTION, ETH_SEND_TRANSACTION, METAMASK_CONNECT_SIGN, WALLET_REVOKE_PERMISSIONS,
74
+ WATCH_ASSET, ETH_REQUEST_ACCOUNTS, GET_METAMASK_PROVIDER_STATE,ETH_ACCOUNTS,
75
+ ETH_SIGN_TYPED_DATA, ETH_SIGN_TYPED_DATA_V3, ETH_SIGN_TYPED_DATA_V4,
76
+ ).map { it.value }
77
+ return resultMethods.contains(method)
78
+ }
79
+ }
80
+ }
@@ -0,0 +1,7 @@
1
+ package io.metamask.androidsdk
2
+
3
+ data class EthereumRequest(
4
+ override var id: String = TimeStampGenerator.timestamp(),
5
+ override val method: String,
6
+ override val params: Any? = null
7
+ ) : RpcRequest()
@@ -0,0 +1,7 @@
1
+ package io.metamask.androidsdk
2
+
3
+ data class EthereumState(
4
+ val chainId: String,
5
+ val sessionId: String,
6
+ val selectedAddress: String
7
+ )
@@ -0,0 +1,77 @@
1
+ package io.metamask.androidsdk
2
+
3
+ import io.metamask.androidsdk.KeyExchangeMessageType.*
4
+
5
+ data class KeyExchangeMessage(
6
+ val type: String,
7
+ val publicKey: String?
8
+ )
9
+
10
+ class KeyExchange(private val crypto: Encryption = Crypto(), private val logger: Logger = DefaultLogger) {
11
+ companion object {
12
+ const val TYPE = "type"
13
+ const val PUBLIC_KEY = "public_key"
14
+ }
15
+
16
+ private var privateKey: String? = null
17
+ var publicKey: String? = null
18
+ private var theirPublicKey: String? = null
19
+ private var isKeysExchanged = false
20
+
21
+ init {
22
+ reset()
23
+ crypto.onInitialized = {
24
+ reset()
25
+ }
26
+ }
27
+
28
+ private fun setIsKeysExchanged(newValue: Boolean) {
29
+ synchronized(this) {
30
+ isKeysExchanged = newValue
31
+ }
32
+ }
33
+
34
+ fun keysExchanged(): Boolean {
35
+ synchronized(this) {
36
+ return isKeysExchanged
37
+ }
38
+ }
39
+
40
+ fun reset() {
41
+ privateKey = crypto.generatePrivateKey()
42
+ privateKey?.let {
43
+ publicKey = crypto.publicKey(it)
44
+ }
45
+ setIsKeysExchanged(false)
46
+ theirPublicKey = null
47
+ }
48
+
49
+ fun encrypt(message: String): String {
50
+ val key: String = theirPublicKey ?: throw NullPointerException("theirPublicKey is null")
51
+ return crypto.encrypt(key, message)
52
+ }
53
+
54
+ fun decrypt(message: String): String {
55
+ val key: String = privateKey ?: throw NullPointerException("privateKey is null")
56
+ return crypto.decrypt(key, message)
57
+ }
58
+
59
+ fun complete() {
60
+ logger.log("KeyExchange:: Key exchange complete")
61
+ setIsKeysExchanged(true)
62
+ }
63
+
64
+ fun nextKeyExchangeMessage(current: KeyExchangeMessage): KeyExchangeMessage? {
65
+ current.publicKey?.let {
66
+ theirPublicKey = it
67
+ }
68
+
69
+ return when(current.type) {
70
+ KEY_HANDSHAKE_START.name -> KeyExchangeMessage(KEY_HANDSHAKE_SYN.name, publicKey)
71
+ KEY_HANDSHAKE_SYN.name -> KeyExchangeMessage(KEY_HANDSHAKE_SYNACK.name, publicKey)
72
+ KEY_HANDSHAKE_SYNACK.name -> KeyExchangeMessage(KEY_HANDSHAKE_ACK.name, publicKey)
73
+ KEY_HANDSHAKE_ACK.name -> null
74
+ else -> null
75
+ }
76
+ }
77
+ }
@@ -0,0 +1,20 @@
1
+ package io.metamask.androidsdk
2
+
3
+ import kotlinx.serialization.SerialName
4
+ import kotlinx.serialization.Serializable
5
+
6
+ @Serializable
7
+ enum class KeyExchangeMessageType {
8
+ @SerialName("none")
9
+ NONE,
10
+ @SerialName("key_handshake_start")
11
+ KEY_HANDSHAKE_START,
12
+ @SerialName("key_handshake_check")
13
+ KEY_HANDSHAKE_CHECK,
14
+ @SerialName("key_exchange_SYN")
15
+ KEY_HANDSHAKE_SYN,
16
+ @SerialName("key_exchange_SYNACK")
17
+ KEY_HANDSHAKE_SYNACK,
18
+ @SerialName("key_exchange_ACK")
19
+ KEY_HANDSHAKE_ACK
20
+ }