├── .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 | [](https://www.nuget.org/packages/OpenSeaClient/)
7 | [](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 |
--------------------------------------------------------------------------------