diff --git a/.github/workflows/branch-name.yml b/.github/workflows/branch-name.yml index 57bf7a20..804238f2 100644 --- a/.github/workflows/branch-name.yml +++ b/.github/workflows/branch-name.yml @@ -9,7 +9,6 @@ jobs: - uses: deepakputhraya/action-branch-name@master with: regex: '([a-z])*\/?([a-z0-9.-])+' - allowed_prefixes: 'feat,fix,build,chore,ci,docs,style,refactor,perf,test,revert,release' ignore: main min_length: 5 max_length: 50 diff --git a/Content/BlueprintSampleContent/ImtblAuthenticatedWidget4_26.uasset b/Content/BlueprintSampleContent/ImtblAuthenticatedWidget4_26.uasset index f6a40f5d..bf567db0 100644 Binary files a/Content/BlueprintSampleContent/ImtblAuthenticatedWidget4_26.uasset and b/Content/BlueprintSampleContent/ImtblAuthenticatedWidget4_26.uasset differ diff --git a/Content/BlueprintSampleContent/ImtblTransakWidget4_26.uasset b/Content/BlueprintSampleContent/ImtblTransakWidget4_26.uasset new file mode 100644 index 00000000..c4ff90c1 Binary files /dev/null and b/Content/BlueprintSampleContent/ImtblTransakWidget4_26.uasset differ diff --git a/Immutable.uplugin b/Immutable.uplugin index dc1e3451..9d589c6b 100644 --- a/Immutable.uplugin +++ b/Immutable.uplugin @@ -1,45 +1,60 @@ -{ - "FileVersion": 3, - "Version": 1, - "VersionName": "1.3.0.alpha", - "FriendlyName": "Immutable", - "Description": "", - "Category": "Other", - "CreatedBy": "", - "CreatedByURL": "", - "DocsURL": "", - "MarketplaceURL": "", - "SupportURL": "", - "EnabledByDefault": true, - "CanContainContent": true, - "IsBetaVersion": true, - "IsExperimentalVersion": true, - "Installed": true, - "Modules": [ - { - "Name": "Immutable", - "Type": "Runtime", - "LoadingPhase": "Default" - }, - { - "Name": "ImmutableEditor", - "Type": "Editor", - "LoadingPhase": "Default" - }, - { - "Name": "ImmutablePluginManager", - "Type": "Runtime", - "LoadingPhase": "EarliestPossible" - }, - { - "Name": "ImmutableOrderbook", - "Type": "Runtime", - "LoadingPhase": "Default" - }, - { - "Name": "ImmutablezkEVMAPI", - "Type": "Runtime", - "LoadingPhase": "Default" - } - ] +{ + "FileVersion": 3, + "Version": 1, + "VersionName": "1.3.0.alpha", + "FriendlyName": "Immutable", + "Description": "", + "Category": "Other", + "CreatedBy": "", + "CreatedByURL": "", + "DocsURL": "", + "MarketplaceURL": "", + "SupportURL": "", + "EnabledByDefault": true, + "CanContainContent": true, + "IsBetaVersion": true, + "IsExperimentalVersion": true, + "Installed": true, + "Modules": [ + { + "Name": "Immutable", + "Type": "Runtime", + "LoadingPhase": "Default" + }, + { + "Name": "ImmutableEditor", + "Type": "Editor", + "LoadingPhase": "Default" + }, + { + "Name": "ImmutablePluginManager", + "Type": "Runtime", + "LoadingPhase": "EarliestPossible" + }, + { + "Name": "ImmutableOrderbook", + "Type": "Runtime", + "LoadingPhase": "Default" + }, + { + "Name": "ImmutablezkEVMAPI", + "Type": "Runtime", + "LoadingPhase": "Default" + }, + { + "Name": "ImmutableMarketplace", + "Type": "Runtime", + "LoadingPhase": "Default" + } + ], + "Plugins": [ + { + "Name": "BLUI", + "Enabled": true + }, + { + "Name": "WebBrowserWidget", + "Enabled": false + } + ] } \ No newline at end of file diff --git a/Source/Immutable/Immutable.Build.cs b/Source/Immutable/Immutable.Build.cs index 953881b2..c2a00b37 100644 --- a/Source/Immutable/Immutable.Build.cs +++ b/Source/Immutable/Immutable.Build.cs @@ -19,7 +19,7 @@ public Immutable(ReadOnlyTargetRules Target) : base(Target) new string[] { "Core", - "JsonUtilities", + "JsonUtilities", } ); @@ -34,19 +34,27 @@ public Immutable(ReadOnlyTargetRules Target) : base(Target) "UMG", "Projects", "DeveloperSettings", + "HTTP", } ); -#if UE_5_0_OR_LATER - PublicDependencyModuleNames.Add("WebBrowserWidget"); +#if UE_5_1_OR_LATER + PublicDependencyModuleNames.AddRange( + new string[] + { + "WebBrowser", + "WebBrowserWidget" + } + ); #else + PrivateDependencyModuleNames.Add("BluExtension"); if (Target.Platform == UnrealTargetPlatform.Win64) { PublicDependencyModuleNames.Add("Blu"); } #endif -#if UE_5_0_OR_LATER +#if UE_5_1_OR_LATER PrivateDependencyModuleNames.Add("WebBrowser"); PublicDefinitions.Add("USING_BUNDLED_CEF=1"); PublicDefinitions.Add("USING_BLUI_CEF=0"); diff --git a/Source/Immutable/Private/Immutable/ImmutablePassport.cpp b/Source/Immutable/Private/Immutable/ImmutablePassport.cpp index 708f6ef6..10b9ac09 100644 --- a/Source/Immutable/Private/Immutable/ImmutablePassport.cpp +++ b/Source/Immutable/Private/Immutable/ImmutablePassport.cpp @@ -4,13 +4,11 @@ #include "ImmutableAnalytics.h" #include "Immutable/Misc/ImtblLogging.h" -#include "Immutable/ImmutableResponses.h" #include "Immutable/ImtblJSConnector.h" #include "JsonObjectConverter.h" #include "Immutable/ImmutableSaveGame.h" -#include "Immutable/ImmutableUtilities.h" +#include "Immutable/ImmutableSettings.h" #include "Kismet/GameplayStatics.h" -#include "Policies/CondensedJsonPrintPolicy.h" #if PLATFORM_ANDROID | PLATFORM_IOS | PLATFORM_MAC #include "GenericPlatform/GenericPlatformHttp.h" @@ -49,8 +47,17 @@ void UImmutablePassport::Initialize(const FImmutablePassportInitData& Data, cons void UImmutablePassport::Initialize(const FImtblPassportResponseDelegate& ResponseDelegate) { check(JSConnector.IsValid()); + + auto Settings = GetDefault(); + + if (Settings) + { + ResponseDelegate.ExecuteIfBound(FImmutablePassportResult{false, "Failed to find Immutable Settings"}); + + return; + } - UApplicationConfig* ApplicationConfig = FImmutableUtilities::GetDefaultApplicationConfig(); + UApplicationConfig* ApplicationConfig = Settings->DefaultApplicationConfig.GetDefaultObject(); if (!ApplicationConfig) { @@ -60,7 +67,7 @@ void UImmutablePassport::Initialize(const FImtblPassportResponseDelegate& Respon } InitData.clientId = ApplicationConfig->GetClientID(); - InitData.environment = ApplicationConfig->GetEnvironment(); + InitData.environment = ApplicationConfig->GetEnvironmentString(); InitData.redirectUri = ApplicationConfig->GetRedirectURL(); InitData.logoutRedirectUri = ApplicationConfig->GetLogoutURL(); diff --git a/Source/Immutable/Private/Immutable/ImmutableUtilities.cpp b/Source/Immutable/Private/Immutable/ImmutableUtilities.cpp index a81db18b..8325b60a 100644 --- a/Source/Immutable/Private/Immutable/ImmutableUtilities.cpp +++ b/Source/Immutable/Private/Immutable/ImmutableUtilities.cpp @@ -1,6 +1,5 @@ #include "Immutable/ImmutableUtilities.h" -#include "Immutable/ImmutablePluginSettings.h" #include "Immutable/Misc/ImtblLogging.h" #include "Interfaces/IPluginManager.h" #include "Misc/FileHelper.h" @@ -20,15 +19,3 @@ bool FImmutableUtilities::LoadGameBridge(FString& GameBridge) return false; } - -UApplicationConfig* FImmutableUtilities::GetDefaultApplicationConfig() -{ - auto Settings = GetDefault(); - - if (!Settings) - { - return nullptr; - } - - return Settings->DefaultApplicationConfig.GetDefaultObject(); -} diff --git a/Source/Immutable/Private/Immutable/Tests/ImtblMessagesTest.cpp b/Source/Immutable/Private/Immutable/Tests/ImtblMessagesTest.cpp index e860b824..3918188d 100644 --- a/Source/Immutable/Private/Immutable/Tests/ImtblMessagesTest.cpp +++ b/Source/Immutable/Private/Immutable/Tests/ImtblMessagesTest.cpp @@ -40,7 +40,7 @@ bool FImtblMessagesTest::RunTest(const FString& Parameters) // string { const FString RedirectUri = "https://example.com"; - const FImmutablePassportInitData InitData{ClientId, RedirectUri, ImmutablePassportAction::EnvSandbox}; + const FImmutablePassportInitData InitData { ClientId, RedirectUri, ImmutablePassportEnvironmentConstants::EnvironmentSandbox }; FString ExpectedJson = "{\"clientId\":\"MyExampleClientId\",\"redirectUri\":\"https://" "example.com\",\"environment\":\"sandbox\""; ExpectedJson += ",\"engineVersion\":{"; ExpectedJson += "\"engine\":\"unreal\""; @@ -57,7 +57,7 @@ bool FImtblMessagesTest::RunTest(const FString& Parameters) // an FImmutablePassportInitData with an empty redirectUri should leave the // redirectUri field out of the json string when converted { - const FImmutablePassportInitData InitData{ClientId, "", ImmutablePassportAction::EnvSandbox}; + const FImmutablePassportInitData InitData { ClientId, "", ImmutablePassportEnvironmentConstants::EnvironmentSandbox }; FString ExpectedJson = "{\"clientId\":\"MyExampleClientId\",\"environment\":\"sandbox\""; ExpectedJson += ",\"engineVersion\":{"; ExpectedJson += "\"engine\":\"unreal\""; diff --git a/Source/Immutable/Public/Immutable/ApplicationConfig.h b/Source/Immutable/Public/Immutable/ApplicationConfig.h index ad46fea3..78f526e6 100644 --- a/Source/Immutable/Public/Immutable/ApplicationConfig.h +++ b/Source/Immutable/Public/Immutable/ApplicationConfig.h @@ -1,5 +1,8 @@ #pragma once +#include "ImmutableEnums.h" +#include "ImmutableNames.h" + #include "ApplicationConfig.generated.h" /** @@ -9,7 +12,7 @@ * client IDs, and environment settings for the zkEVM API, Orderbook API, and Passport. */ UCLASS(Abstract, Blueprintable, ClassGroup = Immutable) -class UApplicationConfig : public UObject +class IMMUTABLE_API UApplicationConfig : public UObject { GENERATED_BODY() @@ -85,13 +88,20 @@ class UApplicationConfig : public UObject } /** - * Retrieves the environment configuration used for Passport initialization. + * Retrieves the environment configuration used for Passport initialization as FString . * * @return A constant reference to an FString representing the environment. */ - const FString& GetEnvironment() + const FString& GetEnvironmentString() const { - return Environment; + switch (Environment) + { + case EPassportEnvironment::Production: + return ImmutablePassportEnvironmentConstants::EnvironmentProduction; + default: + case EPassportEnvironment::Sandbox: + return ImmutablePassportEnvironmentConstants::EnvironmentProduction; + } } /** @@ -144,9 +154,12 @@ class UApplicationConfig : public UObject UPROPERTY(EditDefaultsOnly, Category = "Passport") FString ClientID; - /** Environment used to initialize passport. Ex. sandbox or production */ + /** + * Environment used to initialize passport. Ex. sandbox or production. + * @note The default environment is set to the Sandbox environment. + */ UPROPERTY(EditDefaultsOnly, Category = "Passport") - FString Environment; + EPassportEnvironment Environment = EPassportEnvironment::Sandbox; /** * (Android, iOS, and macOS only) diff --git a/Source/Immutable/Public/Immutable/ImmutableDataTypes.h b/Source/Immutable/Public/Immutable/ImmutableDataTypes.h index 78a56f1d..3fa40cdd 100644 --- a/Source/Immutable/Public/Immutable/ImmutableDataTypes.h +++ b/Source/Immutable/Public/Immutable/ImmutableDataTypes.h @@ -61,9 +61,11 @@ struct IMMUTABLE_API FImmutablePassportInitData UPROPERTY() FString logoutRedirectUri; - /** The environment to connect to. */ + /** The environment to connect to. + * @note Default value is "sandbox" + */ UPROPERTY() - FString environment = ImmutablePassportAction::EnvSandbox; + FString environment = ImmutablePassportEnvironmentConstants::EnvironmentSandbox; /** * Whether silent logout is enabled. diff --git a/Source/Immutable/Public/Immutable/ImmutableEnums.h b/Source/Immutable/Public/Immutable/ImmutableEnums.h new file mode 100644 index 00000000..ad9f31ca --- /dev/null +++ b/Source/Immutable/Public/Immutable/ImmutableEnums.h @@ -0,0 +1,10 @@ +#pragma once + +UENUM(BlueprintType) +enum class EPassportEnvironment : uint8 +{ + Development, + Sandbox, + Production, +}; + diff --git a/Source/Immutable/Public/Immutable/ImmutableNames.h b/Source/Immutable/Public/Immutable/ImmutableNames.h index 7183e94a..a9a54fe6 100644 --- a/Source/Immutable/Public/Immutable/ImmutableNames.h +++ b/Source/Immutable/Public/Immutable/ImmutableNames.h @@ -20,7 +20,7 @@ namespace ImmutablePassportAction #if PLATFORM_ANDROID | PLATFORM_IOS | PLATFORM_MAC const FString GetPKCEAuthUrl = TEXT("getPKCEAuthUrl"); - const FString LOGIN_PKCE = TEXT("loginPKCE");; //+ + const FString LOGIN_PKCE = TEXT("loginPKCE"); const FString CONNECT_PKCE = TEXT("connectPKCE"); #endif @@ -31,10 +31,14 @@ namespace ImmutablePassportAction const FString GetLinkedAddresses = TEXT("getLinkedAddresses"); const FString ImxTransfer = TEXT("imxTransfer"); const FString ImxBatchNftTransfer = TEXT("imxBatchNftTransfer"); - const FString EnvSandbox = TEXT("sandbox"); - const FString EnvProduction = TEXT("production"); const FString ImxIsRegisteredOffchain = TEXT("isRegisteredOffchain"); const FString ImxRegisterOffchain = TEXT("registerOffchain"); const FString TRACK = TEXT("track"); } // namespace ImmutablePassportAction + +namespace ImmutablePassportEnvironmentConstants +{ + const FString EnvironmentSandbox = TEXT("sandbox"); + const FString EnvironmentProduction = TEXT("production"); +} \ No newline at end of file diff --git a/Source/Immutable/Public/Immutable/ImmutablePluginSettings.h b/Source/Immutable/Public/Immutable/ImmutableSettings.h similarity index 72% rename from Source/Immutable/Public/Immutable/ImmutablePluginSettings.h rename to Source/Immutable/Public/Immutable/ImmutableSettings.h index fd875154..002effb4 100644 --- a/Source/Immutable/Public/Immutable/ImmutablePluginSettings.h +++ b/Source/Immutable/Public/Immutable/ImmutableSettings.h @@ -3,16 +3,16 @@ #include "Engine/DeveloperSettings.h" #include "ApplicationConfig.h" -#include "ImmutablePluginSettings.generated.h" +#include "ImmutableSettings.generated.h" /** - * ImmutablePluginSettings is a configuration class for the Immutable plugin. + * Immutable developer settings is a configuration class for the Immutable plugin. * This class contains settings that can be adjusted to control the behavior * of the Immutable plugin within the Unreal Engine environment. */ -UCLASS(config = Game, defaultconfig, meta = (DisplayName = "Immutable Plugin Settings")) -class IMMUTABLE_API UImmutablePluginSettings : public UDeveloperSettings +UCLASS(config = Game, defaultconfig, meta = (DisplayName = "Immutable")) +class IMMUTABLE_API UImmutableSettings : public UDeveloperSettings { GENERATED_BODY() @@ -22,4 +22,5 @@ class IMMUTABLE_API UImmutablePluginSettings : public UDeveloperSettings /// which will be used as the default configuration for the application. UPROPERTY(Config, EditAnywhere, BlueprintReadOnly, Category = "General") TSubclassOf DefaultApplicationConfig; + }; diff --git a/Source/Immutable/Public/Immutable/ImmutableUtilities.h b/Source/Immutable/Public/Immutable/ImmutableUtilities.h index 45af345f..d13015d3 100644 --- a/Source/Immutable/Public/Immutable/ImmutableUtilities.h +++ b/Source/Immutable/Public/Immutable/ImmutableUtilities.h @@ -1,5 +1,4 @@ #pragma once -#include "ApplicationConfig.h" /** A wrapper struct around various Immutable namespace utility and support methods. */ @@ -13,6 +12,4 @@ struct IMMUTABLE_API FImmutableUtilities * @return True if the game bridge content was sucessfully retrieved. Otherwise, false. */ static bool LoadGameBridge(FString& GameBridge); - - static UApplicationConfig* GetDefaultApplicationConfig(); }; diff --git a/Source/ImmutableMarketplace/ImmutableMarketplace.Build.cs b/Source/ImmutableMarketplace/ImmutableMarketplace.Build.cs new file mode 100644 index 00000000..f3eb699c --- /dev/null +++ b/Source/ImmutableMarketplace/ImmutableMarketplace.Build.cs @@ -0,0 +1,44 @@ +using UnrealBuildTool; + +public class ImmutableMarketplace : ModuleRules +{ + public ImmutableMarketplace(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", + "UMG" + } + ); + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "CoreUObject", + "Engine", + "Slate", + "SlateCore", + "DeveloperSettings", + "HTTP" + } + ); + +#if UE_5_1_OR_LATER + PublicDependencyModuleNames.AddRange( + new string[] + { + "WebBrowser", + } + ); +#else + PrivateDependencyModuleNames.Add("BluExtension"); + if (Target.Platform == UnrealTargetPlatform.Win64) + { + PublicDependencyModuleNames.Add("Blu"); + } +#endif + } +} \ No newline at end of file diff --git a/Source/ImmutableMarketplace/Private/ImmutableMarketplace.cpp b/Source/ImmutableMarketplace/Private/ImmutableMarketplace.cpp new file mode 100644 index 00000000..8afdf662 --- /dev/null +++ b/Source/ImmutableMarketplace/Private/ImmutableMarketplace.cpp @@ -0,0 +1,17 @@ +#include "ImmutableMarketplace.h" + +#define LOCTEXT_NAMESPACE "FImmutableMarketplaceModule" + +void FImmutableMarketplaceModule::StartupModule() +{ + +} + +void FImmutableMarketplaceModule::ShutdownModule() +{ + +} + +#undef LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FImmutableMarketplaceModule, ImmutableMarketplace) \ No newline at end of file diff --git a/Source/ImmutableMarketplace/Private/OnRampWebBrowserWidget.cpp b/Source/ImmutableMarketplace/Private/OnRampWebBrowserWidget.cpp new file mode 100644 index 00000000..9747c363 --- /dev/null +++ b/Source/ImmutableMarketplace/Private/OnRampWebBrowserWidget.cpp @@ -0,0 +1,177 @@ +#include "OnRampWebBrowserWidget.h" + +#include "ImmutableMarketplaceSettings.h" +#include "PlatformHttp.h" + +#include "OnRampWidgetConfig.h" + +#if (ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1) +#include "SWebBrowser.h" +#else +#include "UserInterface/BluWebBrowser.h" +#endif + +#define LOCTEXT_NAMESPACE "OnRampWidget" + +DEFINE_LOG_CATEGORY(LogImmutableOnRampWidget); + + +bool UOnRampWidget::IsReady() const +{ + return bIsReady; +} + +void UOnRampWidget::Load(const FString& WalletAddress, const FString& Email, const FString& ProductsAvailed, const FString& ScreenTitle) +{ + if (!WebBrowserWidget.IsValid()) + { + return; + } + + FString UrlToLoad = ComputePath(WalletAddress, Email, ProductsAvailed, ScreenTitle); + CallAndRegister_OnWhenReady(UOnRampWidget::FOnWhenReady::FDelegate::CreateWeakLambda(this, [this, UrlToLoad]() + { + WebBrowserWidget->LoadURL(UrlToLoad); + OnWhenReady.RemoveAll(this); + })); +} + +FDelegateHandle UOnRampWidget::CallAndRegister_OnWhenReady(FOnWhenReady::FDelegate Delegate) +{ + if (bIsReady) + { + Delegate.ExecuteIfBound(); + } + + return OnWhenReady.Add(Delegate); +} + +TSharedRef UOnRampWidget::RebuildWidget() +{ + if (IsDesignTime()) + { + return SNew(SBox) + .HAlign(HAlign_Center) + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(LOCTEXT("OnRamp Web Browser", "OnRamp Web Browser")) + ]; + } + else + { +#if (ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1) + WebBrowserWidget = SNew(SWebBrowser) + .InitialURL(InitialURL) + .ShowControls(false) + .SupportsTransparency(false) + .OnUrlChanged(BIND_UOBJECT_DELEGATE(FOnTextChanged, HandleOnUrlChanged)) + .OnBeforePopup(BIND_UOBJECT_DELEGATE(FOnBeforePopupDelegate, HandleOnBeforePopup)) + .OnConsoleMessage(BIND_UOBJECT_DELEGATE(FOnConsoleMessageDelegate, HandleOnConsoleMessage)); + return WebBrowserWidget.ToSharedRef(); +#else + WebBrowserWidget = SNew(SBluWebBrowser) + .InitialURL(InitialURL) + .OnUrlChanged(BIND_UOBJECT_DELEGATE(FOnTextChanged, HandleOnUrlChanged)); + return WebBrowserWidget.ToSharedRef(); +#endif + } +} + +FString UOnRampWidget::ComputePath(const FString& WalletAddress, const FString& Email, const FString& ProductsAvailed, const FString& ScreenTitle) +{ + auto Settings = GetDefault(); + + UOnRampWidgetConfig* OnRampWidgetConfig = Settings->DefaultOnRampWidgetConfig.GetDefaultObject(); + + if (!OnRampWidgetConfig) + { + UE_LOG(LogImmutableOnRampWidget, Error, TEXT("On ramp widget config is not assigned!")); + + return TEXT(""); + } + + FString Path = OnRampWidgetConfig->GetURL(); + TArray QueryParams; + + QueryParams.Add(FString(TEXT("apiKey=")) + FPlatformHttp::UrlEncode(OnRampWidgetConfig->GetAPIKey())); + QueryParams.Add(FString(TEXT("email=")) + FPlatformHttp::UrlEncode(Email)); + QueryParams.Add(FString(TEXT("walletAddress=")) + FPlatformHttp::UrlEncode(WalletAddress)); + QueryParams.Add(FString(TEXT("themeColor=")) + FPlatformHttp::UrlEncode(OnRampWidgetConfig->GetThemeColor().ToString())); + QueryParams.Add(FString(TEXT("isAutoFillUserData=")) + FPlatformHttp::UrlEncode(OnRampWidgetConfig->IsAutoFillUserData() ? TEXT("true") : TEXT("false"))); + QueryParams.Add(FString(TEXT("disableWalletAddressForm=")) + FPlatformHttp::UrlEncode(OnRampWidgetConfig->IsDisableWalletAddressForm() ? TEXT("true") : TEXT("false"))); + + if (!OnRampWidgetConfig->GetNetwork().IsEmpty()) + { + QueryParams.Add(FString(TEXT("network=")) + FPlatformHttp::UrlEncode(OnRampWidgetConfig->GetNetwork())); + } + + if (!ProductsAvailed.IsEmpty()) + { + QueryParams.Add(FString(TEXT("productsAvailed=")) + FPlatformHttp::UrlEncode(ProductsAvailed)); + } + + if (!ScreenTitle.IsEmpty()) + { + QueryParams.Add(FString(TEXT("exchangeScreenTitle=")) + FPlatformHttp::UrlEncode(ScreenTitle)); + } + + if (!OnRampWidgetConfig->GetDefaultCryptoCurrency().IsEmpty()) + { + QueryParams.Add(FString(TEXT("defaultCryptoCurrency=")) + FPlatformHttp::UrlEncode(OnRampWidgetConfig->GetDefaultCryptoCurrency())); + } + + if (!OnRampWidgetConfig->GetDefaultFiatAmount().IsEmpty()) + { + QueryParams.Add(FString(TEXT("defaultFiatAmount=")) + FPlatformHttp::UrlEncode(OnRampWidgetConfig->GetDefaultFiatAmount())); + } + + if (!OnRampWidgetConfig->GetDefaultFiatCurrency().IsEmpty()) + { + QueryParams.Add(FString(TEXT("defaultFiatCurrency=")) + FPlatformHttp::UrlEncode(OnRampWidgetConfig->GetDefaultFiatCurrency())); + } + + if (!OnRampWidgetConfig->GetDefaultPaymentMethod().IsEmpty()) + { + QueryParams.Add(FString(TEXT("defaultPaymentMethod=")) + FPlatformHttp::UrlEncode(OnRampWidgetConfig->GetDefaultPaymentMethod())); + } + + if (OnRampWidgetConfig->GetCryptoCurrencyList().Num() > 0) + { + QueryParams.Add(FString(TEXT("cryptoCurrencyList=")) + FPlatformHttp::UrlEncode(FString::Join(OnRampWidgetConfig->GetCryptoCurrencyList(), TEXT(",")))); + } + + if (OnRampWidgetConfig->GetDisablePaymentMethods().Num() > 0) + { + QueryParams.Add(FString(TEXT("disablePaymentMethods=")) + FPlatformHttp::UrlEncode(FString::Join(OnRampWidgetConfig->GetDisablePaymentMethods(), TEXT(",")))); + } + + Path += TEXT("?"); + Path += FString::Join(QueryParams, TEXT("&")); + + return Path; +} + +void UOnRampWidget::HandleOnUrlChanged(const FText& Text) +{ + if (Text.EqualToCaseIgnored(FText::FromString(InitialURL))) + { + bIsReady = true; + OnWhenReady.Broadcast(); + OnWhenReady.Clear(); + } +} + +#if (ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1) +void UOnRampWidget::HandleOnConsoleMessage(const FString& Message, const FString& Source, int32 Line, EWebBrowserConsoleLogSeverity Severity) +{ + UE_LOG(LogImmutableOnRampWidget, Log, TEXT("Web Browser console message: %s, Source: %s, Line: %d"), *Message, *Source, Line); +} + +bool UOnRampWidget::HandleOnBeforePopup(FString URL, FString Frame) +{ + return false; +} +#endif + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/ImmutableMarketplace/Public/ImmutableMarketplace.h b/Source/ImmutableMarketplace/Public/ImmutableMarketplace.h new file mode 100644 index 00000000..acc193b9 --- /dev/null +++ b/Source/ImmutableMarketplace/Public/ImmutableMarketplace.h @@ -0,0 +1,11 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Modules/ModuleManager.h" + +class FImmutableMarketplaceModule : public IModuleInterface +{ +public: + virtual void StartupModule() override; + virtual void ShutdownModule() override; +}; diff --git a/Source/ImmutableMarketplace/Public/ImmutableMarketplaceSettings.h b/Source/ImmutableMarketplace/Public/ImmutableMarketplaceSettings.h new file mode 100644 index 00000000..d1da3e1f --- /dev/null +++ b/Source/ImmutableMarketplace/Public/ImmutableMarketplaceSettings.h @@ -0,0 +1,25 @@ +#pragma once + +#include "Engine/DeveloperSettings.h" +#include "OnRampWidgetConfig.h" + +#include "ImmutableMarketplaceSettings.generated.h" + + +/** + * Immutable Marketplace developer settings is a configuration class for the ImmutableMarketplace module. + * This class contains settings that can be adjusted to control the behavior + * of the ImmutableMarketplace tools within the Unreal Engine environment. + */ +UCLASS(config = Game, defaultconfig, meta = (DisplayName = "Immutable Marketplace")) +class IMMUTABLEMARKETPLACE_API UImmutableMarketplaceSettings : public UDeveloperSettings +{ + GENERATED_BODY() + +public: + /// The default on ramp widget configuration class. + /// This property holds a reference to a subclass of UTransakConfig, + /// which is used to load on ramp widget in web browser. + UPROPERTY(Config, EditAnywhere, BlueprintReadOnly) + TSubclassOf DefaultOnRampWidgetConfig; +}; diff --git a/Source/ImmutableMarketplace/Public/OnRampWebBrowserWidget.h b/Source/ImmutableMarketplace/Public/OnRampWebBrowserWidget.h new file mode 100644 index 00000000..a16b8d45 --- /dev/null +++ b/Source/ImmutableMarketplace/Public/OnRampWebBrowserWidget.h @@ -0,0 +1,80 @@ +#pragma once + +#include "Components/Widget.h" + +#include "OnRampWebBrowserWidget.generated.h" + + +DECLARE_LOG_CATEGORY_EXTERN(LogImmutableOnRampWidget, Log, All); + +#if (ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1) +enum class EWebBrowserConsoleLogSeverity; + +class SWebBrowser; +#else +class SBluWebBrowser; +#endif + + +/** + * A custom web browser widget for Immutable On Ramp funds transactions. + */ +UCLASS() +class IMMUTABLEMARKETPLACE_API UOnRampWidget : public UWidget +{ + GENERATED_BODY() + +public: + DECLARE_MULTICAST_DELEGATE(FOnWhenReady); + +public: + /** + * Check if the web browser widget is ready to be loaded. + * + * @return True if the widget is ready, false otherwise. + */ + UFUNCTION(BlueprintPure) + bool IsReady() const; + + /** + * Loads on ramp widget with provided user data. + * + * @param WalletAddress The wallet address to load. + * @param Email The email associated with the user. + * @param ProductsAvailed The products availed by the user. + * @param ScreenTitle The title of the screen to load. + */ + UFUNCTION(BlueprintCallable) + void Load(const FString& WalletAddress, const FString& Email, const FString& ProductsAvailed, const FString& ScreenTitle); + + FDelegateHandle CallAndRegister_OnWhenReady(FOnWhenReady::FDelegate Delegate); + +protected: + // UWidget interface + virtual TSharedRef RebuildWidget() override; + // End of UWidget interface + +protected: + FString ComputePath(const FString& WalletAddress, const FString& Email, const FString& ProductsAvailed, const FString& ScreenTitle); + + void HandleOnUrlChanged(const FText& Text); +#if (ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1) + void HandleOnConsoleMessage(const FString& Message, const FString& Source, int32 Line, EWebBrowserConsoleLogSeverity Severity); + bool HandleOnBeforePopup(FString URL, FString Frame); +#endif + +protected: + /** URL that the browser will initially navigate to. The URL should include the protocol, eg http:// */ + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly) + FString InitialURL = TEXT("about:blank"); + +#if (ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1) + TSharedPtr WebBrowserWidget; +#else + TSharedPtr WebBrowserWidget; +#endif + FOnWhenReady OnWhenReady; + +protected: + bool bIsReady = false; +}; \ No newline at end of file diff --git a/Source/ImmutableMarketplace/Public/OnRampWidgetConfig.h b/Source/ImmutableMarketplace/Public/OnRampWidgetConfig.h new file mode 100644 index 00000000..6d581a0d --- /dev/null +++ b/Source/ImmutableMarketplace/Public/OnRampWidgetConfig.h @@ -0,0 +1,237 @@ +#pragma once + +#include "OnRampWidgetConfig.generated.h" + + +UENUM(BlueprintType) +enum class ETransakEnvironment : uint8 +{ + Sandbox, + Production, +}; + +/** + * @class UOnRampWidgetConfig + * @brief Configuration settings for on ramp widget. + */ +UCLASS(Abstract, Blueprintable, ClassGroup = Immutable) +class IMMUTABLEMARKETPLACE_API UOnRampWidgetConfig : public UObject +{ + GENERATED_BODY() + +public: + /** + * Get the URL based on the current environment setting. + * @return The URL corresponding to the current environment setting. + */ + FString GetURL() const + { + switch (Environment) + { + case ETransakEnvironment::Production: + return TEXT("https://global.transak.com/"); + default: + case ETransakEnvironment::Sandbox: + return TEXT("https://global-stg.transak.com/"); + } + } + + /** + * Get the API key based on the current environment. + * @return The API key corresponding to the current environment. + */ + FString GetAPIKey() const + { + switch (Environment) + { + case ETransakEnvironment::Production: + return TEXT("ad1bca70-d917-4628-bb0f-5609537498bc"); + default: + case ETransakEnvironment::Sandbox: + return TEXT("d14b44fb-0f84-4db5-affb-e044040d724b"); + } + } + + /** + * @details More details could be found under the class parameter + * @return Network as FString + */ + const FString& GetNetwork() const + { + return Network; + } + + /** + * @details More details could be found under the class parameter + * @return DefaultFiatCurrency as FString + */ + const FString& GetDefaultFiatCurrency() const + { + return DefaultFiatCurrency; + } + + /** + * @details More details could be found under the class parameter + * @return DefaultFiatAmount as FString + */ + const FString& GetDefaultFiatAmount() const + { + return DefaultFiatAmount; + } + + /** + * @details More details could be found under the class parameter + * @return DefaultCryptoCurrency as FString + */ + const FString& GetDefaultCryptoCurrency() const + { + return DefaultCryptoCurrency; + } + + /** + * @details More details could be found under the class parameter + * @return DefaultPaymentMethod as FString + */ + const FString& GetDefaultPaymentMethod() const + { + return DefaultPaymentMethod; + } + + /** + * @details More details could be found under the class parameter + * @return DisablePaymentMethods as array of FString + */ + const TArray& GetDisablePaymentMethods() const + { + return DisablePaymentMethods; + } + + /** + * @details More details could be found under the class parameter + * @return bIsAutoFillUserData as bool + */ + bool IsAutoFillUserData() const + { + return bIsAutoFillUserData; + } + + /** + * @details More details could be found under the class parameter + * @return bDisableWalletAddressForm as bool + */ + bool IsDisableWalletAddressForm() const + { + return bDisableWalletAddressForm; + } + + /** + * @details More details could be found under the class parameter + * @return CryptoCurrencyList as array of FString + */ + const TArray& GetCryptoCurrencyList() const + { + return CryptoCurrencyList; + } + + /** + * @details More details could be found under the class parameter + * @return ThemeColor as FLinearColor + */ + const FLinearColor& GetThemeColor() const + { + return ThemeColor; + } + +protected: + /** + * Specifies the environment for transactions. + * @note The default environment is set to the Sandbox environment. + */ + UPROPERTY(EditDefaultsOnly, Category = "General") + ETransakEnvironment Environment = ETransakEnvironment::Sandbox; + + /** + * The default payment method you would prefer the customer to buy/sell with. + * If you pass this param, the payment method will be selected by default and the customer can + * also select another payment method. + */ + UPROPERTY(EditDefaultsOnly, Category = "Fiat") + FString DefaultPaymentMethod; + + /** + * The three letter code of the fiat currency your user will send/receive while buying/selling cryptocurrency. + * Users can change the fiat currency if this is passed. If the fiat currency is not supported by + * a specific product type (BUY/SELL) then the default widget will load with all the supported fiat + * currencies for that product type. + */ + UPROPERTY(EditDefaultsOnly, Category = "Fiat") + FString DefaultFiatCurrency; + + /** + * An integer amount representing how much the customer wants to spend/receive. + * Users can change the fiat amount if this is passed. + */ + UPROPERTY(EditDefaultsOnly, Category = "Fiat") + FString DefaultFiatAmount; + + /** + * The default cryptocurrency you would prefer the customer to buy/sell. + * If you pass this param, the currency will be selected by default, but the customer will + * still be able to select another cryptocurrency. Please ensure that the currency code passed by + * you is available for the specific product type (BUY/SELL). + * If you pass a value that is not supported by BUY/SELL, then the default widget will load. + */ + UPROPERTY(EditDefaultsOnly, Category = "Fiat") + FString DefaultCryptoCurrency; + + /** + * Crypto network that you would allow your customers to buy. + * You can get the supporting networks by opening http://global.transak.com and then go to + * cryptocurrencies select screen. Only the cryptocurrencies supported by this network for + * the specific product type (BUY/SELL) will be shown in the widget. + * If the network selected is not supported by a product type (BUY/SELL) then the default widget will + * all supported networks will be shown. + */ + UPROPERTY(EditDefaultsOnly, Category = "Blockchain") + FString Network; + + /** + * A comma-separated list of payment methods you want to disable and hide from the customers. + * Refer here to the list of supported params for the payment method. + */ + UPROPERTY(EditDefaultsOnly, Category = "Fiat") + TArray DisablePaymentMethods; + + /** + * When true, then the email address will be auto-filled, but the screen will not be skipped. + * User can edit their email address, basic data like first name & the address. + * This parameter will be ignored if email or userData are not passed. + */ + UPROPERTY(EditDefaultsOnly, Category = "User") + bool bIsAutoFillUserData = true; + + /** + * When true, the customer will not be able to change the destination address of + * where the cryptocurrency is sent to. + */ + UPROPERTY(EditDefaultsOnly, Category = "Fiat") + bool bDisableWalletAddressForm = true; + + /** + * A comma-separated list of cryptoCurrencies that you would allow your customers to buy/sell. + * Only these crypto currencies will be shown in the widget. This will be a string of comma + * separated values each of which will represent a valid cryptoCurrency code. + * Please ensure that the crypto currency codes passed in the list are available for the specific + * product type (BUY/SELL). If even one of the crypto currency codes in the list is supported by + * the specific product type (BUY/SELL), then it will be honored, otherwise the default widget will + * load for the product type for which none of the crypto currency codes are supported. + */ + UPROPERTY(EditDefaultsOnly, Category = "Fiat") + TArray CryptoCurrencyList; + + /** + * The theme color code for the widget main color. It is used for buttons, + */ + UPROPERTY(EditDefaultsOnly, Category = "Theme") + FLinearColor ThemeColor; +};