diff --git a/Program.cs b/Program.cs
deleted file mode 100644
index 94bf051..0000000
--- a/Program.cs
+++ /dev/null
@@ -1,58 +0,0 @@
-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);
- }
- }
-}
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/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 81%
rename from Order.cs
rename to src/Order.cs
index 1774996..9bae35e 100644
--- a/Order.cs
+++ b/src/Order.cs
@@ -6,11 +6,12 @@
using Jering.Javascript.NodeJS;
using Newtonsoft.Json;
-namespace NethereumSample
+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,8 +101,22 @@ int chainId
{
// Invoke javascript
string? result = await StaticNodeJSService.InvokeFromFileAsync(
- GlobalVar.JS_FILE_LOCATION,
- args: new object[] { orderHashes, privateKey, 80001 }
+ 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;
}
diff --git a/src/Program.cs b/src/Program.cs
new file mode 100644
index 0000000..c863009
--- /dev/null
+++ b/src/Program.cs
@@ -0,0 +1,79 @@
+using Nethereum.Hex.HexConvertors.Extensions;
+using System.Numerics;
+using Nethereum.Signer;
+using Newtonsoft.Json;
+
+namespace SigningExampleNethereum
+{
+ class Program
+ {
+ static async Task Main(string[] args)
+ {
+ var privateKey = Environment.GetEnvironmentVariable("PRIVATE_KEY_NO_PREFIX");
+ if (privateKey == null)
+ {
+ throw new Exception("PRIVATE_KEY_NO_PREFIX 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 = "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));
+
+ // 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 ordersList = new List(deserialized.data.orders);
+
+ // var cancelSignature = CancelOrdersV1.GetCancelOrdersEIP712Payload(ordersList, chainId, wallet);
+
+ // Console.WriteLine("Cancel signature: " + cancelSignature);
+
+ // var cancelOrderResult = await api.CancelOrder(
+ // deserialized.data.orders,
+ // CancelOrdersV1.PROMPT,
+ // 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 cancelAllOrdersResult = await api.CancelAllOrders(cancelAllOrdersSignature, salt.ToString(), wallet.GetPublicAddress(), timestamp);
+ // Console.WriteLine("Cancel all API call result: " + cancelAllOrdersResult);
+ }
+ }
+}
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..a9470ce
--- /dev/null
+++ b/src/SignatureGeneration/CancelAllOrders.cs
@@ -0,0 +1,50 @@
+using Nethereum.Signer.EIP712;
+using Nethereum.ABI.FunctionEncoding.Attributes;
+using Nethereum.Signer;
+using Nethereum.ABI.EIP712;
+using System.Numerics;
+
+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(byte[] salt, BigInteger chainId)
+ {
+ return new TypedData
+ {
+ Domain = new SaltDomain
+ {
+ Name = "CancelAllOrdersSportX",
+ Version = "1.0",
+ ChainId = chainId,
+ Salt = salt
+ },
+ Types = MemberDescriptionFactory.GetTypesMemberDescription(typeof(SaltDomain), typeof(Details)),
+ PrimaryType = nameof(Details),
+ };
+ }
+
+ public static string GetCancelAllOrdersEIP712Payload(BigInteger chainId, BigInteger timestamp, EthECKey key, byte[] salt)
+ {
+ var typedData = getTypedDefinition(salt, chainId);
+
+ var details = new Details
+ {
+ Timestamp = timestamp
+ };
+
+ return _signer.SignTypedDataV4(details, typedData, key);
+ }
+ }
+}
diff --git a/src/SignatureGeneration/CancelOrdersV1.cs b/src/SignatureGeneration/CancelOrdersV1.cs
new file mode 100644
index 0000000..2bd3904
--- /dev/null
+++ b/src/SignatureGeneration/CancelOrdersV1.cs
@@ -0,0 +1,55 @@
+using Nethereum.Signer.EIP712;
+using Nethereum.ABI.FunctionEncoding.Attributes;
+using Nethereum.Signer;
+using Nethereum.ABI.EIP712;
+using System.Numerics;
+
+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 DomainWithNameVersionAndChainId
+ {
+ Name = "CancelOrderSportX",
+ Version = "1.0",
+ ChainId = chainId,
+ },
+ Types = MemberDescriptionFactory.GetTypesMemberDescription(typeof(DomainWithNameVersionAndChainId), typeof(Details)),
+ PrimaryType = nameof(Details),
+ };
+ }
+
+ public static string GetCancelOrdersEIP712Payload(List orders, BigInteger chainId, EthECKey key)
+ {
+ var typedData = GetTypedDefinition(chainId);
+
+ var mail = new Details
+ {
+ Message = PROMPT,
+ Orders = orders
+ };
+
+ return _signer.SignTypedDataV4(mail, typedData, key);
+ }
+
+ }
+}
diff --git a/src/SignatureGeneration/SaltDomain.cs b/src/SignatureGeneration/SaltDomain.cs
new file mode 100644
index 0000000..0de962a
--- /dev/null
+++ b/src/SignatureGeneration/SaltDomain.cs
@@ -0,0 +1,22 @@
+using Nethereum.ABI.FunctionEncoding.Attributes;
+using System.Numerics;
+
+namespace Nethereum.ABI.EIP712
+{
+ [Struct("EIP712Domain")]
+ 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 byte[] 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..286fce8
--- /dev/null
+++ b/src/Utils.cs
@@ -0,0 +1,17 @@
+using System.Numerics;
+using System.Security.Cryptography;
+
+namespace SigningExampleNethereum {
+
+ public static class Utilities {
+
+ 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 number;
+ }
+ }
+}
\ No newline at end of file