├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ ├── master_ninja-opensea.yml │ └── nuget_push.yml ├── .gitignore ├── LICENSE ├── OpenSeaClient.sln ├── README.md ├── src └── OpenSeaClient │ ├── Helpers │ ├── EnumExtensions.cs │ └── QueryHelpers.cs │ ├── IOpenSeaClient.cs │ ├── Models │ ├── Account.cs │ ├── Asset.cs │ ├── AssetBundle.cs │ ├── AssetContract.cs │ ├── Collection.cs │ ├── CollectionStats.cs │ ├── Event.cs │ ├── Order.cs │ ├── Trait.cs │ ├── Transaction.cs │ └── User.cs │ ├── OpenSeaClient.csproj │ ├── OpenSeaClientExtensions.cs │ ├── OpenSeaHttpClient.cs │ ├── QueryParams │ ├── GetAssetsQueryParams.cs │ ├── GetCollectionsQueryParams.cs │ ├── GetEventsQueryParams.cs │ ├── GetOrdersQueryParams.cs │ ├── OrderAssetsBy.cs │ ├── OrderDirection.cs │ └── OrderOrdersBy.cs │ └── UnitConversion │ ├── BigDecimal.cs │ ├── BigDecimalFormatter.cs │ ├── BigIntegerExtensions.cs │ ├── FormattingExtensions.cs │ ├── NumberFormatting.cs │ └── UnitConversion.cs └── test ├── OpenSeaClient.DemoApi ├── Controllers │ └── AccountController.cs ├── OpenSeaClient.DemoApi.csproj ├── Program.cs ├── Properties │ └── launchSettings.json ├── appsettings.Development.json └── appsettings.json └── OpenSeaClient.DemoConsole ├── OpenSeaClient.DemoConsole.csproj └── Program.cs /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: nuget 4 | directory: "/" 5 | schedule: 6 | interval: weekly 7 | open-pull-requests-limit: 10 8 | assignees: 9 | - "cristipufu" -------------------------------------------------------------------------------- /.github/workflows/master_ninja-opensea.yml: -------------------------------------------------------------------------------- 1 | # Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy 2 | # More GitHub Actions for Azure: https://github.com/Azure/actions 3 | 4 | name: Build and deploy ASP.Net Core app to Azure Web App - ninja-opensea 5 | 6 | on: 7 | push: 8 | branches: 9 | - master 10 | workflow_dispatch: 11 | 12 | jobs: 13 | build: 14 | runs-on: windows-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | 19 | - name: Set up .NET Core 20 | uses: actions/setup-dotnet@v1 21 | with: 22 | dotnet-version: '6.0.x' 23 | include-prerelease: true 24 | 25 | - name: Build with dotnet 26 | run: dotnet build --configuration Release 27 | working-directory: .\test\OpenSeaClient.DemoApi\ 28 | 29 | - name: dotnet publish 30 | run: dotnet publish -c Release -o ${{env.DOTNET_ROOT}}\demo 31 | working-directory: .\test\OpenSeaClient.DemoApi\ 32 | 33 | - name: Upload artifact for deployment job 34 | uses: actions/upload-artifact@v2 35 | with: 36 | name: .net-app 37 | path: ${{env.DOTNET_ROOT}}\demo 38 | 39 | deploy: 40 | runs-on: windows-latest 41 | needs: build 42 | environment: 43 | name: 'Production' 44 | url: ${{ steps.deploy-to-webapp.outputs.webapp-url }} 45 | 46 | steps: 47 | - name: Download artifact from build job 48 | uses: actions/download-artifact@v2 49 | with: 50 | name: .net-app 51 | 52 | - name: Deploy to Azure Web App 53 | id: deploy-to-webapp 54 | uses: azure/webapps-deploy@v2 55 | with: 56 | app-name: 'ninja-opensea' 57 | slot-name: 'Production' 58 | publish-profile: ${{ secrets.AZUREAPPSERVICE_PUBLISHPROFILE_0442857F03EC46A7A18010696AE31DF2 }} 59 | package: . 60 | -------------------------------------------------------------------------------- /.github/workflows/nuget_push.yml: -------------------------------------------------------------------------------- 1 | name: NuGet Push 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | name: Update NuGet package 10 | steps: 11 | 12 | - name: Checkout repository 13 | uses: actions/checkout@v1 14 | 15 | - name: Setup .NET Core @ Latest 16 | uses: actions/setup-dotnet@v1.9.0 17 | 18 | - name: Build and Publish 19 | run: | 20 | cd ./src/OpenSeaClient/ 21 | dotnet pack -c Release -o artifacts -p:PackageVersion=1.0.${{ github.run_number }} 22 | - name: Push 23 | run: dotnet nuget push ./src/OpenSeaClient/artifacts/OpenSeaClient.1.0.${{ github.run_number }}.nupkg -k ${{ secrets.NUGET_APIKEY }} -s https://api.nuget.org/v3/index.json 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Oo]ut/ 33 | [Ll]og/ 34 | [Ll]ogs/ 35 | 36 | # Visual Studio 2015/2017 cache/options directory 37 | .vs/ 38 | # Uncomment if you have tasks that create the project's static files in wwwroot 39 | #wwwroot/ 40 | 41 | # Visual Studio 2017 auto generated files 42 | Generated\ Files/ 43 | 44 | # MSTest test Results 45 | [Tt]est[Rr]esult*/ 46 | [Bb]uild[Ll]og.* 47 | 48 | # NUnit 49 | *.VisualState.xml 50 | TestResult.xml 51 | nunit-*.xml 52 | 53 | # Build Results of an ATL Project 54 | [Dd]ebugPS/ 55 | [Rr]eleasePS/ 56 | dlldata.c 57 | 58 | # Benchmark Results 59 | BenchmarkDotNet.Artifacts/ 60 | 61 | # .NET Core 62 | project.lock.json 63 | project.fragment.lock.json 64 | artifacts/ 65 | 66 | # ASP.NET Scaffolding 67 | ScaffoldingReadMe.txt 68 | 69 | # StyleCop 70 | StyleCopReport.xml 71 | 72 | # Files built by Visual Studio 73 | *_i.c 74 | *_p.c 75 | *_h.h 76 | *.ilk 77 | *.meta 78 | *.obj 79 | *.iobj 80 | *.pch 81 | *.pdb 82 | *.ipdb 83 | *.pgc 84 | *.pgd 85 | *.rsp 86 | *.sbr 87 | *.tlb 88 | *.tli 89 | *.tlh 90 | *.tmp 91 | *.tmp_proj 92 | *_wpftmp.csproj 93 | *.log 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio LightSwitch build output 298 | **/*.HTMLClient/GeneratedArtifacts 299 | **/*.DesktopClient/GeneratedArtifacts 300 | **/*.DesktopClient/ModelManifest.xml 301 | **/*.Server/GeneratedArtifacts 302 | **/*.Server/ModelManifest.xml 303 | _Pvt_Extensions 304 | 305 | # Paket dependency manager 306 | .paket/paket.exe 307 | paket-files/ 308 | 309 | # FAKE - F# Make 310 | .fake/ 311 | 312 | # CodeRush personal settings 313 | .cr/personal 314 | 315 | # Python Tools for Visual Studio (PTVS) 316 | __pycache__/ 317 | *.pyc 318 | 319 | # Cake - Uncomment if you are using it 320 | # tools/** 321 | # !tools/packages.config 322 | 323 | # Tabs Studio 324 | *.tss 325 | 326 | # Telerik's JustMock configuration file 327 | *.jmconfig 328 | 329 | # BizTalk build output 330 | *.btp.cs 331 | *.btm.cs 332 | *.odx.cs 333 | *.xsd.cs 334 | 335 | # OpenCover UI analysis results 336 | OpenCover/ 337 | 338 | # Azure Stream Analytics local run output 339 | ASALocalRun/ 340 | 341 | # MSBuild Binary and Structured Log 342 | *.binlog 343 | 344 | # NVidia Nsight GPU debugger configuration file 345 | *.nvuser 346 | 347 | # MFractors (Xamarin productivity tool) working folder 348 | .mfractor/ 349 | 350 | # Local History for Visual Studio 351 | .localhistory/ 352 | 353 | # BeatPulse healthcheck temp database 354 | healthchecksdb 355 | 356 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 357 | MigrationBackup/ 358 | 359 | # Ionide (cross platform F# VS Code tools) working folder 360 | .ionide/ 361 | 362 | # Fody - auto-generated XML schema 363 | FodyWeavers.xsd -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Cristi Pufu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /OpenSeaClient.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31903.59 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenSeaClient", "src\OpenSeaClient\OpenSeaClient.csproj", "{C5A872D2-EB1C-42F9-A282-EE5FB303CCDF}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{D2B27878-16B9-45AF-9919-2D956A502926}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{7F33ECE0-AA43-4B0F-AEEF-47A736C84CDC}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenSeaClient.DemoConsole", "test\OpenSeaClient.DemoConsole\OpenSeaClient.DemoConsole.csproj", "{14DB1693-C14A-416A-8F8A-79012A8B56A6}" 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenSeaClient.DemoApi", "test\OpenSeaClient.DemoApi\OpenSeaClient.DemoApi.csproj", "{5C4A1662-CBA8-4932-9CE5-E2B8C29ED698}" 15 | EndProject 16 | Global 17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 18 | Debug|Any CPU = Debug|Any CPU 19 | Release|Any CPU = Release|Any CPU 20 | EndGlobalSection 21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 22 | {C5A872D2-EB1C-42F9-A282-EE5FB303CCDF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {C5A872D2-EB1C-42F9-A282-EE5FB303CCDF}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {C5A872D2-EB1C-42F9-A282-EE5FB303CCDF}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {C5A872D2-EB1C-42F9-A282-EE5FB303CCDF}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {14DB1693-C14A-416A-8F8A-79012A8B56A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {14DB1693-C14A-416A-8F8A-79012A8B56A6}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {14DB1693-C14A-416A-8F8A-79012A8B56A6}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {14DB1693-C14A-416A-8F8A-79012A8B56A6}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {5C4A1662-CBA8-4932-9CE5-E2B8C29ED698}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {5C4A1662-CBA8-4932-9CE5-E2B8C29ED698}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {5C4A1662-CBA8-4932-9CE5-E2B8C29ED698}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {5C4A1662-CBA8-4932-9CE5-E2B8C29ED698}.Release|Any CPU.Build.0 = Release|Any CPU 34 | EndGlobalSection 35 | GlobalSection(SolutionProperties) = preSolution 36 | HideSolutionNode = FALSE 37 | EndGlobalSection 38 | GlobalSection(NestedProjects) = preSolution 39 | {C5A872D2-EB1C-42F9-A282-EE5FB303CCDF} = {D2B27878-16B9-45AF-9919-2D956A502926} 40 | {14DB1693-C14A-416A-8F8A-79012A8B56A6} = {7F33ECE0-AA43-4B0F-AEEF-47A736C84CDC} 41 | {5C4A1662-CBA8-4932-9CE5-E2B8C29ED698} = {7F33ECE0-AA43-4B0F-AEEF-47A736C84CDC} 42 | EndGlobalSection 43 | GlobalSection(ExtensibilityGlobals) = postSolution 44 | SolutionGuid = {83860492-2465-47D5-AF92-B15DDAEB70CF} 45 | EndGlobalSection 46 | EndGlobal 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | --- 6 | [![NuGet](https://img.shields.io/nuget/v/OpenSeaClient)](https://www.nuget.org/packages/OpenSeaClient/) 7 | [![GitHub](https://img.shields.io/github/license/ninjastacktech/opensea-net)](https://github.com/ninjastacktech/opensea-net/blob/master/LICENSE) 8 | 9 | .NET 6 C# SDK for the OpenSea marketplace API. 10 | 11 | The API docs can be found here: https://docs.opensea.io/reference/api-overview 12 | 13 | Demo API swagger: https://ninja-opensea.azurewebsites.net/swagger/index.html 14 | 15 | # install 16 | ```xml 17 | PM> Install-Package OpenSeaClient -Version 1.0.7 18 | ``` 19 | # snippets 20 | 21 | ### DI configuration: 22 | ```C# 23 | builder.Services.AddHttpClient(config => 24 | { 25 | config.DefaultRequestHeaders.Add("X-Api-Key", ""); 26 | }); 27 | ``` 28 | 29 | ### Get assets in batches of 50 with 1s delay between API calls to avoid throttling: 30 | ```C# 31 | var client = new OpenSeaHttpClient(apiKey: ""); 32 | 33 | var queryParams = new GetAssetsQueryParams 34 | { 35 | CollectionSlug = collectionSlug, 36 | }; 37 | var count = 0; 38 | var limit = 50; 39 | var it = 0; 40 | 41 | var assetsList = new List(); 42 | 43 | do 44 | { 45 | queryParams.Offset = limit * it; 46 | queryParams.Limit = limit; 47 | 48 | var assets = await client.GetAssetsAsync(queryParams); 49 | 50 | if (assets != null) 51 | { 52 | if (assets.Count > 0) 53 | { 54 | assetsList.AddRange(assets); 55 | } 56 | } 57 | else 58 | { 59 | break; 60 | } 61 | 62 | await Task.Delay(1000); 63 | } 64 | while (count == 50); 65 | ``` 66 | 67 | ### Get the price of a token's sale order: 68 | Note that the listing price of an asset is equal to the currentPrice of the lowest valid sell order on the asset. 69 | Users can lower their listing price without invalidating previous sell orders, so all get shipped down until they're canceled, or one is fulfilled. 70 | ```C# 71 | var client = new OpenSeaHttpClient(apiKey: ""); 72 | 73 | var orders = await client.GetOrdersAsync(new GetOrdersQueryParams 74 | { 75 | AssetContractAddress = contractAddress, 76 | TokenId = tokenId, 77 | Side = 1, 78 | SaleKind = 0, 79 | }); 80 | 81 | if (orders?.Any() == true) 82 | { 83 | var order = orders.Where(x => x.Cancelled == false).OrderBy(x => x.CurrentPriceEth).FirstOrDefault(); 84 | 85 | if (order != null) 86 | { 87 | Console.WriteLine($"Token {tokenId} has a sell order of {order.CurrentPriceEth} ETH"); 88 | } 89 | } 90 | ``` 91 | 92 | 93 | --- 94 | 95 | ### MIT License 96 | 97 | 98 | -------------------------------------------------------------------------------- /src/OpenSeaClient/Helpers/EnumExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Reflection; 3 | 4 | namespace OpenSeaClient 5 | { 6 | internal static class EnumExtensions 7 | { 8 | internal static string? GetDescription(this T? value) 9 | where T : struct 10 | { 11 | Type type = typeof(T); 12 | 13 | if (!type.IsEnum) 14 | { 15 | throw new InvalidOperationException("The type parameter T must be an enum type."); 16 | } 17 | 18 | if (value == null) 19 | { 20 | return null; 21 | } 22 | 23 | if (!Enum.IsDefined(type, value)) 24 | { 25 | throw new InvalidEnumArgumentException("value", Convert.ToInt32(value), type); 26 | } 27 | 28 | var stringValue = value.ToString(); 29 | 30 | if (stringValue == null) 31 | { 32 | return stringValue; 33 | } 34 | 35 | var fi = type.GetField(stringValue, BindingFlags.Static | BindingFlags.Public); 36 | 37 | return fi?.GetCustomAttributes()?.FirstOrDefault()?.Description; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/OpenSeaClient/Helpers/QueryHelpers.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using System.Text.Encodings.Web; 3 | 4 | namespace OpenSeaClient 5 | { 6 | internal static class QueryHelpers 7 | { 8 | internal static string AddQueryString(string uri, IEnumerable<(string, string)>? queryString) 9 | { 10 | if (queryString == null) 11 | { 12 | return uri; 13 | } 14 | 15 | var num = uri.IndexOf('#'); 16 | var text = uri; 17 | var value = ""; 18 | if (num != -1) 19 | { 20 | value = uri[num..]; 21 | text = uri[..num]; 22 | } 23 | 24 | var flag = text.IndexOf('?') != -1; 25 | StringBuilder stringBuilder = new(); 26 | stringBuilder.Append(text); 27 | foreach (var param in queryString) 28 | { 29 | stringBuilder.Append(flag ? '&' : '?'); 30 | stringBuilder.Append(UrlEncoder.Default.Encode(param.Item1)); 31 | stringBuilder.Append('='); 32 | stringBuilder.Append(UrlEncoder.Default.Encode(param.Item2)); 33 | flag = true; 34 | } 35 | 36 | stringBuilder.Append(value); 37 | return stringBuilder.ToString(); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/OpenSeaClient/IOpenSeaClient.cs: -------------------------------------------------------------------------------- 1 | namespace OpenSeaClient 2 | { 3 | public interface IOpenSeaClient 4 | { 5 | /// 6 | /// Retrieving orders from the OpenSea system. 7 | /// Note: this API endpoint requires an API key in order to use. 8 | /// 9 | /// 10 | /// 11 | /// 12 | Task?> GetOrdersAsync(GetOrdersQueryParams? queryParams = null, CancellationToken ct = default); 13 | 14 | /// 15 | /// The /events endpoint provides a list of events that occur on the assets that OpenSea tracks. 16 | /// The "event_type" field indicates what type of event it is (transfer, successful auction, etc). 17 | /// Note: this API endpoint requires an API key in order to use. 18 | /// 19 | /// 20 | /// 21 | /// 22 | Task?> GetEventsAsync(GetEventsQueryParams? queryParams = null, CancellationToken ct = default); 23 | 24 | /// 25 | /// To retrieve assets from our API, call the /assets endpoint with the desired filter parameters. 26 | /// Please Note: this API endpoint requires an API key in order to use. Please fill out the API request form to obtain one. 27 | /// Auctions created on OpenSea don't use an escrow contract, which enables gas-free auctions and allows users to retain ownership of their items while they're on sale. 28 | /// So this is just a heads up in case you notice some assets from opensea.io not appearing in the API. 29 | /// 30 | /// 31 | /// 32 | /// 33 | Task?> GetAssetsAsync(GetAssetsQueryParams? queryParams = null, CancellationToken ct = default); 34 | 35 | /// 36 | /// Used to fetch more in-depth information about an individual asset. 37 | /// To retrieve an individual from our API, call the /asset endpoint with the address of the asset's contract and the token id. 38 | /// The endpoint will return an Asset Object. 39 | /// 40 | /// 41 | /// 42 | /// 43 | /// 44 | /// 45 | Task GetAssetAsync(string assetContractAddress, string tokenId, string? ownerAddress = null, CancellationToken ct = default); 46 | 47 | /// 48 | /// Used to fetch more in-depth information about an contract asset. 49 | /// 50 | /// 51 | /// 52 | /// 53 | Task GetAssetContractAsync(string assetContractAddress, CancellationToken ct = default); 54 | 55 | /// 56 | /// Use this endpoint to fetch collections on OpenSea. 57 | /// The /collections endpoint provides a list of all the collections supported and vetted by OpenSea. 58 | /// To include all collections relevant to a user (including non-whitelisted ones), set the owner param. 59 | /// Each collection in the returned area has an attribute called primary_asset_contracts with info about the smart contracts belonging to that collection. 60 | /// For example, ERC-1155 contracts maybe have multiple collections all referencing the same contract, but many ERC-721 contracts may all belong to the same collection (dapp). 61 | /// You can also use this endpoint to find which dapps an account uses, and how many items they own in each - all in one API call! 62 | /// 63 | /// 64 | /// 65 | /// 66 | Task?> GetCollectionsAsync(GetCollectionsQueryParams? queryParams = null, CancellationToken ct = default); 67 | 68 | /// 69 | /// Used for retrieving more in-depth information about individual collections, including real time statistics like floor price. 70 | /// 71 | /// 72 | /// 73 | /// 74 | Task GetCollectionAsync(string collectionSlug, CancellationToken ct = default); 75 | 76 | /// 77 | /// Use this endpoint to fetch stats for a specific collection, including realtime floor price statistics. 78 | /// 79 | /// 80 | /// 81 | /// 82 | Task GetCollectionStatsAsync(string collectionSlug, CancellationToken ct = default); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/OpenSeaClient/Models/Account.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace OpenSeaClient 4 | { 5 | public class Account 6 | { 7 | [JsonProperty("address")] 8 | public string? Address { get; set; } 9 | 10 | [JsonProperty("profile_image_url")] 11 | public string? ProfileImageUrl { get; set; } 12 | 13 | [JsonProperty("user")] 14 | public User? User { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/OpenSeaClient/Models/Asset.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace OpenSeaClient 4 | { 5 | public class Asset 6 | { 7 | [JsonProperty("id")] 8 | public long Id { get; set; } 9 | 10 | [JsonProperty("token_id")] 11 | public string? TokenId { get; set; } 12 | 13 | [JsonProperty("num_sales")] 14 | public long NumSales { get; set; } 15 | 16 | [JsonProperty("image_url")] 17 | public string? ImageUrl { get; set; } 18 | 19 | [JsonProperty("image_preview_url")] 20 | public string? ImagePreviewUrl { get; set; } 21 | 22 | [JsonProperty("image_thumbnail_url")] 23 | public string? ImageThumbnailUrl { get; set; } 24 | 25 | [JsonProperty("animation_url")] 26 | public string? AnimationUrl { get; set; } 27 | 28 | [JsonProperty("animation_original_url")] 29 | public string? AnimationOriginalUrl { get; set; } 30 | 31 | [JsonProperty("background_url")] 32 | public string? BackgroundUrl { get; set; } 33 | 34 | [JsonProperty("name")] 35 | public string? Name { get; set; } 36 | 37 | [JsonProperty("description")] 38 | public string? Description { get; set; } 39 | 40 | [JsonProperty("external_link")] 41 | public string? ExternalLink { get; set; } 42 | 43 | [JsonProperty("token_metadata")] 44 | public string? TokenMetadata { get; set; } 45 | 46 | [JsonProperty("permalink")] 47 | public string? Permalink { get; set; } 48 | 49 | [JsonProperty("traits")] 50 | public List? Traits { get; set; } 51 | 52 | [JsonProperty("asset_contract")] 53 | public AssetContract? AssetContract { get; set; } 54 | 55 | [JsonProperty("collection")] 56 | public Collection? Collection { get; set; } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/OpenSeaClient/Models/AssetBundle.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace OpenSeaClient 4 | { 5 | public class AssetBundle 6 | { 7 | [JsonProperty("maker")] 8 | public Account? Maker { get; set; } 9 | 10 | [JsonProperty("slug")] 11 | public string? Slug { get; set; } 12 | 13 | [JsonProperty("name")] 14 | public string? Name { get; set; } 15 | 16 | [JsonProperty("assets")] 17 | public List? Assets { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/OpenSeaClient/Models/AssetContract.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace OpenSeaClient 4 | { 5 | public class AssetContract 6 | { 7 | [JsonProperty("address")] 8 | public string? Address { get; set; } 9 | 10 | [JsonProperty("asset_contract_type")] 11 | public string? AssetContractType { get; set; } 12 | 13 | [JsonProperty("schema_name")] 14 | public string? SchemaName { get; set; } 15 | 16 | [JsonProperty("name")] 17 | public string? Name { get; set; } 18 | 19 | [JsonProperty("symbol")] 20 | public string? Symbol { get; set; } 21 | 22 | [JsonProperty("image_url")] 23 | public string? ImageUrl { get; set; } 24 | 25 | [JsonProperty("description")] 26 | public string? Description { get; set; } 27 | 28 | [JsonProperty("external_link")] 29 | public string? ExternalLink { get; set; } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/OpenSeaClient/Models/Collection.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace OpenSeaClient 4 | { 5 | public class Collection 6 | { 7 | [JsonProperty("stats")] 8 | public CollectionStats? Stats { get; set; } 9 | 10 | [JsonProperty("banner_image_url")] 11 | public string? BannerImageUrl { get; set; } 12 | 13 | [JsonProperty("chat_rul")] 14 | public string? ChatUrl { get; set; } 15 | 16 | [JsonProperty("created_date")] 17 | public DateTime? CreatedDate { get; set; } 18 | 19 | [JsonProperty("default_to_fiat")] 20 | public bool DefaultToFiat { get; set; } 21 | 22 | [JsonProperty("description")] 23 | public string? Description { get; set; } 24 | 25 | [JsonProperty("dev_buyer_fee_basis_points")] 26 | public string? DevBuyerFeeBasisPoints { get; set; } 27 | 28 | [JsonProperty("dev_seller_fee_basis_points")] 29 | public string? DevSellerFeeBasisPoints { get; set; } 30 | 31 | [JsonProperty("discord_url")] 32 | public string? DiscordUrl { get; set; } 33 | 34 | [JsonProperty("external_url")] 35 | public string? ExternalUrl { get; set; } 36 | 37 | [JsonProperty("featured")] 38 | public bool Featured { get; set; } 39 | 40 | [JsonProperty("featured_image_url")] 41 | public string? FeaturedImageUrl { get; set; } 42 | 43 | [JsonProperty("hidden")] 44 | public bool Hidden { get; set; } 45 | 46 | [JsonProperty("safelist_request_status")] 47 | public string? SafelistRequestStatus { get; set; } 48 | 49 | [JsonProperty("image_url")] 50 | public string? ImageUrl { get; set; } 51 | 52 | [JsonProperty("is_subject_to_whitelist")] 53 | public bool IsSubjectToWhitelist { get; set; } 54 | 55 | [JsonProperty("large_image_url")] 56 | public string? LargeImageUrl { get; set; } 57 | 58 | [JsonProperty("medium_username")] 59 | public string? MediumUsername { get; set; } 60 | 61 | [JsonProperty("name")] 62 | public string? Name { get; set; } 63 | 64 | [JsonProperty("only_proxied_transfers")] 65 | public bool OnlyProxiedTransfers { get; set; } 66 | 67 | [JsonProperty("opensea_buyer_fee_basis_points")] 68 | public string? OpenSeaBuyerFeeBasisPoints { get; set; } 69 | 70 | [JsonProperty("opensea_seller_fee_basis_points")] 71 | public string? OpenSeaSellerFeeBasisPoints { get; set; } 72 | 73 | [JsonProperty("payout_address")] 74 | public string? PayoutAddress { get; set; } 75 | 76 | [JsonProperty("require_email")] 77 | public bool RequireEmail { get; set; } 78 | 79 | [JsonProperty("short_description")] 80 | public string? ShortDescription { get; set; } 81 | 82 | [JsonProperty("slug")] 83 | public string? Slug { get; set; } 84 | 85 | [JsonProperty("telegram_url")] 86 | public string? TelegramUrl { get; set; } 87 | 88 | [JsonProperty("twitter_username")] 89 | public string? TwitterUsername { get; set; } 90 | 91 | [JsonProperty("instagram_username")] 92 | public string? InstagramUsername { get; set; } 93 | 94 | [JsonProperty("wiki_url")] 95 | public string? WikiUrl { get; set; } 96 | 97 | [JsonProperty("primary_asset_contracts")] 98 | public List? AssetContracts { get; set; } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/OpenSeaClient/Models/CollectionStats.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace OpenSeaClient 4 | { 5 | public class CollectionStats 6 | { 7 | [JsonProperty("one_day_volume")] 8 | public double OneDayVolume { get; set; } 9 | 10 | [JsonProperty("one_day_change")] 11 | public double OneDayChange { get; set; } 12 | 13 | [JsonProperty("one_day_sales")] 14 | public double OneDaySales { get; set; } 15 | 16 | [JsonProperty("one_day_average_price")] 17 | public double OneDayAveragePrice { get; set; } 18 | 19 | [JsonProperty("seven_day_volume")] 20 | public double SevenDayVolume { get; set; } 21 | 22 | [JsonProperty("seven_day_change")] 23 | public double SevenDayChange { get; set; } 24 | 25 | [JsonProperty("seven_day_sales")] 26 | public double SevenDaySales { get; set; } 27 | 28 | [JsonProperty("seven_day_average_price")] 29 | public double SevenDayAveragePrice { get; set; } 30 | 31 | [JsonProperty("thirty_day_volume")] 32 | public double ThirtyDayVolume { get; set; } 33 | 34 | [JsonProperty("thirty_day_change")] 35 | public double ThirtyDayChange { get; set; } 36 | 37 | [JsonProperty("thirty_day_sales")] 38 | public double ThirtyDaySales { get; set; } 39 | 40 | [JsonProperty("thirty_day_average_price")] 41 | public double ThirtyDayAveragePrice { get; set; } 42 | 43 | [JsonProperty("total_volume")] 44 | public double TotalVolume { get; set; } 45 | 46 | [JsonProperty("total_sales")] 47 | public double TotalSales { get; set; } 48 | 49 | [JsonProperty("total_supply")] 50 | public double TotalSupply { get; set; } 51 | 52 | [JsonProperty("count")] 53 | public double Count { get; set; } 54 | 55 | [JsonProperty("num_owners")] 56 | public long Owners { get; set; } 57 | 58 | [JsonProperty("average_price")] 59 | public double AveragePrice { get; set; } 60 | 61 | [JsonProperty("num_reports")] 62 | public long Reports { get; set; } 63 | 64 | [JsonProperty("market_cap")] 65 | public double MarketCap { get; set; } 66 | 67 | [JsonProperty("floor_price")] 68 | public double FloorPrice { get; set; } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/OpenSeaClient/Models/Event.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace OpenSeaClient 4 | { 5 | public class Event 6 | { 7 | [JsonProperty("event_type")] 8 | public string? EventType { get; set; } 9 | 10 | [JsonProperty("asset")] 11 | public Asset? Asset { get; set; } 12 | 13 | [JsonProperty("asset_bundle")] 14 | public AssetBundle? AssetBundle { get; set; } 15 | 16 | [JsonProperty("created_date")] 17 | public DateTime? CreatedDate { get; set; } 18 | 19 | [JsonProperty("from_account")] 20 | public Account? FromAccount { get; set; } 21 | 22 | [JsonProperty("to_account")] 23 | public Account? ToAccount { get; set; } 24 | 25 | [JsonProperty("seller")] 26 | public Account? Seller { get; set; } 27 | 28 | [JsonProperty("winner_account")] 29 | public Account? WinnerAccount { get; set; } 30 | 31 | [JsonProperty("is_private")] 32 | public bool? IsPrivate { get; set; } 33 | 34 | //[JsonProperty("payment_token")] 35 | //public string? PaymentToken { get; set; } 36 | 37 | [JsonProperty("total_price")] 38 | public string? TotalPrice { get; set; } 39 | 40 | public decimal? TotalPriceEth { get; set; } 41 | 42 | [JsonProperty("quantity")] 43 | public string? Quantity { get; set; } 44 | 45 | [JsonProperty("collection_slug")] 46 | public string? CollectionSlug { get; set; } 47 | 48 | [JsonProperty("contract_address")] 49 | public string? ContractAddress { get; set; } 50 | 51 | [JsonProperty("transaction")] 52 | public Transaction? Transaction { get; set; } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/OpenSeaClient/Models/Order.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace OpenSeaClient 4 | { 5 | public class Order 6 | { 7 | [JsonProperty("asset")] 8 | public Asset? Asset { get; set; } 9 | 10 | [JsonProperty("created_date")] 11 | public DateTime? CreatedDate { get; set; } 12 | 13 | [JsonProperty("closing_date")] 14 | public DateTime? ClosingDate { get; set; } 15 | 16 | [JsonProperty("expiration_time")] 17 | public long? ExpirationTime { get; set; } 18 | 19 | [JsonProperty("listing_time")] 20 | public long? ListingTime { get; set; } 21 | 22 | [JsonProperty("order_hash")] 23 | public string? OrderHash { get; set; } 24 | 25 | [JsonProperty("exchange")] 26 | public string? Exchange { get; set; } 27 | 28 | [JsonProperty("maker")] 29 | public Account? Maker { get; set; } 30 | 31 | [JsonProperty("taker")] 32 | public Account? Taker { get; set; } 33 | 34 | [JsonProperty("fee_recipient")] 35 | public Account? FeeRecipient { get; set; } 36 | 37 | [JsonProperty("current_price")] 38 | public string? CurrentPrice { get; set; } 39 | 40 | public decimal? CurrentPriceEth { get; set; } 41 | 42 | [JsonProperty("current_bounty")] 43 | public string? CurrentBounty { get; set; } 44 | 45 | [JsonProperty("bounty_multiple")] 46 | public string? BountyMultiple { get; set; } 47 | 48 | [JsonProperty("maker_relayer_fee")] 49 | public string? MakerRelayerFee { get; set; } 50 | 51 | [JsonProperty("taker_relayer_fee")] 52 | public string? TakerRelayerFee { get; set; } 53 | 54 | [JsonProperty("maker_protocol_fee")] 55 | public string? MakerProtocolFee { get; set; } 56 | 57 | [JsonProperty("taker_protocol_fee")] 58 | public string? TakerProtocolFee { get; set; } 59 | 60 | [JsonProperty("maker_referrer_fee")] 61 | public string? MakerReferrerFee { get; set; } 62 | 63 | [JsonProperty("fee_method")] 64 | public int? FeeMethod { get; set; } 65 | 66 | [JsonProperty("side")] 67 | public int? Side { get; set; } 68 | 69 | [JsonProperty("sale_kind")] 70 | public int? SaleKind { get; set; } 71 | 72 | [JsonProperty("target")] 73 | public string? Target { get; set; } 74 | 75 | [JsonProperty("how_to_call")] 76 | public int? HowToCall { get; set; } 77 | 78 | [JsonProperty("calldata")] 79 | public string? Calldata { get; set; } 80 | 81 | [JsonProperty("replacement_pattern")] 82 | public string? ReplacementPattern { get; set; } 83 | 84 | [JsonProperty("static_target")] 85 | public string? StaticTarget { get; set; } 86 | 87 | [JsonProperty("static_extradata")] 88 | public string? StaticExtradata { get; set; } 89 | 90 | [JsonProperty("payment_token")] 91 | public string? PaymentToken { get; set; } 92 | 93 | [JsonProperty("base_price")] 94 | public string? BasePrice { get; set; } 95 | 96 | [JsonProperty("extra")] 97 | public string? Extra { get; set; } 98 | 99 | [JsonProperty("quantity")] 100 | public string? Quantity { get; set; } 101 | 102 | [JsonProperty("salt")] 103 | public string? Salt { get; set; } 104 | 105 | [JsonProperty("v")] 106 | public long? V { get; set; } 107 | 108 | [JsonProperty("r")] 109 | public string? R { get; set; } 110 | 111 | [JsonProperty("s")] 112 | public string? S { get; set; } 113 | 114 | [JsonProperty("approved_on_chain")] 115 | public bool? ApprovedOnChain { get; set; } 116 | 117 | [JsonProperty("cancelled")] 118 | public bool? Cancelled { get; set; } 119 | 120 | [JsonProperty("finalized")] 121 | public bool? Finalized { get; set; } 122 | 123 | [JsonProperty("marked_invalid")] 124 | public bool? MarkedInvalid { get; set; } 125 | 126 | [JsonProperty("prefixed_hash")] 127 | public string? PrefixedHash { get; set; } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/OpenSeaClient/Models/Trait.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace OpenSeaClient 4 | { 5 | public class Trait 6 | { 7 | [JsonProperty("trait_type")] 8 | public string? TraitType { get; set; } 9 | 10 | [JsonProperty("value")] 11 | public string? Value { get; set; } 12 | 13 | [JsonProperty("display_type")] 14 | public string? DisplayType { get; set; } 15 | 16 | [JsonProperty("max_value")] 17 | public string? MaxValue { get; set; } 18 | 19 | [JsonProperty("trait_count")] 20 | public long? TraitCount { get; set; } 21 | 22 | [JsonProperty("order")] 23 | public string? Order { get; set; } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/OpenSeaClient/Models/Transaction.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace OpenSeaClient 4 | { 5 | public class Transaction 6 | { 7 | [JsonProperty("block_hash")] 8 | public string? BlockHash { get; set; } 9 | 10 | [JsonProperty("block_number")] 11 | public string? BlockNumber { get; set; } 12 | 13 | [JsonProperty("from_account")] 14 | public Account? FromAccount { get; set; } 15 | 16 | [JsonProperty("to_account")] 17 | public Account? ToAccount { get; set; } 18 | 19 | [JsonProperty("timestamp")] 20 | public DateTime? Timestamp { get; set; } 21 | 22 | [JsonProperty("transaction_hash")] 23 | public string? TransactionHash { get; set; } 24 | 25 | [JsonProperty("transaction_index")] 26 | public string? TransactionIndex { get; set; } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/OpenSeaClient/Models/User.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace OpenSeaClient 4 | { 5 | public class User 6 | { 7 | [JsonProperty("username")] 8 | public string? Username { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/OpenSeaClient/OpenSeaClient.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | .NET 6 C# SDK for the OpenSea marketplace 8 | Cristi Pufu 9 | OpenSeaClient 10 | OpenSeaClient 11 | opensea;httpclient;sdk;nft;marketplace 12 | https://github.com/ninjastacktech/opensea-net 13 | MIT 14 | git 15 | https://github.com/ninjastacktech/opensea-net 16 | 1.0.9 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/OpenSeaClient/OpenSeaClientExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace OpenSeaClient 2 | { 3 | public static class OpenSeaClientExtensions 4 | { 5 | /// 6 | /// Get assets in batches of 50 with a default 1s delay between API calls to avoid throttling. 7 | /// 8 | /// 9 | /// 10 | /// 11 | /// 12 | public static async Task> GetAllAssetsAsync( 13 | this IOpenSeaClient client, 14 | GetAssetsQueryParams queryParams, 15 | int taskDelayMs = 1000) 16 | { 17 | var count = 0; 18 | var limit = 50; 19 | var it = 0; 20 | 21 | var assetsList = new List(); 22 | 23 | do 24 | { 25 | queryParams.Offset = limit * it; 26 | queryParams.Limit = limit; 27 | 28 | var assets = await client.GetAssetsAsync(queryParams); 29 | 30 | if (assets != null) 31 | { 32 | if (assets.Count > 0) 33 | { 34 | assetsList.AddRange(assets); 35 | } 36 | } 37 | else 38 | { 39 | break; 40 | } 41 | 42 | await Task.Delay(taskDelayMs); 43 | } 44 | while (count == 50); 45 | 46 | return assetsList; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/OpenSeaClient/OpenSeaHttpClient.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json.Linq; 2 | 3 | namespace OpenSeaClient 4 | { 5 | public class OpenSeaHttpClient : IOpenSeaClient 6 | { 7 | public const string ApiKeyHeaderName = "X-Api-Key"; 8 | 9 | private readonly HttpClient _client; 10 | private readonly string _baseUrl = "https://api.opensea.io/api/v1/"; 11 | private readonly string _wyvernUrl = "https://api.opensea.io/wyvern/v1/"; 12 | private readonly string? _apiKey; 13 | 14 | public OpenSeaHttpClient(HttpClient? client = null, string? apiKey = null) 15 | { 16 | _client = client ?? new HttpClient(); 17 | _apiKey = apiKey ?? ""; 18 | } 19 | 20 | public async Task?> GetOrdersAsync(GetOrdersQueryParams? queryParams = null, CancellationToken ct = default) 21 | { 22 | var uriPart = $"orders"; 23 | 24 | List<(string, string)>? param = null; 25 | 26 | if (queryParams != null) 27 | { 28 | param = queryParams.ToQueryParameters(); 29 | } 30 | 31 | var response = await RequestAsync(_wyvernUrl, uriPart, HttpMethod.Get, queryParams: param, ct: ct); 32 | 33 | var jo = JObject.Parse(response); 34 | 35 | var list = new List(); 36 | 37 | if (jo != null) 38 | { 39 | var ja = jo.SelectToken("orders")?.ToArray(); 40 | 41 | if (ja != null) 42 | { 43 | foreach (var ji in ja) 44 | { 45 | var item = ji.ToObject(); 46 | 47 | if (item != null) 48 | { 49 | if (item.CurrentPrice != null) 50 | { 51 | item.CurrentPriceEth = UnitConversion.Convert.FromWei( 52 | BigDecimal.Parse(item.CurrentPrice).Mantissa); 53 | } 54 | 55 | list.Add(item); 56 | } 57 | } 58 | } 59 | } 60 | 61 | return list; 62 | } 63 | 64 | public async Task?> GetEventsAsync(GetEventsQueryParams? queryParams = null, CancellationToken ct = default) 65 | { 66 | var uriPart = $"events"; 67 | 68 | List<(string, string)>? param = null; 69 | 70 | if (queryParams != null) 71 | { 72 | param = queryParams.ToQueryParameters(); 73 | } 74 | 75 | var response = await RequestAsync(_baseUrl, uriPart, HttpMethod.Get, queryParams: param, ct: ct); 76 | 77 | var jo = JObject.Parse(response); 78 | 79 | var list = new List(); 80 | 81 | if (jo != null) 82 | { 83 | var ja = jo.SelectToken("asset_events")?.ToArray(); 84 | 85 | if (ja != null) 86 | { 87 | foreach (var ji in ja) 88 | { 89 | var item = ji.ToObject(); 90 | 91 | if (item != null) 92 | { 93 | if (item.TotalPrice != null) 94 | { 95 | item.TotalPriceEth = UnitConversion.Convert.FromWei( 96 | BigDecimal.Parse(item.TotalPrice).Mantissa); 97 | } 98 | 99 | list.Add(item); 100 | } 101 | } 102 | } 103 | } 104 | 105 | return list; 106 | } 107 | 108 | public async Task?> GetAssetsAsync(GetAssetsQueryParams? queryParams = null, CancellationToken ct = default) 109 | { 110 | var uriPart = $"assets"; 111 | 112 | List<(string, string)>? param = null; 113 | 114 | if (queryParams != null) 115 | { 116 | param = queryParams.ToQueryParameters(); 117 | } 118 | 119 | var response = await RequestAsync(_baseUrl, uriPart, HttpMethod.Get, queryParams: param, ct: ct); 120 | 121 | var jo = JObject.Parse(response); 122 | 123 | var list = new List(); 124 | 125 | if (jo != null) 126 | { 127 | var ja = jo.SelectToken("assets")?.ToArray(); 128 | 129 | if (ja != null) 130 | { 131 | foreach (var ji in ja) 132 | { 133 | var item = ji.ToObject(); 134 | 135 | if (item != null) 136 | { 137 | list.Add(item); 138 | } 139 | } 140 | } 141 | } 142 | 143 | return list; 144 | } 145 | 146 | public async Task GetAssetAsync(string assetContractAddress, string tokenId, string? ownerAddress = null, CancellationToken ct = default) 147 | { 148 | var uriPart = $"asset/{assetContractAddress}/{tokenId}"; 149 | 150 | if (ownerAddress != null) 151 | { 152 | uriPart = $"{uriPart}/?account_address={ownerAddress}"; 153 | } 154 | 155 | var response = await RequestAsync(_baseUrl, uriPart, HttpMethod.Get, ct: ct); 156 | 157 | var jo = JObject.Parse(response); 158 | 159 | return jo?.ToObject(); 160 | } 161 | 162 | public async Task GetAssetContractAsync(string assetContractAddress, CancellationToken ct = default) 163 | { 164 | var uriPart = $"asset_contract/{assetContractAddress}"; 165 | 166 | var response = await RequestAsync(_baseUrl, uriPart, HttpMethod.Get, ct: ct); 167 | 168 | var jo = JObject.Parse(response); 169 | 170 | return jo?.ToObject(); 171 | } 172 | 173 | public async Task?> GetCollectionsAsync(GetCollectionsQueryParams? queryParams = null, CancellationToken ct = default) 174 | { 175 | var uriPart = $"collections"; 176 | 177 | List<(string, string)>? param = null; 178 | 179 | if (queryParams != null) 180 | { 181 | param = queryParams.ToQueryParameters(); 182 | } 183 | 184 | var response = await RequestAsync(_baseUrl, uriPart, HttpMethod.Get, queryParams: param, ct: ct); 185 | 186 | var ja = JArray.Parse(response); 187 | 188 | var list = new List(); 189 | 190 | foreach (var jo in ja) 191 | { 192 | var item = jo.ToObject(); 193 | 194 | if (item != null) 195 | { 196 | list.Add(item); 197 | } 198 | } 199 | 200 | return list; 201 | } 202 | 203 | public async Task GetCollectionAsync(string collectionSlug, CancellationToken ct = default) 204 | { 205 | var response = await RequestAsync(_baseUrl, $"collection/{collectionSlug}", HttpMethod.Get, ct: ct); 206 | 207 | var jo = JObject.Parse(response); 208 | var stats = jo?.SelectToken("collection"); 209 | 210 | return stats?.ToObject(); 211 | } 212 | 213 | public async Task GetCollectionStatsAsync(string collectionSlug, CancellationToken ct = default) 214 | { 215 | var response = await RequestAsync(_baseUrl, $"collection/{collectionSlug}/stats", HttpMethod.Get, ct: ct); 216 | 217 | var jo = JObject.Parse(response); 218 | var stats = jo?.SelectToken("stats"); 219 | 220 | return stats?.ToObject(); 221 | } 222 | 223 | protected async Task RequestAsync( 224 | string baseUrl, 225 | string uriPart, 226 | HttpMethod method, 227 | HttpContent? content = null, 228 | IEnumerable<(string, string)>? queryParams = null, 229 | Dictionary? headers = null, 230 | CancellationToken ct = default) 231 | { 232 | var uri = new Uri(QueryHelpers.AddQueryString($"{baseUrl}{uriPart}", queryParams)); 233 | 234 | using var request = new HttpRequestMessage(method, uri); 235 | 236 | if (!_client.DefaultRequestHeaders.Contains(ApiKeyHeaderName)) 237 | { 238 | request.Headers.Add(ApiKeyHeaderName, _apiKey); 239 | } 240 | 241 | if (headers != null) 242 | { 243 | foreach (var header in headers) 244 | { 245 | request.Headers.Add(header.Key, header.Value); 246 | } 247 | } 248 | 249 | if (content != null) 250 | { 251 | request.Content = content; 252 | } 253 | 254 | using var response = await _client.SendAsync(request, ct); 255 | 256 | response.EnsureSuccessStatusCode(); 257 | 258 | return await response.Content.ReadAsStringAsync(ct); 259 | } 260 | } 261 | } -------------------------------------------------------------------------------- /src/OpenSeaClient/QueryParams/GetAssetsQueryParams.cs: -------------------------------------------------------------------------------- 1 | namespace OpenSeaClient 2 | { 3 | public class GetAssetsQueryParams 4 | { 5 | /// 6 | /// The address of the owner of the assets. 7 | /// 8 | public string? OwnerAddress { get; set; } 9 | 10 | /// 11 | /// An array of token IDs to search for (e.g. ?token_ids=1&token_ids=209). 12 | /// Will return a list of assets with token_id matching any of the IDs in this array. 13 | /// 14 | public List? TokenIds { get; set; } 15 | 16 | /// 17 | /// The NFT contract address for the assets. 18 | /// 19 | public string? AssetContractAddress { get; set; } 20 | 21 | /// 22 | /// An array of contract addresses to search for (e.g. ?asset_contract_addresses=0x1...&asset_contract_addresses=0x2...). 23 | /// Will return a list of assets with contracts matching any of the addresses in this array. 24 | /// If "token_ids" is also specified, then it will only return assets that match each (address, token_id) pairing, respecting order. 25 | /// 26 | public List? AssetContractAddresses { get; set; } 27 | 28 | /// 29 | /// How to order the assets returned. By default, the API returns the fastest ordering. 30 | /// Options you can set are sale_date (the last sale's transaction's timestamp), sale_count (number of sales), and sale_price (the last sale's total_price). 31 | /// 32 | public OrderAssetsBy? OrderBy { get; set; } 33 | 34 | /// 35 | /// Can be asc for ascending or desc for descending. 36 | /// 37 | public OrderDirection? OrderDirection { get; set; } 38 | 39 | /// 40 | /// Offset. 41 | /// 42 | public long? Offset { get; set; } 43 | 44 | /// 45 | /// Limit. Defaults to 20, capped at 50. 46 | /// 47 | public long? Limit { get; set; } 48 | 49 | /// 50 | /// Limit responses to members of a collection. 51 | /// Case sensitive and must match the collection slug exactly. 52 | /// Will return all assets from all contracts in a collection. 53 | /// 54 | public string? CollectionSlug { get; set; } 55 | 56 | internal List<(string, string)> ToQueryParameters() 57 | { 58 | var queryParams = new List<(string, string)>(); 59 | 60 | if (OwnerAddress != null) 61 | { 62 | queryParams.Add(("owner", OwnerAddress)); 63 | } 64 | 65 | if (TokenIds != null) 66 | { 67 | foreach (var tokenId in TokenIds) 68 | { 69 | queryParams.Add(("token_ids", tokenId)); 70 | } 71 | } 72 | 73 | if (AssetContractAddress != null) 74 | { 75 | queryParams.Add(("asset_contract_address", AssetContractAddress)); 76 | } 77 | 78 | if (AssetContractAddresses != null) 79 | { 80 | foreach (var assetContractAddress in AssetContractAddresses) 81 | { 82 | queryParams.Add(("asset_contract_addresses", assetContractAddress)); 83 | } 84 | } 85 | 86 | if (OrderBy != null) 87 | { 88 | var orderByString = OrderBy.GetDescription(); 89 | 90 | if (orderByString != null) 91 | { 92 | queryParams.Add(("order_by", orderByString)); 93 | } 94 | } 95 | 96 | if (OrderDirection != null) 97 | { 98 | var orderDirectionString = OrderDirection.GetDescription(); 99 | 100 | if (orderDirectionString != null) 101 | { 102 | queryParams.Add(("order_direction", orderDirectionString)); 103 | } 104 | } 105 | 106 | if (Offset != null) 107 | { 108 | queryParams.Add(("offset", Offset.Value.ToString())); 109 | } 110 | 111 | if (Limit != null) 112 | { 113 | queryParams.Add(("limit", Limit.Value.ToString())); 114 | } 115 | 116 | if (CollectionSlug != null) 117 | { 118 | queryParams.Add(("collection", CollectionSlug)); 119 | } 120 | 121 | return queryParams; 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/OpenSeaClient/QueryParams/GetCollectionsQueryParams.cs: -------------------------------------------------------------------------------- 1 | namespace OpenSeaClient 2 | { 3 | public class GetCollectionsQueryParams 4 | { 5 | /// 6 | /// A wallet address. If specified, will return collections where the owner owns at least one asset belonging to smart contracts in the collection. 7 | /// The number of assets the account owns is shown as owned_asset_count for each collection. 8 | /// 9 | public string? AssetOwner { get; set; } 10 | 11 | /// 12 | /// For pagination. Number of contracts offset from the beginning of the result list. 13 | /// 14 | public long? Offset { get; set; } 15 | 16 | /// 17 | /// For pagination. Maximum number of contracts to return. 18 | /// 19 | public long? Limit { get; set; } 20 | 21 | internal List<(string, string)> ToQueryParameters() 22 | { 23 | var queryParams = new List<(string, string)>(); 24 | 25 | if (AssetOwner != null) 26 | { 27 | queryParams.Add(("asset_owner", AssetOwner)); 28 | } 29 | 30 | if (Offset != null) 31 | { 32 | queryParams.Add(("offset", Offset.Value.ToString())); 33 | } 34 | 35 | if (Limit != null) 36 | { 37 | queryParams.Add(("limit", Limit.Value.ToString())); 38 | } 39 | 40 | return queryParams; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/OpenSeaClient/QueryParams/GetEventsQueryParams.cs: -------------------------------------------------------------------------------- 1 | namespace OpenSeaClient 2 | { 3 | public class GetEventsQueryParams 4 | { 5 | /// 6 | /// The NFT contract address for the assets for which to show events. 7 | /// 8 | public string? AssetContractAddress { get; set; } 9 | 10 | /// 11 | /// Limit responses to members of a collection. 12 | /// Case sensitive and must match the collection slug exactly. 13 | /// Will return all assets from all contracts in a collection. 14 | /// 15 | public string? CollectionSlug { get; set; } 16 | 17 | /// 18 | /// The token's id to optionally filter by. 19 | /// 20 | public string? TokenId { get; set; } 21 | 22 | /// 23 | /// A user account's wallet address to filter for events on an account. 24 | /// 25 | public string? AccountAddress { get; set; } 26 | 27 | /// 28 | /// The event type to filter. 29 | /// Can be created for new auctions, successful for sales, cancelled, bid_entered, bid_withdrawn, transfer, or approve. 30 | /// 31 | public string? EventType { get; set; } 32 | 33 | /// 34 | /// Restrict to events on OpenSea auctions. Can be true or false. 35 | /// 36 | public bool? OnlyOpenSea { get; set; } 37 | 38 | /// 39 | /// Filter by an auction type. 40 | /// Can be english for English Auctions, dutch for fixed-price and declining-price sell orders (Dutch Auctions), or min-price for CryptoPunks bidding auctions. 41 | /// 42 | public string? AuctionType { get; set; } 43 | 44 | /// 45 | /// Offset. 46 | /// 47 | public long? Offset { get; set; } 48 | 49 | /// 50 | /// Limit. Defaults to 20, capped at 50. 51 | /// 52 | public long? Limit { get; set; } 53 | 54 | /// 55 | /// Only show events listed before this timestamp. Seconds since the Unix epoch. 56 | /// 57 | public long? OccuredBefore { get; set; } 58 | 59 | /// 60 | /// Only show events listed after this timestamp. Seconds since the Unix epoch.. 61 | /// 62 | public long? OccuredAfter { get; set; } 63 | 64 | internal List<(string, string)> ToQueryParameters() 65 | { 66 | var queryParams = new List<(string, string)>(); 67 | 68 | if (AccountAddress != null) 69 | { 70 | queryParams.Add(("account_address", AccountAddress)); 71 | } 72 | 73 | if (TokenId != null) 74 | { 75 | queryParams.Add(("token_id", TokenId)); 76 | } 77 | 78 | if (AssetContractAddress != null) 79 | { 80 | queryParams.Add(("asset_contract_address", AssetContractAddress)); 81 | } 82 | 83 | if (EventType != null) 84 | { 85 | queryParams.Add(("event_type", EventType)); 86 | } 87 | 88 | if (OnlyOpenSea != null) 89 | { 90 | queryParams.Add(("only_opensea", OnlyOpenSea.Value.ToString().ToLower())); 91 | } 92 | 93 | if (Offset != null) 94 | { 95 | queryParams.Add(("offset", Offset.Value.ToString() ?? "0")); 96 | } 97 | 98 | if (Limit != null) 99 | { 100 | queryParams.Add(("limit", Limit.Value.ToString() ?? "20")); 101 | } 102 | 103 | if (CollectionSlug != null) 104 | { 105 | queryParams.Add(("collection_slug", CollectionSlug)); 106 | } 107 | 108 | if (AuctionType != null) 109 | { 110 | queryParams.Add(("auction_type", AuctionType)); 111 | } 112 | 113 | if (OccuredAfter != null) 114 | { 115 | queryParams.Add(("occurred_after", OccuredAfter.Value.ToString())); 116 | } 117 | 118 | if (OccuredBefore != null) 119 | { 120 | queryParams.Add(("auction_before", OccuredBefore.Value.ToString())); 121 | } 122 | 123 | return queryParams; 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/OpenSeaClient/QueryParams/GetOrdersQueryParams.cs: -------------------------------------------------------------------------------- 1 | namespace OpenSeaClient 2 | { 3 | public class GetOrdersQueryParams 4 | { 5 | /// 6 | /// Filter by smart contract address for the asset category. Needs to be defined together with token_id or token_ids. 7 | /// 8 | public string? AssetContractAddress { get; set; } 9 | 10 | /// 11 | /// Filter by the address of the smart contract of the payment token that is accepted or offered by the order. 12 | /// 13 | public string? PaymentTokenAddress { get; set; } 14 | 15 | /// 16 | /// Filter by the order maker's wallet address. 17 | /// 18 | public string? Maker { get; set; } 19 | 20 | /// 21 | /// Filter by the order taker's wallet address. Orders open for any taker have the null address as their taker. 22 | /// 23 | public string? Taker { get; set; } 24 | 25 | /// 26 | /// Filter by the asset owner's wallet address. 27 | /// 28 | public string? OwnerAddress { get; set; } 29 | 30 | /// 31 | /// When "true", only show English Auction sell orders, which wait for the highest bidder. When "false", exclude those. 32 | /// 33 | public bool? IsEnglishAuction { get; set; } 34 | 35 | /// 36 | /// Only show orders for bundles of assets 37 | /// 38 | public bool Bundled { get; set; } 39 | 40 | /// 41 | /// Include orders on bundles where all assets in the bundle share the address provided in asset_contract_address or where the bundle's maker is the address provided in owner. 42 | /// 43 | public bool IncludeBundled { get; set; } 44 | 45 | /// 46 | /// Only show orders listed after this timestamp. Seconds since the Unix epoch. 47 | /// 48 | public long? ListedAfter { get; set; } 49 | 50 | /// 51 | /// Only show orders listed before this timestamp. Seconds since the Unix epoch. 52 | /// 53 | public long? ListedBefore { get; set; } 54 | 55 | /// 56 | /// Filter by the token ID of the order's asset. Needs to be defined together with asset_contract_address. 57 | /// 58 | public string? TokenId { get; set; } 59 | 60 | /// 61 | /// Filter by a list of token IDs for the order's asset. Needs to be defined together with asset_contract_address. 62 | /// 63 | public List? TokenIds { get; set; } 64 | 65 | /// 66 | /// Filter by the side of the order. 0 for buy orders and 1 for sell orders. 67 | /// 68 | public int? Side { get; set; } 69 | 70 | /// 71 | /// Filter by the kind of sell order. 0 for fixed-price sales or min-bid auctions, and 1 for declining-price Dutch Auctions. 72 | /// NOTE: use only_english=true for filtering for only English Auctions. 73 | /// 74 | public int? SaleKind { get; set; } 75 | 76 | /// 77 | /// Offset. 78 | /// 79 | public long? Offset { get; set; } 80 | 81 | /// 82 | /// Limit. Defaults to 20, capped at 50. 83 | /// 84 | public long? Limit { get; set; } 85 | 86 | /// 87 | /// How to sort the orders. 88 | /// Can be created_date for when they were made, or eth_price to see the lowest-priced orders first (converted to their ETH values). 89 | /// eth_price is only supported when asset_contract_address and token_id are also defined. 90 | /// 91 | public OrderOrdersBy? OrderBy { get; set; } 92 | 93 | /// 94 | /// Can be asc or desc for ascending or descending sort. 95 | /// For example, to see the cheapest orders, do order_direction asc and order_by eth_price. 96 | /// 97 | public OrderDirection? OrderDirection { get; set; } 98 | 99 | internal List<(string, string)> ToQueryParameters() 100 | { 101 | var queryParams = new List<(string, string)>(); 102 | 103 | if (AssetContractAddress != null) 104 | { 105 | queryParams.Add(("asset_contract_address", AssetContractAddress)); 106 | } 107 | 108 | if (PaymentTokenAddress != null) 109 | { 110 | queryParams.Add(("payment_token_address", PaymentTokenAddress)); 111 | } 112 | 113 | if (Maker != null) 114 | { 115 | queryParams.Add(("maker", Maker)); 116 | } 117 | 118 | if (Taker != null) 119 | { 120 | queryParams.Add(("taker", Taker)); 121 | } 122 | 123 | if (OwnerAddress != null) 124 | { 125 | queryParams.Add(("owner", OwnerAddress)); 126 | } 127 | 128 | if (IsEnglishAuction != null) 129 | { 130 | queryParams.Add(("is_english", IsEnglishAuction.Value.ToString().ToLower())); 131 | } 132 | 133 | queryParams.Add(("bundled", Bundled.ToString().ToLower())); 134 | 135 | queryParams.Add(("include_bundled", IncludeBundled.ToString().ToLower())); 136 | 137 | if (ListedAfter != null) 138 | { 139 | queryParams.Add(("listed_after", ListedAfter.Value.ToString())); 140 | } 141 | 142 | if (ListedBefore != null) 143 | { 144 | queryParams.Add(("listed_before", ListedBefore.Value.ToString())); 145 | } 146 | 147 | if (TokenId != null) 148 | { 149 | queryParams.Add(("token_id", TokenId)); 150 | } 151 | 152 | if (TokenIds != null) 153 | { 154 | foreach (var tokenId in TokenIds) 155 | { 156 | queryParams.Add(("token_ids", tokenId)); 157 | } 158 | } 159 | 160 | if (Side != null) 161 | { 162 | queryParams.Add(("side", Side.Value.ToString())); 163 | } 164 | 165 | if (SaleKind != null) 166 | { 167 | queryParams.Add(("sale_kind", SaleKind.Value.ToString())); 168 | } 169 | 170 | if (Offset != null) 171 | { 172 | queryParams.Add(("offset", Offset.Value.ToString())); 173 | } 174 | 175 | if (Limit != null) 176 | { 177 | queryParams.Add(("limit", Limit.Value.ToString())); 178 | } 179 | 180 | if (OrderBy != null) 181 | { 182 | var orderByString = OrderBy.GetDescription(); 183 | 184 | if (orderByString != null) 185 | { 186 | queryParams.Add(("order_by", orderByString)); 187 | } 188 | } 189 | 190 | if (OrderDirection != null) 191 | { 192 | var orderDirectionString = OrderDirection.GetDescription(); 193 | 194 | if (orderDirectionString != null) 195 | { 196 | queryParams.Add(("order_direction", orderDirectionString)); 197 | } 198 | } 199 | 200 | return queryParams; 201 | } 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /src/OpenSeaClient/QueryParams/OrderAssetsBy.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | 3 | namespace OpenSeaClient 4 | { 5 | public enum OrderAssetsBy 6 | { 7 | /// 8 | /// The last sale's transaction's timestamp 9 | /// 10 | [Description("sale_date")] 11 | SaleDate, 12 | 13 | /// 14 | /// Number of sales 15 | /// 16 | [Description("sale_count")] 17 | SaleCount, 18 | 19 | /// 20 | /// The last sale's total_price 21 | /// 22 | [Description("sale_price")] 23 | SalePrice, 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/OpenSeaClient/QueryParams/OrderDirection.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | 3 | namespace OpenSeaClient 4 | { 5 | public enum OrderDirection 6 | { 7 | [Description("asc")] 8 | Asc, 9 | 10 | [Description("desc")] 11 | Desc, 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/OpenSeaClient/QueryParams/OrderOrdersBy.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | 3 | namespace OpenSeaClient 4 | { 5 | public enum OrderOrdersBy 6 | { 7 | /// 8 | /// When the order was made. 9 | /// 10 | [Description("created_date")] 11 | CreatedDate, 12 | 13 | /// 14 | /// See the lowest-priced orders first (converted to their ETH values). 15 | /// eth_price is only supported when asset_contract_address and token_id are also defined. 16 | /// 17 | [Description("eth_price")] 18 | EthPrice, 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/OpenSeaClient/UnitConversion/BigDecimal.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Numerics; 3 | 4 | #nullable disable 5 | 6 | namespace OpenSeaClient 7 | { 8 | /// BigNumber based on the original http://uberscraper.blogspot.co.uk/2013/09/c-bigdecimal-class-from-stackoverflow.html 9 | /// which was inspired by http://stackoverflow.com/a/4524254 10 | /// Original Author: Jan Christoph Bernack (contact: jc.bernack at googlemail.com) 11 | /// Changes JB: Added parse, Fix Normalise, Added Floor, New ToString, Change Equals (normalise to validate first), Change Casting to avoid overflows (even if might be slower), Added Normalise Bigger than zero, test on operations, parsing, casting, and other test coverage for ethereum unit conversions 12 | /// Changes KJ: Added Culture formatting 13 | /// http://stackoverflow.com/a/13813535/956364" /> 14 | /// 15 | /// Arbitrary precision Decimal. 16 | /// All operations are exact, except for division. 17 | /// Division never determines more digits than the given precision of 50. 18 | /// 19 | internal struct BigDecimal : IComparable, IComparable 20 | { 21 | /// 22 | /// Sets the maximum precision of division operations. 23 | /// If AlwaysTruncate is set to true all operations are affected. 24 | /// 25 | public const int Precision = 50; 26 | 27 | public BigDecimal(BigDecimal bigDecimal, bool alwaysTruncate = false) : this(bigDecimal.Mantissa, 28 | bigDecimal.Exponent, alwaysTruncate) 29 | { 30 | } 31 | 32 | public BigDecimal(decimal value, bool alwaysTruncate = false) : this((BigDecimal)value, alwaysTruncate) 33 | { 34 | } 35 | 36 | /// 37 | /// 38 | /// 39 | /// 40 | /// The number of decimal units for example (-18). A positive value will be normalised as 10 ^ 41 | /// exponent 42 | /// 43 | /// 44 | /// Specifies whether the significant digits should be truncated to the given precision after 45 | /// each operation. 46 | /// 47 | public BigDecimal(BigInteger mantissa, int exponent, bool alwaysTruncate = false) : this() 48 | { 49 | Mantissa = mantissa; 50 | Exponent = exponent; 51 | NormaliseExponentBiggerThanZero(); 52 | Normalize(); 53 | if (alwaysTruncate) 54 | Truncate(); 55 | } 56 | 57 | public BigInteger Mantissa { get; internal set; } 58 | public int Exponent { get; internal set; } 59 | 60 | public int CompareTo(object obj) 61 | { 62 | if (ReferenceEquals(obj, null) || !(obj is BigDecimal)) 63 | throw new ArgumentException(); 64 | return CompareTo((BigDecimal)obj); 65 | } 66 | 67 | public int CompareTo(BigDecimal other) 68 | { 69 | return this < other ? -1 : (this > other ? 1 : 0); 70 | } 71 | 72 | public void NormaliseExponentBiggerThanZero() 73 | { 74 | if (Exponent > 0) 75 | { 76 | Mantissa = Mantissa * BigInteger.Pow(10, Exponent); 77 | Exponent = 0; 78 | } 79 | } 80 | 81 | /// 82 | /// Removes trailing zeros on the mantissa 83 | /// 84 | public void Normalize() 85 | { 86 | if (Exponent == 0) return; 87 | 88 | if (Mantissa.IsZero) 89 | { 90 | Exponent = 0; 91 | } 92 | else 93 | { 94 | BigInteger remainder = 0; 95 | while (remainder == 0) 96 | { 97 | var shortened = BigInteger.DivRem(Mantissa, 10, out remainder); 98 | if (remainder != 0) 99 | continue; 100 | Mantissa = shortened; 101 | Exponent++; 102 | } 103 | 104 | NormaliseExponentBiggerThanZero(); 105 | } 106 | } 107 | 108 | /// 109 | /// Truncate the number to the given precision by removing the least significant digits. 110 | /// 111 | /// The truncated number 112 | internal BigDecimal Truncate(int precision = Precision) 113 | { 114 | // copy this instance (remember its a struct) 115 | var shortened = this; 116 | // save some time because the number of digits is not needed to remove trailing zeros 117 | shortened.Normalize(); 118 | // remove the least significant digits, as long as the number of digits is higher than the given Precision 119 | while (shortened.Mantissa.NumberOfDigits() > precision) 120 | { 121 | shortened.Mantissa /= 10; 122 | shortened.Exponent++; 123 | } 124 | 125 | return shortened; 126 | } 127 | 128 | /// 129 | /// Rounds the number to the specified amount of significant digits. 130 | /// Midpoints (like 0.5 or -0.5) are rounded away from 0 (e.g. to 1 and -1 respectively). 131 | /// 132 | public BigDecimal RoundAwayFromZero(int significantDigits) 133 | { 134 | if (significantDigits < 0 || significantDigits > 2_000_000_000) 135 | throw new ArgumentOutOfRangeException(paramName: nameof(significantDigits)); 136 | 137 | if (Exponent >= -significantDigits) return this; 138 | 139 | bool negative = this.Mantissa < 0; 140 | var shortened = negative ? -this : this; 141 | shortened.Normalize(); 142 | 143 | while (shortened.Exponent < -significantDigits) 144 | { 145 | shortened.Mantissa = BigInteger.DivRem(shortened.Mantissa, 10, out var rem); 146 | shortened.Mantissa += rem >= 5 ? +1 : 0; 147 | shortened.Exponent++; 148 | } 149 | 150 | return negative ? -shortened : shortened; 151 | } 152 | 153 | /// 154 | /// Truncate the number, removing all decimal digits. 155 | /// 156 | /// The truncated number 157 | public BigDecimal Floor() 158 | { 159 | return Truncate(Mantissa.NumberOfDigits() + Exponent); 160 | } 161 | 162 | private static int NumberOfDigits(BigInteger value) 163 | { 164 | return value.NumberOfDigits(); 165 | } 166 | 167 | public override string ToString() 168 | { 169 | Normalize(); 170 | bool isNegative = Mantissa < 0; 171 | 172 | var s = BigInteger.Abs(Mantissa).ToString(); 173 | if (Exponent != 0) 174 | { 175 | var decimalPos = s.Length + Exponent; 176 | if (decimalPos < s.Length) 177 | if (decimalPos >= 0) 178 | s = s.Insert(decimalPos, decimalPos == 0 ? "0." : "."); 179 | else 180 | s = "0." + s.PadLeft(decimalPos * -1 + s.Length, '0'); 181 | else 182 | s = s.PadRight(decimalPos, '0'); 183 | } 184 | 185 | return isNegative ? $"-{s}" : s; 186 | } 187 | 188 | public bool Equals(BigDecimal other) 189 | { 190 | var first = this; 191 | var second = other; 192 | first.Normalize(); 193 | second.Normalize(); 194 | return second.Mantissa.Equals(first.Mantissa) && second.Exponent == first.Exponent; 195 | } 196 | 197 | public override bool Equals(object obj) 198 | { 199 | if (ReferenceEquals(null, obj)) 200 | return false; 201 | return obj is BigDecimal && Equals((BigDecimal)obj); 202 | } 203 | 204 | public override int GetHashCode() 205 | { 206 | unchecked 207 | { 208 | return (Mantissa.GetHashCode() * 397) ^ Exponent; 209 | } 210 | } 211 | 212 | #region Conversions 213 | 214 | public static implicit operator BigDecimal(int value) 215 | { 216 | return new BigDecimal(value, 0); 217 | } 218 | 219 | public static implicit operator BigDecimal(BigInteger value) 220 | { 221 | return new BigDecimal(value, 0); 222 | } 223 | 224 | public static implicit operator BigDecimal(double value) 225 | { 226 | var mantissa = (long)value; 227 | var exponent = 0; 228 | double scaleFactor = 1; 229 | while (Math.Abs(value * scaleFactor - (double)mantissa) > 0) 230 | { 231 | exponent -= 1; 232 | scaleFactor *= 10; 233 | mantissa = (long)(value * scaleFactor); 234 | } 235 | 236 | return new BigDecimal(mantissa, exponent); 237 | } 238 | 239 | public static implicit operator BigDecimal(decimal value) 240 | { 241 | var mantissa = (BigInteger)value; 242 | var exponent = 0; 243 | decimal scaleFactor = 1; 244 | while ((decimal)mantissa != value * scaleFactor) 245 | { 246 | exponent -= 1; 247 | scaleFactor *= 10; 248 | mantissa = (BigInteger)(value * scaleFactor); 249 | } 250 | 251 | return new BigDecimal(mantissa, exponent); 252 | } 253 | 254 | public static explicit operator double(BigDecimal value) 255 | { 256 | return double.Parse(value.ToString(), CultureInfo.InvariantCulture); 257 | } 258 | 259 | public static explicit operator float(BigDecimal value) 260 | { 261 | return float.Parse(value.ToString(), CultureInfo.InvariantCulture); 262 | } 263 | 264 | public static explicit operator decimal(BigDecimal value) 265 | { 266 | return decimal.Parse(value.ToString(), CultureInfo.InvariantCulture); 267 | } 268 | 269 | public static explicit operator int(BigDecimal value) 270 | { 271 | return Convert.ToInt32((decimal)value); 272 | } 273 | 274 | public static explicit operator uint(BigDecimal value) 275 | { 276 | return Convert.ToUInt32((decimal)value); 277 | } 278 | 279 | #endregion 280 | 281 | #region Operators 282 | 283 | public static BigDecimal operator +(BigDecimal value) 284 | { 285 | return value; 286 | } 287 | 288 | public static BigDecimal operator -(BigDecimal value) 289 | { 290 | value.Mantissa *= -1; 291 | return value; 292 | } 293 | 294 | public static BigDecimal operator ++(BigDecimal value) 295 | { 296 | return value + 1; 297 | } 298 | 299 | public static BigDecimal operator --(BigDecimal value) 300 | { 301 | return value - 1; 302 | } 303 | 304 | public static BigDecimal operator +(BigDecimal left, BigDecimal right) 305 | { 306 | return Add(left, right); 307 | } 308 | 309 | public static BigDecimal operator -(BigDecimal left, BigDecimal right) 310 | { 311 | return Add(left, -right); 312 | } 313 | 314 | private static BigDecimal Add(BigDecimal left, BigDecimal right) 315 | { 316 | return left.Exponent > right.Exponent 317 | ? new BigDecimal(AlignExponent(left, right) + right.Mantissa, right.Exponent) 318 | : new BigDecimal(AlignExponent(right, left) + left.Mantissa, left.Exponent); 319 | } 320 | 321 | public static BigDecimal operator *(BigDecimal left, BigDecimal right) 322 | { 323 | return new BigDecimal(left.Mantissa * right.Mantissa, left.Exponent + right.Exponent); 324 | } 325 | 326 | public static BigDecimal operator /(BigDecimal dividend, BigDecimal divisor) 327 | { 328 | var exponentChange = Precision - (NumberOfDigits(dividend.Mantissa) - NumberOfDigits(divisor.Mantissa)); 329 | if (exponentChange < 0) 330 | exponentChange = 0; 331 | dividend.Mantissa *= BigInteger.Pow(10, exponentChange); 332 | return new BigDecimal(dividend.Mantissa / divisor.Mantissa, 333 | dividend.Exponent - divisor.Exponent - exponentChange); 334 | } 335 | 336 | public static bool operator ==(BigDecimal left, BigDecimal right) 337 | { 338 | return left.Exponent == right.Exponent && left.Mantissa == right.Mantissa; 339 | } 340 | 341 | public static bool operator !=(BigDecimal left, BigDecimal right) 342 | { 343 | return left.Exponent != right.Exponent || left.Mantissa != right.Mantissa; 344 | } 345 | 346 | public static bool operator <(BigDecimal left, BigDecimal right) 347 | { 348 | return left.Exponent > right.Exponent 349 | ? AlignExponent(left, right) < right.Mantissa 350 | : left.Mantissa < AlignExponent(right, left); 351 | } 352 | 353 | public static bool operator >(BigDecimal left, BigDecimal right) 354 | { 355 | return left.Exponent > right.Exponent 356 | ? AlignExponent(left, right) > right.Mantissa 357 | : left.Mantissa > AlignExponent(right, left); 358 | } 359 | 360 | public static bool operator <=(BigDecimal left, BigDecimal right) 361 | { 362 | return left.Exponent > right.Exponent 363 | ? AlignExponent(left, right) <= right.Mantissa 364 | : left.Mantissa <= AlignExponent(right, left); 365 | } 366 | 367 | public static bool operator >=(BigDecimal left, BigDecimal right) 368 | { 369 | return left.Exponent > right.Exponent 370 | ? AlignExponent(left, right) >= right.Mantissa 371 | : left.Mantissa >= AlignExponent(right, left); 372 | } 373 | 374 | public static BigDecimal Parse(string value) 375 | { 376 | //todo culture format 377 | var decimalCharacter = "."; 378 | var indexOfDecimal = value.IndexOf("."); 379 | var exponent = 0; 380 | if (indexOfDecimal != -1) 381 | exponent = (value.Length - (indexOfDecimal + 1)) * -1; 382 | var mantissa = BigInteger.Parse(value.Replace(decimalCharacter, "")); 383 | return new BigDecimal(mantissa, exponent); 384 | } 385 | 386 | /// 387 | /// Returns the mantissa of value, aligned to the exponent of reference. 388 | /// Assumes the exponent of value is larger than of value. 389 | /// 390 | private static BigInteger AlignExponent(BigDecimal value, BigDecimal reference) 391 | { 392 | return value.Mantissa * BigInteger.Pow(10, value.Exponent - reference.Exponent); 393 | } 394 | 395 | #endregion 396 | 397 | #region Additional mathematical functions 398 | 399 | public static BigDecimal Exp(double exponent) 400 | { 401 | var tmp = (BigDecimal)1; 402 | while (Math.Abs(exponent) > 100) 403 | { 404 | var diff = exponent > 0 ? 100 : -100; 405 | tmp *= Math.Exp(diff); 406 | exponent -= diff; 407 | } 408 | 409 | return tmp * Math.Exp(exponent); 410 | } 411 | 412 | public static BigDecimal Pow(double basis, double exponent) 413 | { 414 | var tmp = (BigDecimal)1; 415 | while (Math.Abs(exponent) > 100) 416 | { 417 | var diff = exponent > 0 ? 100 : -100; 418 | tmp *= Math.Pow(basis, diff); 419 | exponent -= diff; 420 | } 421 | 422 | return tmp * Math.Pow(basis, exponent); 423 | } 424 | 425 | #endregion 426 | 427 | #region Formatting 428 | 429 | public string ToString(string formatSpecifier, IFormatProvider format) 430 | { 431 | char fmt = NumberFormatting.ParseFormatSpecifier(formatSpecifier, out int digits); 432 | if (fmt != 'c' && fmt != 'C') 433 | throw new NotImplementedException(); 434 | 435 | Normalize(); 436 | if (Exponent == 0) 437 | return Mantissa.ToString(formatSpecifier, format); 438 | 439 | var numberFormatInfo = NumberFormatInfo.GetInstance(format); 440 | return BigDecimalFormatter.ToCurrencyString(this, digits, numberFormatInfo); 441 | } 442 | 443 | #endregion 444 | } 445 | } -------------------------------------------------------------------------------- /src/OpenSeaClient/UnitConversion/BigDecimalFormatter.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Text; 3 | 4 | namespace OpenSeaClient 5 | { 6 | internal static class BigDecimalFormatter 7 | { 8 | public static string ToCurrencyString(this BigDecimal value, int maxDigits, NumberFormatInfo format) 9 | { 10 | value.Normalize(); 11 | 12 | if (maxDigits < 0) 13 | maxDigits = format.CurrencyDecimalDigits; 14 | 15 | BigDecimal rounded = value.RoundAwayFromZero(significantDigits: maxDigits); 16 | var digits = rounded.GetDigits(out int exponent); 17 | var result = new StringBuilder(); 18 | NumberFormatting.FormatCurrency(result, 19 | rounded.Mantissa < 0, digits, exponent, 20 | maxDigits: maxDigits, info: format); 21 | return result.ToString(); 22 | } 23 | 24 | internal static IList GetDigits(this BigDecimal value, out int exponent) 25 | { 26 | var nonNegativeMantissa = value.Mantissa < 0 ? -value.Mantissa : value.Mantissa; 27 | var result = new List(); 28 | while (nonNegativeMantissa > 0) 29 | { 30 | result.Add((byte)(nonNegativeMantissa % 10 + '0')); 31 | nonNegativeMantissa /= 10; 32 | } 33 | result.Reverse(); 34 | exponent = value.Exponent; 35 | return result; 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /src/OpenSeaClient/UnitConversion/BigIntegerExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Numerics; 3 | 4 | namespace OpenSeaClient 5 | { 6 | internal static class BigIntegerExtensions 7 | { 8 | internal static int NumberOfDigits(this BigInteger value) 9 | { 10 | return (value * value.Sign).ToString().Length; 11 | } 12 | 13 | internal static BigInteger ParseInvariant(string value) 14 | { 15 | if (value == null) throw new ArgumentNullException(nameof(value)); 16 | 17 | return BigInteger.Parse(value, CultureInfo.InvariantCulture); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/OpenSeaClient/UnitConversion/FormattingExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | 3 | namespace OpenSeaClient 4 | { 5 | internal static class FormattingExtensions 6 | { 7 | /// 8 | /// Converts formattable value to string in a culture-independent way. 9 | /// 10 | public static string ToStringInvariant(this T formattable) where T : IFormattable 11 | { 12 | if (formattable == null) throw new ArgumentNullException(nameof(formattable)); 13 | 14 | return formattable.ToString(null, CultureInfo.InvariantCulture); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/OpenSeaClient/UnitConversion/NumberFormatting.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Globalization; 3 | using System.Text; 4 | 5 | namespace OpenSeaClient 6 | { 7 | internal static class NumberFormatting 8 | { 9 | private static readonly string[] s_posCurrencyFormats = 10 | { 11 | "$#", "#$", "$ #", "# $" 12 | }; 13 | 14 | private static readonly string[] s_negCurrencyFormats = 15 | { 16 | "($#)", "-$#", "$-#", "$#-", 17 | "(#$)", "-#$", "#-$", "#$-", 18 | "-# $", "-$ #", "# $-", "$ #-", 19 | "$ -#", "#- $", "($ #)", "(# $)" 20 | }; 21 | 22 | internal static char ParseFormatSpecifier(string format, out int digits) 23 | { 24 | char c = default; 25 | if (format.Length > 0) 26 | { 27 | // If the format begins with a symbol, see if it's a standard format 28 | // with or without a specified number of digits. 29 | c = format[0]; 30 | if ((uint)(c - 'A') <= 'Z' - 'A' || 31 | (uint)(c - 'a') <= 'z' - 'a') 32 | { 33 | // Fast path for sole symbol, e.g. "D" 34 | if (format.Length == 1) 35 | { 36 | digits = -1; 37 | return c; 38 | } 39 | 40 | if (format.Length == 2) 41 | { 42 | // Fast path for symbol and single digit, e.g. "X4" 43 | int d = format[1] - '0'; 44 | if ((uint)d < 10) 45 | { 46 | digits = d; 47 | return c; 48 | } 49 | } 50 | else if (format.Length == 3) 51 | { 52 | // Fast path for symbol and double digit, e.g. "F12" 53 | int d1 = format[1] - '0', d2 = format[2] - '0'; 54 | if ((uint)d1 < 10 && (uint)d2 < 10) 55 | { 56 | digits = d1 * 10 + d2; 57 | return c; 58 | } 59 | } 60 | 61 | // Fallback for symbol and any length digits. The digits value must be >= 0 && <= 99, 62 | // but it can begin with any number of 0s, and thus we may need to check more than two 63 | // digits. Further, for compat, we need to stop when we hit a null char. 64 | int n = 0; 65 | int i = 1; 66 | while (i < format.Length && (((uint)format[i] - '0') < 10) && n < 10) 67 | { 68 | n = (n * 10) + format[i++] - '0'; 69 | } 70 | 71 | // If we're at the end of the digits rather than having stopped because we hit something 72 | // other than a digit or overflowed, return the standard format info. 73 | if (i == format.Length || format[i] == '\0') 74 | { 75 | digits = n; 76 | return c; 77 | } 78 | } 79 | } 80 | 81 | // Default empty format to be "G"; custom format is signified with '\0'. 82 | digits = -1; 83 | return format.Length == 0 || c == '\0' 84 | ? // For compat, treat '\0' as the end of the specifier, even if the specifier extends beyond it. 85 | 'G' 86 | : '\0'; 87 | } 88 | 89 | internal static void FormatCurrency(StringBuilder result, 90 | bool isNegative, IList digits, int exponent, 91 | int maxDigits, NumberFormatInfo info) 92 | { 93 | string fmt = isNegative 94 | ? s_negCurrencyFormats[info.CurrencyNegativePattern] 95 | : s_posCurrencyFormats[info.CurrencyPositivePattern]; 96 | 97 | foreach (char ch in fmt) 98 | { 99 | switch (ch) 100 | { 101 | case '#': 102 | FormatFixed(result, digits, exponent, 103 | maxFractionalDigits: maxDigits, 104 | info.CurrencyGroupSizes, 105 | decimalSeparator: info.CurrencyDecimalSeparator, 106 | groupSeparator: info.CurrencyGroupSeparator); 107 | break; 108 | case '-': 109 | result.Append(info.NegativeSign); 110 | break; 111 | case '$': 112 | result.Append(info.CurrencySymbol); 113 | break; 114 | default: 115 | result.Append(ch); 116 | break; 117 | } 118 | } 119 | } 120 | 121 | internal static void FormatFixed(StringBuilder sb, IList digits, int exponent, 122 | int maxFractionalDigits, 123 | int[] groupDigits, string decimalSeparator, string groupSeparator) 124 | { 125 | int digPos = digits.Count + exponent; 126 | int digitIndex = 0; 127 | 128 | if (digPos > 0) 129 | { 130 | if (groupDigits != null) 131 | { 132 | Debug.Assert(groupSeparator != null, "Must be null when groupDigits != null"); 133 | int groupSizeIndex = 0; // Index into the groupDigits array. 134 | int bufferSize = digPos; // The length of the result buffer string. 135 | int groupSize = 0; // The current group size. 136 | 137 | // Find out the size of the string buffer for the result. 138 | if (groupDigits.Length != 0) // You can pass in 0 length arrays 139 | { 140 | int groupSizeCount = groupDigits[groupSizeIndex]; // The current total of group size. 141 | 142 | while (digPos > groupSizeCount) 143 | { 144 | groupSize = groupDigits[groupSizeIndex]; 145 | if (groupSize == 0) 146 | break; 147 | 148 | bufferSize += groupSeparator.Length; 149 | if (groupSizeIndex < groupDigits.Length - 1) 150 | groupSizeIndex++; 151 | 152 | groupSizeCount += groupDigits[groupSizeIndex]; 153 | if (groupSizeCount < 0 || bufferSize < 0) 154 | throw new ArgumentOutOfRangeException(); // If we overflow 155 | } 156 | 157 | groupSize = groupSizeCount == 0 158 | ? 0 159 | : groupDigits[0]; // If you passed in an array with one entry as 0, groupSizeCount == 0 160 | } 161 | 162 | groupSizeIndex = 0; 163 | int digitCount = 0; 164 | //int digLength = number.DigitsCount; 165 | int digLength = digits.Count; 166 | int digStart = (digPos < digLength) ? digPos : digLength; 167 | char[] spanPtr = new char[bufferSize]; 168 | int p = bufferSize - 1; 169 | for (int i = digPos - 1; i >= 0; i--) 170 | { 171 | spanPtr[p--] = (i < digStart) ? (char)(digits[digitIndex + i]) : '0'; 172 | 173 | if (groupSize > 0) 174 | { 175 | digitCount++; 176 | if ((digitCount == groupSize) && (i != 0)) 177 | { 178 | for (int j = groupSeparator.Length - 1; j >= 0; j--) 179 | spanPtr[p--] = groupSeparator[j]; 180 | 181 | if (groupSizeIndex < groupDigits.Length - 1) 182 | { 183 | groupSizeIndex++; 184 | groupSize = groupDigits[groupSizeIndex]; 185 | } 186 | 187 | digitCount = 0; 188 | } 189 | } 190 | } 191 | 192 | sb.Append(spanPtr); 193 | 194 | Debug.Assert(p >= -1, "Underflow"); 195 | digitIndex += digStart; 196 | } 197 | else 198 | { 199 | do 200 | { 201 | sb.Append(digitIndex < digits.Count ? (char)digits[digitIndex++] : '0'); 202 | } while (--digPos > 0); 203 | } 204 | } 205 | else 206 | { 207 | sb.Append('0'); 208 | } 209 | 210 | if (maxFractionalDigits > 0) 211 | { 212 | Debug.Assert(decimalSeparator != null); 213 | sb.Append(decimalSeparator); 214 | if ((digPos < 0) && (maxFractionalDigits > 0)) 215 | { 216 | int zeroes = Math.Min(-digPos, maxFractionalDigits); 217 | sb.Append('0', zeroes); 218 | digPos += zeroes; 219 | maxFractionalDigits -= zeroes; 220 | } 221 | 222 | while (maxFractionalDigits > 0) 223 | { 224 | sb.Append((digitIndex < digits.Count) ? (char)digits[digitIndex++] : '0'); 225 | maxFractionalDigits--; 226 | } 227 | } 228 | } 229 | } 230 | } -------------------------------------------------------------------------------- /src/OpenSeaClient/UnitConversion/UnitConversion.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | 3 | namespace OpenSeaClient 4 | { 5 | internal class UnitConversion 6 | { 7 | internal enum EthUnit 8 | { 9 | Wei, 10 | Kwei, 11 | Ada, 12 | Femtoether, 13 | Mwei, 14 | Babbage, 15 | Picoether, 16 | Gwei, 17 | Shannon, 18 | Nanoether, 19 | Nano, 20 | Szabo, 21 | Microether, 22 | Micro, 23 | Finney, 24 | Milliether, 25 | Milli, 26 | Ether, 27 | Kether, 28 | Grand, 29 | Einstein, 30 | Mether, 31 | Gether, 32 | Tether 33 | } 34 | 35 | private static UnitConversion? _convert; 36 | 37 | public static UnitConversion Convert 38 | { 39 | get 40 | { 41 | if (_convert == null) _convert = new UnitConversion(); 42 | return _convert; 43 | } 44 | } 45 | 46 | /// 47 | /// Converts from wei to a unit, NOTE: When the total number of digits is bigger than 29 they will be rounded the less 48 | /// significant digits 49 | /// 50 | public decimal FromWei(BigInteger value, BigInteger toUnit) 51 | { 52 | return FromWei(value, GetEthUnitValueLength(toUnit)); 53 | } 54 | 55 | /// 56 | /// Converts from wei to a unit, NOTE: When the total number of digits is bigger than 29 they will be rounded the less 57 | /// significant digits 58 | /// 59 | public decimal FromWei(BigInteger value, EthUnit toUnit = EthUnit.Ether) 60 | { 61 | return FromWei(value, GetEthUnitValue(toUnit)); 62 | } 63 | 64 | /// 65 | /// Converts from wei to a unit, NOTE: When the total number of digits is bigger than 29 they will be rounded the less 66 | /// significant digits 67 | /// 68 | public decimal FromWei(BigInteger value, int decimalPlacesToUnit) 69 | { 70 | return (decimal)new BigDecimal(value, decimalPlacesToUnit * -1); 71 | } 72 | 73 | public BigDecimal FromWeiToBigDecimal(BigInteger value, int decimalPlacesToUnit) 74 | { 75 | return new BigDecimal(value, decimalPlacesToUnit * -1); 76 | } 77 | 78 | public BigDecimal FromWeiToBigDecimal(BigInteger value, EthUnit toUnit = EthUnit.Ether) 79 | { 80 | return FromWeiToBigDecimal(value, GetEthUnitValue(toUnit)); 81 | } 82 | 83 | public BigDecimal FromWeiToBigDecimal(BigInteger value, BigInteger toUnit) 84 | { 85 | return FromWeiToBigDecimal(value, GetEthUnitValueLength(toUnit)); 86 | } 87 | 88 | private int GetEthUnitValueLength(BigInteger unitValue) 89 | { 90 | return unitValue.ToStringInvariant().Length - 1; 91 | } 92 | 93 | public BigInteger GetEthUnitValue(EthUnit ethUnit) 94 | { 95 | switch (ethUnit) 96 | { 97 | case EthUnit.Wei: 98 | return BigIntegerExtensions.ParseInvariant("1"); 99 | case EthUnit.Kwei: 100 | return BigIntegerExtensions.ParseInvariant("1000"); 101 | case EthUnit.Babbage: 102 | return BigIntegerExtensions.ParseInvariant("1000"); 103 | case EthUnit.Femtoether: 104 | return BigIntegerExtensions.ParseInvariant("1000"); 105 | case EthUnit.Mwei: 106 | return BigIntegerExtensions.ParseInvariant("1000000"); 107 | case EthUnit.Picoether: 108 | return BigIntegerExtensions.ParseInvariant("1000000"); 109 | case EthUnit.Gwei: 110 | return BigIntegerExtensions.ParseInvariant("1000000000"); 111 | case EthUnit.Shannon: 112 | return BigIntegerExtensions.ParseInvariant("1000000000"); 113 | case EthUnit.Nanoether: 114 | return BigIntegerExtensions.ParseInvariant("1000000000"); 115 | case EthUnit.Nano: 116 | return BigIntegerExtensions.ParseInvariant("1000000000"); 117 | case EthUnit.Szabo: 118 | return BigIntegerExtensions.ParseInvariant("1000000000000"); 119 | case EthUnit.Microether: 120 | return BigIntegerExtensions.ParseInvariant("1000000000000"); 121 | case EthUnit.Micro: 122 | return BigIntegerExtensions.ParseInvariant("1000000000000"); 123 | case EthUnit.Finney: 124 | return BigIntegerExtensions.ParseInvariant("1000000000000000"); 125 | case EthUnit.Milliether: 126 | return BigIntegerExtensions.ParseInvariant("1000000000000000"); 127 | case EthUnit.Milli: 128 | return BigIntegerExtensions.ParseInvariant("1000000000000000"); 129 | case EthUnit.Ether: 130 | return BigIntegerExtensions.ParseInvariant("1000000000000000000"); 131 | case EthUnit.Kether: 132 | return BigIntegerExtensions.ParseInvariant("1000000000000000000000"); 133 | case EthUnit.Grand: 134 | return BigIntegerExtensions.ParseInvariant("1000000000000000000000"); 135 | case EthUnit.Einstein: 136 | return BigIntegerExtensions.ParseInvariant("1000000000000000000000"); 137 | case EthUnit.Mether: 138 | return BigIntegerExtensions.ParseInvariant("1000000000000000000000000"); 139 | case EthUnit.Gether: 140 | return BigIntegerExtensions.ParseInvariant("1000000000000000000000000000"); 141 | case EthUnit.Tether: 142 | return BigIntegerExtensions.ParseInvariant("1000000000000000000000000000000"); 143 | } 144 | 145 | throw new NotImplementedException(); 146 | } 147 | 148 | 149 | public bool TryValidateUnitValue(BigInteger ethUnit) 150 | { 151 | if (ethUnit.ToStringInvariant().Trim('0') == "1") return true; 152 | throw new Exception("Invalid unit value, it should be a power of 10 "); 153 | } 154 | 155 | public BigInteger ToWeiFromUnit(decimal amount, BigInteger fromUnit) 156 | { 157 | return ToWeiFromUnit((BigDecimal)amount, fromUnit); 158 | } 159 | 160 | public BigInteger ToWeiFromUnit(BigDecimal amount, BigInteger fromUnit) 161 | { 162 | TryValidateUnitValue(fromUnit); 163 | var bigDecimalFromUnit = new BigDecimal(fromUnit, 0); 164 | var conversion = amount * bigDecimalFromUnit; 165 | return conversion.Floor().Mantissa; 166 | } 167 | 168 | public BigInteger ToWei(BigDecimal amount, EthUnit fromUnit = EthUnit.Ether) 169 | { 170 | return ToWeiFromUnit(amount, GetEthUnitValue(fromUnit)); 171 | } 172 | 173 | public BigInteger ToWei(BigDecimal amount, int decimalPlacesFromUnit) 174 | { 175 | if (decimalPlacesFromUnit == 0) ToWei(amount, 1); 176 | return ToWeiFromUnit(amount, BigInteger.Pow(10, decimalPlacesFromUnit)); 177 | } 178 | 179 | public BigInteger ToWei(decimal amount, int decimalPlacesFromUnit) 180 | { 181 | if (decimalPlacesFromUnit == 0) ToWei(amount, 1); 182 | return ToWeiFromUnit(amount, BigInteger.Pow(10, decimalPlacesFromUnit)); 183 | } 184 | 185 | 186 | public BigInteger ToWei(decimal amount, EthUnit fromUnit = EthUnit.Ether) 187 | { 188 | return ToWeiFromUnit(amount, GetEthUnitValue(fromUnit)); 189 | } 190 | 191 | 192 | public BigInteger ToWei(BigInteger value, EthUnit fromUnit = EthUnit.Ether) 193 | { 194 | return value * GetEthUnitValue(fromUnit); 195 | } 196 | 197 | public BigInteger ToWei(int value, EthUnit fromUnit = EthUnit.Ether) 198 | { 199 | return ToWei(new BigInteger(value), fromUnit); 200 | } 201 | 202 | public BigInteger ToWei(double value, EthUnit fromUnit = EthUnit.Ether) 203 | { 204 | return ToWei(System.Convert.ToDecimal(value), fromUnit); 205 | } 206 | 207 | public BigInteger ToWei(float value, EthUnit fromUnit = EthUnit.Ether) 208 | { 209 | return ToWei(System.Convert.ToDecimal(value), fromUnit); 210 | } 211 | 212 | public BigInteger ToWei(long value, EthUnit fromUnit = EthUnit.Ether) 213 | { 214 | return ToWei(new BigInteger(value), fromUnit); 215 | } 216 | 217 | public BigInteger ToWei(string value, EthUnit fromUnit = EthUnit.Ether) 218 | { 219 | return ToWei(decimal.Parse(value), fromUnit); 220 | } 221 | 222 | private BigInteger CalculateNumberOfDecimalPlaces(double value, int maxNumberOfDecimals, 223 | int currentNumberOfDecimals = 0) 224 | { 225 | return CalculateNumberOfDecimalPlaces(System.Convert.ToDecimal(value), maxNumberOfDecimals, 226 | currentNumberOfDecimals); 227 | } 228 | 229 | private BigInteger CalculateNumberOfDecimalPlaces(float value, int maxNumberOfDecimals, 230 | int currentNumberOfDecimals = 0) 231 | { 232 | return CalculateNumberOfDecimalPlaces(System.Convert.ToDecimal(value), maxNumberOfDecimals, 233 | currentNumberOfDecimals); 234 | } 235 | 236 | private int CalculateNumberOfDecimalPlaces(decimal value, int maxNumberOfDecimals, 237 | int currentNumberOfDecimals = 0) 238 | { 239 | if (currentNumberOfDecimals == 0) 240 | { 241 | if (value.ToStringInvariant() == Math.Round(value).ToStringInvariant()) return 0; 242 | currentNumberOfDecimals = 1; 243 | } 244 | 245 | if (currentNumberOfDecimals == maxNumberOfDecimals) return maxNumberOfDecimals; 246 | var multiplied = value * (decimal)BigInteger.Pow(10, currentNumberOfDecimals); 247 | if (Math.Round(multiplied) == multiplied) 248 | return currentNumberOfDecimals; 249 | return CalculateNumberOfDecimalPlaces(value, maxNumberOfDecimals, currentNumberOfDecimals + 1); 250 | } 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /test/OpenSeaClient.DemoApi/Controllers/AccountController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | 3 | namespace OpenSeaClient.DemoApi.Controllers 4 | { 5 | [ApiController] 6 | [Route("[controller]")] 7 | public class AccountController : ControllerBase 8 | { 9 | private readonly ILogger _logger; 10 | private readonly IOpenSeaClient _openSeaClient; 11 | 12 | public AccountController( 13 | IOpenSeaClient openSeaClient, 14 | ILogger logger) 15 | { 16 | _openSeaClient = openSeaClient; 17 | _logger = logger; 18 | } 19 | 20 | [HttpGet("~/collections/{owner}")] 21 | public Task?> GetCollections(string owner) 22 | { 23 | return _openSeaClient.GetCollectionsAsync(new GetCollectionsQueryParams 24 | { 25 | AssetOwner = owner, 26 | Limit = 50, 27 | Offset = 0, 28 | }); 29 | } 30 | 31 | [HttpGet("~/collection/{collectionSlug}/stats")] 32 | public Task GetCollectionStats(string collectionSlug) 33 | { 34 | return _openSeaClient.GetCollectionStatsAsync(collectionSlug); 35 | } 36 | 37 | [HttpGet("~/collection/{collectionSlug}")] 38 | public Task GetCollection(string collectionSlug) 39 | { 40 | return _openSeaClient.GetCollectionAsync(collectionSlug); 41 | } 42 | 43 | [HttpGet("~/assets/{collectionSlug}/{owner}")] 44 | public Task?> GetAssets(string collectionSlug, string owner) 45 | { 46 | return _openSeaClient.GetAssetsAsync(new GetAssetsQueryParams 47 | { 48 | CollectionSlug = collectionSlug, 49 | OwnerAddress = owner, 50 | Limit = 50, 51 | Offset = 0, 52 | }); 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /test/OpenSeaClient.DemoApi/OpenSeaClient.DemoApi.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /test/OpenSeaClient.DemoApi/Program.cs: -------------------------------------------------------------------------------- 1 | using OpenSeaClient; 2 | 3 | var builder = WebApplication.CreateBuilder(args); 4 | 5 | // Add services to the container. 6 | 7 | builder.Services.AddControllers(); 8 | builder.Services.AddEndpointsApiExplorer(); 9 | builder.Services.AddSwaggerGen(); 10 | 11 | builder.Services.AddHttpClient(config => 12 | { 13 | // Replace with your OpenSea API Key or comment this line 14 | config.DefaultRequestHeaders.Add("X-Api-Key", builder.Configuration["OpenSeaApiKey"]); 15 | }); 16 | 17 | builder.Services.AddCors(options => 18 | { 19 | options.AddDefaultPolicy(cors => 20 | { 21 | var url = builder.Configuration["CrossOriginUrl"]; 22 | 23 | if (!string.IsNullOrEmpty(url)) 24 | { 25 | cors.WithOrigins(url); 26 | } 27 | else 28 | { 29 | cors.AllowAnyOrigin(); 30 | } 31 | }); 32 | }); 33 | 34 | var app = builder.Build(); 35 | 36 | // Configure the HTTP request pipeline. 37 | app.UseSwagger(); 38 | app.UseSwaggerUI(); 39 | 40 | app.UseHttpsRedirection(); 41 | 42 | app.UseRouting(); 43 | 44 | app.UseCors(); 45 | 46 | app.UseAuthorization(); 47 | 48 | app.UseEndpoints(endpoints => 49 | { 50 | endpoints.MapGet("/", ctx => 51 | { 52 | ctx.Response.Redirect("swagger"); 53 | 54 | return Task.CompletedTask; 55 | }); 56 | }); 57 | 58 | app.MapControllers(); 59 | 60 | app.Run(); 61 | -------------------------------------------------------------------------------- /test/OpenSeaClient.DemoApi/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:51724", 8 | "sslPort": 44377 9 | } 10 | }, 11 | "profiles": { 12 | "OpenSeaClient.DemoApi": { 13 | "commandName": "Project", 14 | "dotnetRunMessages": true, 15 | "launchBrowser": true, 16 | "launchUrl": "swagger", 17 | "applicationUrl": "https://localhost:7235;http://localhost:5235", 18 | "environmentVariables": { 19 | "ASPNETCORE_ENVIRONMENT": "Development" 20 | } 21 | }, 22 | "IIS Express": { 23 | "commandName": "IISExpress", 24 | "launchBrowser": true, 25 | "launchUrl": "swagger", 26 | "environmentVariables": { 27 | "ASPNETCORE_ENVIRONMENT": "Development" 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/OpenSeaClient.DemoApi/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/OpenSeaClient.DemoApi/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "OpenSeaApiKey": "", 9 | "AllowedHosts": "*" 10 | } 11 | -------------------------------------------------------------------------------- /test/OpenSeaClient.DemoConsole/OpenSeaClient.DemoConsole.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net6.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /test/OpenSeaClient.DemoConsole/Program.cs: -------------------------------------------------------------------------------- 1 | using OpenSeaClient; 2 | 3 | var client = new OpenSeaHttpClient(apiKey: ""); 4 | 5 | // Get assets in batches of 50 with 1s delay between API calls to avoid throttling: 6 | 7 | var queryParams = new GetAssetsQueryParams 8 | { 9 | CollectionSlug = "niftydegen", 10 | }; 11 | var count = 0; 12 | var limit = 50; 13 | var it = 0; 14 | 15 | var assetsList = new List(); 16 | 17 | do 18 | { 19 | queryParams.Offset = limit * it; 20 | queryParams.Limit = limit; 21 | 22 | var assets = await client.GetAssetsAsync(queryParams); 23 | 24 | if (assets != null) 25 | { 26 | if (assets.Count > 0) 27 | { 28 | assetsList.AddRange(assets); 29 | } 30 | } 31 | else 32 | { 33 | break; 34 | } 35 | 36 | await Task.Delay(1000); 37 | } 38 | while (count == 50); 39 | 40 | // Get the price of a token's sale order: 41 | 42 | var tokenId = "697"; 43 | var contractAddress = "0x986aea67c7d6a15036e18678065eb663fc5be883"; 44 | 45 | var orders = await client.GetOrdersAsync(new GetOrdersQueryParams 46 | { 47 | AssetContractAddress = contractAddress, 48 | TokenId = tokenId, 49 | Side = 1, 50 | SaleKind = 0, 51 | }); 52 | 53 | if (orders?.Any() == true) 54 | { 55 | var order = orders.Where(x => x.Cancelled == false).OrderBy(x => x.CurrentPriceEth).FirstOrDefault(); 56 | 57 | if (order != null) 58 | { 59 | Console.WriteLine($"Token {tokenId} has a sell order of {order.CurrentPriceEth} ETH"); 60 | } 61 | } 62 | 63 | Console.ReadLine(); 64 | --------------------------------------------------------------------------------