From 9af3fa03b583c27edfb821a458132daf1cdbb9fe Mon Sep 17 00:00:00 2001 From: Firekeeper <0xFirekeeper@gmail.com> Date: Wed, 9 Oct 2024 03:28:04 +0300 Subject: [PATCH] Marketplace Extensions (#85) --- Thirdweb.Console/Program.cs | 4 +- .../Thirdweb.Extensions.Tests.cs | 27 + .../Thirdweb.MarketplaceExtensions.Tests.cs | 376 ++++++++ ...onTypes.cs => ThirdwebExtensions.Types.cs} | 0 .../Thirdweb.Extensions/ThirdwebExtensions.cs | 10 + .../ThirdwebMarketplaceExtensions.Types.cs | 469 +++++++++ .../ThirdwebMarketplaceExtensions.cs | 888 ++++++++++++++++++ Thirdweb/Thirdweb.Utils/Constants.cs | 4 + 8 files changed, 1776 insertions(+), 2 deletions(-) create mode 100644 Thirdweb.Tests/Thirdweb.Extensions/Thirdweb.MarketplaceExtensions.Tests.cs rename Thirdweb/Thirdweb.Extensions/{ExtensionTypes.cs => ThirdwebExtensions.Types.cs} (100%) create mode 100644 Thirdweb/Thirdweb.Extensions/ThirdwebMarketplaceExtensions.Types.cs create mode 100644 Thirdweb/Thirdweb.Extensions/ThirdwebMarketplaceExtensions.cs diff --git a/Thirdweb.Console/Program.cs b/Thirdweb.Console/Program.cs index 54d1ce1d..fda24ca7 100644 --- a/Thirdweb.Console/Program.cs +++ b/Thirdweb.Console/Program.cs @@ -74,7 +74,7 @@ #endregion -// #region AA ZkSync +#region AA ZkSync // var zkSmartWallet = await SmartWallet.Create(personalWallet: privateKeyWallet, chainId: 4654, gasless: true); @@ -89,7 +89,7 @@ // Console.WriteLine($"Transaction hash: {hash}"); -// #endregion +#endregion #region Ecosystem Wallet diff --git a/Thirdweb.Tests/Thirdweb.Extensions/Thirdweb.Extensions.Tests.cs b/Thirdweb.Tests/Thirdweb.Extensions/Thirdweb.Extensions.Tests.cs index 724d6c36..a5637f9e 100644 --- a/Thirdweb.Tests/Thirdweb.Extensions/Thirdweb.Extensions.Tests.cs +++ b/Thirdweb.Tests/Thirdweb.Extensions/Thirdweb.Extensions.Tests.cs @@ -117,6 +117,33 @@ public async Task NullChecks() // ERC721_TokenByIndex _ = await Assert.ThrowsAsync(() => ThirdwebExtensions.ERC721_TokenByIndex(null, 0)); + + // SupportsInterface + _ = await Assert.ThrowsAsync(() => ThirdwebExtensions.SupportsInterface(null, "0x01ffc9a7")); + } + + [Fact(Timeout = 120000)] + public async Task SupportsInterface_ERC721() + { + var contract = await this.GetDrop721Contract(); + var supportsInterface = await contract.SupportsInterface(Constants.IERC721_INTERFACE_ID); + Assert.True(supportsInterface); + } + + [Fact(Timeout = 120000)] + public async Task SupportsInterface_ERC1155() + { + var contract = await this.GetDrop1155Contract(); + var supportsInterface = await contract.SupportsInterface(Constants.IERC1155_INTERFACE_ID); + Assert.True(supportsInterface); + } + + [Fact(Timeout = 120000)] + public async Task SupportsInterface_False() + { + var contract = await this.GetTokenERC20Contract(); + var supportsInterface = await contract.SupportsInterface(Constants.IERC721_INTERFACE_ID); + Assert.False(supportsInterface); } [Fact(Timeout = 120000)] diff --git a/Thirdweb.Tests/Thirdweb.Extensions/Thirdweb.MarketplaceExtensions.Tests.cs b/Thirdweb.Tests/Thirdweb.Extensions/Thirdweb.MarketplaceExtensions.Tests.cs new file mode 100644 index 00000000..e30f63b5 --- /dev/null +++ b/Thirdweb.Tests/Thirdweb.Extensions/Thirdweb.MarketplaceExtensions.Tests.cs @@ -0,0 +1,376 @@ +using System.Numerics; + +namespace Thirdweb.Tests.Extensions; + +public class MarketplaceExtensionsTests : BaseTests +{ + private readonly string _marketplaceContractAddress = "0xb80E83b73571e63b3581b68f20bFC9E97965F329"; + private readonly string _drop1155ContractAddress = "0x37116cAe5e152C1A7345AAB0EC286Ff6E97c0605"; + + private readonly BigInteger _chainId = 421614; + + public MarketplaceExtensionsTests(ITestOutputHelper output) + : base(output) { } + + private async Task GetSmartWallet(int claimAmount) + { + var privateKeyWallet = await PrivateKeyWallet.Generate(this.Client); + var smartWallet = await SmartWallet.Create(personalWallet: privateKeyWallet, chainId: 421614); + + if (claimAmount > 0) + { + var drop1155Contract = await ThirdwebContract.Create(this.Client, this._drop1155ContractAddress, this._chainId); + var tokenId = 0; + _ = await drop1155Contract.DropERC1155_Claim(smartWallet, await smartWallet.GetAddress(), tokenId, claimAmount); + } + + return smartWallet; + } + + private async Task GetMarketplaceContract() + { + return await ThirdwebContract.Create(this.Client, this._marketplaceContractAddress, this._chainId); + } + + #region IDirectListings + + [Fact(Timeout = 120000)] + public async Task Marketplace_DirectListings_CreateListing_Success() + { + var contract = await this.GetMarketplaceContract(); + var wallet = await this.GetSmartWallet(1); + + var listingParams = new ListingParameters() + { + AssetContract = this._drop1155ContractAddress, + TokenId = 0, + Quantity = 1, + Currency = Constants.NATIVE_TOKEN_ADDRESS, + PricePerToken = 1, + StartTimestamp = Utils.GetUnixTimeStampNow(), + EndTimestamp = Utils.GetUnixTimeStampNow() + 3600, + Reserved = false + }; + + var receipt = await contract.Marketplace_DirectListings_CreateListing(wallet, listingParams, true); + + Assert.NotNull(receipt); + Assert.True(receipt.TransactionHash.Length == 66); + + var listingId = await contract.Marketplace_DirectListings_TotalListings() - 1; + var listing = await contract.Marketplace_DirectListings_GetListing(listingId); + + Assert.NotNull(listing); + Assert.Equal(listing.ListingId, listingId); + Assert.Equal(listing.TokenId, listingParams.TokenId); + Assert.Equal(listing.Quantity, listingParams.Quantity); + Assert.Equal(listing.PricePerToken, listingParams.PricePerToken); + Assert.True(listing.StartTimestamp >= listingParams.StartTimestamp); + Assert.True(listing.EndTimestamp >= listingParams.EndTimestamp); + Assert.Equal(listing.ListingCreator, await wallet.GetAddress()); + Assert.Equal(listing.AssetContract, listingParams.AssetContract); + Assert.Equal(listing.Currency, listingParams.Currency); + Assert.Equal(TokenType.ERC1155, listing.TokenTypeEnum); + Assert.Equal(Status.CREATED, listing.StatusEnum); + Assert.Equal(listing.Reserved, listingParams.Reserved); + } + + [Fact(Timeout = 120000)] + public async Task Marketplace_DirectListings_UpdateListing_Success() + { + var contract = await this.GetMarketplaceContract(); + var wallet = await this.GetSmartWallet(1); + + var originalTotal = await contract.Marketplace_DirectListings_TotalListings(); + + var originalListing = new ListingParameters() + { + AssetContract = this._drop1155ContractAddress, + TokenId = 0, + Quantity = 1, + Currency = Constants.NATIVE_TOKEN_ADDRESS, + PricePerToken = 1, + StartTimestamp = Utils.GetUnixTimeStampNow() + 1800, + EndTimestamp = Utils.GetUnixTimeStampNow() + 3600, + Reserved = false + }; + + var receipt = await contract.Marketplace_DirectListings_CreateListing(wallet, originalListing, true); + Assert.NotNull(receipt); + + var listingId = await contract.Marketplace_DirectListings_TotalListings() - 1; + Assert.True(listingId == originalTotal); + + var updatedListingParams = originalListing; + updatedListingParams.PricePerToken = 2; + + var updatedReceipt = await contract.Marketplace_DirectListings_UpdateListing(wallet, listingId, updatedListingParams); + Assert.NotNull(updatedReceipt); + Assert.True(updatedReceipt.TransactionHash.Length == 66); + + var listing = await contract.Marketplace_DirectListings_GetListing(listingId); + Assert.NotNull(listing); + Assert.Equal(listing.PricePerToken, 2); + } + + [Fact(Timeout = 120000)] + public async Task Marketplace_DirectListings_CancelListing_Success() + { + var contract = await this.GetMarketplaceContract(); + var wallet = await this.GetSmartWallet(1); + + var originalTotal = await contract.Marketplace_DirectListings_TotalListings(); + + var originalListing = new ListingParameters() + { + AssetContract = this._drop1155ContractAddress, + TokenId = 0, + Quantity = 1, + Currency = Constants.NATIVE_TOKEN_ADDRESS, + PricePerToken = 1, + StartTimestamp = Utils.GetUnixTimeStampNow() + 1800, + EndTimestamp = Utils.GetUnixTimeStampNow() + 3600, + Reserved = false + }; + + var receipt = await contract.Marketplace_DirectListings_CreateListing(wallet, originalListing, true); + Assert.NotNull(receipt); + + var listingId = await contract.Marketplace_DirectListings_TotalListings() - 1; + Assert.True(listingId == originalTotal); + + var removeReceipt = await contract.Marketplace_DirectListings_CancelListing(wallet, listingId); + Assert.NotNull(removeReceipt); + Assert.True(removeReceipt.TransactionHash.Length == 66); + } + + [Fact(Timeout = 120000)] + public async Task Marketplace_DirectListings_ApproveBuyerForListing() + { + var contract = await this.GetMarketplaceContract(); + var wallet = await this.GetSmartWallet(1); + + var reservedListing = new ListingParameters() + { + AssetContract = this._drop1155ContractAddress, + TokenId = 0, + Quantity = 1, + Currency = Constants.NATIVE_TOKEN_ADDRESS, + PricePerToken = 1, + StartTimestamp = Utils.GetUnixTimeStampNow(), + EndTimestamp = Utils.GetUnixTimeStampNow() + 3600, + Reserved = true + }; + + var receipt = await contract.Marketplace_DirectListings_CreateListing(wallet, reservedListing, true); + Assert.NotNull(receipt); + Assert.True(receipt.TransactionHash.Length == 66); + + var listingId = await contract.Marketplace_DirectListings_TotalListings() - 1; + + var buyer = await PrivateKeyWallet.Generate(this.Client); + var approveReceipt = await contract.Marketplace_DirectListings_ApproveBuyerForListing(wallet, listingId, await buyer.GetAddress(), true); + Assert.NotNull(approveReceipt); + Assert.True(approveReceipt.TransactionHash.Length == 66); + + var isApproved = await contract.Marketplace_DirectListings_IsBuyerApprovedForListing(listingId, await buyer.GetAddress()); + Assert.True(isApproved); + } + + [Fact(Timeout = 120000)] + public async Task Marketplace_DirectListings_TotalListings_Success() + { + var contract = await this.GetMarketplaceContract(); + var totalListings = await contract.Marketplace_DirectListings_TotalListings(); + Assert.True(totalListings >= 0); + } + + [Fact(Timeout = 120000)] + public async Task Marketplace_DirectListings_GetAllListings_Success() + { + var contract = await this.GetMarketplaceContract(); + var startId = BigInteger.Zero; + var endId = 10; + + var listings = await contract.Marketplace_DirectListings_GetAllListings(startId, endId); + Assert.NotNull(listings); + Assert.True(listings.Count >= 0); + } + + [Fact(Timeout = 120000)] + public async Task Marketplace_DirectListings_GetAllValidListings_Success() + { + var contract = await this.GetMarketplaceContract(); + var startId = BigInteger.Zero; + var endId = 10; + + var listings = await contract.Marketplace_DirectListings_GetAllValidListings(startId, endId); + Assert.NotNull(listings); + Assert.True(listings.Count >= 0); + } + + [Fact(Timeout = 120000)] + public async Task Marketplace_DirectListings_GetListing_Success() + { + var contract = await this.GetMarketplaceContract(); + var listingId = BigInteger.One; + + var listing = await contract.Marketplace_DirectListings_GetListing(listingId); + Assert.NotNull(listing); + } + + #endregion + + #region IEnglishAuctions + + [Fact(Timeout = 120000)] + public async Task Marketplace_EnglishAuctions_CreateAuction_Success() + { + var contract = await this.GetMarketplaceContract(); + var wallet = await this.GetSmartWallet(1); + + var auctionParams = new AuctionParameters() + { + AssetContract = this._drop1155ContractAddress, + TokenId = 0, + Quantity = 1, + Currency = Constants.NATIVE_TOKEN_ADDRESS, + MinimumBidAmount = 1, + BuyoutBidAmount = BigInteger.Parse("1".ToWei()), + TimeBufferInSeconds = 3600, + BidBufferBps = 100, + StartTimestamp = Utils.GetUnixTimeStampNow() - 3000, + EndTimestamp = Utils.GetUnixTimeStampNow() + (3600 * 24 * 7), + }; + + var receipt = await contract.Marketplace_EnglishAuctions_CreateAuction(wallet, auctionParams, true); + Assert.NotNull(receipt); + Assert.True(receipt.TransactionHash.Length == 66); + + var auctionId = await contract.Marketplace_EnglishAuctions_TotalAuctions() - 1; + var auction = await contract.Marketplace_EnglishAuctions_GetAuction(auctionId); + Assert.NotNull(auction); + Assert.Equal(auction.AuctionId, auctionId); + Assert.Equal(auction.TokenId, auctionParams.TokenId); + Assert.Equal(auction.Quantity, auctionParams.Quantity); + Assert.Equal(auction.MinimumBidAmount, auctionParams.MinimumBidAmount); + Assert.Equal(auction.BuyoutBidAmount, auctionParams.BuyoutBidAmount); + Assert.True(auction.TimeBufferInSeconds >= auctionParams.TimeBufferInSeconds); + Assert.True(auction.BidBufferBps >= auctionParams.BidBufferBps); + Assert.True(auction.StartTimestamp >= auctionParams.StartTimestamp); + Assert.True(auction.EndTimestamp >= auctionParams.EndTimestamp); + Assert.Equal(auction.AuctionCreator, await wallet.GetAddress()); + Assert.Equal(auction.AssetContract, auctionParams.AssetContract); + Assert.Equal(auction.Currency, auctionParams.Currency); + Assert.Equal(TokenType.ERC1155, auction.TokenTypeEnum); + Assert.Equal(Status.CREATED, auction.StatusEnum); + } + + [Fact(Timeout = 120000)] + public async Task Marketplace_EnglishAuctions_GetAuction_Success() + { + var contract = await this.GetMarketplaceContract(); + var auctionId = BigInteger.One; + + var auction = await contract.Marketplace_EnglishAuctions_GetAuction(auctionId); + Assert.NotNull(auction); + } + + [Fact(Timeout = 120000)] + public async Task Marketplace_EnglishAuctions_GetAllAuctions_Success() + { + var contract = await this.GetMarketplaceContract(); + var startId = BigInteger.Zero; + var endId = BigInteger.Zero; + + var auctions = await contract.Marketplace_EnglishAuctions_GetAllAuctions(startId, endId); + Assert.NotNull(auctions); + } + + [Fact(Timeout = 120000)] + public async Task Marketplace_EnglishAuctions_GetAllValidAuctions_Success() + { + var contract = await this.GetMarketplaceContract(); + var startId = BigInteger.Zero; + var endId = BigInteger.Zero; + + var auctions = await contract.Marketplace_EnglishAuctions_GetAllValidAuctions(startId, endId); + Assert.NotNull(auctions); + Assert.True(auctions.Count >= 0); + } + + #endregion + + #region IOffers + + // [Fact(Timeout = 120000)] + // public async Task Marketplace_Offers_MakeOffer_Success() + // { + // var contract = await this.GetMarketplaceContract(); + // var wallet = await this.GetSmartWallet(0); + + // var offerParams = new OfferParams() + // { + // AssetContract = this._drop1155ContractAddress, + // TokenId = 0, + // Quantity = 1, + // Currency = ERC20_HERE, + // TotalPrice = 0, + // ExpirationTimestamp = Utils.GetUnixTimeStampNow() + (3600 * 24), + // }; + + // var receipt = await contract.Marketplace_Offers_MakeOffer(wallet, offerParams, true); + // Assert.NotNull(receipt); + // Assert.True(receipt.TransactionHash.Length == 66); + + // var offerId = await contract.Marketplace_Offers_TotalOffers() - 1; + // var offer = await contract.Marketplace_Offers_GetOffer(offerId); + // Assert.NotNull(offer); + // Assert.Equal(offer.OfferId, offerId); + // Assert.Equal(offer.TokenId, offerParams.TokenId); + // Assert.Equal(offer.Quantity, offerParams.Quantity); + // Assert.Equal(offer.TotalPrice, offerParams.TotalPrice); + // Assert.True(offer.ExpirationTimestamp >= offerParams.ExpirationTimestamp); + // Assert.Equal(offer.Offeror, await wallet.GetAddress()); + // Assert.Equal(offer.AssetContract, offerParams.AssetContract); + // Assert.Equal(offer.Currency, offerParams.Currency); + // Assert.Equal(TokenType.ERC1155, offer.TokenTypeEnum); + // Assert.Equal(Status.CREATED, offer.StatusEnum); + // } + + // [Fact(Timeout = 120000)] + // public async Task Marketplace_Offers_GetOffer_Success() + // { + // var contract = await this.GetMarketplaceContract(); + // var offerId = BigInteger.One; + + // var offer = await contract.Marketplace_Offers_GetOffer(offerId); + // Assert.NotNull(offer); + // } + + // [Fact(Timeout = 120000)] + // public async Task Marketplace_Offers_GetAllOffers_Success() + // { + // var contract = await this.GetMarketplaceContract(); + // var startId = BigInteger.Zero; + // var endId = 10; + + // var offers = await contract.Marketplace_Offers_GetAllOffers(startId, endId); + // Assert.NotNull(offers); + // Assert.True(offers.Count >= 0); + // } + + // [Fact(Timeout = 120000)] + // public async Task Marketplace_Offers_GetAllValidOffers_Success() + // { + // var contract = await this.GetMarketplaceContract(); + // var startId = BigInteger.Zero; + // var endId = 10; + + // var offers = await contract.Marketplace_Offers_GetAllValidOffers(startId, endId); + // Assert.NotNull(offers); + // Assert.True(offers.Count >= 0); + // } + + #endregion +} diff --git a/Thirdweb/Thirdweb.Extensions/ExtensionTypes.cs b/Thirdweb/Thirdweb.Extensions/ThirdwebExtensions.Types.cs similarity index 100% rename from Thirdweb/Thirdweb.Extensions/ExtensionTypes.cs rename to Thirdweb/Thirdweb.Extensions/ThirdwebExtensions.Types.cs diff --git a/Thirdweb/Thirdweb.Extensions/ThirdwebExtensions.cs b/Thirdweb/Thirdweb.Extensions/ThirdwebExtensions.cs index 63ececb4..e5b4b60c 100644 --- a/Thirdweb/Thirdweb.Extensions/ThirdwebExtensions.cs +++ b/Thirdweb/Thirdweb.Extensions/ThirdwebExtensions.cs @@ -9,6 +9,16 @@ public static class ThirdwebExtensions { #region Common + public static async Task SupportsInterface(this ThirdwebContract contract, string interfaceId) + { + if (contract == null) + { + throw new ArgumentNullException(nameof(contract)); + } + + return await ThirdwebContract.Read(contract, "supportsInterface", interfaceId.HexToBytes()); + } + /// /// Reads data from the contract using the specified method. /// diff --git a/Thirdweb/Thirdweb.Extensions/ThirdwebMarketplaceExtensions.Types.cs b/Thirdweb/Thirdweb.Extensions/ThirdwebMarketplaceExtensions.Types.cs new file mode 100644 index 00000000..01ccf9c9 --- /dev/null +++ b/Thirdweb/Thirdweb.Extensions/ThirdwebMarketplaceExtensions.Types.cs @@ -0,0 +1,469 @@ +using System.Numerics; +using Nethereum.ABI.FunctionEncoding.Attributes; + +namespace Thirdweb; + +#region Common + +/// +/// Enumeration representing the type of tokens (ERC721, ERC1155, or ERC20). +/// +public enum TokenType : byte +{ + /// + /// Represents an ERC721 token. + /// + ERC721 = 0, + + /// + /// Represents an ERC1155 token. + /// + ERC1155 = 1, + + /// + /// Represents an ERC20 token. + /// + ERC20 = 2 +} + +/// +/// Enumeration representing the status of an entity (unset, created, completed, or cancelled). +/// +public enum Status : byte +{ + /// + /// The status is not set. + /// + UNSET = 0, + + /// + /// The entity is created. + /// + CREATED = 1, + + /// + /// The entity is completed. + /// + COMPLETED = 2, + + /// + /// The entity is cancelled. + /// + CANCELLED = 3 +} + +#endregion + +#region IDirectListings + +/// +/// Represents the parameters for creating or updating a listing in the marketplace. +/// +[Struct("ListingParameters")] +public class ListingParameters +{ + /// + /// The address of the smart contract of the NFTs being listed. + /// + [Parameter("address", "assetContract", 1)] + public string AssetContract { get; set; } + + /// + /// The tokenId of the NFTs being listed. + /// + [Parameter("uint256", "tokenId", 2)] + public BigInteger TokenId { get; set; } + + /// + /// The quantity of NFTs being listed. + /// + [Parameter("uint256", "quantity", 3)] + public BigInteger Quantity { get; set; } + + /// + /// The currency in which the price must be paid when buying the listed NFTs. + /// + [Parameter("address", "currency", 4)] + public string Currency { get; set; } + + /// + /// The price per token for the NFTs listed. + /// + [Parameter("uint256", "pricePerToken", 5)] + public BigInteger PricePerToken { get; set; } + + /// + /// The UNIX timestamp at and after which NFTs can be bought from the listing. + /// + [Parameter("uint128", "startTimestamp", 6)] + public BigInteger StartTimestamp { get; set; } + + /// + /// The UNIX timestamp after which NFTs cannot be bought from the listing. + /// + [Parameter("uint128", "endTimestamp", 7)] + public BigInteger EndTimestamp { get; set; } + + /// + /// Whether the listing is reserved to be bought from a specific set of buyers. + /// + [Parameter("bool", "reserved", 8)] + public bool Reserved { get; set; } +} + +/// +/// Represents a listing in the marketplace. +/// +[FunctionOutput] +public class Listing +{ + /// + /// The unique ID of the listing. + /// + [Parameter("uint256", "listingId", 1)] + public BigInteger ListingId { get; set; } + + /// + /// The tokenId of the NFTs being listed. + /// + [Parameter("uint256", "tokenId", 2)] + public BigInteger TokenId { get; set; } + + /// + /// The quantity of NFTs being listed. + /// + [Parameter("uint256", "quantity", 3)] + public BigInteger Quantity { get; set; } + + /// + /// The price per token for the NFTs listed. + /// + [Parameter("uint256", "pricePerToken", 4)] + public BigInteger PricePerToken { get; set; } + + /// + /// The UNIX timestamp at and after which NFTs can be bought from the listing. + /// + [Parameter("uint128", "startTimestamp", 5)] + public BigInteger StartTimestamp { get; set; } + + /// + /// The UNIX timestamp after which NFTs cannot be bought from the listing. + /// + [Parameter("uint128", "endTimestamp", 6)] + public BigInteger EndTimestamp { get; set; } + + /// + /// The address of the listing creator. + /// + [Parameter("address", "listingCreator", 7)] + public string ListingCreator { get; set; } + + /// + /// The address of the smart contract of the NFTs being listed. + /// + [Parameter("address", "assetContract", 8)] + public string AssetContract { get; set; } + + /// + /// The currency in which the price must be paid when buying the listed NFTs. + /// + [Parameter("address", "currency", 9)] + public string Currency { get; set; } + + /// + /// The type of token being listed (ERC721 or ERC1155). + /// + [Parameter("uint8", "tokenType", 10)] + public TokenType TokenTypeEnum { get; set; } + + /// + /// The status of the listing (created, completed, or cancelled). + /// + [Parameter("uint8", "status", 11)] + public Status StatusEnum { get; set; } + + /// + /// Whether the listing is reserved for a specific set of buyers. + /// + [Parameter("bool", "reserved", 12)] + public bool Reserved { get; set; } +} + +#endregion + +#region IEnglishAuctions + +/// +/// Represents the parameters for creating or updating an auction. +/// +[Struct("AuctionParameters")] +public class AuctionParameters +{ + /// + /// The address of the smart contract of the NFTs being auctioned. + /// + [Parameter("address", "assetContract", 1)] + public string AssetContract { get; set; } + + /// + /// The tokenId of the NFTs being auctioned. + /// + [Parameter("uint256", "tokenId", 2)] + public BigInteger TokenId { get; set; } + + /// + /// The quantity of NFTs being auctioned. + /// + [Parameter("uint256", "quantity", 3)] + public BigInteger Quantity { get; set; } + + /// + /// The currency in which the bid must be made. + /// + [Parameter("address", "currency", 4)] + public string Currency { get; set; } + + /// + /// The minimum bid amount for the auction. + /// + [Parameter("uint256", "minimumBidAmount", 5)] + public BigInteger MinimumBidAmount { get; set; } + + /// + /// The buyout bid amount to instantly purchase the NFTs and close the auction. + /// + [Parameter("uint256", "buyoutBidAmount", 6)] + public BigInteger BuyoutBidAmount { get; set; } + + /// + /// The buffer time in seconds to extend the auction expiration if a new bid is made. + /// + [Parameter("uint64", "timeBufferInSeconds", 7)] + public long TimeBufferInSeconds { get; set; } + + /// + /// The bid buffer in basis points to ensure a new bid must be a certain percentage higher than the current bid. + /// + [Parameter("uint64", "bidBufferBps", 8)] + public long BidBufferBps { get; set; } + + /// + /// The timestamp at and after which bids can be made to the auction. + /// + [Parameter("uint64", "startTimestamp", 9)] + public long StartTimestamp { get; set; } + + /// + /// The timestamp after which bids cannot be made to the auction. + /// + [Parameter("uint64", "endTimestamp", 10)] + public long EndTimestamp { get; set; } +} + +/// +/// Represents an auction in the marketplace. +/// +[FunctionOutput] +public class Auction +{ + /// + /// The unique ID of the auction. + /// + [Parameter("uint256", "auctionId", 1)] + public BigInteger AuctionId { get; set; } + + /// + /// The tokenId of the NFTs being auctioned. + /// + [Parameter("uint256", "tokenId", 2)] + public BigInteger TokenId { get; set; } + + /// + /// The quantity of NFTs being auctioned. + /// + [Parameter("uint256", "quantity", 3)] + public BigInteger Quantity { get; set; } + + /// + /// The minimum bid amount for the auction. + /// + [Parameter("uint256", "minimumBidAmount", 4)] + public BigInteger MinimumBidAmount { get; set; } + + /// + /// The buyout bid amount to instantly purchase the NFTs and close the auction. + /// + [Parameter("uint256", "buyoutBidAmount", 5)] + public BigInteger BuyoutBidAmount { get; set; } + + /// + /// The buffer time in seconds to extend the auction expiration if a new bid is made. + /// + [Parameter("uint64", "timeBufferInSeconds", 6)] + public long TimeBufferInSeconds { get; set; } + + /// + /// The bid buffer in basis points to ensure a new bid must be a certain percentage higher than the current bid. + /// + [Parameter("uint64", "bidBufferBps", 7)] + public long BidBufferBps { get; set; } + + /// + /// The timestamp at and after which bids can be made to the auction. + /// + [Parameter("uint64", "startTimestamp", 8)] + public long StartTimestamp { get; set; } + + /// + /// The timestamp after which bids cannot be made to the auction. + /// + [Parameter("uint64", "endTimestamp", 9)] + public long EndTimestamp { get; set; } + + /// + /// The address of the auction creator. + /// + [Parameter("address", "auctionCreator", 10)] + public string AuctionCreator { get; set; } + + /// + /// The address of the smart contract of the NFTs being auctioned. + /// + [Parameter("address", "assetContract", 11)] + public string AssetContract { get; set; } + + /// + /// The currency in which the bid must be made. + /// + [Parameter("address", "currency", 12)] + public string Currency { get; set; } + + /// + /// The type of token being auctioned (ERC721 or ERC1155). + /// + [Parameter("uint8", "tokenType", 13)] + public TokenType TokenTypeEnum { get; set; } + + /// + /// The status of the auction (created, completed, or cancelled). + /// + [Parameter("uint8", "status", 14)] + public Status StatusEnum { get; set; } +} + +#endregion + +#region IOffers + +/// +/// Represents the parameters for making an offer on NFTs. +/// +[Struct("OfferParams")] +public class OfferParams +{ + /// + /// The contract address of the NFTs for which the offer is being made. + /// + [Parameter("address", "assetContract", 1)] + public string AssetContract { get; set; } + + /// + /// The tokenId of the NFTs for which the offer is being made. + /// + [Parameter("uint256", "tokenId", 2)] + public BigInteger TokenId { get; set; } + + /// + /// The quantity of NFTs desired in the offer. + /// + [Parameter("uint256", "quantity", 3)] + public BigInteger Quantity { get; set; } + + /// + /// The currency offered in exchange for the NFTs. + /// + [Parameter("address", "currency", 4)] + public string Currency { get; set; } + + /// + /// The total price offered for the NFTs. + /// + [Parameter("uint256", "totalPrice", 5)] + public BigInteger TotalPrice { get; set; } + + /// + /// The UNIX timestamp after which the offer cannot be accepted. + /// + [Parameter("uint256", "expirationTimestamp", 6)] + public BigInteger ExpirationTimestamp { get; set; } +} + +/// +/// Represents an offer made on NFTs. +/// +[FunctionOutput] +public class Offer +{ + /// + /// The unique ID of the offer. + /// + [Parameter("uint256", "offerId", 1)] + public BigInteger OfferId { get; set; } + + /// + /// The tokenId of the NFTs for which the offer is being made. + /// + [Parameter("uint256", "tokenId", 2)] + public BigInteger TokenId { get; set; } + + /// + /// The quantity of NFTs desired in the offer. + /// + [Parameter("uint256", "quantity", 3)] + public BigInteger Quantity { get; set; } + + /// + /// The total price offered for the NFTs. + /// + [Parameter("uint256", "totalPrice", 4)] + public BigInteger TotalPrice { get; set; } + + /// + /// The UNIX timestamp after which the offer cannot be accepted. + /// + [Parameter("uint256", "expirationTimestamp", 5)] + public BigInteger ExpirationTimestamp { get; set; } + + /// + /// The address of the offeror. + /// + [Parameter("address", "offeror", 6)] + public string Offeror { get; set; } + + /// + /// The contract address of the NFTs for which the offer is made. + /// + [Parameter("address", "assetContract", 7)] + public string AssetContract { get; set; } + + /// + /// The currency offered in exchange for the NFTs. + /// + [Parameter("address", "currency", 8)] + public string Currency { get; set; } + + /// + /// The type of token being offered (ERC721, ERC1155, or ERC20). + /// + [Parameter("uint8", "tokenType", 9)] + public TokenType TokenTypeEnum { get; set; } + + /// + /// The status of the offer (created, completed, or cancelled). + /// + [Parameter("uint8", "status", 10)] + public Status StatusEnum { get; set; } +} + +#endregion diff --git a/Thirdweb/Thirdweb.Extensions/ThirdwebMarketplaceExtensions.cs b/Thirdweb/Thirdweb.Extensions/ThirdwebMarketplaceExtensions.cs new file mode 100644 index 00000000..70fa4542 --- /dev/null +++ b/Thirdweb/Thirdweb.Extensions/ThirdwebMarketplaceExtensions.cs @@ -0,0 +1,888 @@ +using System.Numerics; + +namespace Thirdweb; + +public static class ThirdwebMarketplaceExtensions +{ + #region IDirectListings + + /// + /// Creates a new direct listing for selling NFTs at a fixed price. + /// + /// The contract instance. + /// The wallet used for the transaction. + /// The parameters of the listing to be created. + /// Whether to handle token approvals automatically. + /// A task that represents the transaction receipt of the listing creation. + public static async Task Marketplace_DirectListings_CreateListing( + this ThirdwebContract contract, + IThirdwebWallet wallet, + ListingParameters parameters, + bool handleApprovals = false + ) + { + if (contract == null) + { + throw new ArgumentNullException(nameof(contract)); + } + + if (wallet == null) + { + throw new ArgumentNullException(nameof(wallet)); + } + + if (parameters == null) + { + throw new ArgumentNullException(nameof(parameters)); + } + + if (handleApprovals) + { + var assetContractAddress = parameters.AssetContract; + + var prepTasks = new List(); + + var assetContractTask = ThirdwebContract.Create(contract.Client, assetContractAddress, contract.Chain); + prepTasks.Add(assetContractTask); + + var walletAddressTask = wallet.GetAddress(); + prepTasks.Add(walletAddressTask); + + await Task.WhenAll(prepTasks); + + var assetContract = assetContractTask.Result; + var walletAddress = walletAddressTask.Result; + + TokenType assetType; + if (await assetContract.SupportsInterface(Constants.IERC1155_INTERFACE_ID)) + { + assetType = TokenType.ERC1155; + } + else if (await assetContract.SupportsInterface(Constants.IERC721_INTERFACE_ID)) + { + assetType = TokenType.ERC721; + } + else + { + throw new ArgumentException("Asset contract does not support ERC1155 or ERC721 interface."); + } + + if (assetType == TokenType.ERC721) + { + var tokenId = parameters.TokenId; + var @operator = await assetContract.ERC721_GetApproved(tokenId); + if (@operator != contract.Address) + { + _ = await assetContract.ERC721_Approve(wallet, contract.Address, tokenId); + } + } + else + { + var isApprovedForAll = await assetContract.ERC1155_IsApprovedForAll(walletAddress, contract.Address); + if (!isApprovedForAll) + { + _ = await assetContract.ERC1155_SetApprovalForAll(wallet, contract.Address, true); + } + } + } + + return await contract.Write(wallet, "createListing", 0, parameters); + } + + /// + /// Updates an existing direct listing. + /// + /// The contract instance. + /// The wallet used for the transaction. + /// The ID of the listing to update. + /// The updated parameters of the listing. + /// A task that represents the transaction receipt of the listing update. + public static async Task Marketplace_DirectListings_UpdateListing( + this ThirdwebContract contract, + IThirdwebWallet wallet, + BigInteger listingId, + ListingParameters parameters + ) + { + if (contract == null) + { + throw new ArgumentNullException(nameof(contract)); + } + + if (wallet == null) + { + throw new ArgumentNullException(nameof(wallet)); + } + + if (parameters == null) + { + throw new ArgumentNullException(nameof(parameters)); + } + + return await contract.Write(wallet, "updateListing", 0, listingId, parameters); + } + + /// + /// Cancels a direct listing. + /// + /// The contract instance. + /// The wallet used for the transaction. + /// The ID of the listing to cancel. + /// A task that represents the transaction receipt of the listing cancellation. + public static async Task Marketplace_DirectListings_CancelListing(this ThirdwebContract contract, IThirdwebWallet wallet, BigInteger listingId) + { + if (contract == null) + { + throw new ArgumentNullException(nameof(contract)); + } + + if (wallet == null) + { + throw new ArgumentNullException(nameof(wallet)); + } + + return await contract.Write(wallet, "cancelListing", 0, listingId); + } + + /// + /// Approves a buyer to purchase from a reserved listing. + /// + /// The contract instance. + /// The wallet used for the transaction. + /// The ID of the listing. + /// The address of the buyer to approve. + /// Whether to approve or disapprove the buyer. + /// A task that represents the transaction receipt of the approval. + public static async Task Marketplace_DirectListings_ApproveBuyerForListing( + this ThirdwebContract contract, + IThirdwebWallet wallet, + BigInteger listingId, + string buyer, + bool toApprove + ) + { + if (contract == null) + { + throw new ArgumentNullException(nameof(contract)); + } + + if (wallet == null) + { + throw new ArgumentNullException(nameof(wallet)); + } + + return await contract.Write(wallet, "approveBuyerForListing", 0, listingId, buyer, toApprove); + } + + /// + /// Approves a currency for a direct listing. + /// + /// The contract instance. + /// The wallet used for the transaction. + /// The ID of the listing. + /// The address of the currency to approve. + /// The price per token in the specified currency. + /// A task that represents the transaction receipt of the currency approval. + public static async Task Marketplace_DirectListings_ApproveCurrencyForListing( + this ThirdwebContract contract, + IThirdwebWallet wallet, + BigInteger listingId, + string currency, + BigInteger pricePerTokenInCurrency + ) + { + if (contract == null) + { + throw new ArgumentNullException(nameof(contract)); + } + + if (wallet == null) + { + throw new ArgumentNullException(nameof(wallet)); + } + + return await contract.Write(wallet, "approveCurrencyForListing", 0, listingId, currency, pricePerTokenInCurrency); + } + + /// + /// Buys from a direct listing. + /// + /// The contract instance. + /// The wallet used for the transaction. + /// The ID of the listing. + /// The recipient address for the purchased NFTs. + /// The quantity of NFTs to buy. + /// The currency to use for the purchase. + /// The expected total price to pay. + /// Whether to handle token approvals automatically. + /// A task that represents the transaction receipt of the purchase. + public static async Task Marketplace_DirectListings_BuyFromListing( + this ThirdwebContract contract, + IThirdwebWallet wallet, + BigInteger listingId, + string buyFor, + BigInteger quantity, + string currency, + BigInteger expectedTotalPrice, + bool handleApprovals = false + ) + { + if (contract == null) + { + throw new ArgumentNullException(nameof(contract)); + } + + if (wallet == null) + { + throw new ArgumentNullException(nameof(wallet)); + } + + var value = BigInteger.Zero; + + if (currency == Constants.NATIVE_TOKEN_ADDRESS) + { + value = expectedTotalPrice; + } + else if (handleApprovals) + { + var tokenContractAddress = currency; + + var prepTasks = new List(); + + var tokenContractTask = ThirdwebContract.Create(contract.Client, tokenContractAddress, contract.Chain); + prepTasks.Add(tokenContractTask); + + var walletAddressTask = wallet.GetAddress(); + prepTasks.Add(walletAddressTask); + + await Task.WhenAll(prepTasks); + + var tokenContract = tokenContractTask.Result; + var walletAddress = walletAddressTask.Result; + + var allowance = await tokenContract.ERC20_Allowance(walletAddress, contract.Address); + if (allowance < expectedTotalPrice) + { + _ = await tokenContract.ERC20_Approve(wallet, contract.Address, expectedTotalPrice); + } + } + + return await contract.Write(wallet, "buyFromListing", value, listingId, buyFor, quantity, currency, expectedTotalPrice); + } + + /// + /// Gets the total number of direct listings created. + /// + /// The contract instance. + /// A task that represents the total number of direct listings. + public static async Task Marketplace_DirectListings_TotalListings(this ThirdwebContract contract) + { + if (contract == null) + { + throw new ArgumentNullException(nameof(contract)); + } + + return await contract.Read("totalListings"); + } + + /// + /// Gets all direct listings within a given range of IDs. + /// + /// The contract instance. + /// The start ID of the range. + /// The end ID of the range. + /// A task that represents a list of listings within the range. + public static async Task> Marketplace_DirectListings_GetAllListings(this ThirdwebContract contract, BigInteger startId, BigInteger endId) + { + if (contract == null) + { + throw new ArgumentNullException(nameof(contract)); + } + + return await contract.Read>("getAllListings", startId, endId); + } + + /// + /// Gets all valid direct listings within a given range of IDs. + /// + /// The contract instance. + /// The start ID of the range. + /// The end ID of the range. + /// A task that represents a list of valid listings within the range. + public static async Task> Marketplace_DirectListings_GetAllValidListings(this ThirdwebContract contract, BigInteger startId, BigInteger endId) + { + if (contract == null) + { + throw new ArgumentNullException(nameof(contract)); + } + + return await contract.Read>("getAllValidListings", startId, endId); + } + + /// + /// Gets a specific direct listing by its ID. + /// + /// The contract instance. + /// The ID of the listing to fetch. + /// A task that represents the requested listing. + public static async Task Marketplace_DirectListings_GetListing(this ThirdwebContract contract, BigInteger listingId) + { + if (contract == null) + { + throw new ArgumentNullException(nameof(contract)); + } + + return await contract.Read("getListing", listingId); + } + + /// + /// Checks whether a buyer is approved for a direct listing. + /// + /// The contract instance. + /// The ID of the listing. + /// The address of the buyer to check. + /// A task that represents a boolean indicating if the buyer is approved. + public static async Task Marketplace_DirectListings_IsBuyerApprovedForListing(this ThirdwebContract contract, BigInteger listingId, string buyer) + { + if (contract == null) + { + throw new ArgumentNullException(nameof(contract)); + } + + return await contract.Read("isBuyerApprovedForListing", listingId, buyer); + } + + /// + /// Checks whether a currency is approved for a direct listing. + /// + /// The contract instance. + /// The ID of the listing. + /// The address of the currency to check. + /// A task that represents a boolean indicating if the currency is approved. + public static async Task Marketplace_DirectListings_IsCurrencyApprovedForListing(this ThirdwebContract contract, BigInteger listingId, string currency) + { + if (contract == null) + { + throw new ArgumentNullException(nameof(contract)); + } + + return await contract.Read("isCurrencyApprovedForListing", listingId, currency); + } + + /// + /// Gets the price per token for a direct listing in the specified currency. + /// + /// The contract instance. + /// The ID of the listing. + /// The address of the currency to check. + /// A task that represents the price per token in the specified currency. + public static async Task Marketplace_DirectListings_CurrencyPriceForListing(this ThirdwebContract contract, BigInteger listingId, string currency) + { + if (contract == null) + { + throw new ArgumentNullException(nameof(contract)); + } + + return await contract.Read("currencyPriceForListing", listingId, currency); + } + + #endregion + + #region IEnglishAuctions + + /// + /// Creates a new auction listing for NFTs. + /// + /// The contract instance. + /// The wallet used for the transaction. + /// The parameters of the auction to be created. + /// Whether to handle token approvals automatically. + /// A task that represents the transaction receipt of the auction creation. + public static async Task Marketplace_EnglishAuctions_CreateAuction( + this ThirdwebContract contract, + IThirdwebWallet wallet, + AuctionParameters parameters, + bool handleApprovals = false + ) + { + if (contract == null) + { + throw new ArgumentNullException(nameof(contract)); + } + + if (wallet == null) + { + throw new ArgumentNullException(nameof(wallet)); + } + + if (parameters == null) + { + throw new ArgumentNullException(nameof(parameters)); + } + + if (handleApprovals) + { + var assetContractAddress = parameters.AssetContract; + + var prepTasks = new List(); + + var assetContractTask = ThirdwebContract.Create(contract.Client, assetContractAddress, contract.Chain); + prepTasks.Add(assetContractTask); + + var walletAddressTask = wallet.GetAddress(); + prepTasks.Add(walletAddressTask); + + await Task.WhenAll(prepTasks); + + var assetContract = assetContractTask.Result; + var walletAddress = walletAddressTask.Result; + + TokenType assetType; + if (await assetContract.SupportsInterface(Constants.IERC1155_INTERFACE_ID)) + { + assetType = TokenType.ERC1155; + } + else if (await assetContract.SupportsInterface(Constants.IERC721_INTERFACE_ID)) + { + assetType = TokenType.ERC721; + } + else + { + throw new ArgumentException("Asset contract does not support ERC1155 or ERC721 interface."); + } + + if (assetType == TokenType.ERC721) + { + var tokenId = parameters.TokenId; + var @operator = await assetContract.ERC721_GetApproved(tokenId); + if (@operator != contract.Address) + { + _ = await assetContract.ERC721_Approve(wallet, contract.Address, tokenId); + } + } + else + { + var isApprovedForAll = await assetContract.ERC1155_IsApprovedForAll(walletAddress, contract.Address); + if (!isApprovedForAll) + { + _ = await assetContract.ERC1155_SetApprovalForAll(wallet, contract.Address, true); + } + } + } + + return await contract.Write(wallet, "createAuction", 0, parameters); + } + + /// + /// Cancels an existing auction listing. + /// + /// The contract instance. + /// The wallet used for the transaction. + /// The ID of the auction to cancel. + /// A task that represents the transaction receipt of the auction cancellation. + public static async Task Marketplace_EnglishAuctions_CancelAuction(this ThirdwebContract contract, IThirdwebWallet wallet, BigInteger auctionId) + { + if (contract == null) + { + throw new ArgumentNullException(nameof(contract)); + } + + if (wallet == null) + { + throw new ArgumentNullException(nameof(wallet)); + } + + return await contract.Write(wallet, "cancelAuction", 0, auctionId); + } + + /// + /// Collects the payout for a completed auction. + /// + /// The contract instance. + /// The wallet used for the transaction. + /// The ID of the auction for which to collect the payout. + /// A task that represents the transaction receipt of the auction payout collection. + public static async Task Marketplace_EnglishAuctions_CollectAuctionPayout(this ThirdwebContract contract, IThirdwebWallet wallet, BigInteger auctionId) + { + if (contract == null) + { + throw new ArgumentNullException(nameof(contract)); + } + + if (wallet == null) + { + throw new ArgumentNullException(nameof(wallet)); + } + + return await contract.Write(wallet, "collectAuctionPayout", 0, auctionId); + } + + /// + /// Collects the tokens from a completed auction. + /// + /// The contract instance. + /// The wallet used for the transaction. + /// The ID of the auction for which to collect the tokens. + /// A task that represents the transaction receipt of the auction token collection. + public static async Task Marketplace_EnglishAuctions_CollectAuctionTokens(this ThirdwebContract contract, IThirdwebWallet wallet, BigInteger auctionId) + { + if (contract == null) + { + throw new ArgumentNullException(nameof(contract)); + } + + if (wallet == null) + { + throw new ArgumentNullException(nameof(wallet)); + } + + return await contract.Write(wallet, "collectAuctionTokens", 0, auctionId); + } + + /// + /// Places a bid in an auction. + /// + /// The contract instance. + /// The wallet used for the transaction. + /// The ID of the auction to bid in. + /// The bid amount to place. + /// Whether to handle token approvals automatically. + /// A task that represents the transaction receipt of the placed bid. + public static async Task Marketplace_EnglishAuctions_BidInAuction( + this ThirdwebContract contract, + IThirdwebWallet wallet, + BigInteger auctionId, + BigInteger bidAmount, + bool handleApprovals = false + ) + { + if (contract == null) + { + throw new ArgumentNullException(nameof(contract)); + } + + if (wallet == null) + { + throw new ArgumentNullException(nameof(wallet)); + } + + var value = BigInteger.Zero; + + var auctionDetails = await contract.Marketplace_EnglishAuctions_GetAuction(auctionId); + if (auctionDetails.Currency == Constants.NATIVE_TOKEN_ADDRESS) + { + value = bidAmount; + } + else if (handleApprovals) + { + var tokenContractAddress = auctionDetails.Currency; + + var prepTasks = new List(); + + var tokenContractTask = ThirdwebContract.Create(contract.Client, tokenContractAddress, contract.Chain); + prepTasks.Add(tokenContractTask); + + var walletAddressTask = wallet.GetAddress(); + prepTasks.Add(walletAddressTask); + + await Task.WhenAll(prepTasks); + + var tokenContract = tokenContractTask.Result; + var walletAddress = walletAddressTask.Result; + + var allowance = await tokenContract.ERC20_Allowance(walletAddress, contract.Address); + if (allowance < bidAmount) + { + _ = await tokenContract.ERC20_Approve(wallet, contract.Address, bidAmount); + } + } + + return await contract.Write(wallet, "bidInAuction", value, auctionId, bidAmount); + } + + /// + /// Checks whether the bid amount would make for a winning bid in an auction. + /// + /// The contract instance. + /// The ID of the auction. + /// The bid amount to check. + /// A task that represents a boolean indicating if the bid would be a winning bid. + public static async Task Marketplace_EnglishAuctions_IsNewWinningBid(this ThirdwebContract contract, BigInteger auctionId, BigInteger bidAmount) + { + if (contract == null) + { + throw new ArgumentNullException(nameof(contract)); + } + + return await contract.Read("isNewWinningBid", auctionId, bidAmount); + } + + /// + /// Retrieves the details of a specific auction by its ID. + /// + /// The contract instance. + /// The ID of the auction to fetch. + /// A task that represents the requested auction details. + public static async Task Marketplace_EnglishAuctions_GetAuction(this ThirdwebContract contract, BigInteger auctionId) + { + if (contract == null) + { + throw new ArgumentNullException(nameof(contract)); + } + + return await contract.Read("getAuction", auctionId); + } + + /// + /// Gets all auctions within a given range of IDs. + /// + /// The contract instance. + /// The start ID of the range. + /// The end ID of the range. + /// A task that represents a list of auctions within the range. + public static async Task> Marketplace_EnglishAuctions_GetAllAuctions(this ThirdwebContract contract, BigInteger startId, BigInteger endId) + { + if (contract == null) + { + throw new ArgumentNullException(nameof(contract)); + } + + return await contract.Read>("getAllAuctions", startId, endId); + } + + /// + /// Gets all valid auctions within a given range of IDs. + /// + /// The contract instance. + /// The start ID of the range. + /// The end ID of the range. + /// A task that represents a list of valid auctions within the range. + public static async Task> Marketplace_EnglishAuctions_GetAllValidAuctions(this ThirdwebContract contract, BigInteger startId, BigInteger endId) + { + if (contract == null) + { + throw new ArgumentNullException(nameof(contract)); + } + + return await contract.Read>("getAllValidAuctions", startId, endId); + } + + /// + /// Gets the winning bid of a specific auction. + /// + /// The contract instance. + /// The ID of the auction to retrieve the winning bid from. + /// A task that represents the winning bid details (bidder, currency, bidAmount). + public static async Task<(string bidder, string currency, BigInteger bidAmount)> Marketplace_EnglishAuctions_GetWinningBid(this ThirdwebContract contract, BigInteger auctionId) + { + if (contract == null) + { + throw new ArgumentNullException(nameof(contract)); + } + + var res = await contract.Read>("getWinningBid", auctionId); + return (res[0].ToString(), res[1].ToString(), (BigInteger)res[2]); + } + + /// + /// Checks whether an auction is expired. + /// + /// The contract instance. + /// The ID of the auction to check. + /// A task that represents a boolean indicating if the auction is expired. + public static async Task Marketplace_EnglishAuctions_IsAuctionExpired(this ThirdwebContract contract, BigInteger auctionId) + { + if (contract == null) + { + throw new ArgumentNullException(nameof(contract)); + } + + return await contract.Read("isAuctionExpired", auctionId); + } + + /// + /// Gets the total number of auctions created. + /// + /// The contract instance. + /// A task that represents the total number of auctions. + public static async Task Marketplace_EnglishAuctions_TotalAuctions(this ThirdwebContract contract) + { + if (contract == null) + { + throw new ArgumentNullException(nameof(contract)); + } + + return await contract.Read("totalAuctions"); + } + + #endregion + + #region IOffers + + /// + /// Makes an offer for NFTs. + /// + /// The contract instance. + /// The wallet used for the transaction. + /// The parameters of the offer to make. + /// Whether to handle token approvals automatically. + /// A task that represents the transaction receipt of the offer creation. + public static async Task Marketplace_Offers_MakeOffer(this ThirdwebContract contract, IThirdwebWallet wallet, OfferParams parameters, bool handleApprovals = false) + { + if (contract == null) + { + throw new ArgumentNullException(nameof(contract)); + } + + if (wallet == null) + { + throw new ArgumentNullException(nameof(wallet)); + } + + if (parameters == null) + { + throw new ArgumentNullException(nameof(parameters)); + } + + var token = parameters.Currency; + if (token == Constants.NATIVE_TOKEN_ADDRESS) + { + throw new ArgumentException("Native token is not supported for offers, please wrap it or use ERC20 to make an offer.", nameof(parameters)); + } + + if (handleApprovals) + { + var prepTasks = new List(); + + var tokenContractTask = ThirdwebContract.Create(contract.Client, token, contract.Chain); + prepTasks.Add(tokenContractTask); + + var walletAddressTask = wallet.GetAddress(); + prepTasks.Add(walletAddressTask); + + await Task.WhenAll(prepTasks); + + var tokenContract = tokenContractTask.Result; + var walletAddress = walletAddressTask.Result; + + var allowance = await tokenContract.ERC20_Allowance(walletAddress, contract.Address); + if (allowance < parameters.TotalPrice) + { + _ = await tokenContract.ERC20_Approve(wallet, contract.Address, parameters.Quantity); + } + } + + return await contract.Write(wallet, "makeOffer", 0, parameters); + } + + /// + /// Cancels an existing offer. + /// + /// The contract instance. + /// The wallet used for the transaction. + /// The ID of the offer to cancel. + /// A task that represents the transaction receipt of the offer cancellation. + public static async Task Marketplace_Offers_CancelOffer(this ThirdwebContract contract, IThirdwebWallet wallet, BigInteger offerId) + { + if (contract == null) + { + throw new ArgumentNullException(nameof(contract)); + } + + if (wallet == null) + { + throw new ArgumentNullException(nameof(wallet)); + } + + return await contract.Write(wallet, "cancelOffer", 0, offerId); + } + + /// + /// Accepts an existing offer. + /// + /// The contract instance. + /// The wallet used for the transaction. + /// The ID of the offer to accept. + /// A task that represents the transaction receipt of the offer acceptance. + public static async Task Marketplace_Offers_AcceptOffer(this ThirdwebContract contract, IThirdwebWallet wallet, BigInteger offerId) + { + if (contract == null) + { + throw new ArgumentNullException(nameof(contract)); + } + + if (wallet == null) + { + throw new ArgumentNullException(nameof(wallet)); + } + + return await contract.Write(wallet, "acceptOffer", 0, offerId); + } + + /// + /// Retrieves the details of a specific offer by its ID. + /// + /// The contract instance. + /// The ID of the offer to fetch. + /// A task that represents the requested offer details. + public static async Task Marketplace_Offers_GetOffer(this ThirdwebContract contract, BigInteger offerId) + { + if (contract == null) + { + throw new ArgumentNullException(nameof(contract)); + } + + return await contract.Read("getOffer", offerId); + } + + /// + /// Gets all offers within a given range of IDs. + /// + /// The contract instance. + /// The start ID of the range. + /// The end ID of the range. + /// A task that represents a list of offers within the range. + public static async Task> Marketplace_Offers_GetAllOffers(this ThirdwebContract contract, BigInteger startId, BigInteger endId) + { + if (contract == null) + { + throw new ArgumentNullException(nameof(contract)); + } + + return await contract.Read>("getAllOffers", startId, endId); + } + + /// + /// Gets all valid offers within a given range of IDs. + /// + /// The contract instance. + /// The start ID of the range. + /// The end ID of the range. + /// A task that represents a list of valid offers within the range. + public static async Task> Marketplace_Offers_GetAllValidOffers(this ThirdwebContract contract, BigInteger startId, BigInteger endId) + { + if (contract == null) + { + throw new ArgumentNullException(nameof(contract)); + } + + return await contract.Read>("getAllValidOffers", startId, endId); + } + + /// + /// Gets the total number of offers created. + /// + /// The contract instance. + /// A task that represents the total number of offers. + public static async Task Marketplace_Offers_TotalOffers(this ThirdwebContract contract) + { + if (contract == null) + { + throw new ArgumentNullException(nameof(contract)); + } + + return await contract.Read("totalOffers"); + } + + #endregion +} diff --git a/Thirdweb/Thirdweb.Utils/Constants.cs b/Thirdweb/Thirdweb.Utils/Constants.cs index be69811b..3fb1e0b1 100644 --- a/Thirdweb/Thirdweb.Utils/Constants.cs +++ b/Thirdweb/Thirdweb.Utils/Constants.cs @@ -2,6 +2,10 @@ public static class Constants { + public const string IERC20_INTERFACE_ID = "0x36372b07"; + public const string IERC721_INTERFACE_ID = "0x80ac58cd"; + public const string IERC1155_INTERFACE_ID = "0xd9b67a26"; + public const string ADDRESS_ZERO = "0x0000000000000000000000000000000000000000"; public const string NATIVE_TOKEN_ADDRESS = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"; public const double DECIMALS_18 = 1000000000000000000;