@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
package/NitroMetamask.podspec
CHANGED
|
@@ -14,19 +14,28 @@ Pod::Spec.new do |s|
|
|
|
14
14
|
s.source = { :git => "https://github.com/novastera/nitro-metamask.git", :tag => "#{s.version}" }
|
|
15
15
|
|
|
16
16
|
s.source_files = [
|
|
17
|
-
# Implementation (Swift)
|
|
18
|
-
"ios
|
|
17
|
+
# Implementation (Swift) — exclude test directories
|
|
18
|
+
"ios/*.{swift}",
|
|
19
|
+
# Vendored MetaMask iOS SDK source
|
|
20
|
+
"ios/metamask-ios-sdk/**/*.{swift}",
|
|
19
21
|
# Autolinking/Registration (Objective-C++)
|
|
20
22
|
"ios/**/*.{m,mm}",
|
|
21
23
|
# Implementation (C++ objects)
|
|
22
24
|
"cpp/**/*.{hpp,cpp}",
|
|
23
25
|
]
|
|
24
26
|
|
|
27
|
+
s.vendored_frameworks = 'ios/Frameworks/Ecies.xcframework'
|
|
28
|
+
|
|
29
|
+
s.frameworks = 'UIKit', 'SwiftUI', 'Combine'
|
|
30
|
+
|
|
31
|
+
s.pod_target_xcconfig = {
|
|
32
|
+
'FRAMEWORK_SEARCH_PATHS' => '$(inherited) "$(PODS_XCFRAMEWORKS_BUILD_DIR)/NitroMetamask"'
|
|
33
|
+
}
|
|
34
|
+
|
|
25
35
|
load 'nitrogen/generated/ios/NitroMetamask+autolinking.rb'
|
|
26
36
|
add_nitrogen_files(s)
|
|
27
37
|
|
|
28
38
|
s.dependency 'React-jsi'
|
|
29
39
|
s.dependency 'React-callinvoker'
|
|
30
|
-
s.dependency 'metamask-ios-sdk', '~> 0.8.10'
|
|
31
40
|
install_modules_dependencies(s)
|
|
32
41
|
end
|
package/README.md
CHANGED
|
@@ -63,9 +63,11 @@ No extra Android configuration is required for consumers of this package.
|
|
|
63
63
|
For app developers, the expected upgrade path is:
|
|
64
64
|
|
|
65
65
|
1. Update to the new npm version.
|
|
66
|
-
2. Clean install + clean Android build.
|
|
66
|
+
2. Clean install + clean Android build (if you ever hit CMake / `react_codegen_*` / missing `codegen/jni` errors for this package, delete `android/app/.cxx` and `android/app/build`, then rebuild so autolinking regenerates).
|
|
67
67
|
3. Rebuild release AAB/APK.
|
|
68
68
|
|
|
69
|
+
**New Architecture:** this library is intended for apps with `newArchEnabled=true` (Nitro + Nitrogen); it does not register React Native’s Codegen `react_codegen_*` target.
|
|
70
|
+
|
|
69
71
|
No manual Gradle dependency override should be needed in the consuming app.
|
|
70
72
|
|
|
71
73
|
### iOS Configuration
|
package/android/build.gradle
CHANGED
|
@@ -18,15 +18,14 @@ def isNewArchitectureEnabled() {
|
|
|
18
18
|
return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
// Nitro hybrid module: native bindings come from nitrogen/generated (NitroMetamask+autolinking.gradle + CMake).
|
|
22
|
+
// Do not apply com.facebook.react / react { libraryName } here — that registers RN New-Arch Codegen (react_codegen_*)
|
|
23
|
+
// and breaks consumers (CMake expects android/build/generated/source/codegen/jni/ which this package never ships).
|
|
21
24
|
apply plugin: "com.android.library"
|
|
22
25
|
apply plugin: 'org.jetbrains.kotlin.android'
|
|
23
26
|
apply from: '../nitrogen/generated/android/NitroMetamask+autolinking.gradle'
|
|
24
27
|
apply from: "./fix-prefab.gradle"
|
|
25
28
|
|
|
26
|
-
if (isNewArchitectureEnabled()) {
|
|
27
|
-
apply plugin: "com.facebook.react"
|
|
28
|
-
}
|
|
29
|
-
|
|
30
29
|
def getExtOrDefault(name) {
|
|
31
30
|
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["NitroMetamask_" + name]
|
|
32
31
|
}
|
|
@@ -98,6 +97,7 @@ android {
|
|
|
98
97
|
buildFeatures {
|
|
99
98
|
buildConfig true
|
|
100
99
|
prefab true
|
|
100
|
+
aidl true
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
buildTypes {
|
|
@@ -117,13 +117,10 @@ android {
|
|
|
117
117
|
|
|
118
118
|
sourceSets {
|
|
119
119
|
main {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
"${project.buildDir}/generated/source/codegen/java"
|
|
125
|
-
]
|
|
126
|
-
}
|
|
120
|
+
// Local/CI build: libecies.so built from rust/ecies-jni via cargoNdkBuildEcies
|
|
121
|
+
// npm consumers: libecies.so shipped as prebuilt in android/src/main/jniLibs
|
|
122
|
+
jniLibs.srcDirs += ["${buildDir}/intermediates/rust-ecies-jni", "src/main/jniLibs"]
|
|
123
|
+
aidl.srcDirs += ["src/main/aidl"]
|
|
127
124
|
}
|
|
128
125
|
}
|
|
129
126
|
}
|
|
@@ -133,13 +130,6 @@ repositories {
|
|
|
133
130
|
google()
|
|
134
131
|
}
|
|
135
132
|
|
|
136
|
-
// Hard block old prebuilt ECIES .so from any transitive edge.
|
|
137
|
-
// NitroMetamask always provides libecies.so from rust/ecies-jni (16 KB page-size compatible).
|
|
138
|
-
configurations.configureEach {
|
|
139
|
-
exclude group: "io.metamask.ecies", module: "ecies"
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
|
|
143
133
|
dependencies {
|
|
144
134
|
// For < 0.71, this will be from the local maven repo
|
|
145
135
|
// For > 0.71, this will be replaced by `com.facebook.react:react-android:$version` by react gradle plugin
|
|
@@ -153,19 +143,11 @@ dependencies {
|
|
|
153
143
|
// Required for Promise.async with coroutines in Nitro modules
|
|
154
144
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.2"
|
|
155
145
|
|
|
156
|
-
//
|
|
157
|
-
|
|
158
|
-
implementation("io.metamask.androidsdk:metamask-android-sdk:0.6.6") {
|
|
159
|
-
exclude group: "io.metamask.ecies", module: "ecies"
|
|
160
|
-
}
|
|
161
|
-
}
|
|
146
|
+
// Gson — used by vendored MetaMask SDK source for JSON serialization
|
|
147
|
+
implementation "com.google.code.gson:gson:2.11.0"
|
|
162
148
|
|
|
163
|
-
|
|
149
|
+
// ProcessLifecycleOwner — required for user cancellation detection (Requirement 11.4)
|
|
150
|
+
implementation "androidx.lifecycle:lifecycle-process:2.10.0"
|
|
151
|
+
}
|
|
164
152
|
|
|
165
|
-
|
|
166
|
-
react {
|
|
167
|
-
jsRootDir = file("../src/")
|
|
168
|
-
libraryName = "NitroMetamask"
|
|
169
|
-
codegenJavaPackageName = "com.margelo.nitro.nitrometamask"
|
|
170
|
-
}
|
|
171
|
-
}
|
|
153
|
+
apply from: "./cargo-ecies.gradle"
|
|
@@ -1,123 +1,95 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def path = System.getenv("PATH") ?: ""
|
|
12
|
-
if (path.isEmpty()) {
|
|
13
|
-
return false
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
def names = ["cargo"]
|
|
17
|
-
def isWindows = (System.getProperty("os.name") ?: "").toLowerCase().contains("win")
|
|
18
|
-
if (isWindows) {
|
|
19
|
-
names = ["cargo.exe", "cargo.bat", "cargo.cmd"]
|
|
20
|
-
}
|
|
1
|
+
// ─── ECIES native library build ───────────────────────────────────────────────
|
|
2
|
+
//
|
|
3
|
+
// Strategy:
|
|
4
|
+
// Local dev → builds only the host ABI (fast, no cross-compilation needed)
|
|
5
|
+
// CI publish → builds all 4 ABIs via -PreactNativeArchitectures=arm64-v8a,...
|
|
6
|
+
//
|
|
7
|
+
// The built .so files land in:
|
|
8
|
+
// android/build/intermediates/rust-ecies-jni/<abi>/libecies.so
|
|
9
|
+
// which is already on jniLibs.srcDirs in build.gradle.
|
|
10
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
21
11
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
if (candidate.isFile() && candidate.canExecute()) {
|
|
26
|
-
return true
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
}
|
|
12
|
+
def eciesRustDir = file("${projectDir}/../rust/ecies-jni")
|
|
13
|
+
def eciesCargoToml = file("${projectDir}/../rust/ecies-jni/Cargo.toml")
|
|
14
|
+
def eciesJniOut = file("${buildDir}/intermediates/rust-ecies-jni")
|
|
30
15
|
|
|
31
|
-
|
|
16
|
+
// Detect host ABI for local builds
|
|
17
|
+
def hostAbi = { ->
|
|
18
|
+
def arch = System.getProperty("os.arch") ?: ""
|
|
19
|
+
if (arch.contains("aarch64") || arch.contains("arm64")) return "arm64-v8a"
|
|
20
|
+
if (arch.contains("x86_64") || arch.contains("amd64")) return "x86_64"
|
|
21
|
+
return "arm64-v8a" // safe default for Apple Silicon Macs
|
|
32
22
|
}
|
|
33
23
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
return [available: missing.isEmpty(), missing: missing]
|
|
24
|
+
// Resolve which ABIs to build:
|
|
25
|
+
// - If -PreactNativeArchitectures is set (CI), use that list
|
|
26
|
+
// - Otherwise build only the host ABI (local dev)
|
|
27
|
+
def resolveAbis = { ->
|
|
28
|
+
def explicit = rootProject.getProperties().get("reactNativeArchitectures")
|
|
29
|
+
if (explicit) return explicit.split(",").toList()
|
|
30
|
+
return [hostAbi()]
|
|
43
31
|
}
|
|
44
32
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
def
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
)
|
|
59
|
-
}
|
|
33
|
+
// Find cargo/cargo-ndk — searches PATH then ~/.cargo/bin (rustup default)
|
|
34
|
+
def findBin = { String name ->
|
|
35
|
+
def isWindows = (System.getProperty("os.name") ?: "").toLowerCase().contains("win")
|
|
36
|
+
def exeName = isWindows ? "${name}.exe" : name
|
|
37
|
+
def path = System.getenv("PATH") ?: ""
|
|
38
|
+
for (dir in path.split(java.io.File.pathSeparator)) {
|
|
39
|
+
def f = new File(dir, exeName)
|
|
40
|
+
if (f.isFile() && f.canExecute()) return f.absolutePath
|
|
41
|
+
}
|
|
42
|
+
def home = System.getProperty("user.home") ?: System.getenv("HOME") ?: ""
|
|
43
|
+
if (home) {
|
|
44
|
+
def f = new File("${home}/.cargo/bin/${exeName}")
|
|
45
|
+
if (f.isFile() && f.canExecute()) return f.absolutePath
|
|
60
46
|
}
|
|
47
|
+
return null
|
|
61
48
|
}
|
|
62
49
|
|
|
63
50
|
tasks.register("cargoNdkBuildEcies", Exec) {
|
|
64
|
-
group
|
|
65
|
-
description = "Build libecies.so
|
|
66
|
-
onlyIf
|
|
67
|
-
|
|
68
|
-
(buildEciesFromSource || !prebuiltStatus.available) &&
|
|
69
|
-
eciesRustDir.isDirectory() && eciesCargoToml.isFile()
|
|
70
|
-
}
|
|
71
|
-
workingDir eciesRustDir
|
|
51
|
+
group = "build"
|
|
52
|
+
description = "Build libecies.so from Rust source (local: host ABI only, CI: all ABIs)"
|
|
53
|
+
onlyIf { eciesRustDir.isDirectory() && eciesCargoToml.isFile() }
|
|
54
|
+
workingDir eciesRustDir
|
|
72
55
|
standardOutput System.out
|
|
73
|
-
errorOutput
|
|
56
|
+
errorOutput System.err
|
|
74
57
|
outputs.dir(eciesJniOut)
|
|
75
58
|
}
|
|
76
59
|
|
|
77
|
-
// Resolve NDK + ABIs at configuration time (afterEvaluate). Task actions must NOT touch
|
|
78
|
-
// Task.project — configuration cache forbids it (see Gradle 9 config cache requirements).
|
|
79
60
|
afterEvaluate {
|
|
80
61
|
def ndkProp = project.android.ndkDirectory
|
|
81
|
-
def ndkDir
|
|
62
|
+
def ndkDir = (ndkProp instanceof File) ? ndkProp : ndkProp.get().asFile
|
|
82
63
|
def ndkPath = ndkDir.absolutePath
|
|
83
|
-
def ndkVer
|
|
84
|
-
def abis
|
|
64
|
+
def ndkVer = project.android.ndkVersion
|
|
65
|
+
def abis = resolveAbis()
|
|
85
66
|
def outPath = eciesJniOut.absolutePath
|
|
67
|
+
|
|
86
68
|
tasks.named("cargoNdkBuildEcies", Exec).configure {
|
|
87
69
|
doFirst {
|
|
88
|
-
|
|
70
|
+
def cargo = findBin("cargo")
|
|
71
|
+
if (!cargo) {
|
|
89
72
|
throw new GradleException(
|
|
90
|
-
"
|
|
91
|
-
"
|
|
73
|
+
"cargo not found. Install Rust: https://rustup.rs\n" +
|
|
74
|
+
"Then run: cargo install cargo-ndk --locked && rustup target add aarch64-linux-android armv7-linux-androideabi x86_64-linux-android i686-linux-android"
|
|
92
75
|
)
|
|
93
76
|
}
|
|
94
|
-
|
|
95
|
-
eciesJniOut.mkdirs()
|
|
96
77
|
if (!ndkDir.exists()) {
|
|
97
|
-
throw new GradleException("
|
|
78
|
+
throw new GradleException("Android NDK not found (ndkVersion=${ndkVer}). Install it via Android Studio SDK Manager.")
|
|
98
79
|
}
|
|
80
|
+
eciesJniOut.mkdirs()
|
|
99
81
|
environment "ANDROID_NDK_HOME", ndkPath
|
|
100
82
|
environment "ANDROID_NDK_ROOT", ndkPath
|
|
101
83
|
environment "RUSTFLAGS", "-C link-arg=-Wl,-z,max-page-size=16384"
|
|
102
|
-
def args = [
|
|
103
|
-
abis.each { abi ->
|
|
104
|
-
|
|
105
|
-
args.add(abi)
|
|
106
|
-
}
|
|
107
|
-
args.addAll(["-o", outPath, "build", "--release"])
|
|
84
|
+
def args = [cargo, "ndk"]
|
|
85
|
+
abis.each { abi -> args += ["-t", abi] }
|
|
86
|
+
args += ["-o", outPath, "build", "--release"]
|
|
108
87
|
commandLine args
|
|
88
|
+
logger.lifecycle("Building libecies.so for ABIs: ${abis.join(', ')}")
|
|
109
89
|
}
|
|
110
90
|
}
|
|
111
91
|
|
|
112
|
-
def prebuiltStatus = hasPrebuiltEciesForConfiguredAbis()
|
|
113
|
-
def canBuildFromSource = hasCargoExecutable() && eciesRustDir.isDirectory() && eciesCargoToml.isFile()
|
|
114
|
-
def shouldBuildFromSource = buildEciesFromSource || (!prebuiltStatus.available && canBuildFromSource)
|
|
115
|
-
|
|
116
92
|
tasks.matching { it.name.startsWith("preBuild") }.configureEach {
|
|
117
|
-
|
|
118
|
-
dependsOn("cargoNdkBuildEcies")
|
|
119
|
-
} else {
|
|
120
|
-
dependsOn("verifyPrebuiltEciesLibs")
|
|
121
|
-
}
|
|
93
|
+
dependsOn("cargoNdkBuildEcies")
|
|
122
94
|
}
|
|
123
95
|
}
|
|
@@ -3,7 +3,12 @@ package com.margelo.nitro.nitrometamask
|
|
|
3
3
|
import android.content.Intent
|
|
4
4
|
import android.content.pm.PackageManager
|
|
5
5
|
import android.net.Uri
|
|
6
|
+
import android.os.Handler
|
|
7
|
+
import android.os.Looper
|
|
6
8
|
import android.util.Log
|
|
9
|
+
import androidx.lifecycle.DefaultLifecycleObserver
|
|
10
|
+
import androidx.lifecycle.LifecycleOwner
|
|
11
|
+
import androidx.lifecycle.ProcessLifecycleOwner
|
|
7
12
|
import com.margelo.nitro.core.Promise
|
|
8
13
|
import com.margelo.nitro.core.NullType
|
|
9
14
|
import com.margelo.nitro.nitrometamask.HybridNitroMetamaskSpec
|
|
@@ -16,8 +21,10 @@ import io.metamask.androidsdk.Result
|
|
|
16
21
|
import io.metamask.androidsdk.DappMetadata
|
|
17
22
|
import io.metamask.androidsdk.SDKOptions
|
|
18
23
|
import io.metamask.androidsdk.EthereumRequest
|
|
24
|
+
import kotlinx.coroutines.CancellableContinuation
|
|
19
25
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
|
20
26
|
import kotlin.coroutines.resume
|
|
27
|
+
import kotlin.coroutines.resumeWithException
|
|
21
28
|
|
|
22
29
|
class HybridNitroMetamask : HybridNitroMetamaskSpec() {
|
|
23
30
|
// Configurable dapp URL - defaults to novastera.com if not set
|
|
@@ -40,7 +47,41 @@ class HybridNitroMetamask : HybridNitroMetamaskSpec() {
|
|
|
40
47
|
// Cache the detected deep link scheme to avoid repeated detection
|
|
41
48
|
@Volatile
|
|
42
49
|
private var cachedDeepLinkScheme: String? = null
|
|
43
|
-
|
|
50
|
+
|
|
51
|
+
// Pending operation cancellation handler — set when an operation is in-flight,
|
|
52
|
+
// cleared when the SDK callback arrives or when the foreground observer fires.
|
|
53
|
+
@Volatile
|
|
54
|
+
private var pendingOperationCancellation: (() -> Unit)? = null
|
|
55
|
+
|
|
56
|
+
init {
|
|
57
|
+
// addObserver must be called on the main thread — post to main looper
|
|
58
|
+
// since Nitro constructs hybrid objects on the JS thread.
|
|
59
|
+
Handler(Looper.getMainLooper()).post {
|
|
60
|
+
ProcessLifecycleOwner.get().lifecycle.addObserver(object : DefaultLifecycleObserver {
|
|
61
|
+
override fun onStart(owner: LifecycleOwner) {
|
|
62
|
+
checkAndCancelPendingOperation()
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Atomically clears the pending cancellation handler and invokes it.
|
|
70
|
+
* The double-checked pattern ensures only one caller (SDK callback vs. lifecycle event)
|
|
71
|
+
* wins the race (Requirement 11.3).
|
|
72
|
+
*/
|
|
73
|
+
private fun checkAndCancelPendingOperation() {
|
|
74
|
+
val cancel = pendingOperationCancellation
|
|
75
|
+
if (cancel != null) {
|
|
76
|
+
synchronized(this) {
|
|
77
|
+
if (pendingOperationCancellation != null) {
|
|
78
|
+
pendingOperationCancellation = null
|
|
79
|
+
cancel()
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
44
85
|
// Get or create Ethereum SDK instance
|
|
45
86
|
// Important: DappMetadata.url must be a valid HTTP/HTTPS URL (not a deep link scheme)
|
|
46
87
|
// The SDK automatically detects and uses the deep link from AndroidManifest.xml
|
|
@@ -112,6 +153,21 @@ class HybridNitroMetamask : HybridNitroMetamaskSpec() {
|
|
|
112
153
|
}
|
|
113
154
|
}
|
|
114
155
|
|
|
156
|
+
/**
|
|
157
|
+
* Check if MetaMask is installed on the device.
|
|
158
|
+
* Queries PackageManager for the io.metamask package.
|
|
159
|
+
* Returns true if found, false otherwise.
|
|
160
|
+
*/
|
|
161
|
+
private fun isMetaMaskInstalled(): Boolean {
|
|
162
|
+
return try {
|
|
163
|
+
val context = MetamaskContextHolder.get()
|
|
164
|
+
context.packageManager.getPackageInfo("io.metamask", 0)
|
|
165
|
+
true
|
|
166
|
+
} catch (e: PackageManager.NameNotFoundException) {
|
|
167
|
+
false
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
115
171
|
/**
|
|
116
172
|
* Get the deep link scheme - uses configured value first, then attempts auto-detection.
|
|
117
173
|
* Directly reads intent filters from PackageManager to find the scheme with host="mmsdk"
|
|
@@ -248,15 +304,29 @@ class HybridNitroMetamask : HybridNitroMetamaskSpec() {
|
|
|
248
304
|
// Use Promise.async with coroutines for best practice in Nitro modules
|
|
249
305
|
// Reference: https://nitro.margelo.com/docs/types/promises
|
|
250
306
|
return Promise.async {
|
|
307
|
+
if (!isMetaMaskInstalled()) {
|
|
308
|
+
throw Exception("[2] MetaMask is not installed")
|
|
309
|
+
}
|
|
251
310
|
// Convert callback-based connect() to suspend function using suspendCancellableCoroutine
|
|
252
311
|
// This handles cancellation properly when JS GC disposes the promise
|
|
312
|
+
var localContinuation: CancellableContinuation<Result>? = null
|
|
313
|
+
pendingOperationCancellation = {
|
|
314
|
+
localContinuation?.let { cont ->
|
|
315
|
+
if (cont.isActive) cont.resumeWithException(
|
|
316
|
+
Exception("MetaMask operation cancelled: user returned to app without completing the request")
|
|
317
|
+
)
|
|
318
|
+
}
|
|
319
|
+
}
|
|
253
320
|
val result = suspendCancellableCoroutine<Result> { continuation ->
|
|
321
|
+
localContinuation = continuation
|
|
254
322
|
ethereum.connect { callbackResult ->
|
|
323
|
+
pendingOperationCancellation = null
|
|
255
324
|
if (continuation.isActive) {
|
|
256
325
|
continuation.resume(callbackResult)
|
|
257
326
|
}
|
|
258
327
|
}
|
|
259
328
|
}
|
|
329
|
+
pendingOperationCancellation = null
|
|
260
330
|
|
|
261
331
|
when (result) {
|
|
262
332
|
is Result.Success.Item -> {
|
|
@@ -304,6 +374,9 @@ class HybridNitroMetamask : HybridNitroMetamaskSpec() {
|
|
|
304
374
|
// Use Promise.async with coroutines for best practice in Nitro modules
|
|
305
375
|
// Reference: https://nitro.margelo.com/docs/types/promises
|
|
306
376
|
return Promise.async {
|
|
377
|
+
if (!isMetaMaskInstalled()) {
|
|
378
|
+
throw Exception("[2] MetaMask is not installed")
|
|
379
|
+
}
|
|
307
380
|
// Verify connection state before attempting to sign
|
|
308
381
|
// MetaMask SDK requires an active connection to sign messages
|
|
309
382
|
val address = ethereum.selectedAddress
|
|
@@ -322,13 +395,24 @@ class HybridNitroMetamask : HybridNitroMetamaskSpec() {
|
|
|
322
395
|
|
|
323
396
|
// Convert callback-based sendRequest() to suspend function
|
|
324
397
|
// The SDK will automatically handle deep link return to the app
|
|
398
|
+
var localContinuation: CancellableContinuation<Result>? = null
|
|
399
|
+
pendingOperationCancellation = {
|
|
400
|
+
localContinuation?.let { cont ->
|
|
401
|
+
if (cont.isActive) cont.resumeWithException(
|
|
402
|
+
Exception("MetaMask operation cancelled: user returned to app without completing the request")
|
|
403
|
+
)
|
|
404
|
+
}
|
|
405
|
+
}
|
|
325
406
|
val result = suspendCancellableCoroutine<Result> { continuation ->
|
|
407
|
+
localContinuation = continuation
|
|
326
408
|
ethereum.sendRequest(request) { callbackResult ->
|
|
409
|
+
pendingOperationCancellation = null
|
|
327
410
|
if (continuation.isActive) {
|
|
328
411
|
continuation.resume(callbackResult)
|
|
329
412
|
}
|
|
330
413
|
}
|
|
331
414
|
}
|
|
415
|
+
pendingOperationCancellation = null
|
|
332
416
|
|
|
333
417
|
when (result) {
|
|
334
418
|
is Result.Success.Item -> {
|
|
@@ -368,6 +452,9 @@ class HybridNitroMetamask : HybridNitroMetamaskSpec() {
|
|
|
368
452
|
// The SDK's connectSign method handles connection and signing in one call
|
|
369
453
|
return Promise.async {
|
|
370
454
|
try {
|
|
455
|
+
if (!isMetaMaskInstalled()) {
|
|
456
|
+
throw Exception("[2] MetaMask is not installed")
|
|
457
|
+
}
|
|
371
458
|
// Construct JSON message with only nonce and exp
|
|
372
459
|
// We don't include address or chainID - just encrypt nonce and exp
|
|
373
460
|
val message = org.json.JSONObject().apply {
|
|
@@ -380,9 +467,19 @@ class HybridNitroMetamask : HybridNitroMetamaskSpec() {
|
|
|
380
467
|
// Use the SDK's connectSign method - it will connect if needed and sign the message
|
|
381
468
|
// This is the recommended approach per MetaMask Android SDK documentation
|
|
382
469
|
// The SDK will handle bringing the app back to foreground via deep linking
|
|
470
|
+
var localContinuation: CancellableContinuation<Result>? = null
|
|
471
|
+
pendingOperationCancellation = {
|
|
472
|
+
localContinuation?.let { cont ->
|
|
473
|
+
if (cont.isActive) cont.resumeWithException(
|
|
474
|
+
Exception("MetaMask operation cancelled: user returned to app without completing the request")
|
|
475
|
+
)
|
|
476
|
+
}
|
|
477
|
+
}
|
|
383
478
|
val result = suspendCancellableCoroutine<Result> { continuation ->
|
|
479
|
+
localContinuation = continuation
|
|
384
480
|
Log.d("NitroMetamask", "connectSign: Calling ethereum.connectSign with message")
|
|
385
481
|
ethereum.connectSign(message) { callbackResult ->
|
|
482
|
+
pendingOperationCancellation = null
|
|
386
483
|
Log.d("NitroMetamask", "connectSign: Received callback result: ${callbackResult.javaClass.simpleName}")
|
|
387
484
|
if (continuation.isActive) {
|
|
388
485
|
continuation.resume(callbackResult)
|
|
@@ -391,6 +488,7 @@ class HybridNitroMetamask : HybridNitroMetamaskSpec() {
|
|
|
391
488
|
}
|
|
392
489
|
}
|
|
393
490
|
}
|
|
491
|
+
pendingOperationCancellation = null
|
|
394
492
|
|
|
395
493
|
Log.d("NitroMetamask", "connectSign: Processing result")
|
|
396
494
|
when (result) {
|
|
@@ -548,7 +646,7 @@ class HybridNitroMetamask : HybridNitroMetamaskSpec() {
|
|
|
548
646
|
val state = ethereum.ethereumState.value
|
|
549
647
|
val address = state?.selectedAddress?.takeIf { it.isNotEmpty() } ?: ethereum.selectedAddress
|
|
550
648
|
Log.d("NitroMetamask", "getAddress: ethereumState.value?.selectedAddress = ${state?.selectedAddress}, ethereum.selectedAddress = ${ethereum.selectedAddress}, final = $address")
|
|
551
|
-
if (address
|
|
649
|
+
if (address.isEmpty()) {
|
|
552
650
|
Log.w("NitroMetamask", "getAddress: Address is null or empty")
|
|
553
651
|
// Use NullType.NULL singleton as per Nitro documentation: https://nitro.margelo.com/docs/types/nulls
|
|
554
652
|
Variant_NullType_String.First(NullType.NULL)
|
|
@@ -566,7 +664,7 @@ class HybridNitroMetamask : HybridNitroMetamaskSpec() {
|
|
|
566
664
|
val state = ethereum.ethereumState.value
|
|
567
665
|
val chainIdString = state?.chainId?.takeIf { it.isNotEmpty() } ?: ethereum.chainId
|
|
568
666
|
Log.d("NitroMetamask", "getChainId: ethereumState.value?.chainId = ${state?.chainId}, ethereum.chainId = ${ethereum.chainId}, final = $chainIdString")
|
|
569
|
-
if (chainIdString
|
|
667
|
+
if (chainIdString.isEmpty()) {
|
|
570
668
|
Log.w("NitroMetamask", "getChainId: ChainId is null or empty")
|
|
571
669
|
// Use NullType.NULL singleton as per Nitro documentation: https://nitro.margelo.com/docs/types/nulls
|
|
572
670
|
Variant_NullType_Long.First(NullType.NULL)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
package io.metamask.androidsdk
|
|
2
|
+
|
|
3
|
+
import android.os.Bundle
|
|
4
|
+
import io.metamask.nativesdk.IMessegeServiceCallback
|
|
5
|
+
|
|
6
|
+
open class ClientMessageServiceCallback(
|
|
7
|
+
var onMessage: ((Bundle) -> Unit)? = null
|
|
8
|
+
) : IMessegeServiceCallback.Stub() {
|
|
9
|
+
override fun onMessageReceived(bundle: Bundle) {
|
|
10
|
+
onMessage?.invoke(bundle)
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
package io.metamask.androidsdk
|
|
2
|
+
|
|
3
|
+
import android.content.ComponentName
|
|
4
|
+
import android.content.ServiceConnection
|
|
5
|
+
import android.os.Bundle
|
|
6
|
+
import android.os.IBinder
|
|
7
|
+
import io.metamask.nativesdk.IMessegeService
|
|
8
|
+
import io.metamask.nativesdk.IMessegeServiceCallback
|
|
9
|
+
|
|
10
|
+
open class ClientServiceConnection(
|
|
11
|
+
var onConnected: (() -> Unit)? = null,
|
|
12
|
+
var onDisconnected: ((ComponentName?) -> Unit)? = null,
|
|
13
|
+
var onBindingDied: ((ComponentName?) -> Unit)? = null,
|
|
14
|
+
var onNullBinding: ((ComponentName?) -> Unit)? = null
|
|
15
|
+
) : ServiceConnection {
|
|
16
|
+
private var messageService: IMessegeService? = null
|
|
17
|
+
|
|
18
|
+
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
|
19
|
+
messageService = IMessegeService.Stub.asInterface(service)
|
|
20
|
+
onConnected?.invoke()
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
override fun onServiceDisconnected(name: ComponentName?) {
|
|
24
|
+
onDisconnected?.invoke(name)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
override fun onBindingDied(name: ComponentName?) {
|
|
28
|
+
onBindingDied?.invoke(name)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
override fun onNullBinding(name: ComponentName?) {
|
|
32
|
+
onNullBinding?.invoke(name)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
open fun registerCallback(callback: IMessegeServiceCallback) {
|
|
36
|
+
messageService?.registerCallback(callback)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
open fun sendMessage(bundle: Bundle) {
|
|
40
|
+
messageService?.sendMessage(bundle)
|
|
41
|
+
}
|
|
42
|
+
}
|