├── .editorconfig
├── .gitignore
├── LICENSE
├── LocalBitcoinsApi.sln
├── NugetPackageLogo.png
├── README.md
├── azure-pipelines.yml
└── src
├── LocalBitcoinsApi.UnitTests
├── ClientTests.cs
├── LocalBitcoinsApi.UnitTests.csproj
├── ManualTests.cs
├── MockLocalBitcoinsClient.cs
├── TestSettings.cs
├── TestSettings.json
└── data
│ └── MySelf.json
└── LocalBitcoinsApi
├── GlobalSuppressions.cs
├── LocalBitcoinsAPI.cs
├── LocalBitcoinsApi.csproj
├── LocalBitcoinsApi.ruleset
├── LocalBitcoinsApi.snk
├── LocalBitcoinsClient.cs
├── LocalBitcoinsException.cs
├── LocalBitcoinsRestApi.cs
├── RequestType.cs
├── Utility.cs
└── UtilityExtensions.cs
/.editorconfig:
--------------------------------------------------------------------------------
1 | # editorconfig.org
2 | # Based on https://github.com/dotnet/corefx/blob/master/.editorconfig
3 |
4 | # top-most EditorConfig file
5 | root = true
6 |
7 | # Default settings:
8 | # A newline ending every file
9 | # Use 4 spaces as indentation
10 | [*]
11 | insert_final_newline = false
12 | indent_style = space
13 | indent_size = 4
14 |
15 | [project.json]
16 | indent_size = 2
17 |
18 | # C# files
19 | [*.cs]
20 | # New line preferences
21 | csharp_new_line_before_open_brace = all
22 | csharp_new_line_before_else = true
23 | csharp_new_line_before_catch = true
24 | csharp_new_line_before_finally = true
25 | csharp_new_line_before_members_in_object_initializers = true
26 | csharp_new_line_before_members_in_anonymous_types = true
27 | csharp_new_line_between_query_expression_clauses = true
28 |
29 | # Indentation preferences
30 | csharp_indent_block_contents = true
31 | csharp_indent_braces = false
32 | csharp_indent_case_contents = true
33 | csharp_indent_switch_labels = true
34 | csharp_indent_labels = one_less_than_current
35 |
36 | # use language keywords instead of BCL types
37 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
38 | dotnet_style_predefined_type_for_member_access = true:suggestion
39 |
40 | # name all constant fields using PascalCase
41 | dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion
42 | dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
43 | dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
44 |
45 | dotnet_naming_symbols.constant_fields.applicable_kinds = field
46 | dotnet_naming_symbols.constant_fields.required_modifiers = const
47 |
48 | dotnet_naming_style.pascal_case_style.capitalization = pascal_case
49 |
50 | # internal and private fields should be _camelCase
51 | dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion
52 | dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields
53 | dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style
54 |
55 | dotnet_naming_symbols.private_internal_fields.applicable_kinds = field
56 | dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal
57 |
58 | dotnet_naming_style.camel_case_underscore_style.required_prefix = _
59 | dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case
60 |
61 | # Code style defaults
62 | dotnet_sort_system_directives_first = true
63 | csharp_preserve_single_line_blocks = true
64 | csharp_preserve_single_line_statements = false
65 |
66 | # Pattern matching
67 | csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
68 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
69 | csharp_style_inlined_variable_declaration = true:suggestion
70 |
71 | # Null checking preferences
72 | csharp_style_throw_expression = true:suggestion
73 | csharp_style_conditional_delegate_call = true:suggestion
74 |
75 | # Space preferences
76 | csharp_space_after_cast = false
77 | csharp_space_after_colon_in_inheritance_clause = true
78 | csharp_space_after_comma = true
79 | csharp_space_after_dot = false
80 | csharp_space_after_keywords_in_control_flow_statements = true
81 | csharp_space_after_semicolon_in_for_statement = true
82 | csharp_space_around_binary_operators = before_and_after
83 | csharp_space_around_declaration_statements = do_not_ignore
84 | csharp_space_before_colon_in_inheritance_clause = true
85 | csharp_space_before_comma = false
86 | csharp_space_before_dot = false
87 | csharp_space_before_open_square_brackets = false
88 | csharp_space_before_semicolon_in_for_statement = false
89 | csharp_space_between_empty_square_brackets = false
90 | csharp_space_between_method_call_empty_parameter_list_parentheses = false
91 | csharp_space_between_method_call_name_and_opening_parenthesis = false
92 | csharp_space_between_method_call_parameter_list_parentheses = false
93 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
94 | csharp_space_between_method_declaration_name_and_open_parenthesis = false
95 | csharp_space_between_method_declaration_parameter_list_parentheses = false
96 | csharp_space_between_parentheses = false
97 | csharp_space_between_square_brackets = false
98 |
99 | # Xml project files
100 | [*.{csproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}]
101 | indent_size = 2
102 |
103 | # Xml build files
104 | [*.builds]
105 | indent_size = 2
106 |
107 | # Xml files
108 | [*.{xml,stylecop,resx,ruleset}]
109 | indent_size = 2
110 |
111 | # Xml config files
112 | [*.{props,targets,config,nuspec}]
113 | indent_size = 2
114 |
115 | # Shell scripts
116 | [*.sh]
117 | end_of_line = lf
118 | [*.{cmd, bat}]
119 | end_of_line = crlf
120 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 |
13 | # Build results
14 | [Dd]ebug/
15 | [Dd]ebugPublic/
16 | [Rr]elease/
17 | [Rr]eleases/
18 | x64/
19 | x86/
20 | bld/
21 | [Bb]in/
22 | [Oo]bj/
23 | [Ll]og/
24 |
25 | # Visual Studio 2015 cache/options directory
26 | .vs/
27 | # Uncomment if you have tasks that create the project's static files in wwwroot
28 | #wwwroot/
29 |
30 | # MSTest test Results
31 | [Tt]est[Rr]esult*/
32 | [Bb]uild[Ll]og.*
33 |
34 | # NUNIT
35 | *.VisualState.xml
36 | TestResult.xml
37 |
38 | # Build Results of an ATL Project
39 | [Dd]ebugPS/
40 | [Rr]eleasePS/
41 | dlldata.c
42 |
43 | # DNX
44 | project.lock.json
45 | artifacts/
46 |
47 | *_i.c
48 | *_p.c
49 | *_i.h
50 | *.ilk
51 | *.meta
52 | *.obj
53 | *.pch
54 | *.pdb
55 | *.pgc
56 | *.pgd
57 | *.rsp
58 | *.sbr
59 | *.tlb
60 | *.tli
61 | *.tlh
62 | *.tmp
63 | *.tmp_proj
64 | *.log
65 | *.vspscc
66 | *.vssscc
67 | .builds
68 | *.pidb
69 | *.svclog
70 | *.scc
71 |
72 | # Chutzpah Test files
73 | _Chutzpah*
74 |
75 | # Visual C++ cache files
76 | ipch/
77 | *.aps
78 | *.ncb
79 | *.opendb
80 | *.opensdf
81 | *.sdf
82 | *.cachefile
83 | *.VC.db
84 | *.VC.VC.opendb
85 |
86 | # Visual Studio profiler
87 | *.psess
88 | *.vsp
89 | *.vspx
90 | *.sap
91 |
92 | # TFS 2012 Local Workspace
93 | $tf/
94 |
95 | # Guidance Automation Toolkit
96 | *.gpState
97 |
98 | # ReSharper is a .NET coding add-in
99 | _ReSharper*/
100 | *.[Rr]e[Ss]harper
101 | *.DotSettings.user
102 |
103 | # JustCode is a .NET coding add-in
104 | .JustCode
105 |
106 | # TeamCity is a build add-in
107 | _TeamCity*
108 |
109 | # DotCover is a Code Coverage Tool
110 | *.dotCover
111 |
112 | # NCrunch
113 | _NCrunch_*
114 | .*crunch*.local.xml
115 | nCrunchTemp_*
116 |
117 | # MightyMoose
118 | *.mm.*
119 | AutoTest.Net/
120 |
121 | # Web workbench (sass)
122 | .sass-cache/
123 |
124 | # Installshield output folder
125 | [Ee]xpress/
126 |
127 | # DocProject is a documentation generator add-in
128 | DocProject/buildhelp/
129 | DocProject/Help/*.HxT
130 | DocProject/Help/*.HxC
131 | DocProject/Help/*.hhc
132 | DocProject/Help/*.hhk
133 | DocProject/Help/*.hhp
134 | DocProject/Help/Html2
135 | DocProject/Help/html
136 |
137 | # Click-Once directory
138 | publish/
139 |
140 | # Publish Web Output
141 | *.[Pp]ublish.xml
142 | *.azurePubxml
143 | # TODO: Comment the next line if you want to checkin your web deploy settings
144 | # but database connection strings (with potential passwords) will be unencrypted
145 | *.pubxml
146 | *.publishproj
147 |
148 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
149 | # checkin your Azure Web App publish settings, but sensitive information contained
150 | # in these scripts will be unencrypted
151 | PublishScripts/
152 |
153 | # NuGet Packages
154 | *.nupkg
155 | # The packages folder can be ignored because of Package Restore
156 | **/packages/*
157 | # except build/, which is used as an MSBuild target.
158 | !**/packages/build/
159 | # Uncomment if necessary however generally it will be regenerated when needed
160 | #!**/packages/repositories.config
161 | # NuGet v3's project.json files produces more ignoreable files
162 | *.nuget.props
163 | *.nuget.targets
164 |
165 | # Microsoft Azure Build Output
166 | csx/
167 | *.build.csdef
168 |
169 | # Microsoft Azure Emulator
170 | ecf/
171 | rcf/
172 |
173 | # Windows Store app package directories and files
174 | AppPackages/
175 | BundleArtifacts/
176 | Package.StoreAssociation.xml
177 | _pkginfo.txt
178 |
179 | # Visual Studio cache files
180 | # files ending in .cache can be ignored
181 | *.[Cc]ache
182 | # but keep track of directories ending in .cache
183 | !*.[Cc]ache/
184 |
185 | # Others
186 | ClientBin/
187 | ~$*
188 | *~
189 | *.dbmdl
190 | *.dbproj.schemaview
191 | *.pfx
192 | *.publishsettings
193 | node_modules/
194 | orleans.codegen.cs
195 |
196 | # Since there are multiple workflows, uncomment next line to ignore bower_components
197 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
198 | #bower_components/
199 |
200 | # RIA/Silverlight projects
201 | Generated_Code/
202 |
203 | # Backup & report files from converting an old project file
204 | # to a newer Visual Studio version. Backup files are not needed,
205 | # because we have git ;-)
206 | _UpgradeReport_Files/
207 | Backup*/
208 | UpgradeLog*.XML
209 | UpgradeLog*.htm
210 |
211 | # SQL Server files
212 | *.mdf
213 | *.ldf
214 |
215 | # Business Intelligence projects
216 | *.rdl.data
217 | *.bim.layout
218 | *.bim_*.settings
219 |
220 | # Microsoft Fakes
221 | FakesAssemblies/
222 |
223 | # GhostDoc plugin setting file
224 | *.GhostDoc.xml
225 |
226 | # Node.js Tools for Visual Studio
227 | .ntvs_analysis.dat
228 |
229 | # Visual Studio 6 build log
230 | *.plg
231 |
232 | # Visual Studio 6 workspace options file
233 | *.opt
234 |
235 | # Visual Studio LightSwitch build output
236 | **/*.HTMLClient/GeneratedArtifacts
237 | **/*.DesktopClient/GeneratedArtifacts
238 | **/*.DesktopClient/ModelManifest.xml
239 | **/*.Server/GeneratedArtifacts
240 | **/*.Server/ModelManifest.xml
241 | _Pvt_Extensions
242 |
243 | # Paket dependency manager
244 | .paket/paket.exe
245 | paket-files/
246 |
247 | # FAKE - F# Make
248 | .fake/
249 |
250 | # JetBrains Rider
251 | .idea/
252 | *.sln.iml
253 |
254 | *.bak
255 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
--------------------------------------------------------------------------------
/LocalBitcoinsApi.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.26228.4
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LocalBitcoinsApi", "src\LocalBitcoinsApi\LocalBitcoinsApi.csproj", "{1E2333E7-0C1B-4379-BA2B-398D423E4651}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LocalBitcoinsApi.UnitTests", "src\LocalBitcoinsApi.UnitTests\LocalBitcoinsApi.UnitTests.csproj", "{8A97E1A3-26EB-41A4-85E6-15C284C5AAB6}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Release|Any CPU = Release|Any CPU
14 | EndGlobalSection
15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
16 | {1E2333E7-0C1B-4379-BA2B-398D423E4651}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17 | {1E2333E7-0C1B-4379-BA2B-398D423E4651}.Debug|Any CPU.Build.0 = Debug|Any CPU
18 | {1E2333E7-0C1B-4379-BA2B-398D423E4651}.Release|Any CPU.ActiveCfg = Release|Any CPU
19 | {1E2333E7-0C1B-4379-BA2B-398D423E4651}.Release|Any CPU.Build.0 = Release|Any CPU
20 | {8A97E1A3-26EB-41A4-85E6-15C284C5AAB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {8A97E1A3-26EB-41A4-85E6-15C284C5AAB6}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {8A97E1A3-26EB-41A4-85E6-15C284C5AAB6}.Release|Any CPU.ActiveCfg = Release|Any CPU
23 | {8A97E1A3-26EB-41A4-85E6-15C284C5AAB6}.Release|Any CPU.Build.0 = Release|Any CPU
24 | EndGlobalSection
25 | GlobalSection(SolutionProperties) = preSolution
26 | HideSolutionNode = FALSE
27 | EndGlobalSection
28 | GlobalSection(ExtensibilityGlobals) = postSolution
29 | SolutionGuid = {3E2704AA-B760-4598-A78A-2BAA9AAC2FC7}
30 | EndGlobalSection
31 | EndGlobal
32 |
--------------------------------------------------------------------------------
/NugetPackageLogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aabiryukov/LocalBitcoinsApi/73e6fea983f974e068a90ed7e82342026c6443e2/NugetPackageLogo.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://sitronics.visualstudio.com/GithubPipelineTest/_build/latest?definitionId=25?branchName=master)
2 | [](https://www.nuget.org/packages/LocalBitcoinsApi)
3 |
4 | # LocalBitcoinsApi
5 | It is LocalBitcoins.com API .NET Library
6 |
7 | Package in Nuget: https://www.nuget.org/packages/LocalBitcoinsApi/
8 |
9 | This LocalBitcoins.com API wrapper written in C# provides a quick access to most available LocalBitcoins features.
10 | Original API documentation available on the official web site: https://localbitcoins.com/api-docs/
11 |
12 | You can look for code examples in LocalBitcoins.Test project which is a part of the solution.
13 | # Installation
14 | To install LocalBitcoinsApi with Nuget, run the following command in the Package Manager Console
15 | ```
16 | PM> Install-Package LocalBitcoinsApi
17 | ```
18 | # Code examples
19 | To create an instance of LocalBitcoinsClient:
20 | ```
21 | var apiKey = "Your_Key";
22 | var apiSecret = "Your_Secret";
23 | var lbClient = new LocalBitcoinsClient(apiKey, apiSecret);
24 | ```
25 | After creating an instance next and more API methods are available:
26 | ```
27 | var mySelf = await lbClient.GetMyself();
28 | Console.WriteLine("My name is: " + mySelf.data.username);
29 |
30 | var accountInfo = await lbClient.GetAccountInfo("SomeAccountName");
31 | var dashboard = await lbClient.GetDashboard();
32 | var ownAds = await lbClient.GetOwnAds();
33 | var walletBalance = await lbClient.GetWalletBalance();
34 | var contactInfo = await lbClient.GetContactInfo("7652822");
35 | var contactUrl = await lbClient.CreateContact("11534457", 0.1M, "My message");
36 |
37 | // Full list of methods you can find in the project sources
38 | ```
39 |
40 |
--------------------------------------------------------------------------------
/azure-pipelines.yml:
--------------------------------------------------------------------------------
1 | # .NET Core
2 | # Add steps that publish symbols, save build artifacts, and more:
3 | # https://docs.microsoft.com/ru-ru/azure/devops/pipelines/languages/dotnet-core
4 |
5 | pool:
6 | vmImage: 'Ubuntu 16.04'
7 |
8 | variables:
9 | buildConfiguration: 'Release'
10 |
11 | steps:
12 |
13 | - script: dotnet build --configuration $(buildConfiguration)
14 | displayName: 'dotnet build $(buildConfiguration)'
15 |
16 | - task: DotNetCoreCLI@2
17 | displayName: 'Run tests'
18 | inputs:
19 | command: test
20 | projects: '**/*Tests/*.csproj'
21 | arguments: '--configuration $(buildConfiguration)'
22 |
23 | - task: CopyFiles@2
24 | displayName: 'Copy files'
25 | inputs:
26 | #sourceFolder: # Optional
27 | contents: src/LocalBitcoinsApi/bin/**
28 | targetFolder: $(Build.ArtifactStagingDirectory)
29 | #cleanTargetFolder: false # Optional
30 | #overWrite: false # Optional
31 | #flattenFolders: false # Optional
32 |
33 | - task: PublishBuildArtifacts@1
34 | displayName: 'Publish Build Artifacts'
35 |
36 |
37 |
--------------------------------------------------------------------------------
/src/LocalBitcoinsApi.UnitTests/ClientTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using LocalBitcoins;
7 | using Microsoft.VisualStudio.TestTools.UnitTesting;
8 | using Newtonsoft.Json.Linq;
9 |
10 | namespace LocalBitcoinsApi.UnitTests
11 | {
12 | [TestClass]
13 | public class ClientTests
14 | {
15 | private static LocalBitcoinsClient CreateClient()
16 | {
17 | return new MockLocalBitcoinsClient();
18 | }
19 |
20 | [TestMethod]
21 | public async Task GetMyselfTest()
22 | {
23 | var client = CreateClient();
24 |
25 | var result = await client.GetMyself();
26 | Assert.IsTrue(!string.IsNullOrEmpty((string)result.data.username));
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/LocalBitcoinsApi.UnitTests/LocalBitcoinsApi.UnitTests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp2.1
5 |
6 | false
7 |
8 |
9 |
10 | DEBUG;TRACE
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | PreserveNewest
26 |
27 |
28 | PreserveNewest
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/src/LocalBitcoinsApi.UnitTests/ManualTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Threading.Tasks;
4 | using LocalBitcoins;
5 | using Microsoft.VisualStudio.TestTools.UnitTesting;
6 | using Newtonsoft.Json;
7 |
8 | namespace LocalBitcoinsApi.UnitTests
9 | {
10 | #if (!DEBUG)
11 | [Ignore]
12 | #endif
13 | [TestClass]
14 | public class ManualTests
15 | {
16 | [TestMethod]
17 | public async Task TestRealApiMethods()
18 | {
19 | // Read settings from file "TestSettings.json"
20 | var testSettings = GetSettings();
21 |
22 | var client = new LocalBitcoinsClient(testSettings.ApiKey, testSettings.ApiSecret);
23 |
24 | dynamic info;
25 |
26 | // info = client.GetContactMessages("7652822");
27 | // Console.WriteLine("Res: " + info.ToString());
28 |
29 |
30 | info = await client.GetMyself();
31 | Console.WriteLine("Myself: " + info.data.username);
32 |
33 | info = await client.GetWalletBalance();
34 | Console.WriteLine("Wallet Balance: " + (decimal)(info.data.total.balance));
35 |
36 | // info = client.CheckPinCode("8044011");
37 | // Console.WriteLine("Pincode: " + info);
38 |
39 | // Update user online
40 | info = await client.GetDashboard();
41 | Console.WriteLine("Dashboard: " + info.ToString());
42 |
43 | info = await client.GetOwnAds();
44 | Console.WriteLine("Ads: " + info.ToString());
45 |
46 | info = await client.GetContactMessageAttachment("6652854", "38026599");
47 | File.WriteAllBytes(@"c:\temp\LBImage.jpeg", info); // Requires System.IO
48 | Console.WriteLine("Res: " + info.Length);
49 |
50 | info = await client.GetRecentMessages();
51 | Console.WriteLine("Res: " + info.ToString());
52 |
53 | info = await client.GetDashboardReleased();
54 | Console.WriteLine("Res: " + info.ToString());
55 |
56 | info = await client.GetFees();
57 | Console.WriteLine("Deposit Fee: " + info.data.deposit_fee);
58 |
59 | info = await client.GetAccountInfo("SomeAccountName");
60 | Console.WriteLine("User: " + info.data.username);
61 |
62 | info = await client.Logout();
63 | Console.WriteLine("Logout: " + info.ToString());
64 | }
65 |
66 | private static TestSettings GetSettings()
67 | {
68 | const string defaultSettingsFile = "TestSettings.json";
69 | const string overrideSettingsFile = "TestSettings.override.json";
70 |
71 | var settingsFile = File.Exists(overrideSettingsFile) ? overrideSettingsFile : defaultSettingsFile;
72 | var settings = JsonConvert.DeserializeObject(File.ReadAllText(settingsFile));
73 |
74 | if (string.IsNullOrEmpty(settings.ApiKey) || settings.ApiKey == "INSERT-KEY-HERE")
75 | throw new Exception($"Fill settings in file {settingsFile}");
76 |
77 | return settings;
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/LocalBitcoinsApi.UnitTests/MockLocalBitcoinsClient.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Threading.Tasks;
5 | using LocalBitcoins;
6 | using Newtonsoft.Json;
7 | using Newtonsoft.Json.Linq;
8 |
9 | namespace LocalBitcoinsApi.UnitTests
10 | {
11 | internal class MockLocalBitcoinsClient: LocalBitcoinsClient
12 | {
13 | public MockLocalBitcoinsClient()
14 | : base("xxx", "xxx")
15 | {
16 | }
17 | protected override Task CallApiAsync(string apiCommand, RequestType requestType = RequestType.Get, Dictionary args = null)
18 | {
19 | string resourceName;
20 |
21 | switch (apiCommand.ToLowerInvariant())
22 | {
23 | case "/api/myself/":
24 | resourceName = "MySelf";
25 | break;
26 |
27 | default:
28 | throw new ArgumentException($"Unknown api command: {apiCommand}");
29 | }
30 |
31 | var jobj = JsonConvert.DeserializeObject(GetTestJson(resourceName));
32 | return Task.FromResult(jobj);
33 | }
34 |
35 | private static string GetTestJson(string resourceName)
36 | {
37 | return File.ReadAllText(Path.Combine("data", resourceName + ".json"));
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/LocalBitcoinsApi.UnitTests/TestSettings.cs:
--------------------------------------------------------------------------------
1 | namespace LocalBitcoinsApi.UnitTests
2 | {
3 | internal class TestSettings
4 | {
5 | public string ApiKey { get; set; }
6 | public string ApiSecret { get; set; }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/LocalBitcoinsApi.UnitTests/TestSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "ApiKey": "INSERT-KEY-HERE",
3 | "ApiSecret": "INSERT-SECRET-HERE"
4 | }
5 |
--------------------------------------------------------------------------------
/src/LocalBitcoinsApi.UnitTests/data/MySelf.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {
3 | "username": "bitcoinbaron",
4 | "created_at": "2013-06-25T14:11:30+00:00",
5 | "trading_partners_count": 0,
6 | "feedbacks_unconfirmed_count": 0,
7 | "trade_volume_text": "Less than 25 BTC",
8 | "has_common_trades": false,
9 | "confirmed_trade_count_text": "0",
10 | "blocked_count": 0,
11 | "feedback_score": 0,
12 | "feedback_count": 0,
13 | "url": "https://localbitcoins.com/p/bitcoinbaron/",
14 | "trusted_count": 0,
15 | "identity_verified_at": null,
16 | "real_name_verifications_trusted": 1,
17 | "real_name_verifications_untrusted": 4,
18 | "real_name_verifications_rejected": 0
19 | }
20 | }
--------------------------------------------------------------------------------
/src/LocalBitcoinsApi/GlobalSuppressions.cs:
--------------------------------------------------------------------------------
1 |
2 | // This file is used by Code Analysis to maintain SuppressMessage
3 | // attributes that are applied to this project.
4 | // Project-level suppressions either have no target or are given
5 | // a specific target and scoped to a namespace, type, member, etc.
6 |
7 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1001:Types that own disposable fields should be disposable", Justification = "", Scope = "type", Target = "~T:LocalBitcoins.LocalBitcoinsRestApi")]
8 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1707:IdentifiersShouldNotContainUnderscores", Scope = "member", Target = "LocalBitcoins.LocalBitcoinsAPI.#PublicMarket_SellBitcoinsOnlineByCurrency(System.String,System.String,System.Int32)")]
9 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1707:IdentifiersShouldNotContainUnderscores", Scope = "member", Target = "LocalBitcoins.LocalBitcoinsAPI.#PublicMarket_BuyBitcoinsOnlineByCurrency(System.String,System.String,System.Int32)")]
10 |
11 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "", Scope = "member", Target = "~M:LocalBitcoins.LocalBitcoinsClient.PublicMarket_BuyBitcoinsOnlineByCurrency(System.String,System.String,System.Int32)~System.Threading.Tasks.Task{}")]
--------------------------------------------------------------------------------
/src/LocalBitcoinsApi/LocalBitcoinsAPI.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace LocalBitcoins
5 | {
6 | [Obsolete("This class is obsolete. Use instead LocalBitcoinsClient class with async/await.", false)]
7 | public class LocalBitcoinsAPI
8 | {
9 | private const int DefaultApiTimeoutSec = 10;
10 |
11 | private readonly LocalBitcoinsClient _client;
12 |
13 | ///
14 | /// Unique ctor sets access key and secret key, which cannot be changed later.
15 | ///
16 | /// Your Access Key
17 | /// Your Secret Key
18 | /// API request timeout in seconds
19 | /// Override API base address. Default is "https://localbitcoins.net/"
20 | public LocalBitcoinsAPI(string accessKey, string secretKey, int apiTimeoutSec = DefaultApiTimeoutSec, string overrideBaseAddress = null)
21 | {
22 | _client = new LocalBitcoinsClient(accessKey, secretKey, apiTimeoutSec, overrideBaseAddress);
23 | }
24 |
25 | // Returns public user profile information
26 | public dynamic GetAccountInfo(string userName)
27 | => _client.GetAccountInfo(userName).WaitAndUnwrapException();
28 |
29 | // Return the information of the currently logged in user(the owner of authentication token).
30 | public dynamic GetMyself()
31 | => _client.GetMyself().WaitAndUnwrapException();
32 |
33 | // Checks the given PIN code against the user"s currently active PIN code.
34 | // You can use this method to ensure the person using the session is the legitimate user.
35 | public dynamic CheckPinCode(string code)
36 | => _client.CheckPinCode(code).WaitAndUnwrapException();
37 |
38 | // Return open and active contacts
39 | public dynamic GetDashboard()
40 | => _client.GetDashboard().WaitAndUnwrapException();
41 |
42 | // Return released(successful) contacts
43 | public dynamic GetDashboardReleased()
44 | => _client.GetDashboardReleased().WaitAndUnwrapException();
45 |
46 | // Return canceled contacts
47 | public dynamic GetDashboardCanceled()
48 | => _client.GetDashboardCanceled().WaitAndUnwrapException();
49 |
50 | // Return closed contacts, both released and canceled
51 | public dynamic GetDashboardClosed()
52 | => _client.GetDashboardClosed().WaitAndUnwrapException();
53 |
54 | // Releases the escrow of contact specified by ID { contact_id }.
55 | // On success there"s a complimentary message on the data key.
56 | public dynamic ContactRelease(string contactId)
57 | => _client.ContactRelease(contactId).WaitAndUnwrapException();
58 |
59 | // Releases the escrow of contact specified by ID { contact_id }.
60 | // On success there"s a complimentary message on the data key.
61 | public dynamic ContactReleasePin(string contactId, string pincode)
62 | => _client.ContactReleasePin(contactId, pincode).WaitAndUnwrapException();
63 |
64 | // Reads all messaging from the contact.Messages are on the message_list key.
65 | // On success there"s a complimentary message on the data key.
66 | // attachment_* fields exist only if there is an attachment.
67 | public dynamic GetContactMessages(string contactId)
68 | => _client.GetContactMessages(contactId).WaitAndUnwrapException();
69 |
70 | public byte[] GetContactMessageAttachment(string contractId, string attachmentId)
71 | => _client.GetContactMessageAttachment(contractId, attachmentId).WaitAndUnwrapException();
72 |
73 | // Marks a contact as paid.
74 | // It is recommended to access this API through /api/online_buy_contacts/ entries" action key.
75 | public dynamic MarkContactAsPaid(string contactId)
76 | => _client.MarkContactAsPaid(contactId).WaitAndUnwrapException();
77 |
78 | // Post a message to contact
79 | public dynamic PostMessageToContact(string contactId, string message, string attachFileName = null)
80 | => _client.PostMessageToContact(contactId, message, attachFileName).WaitAndUnwrapException();
81 |
82 | // Starts a dispute with the contact, if possible.
83 | // You can provide a short description using topic. This helps support to deal with the problem.
84 | public dynamic StartDispute(string contactId, string topic = null)
85 | => _client.StartDispute(contactId, topic).WaitAndUnwrapException();
86 |
87 | // Cancels the contact, if possible
88 | public dynamic CancelContact(string contactId)
89 | => _client.CancelContact(contactId).WaitAndUnwrapException();
90 |
91 | // Attempts to fund an unfunded local contact from the seller"s wallet.
92 | public dynamic FundContact(string contactId)
93 | => _client.FundContact(contactId).WaitAndUnwrapException();
94 |
95 | // Attempts to create a contact to trade bitcoins.
96 | // Amount is a number in the advertisement"s fiat currency.
97 | // Returns the API URL to the newly created contact at actions.contact_url.
98 | // Whether the contact was able to be funded automatically is indicated at data.funded.
99 | // Only non-floating LOCAL_SELL may return unfunded, all other trade types either fund or fail.
100 | public dynamic CreateContact(string contactId, decimal amount, string message = null)
101 | => _client.CreateContact(contactId, amount, message).WaitAndUnwrapException();
102 |
103 |
104 | // Gets information about a single contact you are involved in. Same fields as in /api/contacts/.
105 | public dynamic GetContactInfo(string contactId)
106 | => _client.GetContactInfo(contactId).WaitAndUnwrapException();
107 |
108 | // contacts is a comma-separated list of contact IDs that you want to access in bulk.
109 | // The token owner needs to be either a buyer or seller in the contacts, contacts that do not pass this check are simply not returned.
110 | // A maximum of 50 contacts can be requested at a time.
111 | // The contacts are not returned in any particular order.
112 | public dynamic GetContactsInfo(string contacts)
113 | => _client.GetContactsInfo(contacts).WaitAndUnwrapException();
114 |
115 | // Returns maximum of 50 newest trade messages.
116 | // Messages are ordered by sending time, and the newest one is first.
117 | // The list has same format as /api/contact_messages/, but each message has also contact_id field.
118 | public dynamic GetRecentMessages()
119 | => _client.GetRecentMessages().WaitAndUnwrapException();
120 |
121 | // Gives feedback to user.
122 | // Possible feedback values are: trust, positive, neutral, block, block_without_feedback as strings.
123 | // You may also set feedback message field with few exceptions. Feedback block_without_feedback clears the message and with block the message is mandatory.
124 | public dynamic PostFeedbackToUser(string userName, string feedback, string message = null)
125 | => _client.PostFeedbackToUser(userName, feedback, message).WaitAndUnwrapException();
126 |
127 | // Gets information about the token owner"s wallet balance.
128 | public dynamic GetWallet()
129 | => _client.GetWallet().WaitAndUnwrapException();
130 |
131 | // Same as / api / wallet / above, but only returns the message, receiving_address_list and total fields.
132 | // (There"s also a receiving_address_count but it is always 1: only the latest receiving address is ever returned by this call.)
133 | // Use this instead if you don"t care about transactions at the moment.
134 | public dynamic GetWalletBalance()
135 | => _client.GetWalletBalance().WaitAndUnwrapException();
136 |
137 | // Sends amount bitcoins from the token owner"s wallet to address.
138 | // Note that this API requires its own API permission called Money.
139 | // On success, this API returns just a message indicating success.
140 | // It is highly recommended to minimize the lifetime of access tokens with the money permission.
141 | // Call / api / logout / to make the current token expire instantly.
142 | public dynamic WalletSend(decimal amount, string address)
143 | => _client.WalletSend(amount, address).WaitAndUnwrapException();
144 |
145 | // As above, but needs the token owner"s active PIN code to succeed.
146 | // Look before you leap. You can check if a PIN code is valid without attempting a send with / api / pincode /.
147 | // Security concern: To get any security beyond the above API, do not retain the PIN code beyond a reasonable user session, a few minutes at most.
148 | // If you are planning to save the PIN code anyway, please save some headache and get the real no-pin - required money permission instead.
149 | public dynamic WalletSendWithPin(decimal amount, string address, string pincode)
150 | => _client.WalletSendWithPin(amount, address, pincode).WaitAndUnwrapException();
151 |
152 | // Gets an unused receiving address for the token owner"s wallet, its address given in the address key of the response.
153 | // Note that this API may keep returning the same(unused) address if called repeatedly.
154 | public dynamic GetWalletAddress()
155 | => _client.GetWalletAddress().WaitAndUnwrapException();
156 |
157 | // Gets the current outgoing and deposit fees in bitcoins (BTC).
158 | public dynamic GetFees()
159 | => _client.GetFees().WaitAndUnwrapException();
160 |
161 | // Expires the current access token immediately.
162 | // To get a new token afterwards, public apps will need to reauthenticate, confidential apps can turn in a refresh token.
163 | public dynamic Logout()
164 | => _client.Logout().WaitAndUnwrapException();
165 |
166 | // Lists the token owner"s all ads on the data key ad_list, optionally filtered. If there"s a lot of ads, the listing will be paginated.
167 | // Refer to the ad editing pages for the field meanings.List item structure is like so:
168 | public dynamic GetOwnAds()
169 | => _client.GetOwnAds().WaitAndUnwrapException();
170 |
171 | // Get ad
172 | public dynamic GetAd(string adId)
173 | => _client.GetAd(adId).WaitAndUnwrapException();
174 |
175 | public dynamic GetAdList(IEnumerable adList)
176 | => _client.GetAdList(adList).WaitAndUnwrapException();
177 |
178 | // Delete ad
179 | public dynamic DeleteAd(string adId)
180 | => _client.DeleteAd(adId).WaitAndUnwrapException();
181 |
182 | // Edit ad visibility
183 | public dynamic EditAdVisiblity(string adId, bool visible)
184 | => _client.EditAdVisiblity(adId, visible).WaitAndUnwrapException();
185 |
186 | // Edit ad
187 | public dynamic EditAd(string adId, Dictionary values, bool preloadAd = false)
188 | => _client.EditAd(adId, values, preloadAd).WaitAndUnwrapException();
189 |
190 | // Edit ad Price equation formula
191 | public dynamic EditAdPriceEquation(string adId, decimal priceEquation)
192 | => _client.EditAdPriceEquation(adId, priceEquation).WaitAndUnwrapException();
193 |
194 | // Retrieves recent notifications.
195 | public dynamic GetNotifications()
196 | => _client.GetNotifications().WaitAndUnwrapException();
197 |
198 | // Marks specific notification as read.
199 | public dynamic NotificationMarkAsRead(string notificationId)
200 | => _client.NotificationMarkAsRead(notificationId).WaitAndUnwrapException();
201 |
202 | ///
203 | /// This API returns buy Bitcoin online ads. It is closely modeled after the online ad listings on LocalBitcoins.com.
204 | ///
205 | /// Three letter currency code
206 | /// An example of a valid argument is national-bank-transfer.
207 | /// Page number, by default 1
208 | /// Ads are returned in the same structure as /api/ads/.
209 | public dynamic PublicMarket_BuyBitcoinsOnlineByCurrency(string currency, string paymentMethod = null, int page = 1)
210 | => _client.PublicMarket_BuyBitcoinsOnlineByCurrency(currency, paymentMethod, page).WaitAndUnwrapException();
211 |
212 | ///
213 | /// This API returns sell Bitcoin online ads. It is closely modeled after the online ad listings on LocalBitcoins.com.
214 | ///
215 | /// Three letter currency code
216 | /// An example of a valid argument is national-bank-transfer.
217 | /// Page number, by default 1
218 | /// Ads are returned in the same structure as /api/ads/.
219 | public dynamic PublicMarket_SellBitcoinsOnlineByCurrency(string currency, string paymentMethod = null, int page = 1)
220 | => _client.PublicMarket_SellBitcoinsOnlineByCurrency(currency, paymentMethod, page).WaitAndUnwrapException();
221 | }
222 | }
223 |
--------------------------------------------------------------------------------
/src/LocalBitcoinsApi/LocalBitcoinsApi.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 | LocalBitcoins .Net API written in C#. Official API documentation here: https://localbitcoins.com/api-docs/
6 | Copyright © Alexander Biryukov 2016-2018
7 |
8 | Alexander Biryukov
9 | true
10 | true
11 | LocalBitcoinsApi.snk
12 | false
13 | https://raw.githubusercontent.com/aabiryukov/LocalBitcoinsApi/master/LICENSE
14 | https://github.com/aabiryukov/LocalBitcoinsApi/
15 | https://raw.githubusercontent.com/aabiryukov/LocalBitcoinsApi/master/NugetPackageLogo.png
16 | https://github.com/aabiryukov/LocalBitcoinsApi/
17 | LocalBitcoins Bitcoin API
18 | 2.0.0.0
19 | 2.0.0.0
20 | 2.0.2
21 | LocalBitcoins .Net API
22 | - Added async/await client (class LocalBitcoinsClient)
23 | - Migrated to .Net Standard 2.0
24 | - Class LocalBitcoinsApi is obslolete, use LocalBitcoinsClient instead
25 |
26 |
27 |
28 |
29 | 0
30 |
31 |
32 |
33 |
34 | all
35 | runtime; build; native; contentfiles; analyzers
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/src/LocalBitcoinsApi/LocalBitcoinsApi.ruleset:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/LocalBitcoinsApi/LocalBitcoinsApi.snk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aabiryukov/LocalBitcoinsApi/73e6fea983f974e068a90ed7e82342026c6443e2/src/LocalBitcoinsApi/LocalBitcoinsApi.snk
--------------------------------------------------------------------------------
/src/LocalBitcoinsApi/LocalBitcoinsClient.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Globalization;
5 | using System.IO;
6 | using System.Threading.Tasks;
7 |
8 | namespace LocalBitcoins
9 | {
10 | // ReSharper disable once InconsistentNaming
11 | public class LocalBitcoinsClient
12 | {
13 | private const int DefaultApiTimeoutSec = 10;
14 | private readonly LocalBitcoinsRestApi _restApi;
15 |
16 | private class NameValueDictionary : Dictionary { }
17 |
18 | ///
19 | /// Unique ctor sets access key and secret key, which cannot be changed later.
20 | ///
21 | /// Your Access Key
22 | /// Your Secret Key
23 | /// API request timeout in seconds
24 | /// Override API base address. Default is "https://localbitcoins.net/"
25 | public LocalBitcoinsClient(string accessKey, string secretKey, int apiTimeoutSec = DefaultApiTimeoutSec, string overrideBaseAddress = null)
26 | {
27 | _restApi = new LocalBitcoinsRestApi(accessKey, secretKey, apiTimeoutSec, overrideBaseAddress);
28 | }
29 |
30 | protected virtual async Task CallApiAsync(string apiCommand, RequestType requestType = RequestType.Get, Dictionary args = null)
31 | {
32 | return await _restApi.CallApiAsync(apiCommand, requestType, args).ConfigureAwait(false);
33 | }
34 |
35 | // Returns public user profile information
36 | public async Task GetAccountInfo(string userName)
37 | {
38 | return await CallApiAsync("/api/account_info/" + userName + "/").ConfigureAwait(false);
39 | }
40 |
41 | // Return the information of the currently logged in user(the owner of authentication token).
42 | public async Task GetMyself()
43 | {
44 | return await CallApiAsync("/api/myself/").ConfigureAwait(false);
45 | }
46 |
47 | // Checks the given PIN code against the user"s currently active PIN code.
48 | // You can use this method to ensure the person using the session is the legitimate user.
49 | public async Task CheckPinCode(string code)
50 | {
51 | var args = new NameValueDictionary
52 | {
53 | {"code", code},
54 | };
55 |
56 | return await CallApiAsync("/api/pincode/", RequestType.Post, args).ConfigureAwait(false);
57 | }
58 |
59 | // Return open and active contacts
60 | public async Task GetDashboard()
61 | {
62 | return await CallApiAsync("/api/dashboard/").ConfigureAwait(false);
63 | }
64 |
65 | // Return released(successful) contacts
66 | public async Task GetDashboardReleased()
67 | {
68 | return await CallApiAsync("/api/dashboard/released/").ConfigureAwait(false);
69 | }
70 |
71 | // Return canceled contacts
72 | public async Task GetDashboardCanceled()
73 | {
74 | return await CallApiAsync("/api/dashboard/canceled/").ConfigureAwait(false);
75 | }
76 |
77 | // Return closed contacts, both released and canceled
78 | public async Task GetDashboardClosed()
79 | {
80 | return await CallApiAsync("/api/dashboard/closed/").ConfigureAwait(false);
81 | }
82 |
83 | // Releases the escrow of contact specified by ID { contact_id }.
84 | // On success there"s a complimentary message on the data key.
85 | public async Task ContactRelease(string contactId)
86 | {
87 | return await CallApiAsync("/api/contact_release/" + contactId + "/", RequestType.Post).ConfigureAwait(false);
88 | }
89 |
90 | // Releases the escrow of contact specified by ID { contact_id }.
91 | // On success there"s a complimentary message on the data key.
92 | public async Task ContactReleasePin(string contactId, string pincode)
93 | {
94 | var args = new NameValueDictionary
95 | {
96 | {"pincode", pincode},
97 | };
98 |
99 | return await CallApiAsync("/api/contact_release_pin/" + contactId + "/", RequestType.Post, args).ConfigureAwait(false);
100 | }
101 |
102 | // Reads all messaging from the contact.Messages are on the message_list key.
103 | // On success there"s a complimentary message on the data key.
104 | // attachment_* fields exist only if there is an attachment.
105 | public async Task GetContactMessages(string contactId)
106 | {
107 | return await CallApiAsync("/api/contact_messages/" + contactId + "/").ConfigureAwait(false);
108 | }
109 |
110 | public async Task GetContactMessageAttachment(string contractId, string attachmentId)
111 | {
112 | return await _restApi.CallApiAsync($"/api/contact_message_attachment/{contractId}/{attachmentId}/", RequestType.Get, null, true).ConfigureAwait(false);
113 | }
114 |
115 | // Marks a contact as paid.
116 | // It is recommended to access this API through /api/online_buy_contacts/ entries" action key.
117 | public async Task MarkContactAsPaid(string contactId)
118 | {
119 | return await CallApiAsync("/api/contact_mark_as_paid/" + contactId + "/").ConfigureAwait(false);
120 | }
121 |
122 | // Post a message to contact
123 | public async Task PostMessageToContact(string contactId, string message, string attachFileName = null)
124 | {
125 | if (attachFileName != null && !File.Exists(attachFileName))
126 | throw new LocalBitcoinsException("PostMessageToContact", "File not found: " + attachFileName);
127 |
128 | NameValueDictionary args = null;
129 | if (!string.IsNullOrEmpty(message))
130 | {
131 | args = new NameValueDictionary
132 | {
133 | {"msg", message},
134 | };
135 | }
136 |
137 | return await _restApi.CallApiPostFileAsync("/api/contact_message_post/" + contactId + "/", args, attachFileName).ConfigureAwait(false);
138 | }
139 |
140 | // Starts a dispute with the contact, if possible.
141 | // You can provide a short description using topic. This helps support to deal with the problem.
142 | public async Task StartDispute(string contactId, string topic = null)
143 | {
144 | NameValueDictionary args = null;
145 | if (topic != null)
146 | {
147 | args = new NameValueDictionary
148 | {
149 | {"topic", topic},
150 | };
151 | }
152 |
153 | return await CallApiAsync("/api/contact_dispute/" + contactId + "/", RequestType.Post, args).ConfigureAwait(false);
154 | }
155 |
156 | // Cancels the contact, if possible
157 | public async Task CancelContact(string contactId)
158 | {
159 | return await CallApiAsync("/api/contact_cancel/" + contactId + "/", RequestType.Post).ConfigureAwait(false);
160 | }
161 |
162 | // Attempts to fund an unfunded local contact from the seller"s wallet.
163 | public async Task FundContact(string contactId)
164 | {
165 | return await CallApiAsync("/api/contact_fund/" + contactId + "/", RequestType.Post).ConfigureAwait(false);
166 | }
167 |
168 | // Attempts to create a contact to trade bitcoins.
169 | // Amount is a number in the advertisement"s fiat currency.
170 | // Returns the API URL to the newly created contact at actions.contact_url.
171 | // Whether the contact was able to be funded automatically is indicated at data.funded.
172 | // Only non-floating LOCAL_SELL may return unfunded, all other trade types either fund or fail.
173 | public async Task CreateContact(string contactId, decimal amount, string message = null)
174 | {
175 | var args = new NameValueDictionary
176 | {
177 | {"amount", amount.ToString(CultureInfo.InvariantCulture)},
178 | };
179 |
180 | if (message != null)
181 | args.Add("message", message);
182 |
183 | return await CallApiAsync("/api/contact_create/" + contactId + "/", RequestType.Post, args).ConfigureAwait(false);
184 | }
185 |
186 |
187 | // Gets information about a single contact you are involved in. Same fields as in /api/contacts/.
188 | public async Task GetContactInfo(string contactId)
189 | {
190 | return await CallApiAsync("/api/contact_info/" + contactId + "/").ConfigureAwait(false);
191 | }
192 |
193 | // contacts is a comma-separated list of contact IDs that you want to access in bulk.
194 | // The token owner needs to be either a buyer or seller in the contacts, contacts that do not pass this check are simply not returned.
195 | // A maximum of 50 contacts can be requested at a time.
196 | // The contacts are not returned in any particular order.
197 | public async Task GetContactsInfo(string contacts)
198 | {
199 | var args = new NameValueDictionary
200 | {
201 | {"contacts", contacts},
202 | };
203 | return await CallApiAsync("/api/contact_info/", RequestType.Get, args).ConfigureAwait(false);
204 | }
205 |
206 | // Returns maximum of 50 newest trade messages.
207 | // Messages are ordered by sending time, and the newest one is first.
208 | // The list has same format as /api/contact_messages/, but each message has also contact_id field.
209 | public async Task GetRecentMessages()
210 | {
211 | return await CallApiAsync("/api/recent_messages/").ConfigureAwait(false);
212 | }
213 |
214 | // Gives feedback to user.
215 | // Possible feedback values are: trust, positive, neutral, block, block_without_feedback as strings.
216 | // You may also set feedback message field with few exceptions. Feedback block_without_feedback clears the message and with block the message is mandatory.
217 | public async Task PostFeedbackToUser(string userName, string feedback, string message = null)
218 | {
219 | var args = new NameValueDictionary
220 | {
221 | {"feedback", feedback},
222 | };
223 |
224 | if (message != null)
225 | args.Add("msg", message);
226 |
227 | return await CallApiAsync("/api/feedback/" + userName + "/", RequestType.Post, args).ConfigureAwait(false);
228 | }
229 |
230 | // Gets information about the token owner"s wallet balance.
231 | public async Task GetWallet()
232 | {
233 | return await CallApiAsync("/api/wallet/").ConfigureAwait(false);
234 | }
235 |
236 | // Same as / api / wallet / above, but only returns the message, receiving_address_list and total fields.
237 | // (There"s also a receiving_address_count but it is always 1: only the latest receiving address is ever returned by this call.)
238 | // Use this instead if you don"t care about transactions at the moment.
239 | public async Task GetWalletBalance()
240 | {
241 | return await CallApiAsync("/api/wallet-balance/").ConfigureAwait(false);
242 | }
243 |
244 | // Sends amount bitcoins from the token owner"s wallet to address.
245 | // Note that this API requires its own API permission called Money.
246 | // On success, this API returns just a message indicating success.
247 | // It is highly recommended to minimize the lifetime of access tokens with the money permission.
248 | // Call / api / logout / to make the current token expire instantly.
249 | public async Task WalletSend(decimal amount, string address)
250 | {
251 | var args = new NameValueDictionary
252 | {
253 | {"amount", amount.ToString(CultureInfo.InvariantCulture)},
254 | {"address", address},
255 | };
256 |
257 | return await CallApiAsync("/api/wallet-send/", RequestType.Post, args).ConfigureAwait(false);
258 | }
259 |
260 | // As above, but needs the token owner"s active PIN code to succeed.
261 | // Look before you leap. You can check if a PIN code is valid without attempting a send with / api / pincode /.
262 | // Security concern: To get any security beyond the above API, do not retain the PIN code beyond a reasonable user session, a few minutes at most.
263 | // If you are planning to save the PIN code anyway, please save some headache and get the real no-pin - required money permission instead.
264 | public async Task WalletSendWithPin(decimal amount, string address, string pincode)
265 | {
266 | var args = new NameValueDictionary
267 | {
268 | {"amount", amount.ToString(CultureInfo.InvariantCulture)},
269 | {"address", address},
270 | {"pincode", pincode},
271 | };
272 |
273 | return await CallApiAsync("/api/wallet-send-pin/", RequestType.Post, args).ConfigureAwait(false);
274 | }
275 |
276 | // Gets an unused receiving address for the token owner"s wallet, its address given in the address key of the response.
277 | // Note that this API may keep returning the same(unused) address if called repeatedly.
278 | public async Task GetWalletAddress()
279 | {
280 | return await CallApiAsync("/api/wallet-addr/", RequestType.Post).ConfigureAwait(false);
281 | }
282 |
283 | // Gets the current outgoing and deposit fees in bitcoins (BTC).
284 | public async Task GetFees()
285 | {
286 | return await CallApiAsync("/api/fees/", RequestType.Get).ConfigureAwait(false);
287 | }
288 |
289 | // Expires the current access token immediately.
290 | // To get a new token afterwards, public apps will need to reauthenticate, confidential apps can turn in a refresh token.
291 | public async Task Logout()
292 | {
293 | return await CallApiAsync("/api/logout/", RequestType.Post).ConfigureAwait(false);
294 | }
295 |
296 | // Lists the token owner"s all ads on the data key ad_list, optionally filtered. If there"s a lot of ads, the listing will be paginated.
297 | // Refer to the ad editing pages for the field meanings.List item structure is like so:
298 | public async Task GetOwnAds()
299 | {
300 | return await CallApiAsync("/api/ads/", RequestType.Get).ConfigureAwait(false);
301 | }
302 |
303 | // Get ad
304 | public async Task GetAd(string adId)
305 | {
306 | return await CallApiAsync("/api/ad-get/" + adId + "/").ConfigureAwait(false);
307 | }
308 |
309 | public async Task GetAdList(IEnumerable adList)
310 | {
311 | var args = new Dictionary
312 | {
313 | {"ads", string.Join(",", adList) },
314 | };
315 |
316 | return await CallApiAsync("/api/ad-get/", RequestType.Get, args).ConfigureAwait(false);
317 | }
318 |
319 | // Delete ad
320 | public async Task DeleteAd(string adId)
321 | {
322 | return await CallApiAsync("/api/ad-delete/" + adId + "/", RequestType.Post).ConfigureAwait(false);
323 | }
324 |
325 | private static Dictionary ParseAdData(dynamic adData)
326 | {
327 | var args = new Dictionary
328 | {
329 | {"lat", (string)adData.lat},
330 | {"price_equation", (string)adData.price_equation},
331 | {"lon", (string)adData.lon},
332 | {"countrycode", (string)adData.countrycode},
333 | {"currency", (string)adData.currency},
334 |
335 | {"min_amount", (string)adData.min_amount},
336 | {"max_amount", (string)adData.max_amount},
337 |
338 | {"msg", (string)adData.msg},
339 | {"require_identification", (string)adData.require_identification},
340 | {"sms_verification_required", (string)adData.sms_verification_required},
341 | {"require_trusted_by_advertiser", (string)adData.require_trusted_by_advertiser},
342 | {"trusted_required", (string)adData.trusted_required},
343 | {"track_max_amount", (string)adData.track_max_amount},
344 | {"email", (string)adData.email},
345 |
346 | {"visible", (string)adData.visible},
347 | };
348 |
349 | if((string)adData.opening_hours != "null")
350 | {
351 | args["opening_hours"] = (string)adData.opening_hours;
352 | }
353 |
354 | if (!string.IsNullOrEmpty((string)adData.limit_to_fiat_amounts))
355 | {
356 | args["limit_to_fiat_amounts"] = (string)adData.limit_to_fiat_amounts;
357 | }
358 |
359 | if (!string.IsNullOrEmpty((string)adData.bank_name))
360 | {
361 | args["bank_name"] = (string)adData.bank_name;
362 | }
363 |
364 | string phone_number = adData.account_details?.phone_number;
365 | if (adData.account_details?.phone_number != null)
366 | {
367 | args["details-phone_number"] = phone_number;
368 | }
369 |
370 | return args;
371 | }
372 |
373 | // Edit ad visibility
374 | public async Task EditAdVisiblity(string adId, bool visible)
375 | {
376 | var oldAd = await GetAd(adId).ConfigureAwait(false);
377 | // Debug.WriteLine(ad.ToString());
378 |
379 | if ((int)oldAd.data.ad_count < 1)
380 | {
381 | throw new LocalBitcoinsException("EditAdVisiblity", "Ad not found. Id=" + adId);
382 | }
383 |
384 | var args = ParseAdData(oldAd.data.ad_list[0].data);
385 |
386 | args["visible"] = visible ? "1" : "0";
387 |
388 | return await CallApiAsync("/api/ad/" + adId + "/", RequestType.Post, args);
389 | }
390 |
391 | // Edit ad
392 | public async Task EditAd(string adId, Dictionary values, bool preloadAd = false)
393 | {
394 | if (values == null)
395 | throw new ArgumentNullException(nameof(values));
396 |
397 | Dictionary args;
398 |
399 | if (preloadAd)
400 | {
401 | var oldAd = await GetAd(adId).ConfigureAwait(false);
402 | // Debug.WriteLine(ad.ToString());
403 |
404 | if ((int)oldAd.data.ad_count < 1)
405 | {
406 | throw new LocalBitcoinsException("EditAd", "Ad not found. Id=" + adId);
407 | }
408 |
409 | args = ParseAdData(oldAd.data.ad_list[0].data);
410 |
411 | // Copy user values
412 | foreach (var val in values)
413 | {
414 | args[val.Key] = val.Value;
415 | }
416 | }
417 | else
418 | {
419 | args = values;
420 | }
421 |
422 | return await CallApiAsync("/api/ad/" + adId + "/", RequestType.Post, args).ConfigureAwait(false);
423 | }
424 |
425 | // Edit ad Price equation formula
426 | public async Task EditAdPriceEquation(string adId, decimal priceEquation)
427 | {
428 | if (priceEquation <= 0)
429 | throw new ArgumentOutOfRangeException(nameof(priceEquation));
430 |
431 | var args = new Dictionary()
432 | {
433 | ["price_equation"] = priceEquation.ToString(CultureInfo.InvariantCulture)
434 | };
435 |
436 | return await CallApiAsync("/api/ad-equation/" + adId + "/", RequestType.Post, args).ConfigureAwait(false);
437 | }
438 |
439 | // Retrieves recent notifications.
440 | public async Task GetNotifications()
441 | {
442 | return await CallApiAsync("/api/notifications/").ConfigureAwait(false);
443 | }
444 |
445 | // Marks specific notification as read.
446 | public async Task NotificationMarkAsRead(string notificationId)
447 | {
448 | return await CallApiAsync("/api/notifications/mark_as_read/" + notificationId + "/", RequestType.Post).ConfigureAwait(false);
449 | }
450 |
451 | ///
452 | /// This API returns buy Bitcoin online ads. It is closely modeled after the online ad listings on LocalBitcoins.com.
453 | ///
454 | /// Three letter currency code
455 | /// An example of a valid argument is national-bank-transfer.
456 | /// Page number, by default 1
457 | /// Ads are returned in the same structure as /api/ads/.
458 | public async Task PublicMarket_BuyBitcoinsOnlineByCurrency(string currency, string paymentMethod = null, int page = 1)
459 | {
460 | var uri = "/buy-bitcoins-online/" + currency + "/";
461 | if (paymentMethod != null)
462 | uri += paymentMethod + "/";
463 |
464 | uri += ".json";
465 |
466 | if (page > 1)
467 | uri += "?page=" + page.ToString(CultureInfo.InvariantCulture);
468 |
469 | return await _restApi.CallPublicApiAsync(uri).ConfigureAwait(false);
470 | }
471 |
472 | ///
473 | /// This API returns sell Bitcoin online ads. It is closely modeled after the online ad listings on LocalBitcoins.com.
474 | ///
475 | /// Three letter currency code
476 | /// An example of a valid argument is national-bank-transfer.
477 | /// Page number, by default 1
478 | /// Ads are returned in the same structure as /api/ads/.
479 | public async Task PublicMarket_SellBitcoinsOnlineByCurrency(string currency, string paymentMethod = null, int page = 1)
480 | {
481 | var uri = "/sell-bitcoins-online/" + currency + "/";
482 | if (paymentMethod != null)
483 | uri += paymentMethod + "/";
484 |
485 | uri += ".json";
486 |
487 | if (page > 1)
488 | uri += "?page=" + page.ToString(CultureInfo.InvariantCulture);
489 |
490 | return await _restApi.CallPublicApiAsync(uri).ConfigureAwait(false);
491 | }
492 | }
493 | }
494 |
--------------------------------------------------------------------------------
/src/LocalBitcoinsApi/LocalBitcoinsException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Globalization;
3 | using System.Runtime.Serialization;
4 |
5 | namespace LocalBitcoins
6 | {
7 | [Serializable]
8 | public class LocalBitcoinsException : Exception
9 | {
10 | public string RequestMethod { get; }
11 | public dynamic DataJson { get; private set; }
12 |
13 | public LocalBitcoinsException()
14 | { }
15 |
16 | public LocalBitcoinsException(string message, Exception innerException)
17 | : base(message, innerException)
18 | {
19 | }
20 |
21 | public LocalBitcoinsException(string message)
22 | : base(message)
23 | {
24 | }
25 |
26 | public LocalBitcoinsException(string callerMethod, string message)
27 | : base(message)
28 | {
29 | RequestMethod = callerMethod;
30 | }
31 |
32 | public static void ThrowException(string callerMethod, dynamic json)
33 | {
34 | var ex = new LocalBitcoinsException(callerMethod, FormatMessage(callerMethod, json))
35 | {
36 | DataJson = json
37 | };
38 |
39 | throw ex;
40 | }
41 |
42 | private static string FormatMessage(string callerMethod, dynamic json)
43 | {
44 | if (json == null)
45 | return string.Format(CultureInfo.InvariantCulture, "Failed request {0}. Message: Null", callerMethod);
46 |
47 | var result =
48 | string.Format(CultureInfo.InvariantCulture, "Failed request {0}. Message: {1}. Error Code: {2}.", callerMethod, (string)json.error.message, (int)json.error.error_code);
49 |
50 | if(json.error.error_lists != null)
51 | {
52 | result += " Details: " + json.error.error_lists.ToString();
53 | }
54 |
55 | return result;
56 | }
57 |
58 | protected LocalBitcoinsException(SerializationInfo serializationInfo, StreamingContext streamingContext)
59 | : base(serializationInfo, streamingContext)
60 | {
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/LocalBitcoinsApi/LocalBitcoinsRestApi.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Globalization;
5 | using System.IO;
6 | using System.Linq;
7 | using System.Net.Http;
8 | using System.Net.Http.Headers;
9 | using System.Security.Cryptography;
10 | using System.Text;
11 | using System.Threading.Tasks;
12 |
13 | namespace LocalBitcoins
14 | {
15 | internal class LocalBitcoinsRestApi
16 | {
17 | private const string DefaultApiUrl = "https://localbitcoins.net/";
18 |
19 | private static readonly DateTime _unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
20 |
21 | private readonly HttpClient _client;
22 |
23 | private readonly string _accessKey;
24 | private readonly string _secretKey;
25 |
26 | public LocalBitcoinsRestApi(string accessKey, string secretKey, int apiTimeoutSec, string overrideBaseAddress = null)
27 | {
28 | if (overrideBaseAddress == null)
29 | overrideBaseAddress = DefaultApiUrl;
30 |
31 | _accessKey = accessKey;
32 | _secretKey = secretKey;
33 |
34 | _client = new HttpClient()
35 | {
36 | BaseAddress = new Uri(overrideBaseAddress), // apiv3
37 | Timeout = TimeSpan.FromSeconds(apiTimeoutSec)
38 | };
39 | }
40 |
41 | #region Public Methods
42 |
43 | public async Task CallPublicApiAsync(string relativeUrl)
44 | {
45 | var response = await _client.GetAsync(relativeUrl).ConfigureAwait(false);
46 | var resultAsString = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
47 | var json = JsonConvert.DeserializeObject(resultAsString);
48 | return json;
49 | }
50 |
51 | public async Task CallApiAsync(string apiCommand, RequestType requestType,
52 | Dictionary args, bool getAsBinary = false)
53 | {
54 | HttpContent httpContent = null;
55 |
56 | if (requestType == RequestType.Post)
57 | {
58 | if (args != null && args.Any())
59 | {
60 | httpContent = new FormUrlEncodedContent(args);
61 | }
62 | }
63 |
64 | try
65 | {
66 | var nonce = GetNonce();
67 | var signature = GetSignature(apiCommand, nonce, args);
68 |
69 | var relativeUrl = apiCommand;
70 | if (requestType == RequestType.Get)
71 | {
72 | if (args != null && args.Any())
73 | {
74 | relativeUrl += "?" + UrlEncodeParams(args);
75 | }
76 | }
77 |
78 | using (var request = new HttpRequestMessage(
79 | requestType == RequestType.Get ? HttpMethod.Get : HttpMethod.Post,
80 | new Uri(_client.BaseAddress, relativeUrl)
81 | ))
82 | {
83 | request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
84 | request.Headers.Add("Apiauth-Key", _accessKey);
85 | request.Headers.Add("Apiauth-Nonce", nonce);
86 | request.Headers.Add("Apiauth-Signature", signature);
87 | request.Content = httpContent;
88 |
89 | var response = await _client.SendAsync(request).ConfigureAwait(false);
90 | if (!response.IsSuccessStatusCode)
91 | {
92 | var resultAsString = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
93 | var json = JsonConvert.DeserializeObject(resultAsString);
94 | LocalBitcoinsException.ThrowException(apiCommand, json);
95 | }
96 |
97 | if (getAsBinary)
98 | {
99 | var resultAsByteArray = await response.Content.ReadAsByteArrayAsync().ConfigureAwait(false);
100 | return resultAsByteArray;
101 | }
102 | else
103 | {
104 | var resultAsString = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
105 | var json = JsonConvert.DeserializeObject(resultAsString);
106 | return json;
107 | }
108 | }
109 | }
110 | finally
111 | {
112 | httpContent?.Dispose();
113 | }
114 | }
115 |
116 | public async Task CallApiPostFileAsync(string apiCommand, Dictionary args, string fileName)
117 | {
118 | using (var httpContent = new MultipartFormDataContent())
119 | {
120 | if (args != null)
121 | {
122 | foreach (var keyValuePair in args)
123 | {
124 | httpContent.Add(new StringContent(keyValuePair.Value),
125 | string.Format(CultureInfo.InvariantCulture, "\"{0}\"", keyValuePair.Key));
126 | }
127 | }
128 |
129 | if (fileName != null)
130 | {
131 | var fileBytes = File.ReadAllBytes(fileName);
132 | httpContent.Add(new ByteArrayContent(fileBytes), "\"document\"",
133 | "\"" + Path.GetFileName(fileName) + "\"");
134 | }
135 |
136 | var bodyAsBytes = await httpContent.ReadAsByteArrayAsync().ConfigureAwait(false);
137 |
138 | var nonce = GetNonce();
139 | var signature = GetSignatureBinary(apiCommand, nonce, bodyAsBytes);
140 |
141 | using (var request = new HttpRequestMessage(
142 | HttpMethod.Post,
143 | new Uri(_client.BaseAddress, apiCommand)
144 | ))
145 | {
146 | request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
147 | request.Headers.Add("Apiauth-Key", _accessKey);
148 | request.Headers.Add("Apiauth-Nonce", nonce);
149 | request.Headers.Add("Apiauth-Signature", signature);
150 | request.Content = httpContent;
151 |
152 | var response = await _client.SendAsync(request).ConfigureAwait(false);
153 | if (!response.IsSuccessStatusCode)
154 | {
155 | var resultAsString = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
156 | var json = JsonConvert.DeserializeObject(resultAsString);
157 | LocalBitcoinsException.ThrowException(apiCommand, json);
158 | }
159 |
160 | {
161 | var resultAsString = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
162 | var json = JsonConvert.DeserializeObject(resultAsString);
163 | return json;
164 | }
165 | }
166 | }
167 | }
168 |
169 | #endregion Public methods
170 |
171 |
172 | #region Private methods
173 |
174 | private static string GetNonce()
175 | {
176 | var nonce = (long)((DateTime.UtcNow - _unixEpoch).TotalMilliseconds * 1000);
177 | return nonce.ToString(CultureInfo.InvariantCulture);
178 | }
179 |
180 | private string GetSignature(string apiCommand, string nonce, Dictionary args)
181 | {
182 | return GetSignature(_accessKey, _secretKey, apiCommand, nonce, args);
183 | }
184 |
185 | private static string GetSignature(string accessKey, string secretKey, string apiCommand, string nonce, Dictionary args)
186 | {
187 | string paramsStr = null;
188 |
189 | if (args != null && args.Any())
190 | {
191 | paramsStr = UrlEncodeParams(args);
192 | }
193 |
194 | var encoding = new ASCIIEncoding();
195 | var secretByte = encoding.GetBytes(secretKey);
196 | using (var hmacsha256 = new HMACSHA256(secretByte))
197 | {
198 | var message = nonce + accessKey + apiCommand;
199 | if (paramsStr != null)
200 | {
201 | message += paramsStr;
202 | }
203 | var messageByte = encoding.GetBytes(message);
204 |
205 | var signature = Utility.ByteToString(hmacsha256.ComputeHash(messageByte));
206 | return signature;
207 | }
208 | }
209 |
210 | private static byte[] CombineBytes(byte[] first, byte[] second)
211 | {
212 | byte[] ret = new byte[first.Length + second.Length];
213 | Buffer.BlockCopy(first, 0, ret, 0, first.Length);
214 | Buffer.BlockCopy(second, 0, ret, first.Length, second.Length);
215 | return ret;
216 | }
217 |
218 | private string GetSignatureBinary(string apiCommand, string nonce, byte[] paramBytes)
219 | {
220 | return GetSignatureBinary(_accessKey, _secretKey, apiCommand, nonce, paramBytes);
221 | }
222 |
223 | private static string GetSignatureBinary(string accessKey, string secretKey, string apiCommand, string nonce, byte[] paramBytes)
224 | {
225 | var encoding = new ASCIIEncoding();
226 | var secretByte = encoding.GetBytes(secretKey);
227 | using (var hmacsha256 = new HMACSHA256(secretByte))
228 | {
229 | var message = nonce + accessKey + apiCommand;
230 | var messageByte = encoding.GetBytes(message);
231 | if (paramBytes != null)
232 | {
233 | messageByte = CombineBytes(messageByte, paramBytes);
234 | }
235 |
236 | var signature = Utility.ByteToString(hmacsha256.ComputeHash(messageByte));
237 | return signature;
238 | }
239 | }
240 |
241 | private static string UrlEncodeString(string text)
242 | {
243 | var result = text == null ? "" : Uri.EscapeDataString(text).Replace("%20", "+");
244 | return result;
245 | }
246 |
247 | private static string UrlEncodeParams(Dictionary args)
248 | {
249 | var sb = new StringBuilder();
250 |
251 | var arr =
252 | args.Select(
253 | x =>
254 | string.Format(CultureInfo.InvariantCulture, "{0}={1}", UrlEncodeString(x.Key), UrlEncodeString(x.Value))).ToArray();
255 |
256 | sb.Append(string.Join("&", arr));
257 | return sb.ToString();
258 | }
259 |
260 | #endregion Private methods
261 | }
262 | }
263 |
--------------------------------------------------------------------------------
/src/LocalBitcoinsApi/RequestType.cs:
--------------------------------------------------------------------------------
1 | namespace LocalBitcoins
2 | {
3 | public enum RequestType
4 | {
5 | Get,
6 | Post
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/LocalBitcoinsApi/Utility.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Globalization;
3 | using System.Linq;
4 |
5 | namespace LocalBitcoins
6 | {
7 | internal static class Utility
8 | {
9 | public static string ByteToString(IEnumerable buff)
10 | {
11 | return buff.Aggregate("", (current, t) => current + t.ToString("X2", CultureInfo.InvariantCulture));
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/LocalBitcoinsApi/UtilityExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 |
4 | namespace LocalBitcoins
5 | {
6 | internal static class UtilityExtensions
7 | {
8 | /*
9 | ///
10 | /// Waits for the task to complete, unwrapping any exceptions.
11 | ///
12 | /// The task. May not be null.
13 | public static void WaitAndUnwrapException(this Task task)
14 | {
15 | if (task == null)
16 | throw new ArgumentNullException(nameof(task));
17 | task.GetAwaiter().GetResult();
18 | }
19 | */
20 | ///
21 | /// Waits for the task to complete, unwrapping any exceptions.
22 | ///
23 | /// The type of the result of the task.
24 | /// The task. May not be null.
25 | /// The result of the task.
26 | public static TResult WaitAndUnwrapException(this Task task)
27 | {
28 | if (task == null)
29 | throw new ArgumentNullException(nameof(task));
30 | return task.GetAwaiter().GetResult();
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------