Skip to content
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Git LFS file not shown
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#include "Checkout/Transak/CallDataCompressor.h"
#include "Misc/Base64.h"
#include "Misc/Compression.h"
#include "GenericPlatform/GenericPlatformHttp.h"

FString FCallDataCompressor::Compress(const FString& In)
{
FTCHARToUTF8 Converter(*In);
const uint8* UncompressedData = reinterpret_cast<const uint8*>(Converter.Get());
int32 UncompressedSize = Converter.Length();

int32 CompressedBufferSize = FCompression::CompressMemoryBound(NAME_Zlib, UncompressedSize);
TArray<uint8> CompressedData;
CompressedData.SetNumUninitialized(CompressedBufferSize);

int32 CompressedSize = CompressedBufferSize;
bool bSuccess = FCompression::CompressMemory(
NAME_Zlib,
CompressedData.GetData(),
CompressedSize,
UncompressedData,
UncompressedSize,
COMPRESS_Default
);

if (!bSuccess)
{
UE_LOG(LogTemp, Error, TEXT("CallData compression failed"));
return TEXT("CompressionError");
}

CompressedData.SetNum(CompressedSize);
FString Base64 = FBase64::Encode(CompressedData);
return FGenericPlatformHttp::UrlEncode(Base64);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
#include "Checkout/Transak/TransakCheckout.h"
#include "Misc/DateTime.h"
#include "Misc/Guid.h"
#include "Misc/Base64.h"
#include "Containers/StringConv.h"
#include "Misc/DefaultValueHelper.h"
#include "Engine/Engine.h"
#include "Checkout/Transak/CallDataCompressor.h"
#include "Checkout/Transak/TransakNFTDataEncoder.h"
#include "Checkout/Structs/TransactionStep.h"
#include "Util/SequenceSupport.h"
void UTransakCheckout::Initialize(const FString& InWalletAddress, ENetwork InNetwork)
{
WalletAddress = InWalletAddress;
Network = InNetwork;
}
FString UTransakCheckout::GetNFTCheckoutLink(const FTransakNFTData& Item, const FString& CallData, const FTransakContractId& ContractId)
{
const FString NetworkString = UEnum::GetValueAsString(Network);

if (ContractId.Chain != NetworkString)
{
UE_LOG(LogTemp, Error, TEXT("Chain mismatch: ContractId.Chain = %s, Network = %s"), *ContractId.Chain, *NetworkString);
return TEXT("Error: Chain mismatch. Transak Checkout object network must match the TransakContractId chain.");
}

const FString TransakCallData = FCallDataCompressor::Compress(CallData);

const FString TransakNftDataEncoded = FTransakNFTDataEncoder::Encode(Item);

const FString BaseURL = TEXT("https://global.transak.com/");

const FString TContractId = ContractId.Id;
const FString TransakCryptocurrencyCode = ContractId.PriceTokenSymbol;
const int64 EstimatedGasLimit = 500000;

if (WalletAddress.IsEmpty())
{
return TEXT("Error: Transak Checkout object wallet address is missing");
}


const int64 Timestamp = FDateTime::UtcNow().ToUnixTimestamp();

const FString PartnerOrderId = FString::Printf(TEXT("%s-%lld"), *WalletAddress, Timestamp);

FString address = *WalletAddress;

FString URL =
BaseURL + TEXT("?apiKey=5911d9ec-46b5-48fa-a755-d59a715ff0cf")
+ TEXT("&isNFT=true")
+ TEXT("&calldata=") + TransakCallData
+ TEXT("&contractId=") + TContractId
+ TEXT("&cryptoCurrencyCode=") + TransakCryptocurrencyCode
+ TEXT("&estimatedGasLimit=") + FString::Printf(TEXT("%lld"), EstimatedGasLimit)
+ TEXT("&nftData=") + TransakNftDataEncoded
+ TEXT("&walletAddress=") + address
+ TEXT("&disableWalletAddressForm=true")
+ TEXT("&partnerOrderId=") + PartnerOrderId;

UE_LOG(LogTemp, Warning, TEXT("Final URL: %s"), *URL);
return URL;
}


FString UTransakCheckout::BuildNFTCheckoutLinkFromERC1155(UERC1155SaleContract* SaleContract, const FTransakNFTData TransakNFTData, const FTransakContractId& ContractId, const TArray<uint8>& Data, const TArray<FString>& Proof)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way to make the Proof and Data parameters optional?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not if its to be blueprint compatible

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha. Could we maybe add a tooltip to the BP and add some C++ overload functions?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, is there a blueprint for this already?

{

if (!SaleContract)
{
return TEXT("Error: Invalid SaleContract");
}

if (TransakNFTData.Quantity <= 0)
{
return TEXT("Error: Invalid quantity");
}

TArray<int32> TokenIds;
TArray<int32> Amounts;

for (const FString& TokenIDString : TransakNFTData.TokenID)
{
int32 ParsedTokenId = FCString::Atoi(*TokenIDString);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a random thought - what do you think about making a wrapper function for converting strings to ints with a more readable name in a future PR? I know that this is a built-in function that should be rather well-known to C/C++ devs, but maybe would be worthwhile anyways as we wouldn't have to do as much mental gymnastics? Or is that just unnecessary overhead? I'm not really sure... What do you think?

TokenIds.Add(ParsedTokenId);
Amounts.Add(TransakNFTData.Quantity);
}


FRawTransaction result = SaleContract->MakePurchaseTransaction(
TransakContractAddresses[Network],
TokenIds,
Amounts,
Proof
);

FString callData = result.data;


return GetNFTCheckoutLink(TransakNFTData, callData, ContractId);
}
void UTransakCheckout::BuildNFTCheckoutLinkFromCollectibleOrder(FSeqCollectibleOrder order, int64 quantity, ENFTType type, FAdditionalFee AdditionalFee, FOnTransakCheckoutGenerated OnSuccessCallback)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should have a method where we can provide an array of FSeqCollectibleOrder as a parameter. quantity may exceed the amount available for a single FSeqCollectibleOrder

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably also validate that those FSeqCollectibleOrders are for the same collectible

{
if (quantity <= 0)
{
UE_LOG(LogTemp, Error, TEXT("Invalid quantity"));
return;
}

TArray<FString> TokenIds = { FString::FromInt(order.TokenMetadata.tokenId) };
TArray<float> Prices = { static_cast<float>(order.Order.PriceUSD) };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be the order.priceAmount - the price in cryptocurrency - normalized with order.priceDecimals so that it is in human-readable format instead of a BigInt

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed


FTransakNFTData NFTData(
order.TokenMetadata.image,
order.TokenMetadata.name,
order.TokenMetadata.contractAddress,
TokenIds,
Prices,
quantity,
ENFTType::ERC721
);


FTransakContractId TransakContractID = GetTransakContractIdFromCollectibleOrder(order);

if (TransakContractID.Id.IsEmpty())
{
UE_LOG(LogTemp, Error, TEXT("TransakContractID is empty. Aborting."));
return;
}

USequenceCheckout* Checkout = NewObject< USequenceCheckout>();

Checkout->GenerateBuyTransaction(
order.Order.ChainId,
WalletAddress,
order.Order,
quantity,
AdditionalFee,
EWalletKind::Sequence,

[this, NFTData, TransakContractID, OnSuccessCallback](const FGenerateTransactionResponse& Response)
{
const FString URL = GetNFTCheckoutLink(NFTData, *Response.Steps[0].ExtractBuyStep(Response.Steps).Data, TransakContractID);
OnSuccessCallback.ExecuteIfBound(URL);
},

[OnSuccessCallback](const FSequenceError& Error)
{
OnSuccessCallback.ExecuteIfBound(TEXT("Error: Failed to generate buy transaction"));
}
);
}

FTransakContractId UTransakCheckout::GetTransakContractIdFromCollectibleOrder(FSeqCollectibleOrder order)
{
FTransakContractId TransakContractID;

if (order.Order.ChainId == 137)
{
switch (order.Order.Marketplace)
{
case EMarketplaceKind::SEQUENCE_MARKETPLACE_V2:

TransakContractID.Id = TEXT("67ac543448035690a20ac131");
TransakContractID.ContractAddress = TEXT("0xfdb42A198a932C8D3B506Ffa5e855bC4b348a712");
TransakContractID.Chain = TransakContractID.Chain = UEnum::GetValueAsString(ENetwork::PolygonChain);
TransakContractID.PriceTokenSymbol = TEXT("POL");

break;

default:

TransakContractID.Id = TEXT("6675a6d0f597abb8f3e2e9c2");
TransakContractID.ContractAddress = TEXT("0xc2c862322e9c97d6244a3506655da95f05246fd8");
TransakContractID.Chain = TransakContractID.Chain = UEnum::GetValueAsString(ENetwork::PolygonChain);
TransakContractID.PriceTokenSymbol = TEXT("MATIC");

break;
}
}
else if (order.Order.ChainId == 42161)
{

if (order.Order.Marketplace == EMarketplaceKind::SEQUENCE_MARKETPLACE_V2)
{
TransakContractID.Id = TEXT("66c5a2cf2fb1688e11fcb167");
TransakContractID.ContractAddress = TEXT("0xB537a160472183f2150d42EB1c3DD6684A55f74c");
TransakContractID.Chain = UEnum::GetValueAsString(ENetwork::ArbitrumOne);
TransakContractID.PriceTokenSymbol = TEXT("USDC");
}
else if (order.Order.Marketplace == EMarketplaceKind::SEQUENCE_MARKETPLACE_V1)
{
TransakContractID.Id = TEXT("66c5a2d8c00223b9cc6edfdc");
TransakContractID.ContractAddress = TEXT("0xfdb42A198a932C8D3B506Ffa5e855bC4b348a712");
TransakContractID.Chain = UEnum::GetValueAsString(ENetwork::ArbitrumOne);
TransakContractID.PriceTokenSymbol = TEXT("USDC");
}
}

*TransakContractID.Id,
*TransakContractID.ContractAddress,
*TransakContractID.Chain,
*TransakContractID.PriceTokenSymbol);

return TransakContractID;
}


const TMap<ENetwork, FString> UTransakCheckout::TransakContractAddresses = {
{ENetwork::Ethereum, TEXT("0xab88cd272863b197b48762ea283f24a13f6586dd")},
{ENetwork::Sepolia, TEXT("0xD84aC4716A082B1F7eCDe9301aA91A7c4B62ECd7")},
{ENetwork::PolygonChain, TEXT("0x4A598B7eC77b1562AD0dF7dc64a162695cE4c78A")},
{ENetwork::PolygonAmoy, TEXT("0xCB9bD5aCD627e8FcCf9EB8d4ba72AEb1Cd8Ff5EF")},
{ENetwork::BNBSmartChain, TEXT("0x4A598B7eC77b1562AD0dF7dc64a162695cE4c78A")},
{ENetwork::BNBSmartChainTestnet, TEXT("0x0E9539455944BE8a307bc43B0a046613a1aD6732")},
{ENetwork::ArbitrumOne, TEXT("0x4A598B7eC77b1562AD0dF7dc64a162695cE4c78A")},
{ENetwork::ArbitrumSepolia, TEXT("0x489F56e3144FF03A887305839bBCD20FF767d3d1")},
{ENetwork::Optimism, TEXT("0x4A598B7eC77b1562AD0dF7dc64a162695cE4c78A")},
{ENetwork::OptimismSepolia, TEXT("0xCB9bD5aCD627e8FcCf9EB8d4ba72AEb1Cd8Ff5EF")},
{ENetwork::Immutable, TEXT("0x8b83dE7B20059864C479640CC33426935DC5F85b")},
{ENetwork::ImmutableTestnet, TEXT("0x489F56e3144FF03A887305839bBCD20FF767d3d1")},
{ENetwork::Base, TEXT("0x8b83dE7B20059864C479640CC33426935DC5F85b")},
{ENetwork::BaseSepolia, TEXT("0xCB9bD5aCD627e8FcCf9EB8d4ba72AEb1Cd8Ff5EF")}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#include "Checkout/Transak/TransakNFTDataEncoder.h"
#include "Serialization/JsonWriter.h"
#include "Policies/CondensedJsonPrintPolicy.h"
#include "Serialization/JsonSerializer.h"
#include "Dom/JsonObject.h"
#include "Dom/JsonValue.h"
#include "Misc/Base64.h"
#include "GenericPlatform/GenericPlatformHttp.h"

FString FTransakNFTDataEncoder::Encode(const FTransakNFTData& NftData)
{

TSharedRef<FJsonObject> JsonObject = MakeShared<FJsonObject>();
JsonObject->SetStringField(TEXT("imageURL"), NftData.ImageURL);
JsonObject->SetStringField(TEXT("nftName"), NftData.Name);
JsonObject->SetStringField(TEXT("collectionAddress"), NftData.CollectionAddress);

TArray<TSharedPtr<FJsonValue>> TokenIDArray;
for (const FString& TokenID : NftData.TokenID)
{
TokenIDArray.Add(MakeShared<FJsonValueString>(TokenID));
}
JsonObject->SetArrayField(TEXT("tokenID"), TokenIDArray);

TArray<TSharedPtr<FJsonValue>> PriceArray;
for (double Price : NftData.Price)
{
double RoundedPrice = FMath::RoundToDouble(Price * 100000.0) / 100000.0;
PriceArray.Add(MakeShared<FJsonValueNumber>(RoundedPrice));
}
JsonObject->SetArrayField(TEXT("price"), PriceArray);

JsonObject->SetNumberField(TEXT("quantity"), static_cast<double>(NftData.Quantity));

FString EnumStr = UEnum::GetValueAsString(NftData.Type).Replace(TEXT("ENFTType::"), TEXT(""));
JsonObject->SetStringField(TEXT("nftType"), EnumStr);

TArray<TSharedPtr<FJsonValue>> JsonArray;
JsonArray.Add(MakeShared<FJsonValueObject>(JsonObject));

FString JsonString;
TSharedRef<TJsonWriter<TCHAR, TCondensedJsonPrintPolicy<TCHAR>>> Writer =TJsonWriterFactory<TCHAR, TCondensedJsonPrintPolicy<TCHAR>>::Create(&JsonString);

if (!FJsonSerializer::Serialize(JsonArray, Writer))
{
return TEXT("");
}

FString Base64Encoded = FBase64::Encode(JsonString);

UE_LOG(LogTemp, Log, TEXT("Base64 Encoded (with padding): %s"), *Base64Encoded);

FString UrlEncoded = FGenericPlatformHttp::UrlEncode(Base64Encoded);

UE_LOG(LogTemp, Log, TEXT("URL Encoded: %s"), *UrlEncoded);

return UrlEncoded;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#pragma once

#include "CoreMinimal.h"

/**
* Utility class for compressing call data using Deflate + Base64 + URI encoding.
*/
class SEQUENCEPLUGIN_API FCallDataCompressor
{
public:
static FString Compress(const FString& In);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#pragma once

#include "CoreMinimal.h"
#include "TransakContractId.generated.h"

USTRUCT(BlueprintType)
struct SEQUENCEPLUGIN_API FTransakContractId
{
GENERATED_BODY()

UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Transak")
FString Id;

UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Transak")
FString ContractAddress;

UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Transak")
FString Chain;

UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Transak")
FString PriceTokenSymbol;
};
Loading
Loading