From dcfd12f65ee671f0d8873da384199a3b6ff4ca15 Mon Sep 17 00:00:00 2001 From: Julian Wilson Date: Fri, 28 Apr 2023 15:27:43 -0400 Subject: [PATCH 1/2] WIP --- SigningExampleNethereum.csproj | 23 +++- node_signatures/src/cancelAllSignature.ts | 43 +++++++ Order.cs => src/Order.cs | 4 +- Program.cs => src/Program.cs | 133 ++++++++++++--------- SportXAPI.cs => src/SXBetApi.cs | 29 ++++- src/SignatureGeneration/CancelAllOrders.cs | 68 +++++++++++ src/SignatureGeneration/CancelOrdersV1.cs | 77 ++++++++++++ src/SignatureGeneration/SaltDomain.cs | 12 ++ src/Utils.cs | 16 +++ test/EIP712Test.cs | 103 ++++++++++++++++ 10 files changed, 442 insertions(+), 66 deletions(-) create mode 100644 node_signatures/src/cancelAllSignature.ts rename Order.cs => src/Order.cs (97%) rename Program.cs => src/Program.cs (54%) rename SportXAPI.cs => src/SXBetApi.cs (65%) create mode 100644 src/SignatureGeneration/CancelAllOrders.cs create mode 100644 src/SignatureGeneration/CancelOrdersV1.cs create mode 100644 src/SignatureGeneration/SaltDomain.cs create mode 100644 src/Utils.cs create mode 100644 test/EIP712Test.cs diff --git a/SigningExampleNethereum.csproj b/SigningExampleNethereum.csproj index cc5d898..99b6ee1 100644 --- a/SigningExampleNethereum.csproj +++ b/SigningExampleNethereum.csproj @@ -5,12 +5,27 @@ net6.0 enable enable + false - - - - + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + diff --git a/node_signatures/src/cancelAllSignature.ts b/node_signatures/src/cancelAllSignature.ts new file mode 100644 index 0000000..eab2016 --- /dev/null +++ b/node_signatures/src/cancelAllSignature.ts @@ -0,0 +1,43 @@ +import ethSigUtil from "eth-sig-util"; + +function getCancelAllOrdersEIP712Payload( + salt: string, + timestamp: number, + chainId: number +) { + const payload = { + types: { + EIP712Domain: [ + { name: "name", type: "string" }, + { name: "version", type: "string" }, + { name: "chainId", type: "uint256" }, + { name: "salt", type: "bytes32" }, + ], + Details: [{ name: "timestamp", type: "uint256" }], + }, + primaryType: "Details", + domain: { + name: "CancelAllOrdersSportX", + version: "1.0", + chainId, + salt, + }, + message: { timestamp }, + }; + return payload; +} + +export = function getCancelAllOrdersSignature( + callback: any, + salt: string, + timestamp: number, + privateKey: string, + chainId: number +) { + const payload = getCancelAllOrdersEIP712Payload(salt, timestamp, chainId); + const bufferPrivateKey = Buffer.from(privateKey.substring(2), "hex"); + const signature = (ethSigUtil as any).signTypedData_v4(bufferPrivateKey, { + data: payload, + }); + callback(null, signature); +}; diff --git a/Order.cs b/src/Order.cs similarity index 97% rename from Order.cs rename to src/Order.cs index 1774996..0a5e6e4 100644 --- a/Order.cs +++ b/src/Order.cs @@ -6,7 +6,7 @@ using Jering.Javascript.NodeJS; using Newtonsoft.Json; -namespace NethereumSample +namespace SigningExampleNethereum { public static class GlobalVar { @@ -101,7 +101,7 @@ int chainId // Invoke javascript string? result = await StaticNodeJSService.InvokeFromFileAsync( GlobalVar.JS_FILE_LOCATION, - args: new object[] { orderHashes, privateKey, 80001 } + args: new object[] { orderHashes, privateKey, chainId } ); return result; } diff --git a/Program.cs b/src/Program.cs similarity index 54% rename from Program.cs rename to src/Program.cs index 94bf051..252fde3 100644 --- a/Program.cs +++ b/src/Program.cs @@ -1,58 +1,75 @@ -using Nethereum.Hex.HexConvertors.Extensions; -using System.Numerics; -using Nethereum.Signer; -using Newtonsoft.Json; - -namespace NethereumSample -{ - class Program - { - static async Task Main(string[] args) - { - var privateKey = Environment.GetEnvironmentVariable("PRIVATE_KEY"); - if (privateKey == null) - { - throw new Exception("PRIVATE_KEY is not defined"); - } - - var order = new Order - { - marketHash = "0x78d2b90f2b585ead37b34005997f551e3f7de26b194b09322f0df6e5246b6f86", - baseToken = "0x6A383cf1F8897585718DCA629a8f1471339abFe4", - totalBetSize = BigInteger.Parse("1000000000000000000000"), - percentageOdds = BigInteger.Parse("51302654005356720000"), - maker = new EthECKey(privateKey).GetPublicAddress(), - executor = "0x3259E7Ccc0993368fCB82689F5C534669A0C06ca", - isMakerBettingOutcomeOne = true, - apiExpiry = DateTimeOffset.Now.AddSeconds(1000).ToUnixTimeSeconds(), - salt = Order.Random32Bytes() - }; - - Console.WriteLine("Order hash: " + order.GetHash().ToHex(true)); - Console.WriteLine("Order signature: " + order.GetSignature(privateKey)); - - var api = new SportXAPI { url = "https://mumbai.api.sportx.bet" }; - var newOrderResult = await api.PostNewOrder(order.GetSignedOrder(privateKey)); - Console.WriteLine("New order result: " + newOrderResult); - - var deserialized = JsonConvert.DeserializeAnonymousType( - newOrderResult, - new { status = default(string), data = new { orders = default(string[]) } } - ); - Console.WriteLine("Deserialized new order result: " + deserialized); - var cancelSignature = await Order.GetCancelSignature( - deserialized.data.orders, - privateKey, - 80001 // use 137 if mainnet. 80001 = mumbai testnet, 137 = polygon mainnet - ); - Console.WriteLine("Cancel signature: " + cancelSignature); - - var cancelOrderResult = await api.CancelOrder( - deserialized.data.orders, - "Are you sure you want to cancel these orders", - cancelSignature - ); - Console.WriteLine("Cancel API call result: " + cancelOrderResult); - } - } -} +using Nethereum.Hex.HexConvertors.Extensions; +using System.Numerics; +using Nethereum.Signer; +using Newtonsoft.Json; + +namespace SigningExampleNethereum +{ + class Program + { + static async Task Main(string[] args) + { + Console.WriteLine("Hello"); + var privateKey = Environment.GetEnvironmentVariable("PRIVATE_KEY"); + if (privateKey == null) + { + throw new Exception("PRIVATE_KEY is not defined"); + } + var chainIdRaw = Environment.GetEnvironmentVariable("CHAIN_ID"); + if (chainIdRaw == null) { + throw new Exception("CHAIN_ID is not defined"); + } + var chainId = Int32.Parse(chainIdRaw); + + EthECKey wallet = new EthECKey(privateKey); + Console.WriteLine("Wallet address: " + wallet.GetPublicAddress()); + + + var order = new Order + { + marketHash = "0x48ee699e68f564a1bfc2ebff1096a6c966033d197a4ea0f09e847efb6d0df97b", + baseToken = "0x5147891461a7C81075950f8eE6384e019e39ab98", + totalBetSize = BigInteger.Parse("100000000"), + percentageOdds = BigInteger.Parse("50000000000000000000"), + maker = wallet.GetPublicAddress(), + executor = "0x3259E7Ccc0993368fCB82689F5C534669A0C06ca", + isMakerBettingOutcomeOne = true, + apiExpiry = DateTimeOffset.Now.AddSeconds(1000).ToUnixTimeSeconds(), + salt = Order.Random32Bytes() + }; + + Console.WriteLine("Order hash: " + order.GetHash().ToHex(true)); + Console.WriteLine("Order signature: " + order.GetSignature(privateKey)); + + var api = new SXBetApi { url = "https://api.toronto.sx.bet" }; + var newOrderResult = await api.PostNewOrder(order.GetSignedOrder(privateKey)); + Console.WriteLine("New order result: " + newOrderResult); + + var deserialized = JsonConvert.DeserializeAnonymousType( + newOrderResult, + new { status = default(string), data = new { orders = default(string[]) } } + ); + Console.WriteLine("Deserialized new order result: " + deserialized); + + var ordersList = new List(deserialized.data.orders); + + ordersList.ForEach(Console.WriteLine); + + var cancelSignature = CancelOrdersV1.GetCancelOrdersEIP712Payload(ordersList, chainId, wallet); + + // var cancelSignature = await Order.GetCancelSignature( + // deserialized.data.orders, + // privateKey, + // chainId // use 137 if mainnet. 80001 = mumbai testnet, 137 = polygon mainnet + // ); + Console.WriteLine("Cancel signature: " + cancelSignature); + + var cancelOrderResult = await api.CancelOrder( + deserialized.data.orders, + CancelOrdersV1.PROMPT, + cancelSignature + ); + Console.WriteLine("Cancel API call result: " + cancelOrderResult); + } + } +} diff --git a/SportXAPI.cs b/src/SXBetApi.cs similarity index 65% rename from SportXAPI.cs rename to src/SXBetApi.cs index 6e78eb8..8c2f4d0 100644 --- a/SportXAPI.cs +++ b/src/SXBetApi.cs @@ -1,9 +1,10 @@ using Newtonsoft.Json; using System.Text; +using System.Numerics; -namespace NethereumSample +namespace SigningExampleNethereum { - class SportXAPI + class SXBetApi { static readonly HttpClient client = new HttpClient(); @@ -29,6 +30,30 @@ public async Task PostNewOrder(SignedOrder order) } } + public async Task CancelAllOrders( + string signature, + string salt, + string maker, + BigInteger timestamp + ) + { + var obj = new { signature, salt, maker, timestamp }; + var serialized = JsonConvert.SerializeObject(obj); + var httpContent = new StringContent(serialized, Encoding.UTF8, "application/json"); + HttpResponseMessage response = await client.PostAsync( + url + "/orders/cancel/all", + httpContent + ); + if (response.Content != null) + { + return await response.Content.ReadAsStringAsync(); + } + else + { + throw new Exception("Content is null"); + } + } + public async Task CancelOrder( string[] orderHashes, string message, diff --git a/src/SignatureGeneration/CancelAllOrders.cs b/src/SignatureGeneration/CancelAllOrders.cs new file mode 100644 index 0000000..4be47b2 --- /dev/null +++ b/src/SignatureGeneration/CancelAllOrders.cs @@ -0,0 +1,68 @@ +using Nethereum.Signer.EIP712; +using Nethereum.ABI.FunctionEncoding.Attributes; +using Nethereum.Signer; +using Nethereum.ABI.EIP712; +using System.Numerics; +using System.Security.Cryptography; + +namespace SigningExampleNethereum +{ + public static class CancelAllOrders + { + + private static readonly Eip712TypedDataSigner _signer = new Eip712TypedDataSigner(); + + //Message types for easier input + private class Details + { + [Parameter("uint256", "timestamp", 1)] + public BigInteger Timestamp { get; set; } + } + + //The generic Typed schema defintion for this message + private static TypedData getTypedDefinition(BigInteger salt, BigInteger chainId) + { + return new TypedData + { + Domain = new SaltDomain + { + Name = "CancelAllOrdersSportX", + Version = "1.0", + ChainId = chainId, + Salt = salt + }, + Types = new Dictionary + { + ["EIP712Domain"] = new[] + { + new MemberDescription {Name = "name", Type = "string"}, + new MemberDescription {Name = "version", Type = "string"}, + new MemberDescription {Name = "chainId", Type = "uint256"}, + new MemberDescription {Name = "salt", Type = "bytes32"}, + }, + ["Details"] = new[] + { + new MemberDescription {Name = "timestamp", Type = "uint256"}, + }, + }, + PrimaryType = "Details", + }; + } + + private static string getCancelAllOrdersEIP712Payload(BigInteger chainId, BigInteger timestamp, EthECKey key) + { + var typedData = getTypedDefinition(Utilities.Random32Bytes(), chainId); + + var mail = new Details + { + Timestamp = timestamp + }; + + var signature = _signer.SignTypedDataV4(mail, typedData, key); + var addressRecovered = _signer.RecoverFromSignatureV4(mail, typedData, signature); + var address = key.GetPublicAddress(); + return signature; + } + + } +} diff --git a/src/SignatureGeneration/CancelOrdersV1.cs b/src/SignatureGeneration/CancelOrdersV1.cs new file mode 100644 index 0000000..d0a2968 --- /dev/null +++ b/src/SignatureGeneration/CancelOrdersV1.cs @@ -0,0 +1,77 @@ +using Nethereum.Signer.EIP712; +using Nethereum.ABI.FunctionEncoding.Attributes; +using Nethereum.Signer; +using Nethereum.ABI.EIP712; +using System.Numerics; +using System.Security.Cryptography; + +namespace SigningExampleNethereum +{ + public static class CancelOrdersV1 + { + private static readonly Eip712TypedDataSigner _signer = new Eip712TypedDataSigner(); + + public static readonly string PROMPT = "Are you sure you want to cancel these orders"; + + //Message types for easier input + private class Details + { + [Parameter("string", "message", 1)] + public string Message { get; set; } + + [Parameter("string[]", "orders", 2)] + public List Orders { get; set; } + } + + //The generic Typed schema defintion for this message + private static TypedData GetTypedDefinition(BigInteger chainId) + { + return new TypedData + { + Domain = new Domain + { + Name = "CancelOrderSportX", + Version = "1.0", + ChainId = chainId, + }, + Types = new Dictionary + { + ["EIP712Domain"] = new[] + { + new MemberDescription {Name = "name", Type = "string"}, + new MemberDescription {Name = "version", Type = "string"}, + new MemberDescription {Name = "chainId", Type = "uint256"}, + }, + ["Details"] = new[] + { + new MemberDescription {Name = "message", Type = "string"}, + new MemberDescription {Name = "orders", Type = "string[]"}, + }, + }, + PrimaryType = "Details", + }; + } + + public static string GetCancelOrdersEIP712Payload(List orders, BigInteger chainId, EthECKey key) + { + var typedData = GetTypedDefinition(chainId); + + Console.WriteLine(typedData); + Console.WriteLine(PROMPT); + + var mail = new Details + { + Message = PROMPT, + Orders = orders + }; + + Console.WriteLine(mail.Orders.Count); + + var signature = _signer.SignTypedDataV4(mail, typedData, key); + var addressRecovered = _signer.RecoverFromSignatureV4(mail, typedData, signature); + var address = key.GetPublicAddress(); + return signature; + } + + } +} diff --git a/src/SignatureGeneration/SaltDomain.cs b/src/SignatureGeneration/SaltDomain.cs new file mode 100644 index 0000000..23e0f87 --- /dev/null +++ b/src/SignatureGeneration/SaltDomain.cs @@ -0,0 +1,12 @@ +using System.Numerics; +using Nethereum.ABI.FunctionEncoding.Attributes; + +namespace Nethereum.ABI.EIP712 +{ + [Struct("EIP712Domain")] + public class SaltDomain : Domain + { + [Parameter("bytes32", "salt", 4)] + public virtual BigInteger Salt { get; set; } + } +} \ No newline at end of file diff --git a/src/Utils.cs b/src/Utils.cs new file mode 100644 index 0000000..c61c897 --- /dev/null +++ b/src/Utils.cs @@ -0,0 +1,16 @@ +using System.Numerics; +using System.Security.Cryptography; + +namespace SigningExampleNethereum { + + public static class Utilities { +public static BigInteger Random32Bytes() + { + byte[] number = new byte[32]; + RandomNumberGenerator rng = RandomNumberGenerator.Create(); + rng.GetBytes(number); + number[^1] &= (byte)0x7F; //force sign bit to positive + return new BigInteger(number); + } + } +} \ No newline at end of file diff --git a/test/EIP712Test.cs b/test/EIP712Test.cs new file mode 100644 index 0000000..dfda3eb --- /dev/null +++ b/test/EIP712Test.cs @@ -0,0 +1,103 @@ +using System.Collections.Generic; +using Nethereum.Signer.EIP712; +using Nethereum.Util; +using Nethereum.ABI.FunctionEncoding.Attributes; +using Nethereum.ABI.EIP712; +using Nethereum.Signer; +using Xunit; + +namespace SigningExampleNethereum +{ + public class Test + { + private readonly Eip712TypedDataSigner _signer = new Eip712TypedDataSigner(); + + //Message types for easier input + public class Mail + { + [Parameter("tuple", "from", 1, "Person")] + public Person From { get; set; } + + [Parameter("tuple[]", "to", 2, "Person[]")] + public List To { get; set; } + + [Parameter("string", "contents", 2)] + public string Contents { get; set; } + } + + public class Person + { + [Parameter("string", "name", 1)] + public string Name { get; set; } + + [Parameter("address[]", "wallets", 2)] + public List Wallets { get; set; } + } + + //The generic Typed schema defintion for this message + public TypedData GetMailTypedDefintion() + { + return new TypedData + { + Domain = new Domain + { + Name = "Ether Mail", + Version = "1", + ChainId = 1, + VerifyingContract = "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + Types = new Dictionary + { + ["EIP712Domain"] = new[] + { + new MemberDescription {Name = "name", Type = "string"}, + new MemberDescription {Name = "version", Type = "string"}, + new MemberDescription {Name = "chainId", Type = "uint256"}, + new MemberDescription {Name = "verifyingContract", Type = "address"}, + }, + ["Group"] = new[] + { + new MemberDescription {Name = "name", Type = "string"}, + new MemberDescription {Name = "members", Type = "Person[]"}, + + }, + ["Mail"] = new[] + { + new MemberDescription {Name = "from", Type = "Person"}, + new MemberDescription {Name = "to", Type = "Person[]"}, + new MemberDescription {Name = "contents", Type = "string"}, + }, + ["Person"] = new[] + { + new MemberDescription {Name = "name", Type = "string"}, + new MemberDescription {Name = "wallets", Type = "address[]"}, + }, + }, + PrimaryType = "Mail", + }; + } + + [Fact] + private void ComplexMessageTypedDataEncodingShouldBeCorrectForV4IncludingArraysAndTypes() + { + var typedData = GetMailTypedDefintion(); + + var mail = new Mail + { + From = new Person { Name = "Cow", Wallets = new List { "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", "0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF" } }, + To = new List(), + Contents = "Hello, Bob!" + }; + mail.To.Add(new Person { Name = "Bob", Wallets = new List { "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB", "0xB0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57", "0xB0B0b0b0b0b0B000000000000000000000000000" } }); + + var key = new EthECKey("94e001d6adf3a3275d5dd45971c2a5f6637d3e9c51f9693f2e678f649e164fa5"); + var signature = _signer.SignTypedDataV4(mail, typedData, key); + Assert.Equal("0x943393c998ab7e067d2875385e2218c9b3140f563694267ac9f6276a9fcc53e15c1526abe460cd6e2f570a35418f132d9733363400c44791ff7b88f0e9c91d091b", signature); + var addressRecovered = _signer.RecoverFromSignatureV4(mail, typedData, signature); + var address = key.GetPublicAddress(); + Assert.True(address.IsTheSameAddress(addressRecovered)); + + } + + } +} From 19fc65c9cd287a016f0e1f3f5143f88cff60116f Mon Sep 17 00:00:00 2001 From: Julian Wilson Date: Mon, 1 May 2023 13:46:34 -0400 Subject: [PATCH 2/2] Repro example --- README.md | 2 +- src/Order.cs | 19 +++- src/Program.cs | 80 ++++++++-------- src/SignatureGeneration/CancelAllOrders.cs | 32 ++----- src/SignatureGeneration/CancelOrdersV1.cs | 34 ++----- src/SignatureGeneration/SaltDomain.cs | 16 +++- src/Utils.cs | 5 +- test/EIP712Test.cs | 103 --------------------- 8 files changed, 89 insertions(+), 202 deletions(-) delete mode 100644 test/EIP712Test.cs diff --git a/README.md b/README.md index e4eefaf..b35e0fb 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# SportX.bet C# examples +# SX.bet C# examples This repo contains some examples for signing various order operations on sportx.bet using C#. Currently has examples for the following: diff --git a/src/Order.cs b/src/Order.cs index 0a5e6e4..9bae35e 100644 --- a/src/Order.cs +++ b/src/Order.cs @@ -10,7 +10,8 @@ namespace SigningExampleNethereum { public static class GlobalVar { - public const string JS_FILE_LOCATION = "./node_signatures/dist/main.js"; + public const string CANCEL_ORDERS_V1_JS_FILE_LOCATION = "./node_signatures/dist/main.js"; + public const string CANCEL_ALL_ORDERS_FILE_LOCATION = "./node_signatures/dist/cancelAllSignature.js"; } class BigIntegerConverter : JsonConverter @@ -100,12 +101,26 @@ int chainId { // Invoke javascript string? result = await StaticNodeJSService.InvokeFromFileAsync( - GlobalVar.JS_FILE_LOCATION, + GlobalVar.CANCEL_ORDERS_V1_JS_FILE_LOCATION, args: new object[] { orderHashes, privateKey, chainId } ); return result; } + public static async Task GetCancelAllSignature( + string salt, + string timestamp, + string privateKey, + int chainId + ) { + // Invoke javascript + string? result = await StaticNodeJSService.InvokeFromFileAsync( + GlobalVar.CANCEL_ALL_ORDERS_FILE_LOCATION, + args: new object[] { salt, timestamp, privateKey, chainId } + ); + return result; + } + public byte[] GetHash() { var abiEncode = new ABIEncode(); diff --git a/src/Program.cs b/src/Program.cs index 252fde3..c863009 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -9,11 +9,10 @@ class Program { static async Task Main(string[] args) { - Console.WriteLine("Hello"); - var privateKey = Environment.GetEnvironmentVariable("PRIVATE_KEY"); + var privateKey = Environment.GetEnvironmentVariable("PRIVATE_KEY_NO_PREFIX"); if (privateKey == null) { - throw new Exception("PRIVATE_KEY is not defined"); + throw new Exception("PRIVATE_KEY_NO_PREFIX is not defined"); } var chainIdRaw = Environment.GetEnvironmentVariable("CHAIN_ID"); if (chainIdRaw == null) { @@ -25,51 +24,56 @@ static async Task Main(string[] args) Console.WriteLine("Wallet address: " + wallet.GetPublicAddress()); - var order = new Order - { - marketHash = "0x48ee699e68f564a1bfc2ebff1096a6c966033d197a4ea0f09e847efb6d0df97b", - baseToken = "0x5147891461a7C81075950f8eE6384e019e39ab98", - totalBetSize = BigInteger.Parse("100000000"), - percentageOdds = BigInteger.Parse("50000000000000000000"), - maker = wallet.GetPublicAddress(), - executor = "0x3259E7Ccc0993368fCB82689F5C534669A0C06ca", - isMakerBettingOutcomeOne = true, - apiExpiry = DateTimeOffset.Now.AddSeconds(1000).ToUnixTimeSeconds(), - salt = Order.Random32Bytes() - }; + // var order = new Order + // { + // marketHash = "0x88c43af903e373ec2b2717813f6c42188aeb2d818a56eda46b6d63a08ad1e406", + // baseToken = "0x5147891461a7C81075950f8eE6384e019e39ab98", + // totalBetSize = BigInteger.Parse("100000000"), + // percentageOdds = BigInteger.Parse("50000000000000000000"), + // maker = wallet.GetPublicAddress(), + // executor = "0x3259E7Ccc0993368fCB82689F5C534669A0C06ca", + // isMakerBettingOutcomeOne = true, + // apiExpiry = DateTimeOffset.Now.AddSeconds(1000).ToUnixTimeSeconds(), + // salt = Order.Random32Bytes() + // }; - Console.WriteLine("Order hash: " + order.GetHash().ToHex(true)); - Console.WriteLine("Order signature: " + order.GetSignature(privateKey)); + // Console.WriteLine("Order hash: " + order.GetHash().ToHex(true)); + // Console.WriteLine("Order signature: " + order.GetSignature(privateKey)); - var api = new SXBetApi { url = "https://api.toronto.sx.bet" }; - var newOrderResult = await api.PostNewOrder(order.GetSignedOrder(privateKey)); - Console.WriteLine("New order result: " + newOrderResult); + // var api = new SXBetApi { url = "http://localhost:8080" }; + // var newOrderResult = await api.PostNewOrder(order.GetSignedOrder(privateKey)); + // Console.WriteLine("New order result: " + newOrderResult); - var deserialized = JsonConvert.DeserializeAnonymousType( - newOrderResult, - new { status = default(string), data = new { orders = default(string[]) } } - ); - Console.WriteLine("Deserialized new order result: " + deserialized); + // var deserialized = JsonConvert.DeserializeAnonymousType( + // newOrderResult, + // new { status = default(string), data = new { orders = default(string[]) } } + // ); + // Console.WriteLine("Deserialized new order result: " + deserialized); - var ordersList = new List(deserialized.data.orders); + // var ordersList = new List(deserialized.data.orders); - ordersList.ForEach(Console.WriteLine); + // var cancelSignature = CancelOrdersV1.GetCancelOrdersEIP712Payload(ordersList, chainId, wallet); - var cancelSignature = CancelOrdersV1.GetCancelOrdersEIP712Payload(ordersList, chainId, wallet); + // Console.WriteLine("Cancel signature: " + cancelSignature); - // var cancelSignature = await Order.GetCancelSignature( + // var cancelOrderResult = await api.CancelOrder( // deserialized.data.orders, - // privateKey, - // chainId // use 137 if mainnet. 80001 = mumbai testnet, 137 = polygon mainnet + // CancelOrdersV1.PROMPT, + // cancelSignature // ); - Console.WriteLine("Cancel signature: " + cancelSignature); + // Console.WriteLine("Cancel API call result: " + cancelOrderResult); + + var salt = Utilities.Random32Bytes(); + + var timestamp = DateTimeOffset.Now.ToUnixTimeSeconds(); + + var cancelAllOrdersSignature = CancelAllOrders.GetCancelAllOrdersEIP712Payload(chainId, timestamp, wallet, salt); + var jsCancelAllOrdersSignature = await Order.GetCancelAllSignature(salt.ToHex(), timestamp.ToString(), wallet.GetPrivateKey(), chainId); + Console.WriteLine("Cancel all orders signature (c#): " + cancelAllOrdersSignature); + Console.WriteLine("Cancel all orders signature (js): " + jsCancelAllOrdersSignature); - var cancelOrderResult = await api.CancelOrder( - deserialized.data.orders, - CancelOrdersV1.PROMPT, - cancelSignature - ); - Console.WriteLine("Cancel API call result: " + cancelOrderResult); + // var cancelAllOrdersResult = await api.CancelAllOrders(cancelAllOrdersSignature, salt.ToString(), wallet.GetPublicAddress(), timestamp); + // Console.WriteLine("Cancel all API call result: " + cancelAllOrdersResult); } } } diff --git a/src/SignatureGeneration/CancelAllOrders.cs b/src/SignatureGeneration/CancelAllOrders.cs index 4be47b2..a9470ce 100644 --- a/src/SignatureGeneration/CancelAllOrders.cs +++ b/src/SignatureGeneration/CancelAllOrders.cs @@ -3,7 +3,6 @@ using Nethereum.Signer; using Nethereum.ABI.EIP712; using System.Numerics; -using System.Security.Cryptography; namespace SigningExampleNethereum { @@ -20,7 +19,7 @@ private class Details } //The generic Typed schema defintion for this message - private static TypedData getTypedDefinition(BigInteger salt, BigInteger chainId) + private static TypedData getTypedDefinition(byte[] salt, BigInteger chainId) { return new TypedData { @@ -31,38 +30,21 @@ private static TypedData getTypedDefinition(BigInteger salt, BigInte ChainId = chainId, Salt = salt }, - Types = new Dictionary - { - ["EIP712Domain"] = new[] - { - new MemberDescription {Name = "name", Type = "string"}, - new MemberDescription {Name = "version", Type = "string"}, - new MemberDescription {Name = "chainId", Type = "uint256"}, - new MemberDescription {Name = "salt", Type = "bytes32"}, - }, - ["Details"] = new[] - { - new MemberDescription {Name = "timestamp", Type = "uint256"}, - }, - }, - PrimaryType = "Details", + Types = MemberDescriptionFactory.GetTypesMemberDescription(typeof(SaltDomain), typeof(Details)), + PrimaryType = nameof(Details), }; } - private static string getCancelAllOrdersEIP712Payload(BigInteger chainId, BigInteger timestamp, EthECKey key) + public static string GetCancelAllOrdersEIP712Payload(BigInteger chainId, BigInteger timestamp, EthECKey key, byte[] salt) { - var typedData = getTypedDefinition(Utilities.Random32Bytes(), chainId); + var typedData = getTypedDefinition(salt, chainId); - var mail = new Details + var details = new Details { Timestamp = timestamp }; - var signature = _signer.SignTypedDataV4(mail, typedData, key); - var addressRecovered = _signer.RecoverFromSignatureV4(mail, typedData, signature); - var address = key.GetPublicAddress(); - return signature; + return _signer.SignTypedDataV4(details, typedData, key); } - } } diff --git a/src/SignatureGeneration/CancelOrdersV1.cs b/src/SignatureGeneration/CancelOrdersV1.cs index d0a2968..2bd3904 100644 --- a/src/SignatureGeneration/CancelOrdersV1.cs +++ b/src/SignatureGeneration/CancelOrdersV1.cs @@ -3,7 +3,6 @@ using Nethereum.Signer; using Nethereum.ABI.EIP712; using System.Numerics; -using System.Security.Cryptography; namespace SigningExampleNethereum { @@ -24,31 +23,18 @@ private class Details } //The generic Typed schema defintion for this message - private static TypedData GetTypedDefinition(BigInteger chainId) + private static TypedData GetTypedDefinition(BigInteger chainId) { - return new TypedData + return new TypedData { - Domain = new Domain + Domain = new DomainWithNameVersionAndChainId { Name = "CancelOrderSportX", Version = "1.0", ChainId = chainId, }, - Types = new Dictionary - { - ["EIP712Domain"] = new[] - { - new MemberDescription {Name = "name", Type = "string"}, - new MemberDescription {Name = "version", Type = "string"}, - new MemberDescription {Name = "chainId", Type = "uint256"}, - }, - ["Details"] = new[] - { - new MemberDescription {Name = "message", Type = "string"}, - new MemberDescription {Name = "orders", Type = "string[]"}, - }, - }, - PrimaryType = "Details", + Types = MemberDescriptionFactory.GetTypesMemberDescription(typeof(DomainWithNameVersionAndChainId), typeof(Details)), + PrimaryType = nameof(Details), }; } @@ -56,21 +42,13 @@ public static string GetCancelOrdersEIP712Payload(List orders, BigIntege { var typedData = GetTypedDefinition(chainId); - Console.WriteLine(typedData); - Console.WriteLine(PROMPT); - var mail = new Details { Message = PROMPT, Orders = orders }; - - Console.WriteLine(mail.Orders.Count); - var signature = _signer.SignTypedDataV4(mail, typedData, key); - var addressRecovered = _signer.RecoverFromSignatureV4(mail, typedData, signature); - var address = key.GetPublicAddress(); - return signature; + return _signer.SignTypedDataV4(mail, typedData, key); } } diff --git a/src/SignatureGeneration/SaltDomain.cs b/src/SignatureGeneration/SaltDomain.cs index 23e0f87..0de962a 100644 --- a/src/SignatureGeneration/SaltDomain.cs +++ b/src/SignatureGeneration/SaltDomain.cs @@ -1,12 +1,22 @@ -using System.Numerics; using Nethereum.ABI.FunctionEncoding.Attributes; +using System.Numerics; namespace Nethereum.ABI.EIP712 { [Struct("EIP712Domain")] - public class SaltDomain : Domain + public class SaltDomain : IDomain { + + [Parameter("string", "name", 1)] + public virtual string Name { get; set; } + + [Parameter("string", "version", 2)] + public virtual string Version { get; set; } + + [Parameter("uint256", "chainId", 3)] + public virtual BigInteger? ChainId { get; set; } + [Parameter("bytes32", "salt", 4)] - public virtual BigInteger Salt { get; set; } + public virtual byte[] Salt { get; set; } } } \ No newline at end of file diff --git a/src/Utils.cs b/src/Utils.cs index c61c897..286fce8 100644 --- a/src/Utils.cs +++ b/src/Utils.cs @@ -4,13 +4,14 @@ namespace SigningExampleNethereum { public static class Utilities { -public static BigInteger Random32Bytes() + + public static byte[] Random32Bytes() { byte[] number = new byte[32]; RandomNumberGenerator rng = RandomNumberGenerator.Create(); rng.GetBytes(number); number[^1] &= (byte)0x7F; //force sign bit to positive - return new BigInteger(number); + return number; } } } \ No newline at end of file diff --git a/test/EIP712Test.cs b/test/EIP712Test.cs deleted file mode 100644 index dfda3eb..0000000 --- a/test/EIP712Test.cs +++ /dev/null @@ -1,103 +0,0 @@ -using System.Collections.Generic; -using Nethereum.Signer.EIP712; -using Nethereum.Util; -using Nethereum.ABI.FunctionEncoding.Attributes; -using Nethereum.ABI.EIP712; -using Nethereum.Signer; -using Xunit; - -namespace SigningExampleNethereum -{ - public class Test - { - private readonly Eip712TypedDataSigner _signer = new Eip712TypedDataSigner(); - - //Message types for easier input - public class Mail - { - [Parameter("tuple", "from", 1, "Person")] - public Person From { get; set; } - - [Parameter("tuple[]", "to", 2, "Person[]")] - public List To { get; set; } - - [Parameter("string", "contents", 2)] - public string Contents { get; set; } - } - - public class Person - { - [Parameter("string", "name", 1)] - public string Name { get; set; } - - [Parameter("address[]", "wallets", 2)] - public List Wallets { get; set; } - } - - //The generic Typed schema defintion for this message - public TypedData GetMailTypedDefintion() - { - return new TypedData - { - Domain = new Domain - { - Name = "Ether Mail", - Version = "1", - ChainId = 1, - VerifyingContract = "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" - }, - Types = new Dictionary - { - ["EIP712Domain"] = new[] - { - new MemberDescription {Name = "name", Type = "string"}, - new MemberDescription {Name = "version", Type = "string"}, - new MemberDescription {Name = "chainId", Type = "uint256"}, - new MemberDescription {Name = "verifyingContract", Type = "address"}, - }, - ["Group"] = new[] - { - new MemberDescription {Name = "name", Type = "string"}, - new MemberDescription {Name = "members", Type = "Person[]"}, - - }, - ["Mail"] = new[] - { - new MemberDescription {Name = "from", Type = "Person"}, - new MemberDescription {Name = "to", Type = "Person[]"}, - new MemberDescription {Name = "contents", Type = "string"}, - }, - ["Person"] = new[] - { - new MemberDescription {Name = "name", Type = "string"}, - new MemberDescription {Name = "wallets", Type = "address[]"}, - }, - }, - PrimaryType = "Mail", - }; - } - - [Fact] - private void ComplexMessageTypedDataEncodingShouldBeCorrectForV4IncludingArraysAndTypes() - { - var typedData = GetMailTypedDefintion(); - - var mail = new Mail - { - From = new Person { Name = "Cow", Wallets = new List { "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", "0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF" } }, - To = new List(), - Contents = "Hello, Bob!" - }; - mail.To.Add(new Person { Name = "Bob", Wallets = new List { "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB", "0xB0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57", "0xB0B0b0b0b0b0B000000000000000000000000000" } }); - - var key = new EthECKey("94e001d6adf3a3275d5dd45971c2a5f6637d3e9c51f9693f2e678f649e164fa5"); - var signature = _signer.SignTypedDataV4(mail, typedData, key); - Assert.Equal("0x943393c998ab7e067d2875385e2218c9b3140f563694267ac9f6276a9fcc53e15c1526abe460cd6e2f570a35418f132d9733363400c44791ff7b88f0e9c91d091b", signature); - var addressRecovered = _signer.RecoverFromSignatureV4(mail, typedData, signature); - var address = key.GetPublicAddress(); - Assert.True(address.IsTheSameAddress(addressRecovered)); - - } - - } -}