diff --git a/Assets/SequenceSDK/Ethereum/Tests/ERC1155SaleTests.cs b/Assets/SequenceSDK/Ethereum/Tests/ERC1155SaleTests.cs new file mode 100644 index 000000000..e47b191b2 --- /dev/null +++ b/Assets/SequenceSDK/Ethereum/Tests/ERC1155SaleTests.cs @@ -0,0 +1,55 @@ +using System; +using System.Numerics; +using System.Threading.Tasks; +using NUnit.Framework; +using Sequence; +using Sequence.ABI; +using Sequence.Contracts; +using Sequence.Ethereum.Tests; +using Sequence.Provider; +using Sequence.Wallet; + +namespace Sequence.Ethereum +{ + public class ERC1155SaleTests + { + private ERC1155Sale _sale = new ERC1155Sale("0xf0056139095224f4eec53c578ab4de1e227b9597"); + private IEthClient _client = new SequenceEthClient(Chain.Polygon); + + [Test] + public async Task TestGetSaleToken() + { + Address saleCurrency = await _sale.GetPaymentTokenAsync(_client); + + Assert.NotNull(saleCurrency); + } + + [Test] + public async Task TestMerkleProof() + { + bool valid = await _sale.CheckMerkleProofAsync(_client, new FixedByte(32, ""), new FixedByte[]{}, _sale.Contract.GetAddress(), new FixedByte(32, "")); + + Assert.IsFalse(valid); + } + + [Test] + public async Task TestGetGlobalSaleDetails() + { + ERC1155Sale.SaleDetails details = await _sale.GetGlobalSaleDetailsAsync(_client); + + Assert.NotNull(details); + } + + [Test] + public async Task TestGetTokenSaleDetails() + { + ERC1155Sale.SaleDetails details = await _sale.TokenSaleDetailsAsync(_client, 1); + + Assert.NotNull(details); + Assert.Greater(details.StartTimeLong, 0); + Assert.Greater(details.EndTimeLong, 0); + Assert.Greater(details.SupplyCap, BigInteger.Zero); + Assert.Greater(details.Cost, BigInteger.Zero); + } + } +} \ No newline at end of file diff --git a/Assets/SequenceSDK/Ethereum/Tests/ERC1155SaleTests.cs.meta b/Assets/SequenceSDK/Ethereum/Tests/ERC1155SaleTests.cs.meta new file mode 100644 index 000000000..4f85154eb --- /dev/null +++ b/Assets/SequenceSDK/Ethereum/Tests/ERC1155SaleTests.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 94e43a9d16754d2bb88152408a3299f1 +timeCreated: 1737747687 \ No newline at end of file diff --git a/Packages/Sequence-Unity/Sequence/SequenceFrontend/Scripts/UI/Demos/PrimarySales/PrimarySalePage.cs b/Packages/Sequence-Unity/Sequence/SequenceFrontend/Scripts/UI/Demos/PrimarySales/PrimarySalePage.cs index a305abbe3..a61999019 100644 --- a/Packages/Sequence-Unity/Sequence/SequenceFrontend/Scripts/UI/Demos/PrimarySales/PrimarySalePage.cs +++ b/Packages/Sequence-Unity/Sequence/SequenceFrontend/Scripts/UI/Demos/PrimarySales/PrimarySalePage.cs @@ -135,7 +135,7 @@ private void SetResult(bool success) _resultText.text = success ? "Success" : "Failed"; } - private string ConvertTime(int timestamp) + private string ConvertTime(long timestamp) { var localDateTime = DateTimeOffset.FromUnixTimeSeconds(timestamp).LocalDateTime; return localDateTime.ToString("dd.MM.yyyy HH:mm:ss"); diff --git a/Packages/Sequence-Unity/Sequence/SequenceFrontend/Scripts/UI/Demos/PrimarySales/PrimarySaleStateERC1155.cs b/Packages/Sequence-Unity/Sequence/SequenceFrontend/Scripts/UI/Demos/PrimarySales/PrimarySaleStateERC1155.cs index dfefc6f4a..44a65cfef 100644 --- a/Packages/Sequence-Unity/Sequence/SequenceFrontend/Scripts/UI/Demos/PrimarySales/PrimarySaleStateERC1155.cs +++ b/Packages/Sequence-Unity/Sequence/SequenceFrontend/Scripts/UI/Demos/PrimarySales/PrimarySaleStateERC1155.cs @@ -18,8 +18,8 @@ public class PrimarySaleStateERC1155 public BigInteger UserPaymentBalance { get; private set; } public BigInteger Cost { get; private set; } public BigInteger SupplyCap { get; private set; } - public int StartTime { get; private set; } - public int EndTime { get; private set; } + public long StartTime { get; private set; } + public long EndTime { get; private set; } public int TotalMinted { get; private set; } public Dictionary TokenSupplies { get; private set; } @@ -81,8 +81,8 @@ private async Task UpdateSaleDetailsAsync() var globalSaleDetails = await _saleContract.GetGlobalSaleDetailsAsync(_client); Cost = globalSaleDetails.Cost; SupplyCap = globalSaleDetails.SupplyCap; - StartTime = globalSaleDetails.StartTime; - EndTime = globalSaleDetails.EndTime; + StartTime = globalSaleDetails.StartTimeLong; + EndTime = globalSaleDetails.EndTimeLong; } private async Task UpdatePaymentTokenAsync() @@ -127,7 +127,7 @@ private void AddAmountToTokenId(BigInteger tokenId, int amount) { TotalMinted += amount; var curSupplyStr = TokenSupplies[tokenId].supply; - var curSupply = int.TryParse(curSupplyStr, out var value) ? value : 0; + var curSupply = BigInteger.TryParse(curSupplyStr, out var value) ? value : 0; TokenSupplies[tokenId].supply = (curSupply + amount).ToString(); } } diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/ABI/ABI.cs b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/ABI/ABI.cs index 353d4ad4a..83b21ef70 100644 --- a/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/ABI/ABI.cs +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/ABI/ABI.cs @@ -191,7 +191,15 @@ public static FunctionAbi DecodeAbi(string abi) if (outputsArray != null && outputsArray.Count > 0) { string[] outputTypes = ExtractTypes(outputsArray); - returnType = $"({string.Join(", ", outputTypes)})"; + if (outputTypes.Length == 1 && outputTypes[0].StartsWith('(') && + outputTypes[0].EndsWith(')')) + { + returnType = outputTypes[0]; + } + else + { + returnType = $"({string.Join(", ", outputTypes)})"; + } } decodedAbi = AddToDictionary(decodedAbi, functionName, argumentTypes, returnType); diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/ABI/Coders/AddressCoder.cs b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/ABI/Coders/AddressCoder.cs index b62a577b6..06d1b1fcd 100644 --- a/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/ABI/Coders/AddressCoder.cs +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/ABI/Coders/AddressCoder.cs @@ -1,5 +1,6 @@ using System; +using Sequence.Utils; using UnityEngine; namespace Sequence.ABI @@ -91,9 +92,13 @@ public string EncodeToString(object value) /// The decoded address string. public string DecodeFromString(string encodedString) { + if (encodedString.IsZeroAddress()) + { + return StringExtensions.ZeroAddress; + } + try { - //cut leading zeros encodedString = encodedString.Replace("0x", "").TrimStart('0'); return "0x" + encodedString; } diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/ABI/Coders/TupleCoder.cs b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/ABI/Coders/TupleCoder.cs index 2d6885921..e80eeffd7 100644 --- a/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/ABI/Coders/TupleCoder.cs +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/ABI/Coders/TupleCoder.cs @@ -49,6 +49,11 @@ public string EncodeToString(object value, string[] evmTypes) throw new ArgumentException($"{nameof(evmTypes)} must not be null"); } + if (value == null) + { + return new string('0', 32); + } + if (value.IsTuple()) { value = value.ToObjectList(); @@ -74,7 +79,11 @@ public string EncodeToString(object value, string[] evmTypes) { string head_i = "", tail_i = ""; ABIType type = ABI.GetTypeFromEvmName(evmTypes[i]); - ABIType temp = ABI.GetParameterType(valueTuple[i]); + ABIType temp = type; + if (valueTuple[i] != null) + { + temp = ABI.GetParameterType(valueTuple[i]); + } if (temp != type && type != ABIType.FIXEDARRAY && type != ABIType.DYNAMICARRAY) // If it is a non-array data type, a mismatch will cause encoding issues - with arrays, a mismatch may cause encoding issues but it is difficult to predict { throw new ArgumentException($"Argument type is not as expected. Expected: {type} Received: {temp}"); @@ -116,7 +125,11 @@ public string EncodeToString(object value, string[] evmTypes) head_i = _numberCoder.EncodeToString((object)(headerTotalByteLength + tailLength)); UnityEngine.Debug.Log("dynamic array head: " + head_i); //intList.Cast().ToList(); - int numberCount = ((IList)valueTuple[i]).Count; + int numberCount = 0; + if (valueTuple[i] != null) + { + numberCount = ((IList)valueTuple[i]).Count; + } UnityEngine.Debug.Log("number count:" + numberCount); string numberCountEncoded = _numberCoder.EncodeToString(numberCount); diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/Contract.cs b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/Contract.cs index e18f51f6b..d824f21fc 100644 --- a/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/Contract.cs +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/Contract.cs @@ -48,6 +48,11 @@ public Address GetAddress() { return address; } + + public static implicit operator Address(Contract contract) + { + return contract.GetAddress(); + } public async Task Deploy(string bytecode, params object[] constructorArgs) { diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/ContractWrapper.cs b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/ContractWrapper.cs new file mode 100644 index 000000000..32a59b5d9 --- /dev/null +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/ContractWrapper.cs @@ -0,0 +1,22 @@ +namespace Sequence.Contracts +{ + public abstract class ContractWrapper + { + public Contract Contract { get; private set; } + + protected ContractWrapper(Contract contract) + { + this.Contract = contract; + } + + protected ContractWrapper(string contractAddress, string abi) + { + this.Contract = new Contract(contractAddress, abi); + } + + public static implicit operator Address(ContractWrapper contractWrapper) + { + return contractWrapper.Contract.GetAddress(); + } + } +} \ No newline at end of file diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/ContractWrapper.cs.meta b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/ContractWrapper.cs.meta new file mode 100644 index 000000000..987ad3ab1 --- /dev/null +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/ContractWrapper.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: aac974b2526e4075955b8e82a2c535fb +timeCreated: 1737650621 \ No newline at end of file diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/ERC1155Sale.cs b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/ERC1155Sale.cs index a017f2ab2..67baad5ec 100644 --- a/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/ERC1155Sale.cs +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/ERC1155Sale.cs @@ -1,21 +1,29 @@ using System; using System.Numerics; using System.Threading.Tasks; +using Sequence.ABI; using Sequence.Provider; using Sequence.Utils; namespace Sequence.Contracts { - public class ERC1155Sale : Ownable + public class ERC1155Sale : MerkleProvable { public struct SaleDetails { public BigInteger Cost; public BigInteger SupplyCap; - public int StartTime; - public int EndTime; + public long StartTimeLong; + public long EndTimeLong; + public byte[] MerkleRoot; + + [Obsolete("Use StartTimeLong instead to avoid overflow errors")] + public int StartTime => (int) StartTimeLong; + + [Obsolete("Use EndTimeLong instead to avoid overflow errors")] + public int EndTime => (int) EndTimeLong; } - + private const string Abi = "[{\"type\":\"function\",\"name\":\"DEFAULT_ADMIN_ROLE\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"checkMerkleProof\",\"inputs\":[{\"name\":\"root\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"proof\",\"type\":\"bytes32[]\",\"internalType\":\"bytes32[]\"},{\"name\":\"addr\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"salt\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getRoleAdmin\",\"inputs\":[{\"name\":\"role\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getRoleMember\",\"inputs\":[{\"name\":\"role\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"index\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getRoleMemberCount\",\"inputs\":[{\"name\":\"role\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"globalSaleDetails\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"tuple\",\"internalType\":\"struct IERC1155SaleFunctions.SaleDetails\",\"components\":[{\"name\":\"cost\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"supplyCap\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"startTime\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"endTime\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"merkleRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}]}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"grantRole\",\"inputs\":[{\"name\":\"role\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"account\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"hasRole\",\"inputs\":[{\"name\":\"role\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"account\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"initialize\",\"inputs\":[{\"name\":\"owner\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"items\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"mint\",\"inputs\":[{\"name\":\"to\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"tokenIds\",\"type\":\"uint256[]\",\"internalType\":\"uint256[]\"},{\"name\":\"amounts\",\"type\":\"uint256[]\",\"internalType\":\"uint256[]\"},{\"name\":\"data\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"expectedPaymentToken\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"maxTotal\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"proof\",\"type\":\"bytes32[]\",\"internalType\":\"bytes32[]\"}],\"outputs\":[],\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"paymentToken\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"renounceRole\",\"inputs\":[{\"name\":\"role\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"account\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"revokeRole\",\"inputs\":[{\"name\":\"role\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"account\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setGlobalSaleDetails\",\"inputs\":[{\"name\":\"cost\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"supplyCap\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"startTime\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"endTime\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"merkleRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setPaymentToken\",\"inputs\":[{\"name\":\"paymentTokenAddr\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setTokenSaleDetails\",\"inputs\":[{\"name\":\"tokenId\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"cost\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"supplyCap\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"startTime\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"endTime\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"merkleRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"supportsInterface\",\"inputs\":[{\"name\":\"interfaceId\",\"type\":\"bytes4\",\"internalType\":\"bytes4\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"tokenSaleDetails\",\"inputs\":[{\"name\":\"tokenId\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"\",\"type\":\"tuple\",\"internalType\":\"struct IERC1155SaleFunctions.SaleDetails\",\"components\":[{\"name\":\"cost\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"supplyCap\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"startTime\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"endTime\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"merkleRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}]}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"withdrawERC20\",\"inputs\":[{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"to\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"value\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"withdrawETH\",\"inputs\":[{\"name\":\"to\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"value\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"event\",\"name\":\"GlobalSaleDetailsUpdated\",\"inputs\":[{\"name\":\"cost\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"},{\"name\":\"supplyCap\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"},{\"name\":\"startTime\",\"type\":\"uint64\",\"indexed\":false,\"internalType\":\"uint64\"},{\"name\":\"endTime\",\"type\":\"uint64\",\"indexed\":false,\"internalType\":\"uint64\"},{\"name\":\"merkleRoot\",\"type\":\"bytes32\",\"indexed\":false,\"internalType\":\"bytes32\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"RoleAdminChanged\",\"inputs\":[{\"name\":\"role\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"previousAdminRole\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"newAdminRole\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"RoleGranted\",\"inputs\":[{\"name\":\"role\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"account\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"sender\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"RoleRevoked\",\"inputs\":[{\"name\":\"role\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"account\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"sender\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"TokenSaleDetailsUpdated\",\"inputs\":[{\"name\":\"tokenId\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"},{\"name\":\"cost\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"},{\"name\":\"supplyCap\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"},{\"name\":\"startTime\",\"type\":\"uint64\",\"indexed\":false,\"internalType\":\"uint64\"},{\"name\":\"endTime\",\"type\":\"uint64\",\"indexed\":false,\"internalType\":\"uint64\"},{\"name\":\"merkleRoot\",\"type\":\"bytes32\",\"indexed\":false,\"internalType\":\"bytes32\"}],\"anonymous\":false},{\"type\":\"error\",\"name\":\"GlobalSaleInactive\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InsufficientPayment\",\"inputs\":[{\"name\":\"currency\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"expected\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"actual\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"InsufficientSupply\",\"inputs\":[{\"name\":\"currentSupply\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"requestedAmount\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"maxSupply\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"InvalidInitialization\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InvalidSaleDetails\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InvalidTokenIds\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"MerkleProofInvalid\",\"inputs\":[{\"name\":\"root\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"proof\",\"type\":\"bytes32[]\",\"internalType\":\"bytes32[]\"},{\"name\":\"addr\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"salt\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}]},{\"type\":\"error\",\"name\":\"SaleInactive\",\"inputs\":[{\"name\":\"tokenId\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"WithdrawFailed\",\"inputs\":[]}]"; private readonly Contract _contract; @@ -44,11 +52,6 @@ public CallContractFunction Mint(string to, BigInteger[] tokenIds, BigInteger[] return _contract.CallFunction("mint", to, tokenIds, amounts, data, expectedPaymentToken, maxTotal, proof); } - public async Task CheckMerkleProofAsync(IEthClient client, byte root, byte[] proof, string address, byte salt) - { - return await _contract.SendQuery(client, "checkMerkleProof", root, proof, address, salt); - } - public async Task GetGlobalSaleDetailsAsync(IEthClient client) { var results = await _contract.SendQuery(client, "globalSaleDetails"); @@ -70,10 +73,11 @@ private SaleDetails ConvertSaleDetails(object[] results) { return new SaleDetails { - Cost = new BigInteger(Convert.ToInt64(results[0].ToString())), - SupplyCap = new BigInteger(Convert.ToInt64(results[1].ToString())), - StartTime = Convert.ToInt32(results[2].ToString()), - EndTime = Convert.ToInt32(results[3].ToString()) + Cost = BigInteger.Parse(results[0].ToString()), + SupplyCap = BigInteger.Parse(results[1].ToString()), + StartTimeLong = long.Parse(results[2].ToString()), + EndTimeLong = long.Parse(results[3].ToString()), + MerkleRoot = results[4] as byte[] }; } } diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/ERC721Sale.cs b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/ERC721Sale.cs index 5ae398157..6325b9322 100644 --- a/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/ERC721Sale.cs +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/ERC721Sale.cs @@ -5,15 +5,20 @@ namespace Sequence.Contracts { - public class ERC721Sale : Ownable + public class ERC721Sale : MerkleProvable { public struct SaleDetails { public Address PaymentToken; public BigInteger Cost; public BigInteger SupplyCap; - public int StartTime; - public int EndTime; + public long StartTimeLong; + public long EndTimeLong; + + [Obsolete("Use StartTimeLong instead to avoid overflow errors")] + public int StartTime => (int)StartTimeLong; + [Obsolete("Use EndTimeLong instead to avoid overflow errors")] + public int EndTime => (int)EndTimeLong; } private const string Abi = "[{\"type\":\"function\",\"name\":\"DEFAULT_ADMIN_ROLE\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"checkMerkleProof\",\"inputs\":[{\"name\":\"root\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"proof\",\"type\":\"bytes32[]\",\"internalType\":\"bytes32[]\"},{\"name\":\"addr\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"salt\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getRoleAdmin\",\"inputs\":[{\"name\":\"role\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getRoleMember\",\"inputs\":[{\"name\":\"role\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"index\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getRoleMemberCount\",\"inputs\":[{\"name\":\"role\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"grantRole\",\"inputs\":[{\"name\":\"role\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"account\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"hasRole\",\"inputs\":[{\"name\":\"role\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"account\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"initialize\",\"inputs\":[{\"name\":\"owner\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"items\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"itemsContract\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"mint\",\"inputs\":[{\"name\":\"to\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"paymentToken\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"maxTotal\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"proof\",\"type\":\"bytes32[]\",\"internalType\":\"bytes32[]\"}],\"outputs\":[],\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"renounceRole\",\"inputs\":[{\"name\":\"role\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"account\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"revokeRole\",\"inputs\":[{\"name\":\"role\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"account\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"saleDetails\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"string\",\"internalType\":\"struct IERC721SaleFunctions.SaleDetails\",\"components\":[{\"name\":\"supplyCap\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"cost\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"paymentToken\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"startTime\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"endTime\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"merkleRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}]}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"setSaleDetails\",\"inputs\":[{\"name\":\"supplyCap\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"cost\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"paymentToken\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"startTime\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"endTime\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"merkleRoot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"supportsInterface\",\"inputs\":[{\"name\":\"interfaceId\",\"type\":\"bytes4\",\"internalType\":\"bytes4\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"withdrawERC20\",\"inputs\":[{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"to\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"value\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"withdrawETH\",\"inputs\":[{\"name\":\"to\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"value\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"event\",\"name\":\"RoleAdminChanged\",\"inputs\":[{\"name\":\"role\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"previousAdminRole\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"newAdminRole\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"RoleGranted\",\"inputs\":[{\"name\":\"role\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"account\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"sender\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"RoleRevoked\",\"inputs\":[{\"name\":\"role\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"account\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"sender\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"SaleDetailsUpdated\",\"inputs\":[{\"name\":\"supplyCap\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"},{\"name\":\"cost\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"},{\"name\":\"paymentToken\",\"type\":\"address\",\"indexed\":false,\"internalType\":\"address\"},{\"name\":\"startTime\",\"type\":\"uint64\",\"indexed\":false,\"internalType\":\"uint64\"},{\"name\":\"endTime\",\"type\":\"uint64\",\"indexed\":false,\"internalType\":\"uint64\"},{\"name\":\"merkleRoot\",\"type\":\"bytes32\",\"indexed\":false,\"internalType\":\"bytes32\"}],\"anonymous\":false},{\"type\":\"error\",\"name\":\"InsufficientPayment\",\"inputs\":[{\"name\":\"currency\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"expected\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"actual\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"InsufficientSupply\",\"inputs\":[{\"name\":\"currentSupply\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"maxSupply\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"InvalidInitialization\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InvalidSaleDetails\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"MerkleProofInvalid\",\"inputs\":[{\"name\":\"root\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"proof\",\"type\":\"bytes32[]\",\"internalType\":\"bytes32[]\"},{\"name\":\"addr\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"salt\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}]},{\"type\":\"error\",\"name\":\"SaleInactive\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"WithdrawFailed\",\"inputs\":[]}]"; @@ -41,11 +46,6 @@ public CallContractFunction Mint(string to, BigInteger amount, string paymentTok return _contract.CallFunction("mint", to, amount, paymentToken, maxTotal, proof); } - public async Task CheckMerkleProofAsync(IEthClient client, byte root, byte[] proof, string address, byte salt) - { - return await _contract.SendQuery(client, "checkMerkleProof", root, proof, address, salt); - } - public async Task ItemsContractAsync(IEthClient client) { return await _contract.SendQuery(client, "itemsContract"); @@ -61,11 +61,11 @@ private SaleDetails ConvertSaleDetails(object[] results) { return new SaleDetails { - Cost = new BigInteger(Convert.ToInt64(results[0].ToString())), - SupplyCap = new BigInteger(Convert.ToInt64(results[1].ToString())), + Cost = BigInteger.Parse(results[0].ToString()), + SupplyCap = BigInteger.Parse(results[1].ToString()), PaymentToken = new Address(results[2].ToString()), - StartTime = Convert.ToInt32(results[3].ToString()), - EndTime = Convert.ToInt32(results[4].ToString()) + StartTimeLong = long.Parse(results[3].ToString()), + EndTimeLong = long.Parse(results[4].ToString()) }; } } diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/MerkleProvable.cs b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/MerkleProvable.cs new file mode 100644 index 000000000..768950c2e --- /dev/null +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/MerkleProvable.cs @@ -0,0 +1,34 @@ +using System.Threading.Tasks; +using Sequence.ABI; +using Sequence.Provider; + +namespace Sequence.Contracts +{ + public class MerkleProvable : ContractWrapper + { + public Contract Contract { get; private set; } + public const string Abi = "[{\"type\":\"function\",\"name\":\"checkMerkleProof\",\"inputs\":[{\"name\":\"root\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"proof\",\"type\":\"bytes32[]\",\"internalType\":\"bytes32[]\"},{\"name\":\"addr\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"salt\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"error\",\"name\":\"MerkleProofInvalid\",\"inputs\":[{\"name\":\"root\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"proof\",\"type\":\"bytes32[]\",\"internalType\":\"bytes32[]\"},{\"name\":\"addr\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"salt\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}]}]"; + + public MerkleProvable(Contract contract) : base(contract) + { + this.Contract = contract; + } + + public MerkleProvable(string contractAddress, string abi = null) : base(contractAddress, abi) + { + if (abi == null) + { + this.Contract = new Contract(contractAddress, Abi); + } + else + { + this.Contract = new Contract(contractAddress, abi); + } + } + + public async Task CheckMerkleProofAsync(IEthClient client, FixedByte root, FixedByte[] proof, string address, FixedByte salt) + { + return await Contract.SendQuery(client, "checkMerkleProof", root, proof, address, salt); + } + } +} \ No newline at end of file diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/MerkleProvable.cs.meta b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/MerkleProvable.cs.meta new file mode 100644 index 000000000..9f389e631 --- /dev/null +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/MerkleProvable.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: e5fcf4af44844a43870f06f382225885 +timeCreated: 1738014024 \ No newline at end of file diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/Ownable.cs b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/Ownable.cs index 6b55fcbd6..c48dee6e5 100644 --- a/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/Ownable.cs +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/Ownable.cs @@ -7,12 +7,12 @@ namespace Sequence.Contracts { - public class Ownable + public class Ownable : ContractWrapper { public Contract Contract { get; private set; } public static readonly string Abi = "[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Approval\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"}],\"name\":\"allowance\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"approve\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"burn\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"burnFrom\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"decimals\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"subtractedValue\",\"type\":\"uint256\"}],\"name\":\"decreaseAllowance\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"addedValue\",\"type\":\"uint256\"}],\"name\":\"increaseAllowance\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"mint\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"name\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"renounceOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"symbol\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"totalSupply\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"transfer\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"transferFrom\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]"; - public Ownable(Contract contract) + public Ownable(Contract contract) : base(contract) { this.Contract = contract; } @@ -23,7 +23,7 @@ public Ownable(Contract contract) /// /// /// - public Ownable(string contractAddress, string abi = null) + public Ownable(string contractAddress, string abi = null) : base(contractAddress, abi) { if (abi == null) { diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Utils/StringExtensions.cs b/Packages/Sequence-Unity/Sequence/SequenceSDK/Utils/StringExtensions.cs index 833ecffd5..9c5b3e661 100644 --- a/Packages/Sequence-Unity/Sequence/SequenceSDK/Utils/StringExtensions.cs +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Utils/StringExtensions.cs @@ -2,6 +2,7 @@ using System.Numerics; using System.Text; using System.Text.RegularExpressions; +using UnityEngine; namespace Sequence.Utils { @@ -28,6 +29,7 @@ public static BigInteger HexStringToBigInteger(this string hexString) public static int HexStringToInt(this string hexString) { hexString = hexString.Replace("0x", ""); + hexString.TrimStart('0'); return int.Parse(hexString, System.Globalization.NumberStyles.HexNumber); } diff --git a/Packages/Sequence-Unity/package.json b/Packages/Sequence-Unity/package.json index 6c2b1312a..04b64ff90 100644 --- a/Packages/Sequence-Unity/package.json +++ b/Packages/Sequence-Unity/package.json @@ -1,6 +1,6 @@ { "name": "xyz.0xsequence.waas-unity", - "version": "3.19.1", + "version": "3.20.0", "displayName": "Sequence Embedded Wallet SDK", "description": "A Unity SDK for the Sequence WaaS API", "unity": "2021.3", diff --git a/testchain/contracts/ERC1155Sale.sol b/testchain/contracts/ERC1155Sale.sol new file mode 100644 index 000000000..9fd3ccec5 --- /dev/null +++ b/testchain/contracts/ERC1155Sale.sol @@ -0,0 +1,317 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.19; + +import { + IERC1155Sale, + IERC1155SaleFunctions +} from "@0xsequence/contracts-library/tokens/ERC1155/utility/sale/IERC1155Sale.sol"; +import {ERC1155Supply} from "@0xsequence/contracts-library/tokens/ERC1155/extensions/supply/ERC1155Supply.sol"; +import { + WithdrawControlled, + AccessControlEnumerable, + SafeERC20, + IERC20 +} from "@0xsequence/contracts-library/tokens/common/WithdrawControlled.sol"; +import {MerkleProofSingleUse} from "@0xsequence/contracts-library/tokens/common/MerkleProofSingleUse.sol"; + +import {IERC1155} from "@0xsequence/erc-1155/contracts/interfaces/IERC1155.sol"; +import {IERC1155SupplyFunctions} from + "@0xsequence/contracts-library/tokens/ERC1155/extensions/supply/IERC1155Supply.sol"; +import {IERC1155ItemsFunctions} from "@0xsequence/contracts-library/tokens/ERC1155/presets/items/IERC1155Items.sol"; + +contract ERC1155Sale is IERC1155Sale, WithdrawControlled, MerkleProofSingleUse { + bytes32 internal constant MINT_ADMIN_ROLE = keccak256("MINT_ADMIN_ROLE"); + + bool private _initialized; + address private _items; + + // ERC20 token address for payment. address(0) indicated payment in ETH. + address private _paymentToken; + + SaleDetails private _globalSaleDetails; + mapping(uint256 => SaleDetails) private _tokenSaleDetails; + + /** + * Initialize the contract. + * @param owner Owner address + * @param items The ERC-1155 Items contract address + * @dev This should be called immediately after deployment. + */ + function initialize(address owner, address items) public virtual { + if (_initialized) { + revert InvalidInitialization(); + } + + _items = items; + + _grantRole(DEFAULT_ADMIN_ROLE, owner); + _grantRole(MINT_ADMIN_ROLE, owner); + _grantRole(WITHDRAW_ROLE, owner); + + _initialized = true; + } + + /** + * Checks if the current block.timestamp is out of the give timestamp range. + * @param _startTime Earliest acceptable timestamp (inclusive). + * @param _endTime Latest acceptable timestamp (exclusive). + * @dev A zero endTime value is always considered out of bounds. + */ + function _blockTimeOutOfBounds(uint256 _startTime, uint256 _endTime) private view returns (bool) { + // 0 end time indicates inactive sale. + return _endTime == 0 || block.timestamp < _startTime || block.timestamp >= _endTime; // solhint-disable-line not-rely-on-time + } + + /** + * Checks the sale is active and takes payment. + * @param _tokenIds Token IDs to mint. + * @param _amounts Amounts of tokens to mint. + * @param _expectedPaymentToken ERC20 token address to accept payment in. address(0) indicates ETH. + * @param _maxTotal Maximum amount of payment tokens. + * @param _proof Merkle proof for allowlist minting. + */ + function _payForActiveMint( + uint256[] memory _tokenIds, + uint256[] memory _amounts, + address _expectedPaymentToken, + uint256 _maxTotal, + bytes32[] calldata _proof + ) + private + { + uint256 lastTokenId; + uint256 totalCost; + uint256 totalAmount; + + SaleDetails memory gSaleDetails = _globalSaleDetails; + bool globalSaleInactive = _blockTimeOutOfBounds(gSaleDetails.startTime, gSaleDetails.endTime); + bool globalMerkleCheckRequired = false; + for (uint256 i; i < _tokenIds.length; i++) { + uint256 tokenId = _tokenIds[i]; + // Test tokenIds ordering + if (i != 0 && lastTokenId >= tokenId) { + revert InvalidTokenIds(); + } + lastTokenId = tokenId; + + uint256 amount = _amounts[i]; + + // Active sale test + SaleDetails memory saleDetails = _tokenSaleDetails[tokenId]; + bool tokenSaleInactive = _blockTimeOutOfBounds(saleDetails.startTime, saleDetails.endTime); + if (tokenSaleInactive) { + // Prefer token sale + if (globalSaleInactive) { + // Both sales inactive + revert SaleInactive(tokenId); + } + // Use global sale details + globalMerkleCheckRequired = true; + totalCost += gSaleDetails.cost * amount; + } else { + // Use token sale details + requireMerkleProof(saleDetails.merkleRoot, _proof, msg.sender, bytes32(tokenId)); + totalCost += saleDetails.cost * amount; + } + totalAmount += amount; + } + + if (globalMerkleCheckRequired) { + // Check it once outside the loop only when required + requireMerkleProof(gSaleDetails.merkleRoot, _proof, msg.sender, bytes32(type(uint256).max)); + } + + if (_expectedPaymentToken != _paymentToken) { + // Caller expected different payment token + revert InsufficientPayment(_paymentToken, totalCost, 0); + } + if (_maxTotal < totalCost) { + // Caller expected to pay less + revert InsufficientPayment(_expectedPaymentToken, totalCost, _maxTotal); + } + if (_expectedPaymentToken == address(0)) { + // Paid in ETH + if (msg.value != totalCost) { + // We expect exact value match + revert InsufficientPayment(_expectedPaymentToken, totalCost, msg.value); + } + } else if (msg.value > 0) { + // Paid in ERC20, but sent ETH + revert InsufficientPayment(address(0), 0, msg.value); + } else { + // Paid in ERC20 + SafeERC20.safeTransferFrom(IERC20(_expectedPaymentToken), msg.sender, address(this), totalCost); + } + } + + // + // Minting + // + + /** + * Mint tokens. + * @param to Address to mint tokens to. + * @param tokenIds Token IDs to mint. + * @param amounts Amounts of tokens to mint. + * @param data Data to pass if receiver is contract. + * @param expectedPaymentToken ERC20 token address to accept payment in. address(0) indicates ETH. + * @param maxTotal Maximum amount of payment tokens. + * @param proof Merkle proof for allowlist minting. + * @notice Sale must be active for all tokens. + * @dev tokenIds must be sorted ascending without duplicates. + * @dev An empty proof is supplied when no proof is required. + */ + function mint( + address to, + uint256[] memory tokenIds, + uint256[] memory amounts, + bytes memory data, + address expectedPaymentToken, + uint256 maxTotal, + bytes32[] calldata proof + ) + public + payable + { + _payForActiveMint(tokenIds, amounts, expectedPaymentToken, maxTotal, proof); + + IERC1155SupplyFunctions items = IERC1155SupplyFunctions(_items); + uint256 totalAmount = 0; + uint256 nMint = tokenIds.length; + for (uint256 i = 0; i < nMint; i++) { + // Update storage balance + uint256 tokenSupplyCap = _tokenSaleDetails[tokenIds[i]].supplyCap; + if ( + tokenSupplyCap > 0 && items.tokenSupply(tokenIds[i]) + amounts[i] > tokenSupplyCap + ) { + revert InsufficientSupply(items.tokenSupply(tokenIds[i]), amounts[i], tokenSupplyCap); + } + totalAmount += amounts[i]; + } + uint256 totalSupplyCap = _globalSaleDetails.supplyCap; + if (totalSupplyCap > 0 && items.totalSupply() + totalAmount > totalSupplyCap) { + revert InsufficientSupply(items.totalSupply(), totalAmount, totalSupplyCap); + } + + IERC1155ItemsFunctions(_items).batchMint(to, tokenIds, amounts, data); + } + + // + // Admin + // + + /** + * Set the payment token. + * @param paymentTokenAddr The ERC20 token address to accept payment in. address(0) indicates ETH. + * @dev This should be set before the sale starts. + */ + function setPaymentToken(address paymentTokenAddr) public onlyRole(MINT_ADMIN_ROLE) { + _paymentToken = paymentTokenAddr; + } + + /** + * Set the global sale details. + * @param cost The amount of payment tokens to accept for each token minted. + * @param supplyCap The maximum number of tokens that can be minted. + * @param startTime The start time of the sale. Tokens cannot be minted before this time. + * @param endTime The end time of the sale. Tokens cannot be minted after this time. + * @param merkleRoot The merkle root for allowlist minting. + * @dev A zero end time indicates an inactive sale. + * @notice The payment token is set globally. + */ + function setGlobalSaleDetails( + uint256 cost, + uint256 supplyCap, + uint64 startTime, + uint64 endTime, + bytes32 merkleRoot + ) + public + onlyRole(MINT_ADMIN_ROLE) + { + // solhint-disable-next-line not-rely-on-time + if (endTime < startTime || endTime <= block.timestamp) { + revert InvalidSaleDetails(); + } + _globalSaleDetails = SaleDetails(cost, supplyCap, startTime, endTime, merkleRoot); + emit GlobalSaleDetailsUpdated(cost, supplyCap, startTime, endTime, merkleRoot); + } + + /** + * Set the sale details for an individual token. + * @param tokenId The token ID to set the sale details for. + * @param cost The amount of payment tokens to accept for each token minted. + * @param supplyCap The maximum number of tokens that can be minted. + * @param startTime The start time of the sale. Tokens cannot be minted before this time. + * @param endTime The end time of the sale. Tokens cannot be minted after this time. + * @param merkleRoot The merkle root for allowlist minting. + * @dev A zero end time indicates an inactive sale. + * @notice The payment token is set globally. + */ + function setTokenSaleDetails( + uint256 tokenId, + uint256 cost, + uint256 supplyCap, + uint64 startTime, + uint64 endTime, + bytes32 merkleRoot + ) + public + onlyRole(MINT_ADMIN_ROLE) + { + // solhint-disable-next-line not-rely-on-time + if (endTime < startTime || endTime <= block.timestamp) { + revert InvalidSaleDetails(); + } + _tokenSaleDetails[tokenId] = SaleDetails(cost, supplyCap, startTime, endTime, merkleRoot); + emit TokenSaleDetailsUpdated(tokenId, cost, supplyCap, startTime, endTime, merkleRoot); + } + + // + // Views + // + + /** + * Get global sales details. + * @return Sale details. + * @notice Global sales details apply to all tokens. + * @notice Global sales details are overriden when token sale is active. + */ + function globalSaleDetails() external view returns (SaleDetails memory) { + return _globalSaleDetails; + } + + /** + * Get token sale details. + * @param tokenId Token ID to get sale details for. + * @return Sale details. + * @notice Token sale details override global sale details. + */ + function tokenSaleDetails(uint256 tokenId) external view returns (SaleDetails memory) { + return _tokenSaleDetails[tokenId]; + } + + /** + * Get payment token. + * @return Payment token address. + * @notice address(0) indicates payment in ETH. + */ + function paymentToken() external view returns (address) { + return _paymentToken; + } + + /** + * Check interface support. + * @param interfaceId Interface id + * @return True if supported + */ + function supportsInterface(bytes4 interfaceId) + public + view + virtual + override (AccessControlEnumerable) + returns (bool) + { + return type(IERC1155SaleFunctions).interfaceId == interfaceId || super.supportsInterface(interfaceId); + } +} \ No newline at end of file diff --git a/testchain/contracts/IERC1155Sale.sol b/testchain/contracts/IERC1155Sale.sol new file mode 100644 index 000000000..eff2cc71a --- /dev/null +++ b/testchain/contracts/IERC1155Sale.sol @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.19; + +interface IERC1155SaleFunctions { + + struct SaleDetails { + uint256 cost; + uint256 supplyCap; // 0 supply cap indicates unlimited supply + uint64 startTime; + uint64 endTime; // 0 end time indicates sale inactive + bytes32 merkleRoot; // Root of allowed addresses + } + + /** + * Get global sales details. + * @return Sale details. + * @notice Global sales details apply to all tokens. + * @notice Global sales details are overriden when token sale is active. + */ + function globalSaleDetails() external view returns (SaleDetails memory); + + /** + * Get token sale details. + * @param tokenId Token ID to get sale details for. + * @return Sale details. + * @notice Token sale details override global sale details. + */ + function tokenSaleDetails(uint256 tokenId) external view returns (SaleDetails memory); + + /** + * Get payment token. + * @return Payment token address. + * @notice address(0) indicates payment in ETH. + */ + function paymentToken() external view returns (address); + + /** + * Mint tokens. + * @param to Address to mint tokens to. + * @param tokenIds Token IDs to mint. + * @param amounts Amounts of tokens to mint. + * @param data Data to pass if receiver is contract. + * @param paymentToken ERC20 token address to accept payment in. address(0) indicates ETH. + * @param maxTotal Maximum amount of payment tokens. + * @param proof Merkle proof for allowlist minting. + * @notice Sale must be active for all tokens. + * @dev tokenIds must be sorted ascending without duplicates. + * @dev An empty proof is supplied when no proof is required. + */ + function mint( + address to, + uint256[] memory tokenIds, + uint256[] memory amounts, + bytes memory data, + address paymentToken, + uint256 maxTotal, + bytes32[] calldata proof + ) + external + payable; +} + +interface IERC1155SaleSignals { + + event GlobalSaleDetailsUpdated(uint256 cost, uint256 supplyCap, uint64 startTime, uint64 endTime, bytes32 merkleRoot); + event TokenSaleDetailsUpdated(uint256 tokenId, uint256 cost, uint256 supplyCap, uint64 startTime, uint64 endTime, bytes32 merkleRoot); + + /** + * Contract already initialized. + */ + error InvalidInitialization(); + + /** + * Sale details supplied are invalid. + */ + error InvalidSaleDetails(); + + /** + * Sale is not active globally. + */ + error GlobalSaleInactive(); + + /** + * Sale is not active. + * @param tokenId Invalid Token ID. + */ + error SaleInactive(uint256 tokenId); + + /** + * Insufficient tokens for payment. + * @param currency Currency address. address(0) indicates ETH. + * @param expected Expected amount of tokens. + * @param actual Actual amount of tokens. + */ + error InsufficientPayment(address currency, uint256 expected, uint256 actual); + + /** + * Invalid token IDs. + */ + error InvalidTokenIds(); + + /** + * Insufficient supply of tokens. + */ + error InsufficientSupply(uint256 currentSupply, uint256 requestedAmount, uint256 maxSupply); +} + +interface IERC1155Sale is IERC1155SaleFunctions, IERC1155SaleSignals {} \ No newline at end of file