Skip to content
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

Fix build #99

Merged
merged 12 commits into from
May 10, 2023
Merged
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
55 changes: 4 additions & 51 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,64 +1,17 @@
>DISCLAIMER: the code contained in this repo is experimental and still in WIP status.

# react-native-haskell-shelley
# cls-mobile-bridge

https://www.npmjs.com/package/@emurgo/react-native-haskell-shelley
https://www.npmjs.com/package/@emurgo/csl-mobile-bridge

## Getting started

`$ npm install @emurgo/react-native-haskell-shelley --save`
`$ npm install @emurgo/csl-mobile-bridge --save`

### Mostly automatic installation

`$ react-native link @emurgo/react-native-haskell-shelley`
`$ react-native link @emurgo/csl-mobile-bridge`

## Usage

See examples in [`App.js`](example/App.js).

## How to add new classes and functions

The process is basically as follows: we start by writting a rust wrapper of some struct method from our target rust library. Both iOS and Android require specific rust wrappers, so there are separate folders for each (`rust/ios` and `rust/android`). When this project is compiled by the host react native app, all the wrappers are transformed into a native library. In Android, java can directly interact with the rust binaries (the instructions for compiling our rust library are in `build.gradle`), while in iOS there is an additional step in which the rust library is transformed into C, with which can we easily interact with through Objective-C. This intermediate step is contained in `ios/build.sh`, where we basically use `cbindgen` to automatically generate C binaries as well as C headers (which are written in `rust/include/react_native_haskell_shelley.h`).
After writing the corresponding iOS and Android wrappers, we finally just write a simple JS library in `index.js` and define its types in `index.d.ts`.

### Android

For every new class:

- Add a new rust module named `<class_name.rs>` (snake_case) in `rust/src/android/`. Here is where we add rust wrappers of the corresponding rust struct methods from the library we want to bind. You can check other modules to see how this is done in `rust/src/android/`.
- Add a `use` declaration in `rust/src/android/mod.rs`


Now you are ready to add functions for your class/rust structure.

For every new function in the module:
- Add a rust wrapper of the form: `pub unsafe extern "C" fn Java_io_emurgo_rnhaskellshelley_Native_functionNameInCamelCase`
- Declare an equivalent java function to the target rust function in `android/src/main/java/io/emurgo/rnhaskellshelley/Native.java`. The function name must be in camelCase and
corresponds to the last part (in camelCase) of the rust wrapper signature mentioned above.

- Add the implementation of the java function that will be exposed to react native in `android/src/main/java/io/emurgo/rnhaskellshelley/HaskellShelleyModule.java`. Note that the types and signatures in `HaskellShelleyModule.java` are different from `Native.java`. In the former, we use java types while in the later we use rust types, ie., we match the signatures of the corresponding rust wrappers.

### iOS

For every new class:

- Add a new rust module named `<class_name.rs>` (snake_case) in `rust/src/ios/`.
- Add a `use` declaration in `rust/src/ios/mod.rs`

As you may have noticed, the two steps above are equivalent to those with Android. The difference is that the rust wrappers are written differently.

For every new function in the module:
- Add a rust wrapper of the form: `pub unsafe extern "C" fn function_name_in_snake_case`
- Write a iOS-native wrapper in Objective-C in `ios/HaskellShelley.m`. In contrast to Android (java), the iOS native wrappers can't directly interact with rust so we actually use a C library. This C library is automatically generated by `cargo` when the project is built.


### Additional steps in Rust

- Any new struct from `cardano_serialization_lib` that is required either as an input or output in our rust wrappers must implement the `RPtrRepresentable` trait. Add them in [`ptr_impl.rs`](rust/src/ptr_impl.rs).

### Javascript

For new classes and methods:

1. Add the javascript class signature in `index.d.ts`
2. Add the javascript class implementation in `index.js`
Original file line number Diff line number Diff line change
Expand Up @@ -5095,6 +5095,14 @@ public final void plutusDataNewEmptyConstrPlutusData(String alternative, Promise
.pour(promise);
}

@ReactMethod
public final void plutusDataNewSingleValueConstrPlutusData(String alternative, String plutusData, Promise promise) {
Native.I
.plutusDataNewSingleValueConstrPlutusData(new RPtr(alternative), new RPtr(plutusData))
.map(RPtr::toJs)
.pour(promise);
}

@ReactMethod
public final void plutusDataNewMap(String map, Promise promise) {
Native.I
Expand Down Expand Up @@ -5189,6 +5197,14 @@ public final void plutusDataFromJson(String json, Double schema, Promise promise
.pour(promise);
}

@ReactMethod
public final void plutusDataFromAddress(String address, Promise promise) {
Native.I
.plutusDataFromAddress(new RPtr(address))
.map(RPtr::toJs)
.pour(promise);
}


@ReactMethod
public final void plutusListToBytes(String self, Promise promise) {
Expand Down Expand Up @@ -10339,6 +10355,13 @@ public final void transactionOutputNew(String address, String amount, Promise pr
.pour(promise);
}

@ReactMethod
public final void transactionOutputSerializationFormat(String self, Promise promise) {
Native.I
.transactionOutputSerializationFormat(new RPtr(self))
.pour(promise);
}


@ReactMethod
public final void transactionOutputAmountBuilderWithValue(String self, String amount, Promise promise) {
Expand Down
3 changes: 3 additions & 0 deletions android/src/main/java/io/emurgo/rnhaskellshelley/Native.java
Original file line number Diff line number Diff line change
Expand Up @@ -730,6 +730,7 @@ private Native() { }
public final native Result<RPtr> plutusDataFromHex(String hexStr);
public final native Result<RPtr> plutusDataNewConstrPlutusData(RPtr constrPlutusData);
public final native Result<RPtr> plutusDataNewEmptyConstrPlutusData(RPtr alternative);
public final native Result<RPtr> plutusDataNewSingleValueConstrPlutusData(RPtr alternative, RPtr plutusData);
public final native Result<RPtr> plutusDataNewMap(RPtr map);
public final native Result<RPtr> plutusDataNewList(RPtr list);
public final native Result<RPtr> plutusDataNewInteger(RPtr integer);
Expand All @@ -742,6 +743,7 @@ private Native() { }
public final native Result<byte[]> plutusDataAsBytes(RPtr self);
public final native Result<String> plutusDataToJson(RPtr self, int schema);
public final native Result<RPtr> plutusDataFromJson(String json, int schema);
public final native Result<RPtr> plutusDataFromAddress(RPtr address);

public final native Result<byte[]> plutusListToBytes(RPtr self);
public final native Result<RPtr> plutusListFromBytes(byte[] bytes);
Expand Down Expand Up @@ -1466,6 +1468,7 @@ private Native() { }
public final native Result<Boolean> transactionOutputHasDataHash(RPtr self);
public final native Result<Boolean> transactionOutputHasScriptRef(RPtr self);
public final native Result<RPtr> transactionOutputNew(RPtr address, RPtr amount);
public final native Result<Integer> transactionOutputSerializationFormat(RPtr self);

public final native Result<RPtr> transactionOutputAmountBuilderWithValue(RPtr self, RPtr amount);
public final native Result<RPtr> transactionOutputAmountBuilderWithCoin(RPtr self, RPtr coin);
Expand Down
5 changes: 4 additions & 1 deletion codegen/android_gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,10 @@ def get_arg_android_rust_result_cast(arg):
else:
cast = "result.into_jlong().jobject(&env)"
elif arg.is_enum:
cast = "(result.to_i32() as jint).jobject(&env)"
if arg.is_optional:
cast = "result.map(|x| x.to_i32() as jint).jobject(&env)"
else:
cast = "(result.to_i32() as jint).jobject(&env)"
else:
cast = "result.rptr().jptr(&env)"
return cast
Expand Down
2 changes: 1 addition & 1 deletion codegen/cardano_serialization_lib.json

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion codegen/js_index_d_gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,10 @@ def map_js_type(arg):
else:
return "number"
elif arg.is_enum:
return arg.struct_orig_name
if optional:
return f"Optional<{arg.struct_orig_name}>"
else:
return arg.struct_orig_name
else:
if optional:
return "Optional<" + arg.struct_name + ">"
Expand Down
24 changes: 24 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3840,6 +3840,13 @@ export class PlutusData extends Ptr {
*/
static new_empty_constr_plutus_data: (alternative: BigNum) => Promise<PlutusData>;

/**
* @param {BigNum} alternative
* @param {PlutusData} plutus_data
* @returns {Promise<PlutusData>}
*/
static new_single_value_constr_plutus_data: (alternative: BigNum, plutus_data: PlutusData) => Promise<PlutusData>;

/**
* @param {PlutusMap} map
* @returns {Promise<PlutusData>}
Expand Down Expand Up @@ -3907,6 +3914,12 @@ export class PlutusData extends Ptr {
*/
static from_json: (json: string, schema: PlutusDatumSchema) => Promise<Optional<PlutusData>>;

/**
* @param {Address} address
* @returns {Promise<Optional<PlutusData>>}
*/
static from_address: (address: Address) => Promise<Optional<PlutusData>>;

}


Expand Down Expand Up @@ -7706,6 +7719,11 @@ export class TransactionOutput extends Ptr {
*/
static new: (address: Address, amount: Value) => Promise<TransactionOutput>;

/**
* @returns {Promise<Optional<CborContainerType>>}
*/
serialization_format: () => Promise<Optional<CborContainerType>>;

}


Expand Down Expand Up @@ -9054,6 +9072,12 @@ export const min_fee: (tx: Transaction, linear_fee: LinearFee) => Promise<Option
*/
export const min_script_fee: (tx: Transaction, ex_unit_prices: ExUnitPrices) => Promise<Optional<BigNum>>;

export enum CborContainerType {
Array = 0,
Map = 1,
}


export enum CertificateKind {
StakeRegistration = 0,
StakeDeregistration = 1,
Expand Down
24 changes: 24 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3718,6 +3718,13 @@ export class PlutusData extends Ptr {
return Ptr._wrap(ret, PlutusData);
}

static async new_single_value_constr_plutus_data(alternative, plutus_data) {
const alternativePtr = Ptr._assertClass(alternative, BigNum);
const plutus_dataPtr = Ptr._assertClass(plutus_data, PlutusData);
const ret = await HaskellShelley.plutusDataNewSingleValueConstrPlutusData(alternativePtr, plutus_dataPtr);
return Ptr._wrap(ret, PlutusData);
}

static async new_map(map) {
const mapPtr = Ptr._assertClass(map, PlutusMap);
const ret = await HaskellShelley.plutusDataNewMap(mapPtr);
Expand Down Expand Up @@ -3781,6 +3788,12 @@ export class PlutusData extends Ptr {
return Ptr._wrap(ret, PlutusData);
}

static async from_address(address) {
const addressPtr = Ptr._assertClass(address, Address);
const ret = await HaskellShelley.plutusDataFromAddress(addressPtr);
return Ptr._wrap(ret, PlutusData);
}

}


Expand Down Expand Up @@ -7518,6 +7531,11 @@ export class TransactionOutput extends Ptr {
return Ptr._wrap(ret, TransactionOutput);
}

async serialization_format() {
const ret = await HaskellShelley.transactionOutputSerializationFormat(this.ptr);
return ret;
}

}


Expand Down Expand Up @@ -8843,6 +8861,12 @@ export const min_script_fee = async (tx, ex_unit_prices) => {
};


export const CborContainerType = Object.freeze({
Array: 0,
Map: 1,
});


export const CertificateKind = Object.freeze({
StakeRegistration: 0,
StakeDeregistration: 1,
Expand Down
34 changes: 34 additions & 0 deletions ios/HaskellShelley.m
Original file line number Diff line number Diff line change
Expand Up @@ -7251,6 +7251,18 @@ + (void)initialize
}] exec:alternativePtr andResolve:resolve orReject:reject];
}

RCT_EXPORT_METHOD(plutusDataNewSingleValueConstrPlutusData:(nonnull NSString *)alternativePtr withPlutusData:(nonnull NSString *)plutusDataPtr withResolve:(RCTPromiseResolveBlock)resolve andReject:(RCTPromiseRejectBlock)reject)
{
[[CSafeOperation new:^NSString*(NSArray* params, CharPtr* error) {
RPtr result;
RPtr alternative = [[params objectAtIndex:0] rPtr];
RPtr plutusData = [[params objectAtIndex:1] rPtr];
return plutus_data_new_single_value_constr_plutus_data(alternative, plutusData, &result, error)
? [NSString stringFromPtr:result]
: nil;
}] exec:@[alternativePtr, plutusDataPtr] andResolve:resolve orReject:reject];
}

RCT_EXPORT_METHOD(plutusDataNewMap:(nonnull NSString *)mapPtr withResolve:(RCTPromiseResolveBlock)resolve andReject:(RCTPromiseRejectBlock)reject)
{
[[CSafeOperation new:^NSString*(NSString* mapPtr, CharPtr* error) {
Expand Down Expand Up @@ -7385,6 +7397,17 @@ + (void)initialize
}] exec:@[jsonVal, schemaVal] andResolve:resolve orReject:reject];
}

RCT_EXPORT_METHOD(plutusDataFromAddress:(nonnull NSString *)addressPtr withResolve:(RCTPromiseResolveBlock)resolve andReject:(RCTPromiseRejectBlock)reject)
{
[[CSafeOperation new:^NSString*(NSString* addressPtr, CharPtr* error) {
RPtr result;
RPtr address = [addressPtr rPtr];
return plutus_data_from_address(address, &result, error)
? [NSString stringFromPtr:result]
: nil;
}] exec:addressPtr andResolve:resolve orReject:reject];
}


RCT_EXPORT_METHOD(plutusListToBytes:(nonnull NSString *)selfPtr withResolve:(RCTPromiseResolveBlock)resolve andReject:(RCTPromiseRejectBlock)reject)
{
Expand Down Expand Up @@ -14724,6 +14747,17 @@ + (void)initialize
}] exec:@[addressPtr, amountPtr] andResolve:resolve orReject:reject];
}

RCT_EXPORT_METHOD(transactionOutputSerializationFormat:(nonnull NSString *)selfPtr withResolve:(RCTPromiseResolveBlock)resolve andReject:(RCTPromiseRejectBlock)reject)
{
[[CSafeOperation new:^NSNumber*(NSString* selfPtr, CharPtr* error) {
int32_t result;
RPtr self = [selfPtr rPtr];
return transaction_output_serialization_format(self, &result, error)
? [NSNumber numberWithLong:result]
: nil;
}] exec:selfPtr andResolve:resolve orReject:reject];
}


RCT_EXPORT_METHOD(transactionOutputAmountBuilderWithValue:(nonnull NSString *)selfPtr withAmount:(nonnull NSString *)amountPtr withResolve:(RCTPromiseResolveBlock)resolve andReject:(RCTPromiseRejectBlock)reject)
{
Expand Down
2 changes: 2 additions & 0 deletions ios/HaskellShelley.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,7 @@
"$(SRCROOT)/../../../React/**",
"$(SRCROOT)/../../react-native/React/**",
);
IPHONEOS_DEPLOYMENT_TARGET = 12.4;
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = HaskellShelley;
Expand All @@ -326,6 +327,7 @@
"$(SRCROOT)/../../../React/**",
"$(SRCROOT)/../../react-native/React/**",
);
IPHONEOS_DEPLOYMENT_TARGET = 12.4;
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = HaskellShelley;
Expand Down
15 changes: 7 additions & 8 deletions ios/Pods/Pods.xcodeproj/project.pbxproj

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading