From b43822cfd8dd9db8ecc0841b9c86ca0e27e0538d Mon Sep 17 00:00:00 2001 From: Yermek Garifullanov Date: Wed, 11 Dec 2024 09:56:06 +1300 Subject: [PATCH 01/25] feat: added transak config --- .../Private/Immutable/ImmutableUtilities.cpp | 16 ++ .../Immutable/ImmutablePluginSettings.h | 4 + .../Public/Immutable/ImmutableUtilities.h | 2 + .../Public/Immutable/TransakConfig.h | 139 ++++++++++++++++++ 4 files changed, 161 insertions(+) create mode 100644 Source/Immutable/Public/Immutable/TransakConfig.h diff --git a/Source/Immutable/Private/Immutable/ImmutableUtilities.cpp b/Source/Immutable/Private/Immutable/ImmutableUtilities.cpp index a81db18b..dad2f900 100644 --- a/Source/Immutable/Private/Immutable/ImmutableUtilities.cpp +++ b/Source/Immutable/Private/Immutable/ImmutableUtilities.cpp @@ -27,8 +27,24 @@ UApplicationConfig* FImmutableUtilities::GetDefaultApplicationConfig() if (!Settings) { + IMTBL_ERR("Failed to retrieve default Immutable application configuration") + return nullptr; } return Settings->DefaultApplicationConfig.GetDefaultObject(); } + +UTransakConfig* FImmutableUtilities::GetDefaultTransakConfig() +{ + auto Settings = GetDefault(); + + if (!Settings) + { + IMTBL_ERR("Failed to retrieve default Immutable Transak configuration") + + return nullptr; + } + + return Settings->DefaultTransakConfig.GetDefaultObject(); +} diff --git a/Source/Immutable/Public/Immutable/ImmutablePluginSettings.h b/Source/Immutable/Public/Immutable/ImmutablePluginSettings.h index fd875154..fa1acff2 100644 --- a/Source/Immutable/Public/Immutable/ImmutablePluginSettings.h +++ b/Source/Immutable/Public/Immutable/ImmutablePluginSettings.h @@ -2,6 +2,7 @@ #include "Engine/DeveloperSettings.h" #include "ApplicationConfig.h" +#include "TransakConfig.h" #include "ImmutablePluginSettings.generated.h" @@ -22,4 +23,7 @@ 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; + + UPROPERTY(Config, EditAnywhere, BlueprintReadOnly, Category = "Transak") + TSubclassOf DefaultTransakConfig; }; diff --git a/Source/Immutable/Public/Immutable/ImmutableUtilities.h b/Source/Immutable/Public/Immutable/ImmutableUtilities.h index 45af345f..6c788d2c 100644 --- a/Source/Immutable/Public/Immutable/ImmutableUtilities.h +++ b/Source/Immutable/Public/Immutable/ImmutableUtilities.h @@ -1,5 +1,6 @@ #pragma once #include "ApplicationConfig.h" +#include "TransakConfig.h" /** A wrapper struct around various Immutable namespace utility and support methods. */ @@ -15,4 +16,5 @@ struct IMMUTABLE_API FImmutableUtilities static bool LoadGameBridge(FString& GameBridge); static UApplicationConfig* GetDefaultApplicationConfig(); + static UTransakConfig* GetDefaultTransakConfig(); }; diff --git a/Source/Immutable/Public/Immutable/TransakConfig.h b/Source/Immutable/Public/Immutable/TransakConfig.h new file mode 100644 index 00000000..b998970b --- /dev/null +++ b/Source/Immutable/Public/Immutable/TransakConfig.h @@ -0,0 +1,139 @@ +#pragma once +#include "ImmutableEnums.h" + +#include "TransakConfig.generated.h" + + +UENUM() +enum class ETransakEnvironment : uint8 +{ + Sandbox, + Production, +}; + +/** + * @class UTransakConfig + * @brief Configuration settings for Passport and various APIs. + * @details This class stores configuration settings such as URLs, chain names, contract addresses, + * client IDs, and environment settings for the zkEVM API, Orderbook API, and Passport. + */ +UCLASS(Abstract, Blueprintable, ClassGroup = Immutable) +class UTransakConfig : public UObject +{ + GENERATED_BODY() + +public: + 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/"); + } + } + + 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"); + } + } + + const FString& GetNetwork() + { + return Network; + } + + const FString& GetDefaultFiatCurrency() + { + return DefaultFiatCurrency; + } + + const FString& GetDefaultFiatAmount() + { + return DefaultFiatAmount; + } + + const FString& GetDefaultCryptoCurrency() + { + return DefaultCryptoCurrency; + } + + const FString& GetDefaultPaymentMethod() + { + return DefaultPaymentMethod; + } + + const TArray& GetDisablePaymentMethods() + { + return DisablePaymentMethods; + } + + bool IsAutoFillUserData() + { + return bIsAutoFillUserData; + } + + bool DisableWalletAddressForm() + { + return bDisableWalletAddressForm; + } + + const TArray& GetCryptoCurrencyList() + { + return CryptoCurrencyList; + } + + const FLinearColor& GetThemeColor() + { + return ThemeColor; + } + +protected: + // UPROPERTY(EditDefaultsOnly, Category = "Transak") + // FString URL; + // + // UPROPERTY(EditDefaultsOnly, Category = "Transak") + // FString APIKey; + + UPROPERTY(EditDefaultsOnly, Category = "General") + ETransakEnvironment Environment = ETransakEnvironment::Sandbox; + + UPROPERTY(EditDefaultsOnly, Category = "Fiat") + FString DefaultPaymentMethod; + + UPROPERTY(EditDefaultsOnly, Category = "Fiat") + FString DefaultFiatCurrency; + + UPROPERTY(EditDefaultsOnly, Category = "Fiat") + FString DefaultFiatAmount; + + UPROPERTY(EditDefaultsOnly, Category = "Fiat") + FString DefaultCryptoCurrency; + + UPROPERTY(EditDefaultsOnly, Category = "Transak") + FString Network; + + UPROPERTY(EditDefaultsOnly, Category = "Fiat") + TArray DisablePaymentMethods; + + UPROPERTY(EditDefaultsOnly, Category = "User") + bool bIsAutoFillUserData = true; + + UPROPERTY(EditDefaultsOnly, Category = "Fiat") + bool bDisableWalletAddressForm = true; + + UPROPERTY(EditDefaultsOnly, Category = "Fiat") + TArray CryptoCurrencyList; + + UPROPERTY(EditDefaultsOnly, Category = "Theme") + FLinearColor ThemeColor; +}; From 03bc8bf37f60b006394abec828d4aa6e8b4a3f30 Mon Sep 17 00:00:00 2001 From: Yermek Garifullanov Date: Thu, 12 Dec 2024 11:28:04 +1300 Subject: [PATCH 02/25] feat: added transak web widget and environment enum --- .../Immutable/Transak/TransakWebBrowser.cpp | 149 ++++++++++++++++++ .../Public/Immutable/ImmutableEnums.h | 10 ++ .../Immutable/Transak/TransakWebBrowser.h | 41 +++++ 3 files changed, 200 insertions(+) create mode 100644 Source/Immutable/Private/Immutable/Transak/TransakWebBrowser.cpp create mode 100644 Source/Immutable/Public/Immutable/ImmutableEnums.h create mode 100644 Source/Immutable/Public/Immutable/Transak/TransakWebBrowser.h diff --git a/Source/Immutable/Private/Immutable/Transak/TransakWebBrowser.cpp b/Source/Immutable/Private/Immutable/Transak/TransakWebBrowser.cpp new file mode 100644 index 00000000..d6efcd3a --- /dev/null +++ b/Source/Immutable/Private/Immutable/Transak/TransakWebBrowser.cpp @@ -0,0 +1,149 @@ +#include "Immutable/Transak/TransakWebBrowser.h" + +#include "SWebBrowser.h" +#include "PlatformHttp.h" +#include "Immutable/ImmutableUtilities.h" +#include "Immutable/TransakConfig.h" + + +#define LOCTEXT_NAMESPACE "Immutable" + +FDelegateHandle UTransakWebBrowser::CallAndRegister_OnWhenReady(FOnWhenReady::FDelegate Delegate) +{ + if (bIsReady) + { + Delegate.ExecuteIfBound(); + } + + return OnWhenReady.Add(Delegate); +} + +TSharedRef UTransakWebBrowser::RebuildWidget() +{ + if ( IsDesignTime() ) + { + return SNew(SBox) + .HAlign(HAlign_Center) + .VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(LOCTEXT("Transak Web Browser", "Transak Web Browser")) + ]; + } + else + { + WebBrowserWidget = SNew(SWebBrowser) + .InitialURL(TEXT("about:blank")) + .ShowControls(false) + .SupportsTransparency(bSupportsTransparency) + .OnUrlChanged(BIND_UOBJECT_DELEGATE(FOnTextChanged, OnUrlChanged)) + .OnBeforePopup(BIND_UOBJECT_DELEGATE(FOnBeforePopupDelegate, HandleOnBeforePopup)) + .OnConsoleMessage(BIND_UOBJECT_DELEGATE(FOnConsoleMessageDelegate, HandleOnConsoleMessage)); + + return WebBrowserWidget.ToSharedRef(); + } +} + +void UTransakWebBrowser::OnUrlChanged(const FText& Text) +{ + HandleOnUrlChanged(Text); + + if (Text.EqualToCaseIgnored(FText::FromString(TEXT("about:blank")))) + { + bIsReady = true; + OnWhenReady.Broadcast(); + } +} + +void UTransakWebBrowser::Load(const FString& WalletAddress, const FString& Email, const FString& ProductsAvailed, const FString& ScreenTitle) +{ + if (!WebBrowserWidget.IsValid()) + { + return; + } + + FString UrlToLoad = ComputePath(WalletAddress, Email, ProductsAvailed, ScreenTitle); + + if (bIsReady) + { + WebBrowserWidget->LoadURL(UrlToLoad); + } + else + { + FDelegateHandle OnWhenReadyHandle = CallAndRegister_OnWhenReady(UTransakWebBrowser::FOnWhenReady::FDelegate::CreateWeakLambda(this, [this, UrlToLoad, OnWhenReadyHandle]() + { + WebBrowserWidget->LoadURL(UrlToLoad); + OnWhenReady.Remove(OnWhenReadyHandle); + })); + } +} + +FString UTransakWebBrowser::ComputePath(const FString& WalletAddress, const FString& Email, const FString& ProductsAvailed, const FString& ScreenTitle) +{ + UTransakConfig* TransakConfig = FImmutableUtilities::GetDefaultTransakConfig(); + + if (!TransakConfig) + { + return ""; + } + + FString Path = TransakConfig->GetURL(); + TArray QueryParams; + + QueryParams.Add(FString(TEXT("apiKey=")) + FPlatformHttp::UrlEncode(TransakConfig->GetAPIKey())); + QueryParams.Add(FString(TEXT("email=")) + FPlatformHttp::UrlEncode(Email)); + QueryParams.Add(FString(TEXT("walletAddress=")) + FPlatformHttp::UrlEncode(WalletAddress)); + QueryParams.Add(FString(TEXT("themeColor=")) + FPlatformHttp::UrlEncode(TransakConfig->GetThemeColor().ToString())); + QueryParams.Add(FString(TEXT("isAutoFillUserData=")) + FPlatformHttp::UrlEncode(TransakConfig->IsAutoFillUserData() ? TEXT("true") : TEXT("false"))); + QueryParams.Add(FString(TEXT("disableWalletAddressForm=")) + FPlatformHttp::UrlEncode(TransakConfig->DisableWalletAddressForm() ? TEXT("true") : TEXT("false"))); + + if (!TransakConfig->GetNetwork().IsEmpty()) + { + QueryParams.Add(FString(TEXT("network=")) + FPlatformHttp::UrlEncode(TransakConfig->GetNetwork())); + } + + if (!ProductsAvailed.IsEmpty()) + { + QueryParams.Add(FString(TEXT("productsAvailed=")) + FPlatformHttp::UrlEncode(ProductsAvailed)); + } + + if (!ScreenTitle.IsEmpty()) + { + QueryParams.Add(FString(TEXT("exchangeScreenTitle=")) + FPlatformHttp::UrlEncode(ScreenTitle)); + } + + if (!TransakConfig->GetDefaultCryptoCurrency().IsEmpty()) + { + QueryParams.Add(FString(TEXT("defaultCryptoCurrency=")) + FPlatformHttp::UrlEncode(TransakConfig->GetDefaultCryptoCurrency())); + } + + if (!TransakConfig->GetDefaultFiatAmount().IsEmpty()) + { + QueryParams.Add(FString(TEXT("defaultFiatAmount=")) + FPlatformHttp::UrlEncode(TransakConfig->GetDefaultFiatAmount())); + } + + if (!TransakConfig->GetDefaultFiatCurrency().IsEmpty()) + { + QueryParams.Add(FString(TEXT("defaultFiatCurrency=")) + FPlatformHttp::UrlEncode(TransakConfig->GetDefaultFiatCurrency())); + } + + if (!TransakConfig->GetDefaultPaymentMethod().IsEmpty()) + { + QueryParams.Add(FString(TEXT("defaultPaymentMethod=")) + FPlatformHttp::UrlEncode(TransakConfig->GetDefaultPaymentMethod())); + } + + if (TransakConfig->GetCryptoCurrencyList().Num() > 0) + { + QueryParams.Add(FString(TEXT("cryptoCurrencyList=")) + FPlatformHttp::UrlEncode(FString::Join(TransakConfig->GetCryptoCurrencyList(), TEXT(",")))); + } + + if (TransakConfig->GetDisablePaymentMethods().Num() > 0) + { + QueryParams.Add(FString(TEXT("disablePaymentMethods=")) + FPlatformHttp::UrlEncode(FString::Join(TransakConfig->GetDisablePaymentMethods(), TEXT(",")))); + } + + Path += TEXT("?"); + Path += FString::Join(QueryParams, TEXT("&")); + + return Path; +} diff --git a/Source/Immutable/Public/Immutable/ImmutableEnums.h b/Source/Immutable/Public/Immutable/ImmutableEnums.h new file mode 100644 index 00000000..ebe3fb39 --- /dev/null +++ b/Source/Immutable/Public/Immutable/ImmutableEnums.h @@ -0,0 +1,10 @@ +#pragma once + +UENUM() +enum class EPassportEnvironment : uint8 +{ + Development, + Sandbox, + Production, +}; + diff --git a/Source/Immutable/Public/Immutable/Transak/TransakWebBrowser.h b/Source/Immutable/Public/Immutable/Transak/TransakWebBrowser.h new file mode 100644 index 00000000..3972d819 --- /dev/null +++ b/Source/Immutable/Public/Immutable/Transak/TransakWebBrowser.h @@ -0,0 +1,41 @@ +#pragma once + +#include "CoreMinimal.h" +#include "WebBrowser.h" + +#include "TransakWebBrowser.generated.h" + +/** + * + */ +UCLASS() +class IMMUTABLE_API UTransakWebBrowser : public UWebBrowser +{ + GENERATED_BODY() + +public: + DECLARE_MULTICAST_DELEGATE(FOnWhenReady); + + UFUNCTION(BlueprintCallable) + void Load(const FString& WalletAddress, const FString& Email, const FString& ProductsAvailed, const FString& ScreenTitle); + + bool IsReady() const { return bIsReady; }; + + FDelegateHandle CallAndRegister_OnWhenReady(FOnWhenReady::FDelegate Delegate); + +protected: + // UWidget interface + virtual TSharedRef RebuildWidget() override; + // End of UWidget interface + +private: + FString ComputePath(const FString& WalletAddress, const FString& Email, const FString& ProductsAvailed, const FString& ScreenTitle); + void OnUrlChanged(const FText& Text); + +protected: + FOnWhenReady OnWhenReady; + +private: + bool bIsReady = false; + +}; From efc18e53eb386422b8bdff5725f8b5c79b048f38 Mon Sep 17 00:00:00 2001 From: Yermek Garifullanov Date: Thu, 12 Dec 2024 11:28:24 +1300 Subject: [PATCH 03/25] feat: added missing module --- Source/Immutable/Immutable.Build.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Immutable/Immutable.Build.cs b/Source/Immutable/Immutable.Build.cs index 953881b2..3c2bd23e 100644 --- a/Source/Immutable/Immutable.Build.cs +++ b/Source/Immutable/Immutable.Build.cs @@ -34,6 +34,7 @@ public Immutable(ReadOnlyTargetRules Target) : base(Target) "UMG", "Projects", "DeveloperSettings", + "HTTP", } ); From a04a6b9f8fe44256acefcb220a1d372ff3dffbb7 Mon Sep 17 00:00:00 2001 From: Yermek Garifullanov Date: Thu, 12 Dec 2024 20:44:50 +1300 Subject: [PATCH 04/25] fix: resolved compile issue with ue4 --- Source/Immutable/Immutable.Build.cs | 4 +++- .../Private/Immutable/Transak/TransakWebBrowser.cpp | 6 ++++-- .../Immutable/Public/Immutable/Transak/TransakWebBrowser.h | 1 + 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Source/Immutable/Immutable.Build.cs b/Source/Immutable/Immutable.Build.cs index 3c2bd23e..ded64fd8 100644 --- a/Source/Immutable/Immutable.Build.cs +++ b/Source/Immutable/Immutable.Build.cs @@ -19,7 +19,9 @@ public Immutable(ReadOnlyTargetRules Target) : base(Target) new string[] { "Core", - "JsonUtilities", + "JsonUtilities", + "WebBrowser", + "WebBrowserWidget", } ); diff --git a/Source/Immutable/Private/Immutable/Transak/TransakWebBrowser.cpp b/Source/Immutable/Private/Immutable/Transak/TransakWebBrowser.cpp index d6efcd3a..ecf8b46b 100644 --- a/Source/Immutable/Private/Immutable/Transak/TransakWebBrowser.cpp +++ b/Source/Immutable/Private/Immutable/Transak/TransakWebBrowser.cpp @@ -38,8 +38,10 @@ TSharedRef UTransakWebBrowser::RebuildWidget() .SupportsTransparency(bSupportsTransparency) .OnUrlChanged(BIND_UOBJECT_DELEGATE(FOnTextChanged, OnUrlChanged)) .OnBeforePopup(BIND_UOBJECT_DELEGATE(FOnBeforePopupDelegate, HandleOnBeforePopup)) - .OnConsoleMessage(BIND_UOBJECT_DELEGATE(FOnConsoleMessageDelegate, HandleOnConsoleMessage)); - +#if (ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1) + .OnConsoleMessage(BIND_UOBJECT_DELEGATE(FOnConsoleMessageDelegate, HandleOnConsoleMessage)) +#endif + ; return WebBrowserWidget.ToSharedRef(); } } diff --git a/Source/Immutable/Public/Immutable/Transak/TransakWebBrowser.h b/Source/Immutable/Public/Immutable/Transak/TransakWebBrowser.h index 3972d819..e1bdcf90 100644 --- a/Source/Immutable/Public/Immutable/Transak/TransakWebBrowser.h +++ b/Source/Immutable/Public/Immutable/Transak/TransakWebBrowser.h @@ -2,6 +2,7 @@ #include "CoreMinimal.h" #include "WebBrowser.h" +#include "WebBrowser.h" #include "TransakWebBrowser.generated.h" From 560900864d8f2a54558835f481c039c207869019 Mon Sep 17 00:00:00 2001 From: Yermek Garifullanov Date: Fri, 13 Dec 2024 14:06:38 +1300 Subject: [PATCH 05/25] fix: compiler issue with ue4 --- Source/Immutable/Immutable.Build.cs | 2 ++ .../Immutable/Transak/TransakWebBrowser.cpp | 16 +++++++++------- .../Public/Immutable/Transak/TransakWebBrowser.h | 15 +++++++++++---- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/Source/Immutable/Immutable.Build.cs b/Source/Immutable/Immutable.Build.cs index ded64fd8..a9079a6c 100644 --- a/Source/Immutable/Immutable.Build.cs +++ b/Source/Immutable/Immutable.Build.cs @@ -20,8 +20,10 @@ public Immutable(ReadOnlyTargetRules Target) : base(Target) { "Core", "JsonUtilities", +#if UE_5_1_OR_LATER "WebBrowser", "WebBrowserWidget", +#endif } ); diff --git a/Source/Immutable/Private/Immutable/Transak/TransakWebBrowser.cpp b/Source/Immutable/Private/Immutable/Transak/TransakWebBrowser.cpp index ecf8b46b..7d6d64d1 100644 --- a/Source/Immutable/Private/Immutable/Transak/TransakWebBrowser.cpp +++ b/Source/Immutable/Private/Immutable/Transak/TransakWebBrowser.cpp @@ -1,6 +1,5 @@ #include "Immutable/Transak/TransakWebBrowser.h" -#include "SWebBrowser.h" #include "PlatformHttp.h" #include "Immutable/ImmutableUtilities.h" #include "Immutable/TransakConfig.h" @@ -32,24 +31,23 @@ TSharedRef UTransakWebBrowser::RebuildWidget() } else { +#if (ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1) WebBrowserWidget = SNew(SWebBrowser) .InitialURL(TEXT("about:blank")) .ShowControls(false) .SupportsTransparency(bSupportsTransparency) .OnUrlChanged(BIND_UOBJECT_DELEGATE(FOnTextChanged, OnUrlChanged)) .OnBeforePopup(BIND_UOBJECT_DELEGATE(FOnBeforePopupDelegate, HandleOnBeforePopup)) -#if (ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1) - .OnConsoleMessage(BIND_UOBJECT_DELEGATE(FOnConsoleMessageDelegate, HandleOnConsoleMessage)) -#endif - ; + .OnConsoleMessage(BIND_UOBJECT_DELEGATE(FOnConsoleMessageDelegate, HandleOnConsoleMessage)); return WebBrowserWidget.ToSharedRef(); +#else + return SNullWidget::NullWidget; +#endif } } void UTransakWebBrowser::OnUrlChanged(const FText& Text) { - HandleOnUrlChanged(Text); - if (Text.EqualToCaseIgnored(FText::FromString(TEXT("about:blank")))) { bIsReady = true; @@ -68,13 +66,17 @@ void UTransakWebBrowser::Load(const FString& WalletAddress, const FString& Email if (bIsReady) { +#if (ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1) WebBrowserWidget->LoadURL(UrlToLoad); +#endif } else { FDelegateHandle OnWhenReadyHandle = CallAndRegister_OnWhenReady(UTransakWebBrowser::FOnWhenReady::FDelegate::CreateWeakLambda(this, [this, UrlToLoad, OnWhenReadyHandle]() { +#if (ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1) WebBrowserWidget->LoadURL(UrlToLoad); +#endif OnWhenReady.Remove(OnWhenReadyHandle); })); } diff --git a/Source/Immutable/Public/Immutable/Transak/TransakWebBrowser.h b/Source/Immutable/Public/Immutable/Transak/TransakWebBrowser.h index e1bdcf90..c4dbedc3 100644 --- a/Source/Immutable/Public/Immutable/Transak/TransakWebBrowser.h +++ b/Source/Immutable/Public/Immutable/Transak/TransakWebBrowser.h @@ -1,8 +1,10 @@ #pragma once -#include "CoreMinimal.h" -#include "WebBrowser.h" -#include "WebBrowser.h" +#include "Components/Widget.h" + +#if (ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1) +#include "SWebBrowser.h" +#endif #include "TransakWebBrowser.generated.h" @@ -10,7 +12,7 @@ * */ UCLASS() -class IMMUTABLE_API UTransakWebBrowser : public UWebBrowser +class IMMUTABLE_API UTransakWebBrowser : public UWidget { GENERATED_BODY() @@ -34,6 +36,11 @@ class IMMUTABLE_API UTransakWebBrowser : public UWebBrowser void OnUrlChanged(const FText& Text); protected: +#if (ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1) + TSharedPtr WebBrowserWidget; +#else + TSharedPtr WebBrowserWidget; +#endif FOnWhenReady OnWhenReady; private: From 5d92e40d9ae4ef0048c8f82ebbd925142bb551b1 Mon Sep 17 00:00:00 2001 From: Yermek Garifullanov Date: Fri, 13 Dec 2024 15:20:23 +1300 Subject: [PATCH 06/25] chore: isolated source code for ue5 --- .../Immutable/Transak/TransakWebBrowser.cpp | 19 ++++++++++++++++--- .../Immutable/Transak/TransakWebBrowser.h | 7 ++++++- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/Source/Immutable/Private/Immutable/Transak/TransakWebBrowser.cpp b/Source/Immutable/Private/Immutable/Transak/TransakWebBrowser.cpp index 7d6d64d1..3f417d8e 100644 --- a/Source/Immutable/Private/Immutable/Transak/TransakWebBrowser.cpp +++ b/Source/Immutable/Private/Immutable/Transak/TransakWebBrowser.cpp @@ -3,6 +3,7 @@ #include "PlatformHttp.h" #include "Immutable/ImmutableUtilities.h" #include "Immutable/TransakConfig.h" +#include "Immutable/Misc/ImtblLogging.h" #define LOCTEXT_NAMESPACE "Immutable" @@ -35,8 +36,8 @@ TSharedRef UTransakWebBrowser::RebuildWidget() WebBrowserWidget = SNew(SWebBrowser) .InitialURL(TEXT("about:blank")) .ShowControls(false) - .SupportsTransparency(bSupportsTransparency) - .OnUrlChanged(BIND_UOBJECT_DELEGATE(FOnTextChanged, OnUrlChanged)) + .SupportsTransparency(false) + .OnUrlChanged(BIND_UOBJECT_DELEGATE(FOnTextChanged, HandleOnUrlChanged)) .OnBeforePopup(BIND_UOBJECT_DELEGATE(FOnBeforePopupDelegate, HandleOnBeforePopup)) .OnConsoleMessage(BIND_UOBJECT_DELEGATE(FOnConsoleMessageDelegate, HandleOnConsoleMessage)); return WebBrowserWidget.ToSharedRef(); @@ -46,7 +47,8 @@ TSharedRef UTransakWebBrowser::RebuildWidget() } } -void UTransakWebBrowser::OnUrlChanged(const FText& Text) +#if (ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1) +void UTransakWebBrowser::HandleOnUrlChanged(const FText& Text) { if (Text.EqualToCaseIgnored(FText::FromString(TEXT("about:blank")))) { @@ -55,6 +57,17 @@ void UTransakWebBrowser::OnUrlChanged(const FText& Text) } } +void UTransakWebBrowser::HandleOnConsoleMessage(const FString& Message, const FString& Source, int32 Line, EWebBrowserConsoleLogSeverity Severity) +{ + IMTBL_LOG("Transak Web Browser console message: %s, Source: %s, Line: %d", *Message, *Source, Line); +} + +bool UTransakWebBrowser::HandleOnBeforePopup(FString URL, FString Frame) +{ + return false; +} +#endif + void UTransakWebBrowser::Load(const FString& WalletAddress, const FString& Email, const FString& ProductsAvailed, const FString& ScreenTitle) { if (!WebBrowserWidget.IsValid()) diff --git a/Source/Immutable/Public/Immutable/Transak/TransakWebBrowser.h b/Source/Immutable/Public/Immutable/Transak/TransakWebBrowser.h index c4dbedc3..7dcdb30a 100644 --- a/Source/Immutable/Public/Immutable/Transak/TransakWebBrowser.h +++ b/Source/Immutable/Public/Immutable/Transak/TransakWebBrowser.h @@ -33,7 +33,12 @@ class IMMUTABLE_API UTransakWebBrowser : public UWidget private: FString ComputePath(const FString& WalletAddress, const FString& Email, const FString& ProductsAvailed, const FString& ScreenTitle); - void OnUrlChanged(const FText& Text); + +#if (ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1) + void HandleOnConsoleMessage(const FString& Message, const FString& Source, int32 Line, EWebBrowserConsoleLogSeverity Severity); + void HandleOnUrlChanged(const FText& Text); + bool HandleOnBeforePopup(FString URL, FString Frame); +#endif protected: #if (ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1) From 44295f112151cbb86113606d83363e03a6341a67 Mon Sep 17 00:00:00 2001 From: Yermek Garifullanov <150758974+YermekG@users.noreply.github.com> Date: Thu, 19 Dec 2024 11:57:02 +1300 Subject: [PATCH 07/25] chore: removed requirement of folder name in branch name (#157) --- .github/workflows/branch-name.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/branch-name.yml b/.github/workflows/branch-name.yml index 81a679a8..57bf7a20 100644 --- a/.github/workflows/branch-name.yml +++ b/.github/workflows/branch-name.yml @@ -8,7 +8,7 @@ jobs: steps: - uses: deepakputhraya/action-branch-name@master with: - regex: '([a-z])+\/([a-z0-9.-])+' + 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 From ec6a8eb565ac879c4c7ff7d62fdd50509319720d Mon Sep 17 00:00:00 2001 From: ImmutableJeffrey Date: Thu, 19 Dec 2024 10:15:36 +1000 Subject: [PATCH 08/25] feat: add 4.26 support to transak widget (#156) * feat: added transak web widget and environment enum * fix: resolved compile issue with ue4 * fix: compiler issue with ue4 * chore: isolated source code for ue5 * feat: add blu web browser to transak widget (#3498) * refactor: apply formatting * refactor: move include inside respective engine macro * refactor: expose is ready to blueprint * refactor: change private to protected * refactor: reorder .h and .cpp functions to match each other * chore: update loctext namespace * fix: compile error * chore: removed redundant code * chore: removed prefixes --------- Co-authored-by: Yermek Garifullanov --- .github/workflows/branch-name.yml | 1 - Source/Immutable/Immutable.Build.cs | 1 + .../Immutable/Transak/TransakWebBrowser.cpp | 211 +++++++++--------- .../Immutable/Transak/TransakWebBrowser.h | 33 ++- 4 files changed, 129 insertions(+), 117 deletions(-) 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/Source/Immutable/Immutable.Build.cs b/Source/Immutable/Immutable.Build.cs index a9079a6c..dc08b2d2 100644 --- a/Source/Immutable/Immutable.Build.cs +++ b/Source/Immutable/Immutable.Build.cs @@ -30,6 +30,7 @@ public Immutable(ReadOnlyTargetRules Target) : base(Target) PrivateDependencyModuleNames.AddRange( new string[] { + "BluExtension", "CoreUObject", "Engine", "Slate", diff --git a/Source/Immutable/Private/Immutable/Transak/TransakWebBrowser.cpp b/Source/Immutable/Private/Immutable/Transak/TransakWebBrowser.cpp index 3f417d8e..1f2d1486 100644 --- a/Source/Immutable/Private/Immutable/Transak/TransakWebBrowser.cpp +++ b/Source/Immutable/Private/Immutable/Transak/TransakWebBrowser.cpp @@ -1,12 +1,39 @@ #include "Immutable/Transak/TransakWebBrowser.h" #include "PlatformHttp.h" + #include "Immutable/ImmutableUtilities.h" #include "Immutable/TransakConfig.h" + +#if (ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1) +#include "SWebBrowser.h" + #include "Immutable/Misc/ImtblLogging.h" +#else +#include "UserInterface/BluWebBrowser.h" +#endif + +#define LOCTEXT_NAMESPACE "TransakWebBrowser" + +bool UTransakWebBrowser::IsReady() const +{ + return bIsReady; +} +void UTransakWebBrowser::Load(const FString& WalletAddress, const FString& Email, const FString& ProductsAvailed, const FString& ScreenTitle) +{ + if (!WebBrowserWidget.IsValid()) + { + return; + } -#define LOCTEXT_NAMESPACE "Immutable" + FString UrlToLoad = ComputePath(WalletAddress, Email, ProductsAvailed, ScreenTitle); + FDelegateHandle OnWhenReadyHandle = CallAndRegister_OnWhenReady(UTransakWebBrowser::FOnWhenReady::FDelegate::CreateWeakLambda(this, [this, UrlToLoad, OnWhenReadyHandle]() + { + WebBrowserWidget->LoadURL(UrlToLoad); + OnWhenReady.Remove(OnWhenReadyHandle); + })); +} FDelegateHandle UTransakWebBrowser::CallAndRegister_OnWhenReady(FOnWhenReady::FDelegate Delegate) { @@ -20,7 +47,7 @@ FDelegateHandle UTransakWebBrowser::CallAndRegister_OnWhenReady(FOnWhenReady::FD TSharedRef UTransakWebBrowser::RebuildWidget() { - if ( IsDesignTime() ) + if (IsDesignTime()) { return SNew(SBox) .HAlign(HAlign_Center) @@ -34,7 +61,7 @@ TSharedRef UTransakWebBrowser::RebuildWidget() { #if (ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1) WebBrowserWidget = SNew(SWebBrowser) - .InitialURL(TEXT("about:blank")) + .InitialURL(InitialURL) .ShowControls(false) .SupportsTransparency(false) .OnUrlChanged(BIND_UOBJECT_DELEGATE(FOnTextChanged, HandleOnUrlChanged)) @@ -42,21 +69,94 @@ TSharedRef UTransakWebBrowser::RebuildWidget() .OnConsoleMessage(BIND_UOBJECT_DELEGATE(FOnConsoleMessageDelegate, HandleOnConsoleMessage)); return WebBrowserWidget.ToSharedRef(); #else - return SNullWidget::NullWidget; + WebBrowserWidget = SNew(SBluWebBrowser) + .InitialURL(InitialURL) + .OnUrlChanged(BIND_UOBJECT_DELEGATE(FOnTextChanged, HandleOnUrlChanged)); + return WebBrowserWidget.ToSharedRef(); #endif } } -#if (ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1) +FString UTransakWebBrowser::ComputePath(const FString& WalletAddress, const FString& Email, const FString& ProductsAvailed, const FString& ScreenTitle) +{ + UTransakConfig* TransakConfig = FImmutableUtilities::GetDefaultTransakConfig(); + + if (!TransakConfig) + { + return ""; + } + + FString Path = TransakConfig->GetURL(); + TArray QueryParams; + + QueryParams.Add(FString(TEXT("apiKey=")) + FPlatformHttp::UrlEncode(TransakConfig->GetAPIKey())); + QueryParams.Add(FString(TEXT("email=")) + FPlatformHttp::UrlEncode(Email)); + QueryParams.Add(FString(TEXT("walletAddress=")) + FPlatformHttp::UrlEncode(WalletAddress)); + QueryParams.Add(FString(TEXT("themeColor=")) + FPlatformHttp::UrlEncode(TransakConfig->GetThemeColor().ToString())); + QueryParams.Add(FString(TEXT("isAutoFillUserData=")) + FPlatformHttp::UrlEncode(TransakConfig->IsAutoFillUserData() ? TEXT("true") : TEXT("false"))); + QueryParams.Add(FString(TEXT("disableWalletAddressForm=")) + FPlatformHttp::UrlEncode(TransakConfig->DisableWalletAddressForm() ? TEXT("true") : TEXT("false"))); + + if (!TransakConfig->GetNetwork().IsEmpty()) + { + QueryParams.Add(FString(TEXT("network=")) + FPlatformHttp::UrlEncode(TransakConfig->GetNetwork())); + } + + if (!ProductsAvailed.IsEmpty()) + { + QueryParams.Add(FString(TEXT("productsAvailed=")) + FPlatformHttp::UrlEncode(ProductsAvailed)); + } + + if (!ScreenTitle.IsEmpty()) + { + QueryParams.Add(FString(TEXT("exchangeScreenTitle=")) + FPlatformHttp::UrlEncode(ScreenTitle)); + } + + if (!TransakConfig->GetDefaultCryptoCurrency().IsEmpty()) + { + QueryParams.Add(FString(TEXT("defaultCryptoCurrency=")) + FPlatformHttp::UrlEncode(TransakConfig->GetDefaultCryptoCurrency())); + } + + if (!TransakConfig->GetDefaultFiatAmount().IsEmpty()) + { + QueryParams.Add(FString(TEXT("defaultFiatAmount=")) + FPlatformHttp::UrlEncode(TransakConfig->GetDefaultFiatAmount())); + } + + if (!TransakConfig->GetDefaultFiatCurrency().IsEmpty()) + { + QueryParams.Add(FString(TEXT("defaultFiatCurrency=")) + FPlatformHttp::UrlEncode(TransakConfig->GetDefaultFiatCurrency())); + } + + if (!TransakConfig->GetDefaultPaymentMethod().IsEmpty()) + { + QueryParams.Add(FString(TEXT("defaultPaymentMethod=")) + FPlatformHttp::UrlEncode(TransakConfig->GetDefaultPaymentMethod())); + } + + if (TransakConfig->GetCryptoCurrencyList().Num() > 0) + { + QueryParams.Add(FString(TEXT("cryptoCurrencyList=")) + FPlatformHttp::UrlEncode(FString::Join(TransakConfig->GetCryptoCurrencyList(), TEXT(",")))); + } + + if (TransakConfig->GetDisablePaymentMethods().Num() > 0) + { + QueryParams.Add(FString(TEXT("disablePaymentMethods=")) + FPlatformHttp::UrlEncode(FString::Join(TransakConfig->GetDisablePaymentMethods(), TEXT(",")))); + } + + Path += TEXT("?"); + Path += FString::Join(QueryParams, TEXT("&")); + + return Path; +} + void UTransakWebBrowser::HandleOnUrlChanged(const FText& Text) { - if (Text.EqualToCaseIgnored(FText::FromString(TEXT("about:blank")))) + if (Text.EqualToCaseIgnored(FText::FromString(InitialURL))) { bIsReady = true; OnWhenReady.Broadcast(); } } +#if (ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1) void UTransakWebBrowser::HandleOnConsoleMessage(const FString& Message, const FString& Source, int32 Line, EWebBrowserConsoleLogSeverity Severity) { IMTBL_LOG("Transak Web Browser console message: %s, Source: %s, Line: %d", *Message, *Source, Line); @@ -66,101 +166,4 @@ bool UTransakWebBrowser::HandleOnBeforePopup(FString URL, FString Frame) { return false; } -#endif - -void UTransakWebBrowser::Load(const FString& WalletAddress, const FString& Email, const FString& ProductsAvailed, const FString& ScreenTitle) -{ - if (!WebBrowserWidget.IsValid()) - { - return; - } - - FString UrlToLoad = ComputePath(WalletAddress, Email, ProductsAvailed, ScreenTitle); - - if (bIsReady) - { -#if (ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1) - WebBrowserWidget->LoadURL(UrlToLoad); -#endif - } - else - { - FDelegateHandle OnWhenReadyHandle = CallAndRegister_OnWhenReady(UTransakWebBrowser::FOnWhenReady::FDelegate::CreateWeakLambda(this, [this, UrlToLoad, OnWhenReadyHandle]() - { -#if (ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1) - WebBrowserWidget->LoadURL(UrlToLoad); -#endif - OnWhenReady.Remove(OnWhenReadyHandle); - })); - } -} - -FString UTransakWebBrowser::ComputePath(const FString& WalletAddress, const FString& Email, const FString& ProductsAvailed, const FString& ScreenTitle) -{ - UTransakConfig* TransakConfig = FImmutableUtilities::GetDefaultTransakConfig(); - - if (!TransakConfig) - { - return ""; - } - - FString Path = TransakConfig->GetURL(); - TArray QueryParams; - - QueryParams.Add(FString(TEXT("apiKey=")) + FPlatformHttp::UrlEncode(TransakConfig->GetAPIKey())); - QueryParams.Add(FString(TEXT("email=")) + FPlatformHttp::UrlEncode(Email)); - QueryParams.Add(FString(TEXT("walletAddress=")) + FPlatformHttp::UrlEncode(WalletAddress)); - QueryParams.Add(FString(TEXT("themeColor=")) + FPlatformHttp::UrlEncode(TransakConfig->GetThemeColor().ToString())); - QueryParams.Add(FString(TEXT("isAutoFillUserData=")) + FPlatformHttp::UrlEncode(TransakConfig->IsAutoFillUserData() ? TEXT("true") : TEXT("false"))); - QueryParams.Add(FString(TEXT("disableWalletAddressForm=")) + FPlatformHttp::UrlEncode(TransakConfig->DisableWalletAddressForm() ? TEXT("true") : TEXT("false"))); - - if (!TransakConfig->GetNetwork().IsEmpty()) - { - QueryParams.Add(FString(TEXT("network=")) + FPlatformHttp::UrlEncode(TransakConfig->GetNetwork())); - } - - if (!ProductsAvailed.IsEmpty()) - { - QueryParams.Add(FString(TEXT("productsAvailed=")) + FPlatformHttp::UrlEncode(ProductsAvailed)); - } - - if (!ScreenTitle.IsEmpty()) - { - QueryParams.Add(FString(TEXT("exchangeScreenTitle=")) + FPlatformHttp::UrlEncode(ScreenTitle)); - } - - if (!TransakConfig->GetDefaultCryptoCurrency().IsEmpty()) - { - QueryParams.Add(FString(TEXT("defaultCryptoCurrency=")) + FPlatformHttp::UrlEncode(TransakConfig->GetDefaultCryptoCurrency())); - } - - if (!TransakConfig->GetDefaultFiatAmount().IsEmpty()) - { - QueryParams.Add(FString(TEXT("defaultFiatAmount=")) + FPlatformHttp::UrlEncode(TransakConfig->GetDefaultFiatAmount())); - } - - if (!TransakConfig->GetDefaultFiatCurrency().IsEmpty()) - { - QueryParams.Add(FString(TEXT("defaultFiatCurrency=")) + FPlatformHttp::UrlEncode(TransakConfig->GetDefaultFiatCurrency())); - } - - if (!TransakConfig->GetDefaultPaymentMethod().IsEmpty()) - { - QueryParams.Add(FString(TEXT("defaultPaymentMethod=")) + FPlatformHttp::UrlEncode(TransakConfig->GetDefaultPaymentMethod())); - } - - if (TransakConfig->GetCryptoCurrencyList().Num() > 0) - { - QueryParams.Add(FString(TEXT("cryptoCurrencyList=")) + FPlatformHttp::UrlEncode(FString::Join(TransakConfig->GetCryptoCurrencyList(), TEXT(",")))); - } - - if (TransakConfig->GetDisablePaymentMethods().Num() > 0) - { - QueryParams.Add(FString(TEXT("disablePaymentMethods=")) + FPlatformHttp::UrlEncode(FString::Join(TransakConfig->GetDisablePaymentMethods(), TEXT(",")))); - } - - Path += TEXT("?"); - Path += FString::Join(QueryParams, TEXT("&")); - - return Path; -} +#endif \ No newline at end of file diff --git a/Source/Immutable/Public/Immutable/Transak/TransakWebBrowser.h b/Source/Immutable/Public/Immutable/Transak/TransakWebBrowser.h index 7dcdb30a..f543e84d 100644 --- a/Source/Immutable/Public/Immutable/Transak/TransakWebBrowser.h +++ b/Source/Immutable/Public/Immutable/Transak/TransakWebBrowser.h @@ -2,11 +2,15 @@ #include "Components/Widget.h" +#include "TransakWebBrowser.generated.h" + #if (ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1) -#include "SWebBrowser.h" -#endif +enum class EWebBrowserConsoleLogSeverity; -#include "TransakWebBrowser.generated.h" +class SWebBrowser; +#else +class SBluWebBrowser; +#endif /** * @@ -18,12 +22,14 @@ class IMMUTABLE_API UTransakWebBrowser : public UWidget public: DECLARE_MULTICAST_DELEGATE(FOnWhenReady); - + +public: + UFUNCTION(BlueprintPure) + bool IsReady() const; + UFUNCTION(BlueprintCallable) void Load(const FString& WalletAddress, const FString& Email, const FString& ProductsAvailed, const FString& ScreenTitle); - bool IsReady() const { return bIsReady; }; - FDelegateHandle CallAndRegister_OnWhenReady(FOnWhenReady::FDelegate Delegate); protected: @@ -31,24 +37,27 @@ class IMMUTABLE_API UTransakWebBrowser : public UWidget virtual TSharedRef RebuildWidget() override; // End of UWidget interface -private: +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); - void HandleOnUrlChanged(const FText& Text); 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; + TSharedPtr WebBrowserWidget; #endif FOnWhenReady OnWhenReady; -private: +protected: bool bIsReady = false; - -}; +}; \ No newline at end of file From f762eb36abeff19f1d3ebe0544b0ffe0a583c7cd Mon Sep 17 00:00:00 2001 From: Yermek Garifullanov Date: Fri, 10 Jan 2025 11:00:32 +1300 Subject: [PATCH 09/25] chore: added comments and moved to dedicated folder --- .../Immutable/Transak/TransakWebBrowser.cpp | 2 +- .../Immutable/ImmutablePluginSettings.h | 2 +- .../Public/Immutable/ImmutableUtilities.h | 2 +- .../Public/Immutable/Transak/TransakConfig.h | 237 ++++++++++++++++++ .../Immutable/Transak/TransakWebBrowser.h | 16 +- .../Public/Immutable/TransakConfig.h | 139 ---------- 6 files changed, 255 insertions(+), 143 deletions(-) create mode 100644 Source/Immutable/Public/Immutable/Transak/TransakConfig.h delete mode 100644 Source/Immutable/Public/Immutable/TransakConfig.h diff --git a/Source/Immutable/Private/Immutable/Transak/TransakWebBrowser.cpp b/Source/Immutable/Private/Immutable/Transak/TransakWebBrowser.cpp index 1f2d1486..1a0250b7 100644 --- a/Source/Immutable/Private/Immutable/Transak/TransakWebBrowser.cpp +++ b/Source/Immutable/Private/Immutable/Transak/TransakWebBrowser.cpp @@ -3,7 +3,7 @@ #include "PlatformHttp.h" #include "Immutable/ImmutableUtilities.h" -#include "Immutable/TransakConfig.h" +#include "Immutable/Transak/TransakConfig.h" #if (ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1) #include "SWebBrowser.h" diff --git a/Source/Immutable/Public/Immutable/ImmutablePluginSettings.h b/Source/Immutable/Public/Immutable/ImmutablePluginSettings.h index fa1acff2..7bd7aa87 100644 --- a/Source/Immutable/Public/Immutable/ImmutablePluginSettings.h +++ b/Source/Immutable/Public/Immutable/ImmutablePluginSettings.h @@ -2,7 +2,7 @@ #include "Engine/DeveloperSettings.h" #include "ApplicationConfig.h" -#include "TransakConfig.h" +#include "Transak/TransakConfig.h" #include "ImmutablePluginSettings.generated.h" diff --git a/Source/Immutable/Public/Immutable/ImmutableUtilities.h b/Source/Immutable/Public/Immutable/ImmutableUtilities.h index 6c788d2c..adc8cb67 100644 --- a/Source/Immutable/Public/Immutable/ImmutableUtilities.h +++ b/Source/Immutable/Public/Immutable/ImmutableUtilities.h @@ -1,6 +1,6 @@ #pragma once #include "ApplicationConfig.h" -#include "TransakConfig.h" +#include "Transak/TransakConfig.h" /** A wrapper struct around various Immutable namespace utility and support methods. */ diff --git a/Source/Immutable/Public/Immutable/Transak/TransakConfig.h b/Source/Immutable/Public/Immutable/Transak/TransakConfig.h new file mode 100644 index 00000000..7c02a07b --- /dev/null +++ b/Source/Immutable/Public/Immutable/Transak/TransakConfig.h @@ -0,0 +1,237 @@ +#pragma once + +#include "TransakConfig.generated.h" + + +UENUM() +enum class ETransakEnvironment : uint8 +{ + Sandbox, + Production, +}; + +/** + * @class UTransakConfig + * @brief Configuration settings for Transak widget. + */ +UCLASS(Abstract, Blueprintable, ClassGroup = Immutable) +class UTransakConfig : 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() + { + return Network; + } + + /** + * @details More details could be found under the class parameter + * @return DefaultFiatCurrency as FString + */ + const FString& GetDefaultFiatCurrency() + { + return DefaultFiatCurrency; + } + + /** + * @details More details could be found under the class parameter + * @return DefaultFiatAmount as FString + */ + const FString& GetDefaultFiatAmount() + { + return DefaultFiatAmount; + } + + /** + * @details More details could be found under the class parameter + * @return DefaultCryptoCurrency as FString + */ + const FString& GetDefaultCryptoCurrency() + { + return DefaultCryptoCurrency; + } + + /** + * @details More details could be found under the class parameter + * @return DefaultPaymentMethod as FString + */ + const FString& GetDefaultPaymentMethod() + { + return DefaultPaymentMethod; + } + + /** + * @details More details could be found under the class parameter + * @return DisablePaymentMethods as array of FString + */ + const TArray& GetDisablePaymentMethods() + { + return DisablePaymentMethods; + } + + /** + * @details More details could be found under the class parameter + * @return bIsAutoFillUserData as bool + */ + bool IsAutoFillUserData() + { + return bIsAutoFillUserData; + } + + /** + * @details More details could be found under the class parameter + * @return bDisableWalletAddressForm as bool + */ + bool DisableWalletAddressForm() + { + return bDisableWalletAddressForm; + } + + /** + * @details More details could be found under the class parameter + * @return CryptoCurrencyList as array of FString + */ + const TArray& GetCryptoCurrencyList() + { + return CryptoCurrencyList; + } + + /** + * @details More details could be found under the class parameter + * @return ThemeColor as FLinearColor + */ + const FLinearColor& GetThemeColor() + { + 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 = "Transak") + 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; +}; diff --git a/Source/Immutable/Public/Immutable/Transak/TransakWebBrowser.h b/Source/Immutable/Public/Immutable/Transak/TransakWebBrowser.h index f543e84d..00ed377b 100644 --- a/Source/Immutable/Public/Immutable/Transak/TransakWebBrowser.h +++ b/Source/Immutable/Public/Immutable/Transak/TransakWebBrowser.h @@ -12,8 +12,9 @@ class SWebBrowser; class SBluWebBrowser; #endif + /** - * + * A custom web browser widget for Transak transactions. */ UCLASS() class IMMUTABLE_API UTransakWebBrowser : public UWidget @@ -24,9 +25,22 @@ class IMMUTABLE_API UTransakWebBrowser : public UWidget 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 Transak 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); diff --git a/Source/Immutable/Public/Immutable/TransakConfig.h b/Source/Immutable/Public/Immutable/TransakConfig.h deleted file mode 100644 index b998970b..00000000 --- a/Source/Immutable/Public/Immutable/TransakConfig.h +++ /dev/null @@ -1,139 +0,0 @@ -#pragma once -#include "ImmutableEnums.h" - -#include "TransakConfig.generated.h" - - -UENUM() -enum class ETransakEnvironment : uint8 -{ - Sandbox, - Production, -}; - -/** - * @class UTransakConfig - * @brief Configuration settings for Passport and various APIs. - * @details This class stores configuration settings such as URLs, chain names, contract addresses, - * client IDs, and environment settings for the zkEVM API, Orderbook API, and Passport. - */ -UCLASS(Abstract, Blueprintable, ClassGroup = Immutable) -class UTransakConfig : public UObject -{ - GENERATED_BODY() - -public: - 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/"); - } - } - - 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"); - } - } - - const FString& GetNetwork() - { - return Network; - } - - const FString& GetDefaultFiatCurrency() - { - return DefaultFiatCurrency; - } - - const FString& GetDefaultFiatAmount() - { - return DefaultFiatAmount; - } - - const FString& GetDefaultCryptoCurrency() - { - return DefaultCryptoCurrency; - } - - const FString& GetDefaultPaymentMethod() - { - return DefaultPaymentMethod; - } - - const TArray& GetDisablePaymentMethods() - { - return DisablePaymentMethods; - } - - bool IsAutoFillUserData() - { - return bIsAutoFillUserData; - } - - bool DisableWalletAddressForm() - { - return bDisableWalletAddressForm; - } - - const TArray& GetCryptoCurrencyList() - { - return CryptoCurrencyList; - } - - const FLinearColor& GetThemeColor() - { - return ThemeColor; - } - -protected: - // UPROPERTY(EditDefaultsOnly, Category = "Transak") - // FString URL; - // - // UPROPERTY(EditDefaultsOnly, Category = "Transak") - // FString APIKey; - - UPROPERTY(EditDefaultsOnly, Category = "General") - ETransakEnvironment Environment = ETransakEnvironment::Sandbox; - - UPROPERTY(EditDefaultsOnly, Category = "Fiat") - FString DefaultPaymentMethod; - - UPROPERTY(EditDefaultsOnly, Category = "Fiat") - FString DefaultFiatCurrency; - - UPROPERTY(EditDefaultsOnly, Category = "Fiat") - FString DefaultFiatAmount; - - UPROPERTY(EditDefaultsOnly, Category = "Fiat") - FString DefaultCryptoCurrency; - - UPROPERTY(EditDefaultsOnly, Category = "Transak") - FString Network; - - UPROPERTY(EditDefaultsOnly, Category = "Fiat") - TArray DisablePaymentMethods; - - UPROPERTY(EditDefaultsOnly, Category = "User") - bool bIsAutoFillUserData = true; - - UPROPERTY(EditDefaultsOnly, Category = "Fiat") - bool bDisableWalletAddressForm = true; - - UPROPERTY(EditDefaultsOnly, Category = "Fiat") - TArray CryptoCurrencyList; - - UPROPERTY(EditDefaultsOnly, Category = "Theme") - FLinearColor ThemeColor; -}; From 24cb276656855c7dce156e17a787336d52942e23 Mon Sep 17 00:00:00 2001 From: Yermek Garifullanov Date: Fri, 10 Jan 2025 11:53:52 +1300 Subject: [PATCH 10/25] feat: added sample asset for transak widget --- .../ImtblAuthenticatedWidget4_26.uasset | Bin 542179 -> 558404 bytes .../ImtblTransakWidget4_26.uasset | Bin 0 -> 46283 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 Content/BlueprintSampleContent/ImtblTransakWidget4_26.uasset diff --git a/Content/BlueprintSampleContent/ImtblAuthenticatedWidget4_26.uasset b/Content/BlueprintSampleContent/ImtblAuthenticatedWidget4_26.uasset index f6a40f5df79740b1ea5c478f2222c3d6d91705dd..bf567db02f5529a80ace1b25496caab29cd403d2 100644 GIT binary patch literal 558404 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�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

`M4^h%koo_`Tw1pG<;;6@ji4&Dc<=C`j@u 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

  • 6X5Hv_o<*4s5xrgVTflsNWm#uOfNC~RBj_IT;6om)*Bl2H7<$Ho;l0;LKUQgQ zrr&oEKi0tfNcuv3tqxdBI{b}@98Y#$c~M~@ekY>1up+Oxu&|)Cq_m=_FfS)Rr?^Zm zMDf^n%JbdRHOadZNLwy`pwD?b26r7%ma9YRHy+044{e@I0M{6jMnGGkpujYjYtUx$wpWf z_JobnIsrD3k_q&uI$6-DANftNcv@_j=evzWc<_4C*6ee_dbm*p=8Bh$}f#oU4IT z4l|OloT#t2q(JHl2#W1&D83xoU}f0b)*^1v%}khO->K_U4u?N=)H8SOIr^Dfb<27B z*xQ6WCmyFR(3PyoIM7IH7^WL>o>(}xFjK6$C5d3>RW$eIRds(^>e=Wy?C;k;@a+Sa znoVMtK2PBRL9;?PG6fc&fu(T<5YVIPupCDXR&wyRUQ`E+*sei4#AaEx(>aPuxb;!{ zlUrR|u>BuG#f$(I3c9)I2y>RBf(l3_{~gi<*`n^xPj8Jj1oY@sJ9_!`xp?mU|H8J+bS3z*d?i0q7G%WJNgaO5lur=Nsn5fnreb{XQeK1a7 zw=3y`0_Bg8XG+<}4!&3XgE+0$b=-!_xDM%j5Pfb&B^8z`J_HV@N_Z9uyB}`DqA;+` pH2?@Bxak9%VV~7XB`t+M1n7tY01?Zw=mW>X__I=Y>e8`y{5ME|EYtu1 literal 0 HcmV?d00001 From 55ab2384dd292b1e1eca4d183f981e296410b20d Mon Sep 17 00:00:00 2001 From: Yermek Garifullanov Date: Mon, 13 Jan 2025 12:12:37 +1300 Subject: [PATCH 11/25] fix: minor changes for consts and enums --- .../Private/Immutable/ImmutablePassport.cpp | 2 +- .../Immutable/Tests/ImtblMessagesTest.cpp | 4 ++-- .../Immutable/Transak/TransakWebBrowser.cpp | 6 +++-- .../Public/Immutable/ApplicationConfig.h | 22 ++++++++++++++----- .../Public/Immutable/ImmutableDataTypes.h | 6 +++-- .../Public/Immutable/ImmutableEnums.h | 2 +- .../Public/Immutable/ImmutableNames.h | 10 ++++++--- .../Public/Immutable/Transak/TransakConfig.h | 2 +- 8 files changed, 37 insertions(+), 17 deletions(-) diff --git a/Source/Immutable/Private/Immutable/ImmutablePassport.cpp b/Source/Immutable/Private/Immutable/ImmutablePassport.cpp index 708f6ef6..6ac2df7f 100644 --- a/Source/Immutable/Private/Immutable/ImmutablePassport.cpp +++ b/Source/Immutable/Private/Immutable/ImmutablePassport.cpp @@ -60,7 +60,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/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/Private/Immutable/Transak/TransakWebBrowser.cpp b/Source/Immutable/Private/Immutable/Transak/TransakWebBrowser.cpp index 1a0250b7..79152f56 100644 --- a/Source/Immutable/Private/Immutable/Transak/TransakWebBrowser.cpp +++ b/Source/Immutable/Private/Immutable/Transak/TransakWebBrowser.cpp @@ -83,7 +83,7 @@ FString UTransakWebBrowser::ComputePath(const FString& WalletAddress, const FStr if (!TransakConfig) { - return ""; + return TEXT(""); } FString Path = TransakConfig->GetURL(); @@ -166,4 +166,6 @@ bool UTransakWebBrowser::HandleOnBeforePopup(FString URL, FString Frame) { return false; } -#endif \ No newline at end of file +#endif + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/Immutable/Public/Immutable/ApplicationConfig.h b/Source/Immutable/Public/Immutable/ApplicationConfig.h index ad46fea3..46dce7ee 100644 --- a/Source/Immutable/Public/Immutable/ApplicationConfig.h +++ b/Source/Immutable/Public/Immutable/ApplicationConfig.h @@ -1,4 +1,6 @@ #pragma once +#include "ImmutableEnums.h" +#include "ImmutableNames.h" #include "ApplicationConfig.generated.h" @@ -85,13 +87,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 +153,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 index ebe3fb39..ad9f31ca 100644 --- a/Source/Immutable/Public/Immutable/ImmutableEnums.h +++ b/Source/Immutable/Public/Immutable/ImmutableEnums.h @@ -1,6 +1,6 @@ #pragma once -UENUM() +UENUM(BlueprintType) enum class EPassportEnvironment : uint8 { Development, 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/Transak/TransakConfig.h b/Source/Immutable/Public/Immutable/Transak/TransakConfig.h index 7c02a07b..c79af2d8 100644 --- a/Source/Immutable/Public/Immutable/Transak/TransakConfig.h +++ b/Source/Immutable/Public/Immutable/Transak/TransakConfig.h @@ -3,7 +3,7 @@ #include "TransakConfig.generated.h" -UENUM() +UENUM(BlueprintType) enum class ETransakEnvironment : uint8 { Sandbox, From 1c8f252579ad3d794723109c7f1d49c7c26751e2 Mon Sep 17 00:00:00 2001 From: YermekG Date: Tue, 14 Jan 2025 22:16:33 +1300 Subject: [PATCH 12/25] fix: resolved compile issues with ue 5.4 --- Source/Immutable/Immutable.Build.cs | 6 +++--- .../Private/Immutable/Transak/TransakWebBrowser.cpp | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Source/Immutable/Immutable.Build.cs b/Source/Immutable/Immutable.Build.cs index dc08b2d2..cb1268c9 100644 --- a/Source/Immutable/Immutable.Build.cs +++ b/Source/Immutable/Immutable.Build.cs @@ -30,7 +30,6 @@ public Immutable(ReadOnlyTargetRules Target) : base(Target) PrivateDependencyModuleNames.AddRange( new string[] { - "BluExtension", "CoreUObject", "Engine", "Slate", @@ -43,16 +42,17 @@ public Immutable(ReadOnlyTargetRules Target) : base(Target) } ); -#if UE_5_0_OR_LATER +#if UE_5_1_OR_LATER PublicDependencyModuleNames.Add("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/Transak/TransakWebBrowser.cpp b/Source/Immutable/Private/Immutable/Transak/TransakWebBrowser.cpp index 79152f56..91c47218 100644 --- a/Source/Immutable/Private/Immutable/Transak/TransakWebBrowser.cpp +++ b/Source/Immutable/Private/Immutable/Transak/TransakWebBrowser.cpp @@ -28,10 +28,10 @@ void UTransakWebBrowser::Load(const FString& WalletAddress, const FString& Email } FString UrlToLoad = ComputePath(WalletAddress, Email, ProductsAvailed, ScreenTitle); - FDelegateHandle OnWhenReadyHandle = CallAndRegister_OnWhenReady(UTransakWebBrowser::FOnWhenReady::FDelegate::CreateWeakLambda(this, [this, UrlToLoad, OnWhenReadyHandle]() + CallAndRegister_OnWhenReady(UTransakWebBrowser::FOnWhenReady::FDelegate::CreateWeakLambda(this, [this, UrlToLoad]() { WebBrowserWidget->LoadURL(UrlToLoad); - OnWhenReady.Remove(OnWhenReadyHandle); + OnWhenReady.RemoveAll(this); })); } From 1ec1597372fe900544f554386aae867f7f3f2e0f Mon Sep 17 00:00:00 2001 From: YermekG Date: Fri, 17 Jan 2025 11:02:11 +1300 Subject: [PATCH 13/25] refactor: added marketplace module and renamed transak widget --- .../ImmutableMarketplace.Build.cs | 26 +++++++++++++++++++ .../Private/ImmutableMarketplace.cpp | 17 ++++++++++++ .../Private/OnRampWebBrowserWidget.cpp} | 2 +- .../Public/ImmutableMarketplace.h | 11 ++++++++ .../Public/OnRampWebBrowserWidget.h} | 2 +- .../Public}/TransakConfig.h | 0 6 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 Source/ImmutableMarketplace/ImmutableMarketplace.Build.cs create mode 100644 Source/ImmutableMarketplace/Private/ImmutableMarketplace.cpp rename Source/{Immutable/Private/Immutable/Transak/TransakWebBrowser.cpp => ImmutableMarketplace/Private/OnRampWebBrowserWidget.cpp} (99%) create mode 100644 Source/ImmutableMarketplace/Public/ImmutableMarketplace.h rename Source/{Immutable/Public/Immutable/Transak/TransakWebBrowser.h => ImmutableMarketplace/Public/OnRampWebBrowserWidget.h} (97%) rename Source/{Immutable/Public/Immutable/Transak => ImmutableMarketplace/Public}/TransakConfig.h (100%) diff --git a/Source/ImmutableMarketplace/ImmutableMarketplace.Build.cs b/Source/ImmutableMarketplace/ImmutableMarketplace.Build.cs new file mode 100644 index 00000000..4ffd79d7 --- /dev/null +++ b/Source/ImmutableMarketplace/ImmutableMarketplace.Build.cs @@ -0,0 +1,26 @@ +using UnrealBuildTool; + +public class ImmutableMarketplace : ModuleRules +{ + public ImmutableMarketplace(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", + } + ); + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "CoreUObject", + "Engine", + "Slate", + "SlateCore" + } + ); + } +} \ 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/Immutable/Private/Immutable/Transak/TransakWebBrowser.cpp b/Source/ImmutableMarketplace/Private/OnRampWebBrowserWidget.cpp similarity index 99% rename from Source/Immutable/Private/Immutable/Transak/TransakWebBrowser.cpp rename to Source/ImmutableMarketplace/Private/OnRampWebBrowserWidget.cpp index 91c47218..de56f13e 100644 --- a/Source/Immutable/Private/Immutable/Transak/TransakWebBrowser.cpp +++ b/Source/ImmutableMarketplace/Private/OnRampWebBrowserWidget.cpp @@ -1,4 +1,4 @@ -#include "Immutable/Transak/TransakWebBrowser.h" +#include "OnRampWebBrowserWidget.h" #include "PlatformHttp.h" 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/Immutable/Public/Immutable/Transak/TransakWebBrowser.h b/Source/ImmutableMarketplace/Public/OnRampWebBrowserWidget.h similarity index 97% rename from Source/Immutable/Public/Immutable/Transak/TransakWebBrowser.h rename to Source/ImmutableMarketplace/Public/OnRampWebBrowserWidget.h index 00ed377b..acd152c6 100644 --- a/Source/Immutable/Public/Immutable/Transak/TransakWebBrowser.h +++ b/Source/ImmutableMarketplace/Public/OnRampWebBrowserWidget.h @@ -2,7 +2,7 @@ #include "Components/Widget.h" -#include "TransakWebBrowser.generated.h" +#include "OnRampWebBrowserWidget.generated.h" #if (ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1) enum class EWebBrowserConsoleLogSeverity; diff --git a/Source/Immutable/Public/Immutable/Transak/TransakConfig.h b/Source/ImmutableMarketplace/Public/TransakConfig.h similarity index 100% rename from Source/Immutable/Public/Immutable/Transak/TransakConfig.h rename to Source/ImmutableMarketplace/Public/TransakConfig.h From 945b705e67a23115eaa5d34f48cfe6efe5e1113c Mon Sep 17 00:00:00 2001 From: YermekG Date: Sun, 19 Jan 2025 18:28:14 +1300 Subject: [PATCH 14/25] refactor: added marketplace module to keep marketplace features such as on ramp widget --- Immutable.uplugin | 103 ++++++++++-------- .../Private/Immutable/ImmutablePassport.cpp | 15 ++- .../Private/Immutable/ImmutableUtilities.cpp | 29 ----- .../Public/Immutable/ApplicationConfig.h | 3 +- ...lePluginSettings.h => ImmutableSettings.h} | 9 +- .../Public/Immutable/ImmutableUtilities.h | 5 - .../ImmutableMarketplace.Build.cs | 13 ++- .../Private/OnRampWebBrowserWidget.cpp | 10 +- .../Public/ImmutableMarketplaceSettings.h | 25 +++++ .../{TransakConfig.h => OnRampConfig.h} | 6 +- .../Public/OnRampWebBrowserWidget.h | 2 +- 11 files changed, 122 insertions(+), 98 deletions(-) rename Source/Immutable/Public/Immutable/{ImmutablePluginSettings.h => ImmutableSettings.h} (64%) create mode 100644 Source/ImmutableMarketplace/Public/ImmutableMarketplaceSettings.h rename Source/ImmutableMarketplace/Public/{TransakConfig.h => OnRampConfig.h} (98%) 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/Private/Immutable/ImmutablePassport.cpp b/Source/Immutable/Private/Immutable/ImmutablePassport.cpp index 6ac2df7f..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) { diff --git a/Source/Immutable/Private/Immutable/ImmutableUtilities.cpp b/Source/Immutable/Private/Immutable/ImmutableUtilities.cpp index dad2f900..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,31 +19,3 @@ bool FImmutableUtilities::LoadGameBridge(FString& GameBridge) return false; } - -UApplicationConfig* FImmutableUtilities::GetDefaultApplicationConfig() -{ - auto Settings = GetDefault(); - - if (!Settings) - { - IMTBL_ERR("Failed to retrieve default Immutable application configuration") - - return nullptr; - } - - return Settings->DefaultApplicationConfig.GetDefaultObject(); -} - -UTransakConfig* FImmutableUtilities::GetDefaultTransakConfig() -{ - auto Settings = GetDefault(); - - if (!Settings) - { - IMTBL_ERR("Failed to retrieve default Immutable Transak configuration") - - return nullptr; - } - - return Settings->DefaultTransakConfig.GetDefaultObject(); -} diff --git a/Source/Immutable/Public/Immutable/ApplicationConfig.h b/Source/Immutable/Public/Immutable/ApplicationConfig.h index 46dce7ee..78f526e6 100644 --- a/Source/Immutable/Public/Immutable/ApplicationConfig.h +++ b/Source/Immutable/Public/Immutable/ApplicationConfig.h @@ -1,4 +1,5 @@ #pragma once + #include "ImmutableEnums.h" #include "ImmutableNames.h" @@ -11,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() diff --git a/Source/Immutable/Public/Immutable/ImmutablePluginSettings.h b/Source/Immutable/Public/Immutable/ImmutableSettings.h similarity index 64% rename from Source/Immutable/Public/Immutable/ImmutablePluginSettings.h rename to Source/Immutable/Public/Immutable/ImmutableSettings.h index 7bd7aa87..7e8626ee 100644 --- a/Source/Immutable/Public/Immutable/ImmutablePluginSettings.h +++ b/Source/Immutable/Public/Immutable/ImmutableSettings.h @@ -2,18 +2,17 @@ #include "Engine/DeveloperSettings.h" #include "ApplicationConfig.h" -#include "Transak/TransakConfig.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 +class IMMUTABLE_API UImmutableSettings : public UDeveloperSettings { GENERATED_BODY() @@ -24,6 +23,4 @@ class IMMUTABLE_API UImmutablePluginSettings : public UDeveloperSettings UPROPERTY(Config, EditAnywhere, BlueprintReadOnly, Category = "General") TSubclassOf DefaultApplicationConfig; - UPROPERTY(Config, EditAnywhere, BlueprintReadOnly, Category = "Transak") - TSubclassOf DefaultTransakConfig; }; diff --git a/Source/Immutable/Public/Immutable/ImmutableUtilities.h b/Source/Immutable/Public/Immutable/ImmutableUtilities.h index adc8cb67..d13015d3 100644 --- a/Source/Immutable/Public/Immutable/ImmutableUtilities.h +++ b/Source/Immutable/Public/Immutable/ImmutableUtilities.h @@ -1,6 +1,4 @@ #pragma once -#include "ApplicationConfig.h" -#include "Transak/TransakConfig.h" /** A wrapper struct around various Immutable namespace utility and support methods. */ @@ -14,7 +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(); - static UTransakConfig* GetDefaultTransakConfig(); }; diff --git a/Source/ImmutableMarketplace/ImmutableMarketplace.Build.cs b/Source/ImmutableMarketplace/ImmutableMarketplace.Build.cs index 4ffd79d7..987b4103 100644 --- a/Source/ImmutableMarketplace/ImmutableMarketplace.Build.cs +++ b/Source/ImmutableMarketplace/ImmutableMarketplace.Build.cs @@ -10,6 +10,7 @@ public ImmutableMarketplace(ReadOnlyTargetRules Target) : base(Target) new string[] { "Core", + "UMG" } ); @@ -19,8 +20,18 @@ public ImmutableMarketplace(ReadOnlyTargetRules Target) : base(Target) "CoreUObject", "Engine", "Slate", - "SlateCore" + "SlateCore", + "DeveloperSettings", + "HTTP" } ); + +#if! UE_5_1_OR_LATER + PrivateDependencyModuleNames.Add("BluExtension"); + if (Target.Platform == UnrealTargetPlatform.Win64) + { + PublicDependencyModuleNames.Add("Blu"); + } +#endif } } \ No newline at end of file diff --git a/Source/ImmutableMarketplace/Private/OnRampWebBrowserWidget.cpp b/Source/ImmutableMarketplace/Private/OnRampWebBrowserWidget.cpp index de56f13e..fbd23d8f 100644 --- a/Source/ImmutableMarketplace/Private/OnRampWebBrowserWidget.cpp +++ b/Source/ImmutableMarketplace/Private/OnRampWebBrowserWidget.cpp @@ -1,9 +1,9 @@ #include "OnRampWebBrowserWidget.h" +#include "ImmutableMarketplaceSettings.h" #include "PlatformHttp.h" -#include "Immutable/ImmutableUtilities.h" -#include "Immutable/Transak/TransakConfig.h" +#include "OnRampConfig.h" #if (ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1) #include "SWebBrowser.h" @@ -13,7 +13,7 @@ #include "UserInterface/BluWebBrowser.h" #endif -#define LOCTEXT_NAMESPACE "TransakWebBrowser" +#define LOCTEXT_NAMESPACE "OnRampWebBrowser" bool UTransakWebBrowser::IsReady() const { @@ -79,7 +79,9 @@ TSharedRef UTransakWebBrowser::RebuildWidget() FString UTransakWebBrowser::ComputePath(const FString& WalletAddress, const FString& Email, const FString& ProductsAvailed, const FString& ScreenTitle) { - UTransakConfig* TransakConfig = FImmutableUtilities::GetDefaultTransakConfig(); + auto Settings = GetDefault(); + + UTransakConfig* TransakConfig = Settings->DefaultOnRampWidgetConfig.GetDefaultObject(); if (!TransakConfig) { diff --git a/Source/ImmutableMarketplace/Public/ImmutableMarketplaceSettings.h b/Source/ImmutableMarketplace/Public/ImmutableMarketplaceSettings.h new file mode 100644 index 00000000..694666bb --- /dev/null +++ b/Source/ImmutableMarketplace/Public/ImmutableMarketplaceSettings.h @@ -0,0 +1,25 @@ +#pragma once + +#include "Engine/DeveloperSettings.h" +#include "OnRampConfig.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 Settings")) +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, Category = "Transak") + TSubclassOf DefaultOnRampWidgetConfig; +}; diff --git a/Source/ImmutableMarketplace/Public/TransakConfig.h b/Source/ImmutableMarketplace/Public/OnRampConfig.h similarity index 98% rename from Source/ImmutableMarketplace/Public/TransakConfig.h rename to Source/ImmutableMarketplace/Public/OnRampConfig.h index c79af2d8..b9a83d3c 100644 --- a/Source/ImmutableMarketplace/Public/TransakConfig.h +++ b/Source/ImmutableMarketplace/Public/OnRampConfig.h @@ -1,6 +1,6 @@ #pragma once -#include "TransakConfig.generated.h" +#include "OnRampConfig.generated.h" UENUM(BlueprintType) @@ -11,11 +11,11 @@ enum class ETransakEnvironment : uint8 }; /** - * @class UTransakConfig + * @class UOnRampConfig * @brief Configuration settings for Transak widget. */ UCLASS(Abstract, Blueprintable, ClassGroup = Immutable) -class UTransakConfig : public UObject +class IMMUTABLEMARKETPLACE_API UTransakConfig : public UObject { GENERATED_BODY() diff --git a/Source/ImmutableMarketplace/Public/OnRampWebBrowserWidget.h b/Source/ImmutableMarketplace/Public/OnRampWebBrowserWidget.h index acd152c6..646073a4 100644 --- a/Source/ImmutableMarketplace/Public/OnRampWebBrowserWidget.h +++ b/Source/ImmutableMarketplace/Public/OnRampWebBrowserWidget.h @@ -17,7 +17,7 @@ class SBluWebBrowser; * A custom web browser widget for Transak transactions. */ UCLASS() -class IMMUTABLE_API UTransakWebBrowser : public UWidget +class IMMUTABLEMARKETPLACE_API UTransakWebBrowser : public UWidget { GENERATED_BODY() From 9d14b5c0c1070f32beedbda0851e3061f3671ee9 Mon Sep 17 00:00:00 2001 From: YermekG Date: Sun, 19 Jan 2025 19:29:38 +1300 Subject: [PATCH 15/25] chore: renamed transak widget to on ramp --- .../Public/Immutable/ImmutableSettings.h | 2 +- .../Private/OnRampWebBrowserWidget.cpp | 22 +++++++++---------- .../Public/ImmutableMarketplaceSettings.h | 6 ++--- .../Public/OnRampWebBrowserWidget.h | 2 +- .../{OnRampConfig.h => OnRampWidgetConfig.h} | 8 +++---- 5 files changed, 20 insertions(+), 20 deletions(-) rename Source/ImmutableMarketplace/Public/{OnRampConfig.h => OnRampWidgetConfig.h} (97%) diff --git a/Source/Immutable/Public/Immutable/ImmutableSettings.h b/Source/Immutable/Public/Immutable/ImmutableSettings.h index 7e8626ee..002effb4 100644 --- a/Source/Immutable/Public/Immutable/ImmutableSettings.h +++ b/Source/Immutable/Public/Immutable/ImmutableSettings.h @@ -11,7 +11,7 @@ * 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")) +UCLASS(config = Game, defaultconfig, meta = (DisplayName = "Immutable")) class IMMUTABLE_API UImmutableSettings : public UDeveloperSettings { GENERATED_BODY() diff --git a/Source/ImmutableMarketplace/Private/OnRampWebBrowserWidget.cpp b/Source/ImmutableMarketplace/Private/OnRampWebBrowserWidget.cpp index fbd23d8f..c62977e3 100644 --- a/Source/ImmutableMarketplace/Private/OnRampWebBrowserWidget.cpp +++ b/Source/ImmutableMarketplace/Private/OnRampWebBrowserWidget.cpp @@ -3,7 +3,7 @@ #include "ImmutableMarketplaceSettings.h" #include "PlatformHttp.h" -#include "OnRampConfig.h" +#include "OnRampWidgetConfig.h" #if (ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1) #include "SWebBrowser.h" @@ -15,12 +15,12 @@ #define LOCTEXT_NAMESPACE "OnRampWebBrowser" -bool UTransakWebBrowser::IsReady() const +bool UOnRampWidget::IsReady() const { return bIsReady; } -void UTransakWebBrowser::Load(const FString& WalletAddress, const FString& Email, const FString& ProductsAvailed, const FString& ScreenTitle) +void UOnRampWidget::Load(const FString& WalletAddress, const FString& Email, const FString& ProductsAvailed, const FString& ScreenTitle) { if (!WebBrowserWidget.IsValid()) { @@ -28,14 +28,14 @@ void UTransakWebBrowser::Load(const FString& WalletAddress, const FString& Email } FString UrlToLoad = ComputePath(WalletAddress, Email, ProductsAvailed, ScreenTitle); - CallAndRegister_OnWhenReady(UTransakWebBrowser::FOnWhenReady::FDelegate::CreateWeakLambda(this, [this, UrlToLoad]() + CallAndRegister_OnWhenReady(UOnRampWidget::FOnWhenReady::FDelegate::CreateWeakLambda(this, [this, UrlToLoad]() { WebBrowserWidget->LoadURL(UrlToLoad); OnWhenReady.RemoveAll(this); })); } -FDelegateHandle UTransakWebBrowser::CallAndRegister_OnWhenReady(FOnWhenReady::FDelegate Delegate) +FDelegateHandle UOnRampWidget::CallAndRegister_OnWhenReady(FOnWhenReady::FDelegate Delegate) { if (bIsReady) { @@ -45,7 +45,7 @@ FDelegateHandle UTransakWebBrowser::CallAndRegister_OnWhenReady(FOnWhenReady::FD return OnWhenReady.Add(Delegate); } -TSharedRef UTransakWebBrowser::RebuildWidget() +TSharedRef UOnRampWidget::RebuildWidget() { if (IsDesignTime()) { @@ -77,11 +77,11 @@ TSharedRef UTransakWebBrowser::RebuildWidget() } } -FString UTransakWebBrowser::ComputePath(const FString& WalletAddress, const FString& Email, const FString& ProductsAvailed, const FString& ScreenTitle) +FString UOnRampWidget::ComputePath(const FString& WalletAddress, const FString& Email, const FString& ProductsAvailed, const FString& ScreenTitle) { auto Settings = GetDefault(); - UTransakConfig* TransakConfig = Settings->DefaultOnRampWidgetConfig.GetDefaultObject(); + UOnRampWidgetConfig* TransakConfig = Settings->DefaultOnRampWidgetConfig.GetDefaultObject(); if (!TransakConfig) { @@ -149,7 +149,7 @@ FString UTransakWebBrowser::ComputePath(const FString& WalletAddress, const FStr return Path; } -void UTransakWebBrowser::HandleOnUrlChanged(const FText& Text) +void UOnRampWidget::HandleOnUrlChanged(const FText& Text) { if (Text.EqualToCaseIgnored(FText::FromString(InitialURL))) { @@ -159,12 +159,12 @@ void UTransakWebBrowser::HandleOnUrlChanged(const FText& Text) } #if (ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1) -void UTransakWebBrowser::HandleOnConsoleMessage(const FString& Message, const FString& Source, int32 Line, EWebBrowserConsoleLogSeverity Severity) +void UOnRampWidget::HandleOnConsoleMessage(const FString& Message, const FString& Source, int32 Line, EWebBrowserConsoleLogSeverity Severity) { IMTBL_LOG("Transak Web Browser console message: %s, Source: %s, Line: %d", *Message, *Source, Line); } -bool UTransakWebBrowser::HandleOnBeforePopup(FString URL, FString Frame) +bool UOnRampWidget::HandleOnBeforePopup(FString URL, FString Frame) { return false; } diff --git a/Source/ImmutableMarketplace/Public/ImmutableMarketplaceSettings.h b/Source/ImmutableMarketplace/Public/ImmutableMarketplaceSettings.h index 694666bb..fa8eddd2 100644 --- a/Source/ImmutableMarketplace/Public/ImmutableMarketplaceSettings.h +++ b/Source/ImmutableMarketplace/Public/ImmutableMarketplaceSettings.h @@ -1,7 +1,7 @@ #pragma once #include "Engine/DeveloperSettings.h" -#include "OnRampConfig.h" +#include "OnRampWidgetConfig.h" #include "ImmutableMarketplaceSettings.generated.h" @@ -11,7 +11,7 @@ * 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 Settings")) +UCLASS(config = Game, defaultconfig, meta = (DisplayName = "Immutable Marketplace")) class IMMUTABLEMARKETPLACE_API UImmutableMarketplaceSettings : public UDeveloperSettings { GENERATED_BODY() @@ -21,5 +21,5 @@ class IMMUTABLEMARKETPLACE_API UImmutableMarketplaceSettings : public UDeveloper /// 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, Category = "Transak") - TSubclassOf DefaultOnRampWidgetConfig; + TSubclassOf DefaultOnRampWidgetConfig; }; diff --git a/Source/ImmutableMarketplace/Public/OnRampWebBrowserWidget.h b/Source/ImmutableMarketplace/Public/OnRampWebBrowserWidget.h index 646073a4..1840d314 100644 --- a/Source/ImmutableMarketplace/Public/OnRampWebBrowserWidget.h +++ b/Source/ImmutableMarketplace/Public/OnRampWebBrowserWidget.h @@ -17,7 +17,7 @@ class SBluWebBrowser; * A custom web browser widget for Transak transactions. */ UCLASS() -class IMMUTABLEMARKETPLACE_API UTransakWebBrowser : public UWidget +class IMMUTABLEMARKETPLACE_API UOnRampWidget : public UWidget { GENERATED_BODY() diff --git a/Source/ImmutableMarketplace/Public/OnRampConfig.h b/Source/ImmutableMarketplace/Public/OnRampWidgetConfig.h similarity index 97% rename from Source/ImmutableMarketplace/Public/OnRampConfig.h rename to Source/ImmutableMarketplace/Public/OnRampWidgetConfig.h index b9a83d3c..019c0ca2 100644 --- a/Source/ImmutableMarketplace/Public/OnRampConfig.h +++ b/Source/ImmutableMarketplace/Public/OnRampWidgetConfig.h @@ -1,6 +1,6 @@ #pragma once -#include "OnRampConfig.generated.h" +#include "OnRampWidgetConfig.generated.h" UENUM(BlueprintType) @@ -11,11 +11,11 @@ enum class ETransakEnvironment : uint8 }; /** - * @class UOnRampConfig - * @brief Configuration settings for Transak widget. + * @class UOnRampWidgetConfig + * @brief Configuration settings for on ramp widget. */ UCLASS(Abstract, Blueprintable, ClassGroup = Immutable) -class IMMUTABLEMARKETPLACE_API UTransakConfig : public UObject +class IMMUTABLEMARKETPLACE_API UOnRampWidgetConfig : public UObject { GENERATED_BODY() From 8b4de957b573532fc177d98c011e5c0441c33367 Mon Sep 17 00:00:00 2001 From: YermekG Date: Sun, 19 Jan 2025 20:39:26 +1300 Subject: [PATCH 16/25] chore: change misleading transak wording --- Source/Immutable/Immutable.Build.cs | 12 ++-- .../ImmutableMarketplace.Build.cs | 9 ++- .../Private/OnRampWebBrowserWidget.cpp | 55 ++++++++++--------- .../Public/ImmutableMarketplaceSettings.h | 2 +- .../Public/OnRampWebBrowserWidget.h | 7 ++- .../Public/OnRampWidgetConfig.h | 2 +- 6 files changed, 51 insertions(+), 36 deletions(-) diff --git a/Source/Immutable/Immutable.Build.cs b/Source/Immutable/Immutable.Build.cs index cb1268c9..c2a00b37 100644 --- a/Source/Immutable/Immutable.Build.cs +++ b/Source/Immutable/Immutable.Build.cs @@ -20,10 +20,6 @@ public Immutable(ReadOnlyTargetRules Target) : base(Target) { "Core", "JsonUtilities", -#if UE_5_1_OR_LATER - "WebBrowser", - "WebBrowserWidget", -#endif } ); @@ -43,7 +39,13 @@ public Immutable(ReadOnlyTargetRules Target) : base(Target) ); #if UE_5_1_OR_LATER - PublicDependencyModuleNames.Add("WebBrowserWidget"); + PublicDependencyModuleNames.AddRange( + new string[] + { + "WebBrowser", + "WebBrowserWidget" + } + ); #else PrivateDependencyModuleNames.Add("BluExtension"); if (Target.Platform == UnrealTargetPlatform.Win64) diff --git a/Source/ImmutableMarketplace/ImmutableMarketplace.Build.cs b/Source/ImmutableMarketplace/ImmutableMarketplace.Build.cs index 987b4103..f3eb699c 100644 --- a/Source/ImmutableMarketplace/ImmutableMarketplace.Build.cs +++ b/Source/ImmutableMarketplace/ImmutableMarketplace.Build.cs @@ -26,7 +26,14 @@ public ImmutableMarketplace(ReadOnlyTargetRules Target) : base(Target) } ); -#if! UE_5_1_OR_LATER +#if UE_5_1_OR_LATER + PublicDependencyModuleNames.AddRange( + new string[] + { + "WebBrowser", + } + ); +#else PrivateDependencyModuleNames.Add("BluExtension"); if (Target.Platform == UnrealTargetPlatform.Win64) { diff --git a/Source/ImmutableMarketplace/Private/OnRampWebBrowserWidget.cpp b/Source/ImmutableMarketplace/Private/OnRampWebBrowserWidget.cpp index c62977e3..99604d33 100644 --- a/Source/ImmutableMarketplace/Private/OnRampWebBrowserWidget.cpp +++ b/Source/ImmutableMarketplace/Private/OnRampWebBrowserWidget.cpp @@ -7,13 +7,14 @@ #if (ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1) #include "SWebBrowser.h" - -#include "Immutable/Misc/ImtblLogging.h" #else #include "UserInterface/BluWebBrowser.h" #endif -#define LOCTEXT_NAMESPACE "OnRampWebBrowser" +#define LOCTEXT_NAMESPACE "OnRampWidget" + +DEFINE_LOG_CATEGORY(LogImmutableOnRampWidget); + bool UOnRampWidget::IsReady() const { @@ -54,7 +55,7 @@ TSharedRef UOnRampWidget::RebuildWidget() .VAlign(VAlign_Center) [ SNew(STextBlock) - .Text(LOCTEXT("Transak Web Browser", "Transak Web Browser")) + .Text(LOCTEXT("OnRamp Web Browser", "OnRamp Web Browser")) ]; } else @@ -81,26 +82,28 @@ FString UOnRampWidget::ComputePath(const FString& WalletAddress, const FString& { auto Settings = GetDefault(); - UOnRampWidgetConfig* TransakConfig = Settings->DefaultOnRampWidgetConfig.GetDefaultObject(); + UOnRampWidgetConfig* OnRampWidgetConfig = Settings->DefaultOnRampWidgetConfig.GetDefaultObject(); - if (!TransakConfig) + if (!OnRampWidgetConfig) { + UE_LOG(LogImmutableOnRampWidget, Error, TEXT("On ramp widget config is not assigned!")); + return TEXT(""); } - FString Path = TransakConfig->GetURL(); + FString Path = OnRampWidgetConfig->GetURL(); TArray QueryParams; - QueryParams.Add(FString(TEXT("apiKey=")) + FPlatformHttp::UrlEncode(TransakConfig->GetAPIKey())); + 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(TransakConfig->GetThemeColor().ToString())); - QueryParams.Add(FString(TEXT("isAutoFillUserData=")) + FPlatformHttp::UrlEncode(TransakConfig->IsAutoFillUserData() ? TEXT("true") : TEXT("false"))); - QueryParams.Add(FString(TEXT("disableWalletAddressForm=")) + FPlatformHttp::UrlEncode(TransakConfig->DisableWalletAddressForm() ? TEXT("true") : TEXT("false"))); + 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->DisableWalletAddressForm() ? TEXT("true") : TEXT("false"))); - if (!TransakConfig->GetNetwork().IsEmpty()) + if (!OnRampWidgetConfig->GetNetwork().IsEmpty()) { - QueryParams.Add(FString(TEXT("network=")) + FPlatformHttp::UrlEncode(TransakConfig->GetNetwork())); + QueryParams.Add(FString(TEXT("network=")) + FPlatformHttp::UrlEncode(OnRampWidgetConfig->GetNetwork())); } if (!ProductsAvailed.IsEmpty()) @@ -113,34 +116,34 @@ FString UOnRampWidget::ComputePath(const FString& WalletAddress, const FString& QueryParams.Add(FString(TEXT("exchangeScreenTitle=")) + FPlatformHttp::UrlEncode(ScreenTitle)); } - if (!TransakConfig->GetDefaultCryptoCurrency().IsEmpty()) + if (!OnRampWidgetConfig->GetDefaultCryptoCurrency().IsEmpty()) { - QueryParams.Add(FString(TEXT("defaultCryptoCurrency=")) + FPlatformHttp::UrlEncode(TransakConfig->GetDefaultCryptoCurrency())); + QueryParams.Add(FString(TEXT("defaultCryptoCurrency=")) + FPlatformHttp::UrlEncode(OnRampWidgetConfig->GetDefaultCryptoCurrency())); } - if (!TransakConfig->GetDefaultFiatAmount().IsEmpty()) + if (!OnRampWidgetConfig->GetDefaultFiatAmount().IsEmpty()) { - QueryParams.Add(FString(TEXT("defaultFiatAmount=")) + FPlatformHttp::UrlEncode(TransakConfig->GetDefaultFiatAmount())); + QueryParams.Add(FString(TEXT("defaultFiatAmount=")) + FPlatformHttp::UrlEncode(OnRampWidgetConfig->GetDefaultFiatAmount())); } - if (!TransakConfig->GetDefaultFiatCurrency().IsEmpty()) + if (!OnRampWidgetConfig->GetDefaultFiatCurrency().IsEmpty()) { - QueryParams.Add(FString(TEXT("defaultFiatCurrency=")) + FPlatformHttp::UrlEncode(TransakConfig->GetDefaultFiatCurrency())); + QueryParams.Add(FString(TEXT("defaultFiatCurrency=")) + FPlatformHttp::UrlEncode(OnRampWidgetConfig->GetDefaultFiatCurrency())); } - if (!TransakConfig->GetDefaultPaymentMethod().IsEmpty()) + if (!OnRampWidgetConfig->GetDefaultPaymentMethod().IsEmpty()) { - QueryParams.Add(FString(TEXT("defaultPaymentMethod=")) + FPlatformHttp::UrlEncode(TransakConfig->GetDefaultPaymentMethod())); + QueryParams.Add(FString(TEXT("defaultPaymentMethod=")) + FPlatformHttp::UrlEncode(OnRampWidgetConfig->GetDefaultPaymentMethod())); } - if (TransakConfig->GetCryptoCurrencyList().Num() > 0) + if (OnRampWidgetConfig->GetCryptoCurrencyList().Num() > 0) { - QueryParams.Add(FString(TEXT("cryptoCurrencyList=")) + FPlatformHttp::UrlEncode(FString::Join(TransakConfig->GetCryptoCurrencyList(), TEXT(",")))); + QueryParams.Add(FString(TEXT("cryptoCurrencyList=")) + FPlatformHttp::UrlEncode(FString::Join(OnRampWidgetConfig->GetCryptoCurrencyList(), TEXT(",")))); } - if (TransakConfig->GetDisablePaymentMethods().Num() > 0) + if (OnRampWidgetConfig->GetDisablePaymentMethods().Num() > 0) { - QueryParams.Add(FString(TEXT("disablePaymentMethods=")) + FPlatformHttp::UrlEncode(FString::Join(TransakConfig->GetDisablePaymentMethods(), TEXT(",")))); + QueryParams.Add(FString(TEXT("disablePaymentMethods=")) + FPlatformHttp::UrlEncode(FString::Join(OnRampWidgetConfig->GetDisablePaymentMethods(), TEXT(",")))); } Path += TEXT("?"); @@ -161,7 +164,7 @@ void UOnRampWidget::HandleOnUrlChanged(const FText& Text) #if (ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1) void UOnRampWidget::HandleOnConsoleMessage(const FString& Message, const FString& Source, int32 Line, EWebBrowserConsoleLogSeverity Severity) { - IMTBL_LOG("Transak Web Browser console message: %s, Source: %s, Line: %d", *Message, *Source, Line); + UE_LOG(LogImmutableOnRampWidget, Log, TEXT("Web Browser console message: %s, Source: %s, Line: %d"), *Message, *Source, Line); } bool UOnRampWidget::HandleOnBeforePopup(FString URL, FString Frame) diff --git a/Source/ImmutableMarketplace/Public/ImmutableMarketplaceSettings.h b/Source/ImmutableMarketplace/Public/ImmutableMarketplaceSettings.h index fa8eddd2..d1da3e1f 100644 --- a/Source/ImmutableMarketplace/Public/ImmutableMarketplaceSettings.h +++ b/Source/ImmutableMarketplace/Public/ImmutableMarketplaceSettings.h @@ -20,6 +20,6 @@ class IMMUTABLEMARKETPLACE_API UImmutableMarketplaceSettings : public UDeveloper /// 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, Category = "Transak") + UPROPERTY(Config, EditAnywhere, BlueprintReadOnly) TSubclassOf DefaultOnRampWidgetConfig; }; diff --git a/Source/ImmutableMarketplace/Public/OnRampWebBrowserWidget.h b/Source/ImmutableMarketplace/Public/OnRampWebBrowserWidget.h index 1840d314..a16b8d45 100644 --- a/Source/ImmutableMarketplace/Public/OnRampWebBrowserWidget.h +++ b/Source/ImmutableMarketplace/Public/OnRampWebBrowserWidget.h @@ -4,6 +4,9 @@ #include "OnRampWebBrowserWidget.generated.h" + +DECLARE_LOG_CATEGORY_EXTERN(LogImmutableOnRampWidget, Log, All); + #if (ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 1) enum class EWebBrowserConsoleLogSeverity; @@ -14,7 +17,7 @@ class SBluWebBrowser; /** - * A custom web browser widget for Transak transactions. + * A custom web browser widget for Immutable On Ramp funds transactions. */ UCLASS() class IMMUTABLEMARKETPLACE_API UOnRampWidget : public UWidget @@ -34,7 +37,7 @@ class IMMUTABLEMARKETPLACE_API UOnRampWidget : public UWidget bool IsReady() const; /** - * Loads Transak widget with provided user data. + * Loads on ramp widget with provided user data. * * @param WalletAddress The wallet address to load. * @param Email The email associated with the user. diff --git a/Source/ImmutableMarketplace/Public/OnRampWidgetConfig.h b/Source/ImmutableMarketplace/Public/OnRampWidgetConfig.h index 019c0ca2..d84cb963 100644 --- a/Source/ImmutableMarketplace/Public/OnRampWidgetConfig.h +++ b/Source/ImmutableMarketplace/Public/OnRampWidgetConfig.h @@ -192,7 +192,7 @@ class IMMUTABLEMARKETPLACE_API UOnRampWidgetConfig : public UObject * 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 = "Transak") + UPROPERTY(EditDefaultsOnly, Category = "Blockchain") FString Network; /** From b870f1556232446be442cee6a75e4a3d990f2634 Mon Sep 17 00:00:00 2001 From: YermekG Date: Sun, 19 Jan 2025 21:22:33 +1300 Subject: [PATCH 17/25] chore: source code polish --- .../Private/OnRampWebBrowserWidget.cpp | 3 ++- .../Public/OnRampWidgetConfig.h | 20 +++++++++---------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/Source/ImmutableMarketplace/Private/OnRampWebBrowserWidget.cpp b/Source/ImmutableMarketplace/Private/OnRampWebBrowserWidget.cpp index 99604d33..9747c363 100644 --- a/Source/ImmutableMarketplace/Private/OnRampWebBrowserWidget.cpp +++ b/Source/ImmutableMarketplace/Private/OnRampWebBrowserWidget.cpp @@ -99,7 +99,7 @@ FString UOnRampWidget::ComputePath(const FString& WalletAddress, const FString& 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->DisableWalletAddressForm() ? TEXT("true") : TEXT("false"))); + QueryParams.Add(FString(TEXT("disableWalletAddressForm=")) + FPlatformHttp::UrlEncode(OnRampWidgetConfig->IsDisableWalletAddressForm() ? TEXT("true") : TEXT("false"))); if (!OnRampWidgetConfig->GetNetwork().IsEmpty()) { @@ -158,6 +158,7 @@ void UOnRampWidget::HandleOnUrlChanged(const FText& Text) { bIsReady = true; OnWhenReady.Broadcast(); + OnWhenReady.Clear(); } } diff --git a/Source/ImmutableMarketplace/Public/OnRampWidgetConfig.h b/Source/ImmutableMarketplace/Public/OnRampWidgetConfig.h index d84cb963..6d581a0d 100644 --- a/Source/ImmutableMarketplace/Public/OnRampWidgetConfig.h +++ b/Source/ImmutableMarketplace/Public/OnRampWidgetConfig.h @@ -56,7 +56,7 @@ class IMMUTABLEMARKETPLACE_API UOnRampWidgetConfig : public UObject * @details More details could be found under the class parameter * @return Network as FString */ - const FString& GetNetwork() + const FString& GetNetwork() const { return Network; } @@ -65,7 +65,7 @@ class IMMUTABLEMARKETPLACE_API UOnRampWidgetConfig : public UObject * @details More details could be found under the class parameter * @return DefaultFiatCurrency as FString */ - const FString& GetDefaultFiatCurrency() + const FString& GetDefaultFiatCurrency() const { return DefaultFiatCurrency; } @@ -74,7 +74,7 @@ class IMMUTABLEMARKETPLACE_API UOnRampWidgetConfig : public UObject * @details More details could be found under the class parameter * @return DefaultFiatAmount as FString */ - const FString& GetDefaultFiatAmount() + const FString& GetDefaultFiatAmount() const { return DefaultFiatAmount; } @@ -83,7 +83,7 @@ class IMMUTABLEMARKETPLACE_API UOnRampWidgetConfig : public UObject * @details More details could be found under the class parameter * @return DefaultCryptoCurrency as FString */ - const FString& GetDefaultCryptoCurrency() + const FString& GetDefaultCryptoCurrency() const { return DefaultCryptoCurrency; } @@ -92,7 +92,7 @@ class IMMUTABLEMARKETPLACE_API UOnRampWidgetConfig : public UObject * @details More details could be found under the class parameter * @return DefaultPaymentMethod as FString */ - const FString& GetDefaultPaymentMethod() + const FString& GetDefaultPaymentMethod() const { return DefaultPaymentMethod; } @@ -101,7 +101,7 @@ class IMMUTABLEMARKETPLACE_API UOnRampWidgetConfig : public UObject * @details More details could be found under the class parameter * @return DisablePaymentMethods as array of FString */ - const TArray& GetDisablePaymentMethods() + const TArray& GetDisablePaymentMethods() const { return DisablePaymentMethods; } @@ -110,7 +110,7 @@ class IMMUTABLEMARKETPLACE_API UOnRampWidgetConfig : public UObject * @details More details could be found under the class parameter * @return bIsAutoFillUserData as bool */ - bool IsAutoFillUserData() + bool IsAutoFillUserData() const { return bIsAutoFillUserData; } @@ -119,7 +119,7 @@ class IMMUTABLEMARKETPLACE_API UOnRampWidgetConfig : public UObject * @details More details could be found under the class parameter * @return bDisableWalletAddressForm as bool */ - bool DisableWalletAddressForm() + bool IsDisableWalletAddressForm() const { return bDisableWalletAddressForm; } @@ -128,7 +128,7 @@ class IMMUTABLEMARKETPLACE_API UOnRampWidgetConfig : public UObject * @details More details could be found under the class parameter * @return CryptoCurrencyList as array of FString */ - const TArray& GetCryptoCurrencyList() + const TArray& GetCryptoCurrencyList() const { return CryptoCurrencyList; } @@ -137,7 +137,7 @@ class IMMUTABLEMARKETPLACE_API UOnRampWidgetConfig : public UObject * @details More details could be found under the class parameter * @return ThemeColor as FLinearColor */ - const FLinearColor& GetThemeColor() + const FLinearColor& GetThemeColor() const { return ThemeColor; } From 23d30cb1428e2faf7a1815a386a5463ca3946a49 Mon Sep 17 00:00:00 2001 From: ImmutableJeffrey Date: Mon, 16 Dec 2024 02:58:54 +1000 Subject: [PATCH 18/25] chore: add custom openapi template (#3423) --- .../template/cpp-ue4/Build.cs.mustache | 20 + .../template/cpp-ue4/api-header.mustache | 55 ++ .../cpp-ue4/api-operations-header.mustache | 67 +++ .../cpp-ue4/api-operations-source.mustache | 330 ++++++++++++ .../template/cpp-ue4/api-source.mustache | 160 ++++++ .../template/cpp-ue4/helpers-header.mustache | 470 ++++++++++++++++++ .../template/cpp-ue4/helpers-source.mustache | 226 +++++++++ .../template/cpp-ue4/licenseInfo.mustache | 11 + .../cpp-ue4/model-base-header.mustache | 100 ++++ .../cpp-ue4/model-base-source.mustache | 40 ++ .../template/cpp-ue4/model-header.mustache | 74 +++ .../template/cpp-ue4/model-source.mustache | 185 +++++++ .../template/cpp-ue4/module-header.mustache | 15 + .../template/cpp-ue4/module-source.mustache | 13 + 14 files changed, 1766 insertions(+) create mode 100644 Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/Build.cs.mustache create mode 100644 Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/api-header.mustache create mode 100644 Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/api-operations-header.mustache create mode 100644 Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/api-operations-source.mustache create mode 100644 Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/api-source.mustache create mode 100644 Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/helpers-header.mustache create mode 100644 Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/helpers-source.mustache create mode 100644 Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/licenseInfo.mustache create mode 100644 Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/model-base-header.mustache create mode 100644 Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/model-base-source.mustache create mode 100644 Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/model-header.mustache create mode 100644 Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/model-source.mustache create mode 100644 Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/module-header.mustache create mode 100644 Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/module-source.mustache diff --git a/Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/Build.cs.mustache b/Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/Build.cs.mustache new file mode 100644 index 00000000..bfbee115 --- /dev/null +++ b/Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/Build.cs.mustache @@ -0,0 +1,20 @@ +{{>licenseInfo}} +using System; +using System.IO; +using UnrealBuildTool; + +public class {{unrealModuleName}} : ModuleRules +{ + public {{unrealModuleName}}(ReadOnlyTargetRules Target) : base(Target) + { + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", + "HTTP", + "Json", + } + ); + PCHUsage = PCHUsageMode.NoPCHs; + } +} \ No newline at end of file diff --git a/Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/api-header.mustache b/Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/api-header.mustache new file mode 100644 index 00000000..4f2bec00 --- /dev/null +++ b/Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/api-header.mustache @@ -0,0 +1,55 @@ +{{>licenseInfo}} +#pragma once + +#include "CoreMinimal.h" +#include "{{modelNamePrefix}}BaseModel.h" + +{{#cppNamespaceDeclarations}} +namespace {{this}} +{ +{{/cppNamespaceDeclarations}} + +class {{dllapi}} {{classname}} +{ +public: + {{classname}}(); + ~{{classname}}(); + + /* Sets the URL Endpoint. + * Note: several fallback endpoints can be configured in request retry policies, see Request::SetShouldRetry */ + void SetURL(const FString& Url); + + /* Adds global header params to all requests */ + void AddHeaderParam(const FString& Key, const FString& Value); + void ClearHeaderParams(); + + /* Sets the retry manager to the user-defined retry manager. User must manage the lifetime of the retry manager. + * If no retry manager is specified and a request needs retries, a default retry manager will be used. + * See also: Request::SetShouldRetry */ + void SetHttpRetryManager(FHttpRetrySystem::FManager& RetryManager); + FHttpRetrySystem::FManager& GetHttpRetryManager(); + + {{#operations}}{{#operation}}class {{operationIdCamelCase}}Request; + class {{operationIdCamelCase}}Response; + {{/operation}}{{/operations}} + {{#operations}}{{#operation}}DECLARE_DELEGATE_OneParam(F{{operationIdCamelCase}}Delegate, const {{operationIdCamelCase}}Response&); + {{/operation}}{{/operations}} + {{#operations}}{{#operation}}{{#description}}/* {{{.}}} */ + {{/description}}FHttpRequestPtr {{operationIdCamelCase}}(const {{operationIdCamelCase}}Request& Request, const F{{operationIdCamelCase}}Delegate& Delegate = F{{operationIdCamelCase}}Delegate()) const; + {{/operation}}{{/operations}} +private: + {{#operations}}{{#operation}}void On{{operationIdCamelCase}}Response(FHttpRequestPtr HttpRequest, FHttpResponsePtr HttpResponse, bool bSucceeded, F{{operationIdCamelCase}}Delegate Delegate) const; + {{/operation}}{{/operations}} + FHttpRequestRef CreateHttpRequest(const Request& Request) const; + bool IsValid() const; + void HandleResponse(FHttpResponsePtr HttpResponse, bool bSucceeded, Response& InOutResponse) const; + + FString Url; + TMap AdditionalHeaderParams; + mutable FHttpRetrySystem::FManager* RetryManager = nullptr; + mutable TUniquePtr DefaultRetryManager; +}; + +{{#cppNamespaceDeclarations}} +} +{{/cppNamespaceDeclarations}} diff --git a/Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/api-operations-header.mustache b/Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/api-operations-header.mustache new file mode 100644 index 00000000..99b57ea2 --- /dev/null +++ b/Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/api-operations-header.mustache @@ -0,0 +1,67 @@ +{{>licenseInfo}} +#pragma once + +#include "{{modelNamePrefix}}BaseModel.h" +#include "{{classname}}.h" + +{{#imports}}{{{import}}} +{{/imports}} + +{{#cppNamespaceDeclarations}} +namespace {{this}} +{ +{{/cppNamespaceDeclarations}} + +{{#operations}} +{{#operation}} +/* {{summary}} +{{#notes}} * + * {{.}}{{/notes}} +*/ +class {{dllapi}} {{classname}}::{{operationIdCamelCase}}Request : public Request +{ +public: + virtual ~{{operationIdCamelCase}}Request() {} + void SetupHttpRequest(const FHttpRequestRef& HttpRequest) const final; + FString ComputePath() const final; + + {{#allParams}} + {{#isEnum}} + {{#allowableValues}} + enum class {{{enumName}}} + { + {{#enumVars}} + {{name}}, + {{/enumVars}} + }; + {{/allowableValues}} + + static FString EnumToString(const {{{enumName}}}& EnumValue); + static bool EnumFromString(const FString& EnumAsString, {{{enumName}}}& EnumValue); + {{#description}}/* {{{.}}} */ + {{/description}}{{^required}}TOptional<{{/required}}{{{datatypeWithEnum}}}{{^required}}>{{/required}} {{paramName}}{{#required}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}{{/required}}; + {{/isEnum}} + {{^isEnum}} + {{#description}}/* {{{.}}} */ + {{/description}}{{^required}}TOptional<{{/required}}{{{dataType}}}{{^required}}>{{/required}} {{paramName}}{{#required}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}{{/required}}; + {{/isEnum}} + {{/allParams}} +}; + +class {{dllapi}} {{classname}}::{{operationIdCamelCase}}Response : public Response +{ +public: + virtual ~{{operationIdCamelCase}}Response() {} + {{#responses.0}} + void SetHttpResponseCode(EHttpResponseCodes::Type InHttpResponseCode) final; + {{/responses.0}} + bool FromJson(const TSharedPtr& JsonValue) final; + + {{#returnType}}{{{.}}} Content;{{/returnType}} +}; + +{{/operation}} +{{/operations}} +{{#cppNamespaceDeclarations}} +} +{{/cppNamespaceDeclarations}} diff --git a/Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/api-operations-source.mustache b/Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/api-operations-source.mustache new file mode 100644 index 00000000..e58a4602 --- /dev/null +++ b/Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/api-operations-source.mustache @@ -0,0 +1,330 @@ +{{>licenseInfo}} +#include "{{classname}}Operations.h" + +#include "{{unrealModuleName}}Module.h" +#include "{{modelNamePrefix}}Helpers.h" + +#include "Dom/JsonObject.h" +#include "Templates/SharedPointer.h" +#include "HttpModule.h" +#include "PlatformHttp.h" + +{{#cppNamespaceDeclarations}} +namespace {{this}} +{ +{{/cppNamespaceDeclarations}} +{{#operations}}{{#operation}} +{{#allParams}} +{{#isEnum}} +inline FString ToString(const {{classname}}::{{operationIdCamelCase}}Request::{{{enumName}}}& Value) +{ + {{#allowableValues}} + switch (Value) + { + {{#enumVars}} + case {{classname}}::{{operationIdCamelCase}}Request::{{{enumName}}}::{{name}}: + return TEXT("{{{value}}}"); + {{/enumVars}} + } + {{/allowableValues}} + + UE_LOG(Log{{unrealModuleName}}, Error, TEXT("Invalid {{classname}}::{{operationIdCamelCase}}Request::{{{enumName}}} Value (%d)"), (int)Value); + return TEXT(""); +} + +FString {{classname}}::{{operationIdCamelCase}}Request::EnumToString(const {{classname}}::{{operationIdCamelCase}}Request::{{{enumName}}}& EnumValue) +{ + return ToString(EnumValue); +} + +inline bool FromString(const FString& EnumAsString, {{classname}}::{{operationIdCamelCase}}Request::{{{enumName}}}& Value) +{ + static TMap StringToEnum = { {{#allowableValues}}{{#enumVars}} + { TEXT("{{{value}}}"), {{classname}}::{{operationIdCamelCase}}Request::{{{enumName}}}::{{name}} },{{/enumVars}}{{/allowableValues}} }; + + const auto Found = StringToEnum.Find(EnumAsString); + if(Found) + Value = *Found; + + return Found != nullptr; +} + +bool {{classname}}::{{operationIdCamelCase}}Request::EnumFromString(const FString& EnumAsString, {{classname}}::{{operationIdCamelCase}}Request::{{{enumName}}}& EnumValue) +{ + return FromString(EnumAsString, EnumValue); +} + +inline void WriteJsonValue(JsonWriter& Writer, const {{classname}}::{{operationIdCamelCase}}Request::{{{enumName}}}& Value) +{ + WriteJsonValue(Writer, ToString(Value)); +} + +inline bool TryGetJsonValue(const TSharedPtr& JsonValue, {{classname}}::{{operationIdCamelCase}}Request::{{{enumName}}}& Value) +{ + {{#allowableValues}} + FString TmpValue; + if (JsonValue->TryGetString(TmpValue)) + { + if(FromString(TmpValue, Value)) + return true; + } + {{/allowableValues}} + return false; +} + +{{/isEnum}} +{{/allParams}} +FString {{classname}}::{{operationIdCamelCase}}Request::ComputePath() const +{ + {{^pathParams.0}} + FString Path(TEXT("{{{path}}}")); + {{/pathParams.0}} + {{#pathParams.0}} + TMap PathParams = { {{#pathParams}} + { TEXT("{{baseName}}"), FStringFormatArg(ToUrlString({{paramName}})) }{{^-last}},{{/-last}}{{/pathParams}} }; + + FString Path = FString::Format(TEXT("{{{path}}}"), PathParams); + + {{/pathParams.0}} + {{#queryParams.0}} + TArray QueryParams; + {{#queryParams}} + {{#required}} + {{^collectionFormat}} + QueryParams.Add(FString(TEXT("{{baseName}}=")) + ToUrlString({{paramName}})); + {{/collectionFormat}} + {{#collectionFormat}} + {{#isCollectionFormatMulti}} + QueryParams.Add(CollectionToUrlString_{{collectionFormat}}({{paramName}}, TEXT("{{baseName}}"))); + {{/isCollectionFormatMulti}} + {{^isCollectionFormatMulti}} + QueryParams.Add(FString(TEXT("{{baseName}}=")) + CollectionToUrlString_{{collectionFormat}}({{paramName}}, TEXT("{{baseName}}"))); + {{/isCollectionFormatMulti}} + {{/collectionFormat}} + {{/required}} + {{^required}} + {{^collectionFormat}} + if({{paramName}}.IsSet()) + { + QueryParams.Add(FString(TEXT("{{baseName}}=")) + ToUrlString({{paramName}}.GetValue())); + } + {{/collectionFormat}} + {{#collectionFormat}} + if({{paramName}}.IsSet()) + { + {{#isCollectionFormatMulti}} + QueryParams.Add(CollectionToUrlString_{{collectionFormat}}({{paramName}}.GetValue(), TEXT("{{baseName}}"))); + {{/isCollectionFormatMulti}} + {{^isCollectionFormatMulti}} + QueryParams.Add(FString(TEXT("{{baseName}}=")) + CollectionToUrlString_{{collectionFormat}}({{paramName}}.GetValue(), TEXT("{{baseName}}"))); + {{/isCollectionFormatMulti}} + } + {{/collectionFormat}} + {{/required}} + {{/queryParams}} + Path += TCHAR('?'); + Path += FString::Join(QueryParams, TEXT("&")); + + {{/queryParams.0}} + return Path; +} + +void {{classname}}::{{operationIdCamelCase}}Request::SetupHttpRequest(const FHttpRequestRef& HttpRequest) const +{ + static const TArray Consumes = { {{#consumes}}TEXT("{{{mediaType}}}"){{^-last}}, {{/-last}}{{/consumes}} }; + //static const TArray Produces = { {{#produces}}TEXT("{{{mediaType}}}"){{^-last}}, {{/-last}}{{/produces}} }; + + HttpRequest->SetVerb(TEXT("{{httpMethod}}")); + {{#headerParams.0}} + + // Header parameters + {{#headerParams}} + {{#required}} + HttpRequest->SetHeader(TEXT("{{baseName}}"), {{paramName}}); + {{/required}} + {{^required}} + if ({{paramName}}.IsSet()) + { + HttpRequest->SetHeader(TEXT("{{baseName}}"), {{paramName}}.GetValue()); + } + {{/required}} + {{/headerParams}} + {{/headerParams.0}} + + {{#isBodyAllowed}} + // Default to Json Body request + if (Consumes.Num() == 0 || Consumes.Contains(TEXT("application/json"))) + { + {{#bodyParams.0}} + // Body parameters + FString JsonBody; + JsonWriter Writer = TJsonWriterFactory<>::Create(&JsonBody); + + {{#bodyParams}} + {{#required}} + WriteJsonValue(Writer, {{paramName}}); + {{/required}} + {{^required}} + if ({{paramName}}.IsSet()) + { + WriteJsonValue(Writer, {{paramName}}.GetValue()); + } + {{/required}} + {{/bodyParams}} + Writer->Close(); + + HttpRequest->SetHeader(TEXT("Content-Type"), TEXT("application/json; charset=utf-8")); + HttpRequest->SetContentAsString(JsonBody); + {{/bodyParams.0}} + {{^bodyParams.0}} + {{#formParams.0}} + // Form parameters added to try to generate a json body when no body parameters are specified. + FString JsonBody; + JsonWriter Writer = TJsonWriterFactory<>::Create(&JsonBody); + Writer->WriteObjectStart(); + {{#formParams}} + {{#isFile}} + UE_LOG(Log{{unrealModuleName}}, Error, TEXT("Form parameter ({{baseName}}) was ignored, Files are not supported in json body")); + {{/isFile}} + {{^isFile}} + {{#required}} + Writer->WriteIdentifierPrefix(TEXT("{{baseName}}")); + WriteJsonValue(Writer, {{paramName}}); + {{/required}} + {{^required}} + if ({{paramName}}.IsSet()){ + Writer->WriteIdentifierPrefix(TEXT("{{baseName}}")); + WriteJsonValue(Writer, {{paramName}}.GetValue()); + } + {{/required}} + {{/isFile}} + {{/formParams}} + Writer->WriteObjectEnd(); + Writer->Close(); + HttpRequest->SetHeader(TEXT("Content-Type"), TEXT("application/json; charset=utf-8")); + HttpRequest->SetContentAsString(JsonBody); + {{/formParams.0}} + {{/bodyParams.0}} + } + else if (Consumes.Contains(TEXT("multipart/form-data"))) + { + {{#formParams.0}} + HttpMultipartFormData FormData; + {{#formParams}} + {{#isContainer}} + UE_LOG(Log{{unrealModuleName}}, Error, TEXT("Form parameter ({{baseName}}) was ignored, Collections are not supported in multipart form")); + {{/isContainer}} + {{^isContainer}} + {{#required}} + {{#isFile}} + FormData.AddFilePart(TEXT("{{baseName}}"), {{paramName}}); + {{/isFile}} + {{^isFile}} + {{#isBinary}} + FormData.AddBinaryPart(TEXT("{{baseName}}"), {{paramName}}); + {{/isBinary}} + {{^isBinary}} + FormData.AddStringPart(TEXT("{{baseName}}"), *ToUrlString({{paramName}})); + {{/isBinary}} + {{/isFile}} + {{/required}} + {{^required}} + if({{paramName}}.IsSet()) + { + {{#isFile}} + FormData.AddFilePart(TEXT("{{baseName}}"), {{paramName}}.GetValue()); + {{/isFile}} + {{^isFile}} + {{#isBinary}} + FormData.AddBinaryPart(TEXT("{{baseName}}"), {{paramName}}.GetValue()); + {{/isBinary}} + {{^isBinary}} + FormData.AddStringPart(TEXT("{{baseName}}"), *ToUrlString({{paramName}}.GetValue())); + {{/isBinary}} + {{/isFile}} + } + {{/required}} + {{/isContainer}} + {{/formParams}} + + FormData.SetupHttpRequest(HttpRequest); + {{/formParams.0}} + {{#bodyParams.0}} + {{#bodyParams}} + UE_LOG(Log{{unrealModuleName}}, Error, TEXT("Body parameter ({{baseName}}) was ignored, not supported in multipart form")); + {{/bodyParams}} + {{/bodyParams.0}} + } + else if (Consumes.Contains(TEXT("application/x-www-form-urlencoded"))) + { + {{#formParams.0}} + TArray FormParams; + {{#formParams}} + {{#isContainer}} + UE_LOG(Log{{unrealModuleName}}, Error, TEXT("Form parameter ({{baseName}}) was ignored, Collections are not supported in urlencoded requests")); + {{/isContainer}} + {{#isFile}} + UE_LOG(Log{{unrealModuleName}}, Error, TEXT("Form parameter ({{baseName}}) was ignored, Files are not supported in urlencoded requests")); + {{/isFile}} + {{^isFile}} + {{^isContainer}} + {{#required}} + FormParams.Add(FString(TEXT("{{baseName}}=")) + ToUrlString({{paramName}})); + {{/required}} + {{^required}} + if({{paramName}}.IsSet()) + { + FormParams.Add(FString(TEXT("{{baseName}}=")) + ToUrlString({{paramName}}.GetValue())); + } + {{/required}} + {{/isContainer}} + {{/isFile}} + {{/formParams}} + + HttpRequest->SetHeader(TEXT("Content-Type"), TEXT("application/x-www-form-urlencoded; charset=utf-8")); + HttpRequest->SetContentAsString(FString::Join(FormParams, TEXT("&"))); + {{/formParams.0}} + {{#bodyParams.0}} + {{#bodyParams}} + UE_LOG(Log{{unrealModuleName}}, Error, TEXT("Body parameter ({{baseName}}) was ignored, not supported in urlencoded requests")); + {{/bodyParams}} + {{/bodyParams.0}} + } + else + { + UE_LOG(Log{{unrealModuleName}}, Error, TEXT("Request ContentType not supported (%s)"), *FString::Join(Consumes, TEXT(","))); + } + {{/isBodyAllowed}} +} + +{{#responses.0}} +void {{classname}}::{{operationIdCamelCase}}Response::SetHttpResponseCode(EHttpResponseCodes::Type InHttpResponseCode) +{ + Response::SetHttpResponseCode(InHttpResponseCode); + switch ((int)InHttpResponseCode) + { + {{#responses}} + case {{code}}: + {{#isDefault}} + default: + {{/isDefault}} + SetResponseString(TEXT("{{message}}")); + break; + {{/responses}} + } +} +{{/responses.0}} + +bool {{classname}}::{{operationIdCamelCase}}Response::FromJson(const TSharedPtr& JsonValue) +{ + {{#returnType}} + return TryGetJsonValue(JsonValue, Content); + {{/returnType}} + {{^returnType}} + return true; + {{/returnType}} +} +{{/operation}}{{/operations}} +{{#cppNamespaceDeclarations}} +} +{{/cppNamespaceDeclarations}} diff --git a/Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/api-source.mustache b/Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/api-source.mustache new file mode 100644 index 00000000..d6091e4e --- /dev/null +++ b/Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/api-source.mustache @@ -0,0 +1,160 @@ +{{>licenseInfo}} +#include "{{classname}}.h" + +#include "{{classname}}Operations.h" +#include "{{unrealModuleName}}Module.h" + +#include "HttpModule.h" +#include "Serialization/JsonSerializer.h" + +{{#cppNamespaceDeclarations}} +namespace {{this}} +{ +{{/cppNamespaceDeclarations}} + +{{classname}}::{{classname}}() +: Url(TEXT("{{basePath}}")) +{ +} + +{{classname}}::~{{classname}}() {} + +void {{classname}}::SetURL(const FString& InUrl) +{ + Url = InUrl; +} + +void {{classname}}::AddHeaderParam(const FString& Key, const FString& Value) +{ + AdditionalHeaderParams.Add(Key, Value); +} + +void {{classname}}::ClearHeaderParams() +{ + AdditionalHeaderParams.Reset(); +} + +bool {{classname}}::IsValid() const +{ + if (Url.IsEmpty()) + { + UE_LOG(Log{{unrealModuleName}}, Error, TEXT("{{classname}}: Endpoint Url is not set, request cannot be performed")); + return false; + } + + return true; +} + +void {{classname}}::SetHttpRetryManager(FHttpRetrySystem::FManager& InRetryManager) +{ + if (RetryManager != &InRetryManager) + { + DefaultRetryManager.Reset(); + RetryManager = &InRetryManager; + } +} + +FHttpRetrySystem::FManager& {{classname}}::GetHttpRetryManager() +{ + checkf(RetryManager, TEXT("{{classname}}: RetryManager is null. You may have meant to set it with SetHttpRetryManager first, or you may not be using a custom RetryManager at all.")) + return *RetryManager; +} + +FHttpRequestRef {{classname}}::CreateHttpRequest(const Request& Request) const +{ + if (!Request.GetRetryParams().IsSet()) + { + return FHttpModule::Get().CreateRequest(); + } + else + { + if (!RetryManager) + { + // Create default retry manager if none was specified + DefaultRetryManager = MakeUnique(6, 60); + RetryManager = DefaultRetryManager.Get(); + } + + const HttpRetryParams& Params = Request.GetRetryParams().GetValue(); + return RetryManager->CreateRequest(Params.RetryLimitCountOverride, Params.RetryTimeoutRelativeSecondsOverride, Params.RetryResponseCodes, Params.RetryVerbs, Params.RetryDomains); + } +} + +void {{classname}}::HandleResponse(FHttpResponsePtr HttpResponse, bool bSucceeded, Response& InOutResponse) const +{ + InOutResponse.SetHttpResponse(HttpResponse); + InOutResponse.SetSuccessful(bSucceeded); + + if (bSucceeded && HttpResponse.IsValid()) + { + InOutResponse.SetHttpResponseCode((EHttpResponseCodes::Type)HttpResponse->GetResponseCode()); + FString ContentType = HttpResponse->GetContentType(); + FString Content; + + if (ContentType.IsEmpty()) + { + return; // Nothing to parse + } + else if (ContentType.StartsWith(TEXT("application/json")) || ContentType.StartsWith("text/json")) + { + Content = HttpResponse->GetContentAsString(); + + TSharedPtr JsonValue; + auto Reader = TJsonReaderFactory<>::Create(Content); + + if (FJsonSerializer::Deserialize(Reader, JsonValue) && JsonValue.IsValid()) + { + if (InOutResponse.FromJson(JsonValue)) + return; // Successfully parsed + } + } + else if(ContentType.StartsWith(TEXT("text/plain"))) + { + Content = HttpResponse->GetContentAsString(); + InOutResponse.SetResponseString(Content); + return; // Successfully parsed + } + + // Report the parse error but do not mark the request as unsuccessful. Data could be partial or malformed, but the request succeeded. + UE_LOG(Log{{unrealModuleName}}, Error, TEXT("Failed to deserialize Http response content (type:%s):\n%s"), *ContentType , *Content); + return; + } + + // By default, assume we failed to establish connection + InOutResponse.SetHttpResponseCode(EHttpResponseCodes::RequestTimeout); +} + +{{#operations}} +{{#operation}} +FHttpRequestPtr {{classname}}::{{operationIdCamelCase}}(const {{operationIdCamelCase}}Request& Request, const F{{operationIdCamelCase}}Delegate& Delegate /*= F{{operationIdCamelCase}}Delegate()*/) const +{ + if (!IsValid()) + return nullptr; + + FHttpRequestRef HttpRequest = CreateHttpRequest(Request); + HttpRequest->SetURL(*(Url + Request.ComputePath())); + + for(const auto& It : AdditionalHeaderParams) + { + HttpRequest->SetHeader(It.Key, It.Value); + } + + Request.SetupHttpRequest(HttpRequest); + + HttpRequest->OnProcessRequestComplete().BindRaw(this, &{{classname}}::On{{operationIdCamelCase}}Response, Delegate); + HttpRequest->ProcessRequest(); + return HttpRequest; +} + +void {{classname}}::On{{operationIdCamelCase}}Response(FHttpRequestPtr HttpRequest, FHttpResponsePtr HttpResponse, bool bSucceeded, F{{operationIdCamelCase}}Delegate Delegate) const +{ + {{operationIdCamelCase}}Response Response; + HandleResponse(HttpResponse, bSucceeded, Response); + Delegate.ExecuteIfBound(Response); +} + +{{/operation}} +{{/operations}} +{{#cppNamespaceDeclarations}} +} +{{/cppNamespaceDeclarations}} diff --git a/Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/helpers-header.mustache b/Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/helpers-header.mustache new file mode 100644 index 00000000..d551cd89 --- /dev/null +++ b/Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/helpers-header.mustache @@ -0,0 +1,470 @@ +{{>licenseInfo}} +#pragma once + +#include "{{modelNamePrefix}}BaseModel.h" + +#include "Serialization/JsonSerializer.h" +#include "Dom/JsonObject.h" +#include "Misc/Base64.h" +#include "PlatformHttp.h" + +class IHttpRequest; + +{{#cppNamespaceDeclarations}} +namespace {{this}} +{ +{{/cppNamespaceDeclarations}} + +typedef TSharedRef> JsonWriter; + +////////////////////////////////////////////////////////////////////////// + +class {{dllapi}} HttpFileInput +{ +public: + explicit HttpFileInput(const TCHAR* InFilePath); + explicit HttpFileInput(const FString& InFilePath); + + // This will automatically set the content type if not already set + void SetFilePath(const TCHAR* InFilePath); + void SetFilePath(const FString& InFilePath); + + // Optional if it can be deduced from the FilePath + void SetContentType(const TCHAR* ContentType); + + HttpFileInput& operator=(const HttpFileInput& Other) = default; + HttpFileInput& operator=(const FString& InFilePath) { SetFilePath(*InFilePath); return*this; } + HttpFileInput& operator=(const TCHAR* InFilePath) { SetFilePath(InFilePath); return*this; } + + const FString& GetFilePath() const { return FilePath; } + const FString& GetContentType() const { return ContentType; } + + // Returns the filename with extension + FString GetFilename() const; + +private: + FString FilePath; + FString ContentType; +}; + +////////////////////////////////////////////////////////////////////////// + +class HttpMultipartFormData +{ +public: + void SetBoundary(const TCHAR* InBoundary); + void SetupHttpRequest(const FHttpRequestRef& HttpRequest); + + void AddStringPart(const TCHAR* Name, const TCHAR* Data); + void AddJsonPart(const TCHAR* Name, const FString& JsonString); + void AddBinaryPart(const TCHAR* Name, const TArray& ByteArray); + void AddFilePart(const TCHAR* Name, const HttpFileInput& File); + +private: + void AppendString(const TCHAR* Str); + const FString& GetBoundary() const; + + mutable FString Boundary; + TArray FormData; + + static const TCHAR* Delimiter; + static const TCHAR* Newline; +}; + +////////////////////////////////////////////////////////////////////////// + +// Decodes Base64Url encoded strings, see https://en.wikipedia.org/wiki/Base64#Variants_summary_table +template +bool Base64UrlDecode(const FString& Base64String, T& Value) +{ + FString TmpCopy(Base64String); + TmpCopy.ReplaceInline(TEXT("-"), TEXT("+")); + TmpCopy.ReplaceInline(TEXT("_"), TEXT("/")); + + return FBase64::Decode(TmpCopy, Value); +} + +// Encodes strings in Base64Url, see https://en.wikipedia.org/wiki/Base64#Variants_summary_table +template +FString Base64UrlEncode(const T& Value) +{ + FString Base64String = FBase64::Encode(Value); + Base64String.ReplaceInline(TEXT("+"), TEXT("-")); + Base64String.ReplaceInline(TEXT("/"), TEXT("_")); + return Base64String; +} + +template +inline auto ToString(const T& Value) + -> typename std::enable_if::value, FString>::type +{ + return ::LexToString(Value); +} + +template +inline auto ToString(const T& EnumModelValue) + -> typename std::enable_if::value, FString>::type +{ + return T::EnumToString(EnumModelValue.Value); +} + +inline FString ToString(const Model& Value) +{ + FString String; + JsonWriter Writer = TJsonWriterFactory<>::Create(&String); + Value.WriteJson(Writer); + Writer->Close(); + return String; +} + +inline FString ToString(const FDateTime& Value) +{ + return Value.ToIso8601(); +} + +inline FString ToString(const FGuid& Value) +{ + return Value.ToString(EGuidFormats::DigitsWithHyphens); +} + +inline FString ToString(const TArray& Value) +{ + return FBase64::Encode(Value); +} + +template +inline FString ToUrlString(const T& Value) +{ + return FPlatformHttp::UrlEncode(ToString(Value)); +} + +inline FString ToUrlString(const TArray& Value) +{ + return Base64UrlEncode(Value); +} + +template +inline FString CollectionToUrlString(const TArray& Collection, const TCHAR* Separator) +{ + FString Output; + if(Collection.Num() == 0) + return Output; + + Output += ToUrlString(Collection[0]); + for(int i = 1; i < Collection.Num(); i++) + { + Output += FString::Format(TEXT("{0}{1}"), { Separator, *ToUrlString(Collection[i]) }); + } + return Output; +} + +template +inline FString CollectionToUrlString_csv(const TArray& Collection, const TCHAR* BaseName) +{ + return CollectionToUrlString(Collection, TEXT(",")); +} + +template +inline FString CollectionToUrlString_ssv(const TArray& Collection, const TCHAR* BaseName) +{ + return CollectionToUrlString(Collection, TEXT(" ")); +} + +template +inline FString CollectionToUrlString_tsv(const TArray& Collection, const TCHAR* BaseName) +{ + return CollectionToUrlString(Collection, TEXT("\t")); +} + +template +inline FString CollectionToUrlString_pipes(const TArray& Collection, const TCHAR* BaseName) +{ + return CollectionToUrlString(Collection, TEXT("|")); +} + +template +inline FString CollectionToUrlString_multi(const TArray& Collection, const TCHAR* BaseName) +{ + FString Output; + if(Collection.Num() == 0) + return Output; + + Output += FString::Format(TEXT("{0}={1}"), { FStringFormatArg(BaseName), ToUrlString(Collection[0]) }); + for(int i = 1; i < Collection.Num(); i++) + { + Output += FString::Format(TEXT("&{0}={1}"), { FStringFormatArg(BaseName), ToUrlString(Collection[i]) }); + } + return Output; +} + + +template +inline FString CollectionToUrlString_multi(const TSet& Collection, const TCHAR* BaseName) +{ + FString Output; + if (Collection.Num() == 0) + { + return Output; + } + + int32 Index = 0; + for (typename TSet::TConstIterator Iter = Collection.CreateConstIterator(); Iter; ++Iter) + { + if (Index == 0) + { + Output += FString::Format(TEXT("{0}={1}"), { FStringFormatArg(BaseName), ToUrlString(*Iter) }); + Index++; + continue; + } + Output += FString::Format(TEXT("&{0}={1}"), { FStringFormatArg(BaseName), ToUrlString(*Iter) }); + } + return Output; +} + +////////////////////////////////////////////////////////////////////////// + +inline void WriteJsonValue(JsonWriter& Writer, const TSharedPtr& Value) +{ + if (Value.IsValid()) + { + FJsonSerializer::Serialize(Value.ToSharedRef(), "", Writer, false); + } + else + { + Writer->WriteObjectStart(); + Writer->WriteObjectEnd(); + } +} + +inline void WriteJsonValue(JsonWriter& Writer, const TSharedPtr& Value) +{ + if (Value.IsValid()) + { + FJsonSerializer::Serialize(Value.ToSharedRef(), Writer, false); + } + else + { + Writer->WriteObjectStart(); + Writer->WriteObjectEnd(); + } +} + +inline void WriteJsonValue(JsonWriter& Writer, const TArray& Value) +{ + Writer->WriteValue(FBase64::Encode(Value)); +} + +inline void WriteJsonValue(JsonWriter& Writer, const FDateTime& Value) +{ + Writer->WriteValue(Value.ToIso8601()); +} + +inline void WriteJsonValue(JsonWriter& Writer, const FGuid& Value) +{ + Writer->WriteValue(Value.ToString(EGuidFormats::DigitsWithHyphens)); +} + +inline void WriteJsonValue(JsonWriter& Writer, const Model& Value) +{ + Value.WriteJson(Writer); +} + +template::value, int>::type = 0> +inline void WriteJsonValue(JsonWriter& Writer, const T& Value) +{ + Writer->WriteValue(Value); +} + +template +inline void WriteJsonValue(JsonWriter& Writer, const TArray& Value) +{ + Writer->WriteArrayStart(); + for (const auto& Element : Value) + { + WriteJsonValue(Writer, Element); + } + Writer->WriteArrayEnd(); +} + +template +inline void WriteJsonValue(JsonWriter& Writer, const TMap& Value) +{ + Writer->WriteObjectStart(); + for (const auto& It : Value) + { + Writer->WriteIdentifierPrefix(It.Key); + WriteJsonValue(Writer, It.Value); + } + Writer->WriteObjectEnd(); +} + +////////////////////////////////////////////////////////////////////////// + +inline bool TryGetJsonValue(const TSharedPtr& JsonValue, FString& Value) +{ + FString TmpValue; + if (JsonValue->TryGetString(TmpValue)) + { + Value = TmpValue; + return true; + } + else + return false; +} + +{{dllapi}} bool ParseDateTime(const FString& DateTimeString, FDateTime& OutDateTime); + +inline bool TryGetJsonValue(const TSharedPtr& JsonValue, FDateTime& Value) +{ + FString TmpValue; + if (JsonValue->TryGetString(TmpValue)) + { + return ParseDateTime(TmpValue, Value); + } + else + return false; +} + +inline bool TryGetJsonValue(const TSharedPtr& JsonValue, FGuid& Value) +{ + FString TmpValue; + if (JsonValue->TryGetString(TmpValue)) + { + return FGuid::Parse(TmpValue, Value); + } + else + return false; +} + +inline bool TryGetJsonValue(const TSharedPtr& JsonValue, bool& Value) +{ + bool TmpValue; + if (JsonValue->TryGetBool(TmpValue)) + { + Value = TmpValue; + return true; + } + else + return false; +} + +inline bool TryGetJsonValue(const TSharedPtr& JsonValue, TSharedPtr& JsonObjectValue) +{ + JsonObjectValue = JsonValue; + return true; +} + +inline bool TryGetJsonValue(const TSharedPtr& JsonValue, TSharedPtr& JsonObjectValue) +{ + const TSharedPtr* Object; + if (JsonValue->TryGetObject(Object)) + { + JsonObjectValue = *Object; + return true; + } + return false; +} + +inline bool TryGetJsonValue(const TSharedPtr& JsonValue, TArray& Value) +{ + FString TmpValue; + if (JsonValue->TryGetString(TmpValue)) + { + FBase64::Decode(TmpValue, Value); + return true; + } + else + return false; +} + +inline bool TryGetJsonValue(const TSharedPtr& JsonValue, Model& Value) +{ + return Value.FromJson(JsonValue); +} + +template::value, int>::type = 0> +inline bool TryGetJsonValue(const TSharedPtr& JsonValue, T& Value) +{ + T TmpValue; + if (JsonValue->TryGetNumber(TmpValue)) + { + Value = TmpValue; + return true; + } + else + return false; +} + +template +inline bool TryGetJsonValue(const TSharedPtr& JsonValue, TArray& ArrayValue) +{ + const TArray>* JsonArray; + if (JsonValue->TryGetArray(JsonArray)) + { + bool ParseSuccess = true; + const int32 Count = JsonArray->Num(); + ArrayValue.Reset(Count); + for (int i = 0; i < Count; i++) + { + T TmpValue; + ParseSuccess &= TryGetJsonValue((*JsonArray)[i], TmpValue); + ArrayValue.Emplace(MoveTemp(TmpValue)); + } + return ParseSuccess; + } + return false; +} + +template +inline bool TryGetJsonValue(const TSharedPtr& JsonValue, TMap& MapValue) +{ + const TSharedPtr* Object; + if (JsonValue->TryGetObject(Object)) + { + MapValue.Reset(); + bool ParseSuccess = true; + for (const auto& It : (*Object)->Values) + { + T TmpValue; + ParseSuccess &= TryGetJsonValue(It.Value, TmpValue); + MapValue.Emplace(It.Key, MoveTemp(TmpValue)); + } + return ParseSuccess; + } + return false; +} + +template +inline bool TryGetJsonValue(const TSharedPtr& JsonObject, const FString& Key, T& Value) +{ + const TSharedPtr JsonValue = JsonObject->TryGetField(Key); + if (JsonValue.IsValid() && !JsonValue->IsNull()) + { + return TryGetJsonValue(JsonValue, Value); + } + return false; +} + +template +inline bool TryGetJsonValue(const TSharedPtr& JsonObject, const FString& Key, TOptional& OptionalValue) +{ + const TSharedPtr JsonValue = JsonObject->TryGetField(Key); + if (JsonValue.IsValid() && !JsonValue->IsNull()) + { + T Value; + if (TryGetJsonValue(JsonValue, Value)) + { + OptionalValue = Value; + return true; + } + else + return false; + } + // Absence of optional value is not a parsing error. + // Nullable is handled like optional. + return true; +} + +{{#cppNamespaceDeclarations}} +} +{{/cppNamespaceDeclarations}} diff --git a/Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/helpers-source.mustache b/Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/helpers-source.mustache new file mode 100644 index 00000000..fdb6ade3 --- /dev/null +++ b/Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/helpers-source.mustache @@ -0,0 +1,226 @@ +{{>licenseInfo}} +#include "{{modelNamePrefix}}Helpers.h" + +#include "{{unrealModuleName}}Module.h" + +#include "Interfaces/IHttpRequest.h" +#include "PlatformHttp.h" +#include "Misc/FileHelper.h" +#include "Misc/Paths.h" + +{{#cppNamespaceDeclarations}} +namespace {{this}} +{ +{{/cppNamespaceDeclarations}} + +HttpFileInput::HttpFileInput(const TCHAR* InFilePath) +{ + SetFilePath(InFilePath); +} + +HttpFileInput::HttpFileInput(const FString& InFilePath) +{ + SetFilePath(InFilePath); +} + +void HttpFileInput::SetFilePath(const TCHAR* InFilePath) +{ + FilePath = InFilePath; + if(ContentType.IsEmpty()) + { + ContentType = FPlatformHttp::GetMimeType(InFilePath); + } +} + +void HttpFileInput::SetFilePath(const FString& InFilePath) +{ + SetFilePath(*InFilePath); +} + +void HttpFileInput::SetContentType(const TCHAR* InContentType) +{ + ContentType = InContentType; +} + +FString HttpFileInput::GetFilename() const +{ + return FPaths::GetCleanFilename(FilePath); +} + +////////////////////////////////////////////////////////////////////////// + +const TCHAR* HttpMultipartFormData::Delimiter = TEXT("--"); +const TCHAR* HttpMultipartFormData::Newline = TEXT("\r\n"); + +void HttpMultipartFormData::SetBoundary(const TCHAR* InBoundary) +{ + checkf(Boundary.IsEmpty(), TEXT("Boundary must be set before usage")); + Boundary = InBoundary; +} + +const FString& HttpMultipartFormData::GetBoundary() const +{ + if (Boundary.IsEmpty()) + { + // Generate a random boundary with enough entropy, should avoid occurrences of the boundary in the data. + // Since the boundary is generated at every request, in case of failure, retries should succeed. + Boundary = FGuid::NewGuid().ToString(EGuidFormats::Short); + } + + return Boundary; +} + +void HttpMultipartFormData::SetupHttpRequest(const FHttpRequestRef& HttpRequest) +{ + if(HttpRequest->GetVerb() != TEXT("POST")) + { + UE_LOG(Log{{unrealModuleName}}, Error, TEXT("Expected POST verb when using multipart form data")); + } + + // Append final boundary + AppendString(Delimiter); + AppendString(*GetBoundary()); + AppendString(Delimiter); + + HttpRequest->SetHeader("Content-Type", FString::Printf(TEXT("multipart/form-data; boundary=%s"), *GetBoundary())); + HttpRequest->SetContent(FormData); +} + +void HttpMultipartFormData::AddStringPart(const TCHAR* Name, const TCHAR* Data) +{ + // Add boundary + AppendString(Delimiter); + AppendString(*GetBoundary()); + AppendString(Newline); + + // Add header + AppendString(*FString::Printf(TEXT("Content-Disposition: form-data; name = \"%s\""), Name)); + AppendString(Newline); + AppendString(*FString::Printf(TEXT("Content-Type: text/plain; charset=utf-8"))); + AppendString(Newline); + + // Add header to body splitter + AppendString(Newline); + + // Add Data + AppendString(Data); + AppendString(Newline); +} + +void HttpMultipartFormData::AddJsonPart(const TCHAR* Name, const FString& JsonString) +{ + // Add boundary + AppendString(Delimiter); + AppendString(*GetBoundary()); + AppendString(Newline); + + // Add header + AppendString(*FString::Printf(TEXT("Content-Disposition: form-data; name=\"%s\""), Name)); + AppendString(Newline); + AppendString(*FString::Printf(TEXT("Content-Type: application/json; charset=utf-8"))); + AppendString(Newline); + + // Add header to body splitter + AppendString(Newline); + + // Add Data + AppendString(*JsonString); + AppendString(Newline); +} + +void HttpMultipartFormData::AddBinaryPart(const TCHAR* Name, const TArray& ByteArray) +{ + // Add boundary + AppendString(Delimiter); + AppendString(*GetBoundary()); + AppendString(Newline); + + // Add header + AppendString(*FString::Printf(TEXT("Content-Disposition: form-data; name=\"%s\""), Name)); + AppendString(Newline); + AppendString(*FString::Printf(TEXT("Content-Type: application/octet-stream"))); + AppendString(Newline); + + // Add header to body splitter + AppendString(Newline); + + // Add Data + FormData.Append(ByteArray); + AppendString(Newline); +} + +void HttpMultipartFormData::AddFilePart(const TCHAR* Name, const HttpFileInput& File) +{ + TArray FileContents; + if (!FFileHelper::LoadFileToArray(FileContents, *File.GetFilePath())) + { + UE_LOG(Log{{unrealModuleName}}, Error, TEXT("Failed to load file (%s)"), *File.GetFilePath()); + return; + } + + // Add boundary + AppendString(Delimiter); + AppendString(*GetBoundary()); + AppendString(Newline); + + // Add header + AppendString(*FString::Printf(TEXT("Content-Disposition: form-data; name=\"%s\"; filename=\"%s\""), Name, *File.GetFilename())); + AppendString(Newline); + AppendString(*FString::Printf(TEXT("Content-Type: %s"), *File.GetContentType())); + AppendString(Newline); + + // Add header to body splitter + AppendString(Newline); + + // Add Data + FormData.Append(FileContents); + AppendString(Newline); +} + +void HttpMultipartFormData::AppendString(const TCHAR* Str) +{ + FTCHARToUTF8 utf8Str(Str); + FormData.Append((uint8*)utf8Str.Get(), utf8Str.Length()); +} + +////////////////////////////////////////////////////////////////////////// + +bool ParseDateTime(const FString& DateTimeString, FDateTime& OutDateTime) +{ + // Iso8601 Format: DateTime: YYYY-mm-ddTHH:MM:SS(.sss)(Z|+hh:mm|+hhmm|-hh:mm|-hhmm) + { + // We cannot call directly FDateTime::ParseIso8601 because it does not allow for precision beyond the millisecond, but DateTimeString might have more digits + int32 DotIndex; + FString StringToParse = DateTimeString; + if (DateTimeString.FindChar('.', DotIndex)) + { + int32 TimeZoneIndex; + if (DateTimeString.FindChar('Z', TimeZoneIndex) || DateTimeString.FindChar('+', TimeZoneIndex) || DateTimeString.FindChar('-', TimeZoneIndex)) + { + // The string contains a time zone designator starting at TimeZoneIndex + if (TimeZoneIndex > DotIndex + 4) + { + // Trim to millisecond + StringToParse = DateTimeString.Left(DotIndex + 4) + DateTimeString.RightChop(TimeZoneIndex); + } + } + else + { + // the string does not contain a time zone designator, trim it to the millisecond + StringToParse = DateTimeString.Left(DotIndex + 4); + } + } + + if (FDateTime::ParseIso8601(*StringToParse, OutDateTime)) + return true; + } + + if (FDateTime::ParseHttpDate(DateTimeString, OutDateTime)) + return true; + + return FDateTime::Parse(DateTimeString, OutDateTime); +} + +{{#cppNamespaceDeclarations}} +} +{{/cppNamespaceDeclarations}} \ No newline at end of file diff --git a/Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/licenseInfo.mustache b/Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/licenseInfo.mustache new file mode 100644 index 00000000..b0182eea --- /dev/null +++ b/Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/licenseInfo.mustache @@ -0,0 +1,11 @@ +/** + * {{{appName}}} + * {{{appDescription}}} + * + * {{#version}}OpenAPI spec version: {{{.}}}{{/version}} + * {{#infoEmail}}Contact: {{{.}}}{{/infoEmail}} + * + * NOTE: This class is auto generated by OpenAPI Generator + * https://github.com/OpenAPITools/openapi-generator + * Do not edit the class manually. + */ diff --git a/Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/model-base-header.mustache b/Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/model-base-header.mustache new file mode 100644 index 00000000..e4fdbd15 --- /dev/null +++ b/Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/model-base-header.mustache @@ -0,0 +1,100 @@ +{{>licenseInfo}} +#pragma once + +#include "Interfaces/IHttpRequest.h" +#include "Interfaces/IHttpResponse.h" +#include "Serialization/JsonWriter.h" +#include "Dom/JsonObject.h" +#include "HttpRetrySystem.h" +#include "Containers/Ticker.h" +#include "Runtime/Launch/Resources/Version.h" + +{{#cppNamespaceDeclarations}} +namespace {{this}} +{ +{{/cppNamespaceDeclarations}} + +typedef TSharedRef> JsonWriter; +using namespace FHttpRetrySystem; + +struct {{dllapi}} HttpRetryManager + : public FManager +#if ENGINE_MAJOR_VERSION <= 4 + , public FTickerObjectBase +#else + , public FTSTickerObjectBase +#endif +{ + using FManager::FManager; + + bool Tick(float DeltaTime) final; +}; + +struct {{dllapi}} HttpRetryParams +{ + HttpRetryParams( + const FRetryLimitCountSetting& InRetryLimitCountOverride = FRetryLimitCountSetting(), + const FRetryTimeoutRelativeSecondsSetting& InRetryTimeoutRelativeSecondsOverride = FRetryTimeoutRelativeSecondsSetting(), + const FRetryResponseCodes& InRetryResponseCodes = FRetryResponseCodes(), + const FRetryVerbs& InRetryVerbs = FRetryVerbs(), + const FRetryDomainsPtr& InRetryDomains = FRetryDomainsPtr() + ); + + FRetryLimitCountSetting RetryLimitCountOverride; + FRetryTimeoutRelativeSecondsSetting RetryTimeoutRelativeSecondsOverride; + FRetryResponseCodes RetryResponseCodes; + FRetryVerbs RetryVerbs; + FRetryDomainsPtr RetryDomains; +}; + +class {{dllapi}} Model +{ +public: + virtual ~Model() {} + virtual void WriteJson(JsonWriter& Writer) const = 0; + virtual bool FromJson(const TSharedPtr& JsonValue) = 0; +}; + +class {{dllapi}} Request +{ +public: + virtual ~Request() {} + virtual void SetupHttpRequest(const FHttpRequestRef& HttpRequest) const = 0; + virtual FString ComputePath() const = 0; + + /* Enables retry and optionally sets a retry policy for this request */ + void SetShouldRetry(const HttpRetryParams& Params = HttpRetryParams()) { RetryParams = Params; } + const TOptional& GetRetryParams() const { return RetryParams; } + +private: + TOptional RetryParams; +}; + +class {{dllapi}} Response +{ +public: + virtual ~Response() {} + virtual bool FromJson(const TSharedPtr& JsonValue) = 0; + + void SetSuccessful(bool InSuccessful) { Successful = InSuccessful; } + bool IsSuccessful() const { return Successful; } + + virtual void SetHttpResponseCode(EHttpResponseCodes::Type InHttpResponseCode); + EHttpResponseCodes::Type GetHttpResponseCode() const { return ResponseCode; } + + void SetResponseString(const FString& InResponseString) { ResponseString = InResponseString; } + const FString& GetResponseString() const { return ResponseString; } + + void SetHttpResponse(const FHttpResponsePtr& InHttpResponse) { HttpResponse = InHttpResponse; } + const FHttpResponsePtr& GetHttpResponse() const { return HttpResponse; } + +private: + bool Successful; + EHttpResponseCodes::Type ResponseCode; + FString ResponseString; + FHttpResponsePtr HttpResponse; +}; + +{{#cppNamespaceDeclarations}} +} +{{/cppNamespaceDeclarations}} diff --git a/Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/model-base-source.mustache b/Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/model-base-source.mustache new file mode 100644 index 00000000..1a551ca5 --- /dev/null +++ b/Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/model-base-source.mustache @@ -0,0 +1,40 @@ +{{>licenseInfo}} +#include "{{modelNamePrefix}}BaseModel.h" + +{{#cppNamespaceDeclarations}} +namespace {{this}} +{ +{{/cppNamespaceDeclarations}} + +bool HttpRetryManager::Tick(float DeltaTime) +{ + FManager::Update(); + return true; +} + +HttpRetryParams::HttpRetryParams(const FRetryLimitCountSetting& InRetryLimitCountOverride /*= FRetryLimitCountSetting()*/, + const FRetryTimeoutRelativeSecondsSetting& InRetryTimeoutRelativeSecondsOverride /*= FRetryTimeoutRelativeSecondsSetting()*/, + const FRetryResponseCodes& InRetryResponseCodes /*= FRetryResponseCodes()*/, + const FRetryVerbs& InRetryVerbs /*= FRetryVerbs()*/, + const FRetryDomainsPtr& InRetryDomains /*= FRetryDomainsPtr() */) + : RetryLimitCountOverride(InRetryLimitCountOverride) + , RetryTimeoutRelativeSecondsOverride(InRetryTimeoutRelativeSecondsOverride) + , RetryResponseCodes(InRetryResponseCodes) + , RetryVerbs(InRetryVerbs) + , RetryDomains(InRetryDomains) +{ +} + +void Response::SetHttpResponseCode(EHttpResponseCodes::Type InHttpResponseCode) +{ + ResponseCode = InHttpResponseCode; + SetSuccessful(EHttpResponseCodes::IsOk(InHttpResponseCode)); + if(InHttpResponseCode == EHttpResponseCodes::RequestTimeout) + { + SetResponseString(TEXT("Request Timeout")); + } +} + +{{#cppNamespaceDeclarations}} +} +{{/cppNamespaceDeclarations}} \ No newline at end of file diff --git a/Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/model-header.mustache b/Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/model-header.mustache new file mode 100644 index 00000000..ee709e58 --- /dev/null +++ b/Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/model-header.mustache @@ -0,0 +1,74 @@ +{{>licenseInfo}} +#pragma once + +#include "{{modelNamePrefix}}BaseModel.h" +{{#imports}}{{{import}}} +{{/imports}} + +{{#cppNamespaceDeclarations}} +namespace {{this}} +{ +{{/cppNamespaceDeclarations}} +{{#models}} +{{#model}} + +/* + * {{classname}} + * + * {{description}} + */ +class {{dllapi}} {{classname}} : public Model +{ +public: + virtual ~{{classname}}() {} + bool FromJson(const TSharedPtr& JsonValue) final; + void WriteJson(JsonWriter& Writer) const final; + + {{#isString}} + {{#isEnum}} + {{#allowableValues}} + enum class Values + { + {{#enumVars}} + {{name}}, + {{/enumVars}} + }; + + Values Value{{#defaultValue}} = {{{.}}}{{/defaultValue}}; + {{/allowableValues}} + + static FString EnumToString(const Values& EnumValue); + static bool EnumFromString(const FString& EnumAsString, Values& EnumValue); + {{/isEnum}} + {{^isEnum}} + FString Value{{#defaultValue}} = {{{.}}}{{/defaultValue}}; + {{/isEnum}} + {{/isString}} + {{#vars}} + {{#isEnum}} + {{#allowableValues}} + enum class {{{enumName}}} + { + {{#enumVars}} + {{name}}, + {{/enumVars}} + }; + {{/allowableValues}} + + static FString EnumToString(const {{{enumName}}}& EnumValue); + static bool EnumFromString(const FString& EnumAsString, {{{enumName}}}& EnumValue); + {{#description}}/* {{{.}}} */ + {{/description}}{{^required}}TOptional<{{/required}}{{{datatypeWithEnum}}}{{^required}}>{{/required}} {{name}}{{#required}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}{{/required}}; + {{/isEnum}} + {{^isEnum}} + {{#description}}/* {{{.}}} */ + {{/description}}{{^required}}TOptional<{{/required}}{{{datatype}}}{{^required}}>{{/required}} {{name}}{{#required}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}{{/required}}; + {{/isEnum}} + {{/vars}} +}; + +{{/model}} +{{/models}} +{{#cppNamespaceDeclarations}} +} +{{/cppNamespaceDeclarations}} diff --git a/Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/model-source.mustache b/Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/model-source.mustache new file mode 100644 index 00000000..7ab8ea53 --- /dev/null +++ b/Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/model-source.mustache @@ -0,0 +1,185 @@ +{{>licenseInfo}} +#include "{{classname}}.h" + +#include "{{unrealModuleName}}Module.h" +#include "{{modelNamePrefix}}Helpers.h" + +#include "Templates/SharedPointer.h" + +{{#cppNamespaceDeclarations}} +namespace {{this}} +{ +{{/cppNamespaceDeclarations}} +{{#models}}{{#model}} +{{#isEnum}} +inline FString ToString(const {{classname}}::Values& Value) +{ + {{#allowableValues}} + switch (Value) + { + {{#enumVars}} + case {{classname}}::Values::{{name}}: + return TEXT("{{{value}}}"); + {{/enumVars}} + } + {{/allowableValues}} + + UE_LOG(Log{{unrealModuleName}}, Error, TEXT("Invalid {{classname}}::Values Value (%d)"), (int)Value); + return TEXT(""); +} + +FString {{classname}}::EnumToString(const {{classname}}::Values& EnumValue) +{ + return ToString(EnumValue); +} + +inline bool FromString(const FString& EnumAsString, {{classname}}::Values& Value) +{ + static TMap StringToEnum = { {{#allowableValues}}{{#enumVars}} + { TEXT("{{{value}}}"), {{classname}}::Values::{{name}} },{{/enumVars}}{{/allowableValues}} }; + + const auto Found = StringToEnum.Find(EnumAsString); + if(Found) + Value = *Found; + + return Found != nullptr; +} + +bool {{classname}}::EnumFromString(const FString& EnumAsString, {{classname}}::Values& EnumValue) +{ + return FromString(EnumAsString, EnumValue); +} + +inline void WriteJsonValue(JsonWriter& Writer, const {{classname}}::Values& Value) +{ + WriteJsonValue(Writer, ToString(Value)); +} + +inline bool TryGetJsonValue(const TSharedPtr& JsonValue, {{classname}}::Values& Value) +{ + {{#allowableValues}} + FString TmpValue; + if (JsonValue->TryGetString(TmpValue)) + { + if(FromString(TmpValue, Value)) + return true; + } + {{/allowableValues}} + return false; +} + +{{/isEnum}} +{{#hasEnums}} +{{#vars}} +{{#isEnum}} +inline FString ToString(const {{classname}}::{{{enumName}}}& Value) +{ + {{#allowableValues}} + switch (Value) + { + {{#enumVars}} + case {{classname}}::{{{enumName}}}::{{name}}: + return TEXT("{{{value}}}"); + {{/enumVars}} + } + {{/allowableValues}} + + UE_LOG(Log{{unrealModuleName}}, Error, TEXT("Invalid {{classname}}::{{{enumName}}} Value (%d)"), (int)Value); + return TEXT(""); +} + +FString {{classname}}::EnumToString(const {{classname}}::{{{enumName}}}& EnumValue) +{ + return ToString(EnumValue); +} + +inline bool FromString(const FString& EnumAsString, {{classname}}::{{{enumName}}}& Value) +{ + static TMap StringToEnum = { {{#allowableValues}}{{#enumVars}} + { TEXT("{{{value}}}"), {{classname}}::{{{enumName}}}::{{name}} },{{/enumVars}}{{/allowableValues}} }; + + const auto Found = StringToEnum.Find(EnumAsString); + if(Found) + Value = *Found; + + return Found != nullptr; +} + +bool {{classname}}::EnumFromString(const FString& EnumAsString, {{classname}}::{{{enumName}}}& EnumValue) +{ + return FromString(EnumAsString, EnumValue); +} + +inline void WriteJsonValue(JsonWriter& Writer, const {{classname}}::{{{enumName}}}& Value) +{ + WriteJsonValue(Writer, ToString(Value)); +} + +inline bool TryGetJsonValue(const TSharedPtr& JsonValue, {{classname}}::{{{enumName}}}& Value) +{ + {{#allowableValues}} + FString TmpValue; + if (JsonValue->TryGetString(TmpValue)) + { + if(FromString(TmpValue, Value)) + return true; + } + {{/allowableValues}} + return false; +} + +{{/isEnum}} +{{/vars}} +{{/hasEnums}} +void {{classname}}::WriteJson(JsonWriter& Writer) const +{ + {{#isString}} + WriteJsonValue(Writer, Value); + {{/isString}} + {{^isString}} + Writer->WriteObjectStart(); + {{#vars}} + {{#required}} + Writer->WriteIdentifierPrefix(TEXT("{{baseName}}")); WriteJsonValue(Writer, {{name}}); + {{/required}} + {{^required}} + if ({{name}}.IsSet()) + { + Writer->WriteIdentifierPrefix(TEXT("{{baseName}}")); WriteJsonValue(Writer, {{name}}.GetValue()); + } + {{/required}} + {{/vars}} + Writer->WriteObjectEnd(); + {{/isString}} +} + +bool {{classname}}::FromJson(const TSharedPtr& JsonValue) +{ + {{#isString}} + return TryGetJsonValue(JsonValue, Value); + {{/isString}} + {{^isString}} + const TSharedPtr* Object; + if (!JsonValue->TryGetObject(Object)) + return false; + + bool ParseSuccess = true; + + {{#vars}} + {{#required}} + {{^isWriteOnly}}ParseSuccess &= {{/isWriteOnly}}TryGetJsonValue(*Object, TEXT("{{baseName}}"), {{name}}); + {{/required}} + {{^required}} + ParseSuccess &= TryGetJsonValue(*Object, TEXT("{{baseName}}"), {{name}}); + {{/required}} + {{/vars}} + + return ParseSuccess; + {{/isString}} +} + +{{/model}} +{{/models}} +{{#cppNamespaceDeclarations}} +} +{{/cppNamespaceDeclarations}} diff --git a/Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/module-header.mustache b/Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/module-header.mustache new file mode 100644 index 00000000..f27de891 --- /dev/null +++ b/Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/module-header.mustache @@ -0,0 +1,15 @@ +{{>licenseInfo}} +#pragma once + +#include "Modules/ModuleInterface.h" +#include "Modules/ModuleManager.h" +#include "Logging/LogMacros.h" + +DECLARE_LOG_CATEGORY_EXTERN(Log{{unrealModuleName}}, Log, All); + +class {{dllapi}} {{unrealModuleName}}Module : public IModuleInterface +{ +public: + void StartupModule() final; + void ShutdownModule() final; +}; diff --git a/Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/module-source.mustache b/Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/module-source.mustache new file mode 100644 index 00000000..e89bb28e --- /dev/null +++ b/Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/module-source.mustache @@ -0,0 +1,13 @@ +{{>licenseInfo}} +#include "{{unrealModuleName}}Module.h" + +IMPLEMENT_MODULE({{unrealModuleName}}Module, {{unrealModuleName}}); +DEFINE_LOG_CATEGORY(Log{{unrealModuleName}}); + +void {{unrealModuleName}}Module::StartupModule() +{ +} + +void {{unrealModuleName}}Module::ShutdownModule() +{ +} From c3ae5e58782ad0168f2479f8f1c3781ac316e38a Mon Sep 17 00:00:00 2001 From: ImmutableJeffrey Date: Mon, 16 Dec 2024 04:50:47 +1000 Subject: [PATCH 19/25] fix: openapi generator unique items conflicting with array (#3423) --- .../template/cpp-ue4/helpers-header.mustache | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/helpers-header.mustache b/Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/helpers-header.mustache index d551cd89..6a903626 100644 --- a/Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/helpers-header.mustache +++ b/Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/helpers-header.mustache @@ -286,6 +286,17 @@ inline void WriteJsonValue(JsonWriter& Writer, const TArray& Value) Writer->WriteArrayEnd(); } +template +inline void WriteJsonValue(JsonWriter& Writer, const TSet& Value) +{ + Writer->WriteArrayStart(); + for (const auto& Element : Value) + { + WriteJsonValue(Writer, Element); + } + Writer->WriteArrayEnd(); +} + template inline void WriteJsonValue(JsonWriter& Writer, const TMap& Value) { @@ -415,6 +426,26 @@ inline bool TryGetJsonValue(const TSharedPtr& JsonValue, TArray& return false; } +template +inline bool TryGetJsonValue(const TSharedPtr& JsonValue, TSet& SetValue) +{ + const TArray>* JsonArray; + if (JsonValue->TryGetArray(JsonArray)) + { + bool ParseSuccess = true; + const int32 Count = JsonArray->Num(); + SetValue.Reset(); + for (int i = 0; i < Count; i++) + { + T TmpValue; + ParseSuccess &= TryGetJsonValue((*JsonArray)[i], TmpValue); + SetValue.Emplace(MoveTemp(TmpValue)); + } + return ParseSuccess; + } + return false; +} + template inline bool TryGetJsonValue(const TSharedPtr& JsonValue, TMap& MapValue) { From 97e3c84d4f3f2690cd7ddf75987699078519bb5e Mon Sep 17 00:00:00 2001 From: ImmutableJeffrey Date: Mon, 16 Dec 2024 04:51:29 +1000 Subject: [PATCH 20/25] chore: update openapi.json (#3423) --- Source/ImmutablezkEVMAPI/openapi.json | 135 ++++++++++++++++++++++---- 1 file changed, 117 insertions(+), 18 deletions(-) diff --git a/Source/ImmutablezkEVMAPI/openapi.json b/Source/ImmutablezkEVMAPI/openapi.json index e19e581d..420e44ed 100644 --- a/Source/ImmutablezkEVMAPI/openapi.json +++ b/Source/ImmutablezkEVMAPI/openapi.json @@ -3488,6 +3488,13 @@ }, "/passport-profile/v2/linked-wallets": { "post": { + "security": [ + { + "BearerAuth": [ + "openid" + ] + } + ], "tags": [ "passport profile" ], @@ -3554,14 +3561,7 @@ } } } - }, - "security": [ - { - "BearerAuth": [ - "openid" - ] - } - ] + } } }, "/passport-profile/v1/user/info": { @@ -3625,7 +3625,6 @@ }, "/v1/chains/{chain_name}/search/filters/{contract_address}": { "get": { - "x-hide-from-sdk": true, "tags": [ "metadata-search" ], @@ -3694,7 +3693,6 @@ }, "/v1/chains/{chain_name}/search/nfts": { "get": { - "x-hide-from-sdk": true, "tags": [ "metadata-search" ], @@ -3823,7 +3821,6 @@ }, "/v1/chains/{chain_name}/search/stacks": { "get": { - "x-hide-from-sdk": true, "tags": [ "metadata-search" ], @@ -3912,6 +3909,16 @@ "example": "sword" } }, + { + "name": "payment_token", + "in": "query", + "required": false, + "description": "Filters the active listings, bids, floor listing and top bid by the specified payment token, either the address of the payment token contract or 'NATIVE'", + "schema": { + "type": "string", + "example": "NATIVE" + } + }, { "name": "sort_by", "in": "query", @@ -3978,7 +3985,6 @@ }, "/v1/chains/{chain_name}/stacks": { "get": { - "x-hide-from-sdk": true, "tags": [ "metadata" ], @@ -4056,7 +4062,6 @@ }, "/v1/chains/{chain_name}/quotes/{contract_address}/stacks": { "get": { - "x-hide-from-sdk": true, "tags": [ "pricing" ], @@ -4105,6 +4110,16 @@ "maxItems": 20 } }, + { + "name": "payment_token", + "in": "query", + "required": false, + "description": "Filters the active listings, bids, floor listing and top bid by the specified payment token, either the address of the payment token contract or 'NATIVE'.", + "schema": { + "type": "string", + "example": "NATIVE" + } + }, { "name": "page_cursor", "in": "query", @@ -4149,7 +4164,6 @@ }, "/v1/chains/{chain_name}/quotes/{contract_address}/nfts": { "get": { - "x-hide-from-sdk": true, "tags": [ "pricing" ], @@ -4197,6 +4211,16 @@ "maxItems": 20 } }, + { + "name": "payment_token", + "in": "query", + "required": false, + "description": "Filters the active listings, bids, floor listing and top bid by the specified payment token, either the address of the payment token contract or 'NATIVE'.", + "schema": { + "type": "string", + "example": "NATIVE" + } + }, { "name": "page_cursor", "in": "query", @@ -4252,6 +4276,11 @@ "type": "apiKey", "in": "header", "name": "x-immutable-api-key" + }, + "BearerAuthWithClient": { + "type": "http", + "scheme": "bearer", + "bearerFormat": "JWT" } }, "parameters": { @@ -9577,6 +9606,48 @@ "amount" ] }, + "Bid": { + "type": "object", + "properties": { + "bid_id": { + "type": "string", + "description": "Global Order identifier", + "example": "018792C9-4AD7-8EC4-4038-9E05C598534A" + }, + "price_details": { + "$ref": "#/components/schemas/MarketPriceDetails" + }, + "token_id": { + "type": "string", + "nullable": true, + "description": "Token ID. Null for collection bids that can be fulfilled by any asset in the collection", + "example": "1" + }, + "contract_address": { + "type": "string", + "description": "ETH Address of collection that the asset belongs to", + "example": "0xe9b00a87700f660e46b6f5deaa1232836bcc07d3" + }, + "creator": { + "type": "string", + "description": "ETH Address of listing creator", + "example": "0xe9b00a87700f660e46b6f5deaa1232836bcc07d3" + }, + "amount": { + "type": "string", + "description": "Amount of token included in the listing", + "example": "1" + } + }, + "required": [ + "bid_id", + "price_details", + "creator", + "token_id", + "contract_address", + "amount" + ] + }, "LastTrade": { "type": "object", "nullable": true, @@ -9639,11 +9710,21 @@ } ] }, + "top_bid": { + "description": "Highest active big", + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/Bid" + } + ] + }, "last_trade": { "$ref": "#/components/schemas/LastTrade" } }, "required": [ + "top_bid", "floor_listing", "last_trade" ] @@ -9665,17 +9746,26 @@ }, "listings": { "type": "array", - "description": "List of open listings for the stack.", + "description": "List of open listings for the NFT.", "maxItems": 10, "items": { "$ref": "#/components/schemas/Listing" } + }, + "bids": { + "type": "array", + "description": "List of open bids for the NFT.", + "maxItems": 10, + "items": { + "$ref": "#/components/schemas/Bid" + } } }, "required": [ "nft_with_stack", "market", - "listings" + "listings", + "bids" ] }, "SearchNFTsResult": { @@ -9817,13 +9907,22 @@ "items": { "$ref": "#/components/schemas/Listing" } + }, + "bids": { + "type": "array", + "description": "List of open bids for the stack.", + "maxItems": 10, + "items": { + "$ref": "#/components/schemas/Bid" + } } }, "required": [ "stack", "stack_count", "market", - "listings" + "listings", + "bids" ] }, "SearchStacksResult": { @@ -9954,4 +10053,4 @@ } } } -} +} \ No newline at end of file From 22b36438afddb184ec0298340715a3ec11a8f8ec Mon Sep 17 00:00:00 2001 From: ImmutableJeffrey Date: Mon, 16 Dec 2024 12:41:32 +1000 Subject: [PATCH 21/25] refactor: to generate openapi with batch files (#3423) --- Source/ImmutablezkEVMAPI/README.md | 7 +- .../batch-files/generate.bat | 13 + .../openapi-generator/batch-files/generate.sh | 11 + Source/ImmutablezkEVMAPI/openapi.json | 10056 ---------------- 4 files changed, 25 insertions(+), 10062 deletions(-) create mode 100644 Source/ImmutablezkEVMAPI/openapi-generator/batch-files/generate.bat create mode 100644 Source/ImmutablezkEVMAPI/openapi-generator/batch-files/generate.sh delete mode 100644 Source/ImmutablezkEVMAPI/openapi.json diff --git a/Source/ImmutablezkEVMAPI/README.md b/Source/ImmutablezkEVMAPI/README.md index 4bdaf2b8..dc823767 100644 --- a/Source/ImmutablezkEVMAPI/README.md +++ b/Source/ImmutablezkEVMAPI/README.md @@ -1,8 +1,3 @@ # Usage -TO generate OpenAPI locally: - - ``` - openapi-generator-cli generate -i openapi.json -g cpp-ue4 -o . --additional-properties=modelNamePrefix="API",cppNamespace=ImmutablezkEVMAPI,unrealModuleName=ImmutablezkEVMAPI - - ``` \ No newline at end of file +To generate OpenAPI locally, use the corresponding batch files in `openapi-generator/batch-files` \ No newline at end of file diff --git a/Source/ImmutablezkEVMAPI/openapi-generator/batch-files/generate.bat b/Source/ImmutablezkEVMAPI/openapi-generator/batch-files/generate.bat new file mode 100644 index 00000000..ae9e07b4 --- /dev/null +++ b/Source/ImmutablezkEVMAPI/openapi-generator/batch-files/generate.bat @@ -0,0 +1,13 @@ +@echo off + +set OPENAPI_GENERATOR_CLI=openapi-generator-cli + +set GENERATOR=-g cpp-ue4 +set TEMPLATE=-t ../template/cpp-ue4 +set INPUT=-i https://imx-openapiv3-mr-sandbox.s3.us-east-2.amazonaws.com/openapi.json +set OUTPUT=-o ../.. +set ADDITIONAL_PROPERTIES=--additional-properties=modelNamePrefix="API",cppNamespace=ImmutablezkEVMAPI,unrealModuleName=ImmutablezkEVMAPI + +%OPENAPI_GENERATOR_CLI% generate %GENERATOR% %TEMPLATE% %INPUT% %OUTPUT% %ADDITIONAL_PROPERTIES% + +pause \ No newline at end of file diff --git a/Source/ImmutablezkEVMAPI/openapi-generator/batch-files/generate.sh b/Source/ImmutablezkEVMAPI/openapi-generator/batch-files/generate.sh new file mode 100644 index 00000000..a6aeef91 --- /dev/null +++ b/Source/ImmutablezkEVMAPI/openapi-generator/batch-files/generate.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +OPENAPI_GENERATOR_CLI=openapi-generator-cli + +GENERATOR="-g cpp-ue4" +TEMPLATE="-t ../template/cpp-ue4" +INPUT="-i https://imx-openapiv3-mr-sandbox.s3.us-east-2.amazonaws.com/openapi.json" +OUTPUT="-o ../.." +ADDITIONAL_PROPERTIES="--additional-properties=modelNamePrefix=API,cppNamespace=ImmutablezkEVMAPI,unrealModuleName=ImmutablezkEVMAPI" + +$OPENAPI_GENERATOR_CLI generate $GENERATOR $TEMPLATE $INPUT $OUTPUT $ADDITIONAL_PROPERTIES \ No newline at end of file diff --git a/Source/ImmutablezkEVMAPI/openapi.json b/Source/ImmutablezkEVMAPI/openapi.json deleted file mode 100644 index 420e44ed..00000000 --- a/Source/ImmutablezkEVMAPI/openapi.json +++ /dev/null @@ -1,10056 +0,0 @@ -{ - "openapi": "3.0.3", - "info": { - "title": "Immutable zkEVM API", - "version": "1.0.0", - "description": "Immutable Multi Rollup API", - "contact": { - "name": "Immutable API Support", - "email": "support@immutable.com", - "url": "https://support.immutable.com" - } - }, - "servers": [ - { - "url": "https://api.sandbox.immutable.com" - } - ], - "tags": [ - { - "name": "activities", - "description": "Activities Endpoints", - "x-displayName": "activities" - }, - { - "name": "chains", - "description": "Chains Endpoints", - "x-displayName": "chains" - }, - { - "name": "collections", - "description": "Collections Endpoints", - "x-displayName": "collections" - }, - { - "name": "nfts", - "description": "NFTs Endpoints", - "x-displayName": "nfts" - }, - { - "name": "nft owners", - "description": "NFT Owner Endpoints", - "x-displayName": "nft owners" - }, - { - "name": "metadata", - "description": "NFT Metadata Endpoints", - "x-displayName": "metadata" - }, - { - "name": "tokens", - "description": "ERC20 Token Endpoints", - "x-displayName": "tokens" - }, - { - "name": "demopage", - "description": "Temporary HTML endpoint for demo purposes", - "x-displayName": "demopage" - }, - { - "name": "verification", - "x-displayName": "verification" - }, - { - "name": "operatorallowlist", - "x-displayName": "operatorallowlist" - }, - { - "name": "crafting", - "x-displayName": "crafting" - }, - { - "name": "listings", - "description": "Listings Endpoints", - "x-displayName": "listings" - }, - { - "name": "orders", - "x-displayName": "orders" - }, - { - "name": "passport", - "description": "Passport operations", - "x-displayName": "passport" - }, - { - "name": "passport profile", - "description": "Passport Profile endpoints", - "x-displayName": "passport profile" - }, - { - "name": "guardian", - "description": "Guardian endpoints", - "x-displayName": "guardian" - }, - { - "name": "pricing", - "description": "Pricing Endpoints", - "x-displayName": "pricing" - }, - { - "name": "metadata-search", - "x-displayName": "metadata-search" - } - ], - "paths": { - "/v1/chains/{chain_name}/activity-history": { - "get": { - "tags": [ - "activities" - ], - "operationId": "ListActivityHistory", - "summary": "List history of activities", - "description": "List activities sorted by updated_at timestamp ascending, useful for time based data replication", - "parameters": [ - { - "name": "chain_name", - "description": "The name of chain", - "schema": { - "$ref": "#/components/schemas/ChainName" - }, - "in": "path", - "required": true, - "examples": { - "testnet": { - "value": "imtbl-zkevm-testnet", - "summary": "Immutable zkEVM Public Testnet" - } - } - }, - { - "name": "from_updated_at", - "in": "query", - "description": "From indexed at including given date", - "required": true, - "schema": { - "type": "string", - "example": "2022-08-16T17:43:26.991388Z", - "format": "date-time" - } - }, - { - "name": "to_updated_at", - "in": "query", - "required": false, - "description": "To indexed at including given date", - "schema": { - "type": "string", - "example": "2022-08-16T17:43:26.991388Z", - "format": "date-time" - } - }, - { - "name": "contract_address", - "in": "query", - "description": "The contract address of the collection", - "required": false, - "schema": { - "type": "string" - }, - "example": "0x8a90cab2b38dba80c64b7734e58ee1db38b8992e" - }, - { - "name": "activity_type", - "in": "query", - "description": "The activity type", - "required": false, - "example": "burn", - "schema": { - "$ref": "#/components/schemas/ActivityType" - } - }, - { - "name": "page_cursor", - "in": "query", - "description": "Encoded page cursor to retrieve previous or next page. Use the value returned in the response.", - "required": false, - "schema": { - "$ref": "#/components/schemas/PageCursor" - } - }, - { - "name": "page_size", - "description": "Maximum number of items to return", - "in": "query", - "required": false, - "schema": { - "$ref": "#/components/schemas/PageSize" - } - } - ], - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ListActivitiesResult" - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "500": { - "$ref": "#/components/responses/InternalServerError" - } - } - } - }, - "/v1/chains/{chain_name}/activities": { - "get": { - "tags": [ - "activities" - ], - "operationId": "ListActivities", - "summary": "List all activities", - "description": "List all activities", - "parameters": [ - { - "name": "chain_name", - "description": "The name of chain", - "schema": { - "$ref": "#/components/schemas/ChainName" - }, - "in": "path", - "required": true, - "examples": { - "testnet": { - "value": "imtbl-zkevm-testnet", - "summary": "Immutable zkEVM Public Testnet" - } - } - }, - { - "name": "contract_address", - "in": "query", - "description": "The contract address of NFT or ERC20 Token", - "required": false, - "schema": { - "type": "string" - }, - "example": "0x8a90cab2b38dba80c64b7734e58ee1db38b8992e" - }, - { - "name": "token_id", - "in": "query", - "description": "An `uint256` token id as string", - "required": false, - "schema": { - "type": "string" - }, - "example": "1" - }, - { - "name": "account_address", - "in": "query", - "required": false, - "schema": { - "type": "string" - }, - "example": "0xe9b00a87700f660e46b6f5deaa1232836bcc07d3", - "description": "The account address activity contains" - }, - { - "name": "activity_type", - "in": "query", - "description": "The activity type", - "required": false, - "example": "burn", - "schema": { - "$ref": "#/components/schemas/ActivityType" - } - }, - { - "name": "transaction_hash", - "in": "query", - "description": "The transaction hash of activity", - "required": false, - "schema": { - "type": "string" - }, - "example": "0x68d9eac5e3b3c3580404989a4030c948a78e1b07b2b5ea5688d8c38a6c61c93e" - }, - { - "name": "page_cursor", - "in": "query", - "description": "Encoded page cursor to retrieve previous or next page. Use the value returned in the response.", - "required": false, - "schema": { - "$ref": "#/components/schemas/PageCursor" - } - }, - { - "name": "page_size", - "description": "Maximum number of items to return", - "in": "query", - "required": false, - "schema": { - "$ref": "#/components/schemas/PageSize" - } - } - ], - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ListActivitiesResult" - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "500": { - "$ref": "#/components/responses/InternalServerError" - } - } - } - }, - "/v1/chains/{chain_name}/activities/{activity_id}": { - "get": { - "tags": [ - "activities" - ], - "operationId": "GetActivity", - "summary": "Get a single activity by ID", - "description": "Get a single activity by ID", - "parameters": [ - { - "name": "chain_name", - "description": "The name of chain", - "schema": { - "$ref": "#/components/schemas/ChainName" - }, - "in": "path", - "required": true, - "examples": { - "testnet": { - "value": "imtbl-zkevm-testnet", - "summary": "Immutable zkEVM Public Testnet" - } - } - }, - { - "name": "activity_id", - "description": "The id of activity", - "schema": { - "$ref": "#/components/schemas/ActivityID" - }, - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/GetActivityResult" - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "500": { - "$ref": "#/components/responses/InternalServerError" - } - } - } - }, - "/v1/chains/{chain_name}/collections": { - "get": { - "description": "List all collections", - "tags": [ - "collections" - ], - "operationId": "ListCollections", - "summary": "List all collections", - "parameters": [ - { - "name": "chain_name", - "in": "path", - "description": "The name of chain", - "required": true, - "schema": { - "$ref": "#/components/schemas/ChainName" - }, - "examples": { - "testnet": { - "value": "imtbl-zkevm-testnet", - "summary": "Immutable zkEVM Public Testnet" - } - } - }, - { - "name": "contract_address", - "in": "query", - "required": false, - "description": "List of contract addresses to filter by", - "schema": { - "type": "array", - "items": { - "type": "string", - "example": "0xe9b00a87700f660e46b6f5deaa1232836bcc07d3" - } - } - }, - { - "name": "verification_status", - "in": "query", - "required": false, - "description": "List of verification status to filter by", - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/AssetVerificationStatus" - } - } - }, - { - "name": "from_updated_at", - "in": "query", - "description": "Datetime to use as the oldest updated timestamp", - "required": false, - "schema": { - "type": "string", - "example": "2022-08-16T17:43:26.991388Z", - "format": "date-time" - } - }, - { - "name": "page_cursor", - "in": "query", - "description": "Encoded page cursor to retrieve previous or next page. Use the value returned in the response.", - "required": false, - "schema": { - "$ref": "#/components/schemas/PageCursor" - } - }, - { - "name": "page_size", - "description": "Maximum number of items to return", - "in": "query", - "required": false, - "schema": { - "$ref": "#/components/schemas/PageSize" - } - } - ], - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ListCollectionsResult" - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "500": { - "$ref": "#/components/responses/InternalServerError" - } - } - } - }, - "/v1/chains/{chain_name}/collections/{contract_address}": { - "get": { - "description": "Get collection by contract address", - "tags": [ - "collections" - ], - "operationId": "GetCollection", - "summary": "Get collection by contract address", - "parameters": [ - { - "name": "contract_address", - "in": "path", - "description": "The address contract", - "required": true, - "schema": { - "type": "string" - }, - "example": "0x8a90cab2b38dba80c64b7734e58ee1db38b8992e" - }, - { - "name": "chain_name", - "description": "The name of chain", - "in": "path", - "required": true, - "schema": { - "$ref": "#/components/schemas/ChainName" - }, - "examples": { - "testnet": { - "value": "imtbl-zkevm-testnet", - "summary": "Immutable zkEVM Public Testnet" - } - } - } - ], - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/GetCollectionResult" - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "500": { - "$ref": "#/components/responses/InternalServerError" - } - } - } - }, - "/v1/chains/{chain_name}/collections/{contract_address}/refresh-metadata": { - "post": { - "description": "Refresh collection metadata", - "summary": "Refresh collection metadata", - "tags": [ - "collections" - ], - "operationId": "RefreshCollectionMetadata", - "security": [ - { - "ImmutableApiKey": [ - "refresh:metadata" - ] - }, - { - "BearerAuth": [ - "refresh:metadata" - ] - } - ], - "parameters": [ - { - "name": "contract_address", - "in": "path", - "description": "The address contract", - "required": true, - "schema": { - "type": "string" - }, - "example": "0x8a90cab2b38dba80c64b7734e58ee1db38b8992e" - }, - { - "name": "chain_name", - "description": "The name of chain", - "in": "path", - "required": true, - "schema": { - "$ref": "#/components/schemas/ChainName" - }, - "examples": { - "testnet": { - "value": "imtbl-zkevm-testnet", - "summary": "Immutable zkEVM Public Testnet" - } - } - } - ], - "requestBody": { - "description": "The request body", - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RefreshCollectionMetadataRequest" - } - } - } - }, - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RefreshCollectionMetadataResult" - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "401": { - "$ref": "#/components/responses/UnauthorisedRequest" - }, - "403": { - "$ref": "#/components/responses/ForbiddenRequest" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "500": { - "$ref": "#/components/responses/InternalServerError" - } - } - } - }, - "/v1/chains/{chain_name}/accounts/{account_address}/collections": { - "get": { - "description": "List collections by NFT owner account address", - "tags": [ - "collections" - ], - "operationId": "ListCollectionsByNFTOwner", - "summary": "List collections by NFT owner", - "parameters": [ - { - "name": "account_address", - "in": "path", - "description": "Account address", - "required": true, - "schema": { - "type": "string" - }, - "example": "0xe9b00a87700f660e46b6f5deaa1232836bcc07d3" - }, - { - "name": "chain_name", - "description": "The name of chain", - "in": "path", - "required": true, - "schema": { - "$ref": "#/components/schemas/ChainName" - }, - "examples": { - "testnet": { - "value": "imtbl-zkevm-testnet", - "summary": "Immutable zkEVM Public Testnet" - } - } - }, - { - "name": "page_cursor", - "in": "query", - "description": "Encoded page cursor to retrieve previous or next page. Use the value returned in the response.", - "required": false, - "schema": { - "$ref": "#/components/schemas/PageCursor" - } - }, - { - "name": "page_size", - "description": "Maximum number of items to return", - "in": "query", - "required": false, - "schema": { - "$ref": "#/components/schemas/PageSize" - } - } - ], - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ListCollectionsResult" - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "500": { - "$ref": "#/components/responses/InternalServerError" - } - } - } - }, - "/v1/chains/{chain_name}/collections/{contract_address}/nfts/{token_id}": { - "get": { - "description": "Get NFT by token ID", - "tags": [ - "nfts" - ], - "summary": "Get NFT by token ID", - "operationId": "GetNFT", - "parameters": [ - { - "name": "contract_address", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "The address of NFT contract", - "example": "0xe9b00a87700f660e46b6f5deaa1232836bcc07d3" - }, - { - "name": "token_id", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "An `uint256` token id as string", - "example": "1" - }, - { - "name": "chain_name", - "description": "The name of chain", - "in": "path", - "required": true, - "schema": { - "$ref": "#/components/schemas/ChainName" - }, - "examples": { - "testnet": { - "value": "imtbl-zkevm-testnet", - "summary": "Immutable zkEVM Public Testnet" - } - } - } - ], - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/GetNFTResult" - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "500": { - "$ref": "#/components/responses/InternalServerError" - } - } - } - }, - "/v1/chains/{chain_name}/collections/{contract_address}/metadata/{metadata_id}": { - "get": { - "description": "Get metadata by ID", - "tags": [ - "metadata" - ], - "summary": "Get metadata by ID", - "operationId": "GetMetadata", - "parameters": [ - { - "name": "chain_name", - "description": "The name of chain", - "in": "path", - "required": true, - "schema": { - "$ref": "#/components/schemas/ChainName" - }, - "examples": { - "testnet": { - "value": "imtbl-zkevm-testnet", - "summary": "Immutable zkEVM Public Testnet" - } - } - }, - { - "name": "contract_address", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "The address of metadata contract", - "example": "0xe9b00a87700f660e46b6f5deaa1232836bcc07d3" - }, - { - "name": "metadata_id", - "in": "path", - "required": true, - "schema": { - "$ref": "#/components/schemas/MetadataID" - }, - "description": "The id of the metadata" - } - ], - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/GetMetadataResult" - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "500": { - "$ref": "#/components/responses/InternalServerError" - } - } - } - }, - "/v1/chains/{chain_name}/collections/{contract_address}/metadata": { - "get": { - "description": "Get a list of metadata from the given contract", - "tags": [ - "metadata" - ], - "summary": "Get a list of metadata from the given contract", - "operationId": "ListMetadata", - "parameters": [ - { - "name": "chain_name", - "description": "The name of chain", - "in": "path", - "required": true, - "schema": { - "$ref": "#/components/schemas/ChainName" - }, - "examples": { - "testnet": { - "value": "imtbl-zkevm-testnet", - "summary": "Immutable zkEVM Public Testnet" - } - } - }, - { - "name": "contract_address", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "The address of metadata contract", - "example": "0xe9b00a87700f660e46b6f5deaa1232836bcc07d3" - }, - { - "name": "from_updated_at", - "in": "query", - "description": "Datetime to use as the oldest updated timestamp", - "required": false, - "schema": { - "type": "string", - "example": "2022-08-16T17:43:26.991388Z", - "format": "date-time" - } - }, - { - "name": "page_cursor", - "in": "query", - "description": "Encoded page cursor to retrieve previous or next page. Use the value returned in the response.", - "required": false, - "schema": { - "$ref": "#/components/schemas/PageCursor" - } - }, - { - "name": "page_size", - "description": "Maximum number of items to return", - "in": "query", - "required": false, - "schema": { - "$ref": "#/components/schemas/PageSize" - } - } - ], - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ListMetadataResult" - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "500": { - "$ref": "#/components/responses/InternalServerError" - } - } - } - }, - "/v1/chains/{chain_name}/metadata": { - "get": { - "description": "Get a list of metadata from the given chain", - "tags": [ - "metadata" - ], - "summary": "Get a list of metadata from the given chain", - "operationId": "ListMetadataForChain", - "parameters": [ - { - "name": "chain_name", - "description": "The name of chain", - "in": "path", - "required": true, - "schema": { - "$ref": "#/components/schemas/ChainName" - }, - "examples": { - "testnet": { - "value": "imtbl-zkevm-testnet", - "summary": "Immutable zkEVM Public Testnet" - } - } - }, - { - "name": "from_updated_at", - "in": "query", - "description": "Datetime to use as the oldest updated timestamp", - "required": false, - "schema": { - "type": "string", - "example": "2022-08-16T17:43:26.991388Z", - "format": "date-time" - } - }, - { - "name": "page_cursor", - "in": "query", - "description": "Encoded page cursor to retrieve previous or next page. Use the value returned in the response.", - "required": false, - "schema": { - "$ref": "#/components/schemas/PageCursor" - } - }, - { - "name": "page_size", - "description": "Maximum number of items to return", - "in": "query", - "required": false, - "schema": { - "$ref": "#/components/schemas/PageSize" - } - } - ], - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ListMetadataResult" - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "500": { - "$ref": "#/components/responses/InternalServerError" - } - } - } - }, - "/v1/chains/{chain_name}/collections/{contract_address}/metadata/refresh-metadata": { - "post": { - "x-go-name": "RefreshMetadataByID", - "operationId": "RefreshMetadataByID", - "description": "Refresh stacked metadata", - "summary": "Refresh stacked metadata", - "tags": [ - "metadata" - ], - "parameters": [ - { - "$ref": "#/components/parameters/ChainName" - }, - { - "$ref": "#/components/parameters/ContractAddress" - } - ], - "security": [ - { - "ImmutableApiKey": [ - "refresh:metadata" - ] - } - ], - "requestBody": { - "description": "NFT Metadata Refresh Request", - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RefreshMetadataByIDRequest" - } - } - } - }, - "responses": { - "202": { - "description": "Accepted", - "headers": { - "imx-refreshes-limit": { - "$ref": "#/components/headers/MetadataRefreshLimit" - }, - "imx-refresh-limit-reset": { - "$ref": "#/components/headers/MetadataRefreshLimitReset" - }, - "imx-remaining-refreshes": { - "$ref": "#/components/headers/MetadataRefreshLimitRemaining" - }, - "retry-after": { - "$ref": "#/components/headers/MetadataRefreshRetryAfter" - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/MetadataRefreshRateLimitResult" - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "401": { - "$ref": "#/components/responses/UnauthorisedRequest" - }, - "403": { - "$ref": "#/components/responses/ForbiddenRequest" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "429": { - "$ref": "#/components/responses/TooManyMetadataRefreshes" - }, - "500": { - "$ref": "#/components/responses/InternalServerError" - } - } - } - }, - "/v1/chains/{chain_name}/collections/{contract_address}/nfts": { - "get": { - "description": "List NFTs by contract address", - "tags": [ - "nfts" - ], - "summary": "List NFTs by contract address", - "operationId": "ListNFTs", - "parameters": [ - { - "name": "contract_address", - "in": "path", - "required": true, - "description": "Contract address", - "schema": { - "type": "string" - }, - "example": "0x8a90cab2b38dba80c64b7734e58ee1db38b8992e" - }, - { - "name": "chain_name", - "description": "The name of chain", - "in": "path", - "required": true, - "schema": { - "$ref": "#/components/schemas/ChainName" - }, - "examples": { - "testnet": { - "value": "imtbl-zkevm-testnet", - "summary": "Immutable zkEVM Public Testnet" - } - } - }, - { - "name": "token_id", - "in": "query", - "required": false, - "description": "List of token IDs to filter by", - "schema": { - "type": "array", - "maxItems": 30, - "items": { - "type": "string", - "example": "1" - } - } - }, - { - "name": "from_updated_at", - "in": "query", - "description": "Datetime to use as the oldest updated timestamp", - "required": false, - "schema": { - "type": "string", - "example": "2022-08-16T17:43:26.991388Z", - "format": "date-time" - } - }, - { - "name": "page_cursor", - "in": "query", - "description": "Encoded page cursor to retrieve previous or next page. Use the value returned in the response.", - "required": false, - "schema": { - "$ref": "#/components/schemas/PageCursor" - } - }, - { - "name": "page_size", - "description": "Maximum number of items to return", - "in": "query", - "required": false, - "schema": { - "$ref": "#/components/schemas/PageSize" - } - } - ], - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ListNFTsResult" - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "500": { - "$ref": "#/components/responses/InternalServerError" - } - } - } - }, - "/v1/chains/{chain_name}/accounts/{account_address}/nfts": { - "get": { - "description": "List NFTs by account address", - "tags": [ - "nfts" - ], - "operationId": "ListNFTsByAccountAddress", - "summary": "List NFTs by account address", - "parameters": [ - { - "name": "account_address", - "in": "path", - "description": "Account address", - "required": true, - "schema": { - "type": "string" - }, - "example": "0xe9b00a87700f660e46b6f5deaa1232836bcc07d3" - }, - { - "name": "chain_name", - "description": "The name of chain", - "in": "path", - "required": true, - "schema": { - "$ref": "#/components/schemas/ChainName" - }, - "examples": { - "testnet": { - "value": "imtbl-zkevm-testnet", - "summary": "Immutable zkEVM Public Testnet" - } - } - }, - { - "name": "contract_address", - "in": "query", - "description": "The address of contract", - "required": false, - "schema": { - "type": "string" - }, - "example": "0x8a90cab2b38dba80c64b7734e58ee1db38b8992e" - }, - { - "name": "token_id", - "in": "query", - "required": false, - "description": "List of token IDs to filter by", - "schema": { - "type": "array", - "maxItems": 30, - "items": { - "type": "string", - "example": "1" - } - } - }, - { - "name": "from_updated_at", - "in": "query", - "description": "Datetime to use as the oldest updated timestamp", - "required": false, - "schema": { - "type": "string", - "example": "2022-08-16T17:43:26.991388Z", - "format": "date-time" - } - }, - { - "name": "page_cursor", - "in": "query", - "description": "Encoded page cursor to retrieve previous or next page. Use the value returned in the response.", - "required": false, - "schema": { - "$ref": "#/components/schemas/PageCursor" - } - }, - { - "name": "page_size", - "description": "Maximum number of items to return", - "in": "query", - "required": false, - "schema": { - "$ref": "#/components/schemas/PageSize" - } - } - ], - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ListNFTsByOwnerResult" - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "500": { - "$ref": "#/components/responses/InternalServerError" - } - } - } - }, - "/v1/chains/{chain_name}/collections/{contract_address}/nfts/{token_id}/owners": { - "get": { - "description": "List NFT owners by token ID", - "tags": [ - "nft owners" - ], - "operationId": "ListNFTOwners", - "summary": "List NFT owners by token ID", - "parameters": [ - { - "name": "contract_address", - "in": "path", - "description": "The address of contract", - "required": true, - "schema": { - "type": "string" - }, - "example": "0x8a90cab2b38dba80c64b7734e58ee1db38b8992e" - }, - { - "name": "token_id", - "in": "path", - "description": "An `uint256` token id as string", - "required": true, - "schema": { - "type": "string" - }, - "example": "1" - }, - { - "name": "chain_name", - "description": "The name of chain", - "in": "path", - "required": true, - "schema": { - "$ref": "#/components/schemas/ChainName" - }, - "examples": { - "testnet": { - "value": "imtbl-zkevm-testnet", - "summary": "Immutable zkEVM Public Testnet" - } - } - }, - { - "name": "page_cursor", - "in": "query", - "description": "Encoded page cursor to retrieve previous or next page. Use the value returned in the response.", - "required": false, - "schema": { - "$ref": "#/components/schemas/PageCursor" - } - }, - { - "name": "page_size", - "description": "Maximum number of items to return", - "in": "query", - "required": false, - "schema": { - "$ref": "#/components/schemas/PageSize" - } - } - ], - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ListNFTOwnersResult" - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "500": { - "$ref": "#/components/responses/InternalServerError" - } - } - } - }, - "/v1/chains/{chain_name}/nfts": { - "get": { - "description": "List all NFTs on a chain", - "tags": [ - "nfts" - ], - "operationId": "ListAllNFTs", - "summary": "List all NFTs", - "parameters": [ - { - "name": "chain_name", - "description": "The name of chain", - "in": "path", - "required": true, - "schema": { - "$ref": "#/components/schemas/ChainName" - }, - "examples": { - "testnet": { - "value": "imtbl-zkevm-testnet", - "summary": "Immutable zkEVM Public Testnet" - } - } - }, - { - "name": "from_updated_at", - "in": "query", - "description": "Datetime to use as the oldest updated timestamp", - "required": false, - "schema": { - "type": "string", - "example": "2022-08-16T17:43:26.991388Z", - "format": "date-time" - } - }, - { - "name": "page_cursor", - "in": "query", - "description": "Encoded page cursor to retrieve previous or next page. Use the value returned in the response.", - "required": false, - "schema": { - "$ref": "#/components/schemas/PageCursor" - } - }, - { - "name": "page_size", - "description": "Maximum number of items to return", - "in": "query", - "required": false, - "schema": { - "$ref": "#/components/schemas/PageSize" - } - } - ], - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ListNFTsResult" - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "500": { - "$ref": "#/components/responses/InternalServerError" - } - } - } - }, - "/v1/chains/{chain_name}/nft-owners": { - "get": { - "description": "List all NFT owners on a chain", - "tags": [ - "nft owners" - ], - "operationId": "ListAllNFTOwners", - "summary": "List all NFT owners", - "parameters": [ - { - "name": "chain_name", - "description": "The name of chain", - "in": "path", - "required": true, - "schema": { - "$ref": "#/components/schemas/ChainName" - }, - "examples": { - "testnet": { - "value": "imtbl-zkevm-testnet", - "summary": "Immutable zkEVM Public Testnet" - } - } - }, - { - "name": "from_updated_at", - "in": "query", - "description": "Datetime to use as the oldest updated timestamp", - "schema": { - "type": "string", - "example": "2022-08-16T17:43:26.991388Z", - "format": "date-time" - } - }, - { - "name": "page_cursor", - "in": "query", - "description": "Encoded page cursor to retrieve previous or next page. Use the value returned in the response.", - "required": false, - "schema": { - "$ref": "#/components/schemas/PageCursor" - } - }, - { - "name": "page_size", - "description": "Maximum number of items to return", - "in": "query", - "required": false, - "schema": { - "$ref": "#/components/schemas/PageSize" - } - } - ], - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ListNFTOwnersResult" - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "500": { - "$ref": "#/components/responses/InternalServerError" - } - } - } - }, - "/v1/chains/{chain_name}/collections/{contract_address}/owners": { - "get": { - "description": "List owners by contract address", - "tags": [ - "nft owners" - ], - "operationId": "ListOwnersByContractAddress", - "summary": "List owners by contract address", - "parameters": [ - { - "name": "contract_address", - "in": "path", - "description": "The address of contract", - "required": true, - "schema": { - "type": "string" - }, - "example": "0x8a90cab2b38dba80c64b7734e58ee1db38b8992e" - }, - { - "name": "chain_name", - "description": "The name of chain", - "in": "path", - "required": true, - "schema": { - "$ref": "#/components/schemas/ChainName" - }, - "examples": { - "testnet": { - "value": "imtbl-zkevm-testnet", - "summary": "Immutable zkEVM Public Testnet" - } - } - }, - { - "name": "account_address", - "in": "query", - "required": false, - "description": "List of account addresses to filter by", - "schema": { - "type": "array", - "maxItems": 30, - "items": { - "type": "string", - "example": "0xe9b00a87700f660e46b6f5deaa1232836bcc07d3" - } - } - }, - { - "name": "from_updated_at", - "in": "query", - "description": "Datetime to use as the oldest updated timestamp", - "required": false, - "schema": { - "type": "string", - "example": "2022-08-16T17:43:26.991388Z", - "format": "date-time" - } - }, - { - "name": "page_cursor", - "in": "query", - "description": "Encoded page cursor to retrieve previous or next page. Use the value returned in the response.", - "required": false, - "schema": { - "$ref": "#/components/schemas/PageCursor" - } - }, - { - "name": "page_size", - "description": "Maximum number of items to return", - "in": "query", - "required": false, - "schema": { - "$ref": "#/components/schemas/PageSize" - } - } - ], - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ListCollectionOwnersResult" - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "500": { - "$ref": "#/components/responses/InternalServerError" - } - } - } - }, - "/v1/chains/{chain_name}/tokens": { - "get": { - "description": "List ERC20 tokens", - "tags": [ - "tokens" - ], - "operationId": "ListERC20Tokens", - "summary": "List ERC20 tokens", - "parameters": [ - { - "name": "chain_name", - "description": "The name of chain", - "in": "path", - "required": true, - "schema": { - "$ref": "#/components/schemas/ChainName" - } - }, - { - "name": "from_updated_at", - "in": "query", - "description": "Datetime to use as the oldest updated timestamp", - "required": false, - "schema": { - "type": "string", - "example": "2022-08-16T17:43:26.991388Z", - "format": "date-time" - } - }, - { - "name": "verification_status", - "in": "query", - "required": false, - "description": "List of verification status to filter by", - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/AssetVerificationStatus" - } - } - }, - { - "name": "is_canonical", - "in": "query", - "required": false, - "description": "[Experimental - Canonical token data may be updated] Filter by canonical or non-canonical tokens.", - "schema": { - "type": "boolean" - } - }, - { - "name": "page_cursor", - "in": "query", - "description": "Encoded page cursor to retrieve previous or next page. Use the value returned in the response.", - "required": false, - "schema": { - "$ref": "#/components/schemas/PageCursor" - } - }, - { - "name": "page_size", - "description": "Maximum number of items to return", - "in": "query", - "required": false, - "schema": { - "$ref": "#/components/schemas/PageSize" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ListTokensResult" - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "500": { - "$ref": "#/components/responses/InternalServerError" - } - } - } - }, - "/v1/chains/{chain_name}/tokens/{contract_address}": { - "get": { - "description": "Get single ERC20 token", - "tags": [ - "tokens" - ], - "operationId": "GetERC20Token", - "summary": "Get single ERC20 token", - "parameters": [ - { - "name": "contract_address", - "in": "path", - "description": "The address of contract", - "required": true, - "schema": { - "type": "string" - }, - "example": "0x8a90cab2b38dba80c64b7734e58ee1db38b8992e" - }, - { - "name": "chain_name", - "description": "The name of chain", - "in": "path", - "required": true, - "schema": { - "$ref": "#/components/schemas/ChainName" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/GetTokenResult" - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "500": { - "$ref": "#/components/responses/InternalServerError" - } - } - } - }, - "/v1/chains": { - "get": { - "description": "List supported chains", - "summary": "List supported chains", - "tags": [ - "chains" - ], - "operationId": "ListChains", - "parameters": [ - { - "name": "page_cursor", - "in": "query", - "description": "Encoded page cursor to retrieve previous or next page. Use the value returned in the response.", - "required": false, - "schema": { - "$ref": "#/components/schemas/PageCursor" - } - }, - { - "name": "page_size", - "description": "Maximum number of items to return", - "in": "query", - "required": false, - "schema": { - "$ref": "#/components/schemas/PageSize" - } - } - ], - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ListChainsResult" - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "500": { - "$ref": "#/components/responses/InternalServerError" - } - } - } - }, - "/v1/chains/{chain_name}/collections/{contract_address}/nfts/refresh-metadata": { - "post": { - "description": "Refresh NFT metadata", - "summary": "Refresh NFT metadata", - "tags": [ - "metadata" - ], - "operationId": "RefreshNFTMetadataByTokenID", - "security": [ - { - "ImmutableApiKey": [ - "refresh:metadata" - ] - } - ], - "parameters": [ - { - "name": "contract_address", - "in": "path", - "description": "The address of contract", - "required": true, - "schema": { - "type": "string" - }, - "example": "0x8a90cab2b38dba80c64b7734e58ee1db38b8992e" - }, - { - "name": "chain_name", - "description": "The name of chain", - "in": "path", - "required": true, - "schema": { - "$ref": "#/components/schemas/ChainName" - } - } - ], - "requestBody": { - "description": "the request body", - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RefreshNFTMetadataByTokenIDRequest" - } - } - } - }, - "responses": { - "202": { - "description": "Accepted", - "headers": { - "imx-refreshes-limit": { - "$ref": "#/components/headers/MetadataRefreshLimit" - }, - "imx-refresh-limit-reset": { - "$ref": "#/components/headers/MetadataRefreshLimitReset" - }, - "imx-remaining-refreshes": { - "$ref": "#/components/headers/MetadataRefreshLimitRemaining" - }, - "retry-after": { - "$ref": "#/components/headers/MetadataRefreshRetryAfter" - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/MetadataRefreshRateLimitResult" - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "401": { - "$ref": "#/components/responses/UnauthorisedRequest" - }, - "403": { - "$ref": "#/components/responses/ForbiddenRequest" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "429": { - "$ref": "#/components/responses/TooManyMetadataRefreshes" - }, - "500": { - "$ref": "#/components/responses/InternalServerError" - } - } - } - }, - "/v1/chains/{chain_name}/collections/{contract_address}/nfts/mint-requests": { - "post": { - "description": "Create a mint request to mint a set of NFTs for a given collection", - "summary": "Mint NFTs", - "security": [ - { - "ImmutableApiKey": [ - "write:mint-request" - ] - } - ], - "tags": [ - "nfts" - ], - "operationId": "CreateMintRequest", - "parameters": [ - { - "name": "contract_address", - "in": "path", - "description": "The address of contract", - "required": true, - "schema": { - "type": "string" - }, - "example": "0x8a90cab2b38dba80c64b7734e58ee1db38b8992e" - }, - { - "name": "chain_name", - "description": "The name of chain", - "in": "path", - "required": true, - "schema": { - "$ref": "#/components/schemas/ChainName" - } - } - ], - "requestBody": { - "description": "Create Mint Request Body", - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CreateMintRequestRequest" - } - } - } - }, - "responses": { - "202": { - "description": "Accepted", - "headers": { - "imx-mint-requests-limit": { - "$ref": "#/components/headers/MintRequestsLimit" - }, - "imx-mint-requests-limit-reset": { - "$ref": "#/components/headers/MintRequestsLimitReset" - }, - "imx-remaining-mint-requests": { - "$ref": "#/components/headers/MintRequestsLimitRemaining" - }, - "imx-mint-requests-retry-after": { - "$ref": "#/components/headers/MintRequestsRetryAfter" - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CreateMintRequestResult" - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "401": { - "$ref": "#/components/responses/UnauthorisedRequest" - }, - "403": { - "$ref": "#/components/responses/ForbiddenRequest" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "409": { - "$ref": "#/components/responses/Conflict" - }, - "429": { - "$ref": "#/components/responses/TooManyMintRequests" - }, - "500": { - "$ref": "#/components/responses/InternalServerError" - } - } - }, - "get": { - "description": "Retrieve the status of all mints for a given contract address", - "summary": "List mint requests", - "security": [ - { - "ImmutableApiKey": [ - "write:mint-request" - ] - } - ], - "tags": [ - "nfts" - ], - "operationId": "ListMintRequests", - "parameters": [ - { - "name": "contract_address", - "in": "path", - "description": "The address of contract", - "required": true, - "schema": { - "type": "string" - }, - "example": "0x8a90cab2b38dba80c64b7734e58ee1db38b8992e" - }, - { - "name": "chain_name", - "description": "The name of chain", - "in": "path", - "required": true, - "schema": { - "$ref": "#/components/schemas/ChainName" - } - }, - { - "name": "page_cursor", - "in": "query", - "description": "Encoded page cursor to retrieve previous or next page. Use the value returned in the response.", - "required": false, - "schema": { - "$ref": "#/components/schemas/PageCursor" - } - }, - { - "name": "page_size", - "description": "Maximum number of items to return", - "in": "query", - "required": false, - "schema": { - "$ref": "#/components/schemas/PageSize" - } - }, - { - "name": "status", - "description": "The status of the mint request", - "in": "query", - "required": false, - "schema": { - "$ref": "#/components/schemas/MintRequestStatus" - } - } - ], - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ListMintRequestsResult" - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "401": { - "$ref": "#/components/responses/UnauthorisedRequest" - }, - "403": { - "$ref": "#/components/responses/ForbiddenRequest" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "500": { - "$ref": "#/components/responses/InternalServerError" - } - } - } - }, - "/v1/chains/{chain_name}/collections/{contract_address}/nfts/mint-requests/{reference_id}": { - "get": { - "description": "Retrieve the status of a mint request identified by its reference_id", - "summary": "Get mint request by reference ID", - "security": [ - { - "ImmutableApiKey": [ - "write:mint-request" - ] - } - ], - "tags": [ - "nfts" - ], - "operationId": "GetMintRequest", - "parameters": [ - { - "name": "contract_address", - "in": "path", - "description": "The address of contract", - "required": true, - "schema": { - "type": "string" - }, - "example": "0x8a90cab2b38dba80c64b7734e58ee1db38b8992e" - }, - { - "name": "chain_name", - "description": "The name of chain", - "in": "path", - "required": true, - "schema": { - "$ref": "#/components/schemas/ChainName" - } - }, - { - "name": "reference_id", - "description": "The id of the mint request", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "example": "67f7d464-b8f0-4f6a-9a3b-8d3cb4a21af0" - } - ], - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ListMintRequestsResult" - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "401": { - "$ref": "#/components/responses/UnauthorisedRequest" - }, - "403": { - "$ref": "#/components/responses/ForbiddenRequest" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "500": { - "$ref": "#/components/responses/InternalServerError" - } - } - } - }, - "/v1/chains/{chain_name}/crafting/sign": { - "post": { - "description": "Sign a crafting payload", - "tags": [ - "crafting" - ], - "operationId": "SignCraftingPayload", - "summary": "Sign a crafting payload", - "security": [ - { - "ImmutableApiKey": [ - "create:collections" - ] - } - ], - "parameters": [ - { - "name": "chain_name", - "description": "The name of chain", - "in": "path", - "required": true, - "schema": { - "$ref": "#/components/schemas/ChainName" - }, - "examples": { - "testnet": { - "value": "imtbl-zkevm-testnet", - "summary": "Immutable zkEVM Public Testnet" - } - } - } - ], - "requestBody": { - "description": "The request body", - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SignCraftingRequest" - } - } - } - }, - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SignCraftingResult" - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "401": { - "$ref": "#/components/responses/UnauthorisedRequest" - }, - "403": { - "$ref": "#/components/responses/ForbiddenRequest" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "500": { - "$ref": "#/components/responses/InternalServerError" - } - } - } - }, - "/v1/chains/{chain_name}/orders/cancel": { - "post": { - "tags": [ - "orders" - ], - "summary": "Cancel one or more orders", - "description": "Cancel one or more orders", - "operationId": "CancelOrders", - "parameters": [ - { - "name": "chain_name", - "in": "path", - "required": true, - "schema": { - "$ref": "#/components/schemas/ChainName" - } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CancelOrdersRequestBody" - } - } - } - }, - "responses": { - "200": { - "description": "Orders cancellation response.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CancelOrdersResult" - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "401": { - "$ref": "#/components/responses/UnauthorisedRequest" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "429": { - "$ref": "#/components/responses/TooManyRequests" - }, - "500": { - "$ref": "#/components/responses/InternalServerError" - }, - "501": { - "$ref": "#/components/responses/NotImplementedError" - } - } - } - }, - "/v1/chains/{chain_name}/orders/listings": { - "get": { - "tags": [ - "orders" - ], - "summary": "List all listings", - "description": "List all listings", - "operationId": "ListListings", - "parameters": [ - { - "name": "chain_name", - "in": "path", - "required": true, - "schema": { - "$ref": "#/components/schemas/ChainName" - } - }, - { - "name": "status", - "in": "query", - "description": "Order status to filter by", - "required": false, - "schema": { - "$ref": "#/components/schemas/OrderStatusName" - } - }, - { - "name": "sell_item_contract_address", - "in": "query", - "description": "Sell item contract address to filter by", - "required": false, - "schema": { - "type": "string", - "example": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "pattern": "^0x[a-fA-F0-9]{40}$" - } - }, - { - "name": "buy_item_type", - "in": "query", - "description": "Buy item type to filter by", - "required": false, - "schema": { - "type": "string", - "enum": [ - "NATIVE", - "ERC20" - ], - "example": "NATIVE" - } - }, - { - "name": "buy_item_contract_address", - "in": "query", - "description": "Buy item contract address to filter by", - "required": false, - "schema": { - "type": "string", - "example": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "pattern": "^0x[a-fA-F0-9]{40}$" - } - }, - { - "name": "account_address", - "in": "query", - "description": "The account address of the user who created the listing", - "required": false, - "schema": { - "type": "string", - "example": "0xc49Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "pattern": "^0x[a-fA-F0-9]{40}$" - } - }, - { - "name": "sell_item_metadata_id", - "in": "query", - "description": "The metadata_id of the sell item", - "required": false, - "schema": { - "type": "string", - "format": "uuid", - "example": "020792C9-4AD7-8EC4-4038-9E05C598535B" - } - }, - { - "name": "sell_item_token_id", - "in": "query", - "description": "Sell item token identifier to filter by", - "required": false, - "schema": { - "type": "string", - "example": "1", - "pattern": "\\d+" - } - }, - { - "name": "from_updated_at", - "in": "query", - "description": "From updated at including given date", - "required": false, - "schema": { - "type": "string", - "format": "date-time", - "example": "2022-03-09T05:00:50.52Z" - } - }, - { - "name": "page_size", - "in": "query", - "description": "Maximum number of orders to return per page", - "schema": { - "$ref": "#/components/schemas/PageSize" - } - }, - { - "name": "sort_by", - "in": "query", - "description": "Order field to sort by. `buy_item_amount` sorts by per token price, for example if 5 ERC-1155s are on sale for 10eth, it’s sorted as 2eth for `buy_item_amount`.", - "required": false, - "schema": { - "type": "string", - "enum": [ - "created_at", - "updated_at", - "buy_item_amount" - ], - "description": "Order field to sort by", - "example": "created_at" - }, - "example": "created_at" - }, - { - "name": "sort_direction", - "in": "query", - "description": "Ascending or descending direction for sort", - "required": false, - "schema": { - "type": "string", - "enum": [ - "asc", - "desc" - ], - "description": "Ascending or descending direction for sort", - "example": "asc" - }, - "example": "asc" - }, - { - "name": "page_cursor", - "in": "query", - "description": "Page cursor to retrieve previous or next page. Use the value returned in the response.", - "required": false, - "schema": { - "$ref": "#/components/schemas/PageCursor" - } - } - ], - "responses": { - "200": { - "description": "OK response.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ListListingsResult" - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "500": { - "$ref": "#/components/responses/InternalServerError" - } - } - }, - "post": { - "tags": [ - "orders" - ], - "summary": "Create a listing", - "description": "Create a listing", - "operationId": "CreateListing", - "parameters": [ - { - "name": "chain_name", - "in": "path", - "required": true, - "schema": { - "$ref": "#/components/schemas/ChainName" - } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CreateListingRequestBody" - } - } - } - }, - "responses": { - "201": { - "description": "Created response.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ListingResult" - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "500": { - "$ref": "#/components/responses/InternalServerError" - } - } - } - }, - "/v1/chains/{chain_name}/orders/bids": { - "get": { - "tags": [ - "orders" - ], - "summary": "List all bids", - "description": "List all bids", - "operationId": "ListBids", - "parameters": [ - { - "name": "chain_name", - "in": "path", - "required": true, - "schema": { - "$ref": "#/components/schemas/ChainName" - } - }, - { - "name": "status", - "in": "query", - "description": "Order status to filter by", - "required": false, - "schema": { - "$ref": "#/components/schemas/OrderStatusName" - } - }, - { - "name": "buy_item_contract_address", - "in": "query", - "description": "Buy item contract address to filter by", - "required": false, - "schema": { - "type": "string", - "example": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "pattern": "^0x[a-fA-F0-9]{40}$" - } - }, - { - "name": "sell_item_contract_address", - "in": "query", - "description": "Sell item contract address to filter by", - "required": false, - "schema": { - "type": "string", - "example": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "pattern": "^0x[a-fA-F0-9]{40}$" - } - }, - { - "name": "account_address", - "in": "query", - "description": "The account address of the user who created the bid", - "required": false, - "schema": { - "type": "string", - "example": "0xc49Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "pattern": "^0x[a-fA-F0-9]{40}$" - } - }, - { - "name": "buy_item_metadata_id", - "in": "query", - "description": "The metadata_id of the buy item", - "required": false, - "schema": { - "type": "string", - "format": "uuid", - "example": "020792C9-4AD7-8EC4-4038-9E05C598535B" - } - }, - { - "name": "buy_item_token_id", - "in": "query", - "description": "buy item token identifier to filter by", - "required": false, - "schema": { - "type": "string", - "example": "1", - "pattern": "\\d+" - } - }, - { - "name": "from_updated_at", - "in": "query", - "description": "From updated at including given date", - "required": false, - "schema": { - "type": "string", - "format": "date-time", - "example": "2022-03-09T05:00:50.52Z" - } - }, - { - "name": "page_size", - "in": "query", - "description": "Maximum number of orders to return per page", - "schema": { - "$ref": "#/components/schemas/PageSize" - } - }, - { - "name": "sort_by", - "in": "query", - "description": "Order field to sort by. `sell_item_amount` sorts by per token price, for example if 10eth is offered for 5 ERC1155 items, it’s sorted as 2eth for `sell_item_amount`.", - "required": false, - "schema": { - "type": "string", - "enum": [ - "created_at", - "updated_at", - "sell_item_amount" - ], - "description": "Order field to sort by", - "example": "created_at" - }, - "example": "created_at" - }, - { - "name": "sort_direction", - "in": "query", - "description": "Ascending or descending direction for sort", - "required": false, - "schema": { - "type": "string", - "enum": [ - "asc", - "desc" - ], - "description": "Ascending or descending direction for sort", - "example": "asc" - }, - "example": "asc" - }, - { - "name": "page_cursor", - "in": "query", - "description": "Page cursor to retrieve previous or next page. Use the value returned in the response.", - "required": false, - "schema": { - "$ref": "#/components/schemas/PageCursor" - } - } - ], - "responses": { - "200": { - "description": "OK response.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ListBidsResult" - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "500": { - "$ref": "#/components/responses/InternalServerError" - } - } - }, - "post": { - "tags": [ - "orders" - ], - "summary": "Create a bid", - "description": "Create a bid", - "operationId": "CreateBid", - "parameters": [ - { - "name": "chain_name", - "in": "path", - "required": true, - "schema": { - "$ref": "#/components/schemas/ChainName" - } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CreateBidRequestBody" - } - } - } - }, - "responses": { - "201": { - "description": "Created response.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BidResult" - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "500": { - "$ref": "#/components/responses/InternalServerError" - }, - "501": { - "$ref": "#/components/responses/NotImplementedError" - } - } - } - }, - "/v1/chains/{chain_name}/orders/collection-bids": { - "get": { - "tags": [ - "orders" - ], - "summary": "List all collection bids", - "description": "List all collection bids", - "operationId": "ListCollectionBids", - "parameters": [ - { - "name": "chain_name", - "in": "path", - "required": true, - "schema": { - "$ref": "#/components/schemas/ChainName" - } - }, - { - "name": "status", - "in": "query", - "description": "Order status to filter by", - "required": false, - "schema": { - "$ref": "#/components/schemas/OrderStatusName" - } - }, - { - "name": "buy_item_contract_address", - "in": "query", - "description": "Buy item contract address to filter by", - "required": false, - "schema": { - "type": "string", - "example": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "pattern": "^0x[a-fA-F0-9]{40}$" - } - }, - { - "name": "sell_item_contract_address", - "in": "query", - "description": "Sell item contract address to filter by", - "required": false, - "schema": { - "type": "string", - "example": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "pattern": "^0x[a-fA-F0-9]{40}$" - } - }, - { - "name": "account_address", - "in": "query", - "description": "The account address of the user who created the bid", - "required": false, - "schema": { - "type": "string", - "example": "0xc49Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "pattern": "^0x[a-fA-F0-9]{40}$" - } - }, - { - "name": "from_updated_at", - "in": "query", - "description": "From updated at including given date", - "required": false, - "schema": { - "type": "string", - "format": "date-time", - "example": "2022-03-09T05:00:50.52Z" - } - }, - { - "name": "page_size", - "in": "query", - "description": "Maximum number of orders to return per page", - "schema": { - "$ref": "#/components/schemas/PageSize" - } - }, - { - "name": "sort_by", - "in": "query", - "description": "Order field to sort by. `sell_item_amount` sorts by per token price, for example if 10eth is offered for 5 ERC1155 items, it’s sorted as 2eth for `sell_item_amount`.", - "required": false, - "schema": { - "type": "string", - "enum": [ - "created_at", - "updated_at", - "sell_item_amount" - ], - "description": "Order field to sort by", - "example": "created_at" - }, - "example": "created_at" - }, - { - "name": "sort_direction", - "in": "query", - "description": "Ascending or descending direction for sort", - "required": false, - "schema": { - "type": "string", - "enum": [ - "asc", - "desc" - ], - "description": "Ascending or descending direction for sort", - "example": "asc" - }, - "example": "asc" - }, - { - "name": "page_cursor", - "in": "query", - "description": "Page cursor to retrieve previous or next page. Use the value returned in the response.", - "required": false, - "schema": { - "$ref": "#/components/schemas/PageCursor" - } - } - ], - "responses": { - "200": { - "description": "OK response.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ListCollectionBidsResult" - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "500": { - "$ref": "#/components/responses/InternalServerError" - } - } - }, - "post": { - "tags": [ - "orders" - ], - "summary": "Create a collection bid", - "description": "Create a collection bid", - "operationId": "CreateCollectionBid", - "parameters": [ - { - "name": "chain_name", - "in": "path", - "required": true, - "schema": { - "$ref": "#/components/schemas/ChainName" - } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CreateCollectionBidRequestBody" - } - } - } - }, - "responses": { - "201": { - "description": "Created response.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CollectionBidResult" - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "500": { - "$ref": "#/components/responses/InternalServerError" - }, - "501": { - "$ref": "#/components/responses/NotImplementedError" - } - } - } - }, - "/v1/chains/{chain_name}/orders/listings/{listing_id}": { - "get": { - "tags": [ - "orders" - ], - "summary": "Get a single listing by ID", - "description": "Get a single listing by ID", - "operationId": "GetListing", - "parameters": [ - { - "name": "chain_name", - "in": "path", - "required": true, - "schema": { - "$ref": "#/components/schemas/ChainName" - } - }, - { - "name": "listing_id", - "in": "path", - "description": "Global Order identifier", - "required": true, - "schema": { - "type": "string", - "format": "uuid", - "description": "Global Order identifier", - "example": "018792C9-4AD7-8EC4-4038-9E05C598534A" - } - } - ], - "responses": { - "200": { - "description": "OK response.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ListingResult" - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "500": { - "$ref": "#/components/responses/InternalServerError" - } - } - } - }, - "/v1/chains/{chain_name}/orders/bids/{bid_id}": { - "get": { - "tags": [ - "orders" - ], - "summary": "Get a single bid by ID", - "description": "Get a single bid by ID", - "operationId": "GetBid", - "parameters": [ - { - "name": "chain_name", - "in": "path", - "required": true, - "schema": { - "$ref": "#/components/schemas/ChainName" - } - }, - { - "name": "bid_id", - "in": "path", - "description": "Global Bid identifier", - "required": true, - "schema": { - "type": "string", - "format": "uuid", - "description": "Global Bid identifier", - "example": "018792C9-4AD7-8EC4-4038-9E05C598534A" - } - } - ], - "responses": { - "200": { - "description": "OK response.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BidResult" - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "500": { - "$ref": "#/components/responses/InternalServerError" - } - } - } - }, - "/v1/chains/{chain_name}/orders/collection-bids/{collection_bid_id}": { - "get": { - "tags": [ - "orders" - ], - "summary": "Get a single collection bid by ID", - "description": "Get a single collection bid by ID", - "operationId": "GetCollectionBid", - "parameters": [ - { - "name": "chain_name", - "in": "path", - "required": true, - "schema": { - "$ref": "#/components/schemas/ChainName" - } - }, - { - "name": "collection_bid_id", - "in": "path", - "description": "Global Collection Bid identifier", - "required": true, - "schema": { - "type": "string", - "format": "uuid", - "description": "Global Collection Bid identifier", - "example": "018792C9-4AD7-8EC4-4038-9E05C598534A" - } - } - ], - "responses": { - "200": { - "description": "OK response.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CollectionBidResult" - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "500": { - "$ref": "#/components/responses/InternalServerError" - } - } - } - }, - "/v1/chains/{chain_name}/orders/fulfillment-data": { - "post": { - "tags": [ - "orders" - ], - "summary": "Retrieve fulfillment data for orders", - "description": "Retrieve signed fulfillment data based on the list of order IDs and corresponding fees.", - "operationId": "fulfillment_data", - "parameters": [ - { - "name": "chain_name", - "in": "path", - "required": true, - "schema": { - "$ref": "#/components/schemas/ChainName" - } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/FulfillmentDataRequest" - } - } - } - } - }, - "responses": { - "200": { - "description": "Successful response", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "result": { - "type": "object", - "properties": { - "fulfillable_orders": { - "type": "array", - "items": { - "$ref": "#/components/schemas/FulfillableOrder" - } - }, - "unfulfillable_orders": { - "type": "array", - "items": { - "$ref": "#/components/schemas/UnfulfillableOrder" - } - } - }, - "required": [ - "fulfillable_orders", - "unfulfillable_orders" - ] - } - }, - "required": [ - "result" - ] - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "500": { - "$ref": "#/components/responses/InternalServerError" - } - } - } - }, - "/v1/chains/{chain_name}/trades": { - "get": { - "tags": [ - "orders" - ], - "summary": "List all trades", - "description": "List all trades", - "operationId": "ListTrades", - "parameters": [ - { - "name": "chain_name", - "in": "path", - "required": true, - "schema": { - "$ref": "#/components/schemas/ChainName" - } - }, - { - "name": "account_address", - "in": "query", - "required": false, - "schema": { - "type": "string", - "description": "Account address to filter trades by (includes buy and sell wallet address)", - "example": "0x784578949A4A50DeA641Fb15dd2B11C72E76919a", - "pattern": "^0x[a-fA-F0-9]{40}$" - } - }, - { - "name": "sell_item_contract_address", - "in": "query", - "required": false, - "schema": { - "type": "string", - "description": "Sell item contract address to filter by", - "example": "0x784578949A4A50DeA641Fb15dd2B11C72E76919a", - "pattern": "^0x[a-fA-F0-9]{40}$" - } - }, - { - "name": "from_indexed_at", - "in": "query", - "description": "From indexed at including given date", - "required": false, - "schema": { - "type": "string", - "format": "date-time", - "example": "2022-03-09T05:00:50.52Z" - } - }, - { - "name": "page_size", - "in": "query", - "description": "Maximum number of trades to return per page", - "schema": { - "$ref": "#/components/schemas/PageSize" - } - }, - { - "name": "sort_by", - "in": "query", - "description": "Trade field to sort by", - "required": false, - "schema": { - "type": "string", - "enum": [ - "indexed_at" - ], - "description": "Trade field to sort by", - "example": "indexed_at" - }, - "example": "indexed_at" - }, - { - "name": "sort_direction", - "in": "query", - "description": "Ascending or descending direction for sort", - "required": false, - "schema": { - "type": "string", - "enum": [ - "asc", - "desc" - ], - "description": "Ascending or descending direction for sort", - "example": "asc" - }, - "example": "asc" - }, - { - "name": "page_cursor", - "in": "query", - "description": "Page cursor to retrieve previous or next page. Use the value returned in the response.", - "required": false, - "schema": { - "$ref": "#/components/schemas/PageCursor" - } - } - ], - "responses": { - "200": { - "description": "OK response.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ListTradeResult" - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "500": { - "$ref": "#/components/responses/InternalServerError" - } - } - } - }, - "/v1/chains/{chain_name}/trades/{trade_id}": { - "get": { - "tags": [ - "orders" - ], - "summary": "Get a single trade by ID", - "description": "Get a single trade by ID", - "operationId": "GetTrade", - "parameters": [ - { - "name": "chain_name", - "in": "path", - "required": true, - "schema": { - "$ref": "#/components/schemas/ChainName" - } - }, - { - "name": "trade_id", - "in": "path", - "description": "Global Trade identifier", - "required": true, - "schema": { - "type": "string", - "format": "uuid", - "description": "Global Trade identifier", - "example": "018792C9-4AD7-8EC4-4038-9E05C598534A" - } - } - ], - "responses": { - "200": { - "description": "OK response.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/TradeResult" - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "500": { - "$ref": "#/components/responses/InternalServerError" - } - } - } - }, - "/v1/chains/{chain_name}/passport/users/{user_id}/linked-addresses": { - "get": { - "deprecated": true, - "summary": "Get Ethereum linked addresses for a user", - "description": "This API has been deprecated, please use https://docs.immutable.com/zkevm/api/reference/#/operations/getUserInfo instead to get a list of linked addresses.", - "tags": [ - "passport" - ], - "parameters": [ - { - "name": "user_id", - "in": "path", - "description": "The user's userId", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "chain_name", - "in": "path", - "required": true, - "schema": { - "$ref": "#/components/schemas/ChainName" - } - } - ], - "operationId": "getLinkedAddresses", - "security": [ - { - "BearerAuth": [ - "openid" - ] - } - ], - "responses": { - "200": { - "description": "User's list of linked addresses response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/GetLinkedAddressesRes" - } - } - } - }, - "400": { - "description": "BadRequestError", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/APIError400" - } - } - } - }, - "401": { - "description": "UnauthorizedError", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/APIError401" - } - } - } - }, - "403": { - "description": "ForbiddenError", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/APIError403" - } - } - } - }, - "429": { - "description": "TooManyRequestsError", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/APIError429" - } - } - } - }, - "500": { - "description": "InternalServerError", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/APIError500" - } - } - } - }, - "default": { - "description": "unexpected error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BasicAPIError" - } - } - } - } - } - } - }, - "/passport-profile/v2/linked-wallets": { - "post": { - "security": [ - { - "BearerAuth": [ - "openid" - ] - } - ], - "tags": [ - "passport profile" - ], - "summary": "Link wallet v2", - "description": "Link an external EOA wallet to an Immutable Passport account by providing an EIP-712 signature.", - "operationId": "link-wallet-v2", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LinkWalletV2Request" - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Wallet" - } - } - } - }, - "400": { - "description": "BadRequestError", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/APIError400" - } - } - } - }, - "401": { - "description": "UnauthorizedError", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/APIError401" - } - } - } - }, - "403": { - "description": "ForbiddenError", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/APIError403" - } - } - } - }, - "500": { - "description": "InternalServerError", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/APIError500" - } - } - } - } - } - } - }, - "/passport-profile/v1/user/info": { - "get": { - "summary": "Get all info for a Passport user", - "description": "Get all the info for an authenticated Passport user", - "tags": [ - "passport profile" - ], - "operationId": "getUserInfo", - "security": [ - { - "BearerAuth": [ - "openid" - ] - } - ], - "responses": { - "200": { - "description": "Passport user's info", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UserInfo" - } - } - } - }, - "401": { - "description": "UnauthorizedError", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/APIError401" - } - } - } - }, - "500": { - "description": "InternalServerError", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/APIError500" - } - } - } - }, - "default": { - "description": "unexpected error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BasicAPIError" - } - } - } - } - } - } - }, - "/v1/chains/{chain_name}/search/filters/{contract_address}": { - "get": { - "tags": [ - "metadata-search" - ], - "operationId": "ListFilters", - "summary": "Get list of metadata attribute filters", - "description": "Get list of metadata filters", - "parameters": [ - { - "name": "chain_name", - "description": "The name of chain", - "schema": { - "$ref": "#/components/schemas/ChainName" - }, - "in": "path", - "required": true, - "examples": { - "testnet": { - "value": "imtbl-zkevm-testnet", - "summary": "Immutable zkEVM Public Testnet" - } - } - }, - { - "name": "contract_address", - "in": "path", - "required": true, - "description": "Contract addresses for collection", - "schema": { - "type": "string", - "pattern": "^0x[a-fA-F0-9]{40}$", - "example": "0xe9b00a87700f660e46b6f5deaa1232836bcc07d3" - } - } - ], - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ListFiltersResult" - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "401": { - "$ref": "#/components/responses/UnauthorisedRequest" - }, - "403": { - "$ref": "#/components/responses/ForbiddenRequest" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "429": { - "$ref": "#/components/responses/TooManyRequests" - }, - "500": { - "$ref": "#/components/responses/InternalServerError" - } - } - } - }, - "/v1/chains/{chain_name}/search/nfts": { - "get": { - "tags": [ - "metadata-search" - ], - "operationId": "SearchNFTs", - "summary": "Search NFTs", - "description": "Search NFTs", - "parameters": [ - { - "name": "chain_name", - "description": "The name of chain", - "schema": { - "$ref": "#/components/schemas/ChainName" - }, - "in": "path", - "required": true, - "examples": { - "testnet": { - "value": "imtbl-zkevm-testnet", - "summary": "Immutable zkEVM Public Testnet" - } - } - }, - { - "name": "contract_address", - "in": "query", - "required": true, - "description": "List of contract addresses to filter by", - "schema": { - "type": "array", - "items": { - "type": "string", - "pattern": "^0x[a-fA-F0-9]{40}$", - "example": "0xe9b00a87700f660e46b6f5deaa1232836bcc07d3" - }, - "maxItems": 20, - "minItems": 1 - } - }, - { - "name": "account_address", - "in": "query", - "required": false, - "description": "Account address to filter by", - "schema": { - "type": "string", - "pattern": "^0x[a-fA-F0-9]{40}$", - "example": "0xe9b00a87700f660e46b6f5deaa1232836bcc07d3" - } - }, - { - "name": "stack_id", - "in": "query", - "required": false, - "description": "Filters NFTs that belong to any of these stacks", - "schema": { - "type": "array", - "items": { - "type": "string", - "format": "uuid", - "example": "7053e765-c119-4efb-b5cf-405ccccaf6c4" - }, - "minItems": 1, - "maxItems": 20 - } - }, - { - "name": "only_include_owner_listings", - "in": "query", - "required": false, - "description": "Whether the listings should include only the owner created listings", - "schema": { - "type": "boolean", - "example": true - } - }, - { - "name": "page_size", - "in": "query", - "required": false, - "description": "Number of results to return per page", - "schema": { - "$ref": "#/components/schemas/PageSize" - } - }, - { - "name": "page_cursor", - "in": "query", - "description": "Encoded page cursor to retrieve previous or next page. Use the value returned in the response.", - "required": false, - "schema": { - "$ref": "#/components/schemas/PageCursor" - } - } - ], - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SearchNFTsResult" - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "401": { - "$ref": "#/components/responses/UnauthorisedRequest" - }, - "403": { - "$ref": "#/components/responses/ForbiddenRequest" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "429": { - "$ref": "#/components/responses/TooManyRequests" - }, - "500": { - "$ref": "#/components/responses/InternalServerError" - } - } - } - }, - "/v1/chains/{chain_name}/search/stacks": { - "get": { - "tags": [ - "metadata-search" - ], - "operationId": "SearchStacks", - "summary": "Search NFT stacks", - "description": "Search NFT stacks", - "parameters": [ - { - "name": "chain_name", - "description": "The name of chain", - "schema": { - "$ref": "#/components/schemas/ChainName" - }, - "in": "path", - "required": true, - "examples": { - "testnet": { - "value": "imtbl-zkevm-testnet", - "summary": "Immutable zkEVM Public Testnet" - } - } - }, - { - "name": "contract_address", - "in": "query", - "required": true, - "description": "List of contract addresses to filter by", - "schema": { - "type": "array", - "items": { - "type": "string", - "pattern": "^0x[a-fA-F0-9]{40}$", - "example": "0xe9b00a87700f660e46b6f5deaa1232836bcc07d3" - }, - "maxItems": 20, - "minItems": 1 - } - }, - { - "name": "account_address", - "in": "query", - "required": false, - "description": "Account address to filter by", - "schema": { - "type": "string", - "pattern": "^0x[a-fA-F0-9]{40}$", - "example": "0xe9b00a87700f660e46b6f5deaa1232836bcc07d3" - } - }, - { - "name": "only_include_owner_listings", - "in": "query", - "required": false, - "description": "Whether to the listings should include only the owner created listings", - "schema": { - "type": "boolean", - "example": true - } - }, - { - "name": "only_if_has_active_listings", - "in": "query", - "required": false, - "description": "Filters results to include only stacks that have a current active listing. False and 'null' return all unfiltered stacks.", - "schema": { - "type": "boolean", - "example": true - } - }, - { - "name": "traits", - "in": "query", - "required": false, - "description": "JSON encoded traits to filter by. e.g. encodeURIComponent(JSON.stringify({\"rarity\": {\"values\": [\"common\", \"rare\"], \"condition\": \"eq\"}}))", - "schema": { - "type": "string" - } - }, - { - "name": "keyword", - "in": "query", - "required": false, - "description": "Keyword to search NFT name and description. Alphanumeric characters only.", - "schema": { - "type": "string", - "example": "sword" - } - }, - { - "name": "payment_token", - "in": "query", - "required": false, - "description": "Filters the active listings, bids, floor listing and top bid by the specified payment token, either the address of the payment token contract or 'NATIVE'", - "schema": { - "type": "string", - "example": "NATIVE" - } - }, - { - "name": "sort_by", - "in": "query", - "required": false, - "description": "Sort results in a specific order", - "schema": { - "type": "string", - "nullable": false, - "enum": [ - "cheapest_first" - ] - } - }, - { - "name": "page_size", - "in": "query", - "required": false, - "description": "Number of results to return per page", - "schema": { - "$ref": "#/components/schemas/PageSize" - } - }, - { - "name": "page_cursor", - "in": "query", - "description": "Encoded page cursor to retrieve previous or next page. Use the value returned in the response.", - "required": false, - "schema": { - "$ref": "#/components/schemas/PageCursor" - } - } - ], - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SearchStacksResult" - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "401": { - "$ref": "#/components/responses/UnauthorisedRequest" - }, - "403": { - "$ref": "#/components/responses/ForbiddenRequest" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "429": { - "$ref": "#/components/responses/TooManyRequests" - }, - "500": { - "$ref": "#/components/responses/InternalServerError" - } - } - } - }, - "/v1/chains/{chain_name}/stacks": { - "get": { - "tags": [ - "metadata" - ], - "operationId": "ListStacks", - "summary": "List NFT stack bundles by stack_id. Response will include Market, Listings & Stack Count information for each stack", - "description": "List NFT stack bundles by stack_id. This endpoint functions similarly to `ListMetadataByID` but extends the response to include Market, Listings & Stack Count information for each stack.", - "parameters": [ - { - "name": "chain_name", - "description": "The name of chain", - "schema": { - "$ref": "#/components/schemas/ChainName" - }, - "in": "path", - "required": true, - "examples": { - "testnet": { - "value": "imtbl-zkevm-testnet", - "summary": "Immutable zkEVM Public Testnet" - } - } - }, - { - "name": "stack_id", - "in": "query", - "required": true, - "description": "List of stack_id to filter by", - "schema": { - "type": "array", - "items": { - "format": "uuid", - "type": "string", - "example": "7053e765-c119-4efb-b5cf-405ccccaf6c4" - }, - "maxItems": 20, - "minItems": 1 - } - } - ], - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "schema": { - "type": "array", - "description": "List of stack bundles", - "items": { - "$ref": "#/components/schemas/StackBundle" - } - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "401": { - "$ref": "#/components/responses/UnauthorisedRequest" - }, - "403": { - "$ref": "#/components/responses/ForbiddenRequest" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "429": { - "$ref": "#/components/responses/TooManyRequests" - }, - "500": { - "$ref": "#/components/responses/InternalServerError" - } - } - } - }, - "/v1/chains/{chain_name}/quotes/{contract_address}/stacks": { - "get": { - "tags": [ - "pricing" - ], - "operationId": "QuotesForStacks", - "summary": "Get pricing data for a list of stack ids", - "description": "Get pricing data for a list of stack ids", - "parameters": [ - { - "name": "chain_name", - "description": "The name of chain", - "schema": { - "$ref": "#/components/schemas/ChainName" - }, - "in": "path", - "required": true, - "examples": { - "testnet": { - "value": "imtbl-zkevm-testnet", - "summary": "Immutable zkEVM Public Testnet" - } - } - }, - { - "name": "contract_address", - "in": "path", - "required": true, - "description": "Contract address for collection that these stacks are on", - "schema": { - "pattern": "^0x[a-fA-F0-9]{40}$", - "type": "string" - } - }, - { - "name": "stack_id", - "in": "query", - "required": true, - "description": "List of stack ids to get pricing data for", - "schema": { - "type": "array", - "items": { - "type": "string", - "format": "uuid", - "example": "7053e765-c119-4efb-b5cf-405ccccaf6c4" - }, - "minItems": 1, - "maxItems": 20 - } - }, - { - "name": "payment_token", - "in": "query", - "required": false, - "description": "Filters the active listings, bids, floor listing and top bid by the specified payment token, either the address of the payment token contract or 'NATIVE'.", - "schema": { - "type": "string", - "example": "NATIVE" - } - }, - { - "name": "page_cursor", - "in": "query", - "description": "Encoded page cursor to retrieve previous or next page. Use the value returned in the response.", - "required": false, - "schema": { - "$ref": "#/components/schemas/PageCursor" - } - } - ], - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/QuotesForStacksResult" - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "401": { - "$ref": "#/components/responses/UnauthorisedRequest" - }, - "403": { - "$ref": "#/components/responses/ForbiddenRequest" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "429": { - "$ref": "#/components/responses/TooManyRequests" - }, - "500": { - "$ref": "#/components/responses/InternalServerError" - } - } - } - }, - "/v1/chains/{chain_name}/quotes/{contract_address}/nfts": { - "get": { - "tags": [ - "pricing" - ], - "operationId": "QuotesForNFTs", - "summary": "Get pricing data for a list of token ids", - "description": "pricing data for a list of token ids", - "parameters": [ - { - "name": "chain_name", - "description": "The name of chain", - "schema": { - "$ref": "#/components/schemas/ChainName" - }, - "in": "path", - "required": true, - "examples": { - "testnet": { - "value": "imtbl-zkevm-testnet", - "summary": "Immutable zkEVM Public Testnet" - } - } - }, - { - "name": "contract_address", - "in": "path", - "required": true, - "description": "Contract address for collection that these token ids are on", - "schema": { - "type": "string", - "pattern": "^0x[a-fA-F0-9]{40}$" - } - }, - { - "name": "token_id", - "in": "query", - "required": true, - "description": "List of token ids to get pricing data for", - "schema": { - "type": "array", - "items": { - "type": "string", - "example": "1337" - }, - "minItems": 1, - "maxItems": 20 - } - }, - { - "name": "payment_token", - "in": "query", - "required": false, - "description": "Filters the active listings, bids, floor listing and top bid by the specified payment token, either the address of the payment token contract or 'NATIVE'.", - "schema": { - "type": "string", - "example": "NATIVE" - } - }, - { - "name": "page_cursor", - "in": "query", - "description": "Encoded page cursor to retrieve previous or next page. Use the value returned in the response.", - "required": false, - "schema": { - "$ref": "#/components/schemas/PageCursor" - } - } - ], - "responses": { - "200": { - "description": "200 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/QuotesForNFTsResult" - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "401": { - "$ref": "#/components/responses/UnauthorisedRequest" - }, - "403": { - "$ref": "#/components/responses/ForbiddenRequest" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "429": { - "$ref": "#/components/responses/TooManyRequests" - }, - "500": { - "$ref": "#/components/responses/InternalServerError" - } - } - } - } - }, - "components": { - "securitySchemes": { - "BearerAuth": { - "type": "http", - "scheme": "bearer", - "bearerFormat": "JWT" - }, - "ImmutableApiKey": { - "x-go-name": "ImmutableApiKey", - "type": "apiKey", - "in": "header", - "name": "x-immutable-api-key" - }, - "BearerAuthWithClient": { - "type": "http", - "scheme": "bearer", - "bearerFormat": "JWT" - } - }, - "parameters": { - "ChainName": { - "name": "chain_name", - "description": "The name of chain", - "in": "path", - "required": true, - "schema": { - "$ref": "#/components/schemas/ChainName" - }, - "examples": { - "testnet": { - "value": "imtbl-zkevm-testnet", - "summary": "Immutable zkEVM Public Testnet" - } - } - }, - "ContractAddress": { - "name": "contract_address", - "in": "path", - "required": true, - "description": "Contract address", - "schema": { - "$ref": "#/components/schemas/Address" - } - } - }, - "responses": { - "NotFound": { - "description": "The specified resource was not found (404)", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/APIError404" - } - } - } - }, - "BadRequest": { - "description": "Bad Request (400)", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/APIError400" - } - } - } - }, - "UnauthorisedRequest": { - "description": "Unauthorised Request (401)", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/APIError401" - } - } - } - }, - "ForbiddenRequest": { - "description": "Forbidden Request (403)", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/APIError403" - } - } - } - }, - "Conflict": { - "description": "Conflict (409)", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/APIError409" - } - } - } - }, - "TooManyRequests": { - "description": "Too Many Requests (429)", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/APIError429" - } - } - }, - "headers": { - "Retry-After": { - "$ref": "#/components/headers/RetryAfter" - } - } - }, - "TooManyMetadataRefreshes": { - "description": "Too Many Metadata refreshes (429)", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/APIError429" - } - } - }, - "headers": { - "imx-refreshes-limit": { - "$ref": "#/components/headers/MetadataRefreshLimit" - }, - "imx-refresh-limit-reset": { - "$ref": "#/components/headers/MetadataRefreshLimitReset" - }, - "imx-remaining-refreshes": { - "$ref": "#/components/headers/MetadataRefreshLimitRemaining" - }, - "Retry-After": { - "$ref": "#/components/headers/MetadataRefreshRetryAfter" - } - } - }, - "TooManyMintRequests": { - "description": "Too Many mint requests (429)", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/APIError429" - } - } - }, - "headers": { - "imx-mint-requests-limit": { - "$ref": "#/components/headers/MintRequestsLimit" - }, - "imx-mint-requests-limit-reset": { - "$ref": "#/components/headers/MintRequestsLimitReset" - }, - "imx-remaining-mint-requests": { - "$ref": "#/components/headers/MintRequestsLimitRemaining" - }, - "imx-mint-requests-retry-after": { - "$ref": "#/components/headers/MintRequestsRetryAfter" - }, - "Retry-After": { - "$ref": "#/components/headers/MintRequestsRetryAfter" - } - } - }, - "InternalServerError": { - "description": "Internal Server Error (500)", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/APIError500" - } - } - } - }, - "NotImplementedError": { - "description": "Not Implemented Error (501)", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/APIError501" - } - } - } - } - }, - "headers": { - "MetadataRefreshLimit": { - "description": "The refresh request limit available to the project for each one-hour window.", - "schema": { - "type": "string" - } - }, - "MetadataRefreshLimitReset": { - "description": "The expiry date of the current one-hour window.", - "schema": { - "type": "string" - } - }, - "MetadataRefreshLimitRemaining": { - "description": "The number of refresh requests remaining in the current window.", - "schema": { - "type": "string" - } - }, - "MetadataRefreshRetryAfter": { - "description": "The number of seconds until the next refresh request can be made.", - "schema": { - "type": "string" - } - }, - "RetryAfter": { - "description": "The number of seconds until the next request can be made.", - "schema": { - "type": "string" - } - }, - "MintRequestsLimit": { - "description": "The mint requests limit available to the project for each time window.", - "schema": { - "type": "string" - } - }, - "MintRequestsLimitReset": { - "description": "The expiry date of the current time window.", - "schema": { - "type": "string" - } - }, - "MintRequestsLimitRemaining": { - "description": "The number of mint requests remaining in the current window.", - "schema": { - "type": "string" - } - }, - "MintRequestsRetryAfter": { - "description": "The number of seconds until the next refresh request can be made.", - "schema": { - "type": "string" - } - } - }, - "schemas": { - "PageSize": { - "type": "integer", - "format": "int32", - "description": "Maximum number of items to return", - "minimum": 1, - "default": 100, - "maximum": 200, - "example": 10 - }, - "PageCursor": { - "type": "string", - "description": "Encoded page cursor to retrieve previous or next page. Use the value returned in the response.", - "example": "ewogICJ0eXBlIjogInByZXYiLAogICJpdGVtIjogewogICAgImlkIjogNjI3NTEzMCwKICAgICJjcmVhdGVkX2F0IjogIjIwMjItMDktMTNUMTc6MDQ6MTIuMDI0MTI2WiIKICB9Cn0=" - }, - "Page": { - "type": "object", - "description": "Pagination properties", - "properties": { - "previous_cursor": { - "type": "string", - "nullable": true, - "description": "First item as an encoded string", - "example": "ewogICJ0eXBlIjogInByZXYiLAogICJpdGVtIjogewogICAgImlkIjogNjI3NTEzMCwKICAgICJjcmVhdGVkX2F0IjogIjIwMjItMDktMTNUMTc6MDQ6MTIuMDI0MTI2WiIKICB9Cn0=" - }, - "next_cursor": { - "type": "string", - "nullable": true, - "description": "Last item as an encoded string", - "example": "ewogICJ0eXBlIjogInByZXYiLAogICJpdGVtIjogewogICAgImlkIjogNjI3NTEzMCwKICAgICJjcmVhdGVkX2F0IjogIjIwMjItMDktMTNUMTc6MDQ6MTIuMDI0MTI2WiIKICB9Cn0=" - } - }, - "required": [ - "previous_cursor", - "next_cursor" - ] - }, - "ActivityType": { - "description": "The activity type", - "example": "mint", - "type": "string", - "enum": [ - "mint", - "burn", - "transfer", - "sale", - "deposit", - "withdrawal" - ] - }, - "ActivityNFT": { - "type": "object", - "properties": { - "contract_type": { - "$ref": "#/components/schemas/NFTContractType" - }, - "contract_address": { - "description": "The token contract address", - "type": "string", - "example": "0x8a90cab2b38dba80c64b7734e58ee1db38b8992e" - }, - "token_id": { - "description": "An `uint256` token id as string", - "type": "string", - "example": "1" - }, - "amount": { - "description": "(deprecated - will never be filled, use amount on Activity instead) The amount of tokens exchanged", - "type": "string", - "example": "1", - "deprecated": true - } - }, - "required": [ - "contract_type", - "contract_address", - "token_id", - "amount" - ] - }, - "ActivityToken": { - "type": "object", - "properties": { - "contract_type": { - "$ref": "#/components/schemas/TokenContractType" - }, - "contract_address": { - "description": "The contract address", - "type": "string", - "example": "0x8a90cab2b38dba80c64b7734e58ee1db38b8992e" - } - }, - "required": [ - "contract_type", - "contract_address" - ] - }, - "ActivityAsset": { - "description": "The contract and asset details for this activity", - "oneOf": [ - { - "$ref": "#/components/schemas/ActivityNFT" - }, - { - "$ref": "#/components/schemas/ActivityToken" - } - ], - "discriminator": { - "propertyName": "contract_type" - } - }, - "Address": { - "x-go-type": "common.Address", - "x-go-type-import": { - "path": "github.com/ethereum/go-ethereum/common", - "name": "common" - }, - "description": "An Ethereum address", - "type": "string", - "example": "0xe9b00a87700f660e46b6f5deaa1232836bcc07d3", - "pattern": "^0x[a-fA-F0-9]{40}$" - }, - "Mint": { - "type": "object", - "description": "The mint activity details", - "properties": { - "to": { - "description": "The account address the asset was minted to", - "type": "string", - "example": "0xe9b00a87700f660e46b6f5deaa1232836bcc07d3" - }, - "amount": { - "description": "The minted amount", - "type": "string", - "example": "1" - }, - "asset": { - "$ref": "#/components/schemas/ActivityAsset" - } - }, - "required": [ - "to", - "amount", - "asset" - ] - }, - "Deposit": { - "type": "object", - "description": "The deposit activity details", - "properties": { - "to": { - "description": "The account address the asset was deposited to", - "type": "string", - "example": "0xe9b00a87700f660e46b6f5deaa1232836bcc07d3" - }, - "amount": { - "description": "The deposited amount", - "type": "string", - "example": "1" - }, - "asset": { - "$ref": "#/components/schemas/ActivityAsset" - } - }, - "required": [ - "to", - "amount", - "asset" - ] - }, - "Burn": { - "description": "The burn activity details", - "type": "object", - "properties": { - "from": { - "description": "The account address the asset was transferred from", - "type": "string", - "example": "0xe9b00a87700f660e46b6f5deaa1232836bcc07d3" - }, - "amount": { - "description": "The amount of assets burnt", - "type": "string", - "example": "1" - }, - "asset": { - "$ref": "#/components/schemas/ActivityAsset" - } - }, - "required": [ - "from", - "amount", - "asset" - ] - }, - "Withdrawal": { - "description": "The withdrawal activity details", - "type": "object", - "properties": { - "from": { - "description": "The account address the asset was withdrawn from", - "type": "string", - "example": "0xe9b00a87700f660e46b6f5deaa1232836bcc07d3" - }, - "amount": { - "description": "The amount of assets withdrawn", - "type": "string", - "example": "1" - }, - "asset": { - "$ref": "#/components/schemas/ActivityAsset" - } - }, - "required": [ - "from", - "amount", - "asset" - ] - }, - "Transfer": { - "type": "object", - "description": "The transfer activity details", - "properties": { - "from": { - "description": "The account address the asset was transferred from", - "type": "string", - "example": "0xe9b00a87700f660e46b6f5deaa1232836bcc07d3" - }, - "to": { - "description": "The account address the asset was transferred to", - "type": "string", - "example": "0xe9b00a87700f660e46b6f5deaa1232836bcc07d3" - }, - "amount": { - "description": "The amount of assets transferred", - "type": "string", - "example": "1" - }, - "asset": { - "$ref": "#/components/schemas/ActivityAsset" - } - }, - "required": [ - "activity_type", - "from", - "to", - "amount", - "asset" - ] - }, - "ActivityNativeToken": { - "type": "object", - "properties": { - "symbol": { - "description": "The token symbol", - "type": "string", - "example": "ETH" - } - }, - "required": [ - "symbol" - ] - }, - "SalePayment": { - "type": "object", - "properties": { - "token": { - "description": "The type of payment token", - "oneOf": [ - { - "$ref": "#/components/schemas/ActivityToken" - }, - { - "$ref": "#/components/schemas/ActivityNativeToken" - } - ] - }, - "price_excluding_fees": { - "description": "The base price of the sale not including any fees", - "type": "string", - "example": "180" - }, - "price_including_fees": { - "description": "The total price of the sale. Includes the sum of all fees", - "type": "string", - "example": "200" - }, - "fees": { - "description": "The fees associated with this sale", - "type": "array", - "items": { - "$ref": "#/components/schemas/SaleFee" - }, - "example": [ - { - "address": "0xB0F3749458169B7Ad51B5503CC3649DE55c2D0D2", - "amount": "20", - "type": "ROYALTY" - } - ], - "minItems": 0 - } - }, - "required": [ - "token", - "price_excluding_fees", - "price_including_fees", - "fees" - ] - }, - "SaleFee": { - "type": "object", - "properties": { - "amount": { - "type": "string", - "description": "Fee payable to recipient upon settlement", - "example": "200" - }, - "type": { - "type": "string", - "description": "Fee type", - "example": "ROYALTY", - "enum": [ - "ROYALTY" - ] - }, - "recipient": { - "type": "string", - "description": "Wallet address of fee recipient", - "example": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92233" - } - } - }, - "NFTSale": { - "description": "The NFT Sale activity details", - "type": "object", - "properties": { - "order_id": { - "description": "The id of order", - "type": "string", - "example": "ARZ3NDEKTSV4RRFFQ69G5FAV" - }, - "to": { - "description": "The account address of buyer", - "type": "string", - "example": "0xe9b00a87700f660e46b6f5deaa1232836bcc07d3" - }, - "from": { - "description": "The account address of seller", - "type": "string", - "example": "0xbD6cFcf93474De653d7B42b346c7c25d1F9c559C" - }, - "asset": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ActivityNFT" - } - }, - "payment": { - "$ref": "#/components/schemas/SalePayment" - } - }, - "required": [ - "order_id", - "to", - "from", - "asset", - "payment" - ] - }, - "ActivityDetails": { - "description": "The activity details", - "oneOf": [ - { - "$ref": "#/components/schemas/Mint" - }, - { - "$ref": "#/components/schemas/Burn" - }, - { - "$ref": "#/components/schemas/Transfer" - }, - { - "$ref": "#/components/schemas/NFTSale" - }, - { - "$ref": "#/components/schemas/Deposit" - }, - { - "$ref": "#/components/schemas/Withdrawal" - } - ] - }, - "BlockchainMetadata": { - "description": "The metadata related to blockchain transaction", - "nullable": true, - "type": "object", - "properties": { - "transaction_hash": { - "type": "string", - "description": "The transaction hash of the activity", - "example": "0x68d9eac5e3b3c3580404989a4030c948a78e1b07b2b5ea5688d8c38a6c61c93e" - }, - "block_number": { - "description": "EVM block number (uint64 as string)", - "type": "string", - "example": "1" - }, - "transaction_index": { - "description": "Transaction index in a block (uint32 as string)", - "type": "string", - "example": "1" - }, - "log_index": { - "description": "The log index of activity in a block (uint32 as string)", - "type": "string", - "nullable": true, - "example": "1" - } - }, - "required": [ - "transaction_hash", - "block_number", - "transaction_index", - "log_index" - ] - }, - "Activity": { - "type": "object", - "properties": { - "id": { - "$ref": "#/components/schemas/ActivityID" - }, - "chain": { - "$ref": "#/components/schemas/Chain" - }, - "type": { - "$ref": "#/components/schemas/ActivityType" - }, - "details": { - "$ref": "#/components/schemas/ActivityDetails" - }, - "updated_at": { - "type": "string", - "description": "The time activity was updated at", - "format": "date-time", - "example": "2022-08-16T17:43:26.991388Z" - }, - "indexed_at": { - "type": "string", - "description": "The time activity was indexed", - "format": "date-time", - "example": "2022-08-16T17:43:26.991388Z" - }, - "blockchain_metadata": { - "$ref": "#/components/schemas/BlockchainMetadata" - } - }, - "required": [ - "id", - "chain", - "type", - "details", - "indexed_at", - "updated_at", - "blockchain_metadata" - ] - }, - "ListActivitiesResult": { - "type": "object", - "description": "List activities response", - "properties": { - "result": { - "type": "array", - "description": "List of activities", - "items": { - "$ref": "#/components/schemas/Activity" - } - }, - "page": { - "$ref": "#/components/schemas/Page" - } - }, - "required": [ - "result", - "page" - ] - }, - "GetActivityResult": { - "type": "object", - "description": "Single activity", - "properties": { - "result": { - "$ref": "#/components/schemas/Activity" - } - }, - "required": [ - "result" - ] - }, - "Collection": { - "type": "object", - "properties": { - "chain": { - "$ref": "#/components/schemas/Chain" - }, - "name": { - "type": "string", - "nullable": true, - "description": "The name of the collection", - "example": "0x8a90cab2b38dba80c64b7734e58ee1db38b8992e" - }, - "symbol": { - "type": "string", - "nullable": true, - "description": "The symbol of contract", - "example": "BASP" - }, - "contract_type": { - "$ref": "#/components/schemas/CollectionContractType" - }, - "contract_address": { - "type": "string", - "description": "The address of the contract", - "example": "0x8a90cab2b38dba80c64b7734e58ee1db38b8992e" - }, - "description": { - "type": "string", - "nullable": true, - "description": "The description of collection", - "example": "Some description" - }, - "image": { - "type": "string", - "description": "The url of the collection image", - "example": "https://some-url", - "nullable": true - }, - "external_link": { - "type": "string", - "description": "The url of external link", - "example": "https://some-url", - "nullable": true - }, - "contract_uri": { - "type": "string", - "description": "The uri for the metadata of the collection", - "example": "https://some-url", - "nullable": true - }, - "base_uri": { - "type": "string", - "nullable": true, - "description": "The metadata uri for nft", - "example": "https://some-url" - }, - "verification_status": { - "$ref": "#/components/schemas/AssetVerificationStatus" - }, - "indexed_at": { - "type": "string", - "format": "date-time", - "example": "2022-08-16T17:43:26.991388Z", - "description": "When the collection was first indexed" - }, - "updated_at": { - "type": "string", - "format": "date-time", - "example": "2022-08-16T17:43:26.991388Z", - "description": "When the collection was last updated" - }, - "metadata_synced_at": { - "type": "string", - "format": "date-time", - "nullable": true, - "example": "2022-08-16T17:43:26.991388Z", - "description": "When the collection metadata was last synced" - } - }, - "required": [ - "chain", - "name", - "symbol", - "contract_type", - "contract_address", - "description", - "image", - "external_link", - "base_uri", - "metadata_uri", - "indexed_at", - "updated_at", - "metadata_synced_at", - "verification_status" - ] - }, - "ListCollectionsResult": { - "type": "object", - "properties": { - "result": { - "description": "List of collections", - "type": "array", - "items": { - "$ref": "#/components/schemas/Collection" - } - }, - "page": { - "$ref": "#/components/schemas/Page" - } - }, - "required": [ - "result", - "page" - ] - }, - "GetCollectionResult": { - "type": "object", - "description": "Single Collection", - "properties": { - "result": { - "$ref": "#/components/schemas/Collection" - } - }, - "required": [ - "result" - ] - }, - "GetMetadataResult": { - "type": "object", - "description": "Single metadata", - "properties": { - "result": { - "$ref": "#/components/schemas/Metadata" - } - }, - "required": [ - "result" - ] - }, - "ListMetadataResult": { - "type": "object", - "properties": { - "result": { - "description": "List of metadata", - "type": "array", - "items": { - "$ref": "#/components/schemas/Metadata" - } - }, - "page": { - "$ref": "#/components/schemas/Page" - } - }, - "required": [ - "result", - "page" - ] - }, - "Metadata": { - "type": "object", - "properties": { - "id": { - "$ref": "#/components/schemas/MetadataID" - }, - "chain": { - "$ref": "#/components/schemas/Chain" - }, - "contract_address": { - "type": "string", - "example": "0x8a90cab2b38dba80c64b7734e58ee1db38b8992e", - "description": "The contract address of the metadata" - }, - "created_at": { - "type": "string", - "format": "date-time", - "example": "2022-08-16T17:43:26.991388Z", - "description": "When the metadata was created" - }, - "updated_at": { - "type": "string", - "format": "date-time", - "nullable": true, - "description": "When the metadata was last updated", - "example": "2022-08-16T17:43:26.991388Z" - }, - "name": { - "type": "string", - "nullable": true, - "example": "Sword", - "description": "The name of the NFT" - }, - "description": { - "type": "string", - "nullable": true, - "example": "2022-08-16T17:43:26.991388Z", - "description": "The description of the NFT" - }, - "image": { - "type": "string", - "nullable": true, - "description": "The image url of the NFT", - "example": "https://some-url" - }, - "external_url": { - "type": "string", - "nullable": true, - "description": "The external website link of NFT", - "example": "https://some-url" - }, - "animation_url": { - "type": "string", - "nullable": true, - "description": "The animation url of the NFT", - "example": "https://some-url" - }, - "youtube_url": { - "type": "string", - "nullable": true, - "description": "The youtube URL of NFT", - "example": "https://some-url" - }, - "attributes": { - "type": "array", - "description": "List of Metadata attributes", - "nullable": true, - "items": { - "$ref": "#/components/schemas/NFTMetadataAttribute" - } - } - }, - "required": [ - "id", - "chain", - "contract_address", - "created_at", - "updated_at", - "name", - "description", - "image", - "external_link", - "animation_url", - "youtube_url", - "attributes" - ] - }, - "RefreshMetadataByIDRequest": { - "type": "object", - "description": "Request body for refreshing metadata by id. Total size of this list should not exceed 228 KiB", - "properties": { - "metadata": { - "type": "array", - "maxItems": 10, - "minItems": 1, - "items": { - "$ref": "#/components/schemas/RefreshMetadataByID" - } - } - }, - "required": [ - "metadata" - ] - }, - "NFT": { - "type": "object", - "properties": { - "chain": { - "$ref": "#/components/schemas/Chain" - }, - "token_id": { - "type": "string", - "example": "1", - "description": "An `uint256` token id as string" - }, - "contract_address": { - "type": "string", - "example": "0x8a90cab2b38dba80c64b7734e58ee1db38b8992e", - "description": "The contract address of the NFT" - }, - "contract_type": { - "$ref": "#/components/schemas/NFTContractType" - }, - "indexed_at": { - "type": "string", - "format": "date-time", - "example": "2022-08-16T17:43:26.991388Z", - "description": "When the NFT was first indexed" - }, - "updated_at": { - "type": "string", - "format": "date-time", - "example": "2022-08-16T17:43:26.991388Z", - "description": "When the NFT owner was last updated" - }, - "metadata_synced_at": { - "type": "string", - "format": "date-time", - "nullable": true, - "description": "When NFT metadata was last synced", - "example": "2022-08-16T17:43:26.991388Z" - }, - "metadata_id": { - "type": "string", - "format": "uuid", - "nullable": true, - "description": "The id of the metadata of this NFT", - "example": "ae83bc80-4dd5-11ee-be56-0242ac120002" - }, - "name": { - "type": "string", - "nullable": true, - "example": "Sword", - "description": "The name of the NFT" - }, - "description": { - "type": "string", - "nullable": true, - "example": "2022-08-16T17:43:26.991388Z", - "description": "The description of the NFT" - }, - "image": { - "type": "string", - "nullable": true, - "description": "The image url of the NFT", - "example": "https://some-url" - }, - "external_link": { - "deprecated": true, - "type": "string", - "nullable": true, - "description": "(deprecated - use external_url instead) The external website link of NFT", - "example": "https://some-url" - }, - "external_url": { - "type": "string", - "nullable": true, - "description": "The external website link of NFT", - "example": "https://some-url" - }, - "animation_url": { - "type": "string", - "nullable": true, - "description": "The animation url of the NFT", - "example": "https://some-url" - }, - "youtube_url": { - "type": "string", - "nullable": true, - "description": "The youtube URL of NFT", - "example": "https://some-url" - }, - "attributes": { - "type": "array", - "description": "List of NFT Metadata attributes", - "items": { - "$ref": "#/components/schemas/NFTMetadataAttribute" - } - }, - "total_supply": { - "type": "string", - "nullable": true, - "description": "The total supply of NFT", - "example": "100" - } - }, - "required": [ - "chain", - "token_id", - "contract_address", - "indexed_at", - "updated_at", - "metadata_synced_at", - "name", - "description", - "image", - "external_link", - "external_url", - "animation_url", - "youtube_url", - "attributes", - "contract_type" - ] - }, - "NFTWithBalance": { - "type": "object", - "properties": { - "chain": { - "$ref": "#/components/schemas/Chain" - }, - "token_id": { - "type": "string", - "example": "1", - "description": "An `uint256` token id as string" - }, - "contract_address": { - "type": "string", - "example": "0x8a90cab2b38dba80c64b7734e58ee1db38b8992e", - "description": "The contract address of the NFT" - }, - "contract_type": { - "$ref": "#/components/schemas/NFTContractType" - }, - "indexed_at": { - "type": "string", - "format": "date-time", - "example": "2022-08-16T17:43:26.991388Z", - "description": "When the NFT was first indexed" - }, - "updated_at": { - "type": "string", - "format": "date-time", - "example": "2022-08-16T17:43:26.991388Z", - "description": "When the NFT owner was last updated" - }, - "metadata_synced_at": { - "type": "string", - "format": "date-time", - "nullable": true, - "description": "When NFT metadata was last synced", - "example": "2022-08-16T17:43:26.991388Z" - }, - "metadata_id": { - "type": "string", - "format": "uuid", - "nullable": true, - "description": "The id of the metadata of this NFT", - "example": "ae83bc80-4dd5-11ee-be56-0242ac120002" - }, - "name": { - "type": "string", - "nullable": true, - "description": "The name of the NFT", - "example": "Sword" - }, - "description": { - "type": "string", - "nullable": true, - "description": "The description of the NFT", - "example": "This is a super awesome sword" - }, - "image": { - "type": "string", - "nullable": true, - "description": "The image url of the NFT", - "example": "https://some-url" - }, - "external_link": { - "type": "string", - "nullable": true, - "description": "The external website link of NFT", - "example": "https://some-url" - }, - "animation_url": { - "type": "string", - "nullable": true, - "description": "The animation url of the NFT", - "example": "https://some-url" - }, - "youtube_url": { - "type": "string", - "nullable": true, - "description": "The youtube URL of NFT", - "example": "https://some-url" - }, - "attributes": { - "type": "array", - "description": "List of Metadata attributes", - "nullable": false, - "items": { - "$ref": "#/components/schemas/NFTMetadataAttribute" - } - }, - "balance": { - "type": "string", - "nullable": false, - "description": "The amount of this NFT this account owns", - "example": "11" - } - }, - "required": [ - "chain", - "token_id", - "contract_address", - "indexed_at", - "updated_at", - "metadata_synced_at", - "name", - "description", - "image", - "external_link", - "animation_url", - "youtube_url", - "balance", - "contract_type", - "attributes" - ] - }, - "NFTMetadataAttribute": { - "type": "object", - "properties": { - "display_type": { - "description": "Display type for this attribute", - "type": "string", - "nullable": true, - "enum": [ - "number", - "boost_percentage", - "boost_number", - "date" - ], - "example": "number" - }, - "trait_type": { - "description": "The metadata trait type", - "type": "string", - "example": "Aqua Power" - }, - "value": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "number" - }, - { - "type": "boolean" - } - ], - "description": "The metadata trait value", - "example": "Happy" - } - }, - "required": [ - "trait_type", - "value" - ] - }, - "ChainName": { - "type": "string", - "description": "The name of chain", - "example": "imtbl-zkevm-testnet" - }, - "ActivityID": { - "description": "Activity id in UUIDv4 format", - "type": "string", - "format": "uuid", - "example": "4e28df8d-f65c-4c11-ba04-6a9dd47b179b" - }, - "MetadataID": { - "description": "Metadata id in UUIDv4 format", - "type": "string", - "format": "uuid", - "example": "4e28df8d-f65c-4c11-ba04-6a9dd47b179b" - }, - "CollectionContractType": { - "description": "The collection contract type", - "type": "string", - "enum": [ - "ERC721", - "ERC1155" - ] - }, - "NFTContractType": { - "description": "The contract type for an NFT", - "type": "string", - "enum": [ - "ERC721", - "ERC1155" - ] - }, - "TokenContractType": { - "description": "The contract type for a token", - "type": "string", - "enum": [ - "ERC20" - ] - }, - "AssetVerificationStatus": { - "description": "The verification status for a given contract", - "type": "string", - "enum": [ - "verified", - "unverified", - "spam", - "inactive" - ] - }, - "VerificationRequestStatus": { - "description": "The status of the verification request", - "type": "string", - "enum": [ - "completed", - "pending" - ] - }, - "GetNFTResult": { - "type": "object", - "description": "Single NFT", - "properties": { - "result": { - "$ref": "#/components/schemas/NFT" - } - }, - "required": [ - "result" - ] - }, - "ListNFTsResult": { - "type": "object", - "properties": { - "result": { - "description": "List of NFTs", - "type": "array", - "items": { - "$ref": "#/components/schemas/NFT" - } - }, - "page": { - "$ref": "#/components/schemas/Page" - } - }, - "required": [ - "result", - "page" - ] - }, - "ListNFTsByOwnerResult": { - "type": "object", - "properties": { - "result": { - "description": "List of NFTs by owner", - "type": "array", - "items": { - "$ref": "#/components/schemas/NFTWithBalance" - } - }, - "page": { - "$ref": "#/components/schemas/Page" - } - }, - "required": [ - "result", - "page" - ] - }, - "NFTOwner": { - "type": "object", - "properties": { - "chain": { - "$ref": "#/components/schemas/Chain" - }, - "contract_address": { - "type": "string", - "description": "The address of NFT contract", - "example": "0x5a019874f4fae314b0eaa4606be746366e661306" - }, - "token_id": { - "type": "string", - "description": "An `uint256` token id as string", - "example": "1" - }, - "account_address": { - "type": "string", - "description": "The account address of the owner of the NFT", - "example": "0x5a019874f4fae314b0eaa4606be746366e661306" - }, - "balance": { - "type": "string", - "description": "The amount of owned tokens (uint256 as string)", - "example": "1" - }, - "updated_at": { - "type": "string", - "format": "date-time", - "example": "2022-08-16T17:43:26.991388Z", - "description": "When the NFT owner was last updated" - } - }, - "required": [ - "chain", - "contract_address", - "token_id", - "account_address", - "balance" - ] - }, - "NFTWithOwner": { - "type": "object", - "properties": { - "chain": { - "$ref": "#/components/schemas/Chain" - }, - "contract_address": { - "type": "string", - "description": "The address of NFT contract", - "example": "0x5a019874f4fae314b0eaa4606be746366e661306" - }, - "token_id": { - "type": "string", - "description": "An `uint256` token id as string", - "example": "1" - }, - "account_address": { - "type": "string", - "description": "The account address of the owner of the NFT", - "example": "0x5a019874f4fae314b0eaa4606be746366e661306" - }, - "balance": { - "type": "string", - "description": "The amount of owned tokens (uint256 as string)", - "example": "1" - }, - "updated_at": { - "type": "string", - "format": "date-time", - "example": "2022-08-16T17:43:26.991388Z", - "description": "When the owner last changed for the given NFT" - } - }, - "required": [ - "chain", - "contract_address", - "token_id", - "account_address", - "balance", - "updated_at" - ] - }, - "GetTokenResult": { - "type": "object", - "description": "Single Token", - "properties": { - "result": { - "$ref": "#/components/schemas/Token" - } - }, - "required": [ - "result" - ] - }, - "ListNFTOwnersResult": { - "type": "object", - "properties": { - "result": { - "description": "List of nft owners", - "type": "array", - "items": { - "$ref": "#/components/schemas/NFTOwner" - } - }, - "page": { - "$ref": "#/components/schemas/Page" - } - }, - "required": [ - "result", - "page" - ] - }, - "ListCollectionOwnersResult": { - "type": "object", - "properties": { - "result": { - "description": "List of NFT owners", - "type": "array", - "items": { - "$ref": "#/components/schemas/NFTWithOwner" - } - }, - "page": { - "$ref": "#/components/schemas/Page" - } - }, - "required": [ - "result", - "page" - ] - }, - "ChainWithDetails": { - "allOf": [ - { - "$ref": "#/components/schemas/Chain" - }, - { - "type": "object", - "properties": { - "rpc_url": { - "type": "string", - "nullable": true, - "description": "URL for RPC node" - }, - "operator_allowlist_address": { - "type": "string", - "nullable": true, - "description": "The address of the Operator Allowlist - https://docs.immutable.com/products/zkevm/minting/royalties/allowlist-spec/" - }, - "minter_address": { - "type": "string", - "nullable": true, - "description": "The address of the minter used in the Minting API - https://docs.immutable.com/products/zkEVM/minting/minting-api#minting-api-prerequisites" - } - }, - "required": [ - "rpc_url", - "operator_allowlist_address", - "minter_address" - ] - } - ] - }, - "Chain": { - "type": "object", - "description": "The chain details", - "properties": { - "id": { - "type": "string", - "description": "The id of chain", - "example": "eip155:13372" - }, - "name": { - "type": "string", - "description": "The name of chain", - "example": "imtbl-zkevm-testnet" - } - }, - "required": [ - "id", - "name" - ] - }, - "ListChainsResult": { - "type": "object", - "properties": { - "result": { - "type": "array", - "description": "List of chains", - "items": { - "$ref": "#/components/schemas/ChainWithDetails" - } - }, - "page": { - "$ref": "#/components/schemas/Page" - } - }, - "required": [ - "result", - "page" - ] - }, - "ListTokensResult": { - "type": "object", - "properties": { - "result": { - "type": "array", - "description": "List of tokens", - "items": { - "$ref": "#/components/schemas/Token" - } - }, - "page": { - "$ref": "#/components/schemas/Page" - } - }, - "required": [ - "result", - "page" - ] - }, - "RefreshMetadataByID": { - "allOf": [ - { - "$ref": "#/components/schemas/RefreshableNFTAttributes" - }, - { - "type": "object", - "properties": { - "metadata_id": { - "$ref": "#/components/schemas/MetadataID" - } - }, - "required": [ - "metadata_id" - ] - } - ] - }, - "RefreshMetadataByTokenID": { - "allOf": [ - { - "$ref": "#/components/schemas/RefreshableNFTAttributes" - }, - { - "type": "object", - "properties": { - "token_id": { - "type": "string", - "description": "An `uint256` token id as string", - "example": "1" - } - }, - "required": [ - "token_id" - ] - } - ] - }, - "RefreshableNFTAttributes": { - "allOf": [ - { - "$ref": "#/components/schemas/NFTMetadataRequest" - } - ], - "required": [ - "name", - "description", - "image", - "external_url", - "animation_url", - "youtube_url", - "attributes" - ] - }, - "NFTMetadataRequest": { - "type": "object", - "description": "The NFT metadata. Total size of this object should not exceed 16 KiB", - "properties": { - "name": { - "type": "string", - "nullable": true, - "example": "Sword", - "description": "The name of the NFT" - }, - "description": { - "type": "string", - "nullable": true, - "example": "2022-08-16T17:43:26.991388Z", - "description": "The description of the NFT" - }, - "image": { - "type": "string", - "nullable": true, - "description": "The image url of the NFT", - "example": "https://some-url" - }, - "external_url": { - "type": "string", - "nullable": true, - "description": "The external link of the NFT", - "example": "https://some-url" - }, - "animation_url": { - "type": "string", - "nullable": true, - "description": "The animation url of the NFT", - "example": "https://some-url" - }, - "youtube_url": { - "type": "string", - "nullable": true, - "description": "The youtube link of the NFT", - "example": "https://some-url" - }, - "attributes": { - "type": "array", - "description": "List of Metadata attributes", - "nullable": true, - "items": { - "$ref": "#/components/schemas/NFTMetadataAttribute" - } - } - } - }, - "RefreshNFTMetadataByTokenIDRequest": { - "type": "object", - "properties": { - "nft_metadata": { - "type": "array", - "description": "List of nft metadata to be refreshed. Total size of the list should not exceed 228 KiB", - "maxItems": 250, - "minItems": 1, - "items": { - "$ref": "#/components/schemas/RefreshMetadataByTokenID" - } - } - }, - "required": [ - "nft_metadata" - ] - }, - "Token": { - "type": "object", - "properties": { - "chain": { - "$ref": "#/components/schemas/Chain" - }, - "contract_address": { - "type": "string", - "description": "The address of token contract", - "example": "0xc344c05eef8876e517072f879dae8905aa2b956b" - }, - "root_contract_address": { - "type": "string", - "description": "The address of root token contract", - "example": "0x43e60b30d5bec48c0f5890e3d1e9f1b1296bb4aa", - "nullable": true - }, - "root_chain_id": { - "type": "string", - "description": "The id of the root chain for a bridged token", - "example": "eip155:1", - "nullable": true - }, - "bridge_used": { - "type": "string", - "description": "The name of the bridge, for bridged tokens only", - "example": "axelar", - "nullable": true - }, - "symbol": { - "type": "string", - "description": "The symbol of token", - "example": "AAA", - "nullable": true - }, - "decimals": { - "type": "integer", - "description": "The decimals of token", - "example": 18, - "nullable": true - }, - "image_url": { - "type": "string", - "description": "The image url of token", - "example": "https://some-url", - "nullable": true - }, - "name": { - "type": "string", - "description": "The name of token", - "example": "Token A", - "nullable": true - }, - "verification_status": { - "$ref": "#/components/schemas/AssetVerificationStatus" - }, - "updated_at": { - "type": "string", - "format": "date-time", - "example": "2022-08-16T17:43:26.991388Z", - "description": "When the collection was last updated" - }, - "is_canonical": { - "type": "boolean", - "example": true, - "description": "Indicates whether the token is canonical or not" - } - }, - "required": [ - "chain", - "contract_address", - "root_contract_address", - "symbol", - "decimals", - "image_url", - "name", - "updated_at", - "verification_status", - "is_canonical" - ] - }, - "CollectionMetadata": { - "type": "object", - "properties": { - "name": { - "type": "string", - "nullable": true, - "description": "The name of the collection", - "example": "Gigantic Lizards" - }, - "symbol": { - "type": "string", - "nullable": true, - "description": "The symbol of contract", - "example": "GLZ" - }, - "description": { - "type": "string", - "nullable": true, - "description": "The description of collection", - "example": "This is the Gigantic Lizards collection" - }, - "image": { - "type": "string", - "description": "The url of the collection image", - "example": "https://some-url", - "nullable": true - }, - "external_link": { - "type": "string", - "description": "The url of external link", - "example": "https://some-url", - "nullable": true - }, - "contract_uri": { - "type": "string", - "description": "The uri for the metadata of the collection", - "example": "https://some-url", - "nullable": true - }, - "base_uri": { - "type": "string", - "nullable": true, - "description": "The metadata uri for nft", - "example": "https://some-url" - } - }, - "required": [ - "name", - "image", - "symbol", - "description", - "base_uri", - "external_link", - "contract_uri" - ] - }, - "RefreshCollectionMetadataRequest": { - "type": "object", - "properties": { - "collection_metadata": { - "$ref": "#/components/schemas/CollectionMetadata" - } - }, - "required": [ - "collection_metadata" - ] - }, - "RefreshCollectionMetadataResult": { - "type": "object", - "properties": { - "contract_address": { - "type": "string" - }, - "chain": { - "$ref": "#/components/schemas/Chain" - }, - "collection_metadata": { - "$ref": "#/components/schemas/CollectionMetadata" - } - }, - "required": [ - "contract_address", - "chain", - "collection_metadata" - ] - }, - "MetadataRefreshRateLimitResult": { - "type": "object", - "properties": { - "imx_refreshes_limit": { - "type": "string" - }, - "imx_refresh_limit_reset": { - "type": "string" - }, - "imx_remaining_refreshes": { - "type": "string" - }, - "retry_after": { - "type": "string" - } - }, - "required": [ - "imx_refreshes_limit", - "imx_refresh_limit_reset", - "imx_remaining_refreshes", - "retry_after" - ] - }, - "CreateMintRequestRequest": { - "type": "object", - "properties": { - "assets": { - "type": "array", - "maxItems": 100, - "minItems": 1, - "description": "List of nft to be minted", - "items": { - "$ref": "#/components/schemas/MintAsset" - } - } - }, - "required": [ - "assets" - ] - }, - "VerificationRequestInternal": { - "description": "The verification request (internal)", - "allOf": [ - { - "$ref": "#/components/schemas/VerificationRequest" - }, - { - "type": "object", - "properties": { - "org_tier": { - "$ref": "#/components/schemas/OrganisationTier" - } - }, - "required": [ - "org_tier" - ] - } - ] - }, - "VerificationRequest": { - "description": "The verification request", - "type": "object", - "properties": { - "id": { - "description": "The id of the verification request", - "type": "string", - "format": "uuid", - "example": "4e28df8d-f65c-4c11-ba04-6a9dd47b179b" - }, - "chain": { - "$ref": "#/components/schemas/Chain" - }, - "contract_address": { - "description": "The contract address", - "type": "string", - "example": "0x8a90cab2b38dba80c64b7734e58ee1db38b8992e" - }, - "org_name": { - "description": "The name of the organisation associated with this contract", - "nullable": true, - "type": "string", - "example": "Immutable" - }, - "name": { - "type": "string", - "nullable": true, - "description": "The name of the collection", - "example": "0x8a90cab2b38dba80c64b7734e58ee1db38b8992e" - }, - "symbol": { - "type": "string", - "nullable": true, - "description": "The symbol of contract", - "example": "BASP" - }, - "description": { - "type": "string", - "nullable": true, - "description": "The description of collection", - "example": "Some description" - }, - "org_id": { - "type": "string", - "nullable": true, - "description": "The id of the organisation associated with this contract", - "example": "753da67a-5d3b-42c7-b87a-eba3d17a6362" - }, - "requester_email": { - "type": "string", - "nullable": true, - "description": "The email address of the user who requested the contract to be verified", - "example": "user@immutable.com" - }, - "contract_type": { - "$ref": "#/components/schemas/VerificationRequestContractType" - }, - "verification_request_status": { - "$ref": "#/components/schemas/VerificationRequestStatus" - } - }, - "required": [ - "id", - "chain", - "contract_address", - "name", - "symbol", - "description", - "contract_type", - "org_name", - "org_id", - "requester_email", - "verification_request_status" - ] - }, - "VerificationRequestContractType": { - "description": "The contract type associated with the given request", - "type": "string", - "enum": [ - "ERC20", - "ERC721", - "ERC1155" - ] - }, - "OrganisationTier": { - "description": "The tier of the organisation", - "type": "string", - "nullable": true, - "enum": [ - "common", - "uncommon", - "rare", - "epic", - "legendary" - ] - }, - "MintAsset": { - "type": "object", - "properties": { - "reference_id": { - "type": "string", - "description": "The id of this asset in the system that originates the mint request", - "example": "67f7d464-b8f0-4f6a-9a3b-8d3cb4a21af0" - }, - "owner_address": { - "type": "string", - "description": "The address of the receiver", - "example": "0xc344c05eef8876e517072f879dae8905aa2b956b" - }, - "token_id": { - "type": "string", - "description": "An optional `uint256` token id as string. Required for ERC1155 collections.", - "example": "1", - "nullable": true - }, - "amount": { - "type": "string", - "description": "Optional mount of tokens to mint. Required for ERC1155 collections. ERC712 collections can omit this field or set it to 1", - "example": "1", - "nullable": true, - "minLength": 1 - }, - "metadata": { - "$ref": "#/components/schemas/NFTMetadataRequest" - } - }, - "required": [ - "reference_id", - "owner_address" - ] - }, - "CreateMintRequestResult": { - "type": "object", - "properties": { - "imx_mint_requests_limit": { - "type": "string" - }, - "imx_mint_requests_limit_reset": { - "type": "string" - }, - "imx_remaining_mint_requests": { - "type": "string" - }, - "imx_mint_requests_retry_after": { - "type": "string" - } - }, - "required": [ - "imx_mint_requests_limit", - "imx_mint_requests_limit_reset", - "imx_remaining_mint_requests", - "imx_mint_requests_retry_after" - ] - }, - "ListMintRequestsResult": { - "type": "object", - "description": "List mint requests", - "properties": { - "result": { - "type": "array", - "description": "List of mint requests", - "items": { - "$ref": "#/components/schemas/GetMintRequestResult" - } - }, - "page": { - "$ref": "#/components/schemas/Page" - } - }, - "required": [ - "result", - "page" - ] - }, - "GetMintRequestResult": { - "type": "object", - "properties": { - "chain": { - "$ref": "#/components/schemas/Chain" - }, - "collection_address": { - "type": "string", - "description": "The address of the contract", - "example": "0x8a90cab2b38dba80c64b7734e58ee1db38b8992e" - }, - "reference_id": { - "type": "string", - "description": "The reference id of this mint request" - }, - "owner_address": { - "type": "string", - "description": "The address of the owner of the NFT" - }, - "token_id": { - "type": "string", - "example": "1", - "nullable": true, - "description": "An `uint256` token id as string. Only available when the mint request succeeds" - }, - "amount": { - "type": "string", - "example": "1", - "nullable": true, - "description": "An `uint256` amount as string. Only relevant for mint requests on ERC1155 contracts" - }, - "activity_id": { - "type": "string", - "format": "uuid", - "example": "4e28df8d-f65c-4c11-ba04-6a9dd47b179b", - "nullable": true, - "description": "The id of the mint activity associated with this mint request" - }, - "transaction_hash": { - "type": "string", - "nullable": true, - "description": "The transaction hash of the activity", - "example": "0x68d9eac5e3b3c3580404989a4030c948a78e1b07b2b5ea5688d8c38a6c61c93e" - }, - "created_at": { - "type": "string", - "format": "date-time", - "example": "2022-08-16T17:43:26.991388Z", - "description": "When the mint request was created" - }, - "updated_at": { - "type": "string", - "format": "date-time", - "description": "When the mint request was last updated", - "example": "2022-08-16T17:43:26.991388Z" - }, - "error": { - "$ref": "#/components/schemas/MintRequestErrorMessage" - }, - "status": { - "$ref": "#/components/schemas/MintRequestStatus" - } - }, - "required": [ - "chain", - "collection_address", - "reference_id", - "owner_address", - "status", - "token_id", - "transaction_hash", - "error", - "created_at", - "updated_at" - ] - }, - "MintRequestErrorMessage": { - "type": "object", - "description": "The error details in case the mint request fails", - "nullable": true, - "properties": { - "message": { - "description": "An error message in case the mint request fails", - "type": "string" - } - } - }, - "MintRequestStatus": { - "description": "The status of the mint request", - "example": "pending", - "type": "string", - "enum": [ - "pending", - "succeeded", - "failed" - ] - }, - "OperatorAllowlistStatus": { - "description": "The status of a contract on the operator allowlist", - "example": "requested", - "type": "string", - "enum": [ - "requested", - "approved", - "rejected", - "removed", - "added" - ] - }, - "OperatorAllowlistStatusDetails": { - "description": "The operator allowlist status details", - "oneOf": [ - { - "$ref": "#/components/schemas/OperatorAllowlistStatusRequested" - }, - { - "$ref": "#/components/schemas/OperatorAllowlistStatusUpdated" - } - ] - }, - "OperatorAllowlistStatusRequested": { - "type": "object", - "description": "The request details", - "properties": { - "purpose": { - "description": "Reason this contract needs to be added", - "type": "string", - "example": "Custom crafting contract" - }, - "is_settlement_contract": { - "description": "Attestation of whether this contract is a settlement contract", - "type": "boolean", - "example": false - } - }, - "required": [ - "purpose", - "is_settlement_contract" - ] - }, - "OperatorAllowlistStatusUpdated": { - "type": "object", - "description": "The update details", - "properties": { - "reason": { - "description": "Why this action was performed", - "type": "string", - "example": "Contract meets expectations" - } - }, - "required": [ - "reason" - ] - }, - "OperatorAllowlistContractStatusInternal": { - "allOf": [ - { - "$ref": "#/components/schemas/OperatorAllowlistContractStatus" - }, - { - "type": "object", - "properties": { - "org_name": { - "description": "The name of the organisation associated with this contract", - "nullable": true, - "type": "string", - "example": "Immutable" - }, - "org_tier": { - "$ref": "#/components/schemas/OrganisationTier" - } - }, - "required": [ - "org_name", - "org_tier" - ] - } - ] - }, - "OperatorAllowlistContractStatus": { - "type": "object", - "properties": { - "chain": { - "$ref": "#/components/schemas/Chain" - }, - "contract_address": { - "type": "string", - "description": "The address of the contract", - "example": "0x8a90cab2b38dba80c64b7734e58ee1db38b8992e" - }, - "status": { - "$ref": "#/components/schemas/OperatorAllowlistStatus" - }, - "details": { - "$ref": "#/components/schemas/OperatorAllowlistStatusDetails" - }, - "created_at": { - "type": "string", - "format": "date-time", - "example": "2022-08-16T17:43:26.991388Z", - "description": "When the contract status was created" - }, - "created_by": { - "type": "string", - "format": "email", - "description": "Who created the status", - "example": "user@immutable.com" - } - }, - "required": [ - "chain", - "contract_address", - "status", - "details", - "created_at", - "created_by" - ] - }, - "Call": { - "type": "object", - "properties": { - "target_address": { - "$ref": "#/components/schemas/Address" - }, - "function_signature": { - "type": "string", - "description": "The function signature", - "example": "mint(address,uint256)" - }, - "function_args": { - "type": "array", - "items": { - "type": "string" - }, - "description": "The function arguments", - "example": [ - "0x8a90cab2b38dba80c64b7734e58ee1db38b8992e", - "1" - ] - } - }, - "required": [ - "target_address", - "function_signature", - "function_args" - ] - }, - "SignCraftingRequest": { - "type": "object", - "properties": { - "multi_caller": { - "type": "object", - "properties": { - "address": { - "$ref": "#/components/schemas/Address" - }, - "name": { - "type": "string", - "description": "The name of the multicaller contract", - "example": "Multicaller" - }, - "version": { - "type": "string", - "description": "The version of the multicaller contract", - "example": "1" - } - }, - "required": [ - "address", - "name", - "version" - ] - }, - "reference_id": { - "type": "string", - "description": "The id of this request in the system that originates the crafting request, specified as a 32 byte hex string", - "example": "67f7d464b8f04f6a9a3b8d3cb4a21af0" - }, - "calls": { - "type": "array", - "description": "The calls to be signed", - "items": { - "$ref": "#/components/schemas/Call" - }, - "minLength": 1, - "maxLength": 100 - }, - "expires_at": { - "type": "string", - "format": "date-time", - "description": "The expiration time of the request", - "example": "2022-08-16T17:43:26.991388Z" - } - }, - "required": [ - "multi_caller", - "reference_id", - "calls", - "expires_at" - ] - }, - "SignCraftingResult": { - "type": "object", - "properties": { - "signer_address": { - "$ref": "#/components/schemas/Address" - }, - "signature": { - "type": "string", - "description": "The signature of the request", - "example": "0x8a90cab2b38dba80c64b7734e58ee1db38b8992e" - } - }, - "required": [ - "signer_address", - "signature" - ] - }, - "BasicAPIError": { - "type": "object", - "properties": { - "message": { - "type": "string", - "description": "Error Message", - "example": "all fields must be provided" - }, - "link": { - "type": "string", - "description": "Link to IMX documentation that can help resolve this error", - "example": "https://docs.x.immutable.com/reference/#/" - }, - "trace_id": { - "type": "string", - "description": "Trace ID of the initial request", - "example": "e47634b79a5cd6894ddc9639ec4aad26" - } - }, - "required": [ - "message", - "link", - "trace_id" - ] - }, - "APIError400": { - "allOf": [ - { - "$ref": "#/components/schemas/BasicAPIError" - }, - { - "type": "object", - "properties": { - "code": { - "type": "string", - "description": "Error Code", - "enum": [ - "VALIDATION_ERROR" - ], - "example": "VALIDATION_ERROR" - }, - "details": { - "type": "object", - "nullable": true, - "description": "Additional details to help resolve the error" - } - }, - "required": [ - "code", - "details" - ] - } - ] - }, - "APIError404": { - "allOf": [ - { - "$ref": "#/components/schemas/BasicAPIError" - }, - { - "type": "object", - "properties": { - "code": { - "type": "string", - "description": "Error Code", - "enum": [ - "RESOURCE_NOT_FOUND" - ], - "example": "RESOURCE_NOT_FOUND" - }, - "details": { - "type": "object", - "nullable": true, - "description": "Additional details to help resolve the error" - } - }, - "required": [ - "code", - "details" - ] - } - ] - }, - "APIError500": { - "allOf": [ - { - "$ref": "#/components/schemas/BasicAPIError" - }, - { - "type": "object", - "properties": { - "code": { - "type": "string", - "description": "Error Code", - "enum": [ - "INTERNAL_SERVER_ERROR" - ], - "example": "INTERNAL_SERVER_ERROR" - }, - "details": { - "type": "object", - "nullable": true, - "description": "Additional details to help resolve the error" - } - }, - "required": [ - "code", - "details" - ] - } - ] - }, - "APIError401": { - "allOf": [ - { - "$ref": "#/components/schemas/BasicAPIError" - }, - { - "type": "object", - "properties": { - "code": { - "type": "string", - "description": "Error Code", - "enum": [ - "UNAUTHORISED_REQUEST" - ], - "example": "UNAUTHORISED_REQUEST" - }, - "details": { - "type": "object", - "nullable": true, - "description": "Additional details to help resolve the error" - } - }, - "required": [ - "code", - "details" - ] - } - ] - }, - "APIError403": { - "allOf": [ - { - "$ref": "#/components/schemas/BasicAPIError" - }, - { - "type": "object", - "properties": { - "code": { - "type": "string", - "description": "Error Code", - "enum": [ - "AUTHENTICATION_ERROR" - ], - "example": "AUTHENTICATION_ERROR" - }, - "details": { - "type": "object", - "nullable": true, - "description": "Additional details to help resolve the error" - } - }, - "required": [ - "code", - "details" - ] - } - ] - }, - "APIError429": { - "allOf": [ - { - "$ref": "#/components/schemas/BasicAPIError" - }, - { - "type": "object", - "properties": { - "code": { - "type": "string", - "description": "Error Code", - "enum": [ - "TOO_MANY_REQUESTS_ERROR" - ], - "example": "TOO_MANY_REQUESTS_ERROR" - }, - "details": { - "type": "object", - "nullable": true, - "description": "Additional details to help resolve the error" - } - }, - "required": [ - "code", - "details" - ] - } - ] - }, - "APIError409": { - "allOf": [ - { - "$ref": "#/components/schemas/BasicAPIError" - }, - { - "type": "object", - "properties": { - "code": { - "type": "string", - "description": "Error Code", - "enum": [ - "CONFLICT_ERROR" - ], - "example": "CONFLICT_ERROR" - }, - "details": { - "type": "object", - "nullable": true, - "description": "Additional details to help resolve the error", - "additionalProperties": true - } - }, - "required": [ - "code", - "details" - ] - } - ] - }, - "Item": { - "oneOf": [ - { - "$ref": "#/components/schemas/NativeItem" - }, - { - "$ref": "#/components/schemas/ERC20Item" - }, - { - "$ref": "#/components/schemas/ERC721Item" - }, - { - "$ref": "#/components/schemas/ERC1155Item" - }, - { - "$ref": "#/components/schemas/ERC721CollectionItem" - }, - { - "$ref": "#/components/schemas/ERC1155CollectionItem" - } - ], - "discriminator": { - "propertyName": "type", - "mapping": { - "NATIVE": "#/components/schemas/NativeItem", - "ERC20": "#/components/schemas/ERC20Item", - "ERC721": "#/components/schemas/ERC721Item", - "ERC1155": "#/components/schemas/ERC1155Item", - "ERC721_COLLECTION": "#/components/schemas/ERC721CollectionItem", - "ERC1155_COLLECTION": "#/components/schemas/ERC1155CollectionItem" - } - } - }, - "AssetCollectionItem": { - "oneOf": [ - { - "$ref": "#/components/schemas/ERC721CollectionItem" - }, - { - "$ref": "#/components/schemas/ERC1155CollectionItem" - } - ], - "discriminator": { - "propertyName": "type", - "mapping": { - "ERC721_COLLECTION": "#/components/schemas/ERC721CollectionItem", - "ERC1155_COLLECTION": "#/components/schemas/ERC1155CollectionItem" - } - } - }, - "FulfillmentDataRequest": { - "type": "object", - "properties": { - "order_id": { - "type": "string", - "format": "uuid" - }, - "taker_address": { - "type": "string", - "description": "Address of the intended account fulfilling the order", - "example": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "pattern": "^0x[a-fA-F0-9]{40}$" - }, - "fees": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Fee" - }, - "example": [ - { - "type": "TAKER_ECOSYSTEM", - "amount": "2250000000000000000", - "recipient_address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92233" - } - ], - "minItems": 0, - "maxItems": 2 - }, - "token_id": { - "type": "string", - "description": "Token ID for the ERC721 or ERC1155 token when fulfilling a collection order", - "example": "123", - "pattern": "\\d+" - } - }, - "required": [ - "order_id", - "fees", - "taker_address" - ] - }, - "FulfillableOrder": { - "type": "object", - "properties": { - "order": { - "$ref": "#/components/schemas/Order" - }, - "token_id": { - "type": "string", - "description": "Token ID for the ERC721 or ERC1155 token when fulfilling a collection order", - "example": "123", - "pattern": "\\d+" - }, - "extra_data": { - "type": "string" - } - }, - "required": [ - "extra_data", - "order" - ] - }, - "UnfulfillableOrder": { - "type": "object", - "properties": { - "order_id": { - "type": "string", - "description": "OrderID for the requested but unfulfillable order", - "example": "7df3e99e-f7b3-459c-bef6-ffb66a18bb59" - }, - "token_id": { - "type": "string", - "description": "Token ID for the ERC721 or ERC1155 token when fulfilling a collection order", - "example": "123", - "pattern": "\\d+" - }, - "reason": { - "type": "string", - "description": "Nullable string containing error reason if the signing is unsuccessful for the order", - "example": "Invalid order status INACTIVE for order 7df3e99e-f7b3-459c-bef6-ffb66a18bb59" - } - }, - "required": [ - "reason", - "order_id" - ] - }, - "NativeItem": { - "type": "object", - "properties": { - "type": { - "type": "string", - "description": "Token type user is offering, which in this case is the native IMX token", - "example": "NATIVE", - "enum": [ - "NATIVE" - ] - }, - "amount": { - "type": "string", - "description": "A string representing the price at which the user is willing to sell the token. This value is provided in the smallest unit of the token (e.g., wei for Ethereum).", - "example": "9750000000000000000", - "pattern": "\\d+" - } - }, - "required": [ - "type", - "amount" - ] - }, - "ERC20Item": { - "type": "object", - "properties": { - "type": { - "type": "string", - "description": "Token type user is offering, which in this case is ERC20", - "example": "ERC20", - "enum": [ - "ERC20" - ] - }, - "contract_address": { - "type": "string", - "description": "Address of ERC20 token", - "example": "0x0165878A594ca255338adfa4d48449f69242Eb8F", - "pattern": "^0x[a-fA-F0-9]{40}$" - }, - "amount": { - "type": "string", - "description": "A string representing the price at which the user is willing to sell the token. This value is provided in the smallest unit of the token (e.g., wei for Ethereum).", - "example": "9750000000000000000", - "pattern": "\\d+" - } - }, - "required": [ - "type", - "contract_address", - "amount" - ] - }, - "ERC721Item": { - "type": "object", - "properties": { - "type": { - "type": "string", - "description": "Token type user is offering, which in this case is ERC721", - "example": "ERC721", - "enum": [ - "ERC721" - ] - }, - "contract_address": { - "type": "string", - "description": "Address of ERC721 token", - "example": "0x692edAd005237c7E737bB2c0F3D8ccCc10D3479E", - "pattern": "^0x[a-fA-F0-9]{40}$" - }, - "token_id": { - "type": "string", - "description": "ID of ERC721 token", - "example": "1", - "pattern": "\\d+" - } - }, - "required": [ - "type", - "contract_address", - "token_id" - ] - }, - "ERC1155Item": { - "type": "object", - "properties": { - "type": { - "type": "string", - "description": "Token type user is offering, which in this case is ERC1155", - "example": "ERC1155", - "enum": [ - "ERC1155" - ] - }, - "contract_address": { - "type": "string", - "description": "Address of ERC1155 token", - "example": "0x692edAd005237c7E737bB2c0F3D8ccCc10D3479E", - "pattern": "^0x[a-fA-F0-9]{40}$" - }, - "token_id": { - "type": "string", - "description": "ID of ERC1155 token", - "example": "1", - "pattern": "\\d+" - }, - "amount": { - "type": "string", - "description": "A string representing the price at which the user is willing to sell the token. This value is provided in the smallest unit of the token (e.g., wei for Ethereum).", - "example": "9750000000000000000", - "pattern": "\\d+" - } - }, - "required": [ - "type", - "contract_address", - "token_id", - "amount" - ] - }, - "ERC721CollectionItem": { - "type": "object", - "properties": { - "type": { - "type": "string", - "description": "Token type user is offering, which in this case is ERC721", - "example": "ERC721_COLLECTION", - "enum": [ - "ERC721_COLLECTION" - ] - }, - "contract_address": { - "type": "string", - "description": "Address of ERC721 collection", - "example": "0x692edAd005237c7E737bB2c0F3D8ccCc10D3479E", - "pattern": "^0x[a-fA-F0-9]{40}$" - }, - "amount": { - "type": "string", - "description": "A string representing the price at which the user is willing to sell the token. This value is provided in the smallest unit of the token (e.g., wei for Ethereum).", - "example": "9750000000000000000", - "pattern": "\\d+" - } - }, - "required": [ - "type", - "contract_address", - "amount" - ] - }, - "ERC1155CollectionItem": { - "type": "object", - "properties": { - "type": { - "type": "string", - "description": "Token type user is offering, which in this case is ERC1155", - "example": "ERC1155_COLLECTION", - "enum": [ - "ERC1155_COLLECTION" - ] - }, - "contract_address": { - "type": "string", - "description": "Address of ERC1155 collection", - "example": "0x692edAd005237c7E737bB2c0F3D8ccCc10D3479E", - "pattern": "^0x[a-fA-F0-9]{40}$" - }, - "amount": { - "type": "string", - "description": "A string representing the price at which the user is willing to sell the token. This value is provided in the smallest unit of the token (e.g., wei for Ethereum).", - "example": "9750000000000000000", - "pattern": "\\d+" - } - }, - "required": [ - "type", - "contract_address", - "amount" - ] - }, - "CancelOrdersRequestBody": { - "type": "object", - "properties": { - "account_address": { - "type": "string", - "description": "Address of the user initiating the cancel request", - "example": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "pattern": "^0x[a-fA-F0-9]{40}$" - }, - "orders": { - "type": "array", - "description": "List of order ids proposed for cancellation", - "items": { - "type": "string", - "format": "uuid" - }, - "uniqueItems": true, - "minItems": 1, - "maxItems": 20 - }, - "signature": { - "type": "string", - "description": "Signature generated by the user for the specific cancellation request", - "example": "0x12345" - } - }, - "required": [ - "account_address", - "orders", - "signature" - ], - "example": { - "account_address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "ids": [ - "018a8c71-d7e4-e303-a2ef-318871ef7756", - "238a8c71-d7e4-e303-a2ef-318871ef7778", - "458a8c71-d7e4-e303-a2ef-318871ef7790" - ], - "signature": 291 - } - }, - "CreateListingRequestBody": { - "type": "object", - "properties": { - "account_address": { - "type": "string", - "example": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "pattern": "^0x[a-fA-F0-9]{40}$" - }, - "order_hash": { - "type": "string", - "example": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" - }, - "buy": { - "type": "array", - "description": "Buy item for listing should either be NATIVE or ERC20 item", - "items": { - "$ref": "#/components/schemas/Item" - }, - "example": [ - { - "type": "NATIVE", - "amount": "9750000000000000000", - "contract_address": "0x0165878A594ca255338adfa4d48449f69242Eb8F" - } - ], - "minItems": 1, - "maxItems": 1 - }, - "fees": { - "type": "array", - "description": "Buy fees should only include maker marketplace fees and should be no more than two entries as more entires will incur more gas. It is best practice to have this as few as possible.", - "items": { - "$ref": "#/components/schemas/Fee" - }, - "example": [], - "minItems": 0, - "maxItems": 2 - }, - "end_at": { - "type": "string", - "description": "Time after which the Order is considered expired", - "format": "date-time", - "example": "2022-03-09T05:00:50.52Z" - }, - "protocol_data": { - "$ref": "#/components/schemas/ProtocolData" - }, - "salt": { - "type": "string", - "description": "A random value added to the create Order request", - "example": "12686911856931635052326433555881236148" - }, - "sell": { - "type": "array", - "description": "Sell item for listing should be an ERC721 item", - "items": { - "$ref": "#/components/schemas/Item" - }, - "example": [ - { - "type": "ERC721", - "contract_address": "0x692edAd005237c7E737bB2c0F3D8ccCc10D3479E", - "token_id": "1" - } - ], - "minItems": 1, - "maxItems": 1 - }, - "signature": { - "type": "string", - "description": "Digital signature generated by the user for the specific Order", - "example": "0x" - }, - "start_at": { - "type": "string", - "description": "Time after which Order is considered active", - "format": "date-time", - "example": "2022-03-09T05:00:50.52Z" - } - }, - "example": { - "account_address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "order_hash": "0x0821933d9391bc9bf11a6010fe84776c84b203abff0c1ad781fb4881409c8770", - "buy": [ - { - "type": "NATIVE", - "amount": "9750000000000000000", - "contract_address": "0x0165878A594ca255338adfa4d48449f69242Eb8F" - } - ], - "fees": [ - { - "type": "MAKER_ECOSYSTEM", - "amount": "2250000000000000000", - "recipient_address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92233" - } - ], - "end_at": "2022-03-10T05:00:50.52Z", - "protocol_data": { - "order_type": "FULL_RESTRICTED", - "counter": "1", - "zone_address": "0x12", - "seaport_address": "0x12", - "seaport_version": "1.4" - }, - "salt": "12686911856931635052326433555881236148", - "sell": [ - { - "type": "ERC721", - "contract_address": "0x692edAd005237c7E737bB2c0F3D8ccCc10D3479E", - "token_id": "1" - } - ], - "signature": "0x", - "start_at": "2022-03-09T05:00:50.52Z" - }, - "required": [ - "account_address", - "order_hash", - "buy", - "sell", - "fees", - "end_at", - "start_at", - "protocol_data", - "salt", - "signature" - ] - }, - "CreateBidRequestBody": { - "type": "object", - "properties": { - "account_address": { - "type": "string", - "example": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "pattern": "^0x[a-fA-F0-9]{40}$" - }, - "order_hash": { - "type": "string", - "example": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" - }, - "buy": { - "type": "array", - "description": "Buy item for bid should either be ERC721 or ERC1155 item", - "items": { - "$ref": "#/components/schemas/Item" - }, - "example": [ - { - "type": "ERC721", - "contract_address": "0x692edAd005237c7E737bB2c0F3D8ccCc10D3479E", - "token_id": "1" - } - ], - "minItems": 1, - "maxItems": 1 - }, - "fees": { - "type": "array", - "description": "Buy fees should only include maker marketplace fees and should be no more than two entries as more entires will incur more gas. It is best practice to have this as few as possible.", - "items": { - "$ref": "#/components/schemas/Fee" - }, - "example": [], - "minItems": 0, - "maxItems": 2 - }, - "end_at": { - "type": "string", - "description": "Time after which the Order is considered expired", - "format": "date-time", - "example": "2022-03-09T05:00:50.52Z" - }, - "protocol_data": { - "$ref": "#/components/schemas/ProtocolData" - }, - "salt": { - "type": "string", - "description": "A random value added to the create Order request", - "example": "12686911856931635052326433555881236148" - }, - "sell": { - "type": "array", - "description": "Sell item for bid should be an ERC20 item", - "items": { - "$ref": "#/components/schemas/ERC20Item" - }, - "example": [ - { - "type": "ERC20", - "contract_address": "0x692edAd005237c7E737bB2c0F3D8ccCc10D3479E", - "amount": "9750000000000000000" - } - ], - "minItems": 1, - "maxItems": 1 - }, - "signature": { - "type": "string", - "description": "Digital signature generated by the user for the specific Order", - "example": "0x" - }, - "start_at": { - "type": "string", - "description": "Time after which Order is considered active", - "format": "date-time", - "example": "2022-03-09T05:00:50.52Z" - } - }, - "example": { - "account_address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "order_hash": "0x0821933d9391bc9bf11a6010fe84776c84b203abff0c1ad781fb4881409c8770", - "sell": [ - { - "type": "ERC20", - "amount": "9750000000000000000", - "contract_address": "0x0165878A594ca255338adfa4d48449f69242Eb8F" - } - ], - "fees": [ - { - "type": "MAKER_ECOSYSTEM", - "amount": "2250000000000000000", - "recipient_address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92233" - } - ], - "end_at": "2022-03-10T05:00:50.52Z", - "protocol_data": { - "order_type": "FULL_RESTRICTED", - "counter": "1", - "zone_address": "0x12", - "seaport_address": "0x12", - "seaport_version": "1.5" - }, - "salt": "12686911856931635052326433555881236148", - "buy": [ - { - "type": "ERC721", - "contract_address": "0x692edAd005237c7E737bB2c0F3D8ccCc10D3479E", - "token_id": "1" - } - ], - "signature": "0x", - "start_at": "2022-03-09T05:00:50.52Z" - }, - "required": [ - "account_address", - "order_hash", - "buy", - "sell", - "fees", - "end_at", - "start_at", - "protocol_data", - "salt", - "signature" - ] - }, - "CreateCollectionBidRequestBody": { - "type": "object", - "properties": { - "account_address": { - "type": "string", - "example": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "pattern": "^0x[a-fA-F0-9]{40}$" - }, - "order_hash": { - "type": "string", - "example": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" - }, - "buy": { - "type": "array", - "description": "Buy item for collection bid should either be ERC721 or ERC1155 collection item", - "items": { - "$ref": "#/components/schemas/AssetCollectionItem" - }, - "example": [ - { - "type": "ERC721_COLLECTION", - "contract_address": "0x692edAd005237c7E737bB2c0F3D8ccCc10D3479E", - "amount": "1" - } - ], - "minItems": 1, - "maxItems": 1 - }, - "fees": { - "type": "array", - "description": "Buy fees should only include maker marketplace fees and should be no more than two entries as more entires will incur more gas. It is best practice to have this as few as possible.", - "items": { - "$ref": "#/components/schemas/Fee" - }, - "example": [], - "minItems": 0, - "maxItems": 2 - }, - "end_at": { - "type": "string", - "description": "Time after which the Order is considered expired", - "format": "date-time", - "example": "2022-03-09T05:00:50.52Z" - }, - "protocol_data": { - "$ref": "#/components/schemas/ProtocolData" - }, - "salt": { - "type": "string", - "description": "A random value added to the create Order request", - "example": "12686911856931635052326433555881236148" - }, - "sell": { - "type": "array", - "description": "Sell item for collection bid should be an ERC20 item", - "items": { - "$ref": "#/components/schemas/ERC20Item" - }, - "example": [ - { - "type": "ERC20", - "contract_address": "0x692edAd005237c7E737bB2c0F3D8ccCc10D3479E", - "amount": "9750000000000000000" - } - ], - "minItems": 1, - "maxItems": 1 - }, - "signature": { - "type": "string", - "description": "Digital signature generated by the user for the specific Order", - "example": "0x" - }, - "start_at": { - "type": "string", - "description": "Time after which Order is considered active", - "format": "date-time", - "example": "2022-03-09T05:00:50.52Z" - } - }, - "example": { - "account_address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "order_hash": "0x0821933d9391bc9bf11a6010fe84776c84b203abff0c1ad781fb4881409c8770", - "sell": [ - { - "type": "ERC20", - "amount": "9750000000000000000", - "contract_address": "0x0165878A594ca255338adfa4d48449f69242Eb8F" - } - ], - "fees": [ - { - "type": "MAKER_ECOSYSTEM", - "amount": "2250000000000000000", - "recipient_address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92233" - } - ], - "end_at": "2022-03-10T05:00:50.52Z", - "protocol_data": { - "order_type": "FULL_RESTRICTED", - "counter": "1", - "zone_address": "0x12", - "seaport_address": "0x12", - "seaport_version": "1.5" - }, - "salt": "12686911856931635052326433555881236148", - "buy": [ - { - "type": "ERC721_COLLECTION", - "contract_address": "0x692edAd005237c7E737bB2c0F3D8ccCc10D3479E", - "amount": "1" - } - ], - "signature": "0x", - "start_at": "2022-03-09T05:00:50.52Z" - }, - "required": [ - "account_address", - "order_hash", - "buy", - "sell", - "fees", - "end_at", - "start_at", - "protocol_data", - "salt", - "signature" - ] - }, - "Fee": { - "type": "object", - "properties": { - "amount": { - "type": "string", - "description": "Fee payable to recipient upon settlement", - "example": "2250000000000000000", - "pattern": "\\d+" - }, - "type": { - "type": "string", - "description": "Fee type", - "example": "ROYALTY", - "enum": [ - "ROYALTY", - "MAKER_ECOSYSTEM", - "TAKER_ECOSYSTEM", - "PROTOCOL" - ] - }, - "recipient_address": { - "type": "string", - "description": "Wallet address of fee recipient", - "example": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92233", - "pattern": "^0x[a-fA-F0-9]{40}$" - } - }, - "example": { - "amount": "2250000000000000000", - "type": "ROYALTY", - "recipient_address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92233" - }, - "required": [ - "type", - "amount", - "recipient_address" - ] - }, - "Order": { - "type": "object", - "properties": { - "account_address": { - "type": "string", - "example": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" - }, - "buy": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Item" - }, - "example": [ - { - "type": "NATIVE", - "amount": "9750000000000000000", - "contract_address": "0x0165878A594ca255338adfa4d48449f69242Eb8F" - } - ], - "minItems": 1, - "maxItems": 1 - }, - "fees": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Fee" - }, - "example": [], - "minItems": 0 - }, - "chain": { - "$ref": "#/components/schemas/Chain" - }, - "created_at": { - "type": "string", - "description": "Time the Order is created", - "format": "date-time", - "example": "2022-03-07T07:20:50.52Z" - }, - "end_at": { - "type": "string", - "description": "Time after which the Order is considered expired", - "format": "date-time", - "example": "2022-03-10T05:00:50.52Z" - }, - "id": { - "type": "string", - "description": "Global Order identifier", - "example": "018792C9-4AD7-8EC4-4038-9E05C598534A" - }, - "order_hash": { - "type": "string", - "example": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" - }, - "protocol_data": { - "$ref": "#/components/schemas/ProtocolData" - }, - "salt": { - "type": "string", - "description": "A random value added to the create Order request", - "example": "12686911856931635052326433555881236148" - }, - "sell": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Item" - }, - "example": [ - { - "type": "ERC721", - "contract_address": "0x692edAd005237c7E737bB2c0F3D8ccCc10D3479E", - "token_id": "1" - } - ], - "minItems": 1, - "maxItems": 1 - }, - "signature": { - "type": "string", - "description": "Digital signature generated by the user for the specific Order", - "example": "0x" - }, - "start_at": { - "type": "string", - "description": "Time after which Order is considered active", - "format": "date-time", - "example": "2022-03-09T05:00:50.52Z" - }, - "status": { - "$ref": "#/components/schemas/OrderStatus" - }, - "type": { - "type": "string", - "description": "Order type", - "example": "LISTING", - "enum": [ - "LISTING", - "BID", - "COLLECTION_BID" - ] - }, - "updated_at": { - "type": "string", - "description": "Time the Order is last updated", - "format": "date-time", - "example": "2022-03-07T07:20:50.52Z" - }, - "fill_status": { - "$ref": "#/components/schemas/FillStatus" - } - }, - "example": { - "account_address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "buy": [ - { - "type": "NATIVE", - "amount": "9750000000000000000", - "contract_address": "0x0165878A594ca255338adfa4d48449f69242Eb8F" - } - ], - "fees": [], - "chain": { - "id": "eip155:11155111", - "name": "sepolia" - }, - "created_at": "2022-03-07T07:20:50.52Z", - "end_at": "2022-03-10T05:00:50.52Z", - "id": "018792C9-4AD7-8EC4-4038-9E05C598534A", - "protocol_data": { - "order_type": "FULL_RESTRICTED", - "counter": "1", - "zone_address": "0x12", - "seaport_address": "0x12", - "seaport_version": "1.4" - }, - "salt": "12686911856931635052326433555881236148", - "sell": [ - { - "type": "ERC721", - "contract_address": "0x692edAd005237c7E737bB2c0F3D8ccCc10D3479E", - "token_id": "1" - } - ], - "signature": "0x", - "start_at": "2022-03-09T05:00:50.52Z", - "status": { - "name": "ACTIVE" - }, - "fill_status": { - "numerator": 1, - "denominator": 2 - }, - "type": "LISTING", - "updated_at": "2022-03-07T07:20:50.52Z" - }, - "required": [ - "id", - "type", - "account_address", - "buy", - "sell", - "fees", - "chain", - "created_at", - "end_at", - "start_at", - "updated_at", - "order_hash", - "protocol_data", - "salt", - "signature", - "status", - "fill_status" - ] - }, - "Trade": { - "type": "object", - "properties": { - "buy": { - "description": "Buy items are transferred from the taker to the maker.", - "type": "array", - "items": { - "$ref": "#/components/schemas/Item" - }, - "example": [ - { - "type": "NATIVE", - "amount": "9750000000000000000", - "contract_address": "0x0165878A594ca255338adfa4d48449f69242Eb8F" - } - ], - "minItems": 1, - "maxItems": 1 - }, - "buyer_address": { - "description": "Deprecated. Use maker and taker addresses instead of buyer and seller addresses.", - "type": "string", - "example": "0xFC99a706C0D05B8C71E1fAAC91b3E1343aC34D40" - }, - "buyer_fees": { - "description": "Deprecated. Use fees instead. The taker always pays the fees.", - "type": "array", - "items": { - "$ref": "#/components/schemas/Fee" - }, - "example": [], - "minItems": 0, - "maxItems": 1 - }, - "fees": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Fee" - }, - "example": [], - "minItems": 0, - "maxItems": 1 - }, - "chain": { - "$ref": "#/components/schemas/Chain" - }, - "order_id": { - "type": "string", - "example": "7df3e99e-f7b3-459c-bef6-ffb66a18bb59" - }, - "blockchain_metadata": { - "$ref": "#/components/schemas/TradeBlockchainMetadata" - }, - "indexed_at": { - "type": "string", - "description": "Time the on-chain trade event is indexed by the order book system", - "format": "date-time", - "example": "2022-03-07T07:20:50.52Z" - }, - "id": { - "type": "string", - "description": "Global Trade identifier", - "example": "018792C9-4AD7-8EC4-4038-9E05C598534A" - }, - "sell": { - "description": "Sell items are transferred from the maker to the taker.", - "type": "array", - "items": { - "$ref": "#/components/schemas/Item" - }, - "example": [ - { - "type": "ERC721", - "contract_address": "0x692edAd005237c7E737bB2c0F3D8ccCc10D3479E", - "token_id": "1" - } - ], - "minItems": 1, - "maxItems": 1 - }, - "seller_address": { - "description": "Deprecated. Use maker and taker addresses instead of buyer and seller addresses.", - "type": "string", - "example": "0x002b9B1cbf464782Df5d48870358FA6c09f1b19D" - }, - "maker_address": { - "type": "string", - "example": "0x002b9B1cbf464782Df5d48870358FA6c09f1b19D" - }, - "taker_address": { - "type": "string", - "example": "0xFC99a706C0D05B8C71E1fAAC91b3E1343aC34D40" - } - }, - "example": { - "buy": [ - { - "type": "NATIVE", - "amount": "9750000000000000000", - "contract_address": "0x0165878A594ca255338adfa4d48449f69242Eb8F" - } - ], - "buyer_address": "0xFC99a706C0D05B8C71E1fAAC91b3E1343aC34D40", - "buyer_fees": [], - "fees": [], - "chain": { - "id": "eip155:11155111", - "name": "sepolia" - }, - "indexed_at": "2022-03-07T07:20:50.52Z", - "id": "018792C9-4AD7-8EC4-4038-9E05C598534A", - "sell": [ - { - "type": "ERC721", - "contract_address": "0x692edAd005237c7E737bB2c0F3D8ccCc10D3479E", - "token_id": "1" - } - ], - "seller_address": "0x002b9B1cbf464782Df5d48870358FA6c09f1b19D", - "maker_address": "0x002b9B1cbf464782Df5d48870358FA6c09f1b19D", - "taker_address": "0xFC99a706C0D05B8C71E1fAAC91b3E1343aC34D40" - }, - "required": [ - "id", - "order_id", - "buy", - "buyer_address", - "sell", - "seller_address", - "buyer_fees", - "fees", - "chain", - "maker_address", - "taker_address", - "indexed_at", - "blockchain_metadata" - ] - }, - "CancelOrdersResult": { - "type": "object", - "properties": { - "result": { - "$ref": "#/components/schemas/CancelOrdersResultData" - } - }, - "required": [ - "result" - ], - "example": { - "result": { - "successful_cancellations": [ - "018a8c71-d7e4-e303-a2ef-318871ef7756", - "458a8c71-d7e4-e303-a2ef-318871ef7778" - ], - "pending_cancellations": [ - "238a8c71-d7e4-e303-a2ef-318871ef7778", - "898a8c71-d7e4-e303-a2ef-318871ef7735" - ], - "failed_cancellations": [ - { - "order": "458a8c71-d7e4-e303-a2ef-318871ef7790", - "reason_code": "FILLED" - }, - { - "order": "338a8c71-d7e4-e303-a2ef-318871ef7342", - "reason_code": "FILLED" - } - ] - } - } - }, - "CancelOrdersResultData": { - "type": "object", - "properties": { - "successful_cancellations": { - "type": "array", - "description": "Orders which were successfully cancelled", - "items": { - "type": "string" - }, - "minItems": 0, - "maxItems": 10 - }, - "pending_cancellations": { - "type": "array", - "description": "Orders which are marked for cancellation but the cancellation cannot be guaranteed", - "items": { - "type": "string" - }, - "minItems": 0, - "maxItems": 10 - }, - "failed_cancellations": { - "type": "array", - "description": "Orders which failed to be cancelled", - "items": { - "$ref": "#/components/schemas/FailedOrderCancellation" - }, - "minItems": 0, - "maxItems": 10 - } - }, - "required": [ - "successful_cancellations", - "pending_cancellations", - "failed_cancellations" - ] - }, - "FailedOrderCancellation": { - "type": "object", - "properties": { - "order": { - "type": "string", - "description": "ID of the order which failed to be cancelled", - "example": "7df3e99e-f7b3-459c-bef6-ffb66a18bb59" - }, - "reason_code": { - "type": "string", - "enum": [ - "FILLED" - ], - "description": "Reason code indicating why the order failed to be cancelled", - "example": "FILLED" - } - }, - "required": [ - "order", - "reason_code" - ] - }, - "ListingResult": { - "type": "object", - "properties": { - "result": { - "$ref": "#/components/schemas/Order" - } - }, - "required": [ - "result" - ], - "example": { - "result": { - "account_address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "buy": [ - { - "type": "ERC20", - "amount": "9750000000000000000", - "contract_address": "0x0165878A594ca255338adfa4d48449f69242Eb8F" - } - ], - "fees": [], - "chain": { - "id": "eip155:11155111", - "name": "sepolia" - }, - "created_at": "2022-03-07T07:20:50.52Z", - "end_at": "2022-03-10T05:00:50.52Z", - "id": "018792C9-4AD7-8EC4-4038-9E05C598534B", - "order_hash": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "protocol_data": { - "order_type": "FULL_RESTRICTED", - "counter": "1", - "zone_address": "0x12", - "seaport_address": "0x12", - "seaport_version": "1.5" - }, - "salt": "12686911856931635052326433555881236148", - "sell": [ - { - "type": "ERC721", - "contract_address": "0x692edAd005237c7E737bB2c0F3D8ccCc10D3479E", - "token_id": "1" - } - ], - "signature": "0x", - "start_at": "2022-03-09T05:00:50.52Z", - "status": { - "name": "EXPIRED" - }, - "type": { - "name": "LISTING" - }, - "updated_at": "2022-03-07T07:20:50.52Z" - } - } - }, - "BidResult": { - "type": "object", - "properties": { - "result": { - "$ref": "#/components/schemas/Order" - } - }, - "required": [ - "result" - ], - "example": { - "result": { - "account_address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "sell": [ - { - "type": "ERC20", - "amount": "9750000000000000000", - "contract_address": "0x0165878A594ca255338adfa4d48449f69242Eb8F" - } - ], - "fees": [], - "chain": { - "id": "eip155:11155111", - "name": "sepolia" - }, - "created_at": "2022-03-07T07:20:50.52Z", - "end_at": "2022-03-10T05:00:50.52Z", - "id": "018792C9-4AD7-8EC4-4038-9E05C598534B", - "order_hash": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "protocol_data": { - "order_type": "FULL_RESTRICTED", - "counter": "1", - "zone_address": "0x12", - "seaport_address": "0x12", - "seaport_version": "1.5" - }, - "salt": "12686911856931635052326433555881236148", - "buy": [ - { - "type": "ERC721", - "contract_address": "0x692edAd005237c7E737bB2c0F3D8ccCc10D3479E", - "token_id": "1" - } - ], - "signature": "0x", - "start_at": "2022-03-09T05:00:50.52Z", - "status": { - "name": "EXPIRED" - }, - "type": "BID", - "updated_at": "2022-03-07T07:20:50.52Z" - } - } - }, - "CollectionBidResult": { - "type": "object", - "properties": { - "result": { - "$ref": "#/components/schemas/Order" - } - }, - "required": [ - "result" - ], - "example": { - "result": { - "account_address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "sell": [ - { - "type": "ERC20", - "amount": "9750000000000000000", - "contract_address": "0x0165878A594ca255338adfa4d48449f69242Eb8F" - } - ], - "fees": [], - "chain": { - "id": "eip155:11155111", - "name": "sepolia" - }, - "created_at": "2022-03-07T07:20:50.52Z", - "end_at": "2022-03-10T05:00:50.52Z", - "id": "018792C9-4AD7-8EC4-4038-9E05C598534A", - "order_hash": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "protocol_data": { - "order_type": "PARTIAL_RESTRICTED", - "counter": "1", - "zone_address": "0x12", - "seaport_address": "0x12", - "seaport_version": "1.5" - }, - "salt": "12686911856931635052326433555881236148", - "buy": [ - { - "type": "ERC721_COLLECTION", - "contract_address": "0x692edAd005237c7E737bB2c0F3D8ccCc10D3479E", - "amount": "1" - } - ], - "signature": "0x", - "start_at": "2022-03-09T05:00:50.52Z", - "status": { - "name": "EXPIRED" - }, - "type": "COLLECTION_BID", - "updated_at": "2022-03-07T07:20:50.52Z" - } - } - }, - "ListListingsResult": { - "type": "object", - "properties": { - "page": { - "$ref": "#/components/schemas/Page" - }, - "result": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Order" - }, - "minItems": 0, - "maxItems": 200 - } - }, - "required": [ - "page", - "result" - ], - "example": { - "page": { - "previous_cursor": "MjAyMy0wMS0yM1QwMTo1NToyNy4zNTM2MzA", - "next_cursor": "MjAyMy0wMS0yM1QwMTo1NToyNy4zNTM2MzA" - }, - "result": [ - { - "account_address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "buy": [ - { - "type": "ERC20", - "amount": "9750000000000000000", - "contract_address": "0x0165878A594ca255338adfa4d48449f69242Eb8F" - } - ], - "fees": [], - "chain": { - "id": "eip155:11155111", - "name": "sepolia" - }, - "created_at": "2022-03-07T07:20:50.52Z", - "end_at": "2022-03-10T05:00:50.52Z", - "id": "018792C9-4AD7-8EC4-4038-9E05C598534A", - "order_hash": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "protocol_data": { - "order_type": "FULL_RESTRICTED", - "counter": "1", - "zone_address": "0x12", - "seaport_address": "0x12", - "seaport_version": "1.5" - }, - "salt": "12686911856931635052326433555881236148", - "sell": [ - { - "type": "ERC721", - "contract_address": "0x692edAd005237c7E737bB2c0F3D8ccCc10D3479E", - "token_id": "1" - } - ], - "signature": "0x", - "start_at": "2022-03-09T05:00:50.52Z", - "status": { - "name": "EXPIRED" - }, - "updated_at": "2022-03-07T07:20:50.52Z" - }, - { - "account_address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "buy": [ - { - "type": "ERC20", - "amount": "9750000000000000000", - "contract_address": "0x0165878A594ca255338adfa4d48449f69242Eb8F" - } - ], - "fees": [], - "chain": { - "id": "eip155:11155111", - "name": "sepolia" - }, - "created_at": "2022-03-07T07:20:50.52Z", - "end_at": "2022-03-10T05:00:50.52Z", - "id": "018792C9-4AD7-8EC4-4038-9E05C598534A", - "order_hash": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "protocol_data": { - "order_type": "FULL_RESTRICTED", - "counter": "1", - "zone_address": "0x12", - "seaport_address": "0x12", - "seaport_version": "1.5" - }, - "salt": "12686911856931635052326433555881236148", - "sell": [ - { - "type": "ERC721", - "contract_address": "0x692edAd005237c7E737bB2c0F3D8ccCc10D3479E", - "token_id": "1" - } - ], - "signature": "0x", - "start_at": "2022-03-09T05:00:50.52Z", - "status": { - "name": "EXPIRED" - }, - "updated_at": "2022-03-07T07:20:50.52Z" - } - ] - } - }, - "ListBidsResult": { - "type": "object", - "properties": { - "page": { - "$ref": "#/components/schemas/Page" - }, - "result": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Order" - }, - "minItems": 0, - "maxItems": 200 - } - }, - "required": [ - "page", - "result" - ], - "example": { - "page": { - "previous_cursor": "MjAyMy0wMS0yM1QwMTo1NToyNy4zNTM2MzA", - "next_cursor": "MjAyMy0wMS0yM1QwMTo1NToyNy4zNTM2MzA" - }, - "result": [ - { - "account_address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "sell": [ - { - "type": "ERC20", - "amount": "9750000000000000000", - "contract_address": "0x0165878A594ca255338adfa4d48449f69242Eb8F" - } - ], - "fees": [], - "chain": { - "id": "eip155:11155111", - "name": "sepolia" - }, - "created_at": "2022-03-07T07:20:50.52Z", - "end_at": "2022-03-10T05:00:50.52Z", - "id": "018792C9-4AD7-8EC4-4038-9E05C598534A", - "order_hash": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "protocol_data": { - "order_type": "FULL_RESTRICTED", - "counter": "1", - "zone_address": "0x12", - "seaport_address": "0x12", - "seaport_version": "1.5" - }, - "salt": "12686911856931635052326433555881236148", - "buy": [ - { - "type": "ERC721", - "contract_address": "0x692edAd005237c7E737bB2c0F3D8ccCc10D3479E", - "token_id": "1" - } - ], - "signature": "0x", - "start_at": "2022-03-09T05:00:50.52Z", - "status": { - "name": "EXPIRED" - }, - "updated_at": "2022-03-07T07:20:50.52Z" - }, - { - "account_address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "sell": [ - { - "type": "ERC20", - "amount": "9750000000000000000", - "contract_address": "0x0165878A594ca255338adfa4d48449f69242Eb8F" - } - ], - "fees": [], - "chain": { - "id": "eip155:11155111", - "name": "sepolia" - }, - "created_at": "2022-03-07T07:20:50.52Z", - "end_at": "2022-03-10T05:00:50.52Z", - "id": "018792C9-4AD7-8EC4-4038-9E05C598534A", - "order_hash": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "protocol_data": { - "order_type": "FULL_RESTRICTED", - "counter": "1", - "zone_address": "0x12", - "seaport_address": "0x12", - "seaport_version": "1.5" - }, - "salt": "12686911856931635052326433555881236148", - "buy": [ - { - "type": "ERC721", - "contract_address": "0x692edAd005237c7E737bB2c0F3D8ccCc10D3479E", - "token_id": "1" - } - ], - "signature": "0x", - "start_at": "2022-03-09T05:00:50.52Z", - "status": { - "name": "EXPIRED" - }, - "updated_at": "2022-03-07T07:20:50.52Z" - } - ] - } - }, - "ListCollectionBidsResult": { - "type": "object", - "properties": { - "page": { - "$ref": "#/components/schemas/Page" - }, - "result": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Order" - }, - "minItems": 0, - "maxItems": 200 - } - }, - "required": [ - "page", - "result" - ], - "example": { - "page": { - "previous_cursor": "MjAyMy0wMS0yM1QwMTo1NToyNy4zNTM2MzA", - "next_cursor": "MjAyMy0wMS0yM1QwMTo1NToyNy4zNTM2MzA" - }, - "result": [ - { - "account_address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "sell": [ - { - "type": "ERC20", - "amount": "9750000000000000000", - "contract_address": "0x0165878A594ca255338adfa4d48449f69242Eb8F" - } - ], - "fees": [], - "chain": { - "id": "eip155:11155111", - "name": "sepolia" - }, - "created_at": "2022-03-07T07:20:50.52Z", - "end_at": "2022-03-10T05:00:50.52Z", - "id": "018792C9-4AD7-8EC4-4038-9E05C598534A", - "order_hash": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "protocol_data": { - "order_type": "PARTIAL_RESTRICTED", - "counter": "1", - "zone_address": "0x12", - "seaport_address": "0x12", - "seaport_version": "1.5" - }, - "salt": "12686911856931635052326433555881236148", - "buy": [ - { - "type": "ERC721_COLLECTION", - "contract_address": "0x692edAd005237c7E737bB2c0F3D8ccCc10D3479E", - "amount": "1" - } - ], - "signature": "0x", - "start_at": "2022-03-09T05:00:50.52Z", - "status": { - "name": "EXPIRED" - }, - "updated_at": "2022-03-07T07:20:50.52Z" - }, - { - "account_address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "sell": [ - { - "type": "ERC20", - "amount": "9750000000000000000", - "contract_address": "0x0165878A594ca255338adfa4d48449f69242Eb8F" - } - ], - "fees": [], - "chain": { - "id": "eip155:11155111", - "name": "sepolia" - }, - "created_at": "2022-03-07T07:20:50.52Z", - "end_at": "2022-03-10T05:00:50.52Z", - "id": "018792C9-4AD7-8EC4-4038-9E05C598534A", - "order_hash": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", - "protocol_data": { - "order_type": "PARTIAL_RESTRICTED", - "counter": "1", - "zone_address": "0x12", - "seaport_address": "0x12", - "seaport_version": "1.5" - }, - "salt": "12686911856931635052326433555881236148", - "buy": [ - { - "type": "ERC721_COLLECTION", - "contract_address": "0x692edAd005237c7E737bB2c0F3D8ccCc10D3479E", - "amount": "1" - } - ], - "signature": "0x", - "start_at": "2022-03-09T05:00:50.52Z", - "status": { - "name": "EXPIRED" - }, - "updated_at": "2022-03-07T07:20:50.52Z" - } - ] - } - }, - "TradeResult": { - "type": "object", - "properties": { - "result": { - "$ref": "#/components/schemas/Trade" - } - }, - "required": [ - "result" - ], - "example": { - "result": { - "buy": [ - { - "type": "ERC20", - "amount": "9750000000000000000", - "contract_address": "0x0165878A594ca255338adfa4d48449f69242Eb8F" - } - ], - "buyer_address": "0xFC99a706C0D05B8C71E1fAAC91b3E1343aC34D40", - "buyer_fees": [], - "chain": { - "id": "eip155:11155111", - "name": "sepolia" - }, - "created_at": "2022-03-07T07:20:50.52Z", - "id": "018792C9-4AD7-8EC4-4038-9E05C598534B", - "sell": [ - { - "type": "ERC721", - "contract_address": "0x692edAd005237c7E737bB2c0F3D8ccCc10D3479E", - "token_id": "1" - } - ], - "seller_address": "0x002b9B1cbf464782Df5d48870358FA6c09f1b19D", - "maker_address": "0x002b9B1cbf464782Df5d48870358FA6c09f1b19D", - "taker_address": "0xFC99a706C0D05B8C71E1fAAC91b3E1343aC34D40", - "updated_at": "2022-03-07T07:20:50.52Z" - } - } - }, - "ListTradeResult": { - "type": "object", - "properties": { - "page": { - "$ref": "#/components/schemas/Page" - }, - "result": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Trade" - }, - "minItems": 0, - "maxItems": 200 - } - }, - "required": [ - "page", - "result" - ], - "example": { - "page": { - "previous_cursor": "MjAyMy0wMS0yM1QwMTo1NToyNy4zNTM2MzA", - "next_cursor": "MjAyMy0wMS0yM1QwMTo1NToyNy4zNTM2MzA" - }, - "result": [ - { - "buy": [ - { - "type": "ERC20", - "amount": "9750000000000000000", - "contract_address": "0x0165878A594ca255338adfa4d48449f69242Eb8F" - } - ], - "buyer_address": "0xFC99a706C0D05B8C71E1fAAC91b3E1343aC34D40", - "buyer_fees": [], - "chain": { - "id": "eip155:11155111", - "name": "sepolia" - }, - "created_at": "2022-03-07T07:20:50.52Z", - "id": "018792C9-4AD7-8EC4-4038-9E05C598534A", - "sell": [ - { - "type": "ERC721", - "contract_address": "0x692edAd005237c7E737bB2c0F3D8ccCc10D3479E", - "token_id": "1" - } - ], - "seller_address": "0x002b9B1cbf464782Df5d48870358FA6c09f1b19D", - "maker_address": "0x002b9B1cbf464782Df5d48870358FA6c09f1b19D", - "taker_address": "0xFC99a706C0D05B8C71E1fAAC91b3E1343aC34D40", - "updated_at": "2022-03-07T07:20:50.52Z" - }, - { - "buy": [ - { - "type": "ERC20", - "amount": "9750000000000000000", - "contract_address": "0x0165878A594ca255338adfa4d48449f69242Eb8F" - } - ], - "buyer_address": "0x017406f3F27d507a1491976B7835CE5CD0fA647a", - "buyer_fees": [], - "chain": { - "id": "eip155:11155111", - "name": "sepolia" - }, - "created_at": "2022-03-07T07:20:50.52Z", - "id": "018792C9-4AD7-8EC4-4038-9E05C598534A", - "sell": [ - { - "type": "ERC721", - "contract_address": "0x692edAd005237c7E737bB2c0F3D8ccCc10D3479E", - "token_id": "1" - } - ], - "seller_address": "0xC73349c545C1D757eb650cDc463A2f6dF1Ec41cb", - "maker_address": "0xC73349c545C1D757eb650cDc463A2f6dF1Ec41cb", - "taker_address": "0x017406f3F27d507a1491976B7835CE5CD0fA647a", - "updated_at": "2022-03-07T07:20:50.52Z" - } - ] - } - }, - "OrderStatus": { - "description": "The Order status", - "oneOf": [ - { - "$ref": "#/components/schemas/CancelledOrderStatus" - }, - { - "$ref": "#/components/schemas/PendingOrderStatus" - }, - { - "$ref": "#/components/schemas/ActiveOrderStatus" - }, - { - "$ref": "#/components/schemas/InactiveOrderStatus" - }, - { - "$ref": "#/components/schemas/FilledOrderStatus" - }, - { - "$ref": "#/components/schemas/ExpiredOrderStatus" - } - ], - "discriminator": { - "propertyName": "name", - "mapping": { - "CANCELLED": "#/components/schemas/CancelledOrderStatus", - "PENDING": "#/components/schemas/PendingOrderStatus", - "ACTIVE": "#/components/schemas/ActiveOrderStatus", - "INACTIVE": "#/components/schemas/InactiveOrderStatus", - "FILLED": "#/components/schemas/FilledOrderStatus", - "EXPIRED": "#/components/schemas/ExpiredOrderStatus" - } - } - }, - "CancelledOrderStatus": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "The order status indicating a order is has been cancelled or about to be cancelled.", - "enum": [ - "CANCELLED" - ] - }, - "pending": { - "type": "boolean", - "description": "Whether the cancellation of the order is pending", - "example": false - }, - "cancellation_type": { - "type": "string", - "description": "Whether the cancellation was done on-chain or off-chain or as a result of an underfunded account", - "enum": [ - "ON_CHAIN", - "OFF_CHAIN", - "UNDERFUNDED" - ], - "example": "ON_CHAIN" - } - }, - "required": [ - "name", - "pending", - "cancellation_type" - ] - }, - "PendingOrderStatus": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "The order status that indicates the order is yet to be active due to various reasons.", - "enum": [ - "PENDING" - ] - }, - "evaluated": { - "type": "boolean", - "description": "Whether the order has been evaluated after its creation", - "example": false - }, - "started": { - "type": "boolean", - "description": "Whether the order has reached its specified start time", - "example": false - } - }, - "required": [ - "name", - "evaluated", - "started" - ] - }, - "ActiveOrderStatus": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "The order status that indicates an order can be fulfilled.", - "enum": [ - "ACTIVE" - ] - } - }, - "required": [ - "name" - ] - }, - "InactiveOrderStatus": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "The order status that indicates an order cannot be fulfilled.", - "enum": [ - "INACTIVE" - ] - }, - "sufficient_approvals": { - "type": "boolean", - "description": "Whether the order offerer has sufficient approvals", - "example": false - }, - "sufficient_balances": { - "type": "boolean", - "description": "Whether the order offerer still has sufficient balance to complete the order", - "example": false - } - }, - "required": [ - "name", - "sufficient_approvals", - "sufficient_balances" - ] - }, - "ExpiredOrderStatus": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "A terminal order status indicating that an order cannot be fulfilled due to expiry.", - "enum": [ - "EXPIRED" - ] - } - }, - "required": [ - "name" - ] - }, - "FilledOrderStatus": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "A terminal order status indicating that an order has been fulfilled.", - "enum": [ - "FILLED" - ] - } - }, - "required": [ - "name" - ] - }, - "OrderStatusName": { - "type": "string", - "description": "The Order status", - "enum": [ - "PENDING", - "ACTIVE", - "INACTIVE", - "FILLED", - "EXPIRED", - "CANCELLED" - ] - }, - "ProtocolData": { - "type": "object", - "properties": { - "order_type": { - "type": "string", - "description": "Seaport order type. Orders containing ERC721 tokens will need to pass in the order type as FULL_RESTRICTED while orders with ERC1155 tokens will need to pass in the order_type as PARTIAL_RESTRICTED", - "example": "FULL_RESTRICTED", - "enum": [ - "FULL_RESTRICTED", - "PARTIAL_RESTRICTED" - ] - }, - "counter": { - "type": "string", - "description": "big.Int or uint256 string for order counter", - "example": "92315562" - }, - "zone_address": { - "type": "string", - "description": "Immutable zone address", - "example": "0x12" - }, - "seaport_address": { - "type": "string", - "description": "Immutable Seaport contract address", - "example": "0x12" - }, - "seaport_version": { - "type": "string", - "description": "Immutable Seaport contract version", - "example": "1.5" - } - }, - "example": { - "order_type": "FULL_RESTRICTED", - "counter": "92315562", - "zone_address": "0x12", - "seaport_address": "0x12", - "seaport_version": "1.5" - }, - "required": [ - "order_type", - "counter", - "zone_address", - "seaport_address", - "seaport_version" - ] - }, - "TradeBlockchainMetadata": { - "description": "The metadata related to the transaction in which the activity occurred", - "nullable": true, - "type": "object", - "properties": { - "transaction_hash": { - "type": "string", - "description": "The transaction hash of the trade", - "example": "0x68d9eac5e3b3c3580404989a4030c948a78e1b07b2b5ea5688d8c38a6c61c93e" - }, - "block_number": { - "description": "EVM block number (uint64 as string)", - "type": "string", - "example": "1" - }, - "transaction_index": { - "description": "Transaction index in a block (uint32 as string)", - "type": "string", - "example": "1" - }, - "log_index": { - "description": "The log index of the fulfillment event in a block (uint32 as string)", - "type": "string", - "example": "1" - } - }, - "required": [ - "transaction_hash", - "block_number", - "transaction_index", - "log_index" - ] - }, - "FillStatus": { - "description": "The ratio of the order that has been filled, an order that has been fully filled will have the same numerator and denominator values.", - "type": "object", - "properties": { - "numerator": { - "type": "string", - "description": "The numerator of the fill status", - "example": "1" - }, - "denominator": { - "type": "string", - "description": "The denominator of the fill status", - "example": "2" - } - }, - "required": [ - "numerator", - "denominator" - ] - }, - "APIError501": { - "allOf": [ - { - "$ref": "#/components/schemas/BasicAPIError" - }, - { - "type": "object", - "properties": { - "code": { - "type": "string", - "description": "Error Code", - "enum": [ - "NOT_IMPLEMENTED_ERROR" - ], - "example": "NOT_IMPLEMENTED_ERROR" - }, - "details": { - "type": "object", - "nullable": true, - "description": "Additional details to help resolve the error" - } - }, - "required": [ - "code", - "details" - ] - } - ] - }, - "GetLinkedAddressesRes": { - "type": "object", - "required": [ - "linked_addresses" - ], - "properties": { - "linked_addresses": { - "type": "array", - "minItems": 0, - "description": "The user's list of linked addresses", - "items": { - "description": "The user's linked address", - "type": "string", - "pattern": "^0x[0-9a-fA-F]*$", - "maxLength": 42 - } - } - } - }, - "EthAddress": { - "description": "Ethereum address", - "type": "string", - "example": "0xd8da6bf26964af9d7eed9e03e53415d37aa96045" - }, - "CreatedAt": { - "description": "Created at", - "type": "string", - "format": "date-time", - "example": "2021-08-31T00:00:00Z" - }, - "Name": { - "description": "Name", - "type": "string", - "example": "Test" - }, - "WalletType": { - "description": "Wallet type", - "type": "string", - "example": "MetaMask" - }, - "Wallet": { - "description": "Linked wallet", - "type": "object", - "required": [ - "address", - "type", - "created_at", - "updated_at", - "clientName" - ], - "properties": { - "address": { - "$ref": "#/components/schemas/EthAddress" - }, - "type": { - "$ref": "#/components/schemas/WalletType" - }, - "created_at": { - "$ref": "#/components/schemas/CreatedAt" - }, - "updated_at": { - "$ref": "#/components/schemas/CreatedAt" - }, - "name": { - "$ref": "#/components/schemas/Name" - }, - "clientName": { - "type": "string", - "description": "Name of client that linked the wallet", - "example": "Passport Dashboard" - } - } - }, - "LinkWalletV2Request": { - "description": "Link wallet V2 request", - "type": "object", - "required": [ - "type", - "wallet_address", - "signature", - "nonce" - ], - "properties": { - "type": { - "type": "string", - "description": "This should be the EIP-6963 rdns value, if you're unable to get the rdns value you can provide \"External\". If using WalletConnect then provide \"WalletConnect\".", - "example": "io.metamask", - "maxLength": 32, - "not": { - "enum": [ - "Passport", - "com.immutable.passport" - ] - } - }, - "wallet_address": { - "description": "The address of the external wallet being linked to Passport", - "type": "string", - "example": "0xd8da6bf26964af9d7eed9e03e53415d37aa96045", - "pattern": "^0x[a-fA-F0-9]{40}$" - }, - "signature": { - "description": "The EIP-712 signature", - "type": "string" - }, - "nonce": { - "description": "A unique identifier for the signature", - "type": "string" - } - } - }, - "UserInfo": { - "type": "object", - "required": [ - "sub", - "linked_addresses" - ], - "properties": { - "sub": { - "type": "string", - "description": "The user's id" - }, - "email": { - "type": "string", - "description": "The user's email address" - }, - "passport_address": { - "type": "string", - "description": "The user's Passport address if it has been registered", - "pattern": "^0x[0-9a-fA-F]*$", - "maxLength": 42 - }, - "linked_addresses": { - "type": "array", - "minItems": 0, - "description": "The user's list of linked addresses", - "items": { - "description": "The user's linked address", - "type": "string", - "pattern": "^0x[0-9a-fA-F]*$", - "maxLength": 42 - } - } - } - }, - "FilterValue": { - "type": "object", - "properties": { - "value": { - "type": "string" - }, - "nft_count": { - "type": "string", - "description": "Number of NFTs that have this trait. Uint256 as string" - } - }, - "example": [ - { - "value": "Common", - "nft_count": "42" - }, - { - "value": "Rare", - "nft_count": "17" - } - ], - "required": [ - "value", - "nft_count" - ] - }, - "Filter": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "Name of trait", - "example": "Rarity" - }, - "values": { - "description": "List of 100 most common values for this trait sorted by number of associated NFTs", - "type": "array", - "items": { - "$ref": "#/components/schemas/FilterValue" - } - }, - "omitted_values_count": { - "type": "integer", - "description": "Indicated how many more distinct values exist", - "example": 0 - } - }, - "required": [ - "name", - "values", - "omitted_values_count" - ] - }, - "FilterResult": { - "type": "object", - "properties": { - "chain": { - "$ref": "#/components/schemas/Chain" - }, - "contract_address": { - "type": "string", - "description": "ETH Address of collection that the asset belongs to", - "example": "0xe9b00a87700f660e46b6f5deaa1232836bcc07d3" - }, - "filters": { - "description": "List of all filters and the most common values", - "type": "array", - "items": { - "$ref": "#/components/schemas/Filter" - } - } - }, - "required": [ - "chain", - "contract_address", - "filters" - ] - }, - "ListFiltersResult": { - "type": "object", - "description": "List filters result", - "properties": { - "result": { - "$ref": "#/components/schemas/FilterResult" - }, - "page": { - "$ref": "#/components/schemas/Page" - } - }, - "required": [ - "result", - "page" - ] - }, - "MarketplaceContractType": { - "description": "The contract type for a collection", - "type": "string", - "enum": [ - "ERC721", - "ERC1155" - ] - }, - "NFTWithStack": { - "type": "object", - "description": "Stack", - "properties": { - "token_id": { - "description": "Token id of NFT (uint256 as string)", - "type": "string" - }, - "stack_id": { - "type": "string", - "format": "uuid", - "description": "Stack ID" - }, - "chain": { - "$ref": "#/components/schemas/Chain" - }, - "contract_address": { - "type": "string", - "description": "Contract address" - }, - "contract_type": { - "$ref": "#/components/schemas/MarketplaceContractType" - }, - "created_at": { - "type": "string", - "format": "date-time", - "example": "2022-08-16T17:43:26.991388Z", - "description": "When the metadata was created" - }, - "updated_at": { - "type": "string", - "format": "date-time", - "description": "When the metadata was last updated", - "example": "2022-08-16T17:43:26.991388Z" - }, - "name": { - "type": "string", - "nullable": true, - "example": "Sword", - "description": "The name of the NFT" - }, - "description": { - "type": "string", - "nullable": true, - "example": "2022-08-16T17:43:26.991388Z", - "description": "The description of the NFT" - }, - "image": { - "type": "string", - "nullable": true, - "description": "The image url of the NFT", - "example": "https://some-url" - }, - "external_url": { - "type": "string", - "nullable": true, - "description": "The external website link of NFT", - "example": "https://some-url" - }, - "animation_url": { - "type": "string", - "nullable": true, - "description": "The animation url of the NFT", - "example": "https://some-url" - }, - "youtube_url": { - "type": "string", - "nullable": true, - "description": "The youtube URL of NFT", - "example": "https://some-url" - }, - "attributes": { - "type": "array", - "description": "List of Metadata attributes", - "nullable": true, - "items": { - "$ref": "#/components/schemas/NFTMetadataAttribute" - } - }, - "balance": { - "type": "integer", - "description": "Balance of NFT", - "minimum": 1, - "nullable": true - } - }, - "required": [ - "token_id", - "stack_id", - "chain", - "contract_address", - "contract_type", - "updated_at", - "created_at", - "name", - "description", - "image", - "external_url", - "animation_url", - "youtube_url", - "attributes", - "balance" - ] - }, - "MarketPriceNativeToken": { - "type": "object", - "properties": { - "type": { - "type": "string", - "description": "Token type user is offering, which in this case is the native IMX token", - "example": "NATIVE", - "enum": [ - "NATIVE" - ] - }, - "symbol": { - "nullable": true, - "type": "string", - "description": "The symbol of token", - "example": "IMX" - } - }, - "required": [ - "type", - "symbol" - ] - }, - "MarketPriceERC20Token": { - "type": "object", - "properties": { - "type": { - "type": "string", - "description": "Token type user is offering, which in this case is ERC20", - "example": "ERC20", - "enum": [ - "ERC20" - ] - }, - "contract_address": { - "type": "string", - "description": "Address of ERC20 token", - "example": "0x0165878A594ca255338adfa4d48449f69242Eb8F", - "pattern": "^0x[a-fA-F0-9]{40}$" - }, - "symbol": { - "nullable": true, - "type": "string", - "description": "The symbol of token", - "example": "ETH" - }, - "decimals": { - "nullable": true, - "type": "integer", - "description": "The decimals of token", - "example": 18 - } - }, - "required": [ - "type", - "contract_address", - "symbol", - "decimals" - ] - }, - "PaymentAmount": { - "type": "string", - "description": "The token amount value. This value is provided in the smallest unit of the token (e.g. wei for ETH)", - "example": "9750000000000000000", - "pattern": "\\d+" - }, - "MarketPriceFees": { - "type": "object", - "properties": { - "amount": { - "type": "string", - "description": "Fee in the payment currency", - "example": "1000000000000000000" - }, - "type": { - "type": "string", - "description": "Fee type", - "example": "ROYALTY", - "enum": [ - "ROYALTY", - "MAKER_ECOSYSTEM", - "TAKER_ECOSYSTEM", - "PROTOCOL" - ] - }, - "recipient_address": { - "type": "string", - "description": "Wallet address of fee recipient", - "example": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92233", - "pattern": "^0x[a-fA-F0-9]{40}$" - } - }, - "example": { - "amount": "1000000000000000000", - "type": "ROYALTY", - "recipient_address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92233" - }, - "required": [ - "type", - "amount", - "recipient_address" - ] - }, - "ConvertedPrices": { - "type": "object", - "description": "A mapping of converted prices for major currencies such as ETH, USD. All converted prices are fee-inclusive.", - "nullable": true, - "additionalProperties": { - "type": "string" - }, - "example": { - "ETH": "0.0058079775", - "USD": "15.89" - } - }, - "MarketPriceDetails": { - "type": "object", - "description": "Market Price details", - "properties": { - "token": { - "description": "Token details", - "oneOf": [ - { - "$ref": "#/components/schemas/MarketPriceNativeToken" - }, - { - "$ref": "#/components/schemas/MarketPriceERC20Token" - } - ], - "discriminator": { - "propertyName": "type", - "mapping": { - "NATIVE": "#/components/schemas/MarketPriceNativeToken", - "ERC20": "#/components/schemas/MarketPriceERC20Token" - } - } - }, - "amount": { - "$ref": "#/components/schemas/PaymentAmount" - }, - "fee_inclusive_amount": { - "$ref": "#/components/schemas/PaymentAmount" - }, - "fees": { - "type": "array", - "items": { - "$ref": "#/components/schemas/MarketPriceFees" - }, - "example": [ - { - "type": "TAKER_ECOSYSTEM", - "recipient_address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92233", - "amount": "1000000000000000000" - } - ] - }, - "converted_prices": { - "$ref": "#/components/schemas/ConvertedPrices" - } - }, - "required": [ - "token", - "amount", - "fee_inclusive_amount", - "fees", - "converted_prices" - ] - }, - "Listing": { - "type": "object", - "properties": { - "listing_id": { - "type": "string", - "description": "Global Order identifier", - "example": "018792C9-4AD7-8EC4-4038-9E05C598534A" - }, - "price_details": { - "$ref": "#/components/schemas/MarketPriceDetails" - }, - "token_id": { - "type": "string", - "description": "Token ID", - "example": "1" - }, - "contract_address": { - "type": "string", - "description": "ETH Address of collection that the asset belongs to", - "example": "0xe9b00a87700f660e46b6f5deaa1232836bcc07d3" - }, - "creator": { - "type": "string", - "description": "ETH Address of listing creator", - "example": "0xe9b00a87700f660e46b6f5deaa1232836bcc07d3" - }, - "amount": { - "type": "string", - "description": "Amount of token included in the listing", - "example": "1" - } - }, - "required": [ - "listing_id", - "price_details", - "creator", - "token_id", - "contract_address", - "amount" - ] - }, - "Bid": { - "type": "object", - "properties": { - "bid_id": { - "type": "string", - "description": "Global Order identifier", - "example": "018792C9-4AD7-8EC4-4038-9E05C598534A" - }, - "price_details": { - "$ref": "#/components/schemas/MarketPriceDetails" - }, - "token_id": { - "type": "string", - "nullable": true, - "description": "Token ID. Null for collection bids that can be fulfilled by any asset in the collection", - "example": "1" - }, - "contract_address": { - "type": "string", - "description": "ETH Address of collection that the asset belongs to", - "example": "0xe9b00a87700f660e46b6f5deaa1232836bcc07d3" - }, - "creator": { - "type": "string", - "description": "ETH Address of listing creator", - "example": "0xe9b00a87700f660e46b6f5deaa1232836bcc07d3" - }, - "amount": { - "type": "string", - "description": "Amount of token included in the listing", - "example": "1" - } - }, - "required": [ - "bid_id", - "price_details", - "creator", - "token_id", - "contract_address", - "amount" - ] - }, - "LastTrade": { - "type": "object", - "nullable": true, - "description": "Most recent trade", - "properties": { - "trade_id": { - "type": "string", - "description": "Trade ID", - "format": "uuid", - "example": "4e28df8d-f65c-4c11-ba04-6a9dd47b179b" - }, - "contract_address": { - "type": "string", - "description": "ETH Address of collection that the asset belongs to", - "example": "0xe9b00a87700f660e46b6f5deaa1232836bcc07d3" - }, - "token_id": { - "type": "string", - "description": "Token id of the traded asset (uint256 as string)", - "example": "1" - }, - "price_details": { - "type": "array", - "description": "Price details, list of payments involved in this trade", - "items": { - "$ref": "#/components/schemas/MarketPriceDetails" - } - }, - "amount": { - "type": "string", - "description": "Amount of the trade (uint256 as string)", - "example": "1" - }, - "created_at": { - "type": "string", - "format": "date-time", - "description": "When the trade was created", - "example": "2022-08-16T17:43:26.991388Z" - } - }, - "required": [ - "trade_id", - "token_id", - "contract_address", - "price_details", - "amount", - "created_at" - ] - }, - "Market": { - "type": "object", - "description": "Market data", - "properties": { - "floor_listing": { - "description": "Cheapest active listing", - "nullable": true, - "allOf": [ - { - "$ref": "#/components/schemas/Listing" - } - ] - }, - "top_bid": { - "description": "Highest active big", - "nullable": true, - "allOf": [ - { - "$ref": "#/components/schemas/Bid" - } - ] - }, - "last_trade": { - "$ref": "#/components/schemas/LastTrade" - } - }, - "required": [ - "top_bid", - "floor_listing", - "last_trade" - ] - }, - "NFTBundle": { - "type": "object", - "description": "NFT bundle includes NFT with stack, markets and listings", - "properties": { - "nft_with_stack": { - "$ref": "#/components/schemas/NFTWithStack" - }, - "market": { - "nullable": true, - "allOf": [ - { - "$ref": "#/components/schemas/Market" - } - ] - }, - "listings": { - "type": "array", - "description": "List of open listings for the NFT.", - "maxItems": 10, - "items": { - "$ref": "#/components/schemas/Listing" - } - }, - "bids": { - "type": "array", - "description": "List of open bids for the NFT.", - "maxItems": 10, - "items": { - "$ref": "#/components/schemas/Bid" - } - } - }, - "required": [ - "nft_with_stack", - "market", - "listings", - "bids" - ] - }, - "SearchNFTsResult": { - "type": "object", - "description": "Search NFTs result", - "properties": { - "result": { - "type": "array", - "description": "List of nft bundles", - "items": { - "$ref": "#/components/schemas/NFTBundle" - } - }, - "page": { - "$ref": "#/components/schemas/Page" - } - }, - "required": [ - "result", - "page" - ] - }, - "Stack": { - "type": "object", - "description": "Stack", - "properties": { - "stack_id": { - "type": "string", - "format": "uuid", - "description": "Stack ID" - }, - "chain": { - "$ref": "#/components/schemas/Chain" - }, - "contract_address": { - "type": "string", - "description": "Contract address" - }, - "contract_type": { - "$ref": "#/components/schemas/MarketplaceContractType" - }, - "created_at": { - "type": "string", - "format": "date-time", - "example": "2022-08-16T17:43:26.991388Z", - "description": "When the metadata was created" - }, - "updated_at": { - "type": "string", - "format": "date-time", - "description": "When the metadata was last updated", - "example": "2022-08-16T17:43:26.991388Z" - }, - "name": { - "type": "string", - "nullable": true, - "example": "Sword", - "description": "The name of the NFT" - }, - "description": { - "type": "string", - "nullable": true, - "example": "2022-08-16T17:43:26.991388Z", - "description": "The description of the NFT" - }, - "image": { - "type": "string", - "nullable": true, - "description": "The image url of the NFT", - "example": "https://some-url" - }, - "external_url": { - "type": "string", - "nullable": true, - "description": "The external website link of NFT", - "example": "https://some-url" - }, - "animation_url": { - "type": "string", - "nullable": true, - "description": "The animation url of the NFT", - "example": "https://some-url" - }, - "youtube_url": { - "type": "string", - "nullable": true, - "description": "The youtube URL of NFT", - "example": "https://some-url" - }, - "attributes": { - "type": "array", - "description": "List of Metadata attributes", - "nullable": true, - "items": { - "$ref": "#/components/schemas/NFTMetadataAttribute" - } - } - }, - "required": [ - "stack_id", - "chain", - "contract_address", - "contract_type", - "updated_at", - "created_at", - "name", - "description", - "image", - "external_url", - "animation_url", - "youtube_url", - "attributes" - ] - }, - "StackBundle": { - "type": "object", - "description": "Stack bundle includes stacks, markets and listings", - "properties": { - "stack": { - "$ref": "#/components/schemas/Stack" - }, - "stack_count": { - "type": "integer", - "description": "Total count of NFTs in the stack matching the filter params", - "example": 1 - }, - "market": { - "nullable": true, - "allOf": [ - { - "$ref": "#/components/schemas/Market" - } - ] - }, - "listings": { - "type": "array", - "description": "List of open listings for the stack.", - "maxItems": 10, - "items": { - "$ref": "#/components/schemas/Listing" - } - }, - "bids": { - "type": "array", - "description": "List of open bids for the stack.", - "maxItems": 10, - "items": { - "$ref": "#/components/schemas/Bid" - } - } - }, - "required": [ - "stack", - "stack_count", - "market", - "listings", - "bids" - ] - }, - "SearchStacksResult": { - "type": "object", - "description": "Search stacks result", - "properties": { - "result": { - "type": "array", - "description": "List of stack bundles", - "items": { - "$ref": "#/components/schemas/StackBundle" - } - }, - "page": { - "$ref": "#/components/schemas/Page" - } - }, - "required": [ - "result", - "page" - ] - }, - "StackQuoteResult": { - "type": "object", - "description": "Stack quote result", - "properties": { - "chain": { - "$ref": "#/components/schemas/Chain" - }, - "stack_id": { - "format": "uuid", - "type": "string" - }, - "market_stack": { - "$ref": "#/components/schemas/Market" - }, - "market_collection": { - "$ref": "#/components/schemas/Market" - } - }, - "required": [ - "stack_id", - "market_stack", - "market_collection", - "chain" - ] - }, - "QuotesForStacksResult": { - "type": "object", - "description": "Quotes for stacks result", - "properties": { - "result": { - "type": "array", - "description": "List of quotes", - "items": { - "$ref": "#/components/schemas/StackQuoteResult" - } - }, - "page": { - "$ref": "#/components/schemas/Page" - } - }, - "required": [ - "result", - "page" - ] - }, - "MarketNft": { - "type": "object", - "description": "NFT market data", - "properties": { - "last_trade": { - "$ref": "#/components/schemas/LastTrade" - } - }, - "required": [ - "last_trade" - ] - }, - "NFTQuoteResult": { - "type": "object", - "description": "NFT quote result", - "properties": { - "chain": { - "$ref": "#/components/schemas/Chain" - }, - "token_id": { - "description": "Token id of NFT (uint256 as string)", - "type": "string" - }, - "market_stack": { - "$ref": "#/components/schemas/Market" - }, - "market_nft": { - "$ref": "#/components/schemas/MarketNft" - }, - "market_collection": { - "$ref": "#/components/schemas/Market" - } - }, - "required": [ - "token_id", - "market_stack", - "market_nft", - "market_collection", - "chain" - ] - }, - "QuotesForNFTsResult": { - "type": "object", - "description": "Quotes for NFTs result", - "properties": { - "result": { - "type": "array", - "description": "List of quotes", - "items": { - "$ref": "#/components/schemas/NFTQuoteResult" - } - }, - "page": { - "$ref": "#/components/schemas/Page" - } - }, - "required": [ - "result", - "page" - ] - } - } - } -} \ No newline at end of file From 57728b967a13967b20f734975d7d878740b0c6d4 Mon Sep 17 00:00:00 2001 From: ImmutableJeffrey Date: Mon, 16 Dec 2024 12:43:19 +1000 Subject: [PATCH 22/25] chore: manually update openapi files (#3423) --- .../.openapi-generator-ignore | 23 ++ .../.openapi-generator/FILES | 377 ++++++++++++++++++ .../.openapi-generator/VERSION | 1 + Source/ImmutablezkEVMAPI/Private/APIBid.cpp | 56 +++ .../ImmutablezkEVMAPI/Private/APIMarket.cpp | 5 + .../APIMetadataSearchApiOperations.cpp | 4 + .../Private/APINFTBundle.cpp | 2 + .../Private/APIPricingApiOperations.cpp | 8 + .../Private/APIStackBundle.cpp | 2 + Source/ImmutablezkEVMAPI/Public/APIBid.h | 46 +++ .../Public/APICancelOrdersRequestBody.h | 2 +- Source/ImmutablezkEVMAPI/Public/APIHelpers.h | 31 ++ Source/ImmutablezkEVMAPI/Public/APIMarket.h | 3 + .../Public/APIMetadataSearchApiOperations.h | 2 + .../ImmutablezkEVMAPI/Public/APINFTBundle.h | 5 +- .../Public/APINFTMetadataAttribute.h | 7 +- .../Public/APIPricingApiOperations.h | 4 + .../ImmutablezkEVMAPI/Public/APIStackBundle.h | 3 + .../batch-files/openapitools.json | 7 + 19 files changed, 580 insertions(+), 8 deletions(-) create mode 100644 Source/ImmutablezkEVMAPI/.openapi-generator-ignore create mode 100644 Source/ImmutablezkEVMAPI/.openapi-generator/FILES create mode 100644 Source/ImmutablezkEVMAPI/.openapi-generator/VERSION create mode 100644 Source/ImmutablezkEVMAPI/Private/APIBid.cpp create mode 100644 Source/ImmutablezkEVMAPI/Public/APIBid.h create mode 100644 Source/ImmutablezkEVMAPI/openapi-generator/batch-files/openapitools.json diff --git a/Source/ImmutablezkEVMAPI/.openapi-generator-ignore b/Source/ImmutablezkEVMAPI/.openapi-generator-ignore new file mode 100644 index 00000000..7484ee59 --- /dev/null +++ b/Source/ImmutablezkEVMAPI/.openapi-generator-ignore @@ -0,0 +1,23 @@ +# OpenAPI Generator Ignore +# Generated by openapi-generator https://github.com/openapitools/openapi-generator + +# Use this file to prevent files from being overwritten by the generator. +# The patterns follow closely to .gitignore or .dockerignore. + +# As an example, the C# client generator defines ApiClient.cs. +# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line: +#ApiClient.cs + +# You can match any string of characters against a directory, file or extension with a single asterisk (*): +#foo/*/qux +# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux + +# You can recursively match patterns against a directory, file or extension with a double asterisk (**): +#foo/**/qux +# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux + +# You can also negate patterns with an exclamation (!). +# For example, you can ignore all files in a docs folder with the file extension .md: +#docs/*.md +# Then explicitly reverse the ignore rule for a single file: +#!docs/README.md diff --git a/Source/ImmutablezkEVMAPI/.openapi-generator/FILES b/Source/ImmutablezkEVMAPI/.openapi-generator/FILES new file mode 100644 index 00000000..3e824ccc --- /dev/null +++ b/Source/ImmutablezkEVMAPI/.openapi-generator/FILES @@ -0,0 +1,377 @@ +ImmutablezkEVMAPI.Build.cs +Private/APIAPIError400.cpp +Private/APIAPIError401.cpp +Private/APIAPIError403.cpp +Private/APIAPIError404.cpp +Private/APIAPIError409.cpp +Private/APIAPIError429.cpp +Private/APIAPIError500.cpp +Private/APIAPIError501.cpp +Private/APIActiveOrderStatus.cpp +Private/APIActivitiesApi.cpp +Private/APIActivitiesApiOperations.cpp +Private/APIActivity.cpp +Private/APIActivityAsset.cpp +Private/APIActivityDetails.cpp +Private/APIActivityNFT.cpp +Private/APIActivityNativeToken.cpp +Private/APIActivityToken.cpp +Private/APIActivityType.cpp +Private/APIAssetCollectionItem.cpp +Private/APIAssetVerificationStatus.cpp +Private/APIBaseModel.cpp +Private/APIBasicAPIError.cpp +Private/APIBid.cpp +Private/APIBidResult.cpp +Private/APIBlockchainMetadata.cpp +Private/APIBurn.cpp +Private/APICall.cpp +Private/APICancelOrdersRequestBody.cpp +Private/APICancelOrdersResult.cpp +Private/APICancelOrdersResultData.cpp +Private/APICancelledOrderStatus.cpp +Private/APIChain.cpp +Private/APIChainWithDetails.cpp +Private/APIChainsApi.cpp +Private/APIChainsApiOperations.cpp +Private/APICollection.cpp +Private/APICollectionBidResult.cpp +Private/APICollectionContractType.cpp +Private/APICollectionMetadata.cpp +Private/APICollectionsApi.cpp +Private/APICollectionsApiOperations.cpp +Private/APICraftingApi.cpp +Private/APICraftingApiOperations.cpp +Private/APICreateBidRequestBody.cpp +Private/APICreateCollectionBidRequestBody.cpp +Private/APICreateListingRequestBody.cpp +Private/APICreateMintRequestRequest.cpp +Private/APICreateMintRequestResult.cpp +Private/APIDeposit.cpp +Private/APIERC1155CollectionItem.cpp +Private/APIERC1155Item.cpp +Private/APIERC20Item.cpp +Private/APIERC721CollectionItem.cpp +Private/APIERC721Item.cpp +Private/APIExpiredOrderStatus.cpp +Private/APIFailedOrderCancellation.cpp +Private/APIFee.cpp +Private/APIFillStatus.cpp +Private/APIFilledOrderStatus.cpp +Private/APIFilter.cpp +Private/APIFilterResult.cpp +Private/APIFilterValue.cpp +Private/APIFulfillableOrder.cpp +Private/APIFulfillmentData200Response.cpp +Private/APIFulfillmentData200ResponseResult.cpp +Private/APIFulfillmentDataRequest.cpp +Private/APIGetActivityResult.cpp +Private/APIGetCollectionResult.cpp +Private/APIGetLinkedAddressesRes.cpp +Private/APIGetMetadataResult.cpp +Private/APIGetMintRequestResult.cpp +Private/APIGetNFTResult.cpp +Private/APIGetTokenResult.cpp +Private/APIHelpers.cpp +Private/APIInactiveOrderStatus.cpp +Private/APIItem.cpp +Private/APILastTrade.cpp +Private/APILinkWalletV2Request.cpp +Private/APIListActivitiesResult.cpp +Private/APIListBidsResult.cpp +Private/APIListChainsResult.cpp +Private/APIListCollectionBidsResult.cpp +Private/APIListCollectionOwnersResult.cpp +Private/APIListCollectionsResult.cpp +Private/APIListFiltersResult.cpp +Private/APIListListingsResult.cpp +Private/APIListMetadataResult.cpp +Private/APIListMintRequestsResult.cpp +Private/APIListNFTOwnersResult.cpp +Private/APIListNFTsByOwnerResult.cpp +Private/APIListNFTsResult.cpp +Private/APIListTokensResult.cpp +Private/APIListTradeResult.cpp +Private/APIListing.cpp +Private/APIListingResult.cpp +Private/APIMarket.cpp +Private/APIMarketNft.cpp +Private/APIMarketPriceDetails.cpp +Private/APIMarketPriceDetailsToken.cpp +Private/APIMarketPriceERC20Token.cpp +Private/APIMarketPriceFees.cpp +Private/APIMarketPriceNativeToken.cpp +Private/APIMarketplaceContractType.cpp +Private/APIMetadata.cpp +Private/APIMetadataApi.cpp +Private/APIMetadataApiOperations.cpp +Private/APIMetadataRefreshRateLimitResult.cpp +Private/APIMetadataSearchApi.cpp +Private/APIMetadataSearchApiOperations.cpp +Private/APIMint.cpp +Private/APIMintAsset.cpp +Private/APIMintRequestErrorMessage.cpp +Private/APIMintRequestStatus.cpp +Private/APINFT.cpp +Private/APINFTBundle.cpp +Private/APINFTContractType.cpp +Private/APINFTMetadataAttribute.cpp +Private/APINFTMetadataAttributeValue.cpp +Private/APINFTMetadataRequest.cpp +Private/APINFTOwner.cpp +Private/APINFTQuoteResult.cpp +Private/APINFTSale.cpp +Private/APINFTWithBalance.cpp +Private/APINFTWithOwner.cpp +Private/APINFTWithStack.cpp +Private/APINativeItem.cpp +Private/APINftOwnersApi.cpp +Private/APINftOwnersApiOperations.cpp +Private/APINftsApi.cpp +Private/APINftsApiOperations.cpp +Private/APIOperatorAllowlistContractStatus.cpp +Private/APIOperatorAllowlistContractStatusInternal.cpp +Private/APIOperatorAllowlistStatus.cpp +Private/APIOperatorAllowlistStatusDetails.cpp +Private/APIOperatorAllowlistStatusRequested.cpp +Private/APIOperatorAllowlistStatusUpdated.cpp +Private/APIOrder.cpp +Private/APIOrderStatus.cpp +Private/APIOrderStatusName.cpp +Private/APIOrdersApi.cpp +Private/APIOrdersApiOperations.cpp +Private/APIOrganisationTier.cpp +Private/APIPage.cpp +Private/APIPassportApi.cpp +Private/APIPassportApiOperations.cpp +Private/APIPassportProfileApi.cpp +Private/APIPassportProfileApiOperations.cpp +Private/APIPendingOrderStatus.cpp +Private/APIPricingApi.cpp +Private/APIPricingApiOperations.cpp +Private/APIProtocolData.cpp +Private/APIQuotesForNFTsResult.cpp +Private/APIQuotesForStacksResult.cpp +Private/APIRefreshCollectionMetadataRequest.cpp +Private/APIRefreshCollectionMetadataResult.cpp +Private/APIRefreshMetadataByID.cpp +Private/APIRefreshMetadataByIDRequest.cpp +Private/APIRefreshMetadataByTokenID.cpp +Private/APIRefreshNFTMetadataByTokenIDRequest.cpp +Private/APIRefreshableNFTAttributes.cpp +Private/APISaleFee.cpp +Private/APISalePayment.cpp +Private/APISalePaymentToken.cpp +Private/APISearchNFTsResult.cpp +Private/APISearchStacksResult.cpp +Private/APISignCraftingRequest.cpp +Private/APISignCraftingRequestMultiCaller.cpp +Private/APISignCraftingResult.cpp +Private/APIStack.cpp +Private/APIStackBundle.cpp +Private/APIStackQuoteResult.cpp +Private/APIToken.cpp +Private/APITokenContractType.cpp +Private/APITokensApi.cpp +Private/APITokensApiOperations.cpp +Private/APITrade.cpp +Private/APITradeBlockchainMetadata.cpp +Private/APITradeResult.cpp +Private/APITransfer.cpp +Private/APIUnfulfillableOrder.cpp +Private/APIUserInfo.cpp +Private/APIVerificationRequest.cpp +Private/APIVerificationRequestContractType.cpp +Private/APIVerificationRequestInternal.cpp +Private/APIVerificationRequestStatus.cpp +Private/APIWallet.cpp +Private/APIWithdrawal.cpp +Private/ImmutablezkEVMAPIModule.cpp +Private/ImmutablezkEVMAPIModule.h +Public/APIAPIError400.h +Public/APIAPIError401.h +Public/APIAPIError403.h +Public/APIAPIError404.h +Public/APIAPIError409.h +Public/APIAPIError429.h +Public/APIAPIError500.h +Public/APIAPIError501.h +Public/APIActiveOrderStatus.h +Public/APIActivitiesApi.h +Public/APIActivitiesApiOperations.h +Public/APIActivity.h +Public/APIActivityAsset.h +Public/APIActivityDetails.h +Public/APIActivityNFT.h +Public/APIActivityNativeToken.h +Public/APIActivityToken.h +Public/APIActivityType.h +Public/APIAssetCollectionItem.h +Public/APIAssetVerificationStatus.h +Public/APIBaseModel.h +Public/APIBasicAPIError.h +Public/APIBid.h +Public/APIBidResult.h +Public/APIBlockchainMetadata.h +Public/APIBurn.h +Public/APICall.h +Public/APICancelOrdersRequestBody.h +Public/APICancelOrdersResult.h +Public/APICancelOrdersResultData.h +Public/APICancelledOrderStatus.h +Public/APIChain.h +Public/APIChainWithDetails.h +Public/APIChainsApi.h +Public/APIChainsApiOperations.h +Public/APICollection.h +Public/APICollectionBidResult.h +Public/APICollectionContractType.h +Public/APICollectionMetadata.h +Public/APICollectionsApi.h +Public/APICollectionsApiOperations.h +Public/APICraftingApi.h +Public/APICraftingApiOperations.h +Public/APICreateBidRequestBody.h +Public/APICreateCollectionBidRequestBody.h +Public/APICreateListingRequestBody.h +Public/APICreateMintRequestRequest.h +Public/APICreateMintRequestResult.h +Public/APIDeposit.h +Public/APIERC1155CollectionItem.h +Public/APIERC1155Item.h +Public/APIERC20Item.h +Public/APIERC721CollectionItem.h +Public/APIERC721Item.h +Public/APIExpiredOrderStatus.h +Public/APIFailedOrderCancellation.h +Public/APIFee.h +Public/APIFillStatus.h +Public/APIFilledOrderStatus.h +Public/APIFilter.h +Public/APIFilterResult.h +Public/APIFilterValue.h +Public/APIFulfillableOrder.h +Public/APIFulfillmentData200Response.h +Public/APIFulfillmentData200ResponseResult.h +Public/APIFulfillmentDataRequest.h +Public/APIGetActivityResult.h +Public/APIGetCollectionResult.h +Public/APIGetLinkedAddressesRes.h +Public/APIGetMetadataResult.h +Public/APIGetMintRequestResult.h +Public/APIGetNFTResult.h +Public/APIGetTokenResult.h +Public/APIHelpers.h +Public/APIInactiveOrderStatus.h +Public/APIItem.h +Public/APILastTrade.h +Public/APILinkWalletV2Request.h +Public/APIListActivitiesResult.h +Public/APIListBidsResult.h +Public/APIListChainsResult.h +Public/APIListCollectionBidsResult.h +Public/APIListCollectionOwnersResult.h +Public/APIListCollectionsResult.h +Public/APIListFiltersResult.h +Public/APIListListingsResult.h +Public/APIListMetadataResult.h +Public/APIListMintRequestsResult.h +Public/APIListNFTOwnersResult.h +Public/APIListNFTsByOwnerResult.h +Public/APIListNFTsResult.h +Public/APIListTokensResult.h +Public/APIListTradeResult.h +Public/APIListing.h +Public/APIListingResult.h +Public/APIMarket.h +Public/APIMarketNft.h +Public/APIMarketPriceDetails.h +Public/APIMarketPriceDetailsToken.h +Public/APIMarketPriceERC20Token.h +Public/APIMarketPriceFees.h +Public/APIMarketPriceNativeToken.h +Public/APIMarketplaceContractType.h +Public/APIMetadata.h +Public/APIMetadataApi.h +Public/APIMetadataApiOperations.h +Public/APIMetadataRefreshRateLimitResult.h +Public/APIMetadataSearchApi.h +Public/APIMetadataSearchApiOperations.h +Public/APIMint.h +Public/APIMintAsset.h +Public/APIMintRequestErrorMessage.h +Public/APIMintRequestStatus.h +Public/APINFT.h +Public/APINFTBundle.h +Public/APINFTContractType.h +Public/APINFTMetadataAttribute.h +Public/APINFTMetadataAttributeValue.h +Public/APINFTMetadataRequest.h +Public/APINFTOwner.h +Public/APINFTQuoteResult.h +Public/APINFTSale.h +Public/APINFTWithBalance.h +Public/APINFTWithOwner.h +Public/APINFTWithStack.h +Public/APINativeItem.h +Public/APINftOwnersApi.h +Public/APINftOwnersApiOperations.h +Public/APINftsApi.h +Public/APINftsApiOperations.h +Public/APIOperatorAllowlistContractStatus.h +Public/APIOperatorAllowlistContractStatusInternal.h +Public/APIOperatorAllowlistStatus.h +Public/APIOperatorAllowlistStatusDetails.h +Public/APIOperatorAllowlistStatusRequested.h +Public/APIOperatorAllowlistStatusUpdated.h +Public/APIOrder.h +Public/APIOrderStatus.h +Public/APIOrderStatusName.h +Public/APIOrdersApi.h +Public/APIOrdersApiOperations.h +Public/APIOrganisationTier.h +Public/APIPage.h +Public/APIPassportApi.h +Public/APIPassportApiOperations.h +Public/APIPassportProfileApi.h +Public/APIPassportProfileApiOperations.h +Public/APIPendingOrderStatus.h +Public/APIPricingApi.h +Public/APIPricingApiOperations.h +Public/APIProtocolData.h +Public/APIQuotesForNFTsResult.h +Public/APIQuotesForStacksResult.h +Public/APIRefreshCollectionMetadataRequest.h +Public/APIRefreshCollectionMetadataResult.h +Public/APIRefreshMetadataByID.h +Public/APIRefreshMetadataByIDRequest.h +Public/APIRefreshMetadataByTokenID.h +Public/APIRefreshNFTMetadataByTokenIDRequest.h +Public/APIRefreshableNFTAttributes.h +Public/APISaleFee.h +Public/APISalePayment.h +Public/APISalePaymentToken.h +Public/APISearchNFTsResult.h +Public/APISearchStacksResult.h +Public/APISignCraftingRequest.h +Public/APISignCraftingRequestMultiCaller.h +Public/APISignCraftingResult.h +Public/APIStack.h +Public/APIStackBundle.h +Public/APIStackQuoteResult.h +Public/APIToken.h +Public/APITokenContractType.h +Public/APITokensApi.h +Public/APITokensApiOperations.h +Public/APITrade.h +Public/APITradeBlockchainMetadata.h +Public/APITradeResult.h +Public/APITransfer.h +Public/APIUnfulfillableOrder.h +Public/APIUserInfo.h +Public/APIVerificationRequest.h +Public/APIVerificationRequestContractType.h +Public/APIVerificationRequestInternal.h +Public/APIVerificationRequestStatus.h +Public/APIWallet.h +Public/APIWithdrawal.h diff --git a/Source/ImmutablezkEVMAPI/.openapi-generator/VERSION b/Source/ImmutablezkEVMAPI/.openapi-generator/VERSION new file mode 100644 index 00000000..758bb9c8 --- /dev/null +++ b/Source/ImmutablezkEVMAPI/.openapi-generator/VERSION @@ -0,0 +1 @@ +7.10.0 diff --git a/Source/ImmutablezkEVMAPI/Private/APIBid.cpp b/Source/ImmutablezkEVMAPI/Private/APIBid.cpp new file mode 100644 index 00000000..32cfd3a9 --- /dev/null +++ b/Source/ImmutablezkEVMAPI/Private/APIBid.cpp @@ -0,0 +1,56 @@ +/** + * Immutable zkEVM API + * Immutable Multi Rollup API + * + * OpenAPI spec version: 1.0.0 + * Contact: support@immutable.com + * + * NOTE: This class is auto generated by OpenAPI Generator + * https://github.com/OpenAPITools/openapi-generator + * Do not edit the class manually. + */ + +#include "APIBid.h" + +#include "ImmutablezkEVMAPIModule.h" +#include "APIHelpers.h" + +#include "Templates/SharedPointer.h" + +namespace ImmutablezkEVMAPI +{ + +void APIBid::WriteJson(JsonWriter& Writer) const +{ + Writer->WriteObjectStart(); + Writer->WriteIdentifierPrefix(TEXT("bid_id")); WriteJsonValue(Writer, BidId); + Writer->WriteIdentifierPrefix(TEXT("price_details")); WriteJsonValue(Writer, PriceDetails); + if (TokenId.IsSet()) + { + Writer->WriteIdentifierPrefix(TEXT("token_id")); WriteJsonValue(Writer, TokenId.GetValue()); + } + Writer->WriteIdentifierPrefix(TEXT("contract_address")); WriteJsonValue(Writer, ContractAddress); + Writer->WriteIdentifierPrefix(TEXT("creator")); WriteJsonValue(Writer, Creator); + Writer->WriteIdentifierPrefix(TEXT("amount")); WriteJsonValue(Writer, Amount); + Writer->WriteObjectEnd(); +} + +bool APIBid::FromJson(const TSharedPtr& JsonValue) +{ + const TSharedPtr* Object; + if (!JsonValue->TryGetObject(Object)) + return false; + + bool ParseSuccess = true; + + ParseSuccess &= TryGetJsonValue(*Object, TEXT("bid_id"), BidId); + ParseSuccess &= TryGetJsonValue(*Object, TEXT("price_details"), PriceDetails); + ParseSuccess &= TryGetJsonValue(*Object, TEXT("token_id"), TokenId); + ParseSuccess &= TryGetJsonValue(*Object, TEXT("contract_address"), ContractAddress); + ParseSuccess &= TryGetJsonValue(*Object, TEXT("creator"), Creator); + ParseSuccess &= TryGetJsonValue(*Object, TEXT("amount"), Amount); + + return ParseSuccess; +} + +} diff --git a/Source/ImmutablezkEVMAPI/Private/APIMarket.cpp b/Source/ImmutablezkEVMAPI/Private/APIMarket.cpp index ac320576..bc9c17ba 100644 --- a/Source/ImmutablezkEVMAPI/Private/APIMarket.cpp +++ b/Source/ImmutablezkEVMAPI/Private/APIMarket.cpp @@ -27,6 +27,10 @@ void APIMarket::WriteJson(JsonWriter& Writer) const { Writer->WriteIdentifierPrefix(TEXT("floor_listing")); WriteJsonValue(Writer, FloorListing.GetValue()); } + if (TopBid.IsSet()) + { + Writer->WriteIdentifierPrefix(TEXT("top_bid")); WriteJsonValue(Writer, TopBid.GetValue()); + } if (LastTrade.IsSet()) { Writer->WriteIdentifierPrefix(TEXT("last_trade")); WriteJsonValue(Writer, LastTrade.GetValue()); @@ -43,6 +47,7 @@ bool APIMarket::FromJson(const TSharedPtr& JsonValue) bool ParseSuccess = true; ParseSuccess &= TryGetJsonValue(*Object, TEXT("floor_listing"), FloorListing); + ParseSuccess &= TryGetJsonValue(*Object, TEXT("top_bid"), TopBid); ParseSuccess &= TryGetJsonValue(*Object, TEXT("last_trade"), LastTrade); return ParseSuccess; diff --git a/Source/ImmutablezkEVMAPI/Private/APIMetadataSearchApiOperations.cpp b/Source/ImmutablezkEVMAPI/Private/APIMetadataSearchApiOperations.cpp index 75224ca2..d8d179e3 100644 --- a/Source/ImmutablezkEVMAPI/Private/APIMetadataSearchApiOperations.cpp +++ b/Source/ImmutablezkEVMAPI/Private/APIMetadataSearchApiOperations.cpp @@ -234,6 +234,10 @@ FString APIMetadataSearchApi::SearchStacksRequest::ComputePath() const { QueryParams.Add(FString(TEXT("keyword=")) + ToUrlString(Keyword.GetValue())); } + if(PaymentToken.IsSet()) + { + QueryParams.Add(FString(TEXT("payment_token=")) + ToUrlString(PaymentToken.GetValue())); + } if(SortBy.IsSet()) { QueryParams.Add(FString(TEXT("sort_by=")) + ToUrlString(SortBy.GetValue())); diff --git a/Source/ImmutablezkEVMAPI/Private/APINFTBundle.cpp b/Source/ImmutablezkEVMAPI/Private/APINFTBundle.cpp index 4e511781..b4f3ad7e 100644 --- a/Source/ImmutablezkEVMAPI/Private/APINFTBundle.cpp +++ b/Source/ImmutablezkEVMAPI/Private/APINFTBundle.cpp @@ -29,6 +29,7 @@ void APINFTBundle::WriteJson(JsonWriter& Writer) const Writer->WriteIdentifierPrefix(TEXT("market")); WriteJsonValue(Writer, Market.GetValue()); } Writer->WriteIdentifierPrefix(TEXT("listings")); WriteJsonValue(Writer, Listings); + Writer->WriteIdentifierPrefix(TEXT("bids")); WriteJsonValue(Writer, Bids); Writer->WriteObjectEnd(); } @@ -43,6 +44,7 @@ bool APINFTBundle::FromJson(const TSharedPtr& JsonValue) ParseSuccess &= TryGetJsonValue(*Object, TEXT("nft_with_stack"), NftWithStack); ParseSuccess &= TryGetJsonValue(*Object, TEXT("market"), Market); ParseSuccess &= TryGetJsonValue(*Object, TEXT("listings"), Listings); + ParseSuccess &= TryGetJsonValue(*Object, TEXT("bids"), Bids); return ParseSuccess; } diff --git a/Source/ImmutablezkEVMAPI/Private/APIPricingApiOperations.cpp b/Source/ImmutablezkEVMAPI/Private/APIPricingApiOperations.cpp index d4c6db6f..19fda9e5 100644 --- a/Source/ImmutablezkEVMAPI/Private/APIPricingApiOperations.cpp +++ b/Source/ImmutablezkEVMAPI/Private/APIPricingApiOperations.cpp @@ -33,6 +33,10 @@ FString APIPricingApi::QuotesForNFTsRequest::ComputePath() const TArray QueryParams; QueryParams.Add(CollectionToUrlString_multi(TokenId, TEXT("token_id"))); + if(PaymentToken.IsSet()) + { + QueryParams.Add(FString(TEXT("payment_token=")) + ToUrlString(PaymentToken.GetValue())); + } if(PageCursor.IsSet()) { QueryParams.Add(FString(TEXT("page_cursor=")) + ToUrlString(PageCursor.GetValue())); @@ -96,6 +100,10 @@ FString APIPricingApi::QuotesForStacksRequest::ComputePath() const TArray QueryParams; QueryParams.Add(CollectionToUrlString_multi(StackId, TEXT("stack_id"))); + if(PaymentToken.IsSet()) + { + QueryParams.Add(FString(TEXT("payment_token=")) + ToUrlString(PaymentToken.GetValue())); + } if(PageCursor.IsSet()) { QueryParams.Add(FString(TEXT("page_cursor=")) + ToUrlString(PageCursor.GetValue())); diff --git a/Source/ImmutablezkEVMAPI/Private/APIStackBundle.cpp b/Source/ImmutablezkEVMAPI/Private/APIStackBundle.cpp index 84638b05..4aff994c 100644 --- a/Source/ImmutablezkEVMAPI/Private/APIStackBundle.cpp +++ b/Source/ImmutablezkEVMAPI/Private/APIStackBundle.cpp @@ -30,6 +30,7 @@ void APIStackBundle::WriteJson(JsonWriter& Writer) const Writer->WriteIdentifierPrefix(TEXT("market")); WriteJsonValue(Writer, Market.GetValue()); } Writer->WriteIdentifierPrefix(TEXT("listings")); WriteJsonValue(Writer, Listings); + Writer->WriteIdentifierPrefix(TEXT("bids")); WriteJsonValue(Writer, Bids); Writer->WriteObjectEnd(); } @@ -45,6 +46,7 @@ bool APIStackBundle::FromJson(const TSharedPtr& JsonValue) ParseSuccess &= TryGetJsonValue(*Object, TEXT("stack_count"), StackCount); ParseSuccess &= TryGetJsonValue(*Object, TEXT("market"), Market); ParseSuccess &= TryGetJsonValue(*Object, TEXT("listings"), Listings); + ParseSuccess &= TryGetJsonValue(*Object, TEXT("bids"), Bids); return ParseSuccess; } diff --git a/Source/ImmutablezkEVMAPI/Public/APIBid.h b/Source/ImmutablezkEVMAPI/Public/APIBid.h new file mode 100644 index 00000000..8af746b0 --- /dev/null +++ b/Source/ImmutablezkEVMAPI/Public/APIBid.h @@ -0,0 +1,46 @@ +/** + * Immutable zkEVM API + * Immutable Multi Rollup API + * + * OpenAPI spec version: 1.0.0 + * Contact: support@immutable.com + * + * NOTE: This class is auto generated by OpenAPI Generator + * https://github.com/OpenAPITools/openapi-generator + * Do not edit the class manually. + */ + +#pragma once + +#include "APIBaseModel.h" +#include "APIMarketPriceDetails.h" + +namespace ImmutablezkEVMAPI +{ + +/* + * APIBid + * + * + */ +class IMMUTABLEZKEVMAPI_API APIBid : public Model +{ +public: + virtual ~APIBid() {} + bool FromJson(const TSharedPtr& JsonValue) final; + void WriteJson(JsonWriter& Writer) const final; + + /* Global Order identifier */ + FString BidId; + APIMarketPriceDetails PriceDetails; + /* Token ID. Null for collection bids that can be fulfilled by any asset in the collection */ + TOptional TokenId; + /* ETH Address of collection that the asset belongs to */ + FString ContractAddress; + /* ETH Address of listing creator */ + FString Creator; + /* Amount of token included in the listing */ + FString Amount; +}; + +} diff --git a/Source/ImmutablezkEVMAPI/Public/APICancelOrdersRequestBody.h b/Source/ImmutablezkEVMAPI/Public/APICancelOrdersRequestBody.h index 583312b4..f2cb774a 100644 --- a/Source/ImmutablezkEVMAPI/Public/APICancelOrdersRequestBody.h +++ b/Source/ImmutablezkEVMAPI/Public/APICancelOrdersRequestBody.h @@ -32,7 +32,7 @@ class IMMUTABLEZKEVMAPI_API APICancelOrdersRequestBody : public Model /* Address of the user initiating the cancel request */ FString AccountAddress; /* List of order ids proposed for cancellation */ - TArray Orders; + TSet Orders; /* Signature generated by the user for the specific cancellation request */ FString Signature; }; diff --git a/Source/ImmutablezkEVMAPI/Public/APIHelpers.h b/Source/ImmutablezkEVMAPI/Public/APIHelpers.h index b55adc05..b587a709 100644 --- a/Source/ImmutablezkEVMAPI/Public/APIHelpers.h +++ b/Source/ImmutablezkEVMAPI/Public/APIHelpers.h @@ -295,6 +295,17 @@ inline void WriteJsonValue(JsonWriter& Writer, const TArray& Value) Writer->WriteArrayEnd(); } +template +inline void WriteJsonValue(JsonWriter& Writer, const TSet& Value) +{ + Writer->WriteArrayStart(); + for (const auto& Element : Value) + { + WriteJsonValue(Writer, Element); + } + Writer->WriteArrayEnd(); +} + template inline void WriteJsonValue(JsonWriter& Writer, const TMap& Value) { @@ -424,6 +435,26 @@ inline bool TryGetJsonValue(const TSharedPtr& JsonValue, TArray& return false; } +template +inline bool TryGetJsonValue(const TSharedPtr& JsonValue, TSet& SetValue) +{ + const TArray>* JsonArray; + if (JsonValue->TryGetArray(JsonArray)) + { + bool ParseSuccess = true; + const int32 Count = JsonArray->Num(); + SetValue.Reset(); + for (int i = 0; i < Count; i++) + { + T TmpValue; + ParseSuccess &= TryGetJsonValue((*JsonArray)[i], TmpValue); + SetValue.Emplace(MoveTemp(TmpValue)); + } + return ParseSuccess; + } + return false; +} + template inline bool TryGetJsonValue(const TSharedPtr& JsonValue, TMap& MapValue) { diff --git a/Source/ImmutablezkEVMAPI/Public/APIMarket.h b/Source/ImmutablezkEVMAPI/Public/APIMarket.h index cf61f090..b9516be3 100644 --- a/Source/ImmutablezkEVMAPI/Public/APIMarket.h +++ b/Source/ImmutablezkEVMAPI/Public/APIMarket.h @@ -13,6 +13,7 @@ #pragma once #include "APIBaseModel.h" +#include "APIBid.h" #include "APILastTrade.h" #include "APIListing.h" @@ -33,6 +34,8 @@ class IMMUTABLEZKEVMAPI_API APIMarket : public Model /* Cheapest active listing */ TOptional FloorListing; + /* Highest active big */ + TOptional TopBid; TOptional LastTrade; }; diff --git a/Source/ImmutablezkEVMAPI/Public/APIMetadataSearchApiOperations.h b/Source/ImmutablezkEVMAPI/Public/APIMetadataSearchApiOperations.h index 69d29ec4..0c479149 100644 --- a/Source/ImmutablezkEVMAPI/Public/APIMetadataSearchApiOperations.h +++ b/Source/ImmutablezkEVMAPI/Public/APIMetadataSearchApiOperations.h @@ -117,6 +117,8 @@ class IMMUTABLEZKEVMAPI_API APIMetadataSearchApi::SearchStacksRequest : public R TOptional Traits; /* Keyword to search NFT name and description. Alphanumeric characters only. */ TOptional Keyword; + /* Filters the active listings, bids, floor listing and top bid by the specified payment token, either the address of the payment token contract or 'NATIVE' */ + TOptional PaymentToken; enum class SortByEnum { CheapestFirst, diff --git a/Source/ImmutablezkEVMAPI/Public/APINFTBundle.h b/Source/ImmutablezkEVMAPI/Public/APINFTBundle.h index ceb31131..23621b74 100644 --- a/Source/ImmutablezkEVMAPI/Public/APINFTBundle.h +++ b/Source/ImmutablezkEVMAPI/Public/APINFTBundle.h @@ -13,6 +13,7 @@ #pragma once #include "APIBaseModel.h" +#include "APIBid.h" #include "APIListing.h" #include "APIMarket.h" #include "APINFTWithStack.h" @@ -34,8 +35,10 @@ class IMMUTABLEZKEVMAPI_API APINFTBundle : public Model APINFTWithStack NftWithStack; TOptional Market; - /* List of open listings for the stack. */ + /* List of open listings for the NFT. */ TArray Listings; + /* List of open bids for the NFT. */ + TArray Bids; }; } diff --git a/Source/ImmutablezkEVMAPI/Public/APINFTMetadataAttribute.h b/Source/ImmutablezkEVMAPI/Public/APINFTMetadataAttribute.h index 811e0103..71d9dd9c 100644 --- a/Source/ImmutablezkEVMAPI/Public/APINFTMetadataAttribute.h +++ b/Source/ImmutablezkEVMAPI/Public/APINFTMetadataAttribute.h @@ -44,12 +44,7 @@ class IMMUTABLEZKEVMAPI_API APINFTMetadataAttribute : public Model TOptional DisplayType; /* The metadata trait type */ FString TraitType; - - // -- DIVERGE - //APINFTMetadataAttributeValue Value; - // NOTE! Edited to FString but has to be OpenAPINFTMetadataAttributeValue; Currently OpenAPI generates empty OpenAPINFTMetadataAttributeValue structure - FString Value; - // -- DIVERGE + APINFTMetadataAttributeValue Value; }; } diff --git a/Source/ImmutablezkEVMAPI/Public/APIPricingApiOperations.h b/Source/ImmutablezkEVMAPI/Public/APIPricingApiOperations.h index 2dd9b1a5..2a75b002 100644 --- a/Source/ImmutablezkEVMAPI/Public/APIPricingApiOperations.h +++ b/Source/ImmutablezkEVMAPI/Public/APIPricingApiOperations.h @@ -44,6 +44,8 @@ class IMMUTABLEZKEVMAPI_API APIPricingApi::QuotesForNFTsRequest : public Request FString ContractAddress; /* List of token ids to get pricing data for */ TArray TokenId; + /* Filters the active listings, bids, floor listing and top bid by the specified payment token, either the address of the payment token contract or 'NATIVE'. */ + TOptional PaymentToken; /* Encoded page cursor to retrieve previous or next page. Use the value returned in the response. */ TOptional PageCursor; }; @@ -75,6 +77,8 @@ class IMMUTABLEZKEVMAPI_API APIPricingApi::QuotesForStacksRequest : public Reque FString ContractAddress; /* List of stack ids to get pricing data for */ TArray StackId; + /* Filters the active listings, bids, floor listing and top bid by the specified payment token, either the address of the payment token contract or 'NATIVE'. */ + TOptional PaymentToken; /* Encoded page cursor to retrieve previous or next page. Use the value returned in the response. */ TOptional PageCursor; }; diff --git a/Source/ImmutablezkEVMAPI/Public/APIStackBundle.h b/Source/ImmutablezkEVMAPI/Public/APIStackBundle.h index a3d849a3..4e0cb956 100644 --- a/Source/ImmutablezkEVMAPI/Public/APIStackBundle.h +++ b/Source/ImmutablezkEVMAPI/Public/APIStackBundle.h @@ -13,6 +13,7 @@ #pragma once #include "APIBaseModel.h" +#include "APIBid.h" #include "APIListing.h" #include "APIMarket.h" #include "APIStack.h" @@ -38,6 +39,8 @@ class IMMUTABLEZKEVMAPI_API APIStackBundle : public Model TOptional Market; /* List of open listings for the stack. */ TArray Listings; + /* List of open bids for the stack. */ + TArray Bids; }; } diff --git a/Source/ImmutablezkEVMAPI/openapi-generator/batch-files/openapitools.json b/Source/ImmutablezkEVMAPI/openapi-generator/batch-files/openapitools.json new file mode 100644 index 00000000..f8d07ce1 --- /dev/null +++ b/Source/ImmutablezkEVMAPI/openapi-generator/batch-files/openapitools.json @@ -0,0 +1,7 @@ +{ + "$schema": "./node_modules/@openapitools/openapi-generator-cli/config.schema.json", + "spaces": 2, + "generator-cli": { + "version": "7.10.0" + } +} From b255b6e14a2e0a6af70117b447a93b5b5dee7668 Mon Sep 17 00:00:00 2001 From: ImmutableJeffrey Date: Tue, 14 Jan 2025 14:05:50 +1000 Subject: [PATCH 23/25] feat: implement oneof for data models (#3518) --- .../Private/APIActivityAsset.cpp | 41 +++++---- .../Private/APIActivityDetails.cpp | 83 +++++++++++++----- .../Private/APIAssetCollectionItem.cpp | 35 +++++--- Source/ImmutablezkEVMAPI/Private/APIItem.cpp | 79 +++++++++++++---- .../Private/APIMarketPriceDetailsToken.cpp | 35 ++++---- .../Private/APINFTMetadataAttributeValue.cpp | 38 +++++++-- .../APIOperatorAllowlistStatusDetails.cpp | 39 +++++---- .../Private/APIOrderStatus.cpp | 85 ++++++++++++++----- .../Private/APISalePaymentToken.cpp | 39 +++++---- .../Public/APIActivityAsset.h | 10 +-- .../Public/APIActivityDetails.h | 13 +-- .../Public/APIAssetCollectionItem.h | 7 +- Source/ImmutablezkEVMAPI/Public/APIItem.h | 9 +- .../Public/APIMarketPriceDetailsToken.h | 9 +- .../Public/APINFTMetadataAttributeValue.h | 3 + .../APIOperatorAllowlistStatusDetails.h | 9 +- .../ImmutablezkEVMAPI/Public/APIOrderStatus.h | 13 +-- .../Public/APISalePaymentToken.h | 8 +- .../template/cpp-ue4/model-header.mustache | 17 ++++ .../template/cpp-ue4/model-source.mustache | 22 +++++ 20 files changed, 396 insertions(+), 198 deletions(-) diff --git a/Source/ImmutablezkEVMAPI/Private/APIActivityAsset.cpp b/Source/ImmutablezkEVMAPI/Private/APIActivityAsset.cpp index 0be72ca6..f92940d6 100644 --- a/Source/ImmutablezkEVMAPI/Private/APIActivityAsset.cpp +++ b/Source/ImmutablezkEVMAPI/Private/APIActivityAsset.cpp @@ -22,28 +22,33 @@ namespace ImmutablezkEVMAPI void APIActivityAsset::WriteJson(JsonWriter& Writer) const { - Writer->WriteObjectStart(); - Writer->WriteIdentifierPrefix(TEXT("contract_type")); WriteJsonValue(Writer, ContractType); - Writer->WriteIdentifierPrefix(TEXT("contract_address")); WriteJsonValue(Writer, ContractAddress); - Writer->WriteIdentifierPrefix(TEXT("token_id")); WriteJsonValue(Writer, TokenId); - Writer->WriteIdentifierPrefix(TEXT("amount")); WriteJsonValue(Writer, Amount); - Writer->WriteObjectEnd(); + if (const APIActivityNFT* APIActivityNFTValue = OneOf.TryGet()) + { + WriteJsonValue(Writer, *APIActivityNFTValue); + } + else if (const APIActivityToken* APIActivityTokenValue = OneOf.TryGet()) + { + WriteJsonValue(Writer, *APIActivityTokenValue); + } } bool APIActivityAsset::FromJson(const TSharedPtr& JsonValue) { - const TSharedPtr* Object; - if (!JsonValue->TryGetObject(Object)) - return false; - - bool ParseSuccess = true; - - ParseSuccess &= TryGetJsonValue(*Object, TEXT("contract_type"), ContractType); - ParseSuccess &= TryGetJsonValue(*Object, TEXT("contract_address"), ContractAddress); - ParseSuccess &= TryGetJsonValue(*Object, TEXT("token_id"), TokenId); - ParseSuccess &= TryGetJsonValue(*Object, TEXT("amount"), Amount); - - return ParseSuccess; + APIActivityNFT APIActivityNFTValue; + if (const bool bIsAPIActivityNFT = TryGetJsonValue(JsonValue, APIActivityNFTValue)) + { + OneOf.Set(APIActivityNFTValue); + return true; + } + + APIActivityToken APIActivityTokenValue; + if (const bool bIsAPIActivityToken = TryGetJsonValue(JsonValue, APIActivityTokenValue)) + { + OneOf.Set(APIActivityTokenValue); + return true; + } + + return false; } } diff --git a/Source/ImmutablezkEVMAPI/Private/APIActivityDetails.cpp b/Source/ImmutablezkEVMAPI/Private/APIActivityDetails.cpp index 225379fd..2f179282 100644 --- a/Source/ImmutablezkEVMAPI/Private/APIActivityDetails.cpp +++ b/Source/ImmutablezkEVMAPI/Private/APIActivityDetails.cpp @@ -22,32 +22,77 @@ namespace ImmutablezkEVMAPI void APIActivityDetails::WriteJson(JsonWriter& Writer) const { - Writer->WriteObjectStart(); - Writer->WriteIdentifierPrefix(TEXT("to")); WriteJsonValue(Writer, To); - Writer->WriteIdentifierPrefix(TEXT("amount")); WriteJsonValue(Writer, Amount); - Writer->WriteIdentifierPrefix(TEXT("asset")); WriteJsonValue(Writer, Asset); - Writer->WriteIdentifierPrefix(TEXT("from")); WriteJsonValue(Writer, From); - Writer->WriteIdentifierPrefix(TEXT("order_id")); WriteJsonValue(Writer, OrderId); - Writer->WriteIdentifierPrefix(TEXT("payment")); WriteJsonValue(Writer, Payment); - Writer->WriteObjectEnd(); + if (const APIBurn* APIBurnValue = OneOf.TryGet()) + { + WriteJsonValue(Writer, *APIBurnValue); + } + else if (const APIDeposit* APIDepositValue = OneOf.TryGet()) + { + WriteJsonValue(Writer, *APIDepositValue); + } + else if (const APIMint* APIMintValue = OneOf.TryGet()) + { + WriteJsonValue(Writer, *APIMintValue); + } + else if (const APINFTSale* APINFTSaleValue = OneOf.TryGet()) + { + WriteJsonValue(Writer, *APINFTSaleValue); + } + else if (const APITransfer* APITransferValue = OneOf.TryGet()) + { + WriteJsonValue(Writer, *APITransferValue); + } + else if (const APIWithdrawal* APIWithdrawalValue = OneOf.TryGet()) + { + WriteJsonValue(Writer, *APIWithdrawalValue); + } } bool APIActivityDetails::FromJson(const TSharedPtr& JsonValue) { - const TSharedPtr* Object; - if (!JsonValue->TryGetObject(Object)) - return false; + APIBurn APIBurnValue; + if (const bool bIsAPIBurn = TryGetJsonValue(JsonValue, APIBurnValue)) + { + OneOf.Set(APIBurnValue); + return true; + } - bool ParseSuccess = true; + APIDeposit APIDepositValue; + if (const bool bIsAPIDeposit = TryGetJsonValue(JsonValue, APIDepositValue)) + { + OneOf.Set(APIDepositValue); + return true; + } - ParseSuccess &= TryGetJsonValue(*Object, TEXT("to"), To); - ParseSuccess &= TryGetJsonValue(*Object, TEXT("amount"), Amount); - ParseSuccess &= TryGetJsonValue(*Object, TEXT("asset"), Asset); - ParseSuccess &= TryGetJsonValue(*Object, TEXT("from"), From); - ParseSuccess &= TryGetJsonValue(*Object, TEXT("order_id"), OrderId); - ParseSuccess &= TryGetJsonValue(*Object, TEXT("payment"), Payment); + APIMint APIMintValue; + if (const bool bIsAPIMint = TryGetJsonValue(JsonValue, APIMintValue)) + { + OneOf.Set(APIMintValue); + return true; + } - return ParseSuccess; + APINFTSale APINFTSaleValue; + if (const bool bIsAPINFTSale = TryGetJsonValue(JsonValue, APINFTSaleValue)) + { + OneOf.Set(APINFTSaleValue); + return true; + } + + APITransfer APITransferValue; + if (const bool bIsAPITransfer = TryGetJsonValue(JsonValue, APITransferValue)) + { + OneOf.Set(APITransferValue); + return true; + } + + APIWithdrawal APIWithdrawalValue; + if (const bool bIsAPIWithdrawal = TryGetJsonValue(JsonValue, APIWithdrawalValue)) + { + OneOf.Set(APIWithdrawalValue); + return true; + } + + return false; } } diff --git a/Source/ImmutablezkEVMAPI/Private/APIAssetCollectionItem.cpp b/Source/ImmutablezkEVMAPI/Private/APIAssetCollectionItem.cpp index 3328839b..25c2191c 100644 --- a/Source/ImmutablezkEVMAPI/Private/APIAssetCollectionItem.cpp +++ b/Source/ImmutablezkEVMAPI/Private/APIAssetCollectionItem.cpp @@ -75,26 +75,33 @@ inline bool TryGetJsonValue(const TSharedPtr& JsonValue, APIAssetCol void APIAssetCollectionItem::WriteJson(JsonWriter& Writer) const { - Writer->WriteObjectStart(); - Writer->WriteIdentifierPrefix(TEXT("type")); WriteJsonValue(Writer, Type); - Writer->WriteIdentifierPrefix(TEXT("contract_address")); WriteJsonValue(Writer, ContractAddress); - Writer->WriteIdentifierPrefix(TEXT("amount")); WriteJsonValue(Writer, Amount); - Writer->WriteObjectEnd(); + if (const APIERC1155CollectionItem* APIERC1155CollectionItemValue = OneOf.TryGet()) + { + WriteJsonValue(Writer, *APIERC1155CollectionItemValue); + } + else if (const APIERC721CollectionItem* APIERC721CollectionItemValue = OneOf.TryGet()) + { + WriteJsonValue(Writer, *APIERC721CollectionItemValue); + } } bool APIAssetCollectionItem::FromJson(const TSharedPtr& JsonValue) { - const TSharedPtr* Object; - if (!JsonValue->TryGetObject(Object)) - return false; - - bool ParseSuccess = true; + APIERC1155CollectionItem APIERC1155CollectionItemValue; + if (const bool bIsAPIERC1155CollectionItem = TryGetJsonValue(JsonValue, APIERC1155CollectionItemValue)) + { + OneOf.Set(APIERC1155CollectionItemValue); + return true; + } - ParseSuccess &= TryGetJsonValue(*Object, TEXT("type"), Type); - ParseSuccess &= TryGetJsonValue(*Object, TEXT("contract_address"), ContractAddress); - ParseSuccess &= TryGetJsonValue(*Object, TEXT("amount"), Amount); + APIERC721CollectionItem APIERC721CollectionItemValue; + if (const bool bIsAPIERC721CollectionItem = TryGetJsonValue(JsonValue, APIERC721CollectionItemValue)) + { + OneOf.Set(APIERC721CollectionItemValue); + return true; + } - return ParseSuccess; + return false; } } diff --git a/Source/ImmutablezkEVMAPI/Private/APIItem.cpp b/Source/ImmutablezkEVMAPI/Private/APIItem.cpp index 622b8de4..a53d77d5 100644 --- a/Source/ImmutablezkEVMAPI/Private/APIItem.cpp +++ b/Source/ImmutablezkEVMAPI/Private/APIItem.cpp @@ -87,28 +87,77 @@ inline bool TryGetJsonValue(const TSharedPtr& JsonValue, APIItem::Ty void APIItem::WriteJson(JsonWriter& Writer) const { - Writer->WriteObjectStart(); - Writer->WriteIdentifierPrefix(TEXT("type")); WriteJsonValue(Writer, Type); - Writer->WriteIdentifierPrefix(TEXT("amount")); WriteJsonValue(Writer, Amount); - Writer->WriteIdentifierPrefix(TEXT("contract_address")); WriteJsonValue(Writer, ContractAddress); - Writer->WriteIdentifierPrefix(TEXT("token_id")); WriteJsonValue(Writer, TokenId); - Writer->WriteObjectEnd(); + if (const APIERC1155CollectionItem* APIERC1155CollectionItemValue = OneOf.TryGet()) + { + WriteJsonValue(Writer, *APIERC1155CollectionItemValue); + } + else if (const APIERC1155Item* APIERC1155ItemValue = OneOf.TryGet()) + { + WriteJsonValue(Writer, *APIERC1155ItemValue); + } + else if (const APIERC20Item* APIERC20ItemValue = OneOf.TryGet()) + { + WriteJsonValue(Writer, *APIERC20ItemValue); + } + else if (const APIERC721CollectionItem* APIERC721CollectionItemValue = OneOf.TryGet()) + { + WriteJsonValue(Writer, *APIERC721CollectionItemValue); + } + else if (const APIERC721Item* APIERC721ItemValue = OneOf.TryGet()) + { + WriteJsonValue(Writer, *APIERC721ItemValue); + } + else if (const APINativeItem* APINativeItemValue = OneOf.TryGet()) + { + WriteJsonValue(Writer, *APINativeItemValue); + } } bool APIItem::FromJson(const TSharedPtr& JsonValue) { - const TSharedPtr* Object; - if (!JsonValue->TryGetObject(Object)) - return false; + APIERC1155CollectionItem APIERC1155CollectionItemValue; + if (const bool bIsAPIERC1155CollectionItem = TryGetJsonValue(JsonValue, APIERC1155CollectionItemValue)) + { + OneOf.Set(APIERC1155CollectionItemValue); + return true; + } - bool ParseSuccess = true; + APIERC1155Item APIERC1155ItemValue; + if (const bool bIsAPIERC1155Item = TryGetJsonValue(JsonValue, APIERC1155ItemValue)) + { + OneOf.Set(APIERC1155ItemValue); + return true; + } + + APIERC20Item APIERC20ItemValue; + if (const bool bIsAPIERC20Item = TryGetJsonValue(JsonValue, APIERC20ItemValue)) + { + OneOf.Set(APIERC20ItemValue); + return true; + } + + APIERC721CollectionItem APIERC721CollectionItemValue; + if (const bool bIsAPIERC721CollectionItem = TryGetJsonValue(JsonValue, APIERC721CollectionItemValue)) + { + OneOf.Set(APIERC721CollectionItemValue); + return true; + } - ParseSuccess &= TryGetJsonValue(*Object, TEXT("type"), Type); - ParseSuccess &= TryGetJsonValue(*Object, TEXT("amount"), Amount); - ParseSuccess &= TryGetJsonValue(*Object, TEXT("contract_address"), ContractAddress); - ParseSuccess &= TryGetJsonValue(*Object, TEXT("token_id"), TokenId); + APIERC721Item APIERC721ItemValue; + if (const bool bIsAPIERC721Item = TryGetJsonValue(JsonValue, APIERC721ItemValue)) + { + OneOf.Set(APIERC721ItemValue); + return true; + } - return ParseSuccess; + APINativeItem APINativeItemValue; + if (const bool bIsAPINativeItem = TryGetJsonValue(JsonValue, APINativeItemValue)) + { + OneOf.Set(APINativeItemValue); + return true; + } + + return false; } } diff --git a/Source/ImmutablezkEVMAPI/Private/APIMarketPriceDetailsToken.cpp b/Source/ImmutablezkEVMAPI/Private/APIMarketPriceDetailsToken.cpp index a89af937..c57939c7 100644 --- a/Source/ImmutablezkEVMAPI/Private/APIMarketPriceDetailsToken.cpp +++ b/Source/ImmutablezkEVMAPI/Private/APIMarketPriceDetailsToken.cpp @@ -75,34 +75,33 @@ inline bool TryGetJsonValue(const TSharedPtr& JsonValue, APIMarketPr void APIMarketPriceDetailsToken::WriteJson(JsonWriter& Writer) const { - Writer->WriteObjectStart(); - Writer->WriteIdentifierPrefix(TEXT("type")); WriteJsonValue(Writer, Type); - if (Symbol.IsSet()) + if (const APIMarketPriceERC20Token* APIMarketPriceERC20TokenValue = OneOf.TryGet()) { - Writer->WriteIdentifierPrefix(TEXT("symbol")); WriteJsonValue(Writer, Symbol.GetValue()); + WriteJsonValue(Writer, *APIMarketPriceERC20TokenValue); } - Writer->WriteIdentifierPrefix(TEXT("contract_address")); WriteJsonValue(Writer, ContractAddress); - if (Decimals.IsSet()) + else if (const APIMarketPriceNativeToken* APIMarketPriceNativeTokenValue = OneOf.TryGet()) { - Writer->WriteIdentifierPrefix(TEXT("decimals")); WriteJsonValue(Writer, Decimals.GetValue()); + WriteJsonValue(Writer, *APIMarketPriceNativeTokenValue); } - Writer->WriteObjectEnd(); } bool APIMarketPriceDetailsToken::FromJson(const TSharedPtr& JsonValue) { - const TSharedPtr* Object; - if (!JsonValue->TryGetObject(Object)) - return false; - - bool ParseSuccess = true; + APIMarketPriceERC20Token APIMarketPriceERC20TokenValue; + if (const bool bIsAPIMarketPriceERC20Token = TryGetJsonValue(JsonValue, APIMarketPriceERC20TokenValue)) + { + OneOf.Set(APIMarketPriceERC20TokenValue); + return true; + } - ParseSuccess &= TryGetJsonValue(*Object, TEXT("type"), Type); - ParseSuccess &= TryGetJsonValue(*Object, TEXT("symbol"), Symbol); - ParseSuccess &= TryGetJsonValue(*Object, TEXT("contract_address"), ContractAddress); - ParseSuccess &= TryGetJsonValue(*Object, TEXT("decimals"), Decimals); + APIMarketPriceNativeToken APIMarketPriceNativeTokenValue; + if (const bool bIsAPIMarketPriceNativeToken = TryGetJsonValue(JsonValue, APIMarketPriceNativeTokenValue)) + { + OneOf.Set(APIMarketPriceNativeTokenValue); + return true; + } - return ParseSuccess; + return false; } } diff --git a/Source/ImmutablezkEVMAPI/Private/APINFTMetadataAttributeValue.cpp b/Source/ImmutablezkEVMAPI/Private/APINFTMetadataAttributeValue.cpp index 57b08738..3b624e57 100644 --- a/Source/ImmutablezkEVMAPI/Private/APINFTMetadataAttributeValue.cpp +++ b/Source/ImmutablezkEVMAPI/Private/APINFTMetadataAttributeValue.cpp @@ -22,20 +22,44 @@ namespace ImmutablezkEVMAPI void APINFTMetadataAttributeValue::WriteJson(JsonWriter& Writer) const { - Writer->WriteObjectStart(); - Writer->WriteObjectEnd(); + if (const FString* FStringValue = OneOf.TryGet()) + { + WriteJsonValue(Writer, *FStringValue); + } + else if (const bool* BoolValue = OneOf.TryGet()) + { + WriteJsonValue(Writer, *BoolValue); + } + else if (const double* DoubleValue = OneOf.TryGet()) + { + WriteJsonValue(Writer, *DoubleValue); + } } bool APINFTMetadataAttributeValue::FromJson(const TSharedPtr& JsonValue) { - const TSharedPtr* Object; - if (!JsonValue->TryGetObject(Object)) - return false; + FString FStringValue; + if (const bool bIsFString = TryGetJsonValue(JsonValue, FStringValue)) + { + OneOf.Set(FStringValue); + return true; + } - bool ParseSuccess = true; + bool BoolValue; + if (const bool bIsBool = TryGetJsonValue(JsonValue, BoolValue)) + { + OneOf.Set(BoolValue); + return true; + } + double DoubleValue; + if (const bool bIsDouble = TryGetJsonValue(JsonValue, DoubleValue)) + { + OneOf.Set(DoubleValue); + return true; + } - return ParseSuccess; + return false; } } diff --git a/Source/ImmutablezkEVMAPI/Private/APIOperatorAllowlistStatusDetails.cpp b/Source/ImmutablezkEVMAPI/Private/APIOperatorAllowlistStatusDetails.cpp index 8553acc1..170b64e0 100644 --- a/Source/ImmutablezkEVMAPI/Private/APIOperatorAllowlistStatusDetails.cpp +++ b/Source/ImmutablezkEVMAPI/Private/APIOperatorAllowlistStatusDetails.cpp @@ -22,26 +22,33 @@ namespace ImmutablezkEVMAPI void APIOperatorAllowlistStatusDetails::WriteJson(JsonWriter& Writer) const { - Writer->WriteObjectStart(); - Writer->WriteIdentifierPrefix(TEXT("purpose")); WriteJsonValue(Writer, Purpose); - Writer->WriteIdentifierPrefix(TEXT("is_settlement_contract")); WriteJsonValue(Writer, IsSettlementContract); - Writer->WriteIdentifierPrefix(TEXT("reason")); WriteJsonValue(Writer, Reason); - Writer->WriteObjectEnd(); + if (const APIOperatorAllowlistStatusRequested* APIOperatorAllowlistStatusRequestedValue = OneOf.TryGet()) + { + WriteJsonValue(Writer, *APIOperatorAllowlistStatusRequestedValue); + } + else if (const APIOperatorAllowlistStatusUpdated* APIOperatorAllowlistStatusUpdatedValue = OneOf.TryGet()) + { + WriteJsonValue(Writer, *APIOperatorAllowlistStatusUpdatedValue); + } } bool APIOperatorAllowlistStatusDetails::FromJson(const TSharedPtr& JsonValue) { - const TSharedPtr* Object; - if (!JsonValue->TryGetObject(Object)) - return false; - - bool ParseSuccess = true; - - ParseSuccess &= TryGetJsonValue(*Object, TEXT("purpose"), Purpose); - ParseSuccess &= TryGetJsonValue(*Object, TEXT("is_settlement_contract"), IsSettlementContract); - ParseSuccess &= TryGetJsonValue(*Object, TEXT("reason"), Reason); - - return ParseSuccess; + APIOperatorAllowlistStatusRequested APIOperatorAllowlistStatusRequestedValue; + if (const bool bIsAPIOperatorAllowlistStatusRequested = TryGetJsonValue(JsonValue, APIOperatorAllowlistStatusRequestedValue)) + { + OneOf.Set(APIOperatorAllowlistStatusRequestedValue); + return true; + } + + APIOperatorAllowlistStatusUpdated APIOperatorAllowlistStatusUpdatedValue; + if (const bool bIsAPIOperatorAllowlistStatusUpdated = TryGetJsonValue(JsonValue, APIOperatorAllowlistStatusUpdatedValue)) + { + OneOf.Set(APIOperatorAllowlistStatusUpdatedValue); + return true; + } + + return false; } } diff --git a/Source/ImmutablezkEVMAPI/Private/APIOrderStatus.cpp b/Source/ImmutablezkEVMAPI/Private/APIOrderStatus.cpp index fb4db253..70781e8d 100644 --- a/Source/ImmutablezkEVMAPI/Private/APIOrderStatus.cpp +++ b/Source/ImmutablezkEVMAPI/Private/APIOrderStatus.cpp @@ -128,34 +128,77 @@ inline bool TryGetJsonValue(const TSharedPtr& JsonValue, APIOrderSta void APIOrderStatus::WriteJson(JsonWriter& Writer) const { - Writer->WriteObjectStart(); - Writer->WriteIdentifierPrefix(TEXT("name")); WriteJsonValue(Writer, Name); - Writer->WriteIdentifierPrefix(TEXT("pending")); WriteJsonValue(Writer, Pending); - Writer->WriteIdentifierPrefix(TEXT("cancellation_type")); WriteJsonValue(Writer, CancellationType); - Writer->WriteIdentifierPrefix(TEXT("evaluated")); WriteJsonValue(Writer, Evaluated); - Writer->WriteIdentifierPrefix(TEXT("started")); WriteJsonValue(Writer, Started); - Writer->WriteIdentifierPrefix(TEXT("sufficient_approvals")); WriteJsonValue(Writer, SufficientApprovals); - Writer->WriteIdentifierPrefix(TEXT("sufficient_balances")); WriteJsonValue(Writer, SufficientBalances); - Writer->WriteObjectEnd(); + if (const APIActiveOrderStatus* APIActiveOrderStatusValue = OneOf.TryGet()) + { + WriteJsonValue(Writer, *APIActiveOrderStatusValue); + } + else if (const APICancelledOrderStatus* APICancelledOrderStatusValue = OneOf.TryGet()) + { + WriteJsonValue(Writer, *APICancelledOrderStatusValue); + } + else if (const APIExpiredOrderStatus* APIExpiredOrderStatusValue = OneOf.TryGet()) + { + WriteJsonValue(Writer, *APIExpiredOrderStatusValue); + } + else if (const APIFilledOrderStatus* APIFilledOrderStatusValue = OneOf.TryGet()) + { + WriteJsonValue(Writer, *APIFilledOrderStatusValue); + } + else if (const APIInactiveOrderStatus* APIInactiveOrderStatusValue = OneOf.TryGet()) + { + WriteJsonValue(Writer, *APIInactiveOrderStatusValue); + } + else if (const APIPendingOrderStatus* APIPendingOrderStatusValue = OneOf.TryGet()) + { + WriteJsonValue(Writer, *APIPendingOrderStatusValue); + } } bool APIOrderStatus::FromJson(const TSharedPtr& JsonValue) { - const TSharedPtr* Object; - if (!JsonValue->TryGetObject(Object)) - return false; + APIActiveOrderStatus APIActiveOrderStatusValue; + if (const bool bIsAPIActiveOrderStatus = TryGetJsonValue(JsonValue, APIActiveOrderStatusValue)) + { + OneOf.Set(APIActiveOrderStatusValue); + return true; + } - bool ParseSuccess = true; + APICancelledOrderStatus APICancelledOrderStatusValue; + if (const bool bIsAPICancelledOrderStatus = TryGetJsonValue(JsonValue, APICancelledOrderStatusValue)) + { + OneOf.Set(APICancelledOrderStatusValue); + return true; + } + + APIExpiredOrderStatus APIExpiredOrderStatusValue; + if (const bool bIsAPIExpiredOrderStatus = TryGetJsonValue(JsonValue, APIExpiredOrderStatusValue)) + { + OneOf.Set(APIExpiredOrderStatusValue); + return true; + } + + APIFilledOrderStatus APIFilledOrderStatusValue; + if (const bool bIsAPIFilledOrderStatus = TryGetJsonValue(JsonValue, APIFilledOrderStatusValue)) + { + OneOf.Set(APIFilledOrderStatusValue); + return true; + } - ParseSuccess &= TryGetJsonValue(*Object, TEXT("name"), Name); - ParseSuccess &= TryGetJsonValue(*Object, TEXT("pending"), Pending); - ParseSuccess &= TryGetJsonValue(*Object, TEXT("cancellation_type"), CancellationType); - ParseSuccess &= TryGetJsonValue(*Object, TEXT("evaluated"), Evaluated); - ParseSuccess &= TryGetJsonValue(*Object, TEXT("started"), Started); - ParseSuccess &= TryGetJsonValue(*Object, TEXT("sufficient_approvals"), SufficientApprovals); - ParseSuccess &= TryGetJsonValue(*Object, TEXT("sufficient_balances"), SufficientBalances); + APIInactiveOrderStatus APIInactiveOrderStatusValue; + if (const bool bIsAPIInactiveOrderStatus = TryGetJsonValue(JsonValue, APIInactiveOrderStatusValue)) + { + OneOf.Set(APIInactiveOrderStatusValue); + return true; + } - return ParseSuccess; + APIPendingOrderStatus APIPendingOrderStatusValue; + if (const bool bIsAPIPendingOrderStatus = TryGetJsonValue(JsonValue, APIPendingOrderStatusValue)) + { + OneOf.Set(APIPendingOrderStatusValue); + return true; + } + + return false; } } diff --git a/Source/ImmutablezkEVMAPI/Private/APISalePaymentToken.cpp b/Source/ImmutablezkEVMAPI/Private/APISalePaymentToken.cpp index 36b1f067..99bf9701 100644 --- a/Source/ImmutablezkEVMAPI/Private/APISalePaymentToken.cpp +++ b/Source/ImmutablezkEVMAPI/Private/APISalePaymentToken.cpp @@ -22,26 +22,33 @@ namespace ImmutablezkEVMAPI void APISalePaymentToken::WriteJson(JsonWriter& Writer) const { - Writer->WriteObjectStart(); - Writer->WriteIdentifierPrefix(TEXT("contract_type")); WriteJsonValue(Writer, ContractType); - Writer->WriteIdentifierPrefix(TEXT("contract_address")); WriteJsonValue(Writer, ContractAddress); - Writer->WriteIdentifierPrefix(TEXT("symbol")); WriteJsonValue(Writer, Symbol); - Writer->WriteObjectEnd(); + if (const APIActivityNativeToken* APIActivityNativeTokenValue = OneOf.TryGet()) + { + WriteJsonValue(Writer, *APIActivityNativeTokenValue); + } + else if (const APIActivityToken* APIActivityTokenValue = OneOf.TryGet()) + { + WriteJsonValue(Writer, *APIActivityTokenValue); + } } bool APISalePaymentToken::FromJson(const TSharedPtr& JsonValue) { - const TSharedPtr* Object; - if (!JsonValue->TryGetObject(Object)) - return false; - - bool ParseSuccess = true; - - ParseSuccess &= TryGetJsonValue(*Object, TEXT("contract_type"), ContractType); - ParseSuccess &= TryGetJsonValue(*Object, TEXT("contract_address"), ContractAddress); - ParseSuccess &= TryGetJsonValue(*Object, TEXT("symbol"), Symbol); - - return ParseSuccess; + APIActivityNativeToken APIActivityNativeTokenValue; + if (const bool bIsAPIActivityNativeToken = TryGetJsonValue(JsonValue, APIActivityNativeTokenValue)) + { + OneOf.Set(APIActivityNativeTokenValue); + return true; + } + + APIActivityToken APIActivityTokenValue; + if (const bool bIsAPIActivityToken = TryGetJsonValue(JsonValue, APIActivityTokenValue)) + { + OneOf.Set(APIActivityTokenValue); + return true; + } + + return false; } } diff --git a/Source/ImmutablezkEVMAPI/Public/APIActivityAsset.h b/Source/ImmutablezkEVMAPI/Public/APIActivityAsset.h index 989e44b3..447ee3dc 100644 --- a/Source/ImmutablezkEVMAPI/Public/APIActivityAsset.h +++ b/Source/ImmutablezkEVMAPI/Public/APIActivityAsset.h @@ -12,6 +12,8 @@ #pragma once +#include "Misc/TVariant.h" + #include "APIBaseModel.h" #include "APIActivityNFT.h" #include "APIActivityToken.h" @@ -32,13 +34,7 @@ class IMMUTABLEZKEVMAPI_API APIActivityAsset : public Model bool FromJson(const TSharedPtr& JsonValue) final; void WriteJson(JsonWriter& Writer) const final; - APITokenContractType ContractType; - /* The contract address */ - FString ContractAddress; - /* An `uint256` token id as string */ - FString TokenId; - /* (deprecated - will never be filled, use amount on Activity instead) The amount of tokens exchanged */ - FString Amount; + TVariant OneOf; }; } diff --git a/Source/ImmutablezkEVMAPI/Public/APIActivityDetails.h b/Source/ImmutablezkEVMAPI/Public/APIActivityDetails.h index 5ad5660f..4d202a8e 100644 --- a/Source/ImmutablezkEVMAPI/Public/APIActivityDetails.h +++ b/Source/ImmutablezkEVMAPI/Public/APIActivityDetails.h @@ -12,6 +12,8 @@ #pragma once +#include "Misc/TVariant.h" + #include "APIBaseModel.h" #include "APIActivityAsset.h" #include "APIBurn.h" @@ -37,16 +39,7 @@ class IMMUTABLEZKEVMAPI_API APIActivityDetails : public Model bool FromJson(const TSharedPtr& JsonValue) final; void WriteJson(JsonWriter& Writer) const final; - /* The account address the asset was deposited to */ - FString To; - /* The amount of assets withdrawn */ - FString Amount; - APIActivityAsset Asset; - /* The account address the asset was withdrawn from */ - FString From; - /* The id of order */ - FString OrderId; - APISalePayment Payment; + TVariant OneOf; }; } diff --git a/Source/ImmutablezkEVMAPI/Public/APIAssetCollectionItem.h b/Source/ImmutablezkEVMAPI/Public/APIAssetCollectionItem.h index 86962802..6afd7636 100644 --- a/Source/ImmutablezkEVMAPI/Public/APIAssetCollectionItem.h +++ b/Source/ImmutablezkEVMAPI/Public/APIAssetCollectionItem.h @@ -12,6 +12,8 @@ #pragma once +#include "Misc/TVariant.h" + #include "APIBaseModel.h" #include "APIERC1155CollectionItem.h" #include "APIERC721CollectionItem.h" @@ -41,10 +43,7 @@ class IMMUTABLEZKEVMAPI_API APIAssetCollectionItem : public Model static bool EnumFromString(const FString& EnumAsString, TypeEnum& EnumValue); /* Token type user is offering, which in this case is ERC721 */ TypeEnum Type; - /* Address of ERC1155 collection */ - FString ContractAddress; - /* A string representing the price at which the user is willing to sell the token. This value is provided in the smallest unit of the token (e.g., wei for Ethereum). */ - FString Amount; + TVariant OneOf; }; } diff --git a/Source/ImmutablezkEVMAPI/Public/APIItem.h b/Source/ImmutablezkEVMAPI/Public/APIItem.h index 175db4ff..b9d12903 100644 --- a/Source/ImmutablezkEVMAPI/Public/APIItem.h +++ b/Source/ImmutablezkEVMAPI/Public/APIItem.h @@ -12,6 +12,8 @@ #pragma once +#include "Misc/TVariant.h" + #include "APIBaseModel.h" #include "APIERC1155CollectionItem.h" #include "APIERC1155Item.h" @@ -49,12 +51,7 @@ class IMMUTABLEZKEVMAPI_API APIItem : public Model static bool EnumFromString(const FString& EnumAsString, TypeEnum& EnumValue); /* Token type user is offering, which in this case is the native IMX token */ TypeEnum Type; - /* A string representing the price at which the user is willing to sell the token. This value is provided in the smallest unit of the token (e.g., wei for Ethereum). */ - FString Amount; - /* Address of ERC1155 collection */ - FString ContractAddress; - /* ID of ERC1155 token */ - FString TokenId; + TVariant OneOf; }; } diff --git a/Source/ImmutablezkEVMAPI/Public/APIMarketPriceDetailsToken.h b/Source/ImmutablezkEVMAPI/Public/APIMarketPriceDetailsToken.h index 6b3e1672..3c258260 100644 --- a/Source/ImmutablezkEVMAPI/Public/APIMarketPriceDetailsToken.h +++ b/Source/ImmutablezkEVMAPI/Public/APIMarketPriceDetailsToken.h @@ -12,6 +12,8 @@ #pragma once +#include "Misc/TVariant.h" + #include "APIBaseModel.h" #include "APIMarketPriceERC20Token.h" #include "APIMarketPriceNativeToken.h" @@ -41,12 +43,7 @@ class IMMUTABLEZKEVMAPI_API APIMarketPriceDetailsToken : public Model static bool EnumFromString(const FString& EnumAsString, TypeEnum& EnumValue); /* Token type user is offering, which in this case is the native IMX token */ TypeEnum Type; - /* The symbol of token */ - TOptional Symbol; - /* Address of ERC20 token */ - FString ContractAddress; - /* The decimals of token */ - TOptional Decimals; + TVariant OneOf; }; } diff --git a/Source/ImmutablezkEVMAPI/Public/APINFTMetadataAttributeValue.h b/Source/ImmutablezkEVMAPI/Public/APINFTMetadataAttributeValue.h index 8ca2d1e1..aee622db 100644 --- a/Source/ImmutablezkEVMAPI/Public/APINFTMetadataAttributeValue.h +++ b/Source/ImmutablezkEVMAPI/Public/APINFTMetadataAttributeValue.h @@ -12,6 +12,8 @@ #pragma once +#include "Misc/TVariant.h" + #include "APIBaseModel.h" namespace ImmutablezkEVMAPI @@ -29,6 +31,7 @@ class IMMUTABLEZKEVMAPI_API APINFTMetadataAttributeValue : public Model bool FromJson(const TSharedPtr& JsonValue) final; void WriteJson(JsonWriter& Writer) const final; + TVariant OneOf; }; } diff --git a/Source/ImmutablezkEVMAPI/Public/APIOperatorAllowlistStatusDetails.h b/Source/ImmutablezkEVMAPI/Public/APIOperatorAllowlistStatusDetails.h index 1b61e093..b4f7f34d 100644 --- a/Source/ImmutablezkEVMAPI/Public/APIOperatorAllowlistStatusDetails.h +++ b/Source/ImmutablezkEVMAPI/Public/APIOperatorAllowlistStatusDetails.h @@ -12,6 +12,8 @@ #pragma once +#include "Misc/TVariant.h" + #include "APIBaseModel.h" #include "APIOperatorAllowlistStatusRequested.h" #include "APIOperatorAllowlistStatusUpdated.h" @@ -31,12 +33,7 @@ class IMMUTABLEZKEVMAPI_API APIOperatorAllowlistStatusDetails : public Model bool FromJson(const TSharedPtr& JsonValue) final; void WriteJson(JsonWriter& Writer) const final; - /* Reason this contract needs to be added */ - FString Purpose; - /* Attestation of whether this contract is a settlement contract */ - bool IsSettlementContract = false; - /* Why this action was performed */ - FString Reason; + TVariant OneOf; }; } diff --git a/Source/ImmutablezkEVMAPI/Public/APIOrderStatus.h b/Source/ImmutablezkEVMAPI/Public/APIOrderStatus.h index 3629c2fb..7e8f7218 100644 --- a/Source/ImmutablezkEVMAPI/Public/APIOrderStatus.h +++ b/Source/ImmutablezkEVMAPI/Public/APIOrderStatus.h @@ -12,6 +12,8 @@ #pragma once +#include "Misc/TVariant.h" + #include "APIBaseModel.h" #include "APIActiveOrderStatus.h" #include "APICancelledOrderStatus.h" @@ -44,8 +46,6 @@ class IMMUTABLEZKEVMAPI_API APIOrderStatus : public Model static bool EnumFromString(const FString& EnumAsString, NameEnum& EnumValue); /* A terminal order status indicating that an order cannot be fulfilled due to expiry. */ NameEnum Name; - /* Whether the cancellation of the order is pending */ - bool Pending = false; enum class CancellationTypeEnum { OnChain, @@ -57,14 +57,7 @@ class IMMUTABLEZKEVMAPI_API APIOrderStatus : public Model static bool EnumFromString(const FString& EnumAsString, CancellationTypeEnum& EnumValue); /* Whether the cancellation was done on-chain or off-chain or as a result of an underfunded account */ CancellationTypeEnum CancellationType; - /* Whether the order has been evaluated after its creation */ - bool Evaluated = false; - /* Whether the order has reached its specified start time */ - bool Started = false; - /* Whether the order offerer has sufficient approvals */ - bool SufficientApprovals = false; - /* Whether the order offerer still has sufficient balance to complete the order */ - bool SufficientBalances = false; + TVariant OneOf; }; } diff --git a/Source/ImmutablezkEVMAPI/Public/APISalePaymentToken.h b/Source/ImmutablezkEVMAPI/Public/APISalePaymentToken.h index eec56681..9a21a467 100644 --- a/Source/ImmutablezkEVMAPI/Public/APISalePaymentToken.h +++ b/Source/ImmutablezkEVMAPI/Public/APISalePaymentToken.h @@ -12,6 +12,8 @@ #pragma once +#include "Misc/TVariant.h" + #include "APIBaseModel.h" #include "APIActivityNativeToken.h" #include "APIActivityToken.h" @@ -32,11 +34,7 @@ class IMMUTABLEZKEVMAPI_API APISalePaymentToken : public Model bool FromJson(const TSharedPtr& JsonValue) final; void WriteJson(JsonWriter& Writer) const final; - APITokenContractType ContractType; - /* The contract address */ - FString ContractAddress; - /* The token symbol */ - FString Symbol; + TVariant OneOf; }; } diff --git a/Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/model-header.mustache b/Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/model-header.mustache index ee709e58..e54a069d 100644 --- a/Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/model-header.mustache +++ b/Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/model-header.mustache @@ -1,6 +1,16 @@ {{>licenseInfo}} #pragma once +{{#models}} + {{#model}} + {{#oneOf}} + {{#-first}} +#include "Misc/TVariant.h" + + {{/-first}} + {{/oneOf}} + {{/model}} +{{/models}} #include "{{modelNamePrefix}}BaseModel.h" {{#imports}}{{{import}}} {{/imports}} @@ -61,10 +71,17 @@ public: {{/description}}{{^required}}TOptional<{{/required}}{{{datatypeWithEnum}}}{{^required}}>{{/required}} {{name}}{{#required}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}{{/required}}; {{/isEnum}} {{^isEnum}} + {{^oneOf}} {{#description}}/* {{{.}}} */ {{/description}}{{^required}}TOptional<{{/required}}{{{datatype}}}{{^required}}>{{/required}} {{name}}{{#required}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}{{/required}}; + {{/oneOf}} {{/isEnum}} {{/vars}} + {{#oneOf}} + {{#-first}} + TVariant<{{#oneOf}}{{{.}}}{{^-last}}, {{/-last}}{{/oneOf}}> OneOf; + {{/-first}} + {{/oneOf}} }; {{/model}} diff --git a/Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/model-source.mustache b/Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/model-source.mustache index 7ab8ea53..7168085c 100644 --- a/Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/model-source.mustache +++ b/Source/ImmutablezkEVMAPI/openapi-generator/template/cpp-ue4/model-source.mustache @@ -136,6 +136,13 @@ void {{classname}}::WriteJson(JsonWriter& Writer) const {{#isString}} WriteJsonValue(Writer, Value); {{/isString}} + {{#oneOf}} + {{^-first}}else {{/-first}}if (const {{.}}* {{#lambda.pascalcase}}{{.}}{{/lambda.pascalcase}}Value = OneOf.TryGet<{{.}}>()) + { + WriteJsonValue(Writer, *{{#lambda.pascalcase}}{{.}}{{/lambda.pascalcase}}Value); + } + {{/oneOf}} + {{^oneOf}} {{^isString}} Writer->WriteObjectStart(); {{#vars}} @@ -151,6 +158,7 @@ void {{classname}}::WriteJson(JsonWriter& Writer) const {{/vars}} Writer->WriteObjectEnd(); {{/isString}} + {{/oneOf}} } bool {{classname}}::FromJson(const TSharedPtr& JsonValue) @@ -158,6 +166,19 @@ bool {{classname}}::FromJson(const TSharedPtr& JsonValue) {{#isString}} return TryGetJsonValue(JsonValue, Value); {{/isString}} + {{#oneOf}} + {{.}} {{#lambda.pascalcase}}{{.}}{{/lambda.pascalcase}}Value; + if (const bool bIs{{#lambda.pascalcase}}{{.}}{{/lambda.pascalcase}} = TryGetJsonValue(JsonValue, {{#lambda.pascalcase}}{{.}}{{/lambda.pascalcase}}Value)) + { + OneOf.Set<{{.}}>({{#lambda.pascalcase}}{{.}}{{/lambda.pascalcase}}Value); + return true; + } + + {{#-last}} + return false; + {{/-last}} + {{/oneOf}} + {{^oneOf}} {{^isString}} const TSharedPtr* Object; if (!JsonValue->TryGetObject(Object)) @@ -176,6 +197,7 @@ bool {{classname}}::FromJson(const TSharedPtr& JsonValue) return ParseSuccess; {{/isString}} + {{/oneOf}} } {{/model}} From adeba165510c9835466c10427417f5ba62c277c8 Mon Sep 17 00:00:00 2001 From: ImmutableJeffrey Date: Tue, 14 Jan 2025 19:34:09 +1000 Subject: [PATCH 24/25] feat: automate the process of updating zkevm repo when it's corresponding openapi changes (#3497) --- .../workflows/update-zkevm-api-package.yml | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 .github/workflows/update-zkevm-api-package.yml diff --git a/.github/workflows/update-zkevm-api-package.yml b/.github/workflows/update-zkevm-api-package.yml new file mode 100644 index 00000000..c612932e --- /dev/null +++ b/.github/workflows/update-zkevm-api-package.yml @@ -0,0 +1,98 @@ +--- +name: Update zkEVM API Package + +on: + workflow_dispatch: + schedule: + - cron: '0 10 * * *' + +env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +jobs: + update-api: + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v3 + + - name: Pull files from Git LFS + run: git lfs pull + + - name: Get current date and time + id: date + run: echo "::set-output name=date::$(date +'%Y-%m-%d-%H-%M-%S')" + + - name: Download remote openapi.json + run: curl -o openapi_remote.json https://imx-openapiv3-mr-sandbox.s3.us-east-2.amazonaws.com/openapi.json + + - name: Ensure local openapi.json exists (if not, assume it's blank) + run: | + if [ ! -f ./Source/ImmutablezkEVMAPI/openapi-generator/openapi.json ]; then + echo "Creating empty openapi.json file..." + mkdir -p ./Source/ImmutablezkEVMAPI/openapi-generator/ + touch ./Source/ImmutablezkEVMAPI/openapi-generator/openapi.json + fi + + - name: Compare remote openapi.json with local openapi.json + id: comparison + run: | + if diff openapi_remote.json ./Source/ImmutablezkEVMAPI/openapi-generator/openapi.json > /dev/null; then + echo "::set-output name=difference::false" + else + echo "::set-output name=difference::true" + fi + + - name: NPM install OpenAPI Generator CLI globally + run: npm install -g @openapitools/openapi-generator-cli + + - name: Set execute permission on generate.sh + run: chmod +x ./Source/ImmutablezkEVMAPI/openapi-generator/batch-files/generate.sh + + - name: Convert line endings of generate.sh to Unix format + run: sed -i -e 's/\r$//' ./Source/ImmutablezkEVMAPI/openapi-generator/batch-files/generate.sh + + - name: Generate API if there are differences + if: steps.comparison.outputs.difference == 'true' + run: | + cd ./Source/ImmutablezkEVMAPI/openapi-generator/batch-files + ./generate.sh + cd ../../../../ + + - name: Clean up + if: steps.comparison.outputs.difference == 'true' + run: | + rm openapi_remote.json + + - name: Create a new branch + if: steps.comparison.outputs.difference == 'true' + run: | + git config --global user.email "action@github.com" + git config --global user.name "GitHub Action" + git checkout -b feat/update-zkevm-api-${{ steps.date.outputs.date }} + + - name: Commit changes + id: commit_changes + if: steps.comparison.outputs.difference == 'true' + run: | + git add ./Source/ImmutablezkEVMAPI/ + if [ -n "$(git diff --cached)" ]; then + git commit -m "feat: update immutable zkEVM API package" + echo "commit=true" >> $GITHUB_ENV + else + echo "No changes to commit." + echo "commit=false" >> $GITHUB_ENV + fi + + - name: Push changes + if: env.commit == 'true' + run: | + git push origin feat/update-zkevm-api-${{ steps.date.outputs.date }} + + - name: Create pull request + if: env.commit == 'true' + run: | + gh pr create --title "feat: update immutable zkEVM API package" \ + --body "Update Immutable zkEVM API package" \ + --base main \ + --head feat/update-zkevm-api-${{ steps.date.outputs.date }} \ No newline at end of file From eb2cad29f80cfab99b07d2ae49f398910aca5228 Mon Sep 17 00:00:00 2001 From: ImmutableJeffrey Date: Wed, 15 Jan 2025 07:33:24 +1000 Subject: [PATCH 25/25] fix: linting errors for zkevm api scripts (#3522) --- .github/workflows/update-zkevm-api-package.yml | 18 +++++++++--------- .../openapi-generator/batch-files/generate.sh | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/update-zkevm-api-package.yml b/.github/workflows/update-zkevm-api-package.yml index c612932e..c4d16f4a 100644 --- a/.github/workflows/update-zkevm-api-package.yml +++ b/.github/workflows/update-zkevm-api-package.yml @@ -21,7 +21,7 @@ jobs: - name: Get current date and time id: date - run: echo "::set-output name=date::$(date +'%Y-%m-%d-%H-%M-%S')" + run: echo "name=date::$(date +'%Y-%m-%d-%H-%M-%S')" >> "$GITHUB_OUTPUT" - name: Download remote openapi.json run: curl -o openapi_remote.json https://imx-openapiv3-mr-sandbox.s3.us-east-2.amazonaws.com/openapi.json @@ -37,10 +37,10 @@ jobs: - name: Compare remote openapi.json with local openapi.json id: comparison run: | - if diff openapi_remote.json ./Source/ImmutablezkEVMAPI/openapi-generator/openapi.json > /dev/null; then - echo "::set-output name=difference::false" + if diff "openapi_remote.json" "./Source/ImmutablezkEVMAPI/openapi-generator/openapi.json" > /dev/null; then + echo "name=difference::false" >> "$GITHUB_OUTPUT" else - echo "::set-output name=difference::true" + echo "name=difference::true" >> "$GITHUB_OUTPUT" fi - name: NPM install OpenAPI Generator CLI globally @@ -55,9 +55,9 @@ jobs: - name: Generate API if there are differences if: steps.comparison.outputs.difference == 'true' run: | - cd ./Source/ImmutablezkEVMAPI/openapi-generator/batch-files + cd "./Source/ImmutablezkEVMAPI/openapi-generator/batch-files" ./generate.sh - cd ../../../../ + cd "../../../../" - name: Clean up if: steps.comparison.outputs.difference == 'true' @@ -75,13 +75,13 @@ jobs: id: commit_changes if: steps.comparison.outputs.difference == 'true' run: | - git add ./Source/ImmutablezkEVMAPI/ + git add "./Source/ImmutablezkEVMAPI/" if [ -n "$(git diff --cached)" ]; then git commit -m "feat: update immutable zkEVM API package" - echo "commit=true" >> $GITHUB_ENV + echo "commit=true" >> "$GITHUB_ENV" else echo "No changes to commit." - echo "commit=false" >> $GITHUB_ENV + echo "commit=false" >> "$GITHUB_ENV" fi - name: Push changes diff --git a/Source/ImmutablezkEVMAPI/openapi-generator/batch-files/generate.sh b/Source/ImmutablezkEVMAPI/openapi-generator/batch-files/generate.sh index a6aeef91..2610544e 100644 --- a/Source/ImmutablezkEVMAPI/openapi-generator/batch-files/generate.sh +++ b/Source/ImmutablezkEVMAPI/openapi-generator/batch-files/generate.sh @@ -8,4 +8,4 @@ INPUT="-i https://imx-openapiv3-mr-sandbox.s3.us-east-2.amazonaws.com/openapi.js OUTPUT="-o ../.." ADDITIONAL_PROPERTIES="--additional-properties=modelNamePrefix=API,cppNamespace=ImmutablezkEVMAPI,unrealModuleName=ImmutablezkEVMAPI" -$OPENAPI_GENERATOR_CLI generate $GENERATOR $TEMPLATE $INPUT $OUTPUT $ADDITIONAL_PROPERTIES \ No newline at end of file +$OPENAPI_GENERATOR_CLI generate "$GENERATOR" "$TEMPLATE" "$INPUT" "$OUTPUT" "$ADDITIONAL_PROPERTIES" \ No newline at end of file