@novastera-oss/nitro-metamask 0.6.3 → 0.7.0
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/android/build.gradle +10 -13
- 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/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/android/build.gradle
CHANGED
|
@@ -98,6 +98,7 @@ android {
|
|
|
98
98
|
buildFeatures {
|
|
99
99
|
buildConfig true
|
|
100
100
|
prefab true
|
|
101
|
+
aidl true
|
|
101
102
|
}
|
|
102
103
|
|
|
103
104
|
buildTypes {
|
|
@@ -117,7 +118,10 @@ android {
|
|
|
117
118
|
|
|
118
119
|
sourceSets {
|
|
119
120
|
main {
|
|
120
|
-
|
|
121
|
+
// Local/CI build: libecies.so built from rust/ecies-jni via cargoNdkBuildEcies
|
|
122
|
+
// npm consumers: libecies.so shipped as prebuilt in android/src/main/jniLibs
|
|
123
|
+
jniLibs.srcDirs += ["${buildDir}/intermediates/rust-ecies-jni", "src/main/jniLibs"]
|
|
124
|
+
aidl.srcDirs += ["src/main/aidl"]
|
|
121
125
|
if (isNewArchitectureEnabled()) {
|
|
122
126
|
java.srcDirs += [
|
|
123
127
|
// React Codegen files
|
|
@@ -133,13 +137,6 @@ repositories {
|
|
|
133
137
|
google()
|
|
134
138
|
}
|
|
135
139
|
|
|
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
140
|
dependencies {
|
|
144
141
|
// For < 0.71, this will be from the local maven repo
|
|
145
142
|
// For > 0.71, this will be replaced by `com.facebook.react:react-android:$version` by react gradle plugin
|
|
@@ -153,11 +150,11 @@ dependencies {
|
|
|
153
150
|
// Required for Promise.async with coroutines in Nitro modules
|
|
154
151
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.2"
|
|
155
152
|
|
|
156
|
-
//
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
153
|
+
// Gson — used by vendored MetaMask SDK source for JSON serialization
|
|
154
|
+
implementation "com.google.code.gson:gson:2.11.0"
|
|
155
|
+
|
|
156
|
+
// ProcessLifecycleOwner — required for user cancellation detection (Requirement 11.4)
|
|
157
|
+
implementation "androidx.lifecycle:lifecycle-process:2.10.0"
|
|
161
158
|
}
|
|
162
159
|
|
|
163
160
|
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
|
+
}
|