Skip to content

feat: RN Yttrium #5550

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 15 commits into
base: v2.0
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions lefthook.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# EXAMPLE USAGE:
#
# Refer for explanation to following link:
# https://github.com/evilmartians/lefthook/blob/master/docs/configuration.md
#
# pre-push:
# commands:
# packages-audit:
# tags: frontend security
# run: yarn audit
# gems-audit:
# tags: backend security
# run: bundle audit
#
# pre-commit:
# parallel: true
# commands:
# eslint:
# glob: "*.{js,ts,jsx,tsx}"
# run: yarn eslint {staged_files}
# rubocop:
# tags: backend style
# glob: "*.rb"
# exclude: '(^|/)(application|routes)\.rb$'
# run: bundle exec rubocop --force-exclusion {all_files}
# govet:
# tags: backend style
# files: git ls-files -m
# glob: "*.go"
# run: go vet {files}
# scripts:
# "hello.js":
# runner: node
# "any.go":
# runner: go run
17 changes: 16 additions & 1 deletion packages/react-native-compat/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,25 @@ buildscript {
repositories {
google()
mavenCentral()
maven { url 'https://jitpack.io' }
}

dependencies {
classpath "com.android.tools.build:gradle:7.2.1"
classpath "com.android.tools.build:gradle:8.5.1"
// noinspection DifferentKotlinGradleVersion
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}

allprojects {
repositories {
google()
mavenCentral()
maven { url 'https://jitpack.io' }
}
}


def isNewArchitectureEnabled() {
return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
}
Expand Down Expand Up @@ -109,6 +119,11 @@ dependencies {
//noinspection GradleDynamicVersion
implementation "com.facebook.react:react-native:+"
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'net.java.dev.jna:jna:5.12.1@aar'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0' // Latest stable version
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0' // For Dispatchers.Main
implementation 'com.google.code.gson:gson:2.9.0'
implementation 'com.github.reown-com:yttrium:0.8.19'
}

if (isNewArchitectureEnabled()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#Thu Dec 12 15:47:44 EET 2024
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,17 @@ package com.walletconnect.reactnativemodule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactMethod
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReadableArray
import com.facebook.react.bridge.ReadableMap
import android.content.pm.PackageManager
import uniffi.uniffi_yttrium.ChainAbstractionClient
import uniffi.yttrium.PrepareResponse
import kotlinx.coroutines.*
import uniffi.uniffi_yttrium.*
import uniffi.yttrium.*
import com.google.gson.Gson
import com.google.gson.JsonObject
import com.google.gson.JsonElement

class RNWalletConnectModuleModule internal constructor(context: ReactApplicationContext) :
RNWalletConnectModuleSpec(context) {
Expand Down Expand Up @@ -49,6 +59,241 @@ class RNWalletConnectModuleModule internal constructor(context: ReactApplication
}
}



// ------------------------------ Yttrium Chain Abstraction ------------------------------

private var prepareAvailableResultsMap: MutableMap<String, PrepareResponseAvailable> = mutableMapOf()
private var prepareDetailedAvailableResultsMap: MutableMap<String, UiFields> = mutableMapOf()
private lateinit var client: ChainAbstractionClient

@OptIn(DelicateCoroutinesApi::class)
@ReactMethod
override fun initialize(params: ReadableMap, promise: Promise){
GlobalScope.launch(Dispatchers.Main) {
try {
var projectId = params.getString("projectId") as String
var sdkVersion = params.getString("sdkVersion") as String
var url = params.getString("url") as String

client = ChainAbstractionClient(projectId, PulseMetadata(url= url, bundleId = "test", packageName = "test package", sdkVersion= sdkVersion, sdkPlatform = "mobile"))
promise.resolve(true)
} catch (e: Exception) {
// In case of an error, reject the promise
promise.reject("ERROR", "Yttrium initialize Error:" + e.message, e)
}
}
}

@ReactMethod
override fun prepare(params: ReadableMap, promise: Promise){
System.out.println("checkRoute: Hello from YttriumModule")
GlobalScope.launch(Dispatchers.Main) {
try {
val transactionMap = params.getMap("transaction")

if(transactionMap === null) {
throw Error("no params")
}
// Extract values from the nested transaction map
val chainId = transactionMap.getString("chainId") ?: ""
val input = transactionMap.getString("input") ?: ""
val from = transactionMap.getString("from") ?: ""
val to = transactionMap.getString("to") ?: ""
val value = transactionMap.getString("value") ?: "0"
val result = client.prepare(chainId = chainId, from = from, Call(
to= to,
value = value,
input = input
))

when(result) {
is PrepareResponse.Success -> {
when (result.v1) {
is PrepareResponseSuccess.Available -> {
val availableResult = (result.v1 as PrepareResponseSuccess.Available).v1;
prepareAvailableResultsMap[availableResult.orchestrationId] = availableResult;
}

is PrepareResponseSuccess.NotRequired -> {
println("not required")
}
}
}
is PrepareResponse.Error -> {
println("prepare error: ")
println(result.v1.error)
}
}
println("checkRoute: result: ")
println(result)

val gson = Gson()
val jsonResult = gson.toJson(result)
promise.resolve(jsonResult)
} catch (e: Exception) {
// In case of an error, reject the promise
promise.reject("ERROR", "Yttrium checkRoute Error:" + e.message, e)
}
}
}

@ReactMethod
override fun prepareDetailed(params: ReadableMap, promise: Promise){
System.out.println("prepareDetailed: Hello from YttriumModule")
GlobalScope.launch(Dispatchers.Main) {
try {
val transactionMap = params.getMap("transaction")

if(transactionMap === null) {
throw Error("no params")
}
// Extract values from the nested transaction map
val chainId = transactionMap.getString("chainId") ?: ""
val input = transactionMap.getString("input") ?: ""
val from = transactionMap.getString("from") ?: ""
val to = transactionMap.getString("to") ?: ""
val value = transactionMap.getString("value") ?: "0"
val result = client.prepareDetailed(chainId = chainId, from = from, Call(
to= to,
value = value,
input = input
), Currency.USD)


println("prepareDetailed: result: ")
println(result)

when(result) {
is PrepareDetailedResponse.Success -> {
when (result.v1) {
is PrepareDetailedResponseSuccess.Available -> {
val availableResult = (result.v1 as PrepareDetailedResponseSuccess.Available).v1
prepareDetailedAvailableResultsMap[availableResult.routeResponse.orchestrationId] = availableResult
}
is PrepareDetailedResponseSuccess.NotRequired -> {
println("prepareDetailed NotRequired: ")
}
}
}
is PrepareDetailedResponse.Error -> {
println("prepareDetailed error: ")
println(result.v1.error)
}
}

val gson = Gson()
val jsonResult = gson.toJson(result)
promise.resolve(jsonResult)
} catch (e: Exception) {
// In case of an error, reject the promise
promise.reject("ERROR", "Yttrium checkRoute Error:" + e.message, e)
}
}
}

@ReactMethod
override fun status(params: ReadableMap, promise: Promise){
System.out.println("checkStatus: Hello from YttriumModule address")

GlobalScope.launch(Dispatchers.Main) {
try {
var orchestrationId = params.getString("orchestrationId") as String
val result = client.status(orchestrationId)
println("checkRoute: status: ")
println(result)

val gson = Gson()
val jsonResult = gson.toJson(result)
promise.resolve(jsonResult)
} catch (e: Exception) {
// In case of an error, reject the promise
promise.reject("ERROR", "Yttrium checkStatus Error:" + e.message, e)
}
}
}

@ReactMethod
override fun getBridgeDetails(params: ReadableMap, promise: Promise){
System.out.println("getFulfilmentDetails: Hello from YttriumModule address")

GlobalScope.launch(Dispatchers.Main) {
try {
val orchestrationId = params.getString("orchestrationId") as String

val availableResult = prepareAvailableResultsMap[orchestrationId]
val uiFields = availableResult.let {
if (it != null) {
client.getUiFields(it, Currency.USD)
}
}
val gson = Gson()
val resultJson: JsonElement = gson.toJsonTree(uiFields)
promise.resolve(gson.toJson(resultJson))
} catch (e: Exception) {
// In case of an error, reject the promise
promise.reject("ERROR", "Yttrium getFulfilmentDetails Error:" + e.message, e)
}
}
}

@ReactMethod
override fun getERC20Balance(params: ReadableMap, promise: Promise){
System.out.println("getERC20Balance: Hello from YttriumModule address")

GlobalScope.launch(Dispatchers.Main) {
try {
val tokenAddress = params.getString("tokenAddress") as String
val ownerAddress = params.getString("ownerAddress") as String
val chainId = params.getString("chainId") as String
val result = client.erc20TokenBalance(chainId = chainId, token = tokenAddress, owner = ownerAddress)
val gson = Gson()
val resultJson: JsonElement = gson.toJsonTree(result)
promise.resolve(gson.toJson(resultJson))
} catch (e: Exception) {
// In case of an error, reject the promise
promise.reject("ERROR", "Yttrium getERC20Balance Error:" + e.message, e)
}
}
}

private fun getListOfStrings(params: ReadableMap, key: String): List<String> {
val readableArray: ReadableArray? = params.getArray(key)
return readableArray?.toArrayList()?.map { it as String } ?: emptyList()
}

@ReactMethod
override fun execute(params: ReadableMap, promise: Promise){
System.out.println("getERC20Balance: Hello from YttriumModule address")

GlobalScope.launch(Dispatchers.Main) {
try {
val orchestrationId = params.getString("orchestrationId") as String
val bridgeSignedTransactions = getListOfStrings(params, "bridgeSignedTransactions")
val initialSignedTransaction = params.getString("initialSignedTransaction") as String


val prepareDetailedResult = prepareDetailedAvailableResultsMap[orchestrationId]

val result =
prepareDetailedResult?.let {
client.execute(
uiFields = it,
routeTxnSigs = bridgeSignedTransactions,
initialTxnSig = initialSignedTransaction,
)
}
val gson = Gson()
val resultJson: JsonElement = gson.toJsonTree(result)
promise.resolve(gson.toJson(resultJson))
} catch (e: Exception) {
// In case of an error, reject the promise
promise.reject("ERROR", "Yttrium getERC20Balance Error:" + e.message, e)
}
}
}


companion object {
const val NAME = "RNWalletConnectModule"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,25 @@ package com.walletconnect.reactnativemodule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReadableMap

abstract class RNWalletConnectModuleSpec internal constructor(context: ReactApplicationContext) :
ReactContextBaseJavaModule(context) {

abstract fun isAppInstalled(packageName: String?, promise: Promise);
abstract fun initialize(params: ReadableMap, promise: Promise);
abstract fun prepare(params: ReadableMap, promise: Promise);
abstract fun prepareDetailed(params: ReadableMap, promise: Promise);
abstract fun status(params: ReadableMap, promise: Promise);
abstract fun getBridgeDetails(params: ReadableMap, promise: Promise);
abstract fun getERC20Balance(params: ReadableMap, promise: Promise);
abstract fun execute(params: ReadableMap, promise: Promise);

protected abstract fun getTypedExportedConstants(): Map<String, String>

override fun getConstants(): Map<String, String> {
val constants: Map<String, String> = getTypedExportedConstants()
return constants
}

}
7 changes: 7 additions & 0 deletions packages/react-native-compat/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { NativeModules } from "react-native";
import { getApplicationModule } from "./module";

// Polyfill TextEncode / TextDecode
Expand Down Expand Up @@ -75,3 +76,9 @@ if (typeof global?.Application === "undefined") {
console.error("react-native-compat: Application module is not available");
}
}

// iOS uses Yttrium, Android uses RNWalletConnectModule
global.yttrium = NativeModules.Yttrium || NativeModules.RNWalletConnectModule;

// eslint-disable-next-line no-console
console.log("RN yttrium", global.yttrium);
2 changes: 2 additions & 0 deletions packages/react-native-compat/ios/Yttrium-Bridging-Header.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#import <React/RCTBridgeModule.h>
#import <React/RCTViewManager.h>
Loading