Skip to content

Commit

Permalink
feat: add sign typed data v4 method
Browse files Browse the repository at this point in the history
  • Loading branch information
CodeSchwert committed Oct 3, 2024
1 parent 8203fad commit cea584b
Show file tree
Hide file tree
Showing 7 changed files with 325 additions and 0 deletions.
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;
// };

0 comments on commit cea584b

Please sign in to comment.