From 226371cf5fb28ac37af69abdca3d8b0a8490ab87 Mon Sep 17 00:00:00 2001 From: Juan Blanco Date: Thu, 27 Jul 2023 20:26:59 +0200 Subject: [PATCH] Nethereum.DataServices.Etherscan Etherscan api integration --- Nethereum.sln | 38 ++++++ .../EtherscanApiAccountsService.cs | 41 +++++++ .../EtherscanApiContractsService.cs | 38 ++++++ .../EtherscanApiService.cs | 45 +++++++ .../EtherscanChain.cs | 32 +++++ .../EtherscanRequestService.cs | 66 ++++++++++ .../Nethereum.DataServices.Etherscan.csproj | 30 +++++ ...nGetAccountInternalTransactionsResponse.cs | 50 ++++++++ ...EtherscanGetAccountTransactionsResponse.cs | 68 +++++++++++ .../EtherscanGetContractCreatorResponse.cs | 17 +++ .../EtherscanGetSourceCodeResponse.cs | 77 ++++++++++++ .../Responses/EtherscanResponse.cs | 16 +++ .../EtherscanApiServiceTests.cs | 114 ++++++++++++++++++ ...Services.Etherscan.IntegrationTests.csproj | 38 ++++++ 14 files changed, 670 insertions(+) create mode 100644 src/Nethereum.DataServices.Etherscan/EtherscanApiAccountsService.cs create mode 100644 src/Nethereum.DataServices.Etherscan/EtherscanApiContractsService.cs create mode 100644 src/Nethereum.DataServices.Etherscan/EtherscanApiService.cs create mode 100644 src/Nethereum.DataServices.Etherscan/EtherscanChain.cs create mode 100644 src/Nethereum.DataServices.Etherscan/EtherscanRequestService.cs create mode 100644 src/Nethereum.DataServices.Etherscan/Nethereum.DataServices.Etherscan.csproj create mode 100644 src/Nethereum.DataServices.Etherscan/Responses/EtherscanGetAccountInternalTransactionsResponse.cs create mode 100644 src/Nethereum.DataServices.Etherscan/Responses/EtherscanGetAccountTransactionsResponse.cs create mode 100644 src/Nethereum.DataServices.Etherscan/Responses/EtherscanGetContractCreatorResponse.cs create mode 100644 src/Nethereum.DataServices.Etherscan/Responses/EtherscanGetSourceCodeResponse.cs create mode 100644 src/Nethereum.DataServices.Etherscan/Responses/EtherscanResponse.cs create mode 100644 tests/Nethereum.DataServices.Etherscan.IntegrationTests/EtherscanApiServiceTests.cs create mode 100644 tests/Nethereum.DataServices.Etherscan.IntegrationTests/Nethereum.DataServices.Etherscan.IntegrationTests.csproj diff --git a/Nethereum.sln b/Nethereum.sln index aef3ab834..0d77db5cc 100644 --- a/Nethereum.sln +++ b/Nethereum.sln @@ -254,6 +254,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Nethereum.EVM.Contracts", " EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Nethereum.RPC.Extensions", "src\Nethereum.RPC.Extensions\Nethereum.RPC.Extensions.csproj", "{DC1269E2-E56A-4D8D-BDD8-F6B67D522194}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Nethereum.DataServices.Etherscan", "src\Nethereum.DataServices.Etherscan\Nethereum.DataServices.Etherscan.csproj", "{B317D562-ABFF-4A1C-AE84-7E65EB16F80D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Nethereum.DataServices.Etherscan.IntegrationTests", "tests\Nethereum.DataServices.Etherscan.IntegrationTests\Nethereum.DataServices.Etherscan.IntegrationTests.csproj", "{81CCC4DA-CA23-4B37-A68C-165F0007F541}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1658,6 +1662,38 @@ Global {DC1269E2-E56A-4D8D-BDD8-F6B67D522194}.Release|x64.Build.0 = Release|Any CPU {DC1269E2-E56A-4D8D-BDD8-F6B67D522194}.Release|x86.ActiveCfg = Release|Any CPU {DC1269E2-E56A-4D8D-BDD8-F6B67D522194}.Release|x86.Build.0 = Release|Any CPU + {B317D562-ABFF-4A1C-AE84-7E65EB16F80D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B317D562-ABFF-4A1C-AE84-7E65EB16F80D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B317D562-ABFF-4A1C-AE84-7E65EB16F80D}.Debug|ARM.ActiveCfg = Debug|Any CPU + {B317D562-ABFF-4A1C-AE84-7E65EB16F80D}.Debug|ARM.Build.0 = Debug|Any CPU + {B317D562-ABFF-4A1C-AE84-7E65EB16F80D}.Debug|x64.ActiveCfg = Debug|Any CPU + {B317D562-ABFF-4A1C-AE84-7E65EB16F80D}.Debug|x64.Build.0 = Debug|Any CPU + {B317D562-ABFF-4A1C-AE84-7E65EB16F80D}.Debug|x86.ActiveCfg = Debug|Any CPU + {B317D562-ABFF-4A1C-AE84-7E65EB16F80D}.Debug|x86.Build.0 = Debug|Any CPU + {B317D562-ABFF-4A1C-AE84-7E65EB16F80D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B317D562-ABFF-4A1C-AE84-7E65EB16F80D}.Release|Any CPU.Build.0 = Release|Any CPU + {B317D562-ABFF-4A1C-AE84-7E65EB16F80D}.Release|ARM.ActiveCfg = Release|Any CPU + {B317D562-ABFF-4A1C-AE84-7E65EB16F80D}.Release|ARM.Build.0 = Release|Any CPU + {B317D562-ABFF-4A1C-AE84-7E65EB16F80D}.Release|x64.ActiveCfg = Release|Any CPU + {B317D562-ABFF-4A1C-AE84-7E65EB16F80D}.Release|x64.Build.0 = Release|Any CPU + {B317D562-ABFF-4A1C-AE84-7E65EB16F80D}.Release|x86.ActiveCfg = Release|Any CPU + {B317D562-ABFF-4A1C-AE84-7E65EB16F80D}.Release|x86.Build.0 = Release|Any CPU + {81CCC4DA-CA23-4B37-A68C-165F0007F541}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {81CCC4DA-CA23-4B37-A68C-165F0007F541}.Debug|Any CPU.Build.0 = Debug|Any CPU + {81CCC4DA-CA23-4B37-A68C-165F0007F541}.Debug|ARM.ActiveCfg = Debug|Any CPU + {81CCC4DA-CA23-4B37-A68C-165F0007F541}.Debug|ARM.Build.0 = Debug|Any CPU + {81CCC4DA-CA23-4B37-A68C-165F0007F541}.Debug|x64.ActiveCfg = Debug|Any CPU + {81CCC4DA-CA23-4B37-A68C-165F0007F541}.Debug|x64.Build.0 = Debug|Any CPU + {81CCC4DA-CA23-4B37-A68C-165F0007F541}.Debug|x86.ActiveCfg = Debug|Any CPU + {81CCC4DA-CA23-4B37-A68C-165F0007F541}.Debug|x86.Build.0 = Debug|Any CPU + {81CCC4DA-CA23-4B37-A68C-165F0007F541}.Release|Any CPU.ActiveCfg = Release|Any CPU + {81CCC4DA-CA23-4B37-A68C-165F0007F541}.Release|Any CPU.Build.0 = Release|Any CPU + {81CCC4DA-CA23-4B37-A68C-165F0007F541}.Release|ARM.ActiveCfg = Release|Any CPU + {81CCC4DA-CA23-4B37-A68C-165F0007F541}.Release|ARM.Build.0 = Release|Any CPU + {81CCC4DA-CA23-4B37-A68C-165F0007F541}.Release|x64.ActiveCfg = Release|Any CPU + {81CCC4DA-CA23-4B37-A68C-165F0007F541}.Release|x64.Build.0 = Release|Any CPU + {81CCC4DA-CA23-4B37-A68C-165F0007F541}.Release|x86.ActiveCfg = Release|Any CPU + {81CCC4DA-CA23-4B37-A68C-165F0007F541}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1786,6 +1822,8 @@ Global {F73B9EBD-7C78-4A2E-8058-09F255FBAED1} = {962EC435-6130-4B75-BAC1-12323B067443} {B00B0908-B55D-4F9D-BD8D-70F81DDBA2F4} = {8F18B64A-CF6B-454F-A19C-8CE8FBFE2E58} {DC1269E2-E56A-4D8D-BDD8-F6B67D522194} = {F7E94706-FF67-4766-BBBD-27E7CED957A3} + {B317D562-ABFF-4A1C-AE84-7E65EB16F80D} = {F73B9EBD-7C78-4A2E-8058-09F255FBAED1} + {81CCC4DA-CA23-4B37-A68C-165F0007F541} = {F73B9EBD-7C78-4A2E-8058-09F255FBAED1} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {07A44726-8749-4A9A-8079-FA3C3213BDC1} diff --git a/src/Nethereum.DataServices.Etherscan/EtherscanApiAccountsService.cs b/src/Nethereum.DataServices.Etherscan/EtherscanApiAccountsService.cs new file mode 100644 index 000000000..6e2b9c158 --- /dev/null +++ b/src/Nethereum.DataServices.Etherscan/EtherscanApiAccountsService.cs @@ -0,0 +1,41 @@ +using Nethereum.DataServices.Etherscan.Responses; +using System.Collections.Generic; +using System.Numerics; +using System.Threading.Tasks; + +namespace Nethereum.DataServices.Etherscan +{ + public class EtherscanApiAccountsService + { + + public EtherscanRequestService EtherscanRequestService { get; private set; } + + public EtherscanApiAccountsService(EtherscanRequestService etherscanRequestService) + { + EtherscanRequestService = etherscanRequestService; + } + + public Task>> GetAccountTransactionsAsync(string address, int page = 1, int offset = 10, EtherscanResultSort sort = Etherscan.EtherscanResultSort.Ascending) + { + return GetAccountTransactionsAsync(address, 0, BigInteger.Parse("999999999999999"), page, offset, sort); + } + + + public async Task>> GetAccountTransactionsAsync(string address, BigInteger startBlock, BigInteger endBlock, int page = 1, int offset = 10, EtherscanResultSort sort = Etherscan.EtherscanResultSort.Ascending) + { + var url = $"{EtherscanRequestService.BaseUrl}api?module=account&action=txlist&address={address}&startblock={startBlock}&endblock{endBlock}&page={page}&offset={offset}&sort={sort.ConvertToRequestFormattedString()}&apikey={EtherscanRequestService.ApiKey}"; + return await EtherscanRequestService.GetDataAsync>(url).ConfigureAwait(false); + } + + public Task>> GetAccountInternalTransactionsAsync(string address, int page = 1, int offset = 10, EtherscanResultSort sort = Etherscan.EtherscanResultSort.Ascending) + { + return GetAccountInternalTransactionsAsync(address, 0, BigInteger.Parse("999999999999999"), page, offset, sort); + } + + public async Task>> GetAccountInternalTransactionsAsync(string address, BigInteger startBlock, BigInteger endBlock, int page = 1, int offset = 10, EtherscanResultSort sort = Etherscan.EtherscanResultSort.Ascending) + { + var url = $"{EtherscanRequestService.BaseUrl}api?module=account&action=txlistinternal&address={address}&startblock={startBlock}&endblock{endBlock}&page={page}&offset={offset}&sort={sort.ConvertToRequestFormattedString()}&apikey={EtherscanRequestService.ApiKey}"; + return await EtherscanRequestService.GetDataAsync>(url).ConfigureAwait(false); + } + } +} diff --git a/src/Nethereum.DataServices.Etherscan/EtherscanApiContractsService.cs b/src/Nethereum.DataServices.Etherscan/EtherscanApiContractsService.cs new file mode 100644 index 000000000..029c4f0cb --- /dev/null +++ b/src/Nethereum.DataServices.Etherscan/EtherscanApiContractsService.cs @@ -0,0 +1,38 @@ +using Nethereum.DataServices.Etherscan.Responses; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Threading.Tasks; + +namespace Nethereum.DataServices.Etherscan +{ + + + public class EtherscanApiContractsService + { + public EtherscanRequestService EtherscanRequestService { get; private set; } + + public EtherscanApiContractsService(EtherscanRequestService etherscanRequestService) + { + EtherscanRequestService = etherscanRequestService; + } + + public async Task>> GetSourceCodeAsync(string address) + { + var url = $"{EtherscanRequestService.BaseUrl}api?module=contract&action=getsourcecode&address={address}&apikey={EtherscanRequestService.ApiKey}"; + return await EtherscanRequestService.GetDataAsync>(url).ConfigureAwait(false); + } + + public async Task> GetAbiAsync(string address) + { + var url = $"{EtherscanRequestService.BaseUrl}api?module=contract&action=getabi&address={address}&apikey={EtherscanRequestService.ApiKey}"; + return await EtherscanRequestService.GetDataAsync(url).ConfigureAwait(false); + } + + public async Task>> GetContractCreatorAndCreationTxHashAsync(params string[] addresses) + { + var url = $"{EtherscanRequestService.BaseUrl}api?module=contract&action=getcontractcreation&contractaddresses={string.Join(",",addresses)}&apikey={EtherscanRequestService.ApiKey}"; + return await EtherscanRequestService.GetDataAsync>(url).ConfigureAwait(false); + } + } +} diff --git a/src/Nethereum.DataServices.Etherscan/EtherscanApiService.cs b/src/Nethereum.DataServices.Etherscan/EtherscanApiService.cs new file mode 100644 index 000000000..b9392e9ed --- /dev/null +++ b/src/Nethereum.DataServices.Etherscan/EtherscanApiService.cs @@ -0,0 +1,45 @@ +using Nethereum.DataServices.Etherscan.Responses; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.IO; +using System.Net.Http; +using System.Threading.Tasks; + +namespace Nethereum.DataServices.Etherscan +{ + public class EtherscanApiService + { + private EtherscanRequestService requestService; + public EtherscanApiContractsService Contracts { get; private set; } + public EtherscanApiAccountsService Accounts { get; private set; } + public EtherscanApiService(HttpClient httpClient, string baseUrl, string apiKey = EtherscanRequestService.DefaultToken):this(new EtherscanRequestService(httpClient, baseUrl, apiKey)) + { + + } + + public EtherscanApiService(HttpClient httpClient, EtherscanChain chain, string apiKey = EtherscanRequestService.DefaultToken): + this(new EtherscanRequestService(httpClient, chain, apiKey)) + { + + } + + public EtherscanApiService(EtherscanChain chain = EtherscanChain.Mainnet, string apiKey = EtherscanRequestService.DefaultToken) + :this(new EtherscanRequestService(chain, apiKey)) + { + + } + + public EtherscanApiService(EtherscanRequestService etherscanRequestService) + { + requestService = etherscanRequestService; + InitialiseServices(); + } + + private void InitialiseServices() + { + Contracts = new EtherscanApiContractsService(requestService); + Accounts = new EtherscanApiAccountsService(requestService); + } + } +} diff --git a/src/Nethereum.DataServices.Etherscan/EtherscanChain.cs b/src/Nethereum.DataServices.Etherscan/EtherscanChain.cs new file mode 100644 index 000000000..4a377b15f --- /dev/null +++ b/src/Nethereum.DataServices.Etherscan/EtherscanChain.cs @@ -0,0 +1,32 @@ +using System; + +namespace Nethereum.DataServices.Etherscan +{ + public enum EtherscanChain + { + Mainnet, + + } + + public enum EtherscanResultSort + { + Ascending, + Descending + } + + public static class EtherscanExtensions + { + public static string ConvertToRequestFormattedString(this EtherscanResultSort value) + { + switch (value) + { + case EtherscanResultSort.Ascending: + return "asc"; + case EtherscanResultSort.Descending: + return "desc"; + } + + throw new NotImplementedException(); + } + } +} diff --git a/src/Nethereum.DataServices.Etherscan/EtherscanRequestService.cs b/src/Nethereum.DataServices.Etherscan/EtherscanRequestService.cs new file mode 100644 index 000000000..fd0d1fd44 --- /dev/null +++ b/src/Nethereum.DataServices.Etherscan/EtherscanRequestService.cs @@ -0,0 +1,66 @@ +using Nethereum.DataServices.Etherscan.Responses; +using Newtonsoft.Json; +using System; +using System.IO; +using System.Net.Http; +using System.Threading.Tasks; + +namespace Nethereum.DataServices.Etherscan +{ + public class EtherscanRequestService + { + public const string DefaultToken = "YourApiKeyToken"; + + public static string GetBaseUrl(EtherscanChain chain) + { + switch (chain) + { + case EtherscanChain.Mainnet: + return "https://api.etherscan.io/"; + } + throw new NotImplementedException(); + } + + public EtherscanRequestService(HttpClient httpClient, string baseUrl, string apiKey= DefaultToken) + { + HttpClient = httpClient; + BaseUrl = baseUrl; + ApiKey = apiKey; + } + + public EtherscanRequestService(HttpClient httpClient, EtherscanChain chain, string apiKey = DefaultToken) + { + HttpClient = httpClient; + BaseUrl = GetBaseUrl(chain); + ApiKey = apiKey; + } + + public EtherscanRequestService(EtherscanChain chain = EtherscanChain.Mainnet, string apiKey = DefaultToken) + { + HttpClient = new HttpClient(); + BaseUrl = GetBaseUrl(chain); + ApiKey = apiKey; + } + + public HttpClient HttpClient { get; } + public string BaseUrl { get; } + public string ApiKey { get; } + + + + public async Task> GetDataAsync(string url) + { + var httpResponseMessage = await HttpClient.GetAsync(url).ConfigureAwait(false); + httpResponseMessage.EnsureSuccessStatusCode(); + var stream = await httpResponseMessage.Content.ReadAsStreamAsync().ConfigureAwait(false); + using (var streamReader = new StreamReader(stream)) + using (var reader = new JsonTextReader(streamReader)) + { + var serializer = JsonSerializer.Create(); + var message = serializer.Deserialize>(reader); + + return message; + } + } + } +} diff --git a/src/Nethereum.DataServices.Etherscan/Nethereum.DataServices.Etherscan.csproj b/src/Nethereum.DataServices.Etherscan/Nethereum.DataServices.Etherscan.csproj new file mode 100644 index 000000000..dcec57dc4 --- /dev/null +++ b/src/Nethereum.DataServices.Etherscan/Nethereum.DataServices.Etherscan.csproj @@ -0,0 +1,30 @@ + + + + Nethereum DataServices Etherscan library, provides client access to Etherscan rest apis + Nethereum.DataServices.Etherscan + $(NethereumVersion) + $(DefaultFrameworks) + Nethereum.DataServices.Etherscan + Nethereum.DataServices.Etherscan + Netherum;Ethereum;Blockchain;Etherscan + true + + + + + true + ..\..\NethereumKey.snk + + + + + + + + + + + + + diff --git a/src/Nethereum.DataServices.Etherscan/Responses/EtherscanGetAccountInternalTransactionsResponse.cs b/src/Nethereum.DataServices.Etherscan/Responses/EtherscanGetAccountInternalTransactionsResponse.cs new file mode 100644 index 000000000..a1e4678f1 --- /dev/null +++ b/src/Nethereum.DataServices.Etherscan/Responses/EtherscanGetAccountInternalTransactionsResponse.cs @@ -0,0 +1,50 @@ +using Newtonsoft.Json; + +namespace Nethereum.DataServices.Etherscan.Responses +{ + public class EtherscanGetAccountInternalTransactionsResponse + { + [JsonProperty("blockNumber")] + public string BlockNumber { get; set; } + + [JsonProperty("timeStamp")] + public string TimeStamp { get; set; } + + [JsonProperty("hash")] + public string Hash { get; set; } + + [JsonProperty("from")] + public string From { get; set; } + + [JsonProperty("to")] + public string To { get; set; } + + [JsonProperty("value")] + public string Value { get; set; } + + [JsonProperty("contractAddress")] + public string ContractAddress { get; set; } + + [JsonProperty("input")] + public string Input { get; set; } + + [JsonProperty("type")] + public string Type { get; set; } + + [JsonProperty("gas")] + public string Gas { get; set; } + + [JsonProperty("gasUsed")] + public string GasUsed { get; set; } + + [JsonProperty("traceId")] + public string TraceId { get; set; } + + [JsonProperty("isError")] + public string IsError { get; set; } + + [JsonProperty("errCode")] + public string ErrCode { get; set; } + } + +} diff --git a/src/Nethereum.DataServices.Etherscan/Responses/EtherscanGetAccountTransactionsResponse.cs b/src/Nethereum.DataServices.Etherscan/Responses/EtherscanGetAccountTransactionsResponse.cs new file mode 100644 index 000000000..1184f9dd7 --- /dev/null +++ b/src/Nethereum.DataServices.Etherscan/Responses/EtherscanGetAccountTransactionsResponse.cs @@ -0,0 +1,68 @@ +using Newtonsoft.Json; + +namespace Nethereum.DataServices.Etherscan.Responses +{ + public class EtherscanGetAccountTransactionsResponse + { + [JsonProperty("blockNumber")] + public string BlockNumber { get; set; } + + [JsonProperty("timeStamp")] + public string TimeStamp { get; set; } + + [JsonProperty("hash")] + public string Hash { get; set; } + + [JsonProperty("nonce")] + public string Nonce { get; set; } + + [JsonProperty("blockHash")] + public string BlockHash { get; set; } + + [JsonProperty("transactionIndex")] + public string TransactionIndex { get; set; } + + [JsonProperty("from")] + public string From { get; set; } + + [JsonProperty("to")] + public string To { get; set; } + + [JsonProperty("value")] + public string Value { get; set; } + + [JsonProperty("gas")] + public string Gas { get; set; } + + [JsonProperty("gasPrice")] + public string GasPrice { get; set; } + + [JsonProperty("isError")] + public string IsError { get; set; } + + [JsonProperty("txreceipt_status")] + public string TxnReceiptStatus { get; set; } + + [JsonProperty("input")] + public string Input { get; set; } + + [JsonProperty("contractAddress")] + public string ContractAddress { get; set; } + + [JsonProperty("cumulativeGasUsed")] + public string CumulativeGasUsed { get; set; } + + [JsonProperty("gasUsed")] + public string GasUsed { get; set; } + + [JsonProperty("confirmations")] + public string Confirmations { get; set; } + + [JsonProperty("methodId")] + public string MethodId { get; set; } + + [JsonProperty("functionName")] + public string FunctionName { get; set; } + } + +} diff --git a/src/Nethereum.DataServices.Etherscan/Responses/EtherscanGetContractCreatorResponse.cs b/src/Nethereum.DataServices.Etherscan/Responses/EtherscanGetContractCreatorResponse.cs new file mode 100644 index 000000000..9ab442215 --- /dev/null +++ b/src/Nethereum.DataServices.Etherscan/Responses/EtherscanGetContractCreatorResponse.cs @@ -0,0 +1,17 @@ +using Newtonsoft.Json; + +namespace Nethereum.DataServices.Etherscan.Responses +{ + public class EtherscanGetContractCreatorResponse + { + [JsonProperty("contractAddress")] + public string ContractAddress { get; set; } + + [JsonProperty("contractCreator")] + public string ContractCreator { get; set; } + + [JsonProperty("txHash")] + public string TxHash { get; set; } + } + +} diff --git a/src/Nethereum.DataServices.Etherscan/Responses/EtherscanGetSourceCodeResponse.cs b/src/Nethereum.DataServices.Etherscan/Responses/EtherscanGetSourceCodeResponse.cs new file mode 100644 index 000000000..473727e08 --- /dev/null +++ b/src/Nethereum.DataServices.Etherscan/Responses/EtherscanGetSourceCodeResponse.cs @@ -0,0 +1,77 @@ +using Nethereum.ABI.CompilationMetadata; +using Newtonsoft.Json; +using System.Collections; +using System.Collections.Generic; + +namespace Nethereum.DataServices.Etherscan.Responses +{ + + public class EtherscanGetSourceCodeResponse + { + [JsonProperty("SourceCode")] + public string SourceCode { get; set; } + + [JsonProperty("ABI")] + public string ABI { get; set; } + + [JsonProperty("ContractName")] + public string ContractName { get; set; } + + [JsonProperty("CompilerVersion")] + public string CompilerVersion { get; set; } + + [JsonProperty("OptimizationUsed")] + public string OptimizationUsed { get; set; } + + [JsonProperty("Runs")] + public string Runs { get; set; } + + [JsonProperty("ConstructorArguments")] + public string ConstructorArguments { get; set; } + + [JsonProperty("EVMVersion")] + public string EVMVersion { get; set; } + + [JsonProperty("Library")] + public string Library { get; set; } + + [JsonProperty("LicenseType")] + public string LicenseType { get; set; } + + [JsonProperty("Proxy")] + public string Proxy { get; set; } + + [JsonProperty("Implementation")] + public string Implementation { get; set; } + + [JsonProperty("SwarmSource")] + public string SwarmSource { get; set; } + + public bool ContainsSourceCodeCompilationMetadata() + { + return !string.IsNullOrEmpty(SourceCode) && SourceCode.TrimStart().StartsWith("{{"); + } + + public CompilationMetadata DeserialiseCompilationMetadata() + { + if (ContainsSourceCodeCompilationMetadata()) + { + var source = SourceCode.Trim().Substring(1, SourceCode.Length - 2); + + return JsonConvert.DeserializeObject(source); + } + //TODO: Convert model to CompilationMetadata + return null; + } + } + + public static class EtherscanCompilationMetadataExtensions + { + public static SourceCode GetLocalSourceCode(this CompilationMetadata compilationMetadata, string contractPathName) + { + if (compilationMetadata.Language == "Solidity" && !contractPathName.EndsWith(".sol")) contractPathName = contractPathName + ".sol"; + return compilationMetadata.Sources["contracts/" + contractPathName]; + } + } + +} diff --git a/src/Nethereum.DataServices.Etherscan/Responses/EtherscanResponse.cs b/src/Nethereum.DataServices.Etherscan/Responses/EtherscanResponse.cs new file mode 100644 index 000000000..50132773b --- /dev/null +++ b/src/Nethereum.DataServices.Etherscan/Responses/EtherscanResponse.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; + +namespace Nethereum.DataServices.Etherscan.Responses +{ + public class EtherscanResponse + { + [JsonProperty("status")] + public string Status { get; set; } + + [JsonProperty("message")] + public string Message { get; set; } + + [JsonProperty("result")] + public T Result { get; set; } + } +} diff --git a/tests/Nethereum.DataServices.Etherscan.IntegrationTests/EtherscanApiServiceTests.cs b/tests/Nethereum.DataServices.Etherscan.IntegrationTests/EtherscanApiServiceTests.cs new file mode 100644 index 000000000..bbebae5e1 --- /dev/null +++ b/tests/Nethereum.DataServices.Etherscan.IntegrationTests/EtherscanApiServiceTests.cs @@ -0,0 +1,114 @@ + +using Nethereum.DataServices.Etherscan.Responses; +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace Nethereum.DataServices.Etherscan.IntegrationTests +{ + public class EtherscanApiServiceTests + { + private static readonly SemaphoreSlim throttler = new SemaphoreSlim(1); + + public async Task ThrottleEtherscanCallAsync(Func action) + { + await throttler.WaitAsync(); + try + { + await action(); + await Task.Delay(5000); + } + finally + { + throttler.Release(); + } + + } + + [Fact] + public async void ShouldGetAbi() + { + await ThrottleEtherscanCallAsync(async () => + { + + var etherscanService = new EtherscanApiService(); + var result = await etherscanService.Contracts.GetAbiAsync("0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413"); + Assert.Equal("1", result.Status); + } + ); + + + } + + [Fact] + public async void ShouldGetContractCreators() + { + await ThrottleEtherscanCallAsync(async () => + { + + var etherscanService = new EtherscanApiService(); + var result = await etherscanService.Contracts.GetContractCreatorAndCreationTxHashAsync("0xB83c27805aAcA5C7082eB45C868d955Cf04C337F","0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45"); + Assert.Equal("1", result.Status); + } + ); + + } + + [Fact] + public async void ShouldGetContract() + { + await ThrottleEtherscanCallAsync(async () => + { + + var etherscanService = new EtherscanApiService(); + var result = await etherscanService.Contracts.GetSourceCodeAsync("0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413"); + Assert.Equal("1", result.Status); + }); + } + + [Fact] + public async void ShouldGetContractCompilationMetadata() + { + await ThrottleEtherscanCallAsync(async () => + { + var etherscanService = new EtherscanApiService(); + var result = await etherscanService.Contracts.GetSourceCodeAsync("0xC36442b4a4522E871399CD717aBDD847Ab11FE88"); + Assert.Equal("1", result.Status); + var contract = result.Result.First(); + Assert.True(contract.ContainsSourceCodeCompilationMetadata()); + var compilationMetadata = contract.DeserialiseCompilationMetadata(); + var contractSource = compilationMetadata.GetLocalSourceCode(contract.ContractName); + Assert.NotNull(contractSource); + }); + } + + [Fact] + public async void ShouldGetAccountTransactions() + { + await ThrottleEtherscanCallAsync(async () => + { + var etherscanService = new EtherscanApiService(); + var result = await etherscanService.Accounts.GetAccountTransactionsAsync("0xc5102fE9359FD9a28f877a67E36B0F050d81a3CC"); + Assert.Equal("1", result.Status); + Assert.Equal(10, result.Result.Count); + + }); + } + + [Fact] + public async void ShouldGetAccountInternalTransactions() + { + await ThrottleEtherscanCallAsync(async () => + { + var etherscanService = new EtherscanApiService(); + var result = await etherscanService.Accounts.GetAccountInternalTransactionsAsync("0x2c1ba59d6f58433fb1eaee7d20b26ed83bda51a3", 1, 12, EtherscanResultSort.Descending); + Assert.Equal("1", result.Status); + Assert.Equal(12, result.Result.Count); + + }); + } + + } +} \ No newline at end of file diff --git a/tests/Nethereum.DataServices.Etherscan.IntegrationTests/Nethereum.DataServices.Etherscan.IntegrationTests.csproj b/tests/Nethereum.DataServices.Etherscan.IntegrationTests/Nethereum.DataServices.Etherscan.IntegrationTests.csproj new file mode 100644 index 000000000..5f716da17 --- /dev/null +++ b/tests/Nethereum.DataServices.Etherscan.IntegrationTests/Nethereum.DataServices.Etherscan.IntegrationTests.csproj @@ -0,0 +1,38 @@ + + + + netcoreapp3.1;net461;net5.0 + Nethereum.DataServices.Etherscan.IntegrationTests + Nethereum.DataServices.Etherscan.IntegrationTests + + + + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + +