@novastera-oss/nitro-metamask 0.2.1 → 0.2.3

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.
@@ -1,46 +1,92 @@
1
1
  package com.margelo.nitro.nitrometamask
2
2
 
3
- import io.metamask.androidsdk.ConnectOptions
4
- import io.metamask.androidsdk.EthereumClient
5
- import io.metamask.androidsdk.Request
6
- import io.metamask.androidsdk.RequestResult
3
+ import com.margelo.nitro.Promise
4
+ import io.metamask.androidsdk.Ethereum
5
+ import io.metamask.androidsdk.Result
6
+ import io.metamask.androidsdk.DappMetadata
7
+ import io.metamask.androidsdk.SDKOptions
8
+ import com.facebook.react.bridge.ReactApplicationContext
9
+ import kotlinx.coroutines.suspendCoroutine
7
10
 
8
11
  class HybridMetamaskConnector : HybridMetamaskConnectorSpec() {
9
- private val client by lazy { EthereumClient.getInstance() }
10
-
11
- override suspend fun connect(): ConnectResult {
12
- val response = client.connect(ConnectOptions())
13
- val address = response.accounts.firstOrNull()
14
- ?: throw IllegalStateException("MetaMask SDK returned no accounts")
15
-
16
- return ConnectResult(
17
- address = address,
18
- chainId = response.chainId.toString()
19
- )
20
- }
21
-
22
- override suspend fun signMessage(message: String): String {
23
- val address = client.getSelectedAddress()
24
- ?: throw IllegalStateException("No connected account. Call connect() first.")
12
+ companion object {
13
+ @Volatile
14
+ private var reactContext: ReactApplicationContext? = null
25
15
 
26
- // Convert message to hex-encoded format for personal_sign
27
- // personal_sign expects: personal_sign(messageHex, address)
28
- // where messageHex is "0x" + hex-encoded UTF-8 bytes
29
- val messageBytes = message.toByteArray(Charsets.UTF_8)
30
- val messageHex = "0x" + messageBytes.joinToString("") { "%02x".format(it) }
16
+ fun setReactContext(context: ReactApplicationContext) {
17
+ reactContext = context
18
+ }
19
+ }
20
+
21
+ // Initialize Ethereum SDK with Context, DappMetadata, and SDKOptions
22
+ // Based on: https://github.com/MetaMask/metamask-android-sdk
23
+ // The SDK requires a Context for initialization
24
+ private val ethereum: Ethereum by lazy {
25
+ val context = reactContext?.applicationContext
26
+ ?: throw IllegalStateException("ReactApplicationContext not initialized. Make sure NitroMetamaskPackage is properly registered.")
31
27
 
32
- val request = Request(
33
- method = "personal_sign",
34
- params = listOf(messageHex, address)
28
+ val dappMetadata = DappMetadata(
29
+ name = "Nitro MetaMask Connector",
30
+ url = "https://novastera.com"
35
31
  )
32
+ val sdkOptions = SDKOptions()
36
33
 
37
- val result = client.sendRequest(request)
38
- return when (result) {
39
- is RequestResult.Success -> {
40
- result.data as? String ?: throw IllegalStateException("Invalid signature response")
34
+ Ethereum(context, dappMetadata, sdkOptions)
35
+ }
36
+
37
+ override fun connect(): Promise<ConnectResult> {
38
+ // Use Promise.async with coroutines for best practice in Nitro modules
39
+ // Reference: https://nitro.margelo.com/docs/types/promises
40
+ return Promise.async {
41
+ // Convert callback-based connect() to suspend function using suspendCoroutine
42
+ val result = suspendCoroutine<Result> { continuation ->
43
+ ethereum.connect { callbackResult ->
44
+ continuation.resume(callbackResult)
45
+ }
46
+ }
47
+
48
+ when (result) {
49
+ is Result.Success.Item -> {
50
+ // After successful connection, get account info from SDK
51
+ val address = ethereum.selectedAddress
52
+ ?: throw IllegalStateException("MetaMask SDK returned no address after connection")
53
+ val chainId = ethereum.chainId
54
+ ?: throw IllegalStateException("MetaMask SDK returned no chainId after connection")
55
+
56
+ ConnectResult(
57
+ address = address,
58
+ chainId = chainId.toString()
59
+ )
60
+ }
61
+ is Result.Error -> {
62
+ throw Exception(result.error.message ?: "Failed to connect to MetaMask")
63
+ }
64
+ else -> {
65
+ throw IllegalStateException("Unexpected result type from MetaMask connect")
66
+ }
41
67
  }
42
- is RequestResult.Error -> {
43
- throw Exception(result.error.message ?: "Failed to sign message")
68
+ }
69
+ }
70
+
71
+ override fun signMessage(message: String): Promise<String> {
72
+ // Use Promise.async with coroutines for best practice in Nitro modules
73
+ // Reference: https://nitro.margelo.com/docs/types/promises
74
+ return Promise.async {
75
+ // Use the convenience method connectSign() which connects and signs in one call
76
+ // Based on SDK docs: ethereum.connectSign(message) returns Result synchronously
77
+ // Reference: https://github.com/MetaMask/metamask-android-sdk
78
+ when (val result = ethereum.connectSign(message)) {
79
+ is Result.Success.Item -> {
80
+ // Extract signature from result
81
+ result.value as? String
82
+ ?: throw IllegalStateException("Invalid signature response format")
83
+ }
84
+ is Result.Error -> {
85
+ throw Exception(result.error.message ?: "Failed to sign message")
86
+ }
87
+ else -> {
88
+ throw IllegalStateException("Unexpected result type from MetaMask signMessage")
89
+ }
44
90
  }
45
91
  }
46
92
  }
@@ -6,7 +6,11 @@ import com.facebook.react.module.model.ReactModuleInfoProvider
6
6
  import com.facebook.react.BaseReactPackage
7
7
 
8
8
  class NitroMetamaskPackage : BaseReactPackage() {
9
- override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? = null
9
+ override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
10
+ // Store the ReactApplicationContext for use in HybridMetamaskConnector
11
+ HybridMetamaskConnector.setReactContext(reactContext)
12
+ return null
13
+ }
10
14
 
11
15
  override fun getReactModuleInfoProvider(): ReactModuleInfoProvider = ReactModuleInfoProvider { HashMap() }
12
16
 
@@ -1,57 +1,52 @@
1
1
  import Foundation
2
2
  import MetaMaskSDK
3
+ import NitroModules
3
4
 
4
5
  class HybridMetamaskConnector: HybridMetamaskConnectorSpec {
5
6
  private let sdk = MetaMaskSDK.shared
6
7
 
7
- func connect() async throws -> ConnectResult {
8
- try await withCheckedThrowingContinuation { continuation in
9
- sdk.connect { response in
10
- switch response {
11
- case .success(let account):
12
- let result = ConnectResult(address: account.address, chainId: "\(account.chainId)")
13
- continuation.resume(returning: result)
14
- case .failure(let error):
15
- continuation.resume(throwing: error)
8
+ func connect() -> Promise<ConnectResult> {
9
+ // Use Promise.async with Swift async/await for best practice in Nitro modules
10
+ // Reference: https://nitro.margelo.com/docs/types/promises
11
+ return Promise.async {
12
+ // Based on MetaMask iOS SDK docs: let connectResult = await metamaskSDK.connect()
13
+ // Reference: https://github.com/MetaMask/metamask-ios-sdk
14
+ let connectResult = try await self.sdk.connect()
15
+
16
+ switch connectResult {
17
+ case .success(let value):
18
+ // After successful connection, get account info from SDK
19
+ // Note: sdk.account is a String (address), not an object
20
+ // Reference: https://raw.githubusercontent.com/MetaMask/metamask-ios-sdk/924d91bb3e98a5383c3082d6d5ba3ddac9e1c565/README.md
21
+ guard let address = self.sdk.account, !address.isEmpty else {
22
+ throw NSError(domain: "MetamaskConnector", code: -1, userInfo: [NSLocalizedDescriptionKey: "MetaMask SDK returned no address after connection"])
16
23
  }
24
+ guard let chainId = self.sdk.chainId, !chainId.isEmpty else {
25
+ throw NSError(domain: "MetamaskConnector", code: -1, userInfo: [NSLocalizedDescriptionKey: "MetaMask SDK returned no chainId after connection"])
26
+ }
27
+ return ConnectResult(address: address, chainId: chainId)
28
+ case .failure(let error):
29
+ throw error
17
30
  }
18
31
  }
19
32
  }
20
33
 
21
- func signMessage(message: String) async throws -> String {
22
- // Get the connected account address
23
- guard let account = sdk.account else {
24
- throw NSError(domain: "MetamaskConnector", code: -1, userInfo: [NSLocalizedDescriptionKey: "No connected account. Call connect() first."])
25
- }
26
-
27
- // Convert message to hex-encoded format for personal_sign
28
- // personal_sign expects: personal_sign(messageHex, address)
29
- // where messageHex is "0x" + hex-encoded UTF-8 bytes
30
- // Reference: https://github.com/MetaMask/metamask-ios-sdk#5-connect-with-request
31
- guard let messageData = message.data(using: .utf8) else {
32
- throw NSError(domain: "MetamaskConnector", code: -1, userInfo: [NSLocalizedDescriptionKey: "Failed to encode message"])
33
- }
34
-
35
- let messageHex = "0x" + messageData.map { String(format: "%02x", $0) }.joined()
36
- let params: [String] = [messageHex, account.address]
37
-
38
- // Create EthereumRequest for personal_sign JSON-RPC method
39
- let request = EthereumRequest(
40
- method: .personalSign,
41
- params: params
42
- )
43
-
44
- // Make the request using the SDK's async request method
45
- let result = try await sdk.request(request)
46
-
47
- // Extract signature from response
48
- // The signature should be a hex-encoded string (0x-prefixed)
49
- if let signature = result as? String {
50
- return signature
51
- } else if let dict = result as? [String: Any], let sig = dict["signature"] as? String ?? dict["result"] as? String {
52
- return sig
53
- } else {
54
- throw NSError(domain: "MetamaskConnector", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid signature response format"])
34
+ func signMessage(message: String) -> Promise<String> {
35
+ // Use Promise.async with Swift async/await for best practice in Nitro modules
36
+ // Reference: https://nitro.margelo.com/docs/types/promises
37
+ return Promise.async {
38
+ // Use the convenience method connectAndSign() which connects and signs in one call
39
+ // This is equivalent to Android's connectSign() method
40
+ // Reference: https://raw.githubusercontent.com/MetaMask/metamask-ios-sdk/924d91bb3e98a5383c3082d6d5ba3ddac9e1c565/README.md
41
+ // Example: https://raw.githubusercontent.com/MetaMask/metamask-ios-sdk/924d91bb3e98a5383c3082d6d5ba3ddac9e1c565/Example/metamask-ios-sdk/SignView.swift
42
+ let connectSignResult = try await self.sdk.connectAndSign(message: message)
43
+
44
+ switch connectSignResult {
45
+ case .success(let signature):
46
+ return signature
47
+ case .failure(let error):
48
+ throw error
49
+ }
55
50
  }
56
51
  }
57
52
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@novastera-oss/nitro-metamask",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "nitro-metamask",
5
5
  "main": "lib/index",
6
6
  "module": "lib/index",