diff --git a/packages/ironfish-native-module/android/build.gradle b/packages/ironfish-native-module/android/build.gradle index 35209cb..2aa8a57 100644 --- a/packages/ironfish-native-module/android/build.gradle +++ b/packages/ironfish-native-module/android/build.gradle @@ -25,7 +25,7 @@ if (useManagedAndroidSdkVersions) { project.android { compileSdkVersion safeExtGet("compileSdkVersion", 34) defaultConfig { - minSdkVersion safeExtGet("minSdkVersion", 21)minSdkVersion safeExtGet("minSdkVersion", 21) + minSdkVersion safeExtGet("minSdkVersion", 21) targetSdkVersion safeExtGet("targetSdkVersion", 34) } } @@ -41,3 +41,7 @@ android { abortOnError false } } + +dependencies { + implementation "net.java.dev.jna:jna:5.14.0@aar" +} diff --git a/packages/ironfish-native-module/android/src/main/java/expo/modules/ironfishnativemodule/IronfishNativeModule.kt b/packages/ironfish-native-module/android/src/main/java/expo/modules/ironfishnativemodule/IronfishNativeModule.kt index dbbf499..c576e1e 100644 --- a/packages/ironfish-native-module/android/src/main/java/expo/modules/ironfishnativemodule/IronfishNativeModule.kt +++ b/packages/ironfish-native-module/android/src/main/java/expo/modules/ironfishnativemodule/IronfishNativeModule.kt @@ -1,10 +1,16 @@ package expo.modules.ironfishnativemodule +import expo.modules.kotlin.Promise +import expo.modules.kotlin.exception.CodedException import expo.modules.kotlin.records.Field import expo.modules.kotlin.modules.Module import expo.modules.kotlin.modules.ModuleDefinition import expo.modules.kotlin.records.Record +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + class ExpoKey( @Field val spendingKey: String, @@ -20,6 +26,46 @@ class ExpoKey( val proofAuthorizingKey: String ) : Record +class ExpoOutput( + @Field + val index: Int, + + @Field + val note: String, + ) : Record + +class SpendComponentsInput( + @Field + val components: List, + ) : Record + +class SpendComponentInput( + @Field + val note: String, + + @Field + val witnessRootHash: String, + + @Field + val witnessTreeSize: String, + + @Field + val witnessAuthPath: List, +) : Record + +class WitnessNodeInput ( + @Field + val side: String, + + @Field + val hashOfSibling: String, +): Record + +class OutputsInput ( + @Field + val outputs: List, +): Record + class IronfishNativeModule : Module() { // Each module class must implement the definition function. The definition consists of components // that describes the module's functionality and behavior. @@ -67,5 +113,82 @@ class IronfishNativeModule : Module() { Function("isValidPublicAddress") { hexAddress: String -> uniffi.rust_lib.isValidPublicAddress(hexAddress) } + + AsyncFunction("unpackGzip") { gzipPath: String, outputPath: String -> + uniffi.rust_lib.unpackGzip(gzipPath, outputPath) + } + + AsyncFunction("readPartialFile") { path: String, offset: Long, length: Long -> + uniffi.rust_lib.readPartialFile(path, offset.toUInt(), length.toUInt()) + } + + AsyncFunction("decryptNotesForOwner") { noteEncrypteds: List, incomingHexKey: String, promise: Promise -> + CoroutineScope(Dispatchers.Default).launch { + try { + val decryptedNotes = uniffi.rust_lib.decryptNotesForOwner(noteEncrypteds, incomingHexKey) + val expoOutputs = decryptedNotes.map { note -> + ExpoOutput(index = note.index.toInt(), note = (note.note)) + } + promise.resolve(expoOutputs) + } catch (e: Exception) { + promise.reject(CodedException(e)) + } + } + } + + AsyncFunction("decryptNotesForSpender") { noteEncrypteds: List, outgoingHexKey: String, promise: Promise -> + CoroutineScope(Dispatchers.Default).launch { + try { + val decryptedNotes = uniffi.rust_lib.decryptNotesForSpender(noteEncrypteds, outgoingHexKey) + val expoOutputs = decryptedNotes.map { note -> + ExpoOutput(index = note.index.toInt(), note = note.note) + } + promise.resolve(expoOutputs) + } catch (e: Exception) { + promise.reject(CodedException(e)) + } + } + } + + AsyncFunction("nullifier") { note: String, position: String, viewKey: String -> + uniffi.rust_lib.nullifier(note, position, viewKey) + } + + AsyncFunction("createNote") { owner: ByteArray, value: String, memo: ByteArray, assetId: ByteArray, sender: ByteArray -> + try { + uniffi.rust_lib.createNote(uniffi.rust_lib.NoteParams(owner, value.toULong(), memo, assetId, sender)) + } catch (e: Exception) { + println("Unexpected error: ${e.message}") + throw e + } + } + + AsyncFunction("createTransaction") { transactionVersion: Int, transactionFee: String, expirationSequence: Int, spendComponents: SpendComponentsInput, outputs: OutputsInput, spendingKey: ByteArray -> + val spendComponentsConverted = spendComponents.components.map { spendComponent -> + val witnessAuthPath = spendComponent.witnessAuthPath.map { uniffi.rust_lib.WitnessNode(it.side, it.hashOfSibling.hexStringToByteArray()) } + uniffi.rust_lib.SpendComponents(spendComponent.note.hexStringToByteArray(), spendComponent.witnessRootHash.hexStringToByteArray(), spendComponent.witnessTreeSize.toULong(), witnessAuthPath) + } + try { + val transaction = uniffi.rust_lib.createTransaction(transactionVersion.toUByte(), transactionFee.toULong(), expirationSequence.toUInt(), spendComponentsConverted, outputs.outputs.map { it.hexStringToByteArray() }, spendingKey) + transaction + } catch (e: Exception) { + println("Unexpected error: ${e.message}") + throw e + } + } + + AsyncFunction("hashTransaction") { transaction: ByteArray -> + uniffi.rust_lib.hashTransaction(transaction) + } } } + +fun String.hexStringToByteArray(): ByteArray { + val len = this.length + require(len % 2 == 0) { "Hex string must have an even length" } + + return ByteArray(len / 2) { i -> + val index = i * 2 + ((this[index].digitToInt(16) shl 4) + this[index + 1].digitToInt(16)).toByte() + } +} diff --git a/packages/mobile-app/project.json b/packages/mobile-app/project.json index e9e4be2..ec7368a 100644 --- a/packages/mobile-app/project.json +++ b/packages/mobile-app/project.json @@ -2,7 +2,7 @@ "$schema": "../../node_modules/nx/schemas/project-schema.json", "targets": { "android": { - "dependsOn": ["^cargo-android"] + "dependsOn": ["^build", "^cargo-android"] }, "pod-sim": { "executor": "@nx/react-native:pod-install",