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

feat: add sign typed data v4 method #138

Closed
wants to merge 1 commit into from
Closed
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Fill out your copyright notice in the Description page of Project Settings.

#include "Immutable/Actions/ImtblPassportZkEvmSignTypedDataV4AsyncAction.h"

#include "Immutable/ImmutablePassport.h"
#include "Immutable/ImmutableSubsystem.h"
#include "Immutable/Misc/ImtblLogging.h"

UImtblPassportZkEvmSignTypedDataV4AsyncAction* UImtblPassportZkEvmSignTypedDataV4AsyncAction::ZkEvmSignTypedDataV4(UObject* WorldContextObject, const FZkEvmSignTypedDataV4Request& Request)
{
UImtblPassportZkEvmSignTypedDataV4AsyncAction* PassportZkEvmSignTypedDataV4BlueprintNode = NewObject<UImtblPassportZkEvmSignTypedDataV4AsyncAction>();
PassportZkEvmSignTypedDataV4BlueprintNode->WorldContextObject = WorldContextObject;
PassportZkEvmSignTypedDataV4BlueprintNode->SignRequest = Request;
return PassportZkEvmSignTypedDataV4BlueprintNode;
}

void UImtblPassportZkEvmSignTypedDataV4AsyncAction::Activate()
{
if (!WorldContextObject || !WorldContextObject->GetWorld())
{
FString Err = "zkEVM Sign Typed Data V4 failed due to missing world or world " "context object.";
IMTBL_WARN("%s", *Err)
Failed.Broadcast(Err, TEXT(""));
return;
}

GetSubsystem()->WhenReady(this, &UImtblPassportZkEvmSignTypedDataV4AsyncAction::DoZkEvmSignTypedDataV4);
}

void UImtblPassportZkEvmSignTypedDataV4AsyncAction::DoZkEvmSignTypedDataV4(TWeakObjectPtr<UImtblJSConnector> JSConnector)
{
auto Passport = GetSubsystem()->GetPassport();

if (Passport.IsValid())
{
Passport->ZkEvmSignTypedDataV4(SignRequest, UImmutablePassport::FImtblPassportResponseDelegate::CreateUObject(this, &UImtblPassportZkEvmSignTypedDataV4AsyncAction::OnZkEvmSignTypedDataV4Response));
}
}

void UImtblPassportZkEvmSignTypedDataV4AsyncAction::OnZkEvmSignTypedDataV4Response(FImmutablePassportResult Result)
{
if (Result.Success)
{
IMTBL_LOG("zkEVM Sign Typed Data V4 success")
MessageSigned.Broadcast(TEXT(""), UImmutablePassport::GetResponseResultAsString(Result.Response));
}
else
{
IMTBL_LOG("zkEVM Sign Typed Data V4 failed")
Failed.Broadcast(Result.Error, TEXT(""));
}
}
60 changes: 60 additions & 0 deletions Source/Immutable/Private/Immutable/ImmutablePassport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "Immutable/ImmutableSaveGame.h"
#include "Kismet/GameplayStatics.h"
#include "Policies/CondensedJsonPrintPolicy.h"
#include "Json.h"

#if PLATFORM_ANDROID | PLATFORM_IOS | PLATFORM_MAC
#include "GenericPlatform/GenericPlatformHttp.h"
Expand All @@ -27,6 +28,34 @@

#define PASSPORT_SAVE_GAME_SLOT_NAME TEXT("Immutable")

TSharedPtr<FJsonObject> UStructToJsonObject(const UStruct* Struct, const void* StructData)
{
TSharedPtr<FJsonObject> JsonObject = MakeShareable(new FJsonObject());

if (FJsonObjectConverter::UStructToJsonObject(Struct, StructData, JsonObject.ToSharedRef(), 0, 0))
{
return JsonObject;
}

return nullptr; // Return nullptr if conversion fails
}

TArray<TSharedPtr<FJsonValue>> ConvertNameTypeToJsonArray(const TArray<FZkEvmSignTypedDataV4NameType>& NameTypes)
{
TArray<TSharedPtr<FJsonValue>> NameTypeArray;

for (const FZkEvmSignTypedDataV4NameType& Item : NameTypes)
{
TSharedPtr<FJsonObject> NameTypeItemObject = MakeShareable(new FJsonObject());
NameTypeItemObject->SetStringField("name", Item.Name);
NameTypeItemObject->SetStringField("type", Item.Type);

NameTypeArray.Add(MakeShareable(new FJsonValueObject(NameTypeItemObject)));
}

return NameTypeArray;
}

void UImmutablePassport::Initialize(const FImmutablePassportInitData& Data, const FImtblPassportResponseDelegate& ResponseDelegate)
{
check(JSConnector.IsValid());
Expand Down Expand Up @@ -130,6 +159,37 @@ void UImmutablePassport::ZkEvmGetTransactionReceipt(const FZkEvmTransactionRecei
CallJS(ImmutablePassportAction::ZkEvmGetTransactionReceipt, UStructToJsonString(Request), ResponseDelegate, FImtblJSResponseDelegate::CreateUObject(this, &UImmutablePassport::OnBridgeCallbackResponse));
}

void UImmutablePassport::ZkEvmSignTypedDataV4(const FZkEvmSignTypedDataV4Request& Request, const FImtblPassportResponseDelegate& ResponseDelegate)
{
// this messy and manual JSON conversion is because the UR4 JSON util automatically downcases the first letter of
// property names. The SignTypedDataV4 method on the game bridge side requires the `Request.types` properties
// to be capitalised, otherwise the TS Passport module will throw a validation erro.
TSharedPtr<FJsonObject> DomainJsonObject = UStructToJsonObject(FZkEvmSignTypedDataV4Domain::StaticStruct(), &Request.domain);
TSharedPtr<FJsonObject> MessageJsonObject = UStructToJsonObject(FZkEvmSignTypedDataV4Message::StaticStruct(), &Request.message);

TArray<TSharedPtr<FJsonValue>> OrderComponentsArray = ConvertNameTypeToJsonArray(Request.types.OrderComponents);
TArray<TSharedPtr<FJsonValue>> ConsiderationItemArray = ConvertNameTypeToJsonArray(Request.types.ConsiderationItem);
TArray<TSharedPtr<FJsonValue>> OfferItemArray = ConvertNameTypeToJsonArray(Request.types.OfferItem);
TArray<TSharedPtr<FJsonValue>> EIP712DomainArray = ConvertNameTypeToJsonArray(Request.types.EIP712Domain);
TSharedPtr<FJsonObject> TypesJsonObject = MakeShareable(new FJsonObject());
TypesJsonObject->SetArrayField("OrderComponents", OrderComponentsArray);
TypesJsonObject->SetArrayField("ConsiderationItem", ConsiderationItemArray);
TypesJsonObject->SetArrayField("OfferItem", OfferItemArray);
TypesJsonObject->SetArrayField("EIP712Domain", EIP712DomainArray);

TSharedPtr<FJsonObject> JsonObject = MakeShareable(new FJsonObject());
JsonObject->SetObjectField("domain", DomainJsonObject);
JsonObject->SetObjectField("types", TypesJsonObject);
JsonObject->SetObjectField("message", MessageJsonObject);
JsonObject->SetStringField("primaryType", Request.primaryType);

FString JsonString;
TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&JsonString);
FJsonSerializer::Serialize(JsonObject.ToSharedRef(), Writer);

CallJS(ImmutablePassportAction::ZkEvmSignTypedDataV4, JsonString, ResponseDelegate, FImtblJSResponseDelegate::CreateUObject(this, &UImmutablePassport::OnBridgeCallbackResponse));
}

void UImmutablePassport::ConfirmCode(const FString& DeviceCode, const float Interval, const FImtblPassportResponseDelegate& ResponseDelegate)
{
FImmutablePassportCodeConfirmRequestData Data{DeviceCode, Interval};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "Immutable/ImmutablePassport.h"
#include "ImtblBlueprintAsyncAction.h"
#include "ImtblPassportZkEvmSignTypedDataV4AsyncAction.generated.h"

/**
* Async action blueprint node for zkEVM Send Transaction
*/
UCLASS()
class IMMUTABLE_API UImtblPassportZkEvmSignTypedDataV4AsyncAction : public UImtblBlueprintAsyncAction
{
GENERATED_BODY()

DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FPassportZkEvmSignTypedDataV4OutputPin, FString, ErrorMessage, FString, Signature);

public:
UFUNCTION(BlueprintCallable, meta = (WorldContext = "WorldContextObject", BlueprintInternalUseOnly = "true"), Category = "Immutable")
static UImtblPassportZkEvmSignTypedDataV4AsyncAction* ZkEvmSignTypedDataV4(UObject* WorldContextObject, const FZkEvmSignTypedDataV4Request& Request);

virtual void Activate() override;

private:
FZkEvmSignTypedDataV4Request SignRequest;

UPROPERTY(BlueprintAssignable)
FPassportZkEvmSignTypedDataV4OutputPin MessageSigned;
UPROPERTY(BlueprintAssignable)
FPassportZkEvmSignTypedDataV4OutputPin Failed;

void DoZkEvmSignTypedDataV4(TWeakObjectPtr<class UImtblJSConnector> JSGetConnector);
void OnZkEvmSignTypedDataV4Response(FImmutablePassportResult Result);
};
9 changes: 9 additions & 0 deletions Source/Immutable/Public/Immutable/ImmutableDataTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -276,3 +276,12 @@ struct IMMUTABLE_API FZkEvmTransactionReceipt
UPROPERTY()
FString type;
};

USTRUCT(BlueprintType)
struct FZkEvmSignTypedDataV4Response
{
GENERATED_BODY()

UPROPERTY()
FString signature;
};
1 change: 1 addition & 0 deletions Source/Immutable/Public/Immutable/ImmutableNames.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ namespace ImmutablePassportAction
const FString ZkEvmSendTransaction = TEXT("zkEvmSendTransaction");
const FString zkEvmSendTransactionWithConfirmation = TEXT("zkEvmSendTransactionWithConfirmation");
const FString ZkEvmGetTransactionReceipt = TEXT("zkEvmGetTransactionReceipt");
const FString ZkEvmSignTypedDataV4 = TEXT("zkEvmSignTypedDataV4");

#if PLATFORM_ANDROID | PLATFORM_IOS | PLATFORM_MAC
const FString GetPKCEAuthUrl = TEXT("getPKCEAuthUrl");
Expand Down
7 changes: 7 additions & 0 deletions Source/Immutable/Public/Immutable/ImmutablePassport.h
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,13 @@ class IMMUTABLE_API UImmutablePassport : public UObject
* FImtblPassportResponseDelegate to call on response from JS.
*/
void ZkEvmGetTransactionReceipt(const FZkEvmTransactionReceiptRequest& Request, const FImtblPassportResponseDelegate& ResponseDelegate);

/**
* Generate a signature for a typed data V4 object
* @param Request Type data to sign
* @param ResponseDelegate The response delegate of type FImtblPassportResponseDelegate to call on response from JS.
*/
void ZkEvmSignTypedDataV4(const FZkEvmSignTypedDataV4Request& Request, const FImtblPassportResponseDelegate& ResponseDelegate);

void GetIdToken(const FImtblPassportResponseDelegate& ResponseDelegate);
void GetAccessToken(const FImtblPassportResponseDelegate& ResponseDelegate);
Expand Down
160 changes: 160 additions & 0 deletions Source/Immutable/Public/Immutable/ImmutableRequests.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,163 @@ struct IMMUTABLE_API FZkEvmTransactionReceiptRequest
UPROPERTY()
FString txHash;
};

USTRUCT(BlueprintType)
struct IMMUTABLE_API FZkEvmSignTypedDataV4Domain
{
GENERATED_BODY()

UPROPERTY(BlueprintReadWrite, EditAnywhere)
FString ChainId;

UPROPERTY(BlueprintReadWrite, EditAnywhere)
FString Name;

UPROPERTY(BlueprintReadWrite, EditAnywhere)
FString VerifyingContract;

UPROPERTY(BlueprintReadWrite, EditAnywhere)
FString Version;
};

USTRUCT(BlueprintType)
struct FZkEvmSignTypedDataV4NameType
{
GENERATED_BODY()

UPROPERTY(BlueprintReadWrite, EditAnywhere)
FString Name;

UPROPERTY(BlueprintReadWrite, EditAnywhere)
FString Type;
};

USTRUCT(BlueprintType)
struct FZkEvmSignTypedDataV4SignableMessageTypes
{
GENERATED_BODY()

// JsonFieldName metadata specifier is used to define the exact name that should be used in the JSON output
UPROPERTY(BlueprintReadWrite, EditAnywhere, meta = (JsonFieldName = "OrderComponents"))
TArray<FZkEvmSignTypedDataV4NameType> OrderComponents;

UPROPERTY(BlueprintReadWrite, EditAnywhere, meta = (JsonFieldName = "OfferItem"))
TArray<FZkEvmSignTypedDataV4NameType> OfferItem;

UPROPERTY(BlueprintReadWrite, EditAnywhere, meta = (JsonFieldName = "ConsiderationItem"))
TArray<FZkEvmSignTypedDataV4NameType> ConsiderationItem;

UPROPERTY(BlueprintReadWrite, EditAnywhere, meta = (JsonFieldName = "EIP712Domain"))
TArray<FZkEvmSignTypedDataV4NameType> EIP712Domain;
};

USTRUCT(BlueprintType)
struct FZkEvmSignTypedDataV4OfferItem
{
GENERATED_BODY()

UPROPERTY(BlueprintReadWrite, EditAnywhere)
uint8 ItemType;

UPROPERTY(BlueprintReadWrite, EditAnywhere)
FString Token;

UPROPERTY(BlueprintReadWrite, EditAnywhere)
FString IdentifierOrCriteria;

UPROPERTY(BlueprintReadWrite, EditAnywhere)
FString StartAmount;

UPROPERTY(BlueprintReadWrite, EditAnywhere)
FString EndAmount;
};

USTRUCT(BlueprintType)
struct FZkEvmSignTypedDataV4ConsiderationItem
{
GENERATED_BODY()

UPROPERTY(BlueprintReadWrite, EditAnywhere)
uint8 ItemType;

UPROPERTY(BlueprintReadWrite, EditAnywhere)
FString Token;

UPROPERTY(BlueprintReadWrite, EditAnywhere)
FString IdentifierOrCriteria;

UPROPERTY(BlueprintReadWrite, EditAnywhere)
FString StartAmount;

UPROPERTY(BlueprintReadWrite, EditAnywhere)
FString EndAmount;

UPROPERTY(BlueprintReadWrite, EditAnywhere)
FString Recipient;
};

USTRUCT(BlueprintType)
struct FZkEvmSignTypedDataV4Message
{
GENERATED_BODY()

UPROPERTY(BlueprintReadWrite, EditAnywhere)
FString Offerer;

UPROPERTY(BlueprintReadWrite, EditAnywhere)
FString Zone;

UPROPERTY(BlueprintReadWrite, EditAnywhere)
TArray<FZkEvmSignTypedDataV4OfferItem> Offer;

UPROPERTY(BlueprintReadWrite, EditAnywhere)
TArray<FZkEvmSignTypedDataV4ConsiderationItem> Consideration;

UPROPERTY(BlueprintReadWrite, EditAnywhere)
uint8 OrderType;

UPROPERTY(BlueprintReadWrite, EditAnywhere)
FString StartTime;

UPROPERTY(BlueprintReadWrite, EditAnywhere)
FString EndTime;

UPROPERTY(BlueprintReadWrite, EditAnywhere)
FString ZoneHash;

UPROPERTY(BlueprintReadWrite, EditAnywhere)
FString Salt;

UPROPERTY(BlueprintReadWrite, EditAnywhere)
FString ConduitKey;

UPROPERTY(BlueprintReadWrite, EditAnywhere)
FString Counter;
};

USTRUCT(BlueprintType)
struct IMMUTABLE_API FZkEvmSignTypedDataV4Request
{
GENERATED_BODY()

UPROPERTY(BlueprintReadWrite, EditAnywhere)
FZkEvmSignTypedDataV4Domain domain;

UPROPERTY(BlueprintReadWrite, EditAnywhere)
FZkEvmSignTypedDataV4SignableMessageTypes types;

UPROPERTY(BlueprintReadWrite, EditAnywhere)
FZkEvmSignTypedDataV4Message message;

UPROPERTY(BlueprintReadWrite, EditAnywhere)
FString primaryType;
};

// USTRUCT(BlueprintType)
// struct IMMUTABLE_API FZkEvmSignTypedDataV4Request
// {
// GENERATED_BODY()
//
// UPROPERTY(BlueprintReadWrite, EditAnywhere)
// FSignableMessage message;
// };
Loading