├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ └── go.yml ├── .gitignore ├── LICENSE ├── README.md ├── README_ZH.md ├── abi ├── addr_abi_map.go ├── addr_abi_map_test.go └── erc20abi.go ├── client.go ├── client_test.go ├── docs ├── _config.yml ├── images │ └── jetbrains │ │ └── jetbrains-variant-3.svg └── index.md ├── examples ├── aes │ └── aes.go ├── ethclient │ └── ethclient.go └── pool-address │ └── main.go ├── go.mod ├── go.sum ├── signer.go ├── tests └── abi_json_test.go ├── uniswap ├── address.go ├── address_test.go ├── multicall.go ├── path.go ├── path_test.go ├── sqrt_price_x96.go └── sqrt_price_x96_test.go └── wallet ├── aes.go ├── aes_test.go ├── hd.go └── hd_test.go /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | 16 | - name: Run golangci-lint 17 | uses: golangci/golangci-lint-action@v2.5.2 18 | 19 | - name: Set up Go 20 | uses: actions/setup-go@v2 21 | with: 22 | go-version: 1.16 23 | 24 | - name: Build 25 | run: go build -v ./... 26 | 27 | - name: Test 28 | run: go test -v ./... 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | .idea -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 2.1, February 1999 3 | 4 | Copyright (C) 1991, 1999 Free Software Foundation, Inc. 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | [This is the first released version of the Lesser GPL. It also counts 10 | as the successor of the GNU Library Public License, version 2, hence 11 | the version number 2.1.] 12 | 13 | Preamble 14 | 15 | The licenses for most software are designed to take away your 16 | freedom to share and change it. By contrast, the GNU General Public 17 | Licenses are intended to guarantee your freedom to share and change 18 | free software--to make sure the software is free for all its users. 19 | 20 | This license, the Lesser General Public License, applies to some 21 | specially designated software packages--typically libraries--of the 22 | Free Software Foundation and other authors who decide to use it. You 23 | can use it too, but we suggest you first think carefully about whether 24 | this license or the ordinary General Public License is the better 25 | strategy to use in any particular case, based on the explanations below. 26 | 27 | When we speak of free software, we are referring to freedom of use, 28 | not price. Our General Public Licenses are designed to make sure that 29 | you have the freedom to distribute copies of free software (and charge 30 | for this service if you wish); that you receive source code or can get 31 | it if you want it; that you can change the software and use pieces of 32 | it in new free programs; and that you are informed that you can do 33 | these things. 34 | 35 | To protect your rights, we need to make restrictions that forbid 36 | distributors to deny you these rights or to ask you to surrender these 37 | rights. These restrictions translate to certain responsibilities for 38 | you if you distribute copies of the library or if you modify it. 39 | 40 | For example, if you distribute copies of the library, whether gratis 41 | or for a fee, you must give the recipients all the rights that we gave 42 | you. You must make sure that they, too, receive or can get the source 43 | code. If you link other code with the library, you must provide 44 | complete object files to the recipients, so that they can relink them 45 | with the library after making changes to the library and recompiling 46 | it. And you must show them these terms so they know their rights. 47 | 48 | We protect your rights with a two-step method: (1) we copyright the 49 | library, and (2) we offer you this license, which gives you legal 50 | permission to copy, distribute and/or modify the library. 51 | 52 | To protect each distributor, we want to make it very clear that 53 | there is no warranty for the free library. Also, if the library is 54 | modified by someone else and passed on, the recipients should know 55 | that what they have is not the original version, so that the original 56 | author's reputation will not be affected by problems that might be 57 | introduced by others. 58 | 59 | Finally, software patents pose a constant threat to the existence of 60 | any free program. We wish to make sure that a company cannot 61 | effectively restrict the users of a free program by obtaining a 62 | restrictive license from a patent holder. Therefore, we insist that 63 | any patent license obtained for a version of the library must be 64 | consistent with the full freedom of use specified in this license. 65 | 66 | Most GNU software, including some libraries, is covered by the 67 | ordinary GNU General Public License. This license, the GNU Lesser 68 | General Public License, applies to certain designated libraries, and 69 | is quite different from the ordinary General Public License. We use 70 | this license for certain libraries in order to permit linking those 71 | libraries into non-free programs. 72 | 73 | When a program is linked with a library, whether statically or using 74 | a shared library, the combination of the two is legally speaking a 75 | combined work, a derivative of the original library. The ordinary 76 | General Public License therefore permits such linking only if the 77 | entire combination fits its criteria of freedom. The Lesser General 78 | Public License permits more lax criteria for linking other code with 79 | the library. 80 | 81 | We call this license the "Lesser" General Public License because it 82 | does Less to protect the user's freedom than the ordinary General 83 | Public License. It also provides other free software developers Less 84 | of an advantage over competing non-free programs. These disadvantages 85 | are the reason we use the ordinary General Public License for many 86 | libraries. However, the Lesser license provides advantages in certain 87 | special circumstances. 88 | 89 | For example, on rare occasions, there may be a special need to 90 | encourage the widest possible use of a certain library, so that it becomes 91 | a de-facto standard. To achieve this, non-free programs must be 92 | allowed to use the library. A more frequent case is that a free 93 | library does the same job as widely used non-free libraries. In this 94 | case, there is little to gain by limiting the free library to free 95 | software only, so we use the Lesser General Public License. 96 | 97 | In other cases, permission to use a particular library in non-free 98 | programs enables a greater number of people to use a large body of 99 | free software. For example, permission to use the GNU C Library in 100 | non-free programs enables many more people to use the whole GNU 101 | operating system, as well as its variant, the GNU/Linux operating 102 | system. 103 | 104 | Although the Lesser General Public License is Less protective of the 105 | users' freedom, it does ensure that the user of a program that is 106 | linked with the Library has the freedom and the wherewithal to run 107 | that program using a modified version of the Library. 108 | 109 | The precise terms and conditions for copying, distribution and 110 | modification follow. Pay close attention to the difference between a 111 | "work based on the library" and a "work that uses the library". The 112 | former contains code derived from the library, whereas the latter must 113 | be combined with the library in order to run. 114 | 115 | GNU LESSER GENERAL PUBLIC LICENSE 116 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 117 | 118 | 0. This License Agreement applies to any software library or other 119 | program which contains a notice placed by the copyright holder or 120 | other authorized party saying it may be distributed under the terms of 121 | this Lesser General Public License (also called "this License"). 122 | Each licensee is addressed as "you". 123 | 124 | A "library" means a collection of software functions and/or data 125 | prepared so as to be conveniently linked with application programs 126 | (which use some of those functions and data) to form executables. 127 | 128 | The "Library", below, refers to any such software library or work 129 | which has been distributed under these terms. A "work based on the 130 | Library" means either the Library or any derivative work under 131 | copyright law: that is to say, a work containing the Library or a 132 | portion of it, either verbatim or with modifications and/or translated 133 | straightforwardly into another language. (Hereinafter, translation is 134 | included without limitation in the term "modification".) 135 | 136 | "Source code" for a work means the preferred form of the work for 137 | making modifications to it. For a library, complete source code means 138 | all the source code for all modules it contains, plus any associated 139 | interface definition files, plus the scripts used to control compilation 140 | and installation of the library. 141 | 142 | Activities other than copying, distribution and modification are not 143 | covered by this License; they are outside its scope. The act of 144 | running a program using the Library is not restricted, and output from 145 | such a program is covered only if its contents constitute a work based 146 | on the Library (independent of the use of the Library in a tool for 147 | writing it). Whether that is true depends on what the Library does 148 | and what the program that uses the Library does. 149 | 150 | 1. You may copy and distribute verbatim copies of the Library's 151 | complete source code as you receive it, in any medium, provided that 152 | you conspicuously and appropriately publish on each copy an 153 | appropriate copyright notice and disclaimer of warranty; keep intact 154 | all the notices that refer to this License and to the absence of any 155 | warranty; and distribute a copy of this License along with the 156 | Library. 157 | 158 | You may charge a fee for the physical act of transferring a copy, 159 | and you may at your option offer warranty protection in exchange for a 160 | fee. 161 | 162 | 2. You may modify your copy or copies of the Library or any portion 163 | of it, thus forming a work based on the Library, and copy and 164 | distribute such modifications or work under the terms of Section 1 165 | above, provided that you also meet all of these conditions: 166 | 167 | a) The modified work must itself be a software library. 168 | 169 | b) You must cause the files modified to carry prominent notices 170 | stating that you changed the files and the date of any change. 171 | 172 | c) You must cause the whole of the work to be licensed at no 173 | charge to all third parties under the terms of this License. 174 | 175 | d) If a facility in the modified Library refers to a function or a 176 | table of data to be supplied by an application program that uses 177 | the facility, other than as an argument passed when the facility 178 | is invoked, then you must make a good faith effort to ensure that, 179 | in the event an application does not supply such function or 180 | table, the facility still operates, and performs whatever part of 181 | its purpose remains meaningful. 182 | 183 | (For example, a function in a library to compute square roots has 184 | a purpose that is entirely well-defined independent of the 185 | application. Therefore, Subsection 2d requires that any 186 | application-supplied function or table used by this function must 187 | be optional: if the application does not supply it, the square 188 | root function must still compute square roots.) 189 | 190 | These requirements apply to the modified work as a whole. If 191 | identifiable sections of that work are not derived from the Library, 192 | and can be reasonably considered independent and separate works in 193 | themselves, then this License, and its terms, do not apply to those 194 | sections when you distribute them as separate works. But when you 195 | distribute the same sections as part of a whole which is a work based 196 | on the Library, the distribution of the whole must be on the terms of 197 | this License, whose permissions for other licensees extend to the 198 | entire whole, and thus to each and every part regardless of who wrote 199 | it. 200 | 201 | Thus, it is not the intent of this section to claim rights or contest 202 | your rights to work written entirely by you; rather, the intent is to 203 | exercise the right to control the distribution of derivative or 204 | collective works based on the Library. 205 | 206 | In addition, mere aggregation of another work not based on the Library 207 | with the Library (or with a work based on the Library) on a volume of 208 | a storage or distribution medium does not bring the other work under 209 | the scope of this License. 210 | 211 | 3. You may opt to apply the terms of the ordinary GNU General Public 212 | License instead of this License to a given copy of the Library. To do 213 | this, you must alter all the notices that refer to this License, so 214 | that they refer to the ordinary GNU General Public License, version 2, 215 | instead of to this License. (If a newer version than version 2 of the 216 | ordinary GNU General Public License has appeared, then you can specify 217 | that version instead if you wish.) Do not make any other change in 218 | these notices. 219 | 220 | Once this change is made in a given copy, it is irreversible for 221 | that copy, so the ordinary GNU General Public License applies to all 222 | subsequent copies and derivative works made from that copy. 223 | 224 | This option is useful when you wish to copy part of the code of 225 | the Library into a program that is not a library. 226 | 227 | 4. You may copy and distribute the Library (or a portion or 228 | derivative of it, under Section 2) in object code or executable form 229 | under the terms of Sections 1 and 2 above provided that you accompany 230 | it with the complete corresponding machine-readable source code, which 231 | must be distributed under the terms of Sections 1 and 2 above on a 232 | medium customarily used for software interchange. 233 | 234 | If distribution of object code is made by offering access to copy 235 | from a designated place, then offering equivalent access to copy the 236 | source code from the same place satisfies the requirement to 237 | distribute the source code, even though third parties are not 238 | compelled to copy the source along with the object code. 239 | 240 | 5. A program that contains no derivative of any portion of the 241 | Library, but is designed to work with the Library by being compiled or 242 | linked with it, is called a "work that uses the Library". Such a 243 | work, in isolation, is not a derivative work of the Library, and 244 | therefore falls outside the scope of this License. 245 | 246 | However, linking a "work that uses the Library" with the Library 247 | creates an executable that is a derivative of the Library (because it 248 | contains portions of the Library), rather than a "work that uses the 249 | library". The executable is therefore covered by this License. 250 | Section 6 states terms for distribution of such executables. 251 | 252 | When a "work that uses the Library" uses material from a header file 253 | that is part of the Library, the object code for the work may be a 254 | derivative work of the Library even though the source code is not. 255 | Whether this is true is especially significant if the work can be 256 | linked without the Library, or if the work is itself a library. The 257 | threshold for this to be true is not precisely defined by law. 258 | 259 | If such an object file uses only numerical parameters, data 260 | structure layouts and accessors, and small macros and small inline 261 | functions (ten lines or less in length), then the use of the object 262 | file is unrestricted, regardless of whether it is legally a derivative 263 | work. (Executables containing this object code plus portions of the 264 | Library will still fall under Section 6.) 265 | 266 | Otherwise, if the work is a derivative of the Library, you may 267 | distribute the object code for the work under the terms of Section 6. 268 | Any executables containing that work also fall under Section 6, 269 | whether or not they are linked directly with the Library itself. 270 | 271 | 6. As an exception to the Sections above, you may also combine or 272 | link a "work that uses the Library" with the Library to produce a 273 | work containing portions of the Library, and distribute that work 274 | under terms of your choice, provided that the terms permit 275 | modification of the work for the customer's own use and reverse 276 | engineering for debugging such modifications. 277 | 278 | You must give prominent notice with each copy of the work that the 279 | Library is used in it and that the Library and its use are covered by 280 | this License. You must supply a copy of this License. If the work 281 | during execution displays copyright notices, you must include the 282 | copyright notice for the Library among them, as well as a reference 283 | directing the user to the copy of this License. Also, you must do one 284 | of these things: 285 | 286 | a) Accompany the work with the complete corresponding 287 | machine-readable source code for the Library including whatever 288 | changes were used in the work (which must be distributed under 289 | Sections 1 and 2 above); and, if the work is an executable linked 290 | with the Library, with the complete machine-readable "work that 291 | uses the Library", as object code and/or source code, so that the 292 | user can modify the Library and then relink to produce a modified 293 | executable containing the modified Library. (It is understood 294 | that the user who changes the contents of definitions files in the 295 | Library will not necessarily be able to recompile the application 296 | to use the modified definitions.) 297 | 298 | b) Use a suitable shared library mechanism for linking with the 299 | Library. A suitable mechanism is one that (1) uses at run time a 300 | copy of the library already present on the user's computer system, 301 | rather than copying library functions into the executable, and (2) 302 | will operate properly with a modified version of the library, if 303 | the user installs one, as long as the modified version is 304 | interface-compatible with the version that the work was made with. 305 | 306 | c) Accompany the work with a written offer, valid for at 307 | least three years, to give the same user the materials 308 | specified in Subsection 6a, above, for a charge no more 309 | than the cost of performing this distribution. 310 | 311 | d) If distribution of the work is made by offering access to copy 312 | from a designated place, offer equivalent access to copy the above 313 | specified materials from the same place. 314 | 315 | e) Verify that the user has already received a copy of these 316 | materials or that you have already sent this user a copy. 317 | 318 | For an executable, the required form of the "work that uses the 319 | Library" must include any data and utility programs needed for 320 | reproducing the executable from it. However, as a special exception, 321 | the materials to be distributed need not include anything that is 322 | normally distributed (in either source or binary form) with the major 323 | components (compiler, kernel, and so on) of the operating system on 324 | which the executable runs, unless that component itself accompanies 325 | the executable. 326 | 327 | It may happen that this requirement contradicts the license 328 | restrictions of other proprietary libraries that do not normally 329 | accompany the operating system. Such a contradiction means you cannot 330 | use both them and the Library together in an executable that you 331 | distribute. 332 | 333 | 7. You may place library facilities that are a work based on the 334 | Library side-by-side in a single library together with other library 335 | facilities not covered by this License, and distribute such a combined 336 | library, provided that the separate distribution of the work based on 337 | the Library and of the other library facilities is otherwise 338 | permitted, and provided that you do these two things: 339 | 340 | a) Accompany the combined library with a copy of the same work 341 | based on the Library, uncombined with any other library 342 | facilities. This must be distributed under the terms of the 343 | Sections above. 344 | 345 | b) Give prominent notice with the combined library of the fact 346 | that part of it is a work based on the Library, and explaining 347 | where to find the accompanying uncombined form of the same work. 348 | 349 | 8. You may not copy, modify, sublicense, link with, or distribute 350 | the Library except as expressly provided under this License. Any 351 | attempt otherwise to copy, modify, sublicense, link with, or 352 | distribute the Library is void, and will automatically terminate your 353 | rights under this License. However, parties who have received copies, 354 | or rights, from you under this License will not have their licenses 355 | terminated so long as such parties remain in full compliance. 356 | 357 | 9. You are not required to accept this License, since you have not 358 | signed it. However, nothing else grants you permission to modify or 359 | distribute the Library or its derivative works. These actions are 360 | prohibited by law if you do not accept this License. Therefore, by 361 | modifying or distributing the Library (or any work based on the 362 | Library), you indicate your acceptance of this License to do so, and 363 | all its terms and conditions for copying, distributing or modifying 364 | the Library or works based on it. 365 | 366 | 10. Each time you redistribute the Library (or any work based on the 367 | Library), the recipient automatically receives a license from the 368 | original licensor to copy, distribute, link with or modify the Library 369 | subject to these terms and conditions. You may not impose any further 370 | restrictions on the recipients' exercise of the rights granted herein. 371 | You are not responsible for enforcing compliance by third parties with 372 | this License. 373 | 374 | 11. If, as a consequence of a court judgment or allegation of patent 375 | infringement or for any other reason (not limited to patent issues), 376 | conditions are imposed on you (whether by court order, agreement or 377 | otherwise) that contradict the conditions of this License, they do not 378 | excuse you from the conditions of this License. If you cannot 379 | distribute so as to satisfy simultaneously your obligations under this 380 | License and any other pertinent obligations, then as a consequence you 381 | may not distribute the Library at all. For example, if a patent 382 | license would not permit royalty-free redistribution of the Library by 383 | all those who receive copies directly or indirectly through you, then 384 | the only way you could satisfy both it and this License would be to 385 | refrain entirely from distribution of the Library. 386 | 387 | If any portion of this section is held invalid or unenforceable under any 388 | particular circumstance, the balance of the section is intended to apply, 389 | and the section as a whole is intended to apply in other circumstances. 390 | 391 | It is not the purpose of this section to induce you to infringe any 392 | patents or other property right claims or to contest validity of any 393 | such claims; this section has the sole purpose of protecting the 394 | integrity of the free software distribution system which is 395 | implemented by public license practices. Many people have made 396 | generous contributions to the wide range of software distributed 397 | through that system in reliance on consistent application of that 398 | system; it is up to the author/donor to decide if he or she is willing 399 | to distribute software through any other system and a licensee cannot 400 | impose that choice. 401 | 402 | This section is intended to make thoroughly clear what is believed to 403 | be a consequence of the rest of this License. 404 | 405 | 12. If the distribution and/or use of the Library is restricted in 406 | certain countries either by patents or by copyrighted interfaces, the 407 | original copyright holder who places the Library under this License may add 408 | an explicit geographical distribution limitation excluding those countries, 409 | so that distribution is permitted only in or among countries not thus 410 | excluded. In such case, this License incorporates the limitation as if 411 | written in the body of this License. 412 | 413 | 13. The Free Software Foundation may publish revised and/or new 414 | versions of the Lesser General Public License from time to time. 415 | Such new versions will be similar in spirit to the present version, 416 | but may differ in detail to address new problems or concerns. 417 | 418 | Each version is given a distinguishing version number. If the Library 419 | specifies a version number of this License which applies to it and 420 | "any later version", you have the option of following the terms and 421 | conditions either of that version or of any later version published by 422 | the Free Software Foundation. If the Library does not specify a 423 | license version number, you may choose any version ever published by 424 | the Free Software Foundation. 425 | 426 | 14. If you wish to incorporate parts of the Library into other free 427 | programs whose distribution conditions are incompatible with these, 428 | write to the author to ask for permission. For software which is 429 | copyrighted by the Free Software Foundation, write to the Free 430 | Software Foundation; we sometimes make exceptions for this. Our 431 | decision will be guided by the two goals of preserving the free status 432 | of all derivatives of our free software and of promoting the sharing 433 | and reuse of software generally. 434 | 435 | NO WARRANTY 436 | 437 | 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO 438 | WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. 439 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR 440 | OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY 441 | KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE 442 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 443 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE 444 | LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME 445 | THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 446 | 447 | 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN 448 | WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY 449 | AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU 450 | FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR 451 | CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE 452 | LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING 453 | RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A 454 | FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF 455 | SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH 456 | DAMAGES. 457 | 458 | END OF TERMS AND CONDITIONS 459 | 460 | How to Apply These Terms to Your New Libraries 461 | 462 | If you develop a new library, and you want it to be of the greatest 463 | possible use to the public, we recommend making it free software that 464 | everyone can redistribute and change. You can do so by permitting 465 | redistribution under these terms (or, alternatively, under the terms of the 466 | ordinary General Public License). 467 | 468 | To apply these terms, attach the following notices to the library. It is 469 | safest to attach them to the start of each source file to most effectively 470 | convey the exclusion of warranty; and each file should have at least the 471 | "copyright" line and a pointer to where the full notice is found. 472 | 473 | 474 | Copyright (C) 475 | 476 | This library is free software; you can redistribute it and/or 477 | modify it under the terms of the GNU Lesser General Public 478 | License as published by the Free Software Foundation; either 479 | version 2.1 of the License, or (at your option) any later version. 480 | 481 | This library is distributed in the hope that it will be useful, 482 | but WITHOUT ANY WARRANTY; without even the implied warranty of 483 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 484 | Lesser General Public License for more details. 485 | 486 | You should have received a copy of the GNU Lesser General Public 487 | License along with this library; if not, write to the Free Software 488 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 489 | USA 490 | 491 | Also add information on how to contact you by electronic and paper mail. 492 | 493 | You should also get your employer (if you work as a programmer) or your 494 | school, if any, to sign a "copyright disclaimer" for the library, if 495 | necessary. Here is a sample; alter the names: 496 | 497 | Yoyodyne, Inc., hereby disclaims all copyright interest in the 498 | library `Frob' (a library for tweaking knobs) written by James Random 499 | Hacker. 500 | 501 | , 1 April 1990 502 | Ty Coon, President of Vice 503 | 504 | That's all there is to it! 505 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | English | [🇨🇳中文](README_ZH.md) 2 | 3 | ## Introduction 4 | 5 | ethclient is extend [go-ethereum](https://github.com/ethereum/go-ethereum) client for interact with smart contract. 6 | 7 | ## Features: 8 | - query token balance 9 | 10 | - calculate uniswap v2/v3 liquid pool address offline 11 | 12 | - sort token address 13 | 14 | - encode uniswap v3 path 15 | 16 | - uniswap v3 liquid pool x96 price format convert 17 | 18 | - query smart contract data 19 | 20 | - build contract transaction and main currency transaction 21 | 22 | - HD wallet 23 | ## Install 24 | 25 | ``` 26 | go get -u github.com/ackermanx/ethclient 27 | ``` 28 | 29 | ## Usage 30 | Below is an example which shows some common use cases for ethereum/client. Check [client_test.go](https://github.com/ackermanx/ethclient/blob/main/client_test.go) for more usage. 31 | 32 | ### get balance/token balance/token transfer/main currency transfer 33 | 34 | ```go 35 | package main 36 | 37 | import ( 38 | "context" 39 | "log" 40 | "math/big" 41 | "time" 42 | 43 | "github.com/ackermanx/ethclient" 44 | "github.com/ackermanx/ethclient/abi" 45 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 46 | "github.com/ethereum/go-ethereum/common" 47 | ) 48 | 49 | var ( 50 | testKey = "33f46d353f191f8067dc7d256e9d9ee7a2a3300649ff7c70fe1cd7e5d5237da5" 51 | ) 52 | 53 | func main() { 54 | var binanceMainnet = `https://data-seed-prebsc-1-s1.binance.org:8545` 55 | 56 | ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) 57 | c, err := ethclient.DialContext(ctx, binanceMainnet) 58 | cancel() 59 | if err != nil { 60 | panic(err) 61 | } 62 | 63 | // get latest height 64 | ctx, cancel = context.WithTimeout(context.Background(), time.Second*5) 65 | blockNumber, err := c.BlockNumber(ctx) 66 | cancel() 67 | if err != nil { 68 | panic(err) 69 | } 70 | log.Println("latest block number: ", blockNumber) 71 | 72 | // get busd balance 73 | busdContractAddress := common.HexToAddress("0xed24fc36d5ee211ea25a80239fb8c4cfd80f12ee") 74 | address := "0xe96e6b50db659935878f6f3b0491B7F192cf5F59" 75 | bnbBalance, err := c.BalanceAt(context.Background(), common.HexToAddress(address), nil) 76 | if err != nil { 77 | panic(err) 78 | } 79 | log.Println("bnbBalance: ", bnbBalance.String()) 80 | balance, err := c.BalanceOf(address, busdContractAddress.String()) 81 | if err != nil { 82 | panic(err) 83 | } 84 | log.Printf("address busd balance: %s\n", balance.String()) 85 | 86 | // build contract transfer 87 | tx, err := c.BuildContractTx( 88 | testKey, "transfer", 89 | abi.ERC20Abi, 90 | &busdContractAddress, &bind.TransactOpts{From: common.HexToAddress(address)}, 91 | common.HexToAddress("0x38F32C2473a314d447d681D30e1C0f5D07194371"), 92 | big.NewInt(100000000000000000), 93 | ) 94 | if err != nil { 95 | panic(err) 96 | } 97 | err = c.SendTransaction(context.Background(), tx) 98 | if err != nil { 99 | panic(err) 100 | } 101 | log.Println(tx.Hash().String()) 102 | 103 | // send bnb 104 | tx, err = c.BuildTransferTx(testKey, "0x38F32C2473a314d447d681D30e1C0f5D07194371", &bind.TransactOpts{ 105 | From: common.HexToAddress("0xe96e6b50db659935878f6f3b0491b7f192cf5f59"), 106 | Value: big.NewInt(20000000000000000), 107 | GasLimit: 21000, 108 | }) 109 | if err != nil { 110 | panic(err) 111 | } 112 | log.Println(tx.Hash().String()) 113 | err = c.SendTransaction(context.Background(), tx) 114 | if err != nil { 115 | panic(err) 116 | } 117 | } 118 | ``` 119 | 120 | ### generate pool address offline 121 | 122 | ```go 123 | package main 124 | 125 | import ( 126 | "fmt" 127 | "math/big" 128 | 129 | "github.com/ackermanx/ethclient/uniswap" 130 | ) 131 | 132 | func main() { 133 | weth := "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" 134 | dai := "0x6b175474e89094c44da98b954eedeac495271d0f" 135 | pair, err := uniswap.CalculatePoolAddressV2(weth, dai) 136 | if err != nil { 137 | panic(err) 138 | } 139 | 140 | fmt.Printf("weth-dai pair address in uniswap v2: %s\n", pair.String()) 141 | 142 | fee := big.NewInt(3000) 143 | poolAddress, err := uniswap.CalculatePoolAddressV3(weth, dai, fee) 144 | if err != nil { 145 | panic(err) 146 | } 147 | fmt.Printf("weth-dai pool address in uniswap v3: %s\n", poolAddress.String()) 148 | } 149 | 150 | ``` 151 | 152 | ## JetBrains OS licenses 153 | 154 | `ethereum` had been being developed with GoLand under the **free JetBrains Open Source license(s)** granted by JetBrains s.r.o., hence I would like to express my thanks here. 155 | 156 | 157 | -------------------------------------------------------------------------------- /README_ZH.md: -------------------------------------------------------------------------------- 1 | 🇨🇳中文 | [English](README.md) 2 | 3 | ## 简介 4 | 5 | ethclient是对go-ethereum/client的扩展,添加uniswap v3相关工具、分层确定性钱包以及合约交互相关功能。 6 | 7 | ## 功能: 8 | 9 | - 查询erc20代币余额 10 | 11 | - uniswap v2/v3流动池地址离线计算 12 | 13 | - token地址排序 14 | 15 | - uniswap v3 path编码 16 | 17 | - uniswap v3流动池x96格式价格转换 18 | 19 | - 智能合约数据查询 20 | 21 | - 智能合约/主币交易构建 22 | 23 | - 分层确定性钱包 24 | 25 | ## 安装 26 | 27 | ``` 28 | go get -u github.com/ackermanx/ethclient 29 | ``` 30 | 31 | ## 使用 32 | 33 | 下面是一些常用例子,更多使用方式可以查看[client_test.go](https://github.com/ackermanx/ethclient/blob/main/client_test.go)以及 examples下面的示例。 34 | 35 | ### 获取余额/代币余额/token转账/主币转账 36 | 37 | ```go 38 | package main 39 | 40 | import ( 41 | "context" 42 | "log" 43 | "math/big" 44 | "time" 45 | 46 | "github.com/ackermanx/ethclient" 47 | "github.com/ackermanx/ethclient/abi" 48 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 49 | "github.com/ethereum/go-ethereum/common" 50 | ) 51 | 52 | var ( 53 | testKey = "33f46d353f191f8067dc7d256e9d9ee7a2a3300649ff7c70fe1cd7e5d5237da5" 54 | ) 55 | 56 | func main() { 57 | var binanceMainnet = `https://data-seed-prebsc-1-s1.binance.org:8545` 58 | 59 | ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) 60 | c, err := ethclient.DialContext(ctx, binanceMainnet) 61 | cancel() 62 | if err != nil { 63 | panic(err) 64 | } 65 | 66 | // get latest height 67 | ctx, cancel = context.WithTimeout(context.Background(), time.Second*5) 68 | blockNumber, err := c.BlockNumber(ctx) 69 | cancel() 70 | if err != nil { 71 | panic(err) 72 | } 73 | log.Println("latest block number: ", blockNumber) 74 | 75 | // get busd balance 76 | busdContractAddress := common.HexToAddress("0xed24fc36d5ee211ea25a80239fb8c4cfd80f12ee") 77 | address := "0xe96e6b50db659935878f6f3b0491B7F192cf5F59" 78 | bnbBalance, err := c.BalanceAt(context.Background(), common.HexToAddress(address), nil) 79 | if err != nil { 80 | panic(err) 81 | } 82 | log.Println("bnbBalance: ", bnbBalance.String()) 83 | balance, err := c.BalanceOf(address, busdContractAddress.String()) 84 | if err != nil { 85 | panic(err) 86 | } 87 | log.Printf("address busd balance: %s\n", balance.String()) 88 | 89 | // build contract transfer 90 | tx, err := c.BuildContractTx( 91 | testKey, "transfer", 92 | abi.ERC20Abi, 93 | &busdContractAddress, &bind.TransactOpts{From: common.HexToAddress(address)}, 94 | common.HexToAddress("0x38F32C2473a314d447d681D30e1C0f5D07194371"), 95 | big.NewInt(100000000000000000), 96 | ) 97 | if err != nil { 98 | panic(err) 99 | } 100 | err = c.SendTransaction(context.Background(), tx) 101 | if err != nil { 102 | panic(err) 103 | } 104 | log.Println(tx.Hash().String()) 105 | 106 | // send bnb 107 | tx, err = c.BuildTransferTx(testKey, "0x38F32C2473a314d447d681D30e1C0f5D07194371", &bind.TransactOpts{ 108 | From: common.HexToAddress("0xe96e6b50db659935878f6f3b0491b7f192cf5f59"), 109 | Value: big.NewInt(20000000000000000), 110 | GasLimit: 21000, 111 | }) 112 | if err != nil { 113 | panic(err) 114 | } 115 | log.Println(tx.Hash().String()) 116 | err = c.SendTransaction(context.Background(), tx) 117 | if err != nil { 118 | panic(err) 119 | } 120 | } 121 | ``` 122 | 123 | ### 离线生成uniswap v3流动池地址 124 | 125 | ```go 126 | package main 127 | 128 | import ( 129 | "fmt" 130 | "math/big" 131 | 132 | "github.com/ackermanx/ethclient/uniswap" 133 | ) 134 | 135 | func main() { 136 | weth := "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" 137 | dai := "0x6b175474e89094c44da98b954eedeac495271d0f" 138 | pair, err := uniswap.CalculatePoolAddressV2(weth, dai) 139 | if err != nil { 140 | panic(err) 141 | } 142 | 143 | fmt.Printf("weth-dai pair address in uniswap v2: %s\n", pair.String()) 144 | 145 | fee := big.NewInt(3000) 146 | poolAddress, err := uniswap.CalculatePoolAddressV3(weth, dai, fee) 147 | if err != nil { 148 | panic(err) 149 | } 150 | fmt.Printf("weth-dai pool address in uniswap v3: %s\n", poolAddress.String()) 151 | } 152 | 153 | ``` 154 | 155 | ## JetBrains 开源证书支持 156 | 157 | `ethereum` 项目是在 JetBrains 公司旗下的 GoLand 集成开发环境中进行开发,基于 free JetBrains Open Source license(s) 正版免费授权,在此表达我的谢意。 158 | 159 | -------------------------------------------------------------------------------- /abi/addr_abi_map.go: -------------------------------------------------------------------------------- 1 | package abi 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/ethereum/go-ethereum/accounts/abi" 7 | "github.com/ethereum/go-ethereum/common" 8 | ) 9 | 10 | type AddrAbiMap struct { 11 | m sync.Map 12 | } 13 | 14 | func (addrAbiMap *AddrAbiMap) Delete(key common.Address) { 15 | addrAbiMap.m.Delete(key) 16 | } 17 | 18 | func (addrAbiMap *AddrAbiMap) Load(key common.Address) (value abi.ABI, ok bool) { 19 | v, ok := addrAbiMap.m.Load(key) 20 | if v != nil { 21 | value = v.(abi.ABI) 22 | } 23 | return 24 | } 25 | 26 | func (addrAbiMap *AddrAbiMap) LoadOrStore(key common.Address, value abi.ABI) (actual abi.ABI, loaded bool) { 27 | a, loaded := addrAbiMap.m.LoadOrStore(key, value) 28 | actual = a.(abi.ABI) 29 | return 30 | } 31 | 32 | func (addrAbiMap *AddrAbiMap) Store(key common.Address, value abi.ABI) { 33 | addrAbiMap.m.Store(key, value) 34 | } 35 | 36 | func (addrAbiMap *AddrAbiMap) Range(f func(key common.Address, value abi.ABI) bool) { 37 | f1 := func(key, value interface{}) bool { 38 | return f(key.(common.Address), value.(abi.ABI)) 39 | } 40 | addrAbiMap.m.Range(f1) 41 | } 42 | -------------------------------------------------------------------------------- /abi/addr_abi_map_test.go: -------------------------------------------------------------------------------- 1 | package abi 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/ethereum/go-ethereum/accounts/abi" 8 | "github.com/ethereum/go-ethereum/common" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestAbi(t *testing.T) { 13 | addr := common.HexToAddress("0x431beE0E54b49105964E11b9035A198A1D4735AD") 14 | m := AddrAbiMap{} 15 | parsedAbi, err := abi.JSON(strings.NewReader(ERC20Abi)) 16 | if err != nil { 17 | t.Fatal(err) 18 | } 19 | 20 | m.Store(addr, parsedAbi) 21 | 22 | v, ok := m.Load(addr) 23 | if !ok { 24 | t.Log("value not exsit") 25 | t.FailNow() 26 | } 27 | assert.Equal(t, parsedAbi, v) 28 | } 29 | -------------------------------------------------------------------------------- /abi/erc20abi.go: -------------------------------------------------------------------------------- 1 | package abi 2 | 3 | const ERC20Abi = `[ 4 | { 5 | "constant": false, 6 | "inputs": [ 7 | { 8 | "name": "spender", 9 | "type": "address" 10 | }, 11 | { 12 | "name": "value", 13 | "type": "uint256" 14 | } 15 | ], 16 | "name": "approve", 17 | "outputs": [ 18 | { 19 | "name": "", 20 | "type": "bool" 21 | } 22 | ], 23 | "payable": false, 24 | "stateMutability": "nonpayable", 25 | "type": "function" 26 | }, 27 | { 28 | "constant": true, 29 | "inputs": [], 30 | "name": "totalSupply", 31 | "outputs": [ 32 | { 33 | "name": "", 34 | "type": "uint256" 35 | } 36 | ], 37 | "payable": false, 38 | "stateMutability": "view", 39 | "type": "function" 40 | }, 41 | { 42 | "constant": false, 43 | "inputs": [ 44 | { 45 | "name": "from", 46 | "type": "address" 47 | }, 48 | { 49 | "name": "to", 50 | "type": "address" 51 | }, 52 | { 53 | "name": "value", 54 | "type": "uint256" 55 | } 56 | ], 57 | "name": "transferFrom", 58 | "outputs": [ 59 | { 60 | "name": "", 61 | "type": "bool" 62 | } 63 | ], 64 | "payable": false, 65 | "stateMutability": "nonpayable", 66 | "type": "function" 67 | }, 68 | { 69 | "constant": false, 70 | "inputs": [ 71 | { 72 | "name": "spender", 73 | "type": "address" 74 | }, 75 | { 76 | "name": "addedValue", 77 | "type": "uint256" 78 | } 79 | ], 80 | "name": "increaseAllowance", 81 | "outputs": [ 82 | { 83 | "name": "", 84 | "type": "bool" 85 | } 86 | ], 87 | "payable": false, 88 | "stateMutability": "nonpayable", 89 | "type": "function" 90 | }, 91 | { 92 | "constant": true, 93 | "inputs": [ 94 | { 95 | "name": "owner", 96 | "type": "address" 97 | } 98 | ], 99 | "name": "balanceOf", 100 | "outputs": [ 101 | { 102 | "name": "", 103 | "type": "uint256" 104 | } 105 | ], 106 | "payable": false, 107 | "stateMutability": "view", 108 | "type": "function" 109 | }, 110 | { 111 | "constant": false, 112 | "inputs": [ 113 | { 114 | "name": "spender", 115 | "type": "address" 116 | }, 117 | { 118 | "name": "subtractedValue", 119 | "type": "uint256" 120 | } 121 | ], 122 | "name": "decreaseAllowance", 123 | "outputs": [ 124 | { 125 | "name": "", 126 | "type": "bool" 127 | } 128 | ], 129 | "payable": false, 130 | "stateMutability": "nonpayable", 131 | "type": "function" 132 | }, 133 | { 134 | "constant": false, 135 | "inputs": [ 136 | { 137 | "name": "to", 138 | "type": "address" 139 | }, 140 | { 141 | "name": "value", 142 | "type": "uint256" 143 | } 144 | ], 145 | "name": "transfer", 146 | "outputs": [ 147 | { 148 | "name": "", 149 | "type": "bool" 150 | } 151 | ], 152 | "payable": false, 153 | "stateMutability": "nonpayable", 154 | "type": "function" 155 | }, 156 | { 157 | "constant": true, 158 | "inputs": [ 159 | { 160 | "name": "owner", 161 | "type": "address" 162 | }, 163 | { 164 | "name": "spender", 165 | "type": "address" 166 | } 167 | ], 168 | "name": "allowance", 169 | "outputs": [ 170 | { 171 | "name": "", 172 | "type": "uint256" 173 | } 174 | ], 175 | "payable": false, 176 | "stateMutability": "view", 177 | "type": "function" 178 | }, 179 | { 180 | "anonymous": false, 181 | "inputs": [ 182 | { 183 | "indexed": true, 184 | "name": "from", 185 | "type": "address" 186 | }, 187 | { 188 | "indexed": true, 189 | "name": "to", 190 | "type": "address" 191 | }, 192 | { 193 | "indexed": false, 194 | "name": "value", 195 | "type": "uint256" 196 | } 197 | ], 198 | "name": "Transfer", 199 | "type": "event" 200 | }, 201 | { 202 | "anonymous": false, 203 | "inputs": [ 204 | { 205 | "indexed": true, 206 | "name": "owner", 207 | "type": "address" 208 | }, 209 | { 210 | "indexed": true, 211 | "name": "spender", 212 | "type": "address" 213 | }, 214 | { 215 | "indexed": false, 216 | "name": "value", 217 | "type": "uint256" 218 | } 219 | ], 220 | "name": "Approval", 221 | "type": "event" 222 | } 223 | ]` 224 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | // Package client provides a client for the Ethereum RPC API. 2 | package ethclient 3 | 4 | import ( 5 | "context" 6 | "encoding/json" 7 | "fmt" 8 | "math/big" 9 | "strings" 10 | "time" 11 | 12 | erc20 "github.com/ackermanx/ethclient/abi" 13 | "github.com/ethereum/go-ethereum" 14 | "github.com/ethereum/go-ethereum/accounts/abi" 15 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 16 | "github.com/ethereum/go-ethereum/common" 17 | "github.com/ethereum/go-ethereum/common/hexutil" 18 | "github.com/ethereum/go-ethereum/core/types" 19 | "github.com/ethereum/go-ethereum/crypto" 20 | "github.com/ethereum/go-ethereum/rpc" 21 | "github.com/pkg/errors" 22 | ) 23 | 24 | // Client defines typed wrappers for the Ethereum RPC API. 25 | type Client struct { 26 | c *rpc.Client 27 | timeout int 28 | chainID *big.Int 29 | parsedAbis erc20.AddrAbiMap 30 | } 31 | 32 | // Dial connects a client to the given URL. 33 | func Dial(rawurl string) (*Client, error) { 34 | return DialContext(context.Background(), rawurl) 35 | } 36 | 37 | func DialContext(ctx context.Context, rawurl string) (*Client, error) { 38 | c, err := rpc.DialContext(ctx, rawurl) 39 | if err != nil { 40 | return nil, err 41 | } 42 | return NewClient(c), nil 43 | } 44 | 45 | // NewClient creates a client that uses the given RPC client. 46 | func NewClient(c *rpc.Client) *Client { 47 | return &Client{c: c, timeout: 10, parsedAbis: erc20.AddrAbiMap{}} 48 | } 49 | 50 | // NewClientWithTimeout creates a client that uses the given RPC client and timeout. 51 | func NewClientWithTimeout(c *rpc.Client, timeout int) *Client { 52 | return &Client{c: c, timeout: timeout, parsedAbis: erc20.AddrAbiMap{}} 53 | } 54 | 55 | func (ec *Client) Close() { 56 | ec.c.Close() 57 | } 58 | 59 | // Blockchain Access 60 | 61 | // ChainId retrieves the current chain ID for transaction replay protection. 62 | func (ec *Client) ChainID(ctx context.Context) (*big.Int, error) { 63 | var result hexutil.Big 64 | err := ec.c.CallContext(ctx, &result, "eth_chainId") 65 | if err != nil { 66 | return nil, err 67 | } 68 | return (*big.Int)(&result), err 69 | } 70 | 71 | // BlockByHash returns the given full block. 72 | // 73 | // Note that loading full blocks requires two requests. Use HeaderByHash 74 | // if you don't need all transactions or uncle headers. 75 | func (ec *Client) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { 76 | return ec.getBlock(ctx, "eth_getBlockByHash", hash, true) 77 | } 78 | 79 | // BlockByNumber returns a block from the current canonical chain. If number is nil, the 80 | // latest known block is returned. 81 | // 82 | // Note that loading full blocks requires two requests. Use HeaderByNumber 83 | // if you don't need all transactions or uncle headers. 84 | func (ec *Client) BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) { 85 | return ec.getBlock(ctx, "eth_getBlockByNumber", toBlockNumArg(number), true) 86 | } 87 | 88 | // BlockNumber returns the most recent block number 89 | func (ec *Client) BlockNumber(ctx context.Context) (uint64, error) { 90 | var result hexutil.Uint64 91 | err := ec.c.CallContext(ctx, &result, "eth_blockNumber") 92 | return uint64(result), err 93 | } 94 | 95 | type rpcBlock struct { 96 | Hash common.Hash `json:"hash"` 97 | Transactions []rpcTransaction `json:"transactions"` 98 | UncleHashes []common.Hash `json:"uncles"` 99 | } 100 | 101 | func (ec *Client) getBlock(ctx context.Context, method string, args ...interface{}) (*types.Block, error) { 102 | var raw json.RawMessage 103 | err := ec.c.CallContext(ctx, &raw, method, args...) 104 | if err != nil { 105 | return nil, err 106 | } else if len(raw) == 0 { 107 | return nil, ethereum.NotFound 108 | } 109 | // Decode header and transactions. 110 | var head *types.Header 111 | var body rpcBlock 112 | if err := json.Unmarshal(raw, &head); err != nil { 113 | return nil, err 114 | } 115 | if err := json.Unmarshal(raw, &body); err != nil { 116 | return nil, err 117 | } 118 | // Quick-verify transaction and uncle lists. This mostly helps with debugging the server. 119 | if head.UncleHash == types.EmptyUncleHash && len(body.UncleHashes) > 0 { 120 | return nil, fmt.Errorf("server returned non-empty uncle list but block header indicates no uncles") 121 | } 122 | if head.UncleHash != types.EmptyUncleHash && len(body.UncleHashes) == 0 { 123 | return nil, fmt.Errorf("server returned empty uncle list but block header indicates uncles") 124 | } 125 | if head.TxHash == types.EmptyRootHash && len(body.Transactions) > 0 { 126 | return nil, fmt.Errorf("server returned non-empty transaction list but block header indicates no transactions") 127 | } 128 | if head.TxHash != types.EmptyRootHash && len(body.Transactions) == 0 { 129 | return nil, fmt.Errorf("server returned empty transaction list but block header indicates transactions") 130 | } 131 | // Load uncles because they are not included in the block response. 132 | var uncles []*types.Header 133 | if len(body.UncleHashes) > 0 { 134 | uncles = make([]*types.Header, len(body.UncleHashes)) 135 | reqs := make([]rpc.BatchElem, len(body.UncleHashes)) 136 | for i := range reqs { 137 | reqs[i] = rpc.BatchElem{ 138 | Method: "eth_getUncleByBlockHashAndIndex", 139 | Args: []interface{}{body.Hash, hexutil.EncodeUint64(uint64(i))}, 140 | Result: &uncles[i], 141 | } 142 | } 143 | if err := ec.c.BatchCallContext(ctx, reqs); err != nil { 144 | return nil, err 145 | } 146 | for i := range reqs { 147 | if reqs[i].Error != nil { 148 | return nil, reqs[i].Error 149 | } 150 | if uncles[i] == nil { 151 | return nil, fmt.Errorf("got null header for uncle %d of block %x", i, body.Hash[:]) 152 | } 153 | } 154 | } 155 | // Fill the sender cache of transactions in the block. 156 | txs := make([]*types.Transaction, len(body.Transactions)) 157 | for i, tx := range body.Transactions { 158 | if tx.From != nil { 159 | setSenderFromServer(tx.tx, *tx.From, body.Hash) 160 | } 161 | txs[i] = tx.tx 162 | } 163 | return types.NewBlockWithHeader(head).WithBody(txs, uncles), nil 164 | } 165 | 166 | // HeaderByHash returns the block header with the given hash. 167 | func (ec *Client) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { 168 | var head *types.Header 169 | err := ec.c.CallContext(ctx, &head, "eth_getBlockByHash", hash, false) 170 | if err == nil && head == nil { 171 | err = ethereum.NotFound 172 | } 173 | return head, err 174 | } 175 | 176 | // HeaderByNumber returns a block header from the current canonical chain. If number is 177 | // nil, the latest known header is returned. 178 | func (ec *Client) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) { 179 | var head *types.Header 180 | err := ec.c.CallContext(ctx, &head, "eth_getBlockByNumber", toBlockNumArg(number), false) 181 | if err == nil && head == nil { 182 | err = ethereum.NotFound 183 | } 184 | return head, err 185 | } 186 | 187 | type rpcTransaction struct { 188 | tx *types.Transaction 189 | txExtraInfo 190 | } 191 | 192 | type txExtraInfo struct { 193 | BlockNumber *string `json:"blockNumber,omitempty"` 194 | BlockHash *common.Hash `json:"blockHash,omitempty"` 195 | From *common.Address `json:"from,omitempty"` 196 | } 197 | 198 | func (tx *rpcTransaction) UnmarshalJSON(msg []byte) error { 199 | if err := json.Unmarshal(msg, &tx.tx); err != nil { 200 | return err 201 | } 202 | return json.Unmarshal(msg, &tx.txExtraInfo) 203 | } 204 | 205 | // TransactionByHash returns the transaction with the given hash. 206 | func (ec *Client) TransactionByHash(ctx context.Context, hash common.Hash) (tx *types.Transaction, isPending bool, err error) { 207 | var json *rpcTransaction 208 | err = ec.c.CallContext(ctx, &json, "eth_getTransactionByHash", hash) 209 | if err != nil { 210 | return nil, false, err 211 | } else if json == nil { 212 | return nil, false, ethereum.NotFound 213 | } else if _, r, _ := json.tx.RawSignatureValues(); r == nil { 214 | return nil, false, fmt.Errorf("server returned transaction without signature") 215 | } 216 | if json.From != nil && json.BlockHash != nil { 217 | setSenderFromServer(json.tx, *json.From, *json.BlockHash) 218 | } 219 | return json.tx, json.BlockNumber == nil, nil 220 | } 221 | 222 | // TransactionSender returns the sender address of the given transaction. The transaction 223 | // must be known to the remote node and included in the blockchain at the given block and 224 | // index. The sender is the one derived by the protocol at the time of inclusion. 225 | // 226 | // There is a fast-path for transactions retrieved by TransactionByHash and 227 | // TransactionInBlock. Getting their sender address can be done without an RPC interaction. 228 | func (ec *Client) TransactionSender(ctx context.Context, tx *types.Transaction, block common.Hash, index uint) (common.Address, error) { 229 | // Try to load the address from the cache. 230 | sender, err := types.Sender(&senderFromServer{blockhash: block}, tx) 231 | if err == nil { 232 | return sender, nil 233 | } 234 | var meta struct { 235 | Hash common.Hash 236 | From common.Address 237 | } 238 | if err = ec.c.CallContext(ctx, &meta, "eth_getTransactionByBlockHashAndIndex", block, hexutil.Uint64(index)); err != nil { 239 | return common.Address{}, err 240 | } 241 | if meta.Hash == (common.Hash{}) || meta.Hash != tx.Hash() { 242 | return common.Address{}, errors.New("wrong inclusion block/index") 243 | } 244 | return meta.From, nil 245 | } 246 | 247 | // TransactionCount returns the total number of transactions in the given block. 248 | func (ec *Client) TransactionCount(ctx context.Context, blockHash common.Hash) (uint, error) { 249 | var num hexutil.Uint 250 | err := ec.c.CallContext(ctx, &num, "eth_getBlockTransactionCountByHash", blockHash) 251 | return uint(num), err 252 | } 253 | 254 | // TransactionInBlock returns a single transaction at index in the given block. 255 | func (ec *Client) TransactionInBlock(ctx context.Context, blockHash common.Hash, index uint) (*types.Transaction, error) { 256 | var json *rpcTransaction 257 | err := ec.c.CallContext(ctx, &json, "eth_getTransactionByBlockHashAndIndex", blockHash, hexutil.Uint64(index)) 258 | if err != nil { 259 | return nil, err 260 | } 261 | if json == nil { 262 | return nil, ethereum.NotFound 263 | } else if _, r, _ := json.tx.RawSignatureValues(); r == nil { 264 | return nil, fmt.Errorf("server returned transaction without signature") 265 | } 266 | if json.From != nil && json.BlockHash != nil { 267 | setSenderFromServer(json.tx, *json.From, *json.BlockHash) 268 | } 269 | return json.tx, err 270 | } 271 | 272 | // TransactionReceipt returns the receipt of a transaction by transaction hash. 273 | // Note that the receipt is not available for pending transactions. 274 | func (ec *Client) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) { 275 | var r *types.Receipt 276 | err := ec.c.CallContext(ctx, &r, "eth_getTransactionReceipt", txHash) 277 | if err == nil { 278 | if r == nil { 279 | return nil, ethereum.NotFound 280 | } 281 | } 282 | return r, err 283 | } 284 | 285 | func toBlockNumArg(number *big.Int) string { 286 | if number == nil { 287 | return "latest" 288 | } 289 | pending := big.NewInt(-1) 290 | if number.Cmp(pending) == 0 { 291 | return "pending" 292 | } 293 | return hexutil.EncodeBig(number) 294 | } 295 | 296 | type rpcProgress struct { 297 | StartingBlock hexutil.Uint64 298 | CurrentBlock hexutil.Uint64 299 | HighestBlock hexutil.Uint64 300 | PulledStates hexutil.Uint64 301 | KnownStates hexutil.Uint64 302 | } 303 | 304 | // SyncProgress retrieves the current progress of the sync algorithm. If there's 305 | // no sync currently running, it returns nil. 306 | func (ec *Client) SyncProgress(ctx context.Context) (*ethereum.SyncProgress, error) { 307 | var raw json.RawMessage 308 | if err := ec.c.CallContext(ctx, &raw, "eth_syncing"); err != nil { 309 | return nil, err 310 | } 311 | // Handle the possible response types 312 | var syncing bool 313 | if err := json.Unmarshal(raw, &syncing); err == nil { 314 | return nil, nil // Not syncing (always false) 315 | } 316 | var progress *rpcProgress 317 | if err := json.Unmarshal(raw, &progress); err != nil { 318 | return nil, err 319 | } 320 | return ðereum.SyncProgress{ 321 | StartingBlock: uint64(progress.StartingBlock), 322 | CurrentBlock: uint64(progress.CurrentBlock), 323 | HighestBlock: uint64(progress.HighestBlock), 324 | PulledStates: uint64(progress.PulledStates), 325 | KnownStates: uint64(progress.KnownStates), 326 | }, nil 327 | } 328 | 329 | // SubscribeNewHead subscribes to notifications about the current blockchain head 330 | // on the given channel. 331 | func (ec *Client) SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) (ethereum.Subscription, error) { 332 | return ec.c.EthSubscribe(ctx, ch, "newHeads") 333 | } 334 | 335 | // State Access 336 | 337 | // NetworkID returns the network ID (also known as the chain ID) for this chain. 338 | func (ec *Client) NetworkID(ctx context.Context) (*big.Int, error) { 339 | version := new(big.Int) 340 | var ver string 341 | if err := ec.c.CallContext(ctx, &ver, "net_version"); err != nil { 342 | return nil, err 343 | } 344 | if _, ok := version.SetString(ver, 10); !ok { 345 | return nil, fmt.Errorf("invalid net_version result %q", ver) 346 | } 347 | return version, nil 348 | } 349 | 350 | // BalanceAt returns the wei balance of the given account. 351 | // The block number can be nil, in which case the balance is taken from the latest known block. 352 | func (ec *Client) BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (*big.Int, error) { 353 | var result hexutil.Big 354 | err := ec.c.CallContext(ctx, &result, "eth_getBalance", account, toBlockNumArg(blockNumber)) 355 | return (*big.Int)(&result), err 356 | } 357 | 358 | // StorageAt returns the value of key in the contract storage of the given account. 359 | // The block number can be nil, in which case the value is taken from the latest known block. 360 | func (ec *Client) StorageAt(ctx context.Context, account common.Address, key common.Hash, blockNumber *big.Int) ([]byte, error) { 361 | var result hexutil.Bytes 362 | err := ec.c.CallContext(ctx, &result, "eth_getStorageAt", account, key, toBlockNumArg(blockNumber)) 363 | return result, err 364 | } 365 | 366 | // CodeAt returns the contract code of the given account. 367 | // The block number can be nil, in which case the code is taken from the latest known block. 368 | func (ec *Client) CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error) { 369 | var result hexutil.Bytes 370 | err := ec.c.CallContext(ctx, &result, "eth_getCode", account, toBlockNumArg(blockNumber)) 371 | return result, err 372 | } 373 | 374 | // NonceAt returns the account nonce of the given account. 375 | // The block number can be nil, in which case the nonce is taken from the latest known block. 376 | func (ec *Client) NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error) { 377 | var result hexutil.Uint64 378 | err := ec.c.CallContext(ctx, &result, "eth_getTransactionCount", account, toBlockNumArg(blockNumber)) 379 | return uint64(result), err 380 | } 381 | 382 | // Filters 383 | 384 | // FilterLogs executes a filter query. 385 | func (ec *Client) FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) { 386 | var result []types.Log 387 | arg, err := toFilterArg(q) 388 | if err != nil { 389 | return nil, err 390 | } 391 | err = ec.c.CallContext(ctx, &result, "eth_getLogs", arg) 392 | return result, err 393 | } 394 | 395 | // SubscribeFilterLogs subscribes to the results of a streaming filter query. 396 | func (ec *Client) SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) { 397 | arg, err := toFilterArg(q) 398 | if err != nil { 399 | return nil, err 400 | } 401 | return ec.c.EthSubscribe(ctx, ch, "logs", arg) 402 | } 403 | 404 | func toFilterArg(q ethereum.FilterQuery) (interface{}, error) { 405 | arg := map[string]interface{}{ 406 | "address": q.Addresses, 407 | "topics": q.Topics, 408 | } 409 | if q.BlockHash != nil { 410 | arg["blockHash"] = *q.BlockHash 411 | if q.FromBlock != nil || q.ToBlock != nil { 412 | return nil, fmt.Errorf("cannot specify both BlockHash and FromBlock/ToBlock") 413 | } 414 | } else { 415 | if q.FromBlock == nil { 416 | arg["fromBlock"] = "0x0" 417 | } else { 418 | arg["fromBlock"] = toBlockNumArg(q.FromBlock) 419 | } 420 | arg["toBlock"] = toBlockNumArg(q.ToBlock) 421 | } 422 | return arg, nil 423 | } 424 | 425 | // Pending State 426 | 427 | // PendingBalanceAt returns the wei balance of the given account in the pending state. 428 | func (ec *Client) PendingBalanceAt(ctx context.Context, account common.Address) (*big.Int, error) { 429 | var result hexutil.Big 430 | err := ec.c.CallContext(ctx, &result, "eth_getBalance", account, "pending") 431 | return (*big.Int)(&result), err 432 | } 433 | 434 | // PendingStorageAt returns the value of key in the contract storage of the given account in the pending state. 435 | func (ec *Client) PendingStorageAt(ctx context.Context, account common.Address, key common.Hash) ([]byte, error) { 436 | var result hexutil.Bytes 437 | err := ec.c.CallContext(ctx, &result, "eth_getStorageAt", account, key, "pending") 438 | return result, err 439 | } 440 | 441 | // PendingCodeAt returns the contract code of the given account in the pending state. 442 | func (ec *Client) PendingCodeAt(ctx context.Context, account common.Address) ([]byte, error) { 443 | var result hexutil.Bytes 444 | err := ec.c.CallContext(ctx, &result, "eth_getCode", account, "pending") 445 | return result, err 446 | } 447 | 448 | // PendingNonceAt returns the account nonce of the given account in the pending state. 449 | // This is the nonce that should be used for the next transaction. 450 | func (ec *Client) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) { 451 | var result hexutil.Uint64 452 | err := ec.c.CallContext(ctx, &result, "eth_getTransactionCount", account, "pending") 453 | return uint64(result), err 454 | } 455 | 456 | // PendingTransactionCount returns the total number of transactions in the pending state. 457 | func (ec *Client) PendingTransactionCount(ctx context.Context) (uint, error) { 458 | var num hexutil.Uint 459 | err := ec.c.CallContext(ctx, &num, "eth_getBlockTransactionCountByNumber", "pending") 460 | return uint(num), err 461 | } 462 | 463 | // Contract Calling 464 | 465 | // CallContract executes a message call transaction, which is directly executed in the VM 466 | // of the node, but never mined into the blockchain. 467 | // 468 | // blockNumber selects the block height at which the call runs. It can be nil, in which 469 | // case the code is taken from the latest known block. Note that state from very old 470 | // blocks might not be available. 471 | func (ec *Client) CallContract(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) { 472 | var hex hexutil.Bytes 473 | err := ec.c.CallContext(ctx, &hex, "eth_call", toCallArg(msg), toBlockNumArg(blockNumber)) 474 | if err != nil { 475 | return nil, err 476 | } 477 | return hex, nil 478 | } 479 | 480 | // PendingCallContract executes a message call transaction using the EVM. 481 | // The state seen by the contract call is the pending state. 482 | func (ec *Client) PendingCallContract(ctx context.Context, msg ethereum.CallMsg) ([]byte, error) { 483 | var hex hexutil.Bytes 484 | err := ec.c.CallContext(ctx, &hex, "eth_call", toCallArg(msg), "pending") 485 | if err != nil { 486 | return nil, err 487 | } 488 | return hex, nil 489 | } 490 | 491 | // SuggestGasPrice retrieves the currently suggested gas price to allow a timely 492 | // execution of a transaction. 493 | func (ec *Client) SuggestGasPrice(ctx context.Context) (*big.Int, error) { 494 | var hex hexutil.Big 495 | if err := ec.c.CallContext(ctx, &hex, "eth_gasPrice"); err != nil { 496 | return nil, err 497 | } 498 | return (*big.Int)(&hex), nil 499 | } 500 | 501 | // SuggestGasTipCap retrieves the currently suggested gas tip cap after 1559 to 502 | // allow a timely execution of a transaction. 503 | func (ec *Client) SuggestGasTipCap(ctx context.Context) (*big.Int, error) { 504 | var hex hexutil.Big 505 | if err := ec.c.CallContext(ctx, &hex, "eth_maxPriorityFeePerGas"); err != nil { 506 | return nil, err 507 | } 508 | return (*big.Int)(&hex), nil 509 | } 510 | 511 | // EstimateGas tries to estimate the gas needed to execute a specific transaction based on 512 | // the current pending state of the backend blockchain. There is no guarantee that this is 513 | // the true gas limit requirement as other transactions may be added or removed by miners, 514 | // but it should provide a basis for setting a reasonable default. 515 | func (ec *Client) EstimateGas(ctx context.Context, msg ethereum.CallMsg) (uint64, error) { 516 | var hex hexutil.Uint64 517 | err := ec.c.CallContext(ctx, &hex, "eth_estimateGas", toCallArg(msg)) 518 | if err != nil { 519 | return 0, err 520 | } 521 | return uint64(hex), nil 522 | } 523 | 524 | // SendTransaction injects a signed transaction into the pending pool for execution. 525 | // 526 | // If the transaction was a contract creation use the TransactionReceipt method to get the 527 | // contract address after the transaction has been mined. 528 | func (ec *Client) SendTransaction(ctx context.Context, tx *types.Transaction) error { 529 | data, err := tx.MarshalBinary() 530 | if err != nil { 531 | return err 532 | } 533 | return ec.c.CallContext(ctx, nil, "eth_sendRawTransaction", hexutil.Encode(data)) 534 | } 535 | 536 | // Call invokes the (constant) contract method with params as input values and 537 | // sets the output to result. The result type might be a single field for simple 538 | // returns, a slice of interfaces for anonymous returns and a struct for named 539 | // returns. 540 | func (ec *Client) Call(contractAddr common.Address, opts *bind.CallOpts, results *[]interface{}, method, abiStr string, params ...interface{}) error { 541 | // Don't crash on a lazy user 542 | if opts == nil { 543 | opts = new(bind.CallOpts) 544 | } 545 | if results == nil { 546 | results = new([]interface{}) 547 | } 548 | // cache parsedAbi 549 | parsedAbi, ok := ec.parsedAbis.Load(contractAddr) 550 | if !ok { 551 | p, err := abi.JSON(strings.NewReader(abiStr)) 552 | if err != nil { 553 | return errors.WithMessagef(err, "parse abi: %s", abiStr) 554 | } 555 | parsedAbi = p 556 | ec.parsedAbis.Store(contractAddr, parsedAbi) 557 | } 558 | // Pack the input, call and unpack the results 559 | input, err := parsedAbi.Pack(method, params...) 560 | if err != nil { 561 | return errors.WithMessagef(err, "pack method: %s, params: %+v", method, params) 562 | } 563 | var ( 564 | msg = ethereum.CallMsg{From: opts.From, To: &contractAddr, Data: input} 565 | ctx = ensureContext(opts.Context) 566 | code []byte 567 | output []byte 568 | ) 569 | if opts.Pending { 570 | output, err = ec.PendingCallContract(ctx, msg) 571 | if err == nil && len(output) == 0 { 572 | // Make sure we have a contract to operate on, and bail out otherwise. 573 | if code, err = ec.PendingCodeAt(ctx, contractAddr); err != nil { 574 | return err 575 | } else if len(code) == 0 { 576 | return bind.ErrNoCode 577 | } 578 | } 579 | } else { 580 | output, err = ec.CallContract(ctx, msg, opts.BlockNumber) 581 | if err != nil { 582 | return err 583 | } 584 | if len(output) == 0 { 585 | // Make sure we have a contract to operate on, and bail out otherwise. 586 | if code, err = ec.CodeAt(ctx, contractAddr, opts.BlockNumber); err != nil { 587 | return err 588 | } else if len(code) == 0 { 589 | return bind.ErrNoCode 590 | } 591 | } 592 | } 593 | 594 | if len(*results) == 0 { 595 | res, err := parsedAbi.Unpack(method, output) 596 | *results = res 597 | return err 598 | } 599 | res := *results 600 | return parsedAbi.UnpackIntoInterface(res[0], method, output) 601 | } 602 | 603 | // BalanceOf query address in contract balance 604 | // returns *big.Int and error 605 | func (ec *Client) BalanceOf(address, contractAddr string) (balance *big.Int, err error) { 606 | var results = make([]interface{}, 0) 607 | err = ec.Call(common.HexToAddress(contractAddr), nil, &results, "balanceOf", erc20.ERC20Abi, common.HexToAddress(address)) 608 | if err != nil { 609 | return nil, err 610 | } 611 | balance, ok := results[0].(*big.Int) 612 | if !ok { 613 | return nil, errors.New("results[0] is not *big.Int") 614 | } 615 | return 616 | } 617 | 618 | // BuildContractTx build contract transaction 619 | func (ec *Client) BuildContractTx(privKey, method, abiStr string, contract *common.Address, opts *bind.TransactOpts, params ...interface{}) (tx *types.Transaction, err error) { 620 | if contract == nil { 621 | return nil, errors.New("contract is nil") 622 | } 623 | // decode private key 624 | pKey, err := crypto.HexToECDSA(privKey) 625 | if err != nil { 626 | err = errors.WithMessage(err, "hex private key to ECDSA key: ") 627 | return 628 | } 629 | from := crypto.PubkeyToAddress(pKey.PublicKey) 630 | // Don't crash on a lazy user 631 | if opts == nil { 632 | opts = &bind.TransactOpts{From: from} 633 | } 634 | 635 | // pack input params and cache parsedAbi 636 | parsedAbi, ok := ec.parsedAbis.Load(*contract) 637 | if !ok { 638 | p, err := abi.JSON(strings.NewReader(abiStr)) 639 | if err != nil { 640 | return nil, errors.WithMessagef(err, "parse abi: %s", abiStr) 641 | } 642 | parsedAbi = p 643 | ec.parsedAbis.Store(*contract, parsedAbi) 644 | } 645 | 646 | // Pack the input, call and unpack the results 647 | input, err := parsedAbi.Pack(method, params...) 648 | if err != nil { 649 | err = errors.WithMessagef(err, "pack method: %s, params: %+v", method, params) 650 | return 651 | } 652 | 653 | // Ensure a valid value field and resolve the account nonce 654 | value := opts.Value 655 | if value == nil { 656 | value = new(big.Int) 657 | } 658 | var nonce uint64 659 | if opts.Nonce == nil { 660 | nonce, err = ec.PendingNonceAt(ensureContext(opts.Context), from) 661 | if err != nil { 662 | return nil, fmt.Errorf("failed to retrieve account nonce: %v", err) 663 | } 664 | } else { 665 | nonce = opts.Nonce.Uint64() 666 | } 667 | 668 | // Figure out reasonable gas price values 669 | if opts.GasPrice != nil && (opts.GasFeeCap != nil || opts.GasTipCap != nil) { 670 | return nil, errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified") 671 | } 672 | head, err := ec.HeaderByNumber(ensureContext(opts.Context), nil) 673 | if err != nil { 674 | return nil, errors.WithMessage(err, "header by number") 675 | } 676 | if head.BaseFee != nil && opts.GasPrice == nil { 677 | if opts.GasTipCap == nil { 678 | tip, err := ec.SuggestGasTipCap(ensureContext(opts.Context)) 679 | if err != nil { 680 | return nil, err 681 | } 682 | opts.GasTipCap = tip 683 | } 684 | if opts.GasFeeCap == nil { 685 | gasFeeCap := new(big.Int).Add( 686 | opts.GasTipCap, 687 | new(big.Int).Mul(head.BaseFee, big.NewInt(2)), 688 | ) 689 | opts.GasFeeCap = gasFeeCap 690 | } 691 | if opts.GasFeeCap.Cmp(opts.GasTipCap) < 0 { 692 | return nil, fmt.Errorf("maxFeePerGas (%v) < maxPriorityFeePerGas (%v)", opts.GasFeeCap, opts.GasTipCap) 693 | } 694 | } else { 695 | if opts.GasFeeCap != nil || opts.GasTipCap != nil { 696 | return nil, errors.New("maxFeePerGas or maxPriorityFeePerGas specified but london is not active yet") 697 | } 698 | if opts.GasPrice == nil { 699 | price, err := ec.SuggestGasPrice(ensureContext(opts.Context)) 700 | if err != nil { 701 | return nil, err 702 | } 703 | opts.GasPrice = price 704 | } 705 | } 706 | 707 | gasLimit := opts.GasLimit 708 | if gasLimit == 0 { 709 | // Gas estimation cannot succeed without code for method invocations 710 | if code, err := ec.PendingCodeAt(ensureContext(opts.Context), *contract); err != nil { 711 | return nil, err 712 | } else if len(code) == 0 { 713 | return nil, bind.ErrNoCode 714 | } 715 | // If the contract surely has code (or code is not needed), estimate the transaction 716 | msg := ethereum.CallMsg{From: opts.From, To: contract, GasPrice: opts.GasPrice, GasTipCap: opts.GasTipCap, GasFeeCap: opts.GasFeeCap, Value: value, Data: input} 717 | gasLimit, err = ec.EstimateGas(ensureContext(opts.Context), msg) 718 | if err != nil { 719 | return nil, fmt.Errorf("failed to estimate gas needed: %v", err) 720 | } 721 | } 722 | 723 | // Create the transaction, sign it and schedule it for execution 724 | var rawTx *types.Transaction 725 | if opts.GasFeeCap == nil { 726 | baseTx := &types.LegacyTx{ 727 | Nonce: nonce, 728 | GasPrice: opts.GasPrice, 729 | To: contract, 730 | Gas: gasLimit, 731 | Value: value, 732 | Data: input, 733 | } 734 | rawTx = types.NewTx(baseTx) 735 | } else { 736 | baseTx := &types.DynamicFeeTx{ 737 | Nonce: nonce, 738 | GasFeeCap: opts.GasFeeCap, 739 | GasTipCap: opts.GasTipCap, 740 | Gas: gasLimit, 741 | Value: value, 742 | To: contract, 743 | Data: input, 744 | } 745 | rawTx = types.NewTx(baseTx) 746 | } 747 | 748 | if ec.chainID == nil { 749 | ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(ec.timeout)) 750 | chainID, err := ec.ChainID(ctx) 751 | cancel() 752 | if err != nil { 753 | return nil, errors.WithMessage(err, "get chain id: ") 754 | } 755 | ec.chainID = chainID 756 | } 757 | 758 | signedTx, err := types.SignTx(rawTx, types.NewLondonSigner(ec.chainID), pKey) 759 | if err != nil { 760 | err = errors.WithMessage(err, "signed raw tx") 761 | return 762 | } 763 | return signedTx, nil 764 | } 765 | 766 | func (ec *Client) BuildTransferTx(privKey, to string, opts *bind.TransactOpts) (tx *types.Transaction, err error) { 767 | // decode private key 768 | pKey, err := crypto.HexToECDSA(privKey) 769 | if err != nil { 770 | err = errors.WithMessage(err, "hex private key to ECDSA key: ") 771 | return 772 | } 773 | from := crypto.PubkeyToAddress(pKey.PublicKey) 774 | 775 | // Don't crash on a lazy user 776 | if opts == nil { 777 | opts = &bind.TransactOpts{From: from, GasLimit: 21000} 778 | } 779 | 780 | // Ensure a valid value field and resolve the account nonce 781 | value := opts.Value 782 | if value == nil { 783 | opts.Value = new(big.Int) 784 | } 785 | var nonce uint64 786 | if opts.Nonce == nil { 787 | nonce, err = ec.PendingNonceAt(ensureContext(opts.Context), from) 788 | if err != nil { 789 | return nil, fmt.Errorf("failed to retrieve account nonce: %v", err) 790 | } 791 | } else { 792 | nonce = opts.Nonce.Uint64() 793 | } 794 | 795 | // Figure out reasonable gas price values 796 | if opts.GasPrice != nil && (opts.GasFeeCap != nil || opts.GasTipCap != nil) { 797 | return nil, errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified") 798 | } 799 | head, err := ec.HeaderByNumber(ensureContext(opts.Context), nil) 800 | if err != nil { 801 | return nil, errors.WithMessage(err, "header by number") 802 | } 803 | if head.BaseFee != nil && opts.GasPrice == nil { 804 | if opts.GasTipCap == nil { 805 | tip, err := ec.SuggestGasTipCap(ensureContext(opts.Context)) 806 | if err != nil { 807 | return nil, err 808 | } 809 | opts.GasTipCap = tip 810 | } 811 | if opts.GasFeeCap == nil { 812 | gasFeeCap := new(big.Int).Add( 813 | opts.GasTipCap, 814 | new(big.Int).Mul(head.BaseFee, big.NewInt(2)), 815 | ) 816 | opts.GasFeeCap = gasFeeCap 817 | } 818 | if opts.GasFeeCap.Cmp(opts.GasTipCap) < 0 { 819 | return nil, fmt.Errorf("maxFeePerGas (%v) < maxPriorityFeePerGas (%v)", opts.GasFeeCap, opts.GasTipCap) 820 | } 821 | } else { 822 | if opts.GasFeeCap != nil || opts.GasTipCap != nil { 823 | return nil, errors.New("maxFeePerGas or maxPriorityFeePerGas specified but london is not active yet") 824 | } 825 | if opts.GasPrice == nil { 826 | price, err := ec.SuggestGasPrice(ensureContext(opts.Context)) 827 | if err != nil { 828 | return nil, err 829 | } 830 | opts.GasPrice = price 831 | } 832 | } 833 | toAddr := common.HexToAddress(to) 834 | // Create the transaction, sign it and schedule it for execution 835 | var rawTx *types.Transaction 836 | if opts.GasFeeCap == nil { 837 | baseTx := types.LegacyTx{ 838 | Nonce: nonce, 839 | To: &toAddr, 840 | GasPrice: opts.GasPrice, 841 | Gas: opts.GasLimit, 842 | Value: opts.Value, 843 | Data: []byte{}, 844 | } 845 | rawTx = types.NewTx(&baseTx) 846 | } else { 847 | baseTx := types.DynamicFeeTx{ 848 | Nonce: nonce, 849 | GasFeeCap: opts.GasFeeCap, 850 | GasTipCap: opts.GasTipCap, 851 | Gas: opts.GasLimit, 852 | Value: value, 853 | To: &toAddr, 854 | Data: []byte{}, 855 | } 856 | rawTx = types.NewTx(&baseTx) 857 | } 858 | 859 | if ec.chainID == nil { 860 | ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(ec.timeout)) 861 | chainID, err := ec.ChainID(ctx) 862 | cancel() 863 | if err != nil { 864 | return nil, errors.WithMessage(err, "get chain id: ") 865 | } 866 | ec.chainID = chainID 867 | } 868 | 869 | signedTx, err := types.SignTx(rawTx, types.NewLondonSigner(ec.chainID), pKey) 870 | if err != nil { 871 | err = errors.WithMessage(err, "signed raw tx") 872 | return 873 | } 874 | return signedTx, nil 875 | } 876 | 877 | func toCallArg(msg ethereum.CallMsg) interface{} { 878 | arg := map[string]interface{}{ 879 | "from": msg.From, 880 | "to": msg.To, 881 | } 882 | if len(msg.Data) > 0 { 883 | arg["data"] = hexutil.Bytes(msg.Data) 884 | } 885 | if msg.Value != nil { 886 | arg["value"] = (*hexutil.Big)(msg.Value) 887 | } 888 | if msg.Gas != 0 { 889 | arg["gas"] = hexutil.Uint64(msg.Gas) 890 | } 891 | if msg.GasPrice != nil { 892 | arg["gasPrice"] = (*hexutil.Big)(msg.GasPrice) 893 | } 894 | return arg 895 | } 896 | 897 | // ensureContext is a helper method to ensure a context is not nil, even if the 898 | // user specified it as such. 899 | func ensureContext(ctx context.Context) context.Context { 900 | if ctx == nil { 901 | return context.Background() 902 | } 903 | return ctx 904 | } 905 | -------------------------------------------------------------------------------- /client_test.go: -------------------------------------------------------------------------------- 1 | package ethclient 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "errors" 7 | "fmt" 8 | "math/big" 9 | "reflect" 10 | "testing" 11 | "time" 12 | 13 | "github.com/ethereum/go-ethereum" 14 | "github.com/ethereum/go-ethereum/common" 15 | "github.com/ethereum/go-ethereum/consensus/ethash" 16 | "github.com/ethereum/go-ethereum/core" 17 | "github.com/ethereum/go-ethereum/core/rawdb" 18 | "github.com/ethereum/go-ethereum/core/types" 19 | "github.com/ethereum/go-ethereum/crypto" 20 | "github.com/ethereum/go-ethereum/eth" 21 | "github.com/ethereum/go-ethereum/eth/ethconfig" 22 | "github.com/ethereum/go-ethereum/node" 23 | "github.com/ethereum/go-ethereum/params" 24 | "github.com/ethereum/go-ethereum/rpc" 25 | ) 26 | 27 | // Verify that Client implements the ethereum interfaces. 28 | var ( 29 | _ = ethereum.ChainReader(&Client{}) 30 | _ = ethereum.TransactionReader(&Client{}) 31 | _ = ethereum.ChainStateReader(&Client{}) 32 | _ = ethereum.ChainSyncReader(&Client{}) 33 | _ = ethereum.ContractCaller(&Client{}) 34 | _ = ethereum.GasEstimator(&Client{}) 35 | _ = ethereum.GasPricer(&Client{}) 36 | _ = ethereum.LogFilterer(&Client{}) 37 | _ = ethereum.PendingStateReader(&Client{}) 38 | // _ = ethereum.PendingStateEventer(&Client{}) 39 | _ = ethereum.PendingContractCaller(&Client{}) 40 | ) 41 | 42 | func TestToFilterArg(t *testing.T) { 43 | blockHashErr := fmt.Errorf("cannot specify both BlockHash and FromBlock/ToBlock") 44 | addresses := []common.Address{ 45 | common.HexToAddress("0xD36722ADeC3EdCB29c8e7b5a47f352D701393462"), 46 | } 47 | blockHash := common.HexToHash( 48 | "0xeb94bb7d78b73657a9d7a99792413f50c0a45c51fc62bdcb08a53f18e9a2b4eb", 49 | ) 50 | 51 | for _, testCase := range []struct { 52 | name string 53 | input ethereum.FilterQuery 54 | output interface{} 55 | err error 56 | }{ 57 | { 58 | "without BlockHash", 59 | ethereum.FilterQuery{ 60 | Addresses: addresses, 61 | FromBlock: big.NewInt(1), 62 | ToBlock: big.NewInt(2), 63 | Topics: [][]common.Hash{}, 64 | }, 65 | map[string]interface{}{ 66 | "address": addresses, 67 | "fromBlock": "0x1", 68 | "toBlock": "0x2", 69 | "topics": [][]common.Hash{}, 70 | }, 71 | nil, 72 | }, 73 | { 74 | "with nil fromBlock and nil toBlock", 75 | ethereum.FilterQuery{ 76 | Addresses: addresses, 77 | Topics: [][]common.Hash{}, 78 | }, 79 | map[string]interface{}{ 80 | "address": addresses, 81 | "fromBlock": "0x0", 82 | "toBlock": "latest", 83 | "topics": [][]common.Hash{}, 84 | }, 85 | nil, 86 | }, 87 | { 88 | "with negative fromBlock and negative toBlock", 89 | ethereum.FilterQuery{ 90 | Addresses: addresses, 91 | FromBlock: big.NewInt(-1), 92 | ToBlock: big.NewInt(-1), 93 | Topics: [][]common.Hash{}, 94 | }, 95 | map[string]interface{}{ 96 | "address": addresses, 97 | "fromBlock": "pending", 98 | "toBlock": "pending", 99 | "topics": [][]common.Hash{}, 100 | }, 101 | nil, 102 | }, 103 | { 104 | "with blockhash", 105 | ethereum.FilterQuery{ 106 | Addresses: addresses, 107 | BlockHash: &blockHash, 108 | Topics: [][]common.Hash{}, 109 | }, 110 | map[string]interface{}{ 111 | "address": addresses, 112 | "blockHash": blockHash, 113 | "topics": [][]common.Hash{}, 114 | }, 115 | nil, 116 | }, 117 | { 118 | "with blockhash and from block", 119 | ethereum.FilterQuery{ 120 | Addresses: addresses, 121 | BlockHash: &blockHash, 122 | FromBlock: big.NewInt(1), 123 | Topics: [][]common.Hash{}, 124 | }, 125 | nil, 126 | blockHashErr, 127 | }, 128 | { 129 | "with blockhash and to block", 130 | ethereum.FilterQuery{ 131 | Addresses: addresses, 132 | BlockHash: &blockHash, 133 | ToBlock: big.NewInt(1), 134 | Topics: [][]common.Hash{}, 135 | }, 136 | nil, 137 | blockHashErr, 138 | }, 139 | { 140 | "with blockhash and both from / to block", 141 | ethereum.FilterQuery{ 142 | Addresses: addresses, 143 | BlockHash: &blockHash, 144 | FromBlock: big.NewInt(1), 145 | ToBlock: big.NewInt(2), 146 | Topics: [][]common.Hash{}, 147 | }, 148 | nil, 149 | blockHashErr, 150 | }, 151 | } { 152 | t.Run(testCase.name, func(t *testing.T) { 153 | output, err := toFilterArg(testCase.input) 154 | if (testCase.err == nil) != (err == nil) { 155 | t.Fatalf("expected error %v but got %v", testCase.err, err) 156 | } 157 | if testCase.err != nil { 158 | if testCase.err.Error() != err.Error() { 159 | t.Fatalf("expected error %v but got %v", testCase.err, err) 160 | } 161 | } else if !reflect.DeepEqual(testCase.output, output) { 162 | t.Fatalf("expected filter arg %v but got %v", testCase.output, output) 163 | } 164 | }) 165 | } 166 | } 167 | 168 | var ( 169 | testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") 170 | testAddr = crypto.PubkeyToAddress(testKey.PublicKey) 171 | testBalance = big.NewInt(2e15) 172 | ) 173 | 174 | func newTestBackend(t *testing.T) (*node.Node, []*types.Block) { 175 | // Generate test chain. 176 | genesis, blocks := generateTestChain() 177 | // Create node 178 | n, err := node.New(&node.Config{}) 179 | if err != nil { 180 | t.Fatalf("can't create new node: %v", err) 181 | } 182 | // Create Ethereum Service 183 | config := ðconfig.Config{Genesis: genesis} 184 | config.Ethash.PowMode = ethash.ModeFake 185 | ethservice, err := eth.New(n, config) 186 | if err != nil { 187 | t.Fatalf("can't create new ethereum service: %v", err) 188 | } 189 | // Import the test chain. 190 | if err := n.Start(); err != nil { 191 | t.Fatalf("can't start test node: %v", err) 192 | } 193 | if _, err := ethservice.BlockChain().InsertChain(blocks[1:]); err != nil { 194 | t.Fatalf("can't import test blocks: %v", err) 195 | } 196 | return n, blocks 197 | } 198 | 199 | func generateTestChain() (*core.Genesis, []*types.Block) { 200 | db := rawdb.NewMemoryDatabase() 201 | config := params.AllEthashProtocolChanges 202 | genesis := &core.Genesis{ 203 | Config: config, 204 | Alloc: core.GenesisAlloc{testAddr: {Balance: testBalance}}, 205 | ExtraData: []byte("test genesis"), 206 | Timestamp: 9000, 207 | BaseFee: big.NewInt(params.InitialBaseFee), 208 | } 209 | generate := func(i int, g *core.BlockGen) { 210 | g.OffsetTime(5) 211 | g.SetExtra([]byte("test")) 212 | } 213 | gblock := genesis.ToBlock() 214 | engine := ethash.NewFaker() 215 | blocks, _ := core.GenerateChain(config, gblock, engine, db, 1, generate) 216 | blocks = append([]*types.Block{gblock}, blocks...) 217 | return genesis, blocks 218 | } 219 | 220 | func TestEthClient(t *testing.T) { 221 | backend, chain := newTestBackend(t) 222 | client, _ := backend.Attach() 223 | defer backend.Close() 224 | defer client.Close() 225 | 226 | tests := map[string]struct { 227 | test func(t *testing.T) 228 | }{ 229 | "TestHeader": { 230 | func(t *testing.T) { testHeader(t, chain, client) }, 231 | }, 232 | "TestBalanceAt": { 233 | func(t *testing.T) { testBalanceAt(t, client) }, 234 | }, 235 | "TestTxInBlockInterrupted": { 236 | func(t *testing.T) { testTransactionInBlockInterrupted(t, client) }, 237 | }, 238 | "TestChainID": { 239 | func(t *testing.T) { testChainID(t, client) }, 240 | }, 241 | "TestGetBlock": { 242 | func(t *testing.T) { testGetBlock(t, client) }, 243 | }, 244 | "TestStatusFunctions": { 245 | func(t *testing.T) { testStatusFunctions(t, client) }, 246 | }, 247 | "TestCallContract": { 248 | func(t *testing.T) { testCallContract(t, client) }, 249 | }, 250 | "TestAtFunctions": { 251 | func(t *testing.T) { testAtFunctions(t, client) }, 252 | }, 253 | } 254 | 255 | t.Parallel() 256 | for name, tt := range tests { 257 | t.Run(name, tt.test) 258 | } 259 | } 260 | 261 | func testHeader(t *testing.T, chain []*types.Block, client *rpc.Client) { 262 | tests := map[string]struct { 263 | block *big.Int 264 | want *types.Header 265 | wantErr error 266 | }{ 267 | "genesis": { 268 | block: big.NewInt(0), 269 | want: chain[0].Header(), 270 | }, 271 | "first_block": { 272 | block: big.NewInt(1), 273 | want: chain[1].Header(), 274 | }, 275 | "future_block": { 276 | block: big.NewInt(1000000000), 277 | want: nil, 278 | wantErr: ethereum.NotFound, 279 | }, 280 | } 281 | for name, tt := range tests { 282 | t.Run(name, func(t *testing.T) { 283 | ec := NewClient(client) 284 | ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) 285 | defer cancel() 286 | 287 | got, err := ec.HeaderByNumber(ctx, tt.block) 288 | if !errors.Is(err, tt.wantErr) { 289 | t.Fatalf("HeaderByNumber(%v) error = %q, want %q", tt.block, err, tt.wantErr) 290 | } 291 | if got != nil && got.Number != nil && got.Number.Sign() == 0 { 292 | got.Number = big.NewInt(0) // hack to make DeepEqual work 293 | } 294 | if !reflect.DeepEqual(got, tt.want) { 295 | t.Fatalf("HeaderByNumber(%v)\n = %v\nwant %v", tt.block, got, tt.want) 296 | } 297 | }) 298 | } 299 | } 300 | 301 | func testBalanceAt(t *testing.T, client *rpc.Client) { 302 | tests := map[string]struct { 303 | account common.Address 304 | block *big.Int 305 | want *big.Int 306 | wantErr error 307 | }{ 308 | "valid_account": { 309 | account: testAddr, 310 | block: big.NewInt(1), 311 | want: testBalance, 312 | }, 313 | "non_existent_account": { 314 | account: common.Address{1}, 315 | block: big.NewInt(1), 316 | want: big.NewInt(0), 317 | }, 318 | "future_block": { 319 | account: testAddr, 320 | block: big.NewInt(1000000000), 321 | want: big.NewInt(0), 322 | wantErr: errors.New("header not found"), 323 | }, 324 | } 325 | for name, tt := range tests { 326 | t.Run(name, func(t *testing.T) { 327 | ec := NewClient(client) 328 | ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) 329 | defer cancel() 330 | 331 | got, err := ec.BalanceAt(ctx, tt.account, tt.block) 332 | if tt.wantErr != nil && (err == nil || err.Error() != tt.wantErr.Error()) { 333 | t.Fatalf("BalanceAt(%x, %v) error = %q, want %q", tt.account, tt.block, err, tt.wantErr) 334 | } 335 | if got.Cmp(tt.want) != 0 { 336 | t.Fatalf("BalanceAt(%x, %v) = %v, want %v", tt.account, tt.block, got, tt.want) 337 | } 338 | }) 339 | } 340 | } 341 | 342 | func testTransactionInBlockInterrupted(t *testing.T, client *rpc.Client) { 343 | ec := NewClient(client) 344 | 345 | // Get current block by number 346 | block, err := ec.BlockByNumber(context.Background(), nil) 347 | if err != nil { 348 | t.Fatalf("unexpected error: %v", err) 349 | } 350 | // Test tx in block interupted 351 | ctx, cancel := context.WithCancel(context.Background()) 352 | cancel() 353 | tx, err := ec.TransactionInBlock(ctx, block.Hash(), 1) 354 | if tx != nil { 355 | t.Fatal("transaction should be nil") 356 | } 357 | if err == nil || err == ethereum.NotFound { 358 | t.Fatal("error should not be nil/notfound") 359 | } 360 | // Test tx in block not found 361 | if _, err := ec.TransactionInBlock(context.Background(), block.Hash(), 1); err != ethereum.NotFound { 362 | t.Fatal("error should be ethereum.NotFound") 363 | } 364 | } 365 | 366 | func testChainID(t *testing.T, client *rpc.Client) { 367 | ec := NewClient(client) 368 | id, err := ec.ChainID(context.Background()) 369 | if err != nil { 370 | t.Fatalf("unexpected error: %v", err) 371 | } 372 | if id == nil || id.Cmp(params.AllEthashProtocolChanges.ChainID) != 0 { 373 | t.Fatalf("ChainID returned wrong number: %+v", id) 374 | } 375 | } 376 | 377 | func testGetBlock(t *testing.T, client *rpc.Client) { 378 | ec := NewClient(client) 379 | // Get current block number 380 | blockNumber, err := ec.BlockNumber(context.Background()) 381 | if err != nil { 382 | t.Fatalf("unexpected error: %v", err) 383 | } 384 | if blockNumber != 1 { 385 | t.Fatalf("BlockNumber returned wrong number: %d", blockNumber) 386 | } 387 | // Get current block by number 388 | block, err := ec.BlockByNumber(context.Background(), new(big.Int).SetUint64(blockNumber)) 389 | if err != nil { 390 | t.Fatalf("unexpected error: %v", err) 391 | } 392 | if block.NumberU64() != blockNumber { 393 | t.Fatalf("BlockByNumber returned wrong block: want %d got %d", blockNumber, block.NumberU64()) 394 | } 395 | // Get current block by hash 396 | blockH, err := ec.BlockByHash(context.Background(), block.Hash()) 397 | if err != nil { 398 | t.Fatalf("unexpected error: %v", err) 399 | } 400 | if block.Hash() != blockH.Hash() { 401 | t.Fatalf("BlockByHash returned wrong block: want %v got %v", block.Hash().Hex(), blockH.Hash().Hex()) 402 | } 403 | // Get header by number 404 | header, err := ec.HeaderByNumber(context.Background(), new(big.Int).SetUint64(blockNumber)) 405 | if err != nil { 406 | t.Fatalf("unexpected error: %v", err) 407 | } 408 | if block.Header().Hash() != header.Hash() { 409 | t.Fatalf("HeaderByNumber returned wrong header: want %v got %v", block.Header().Hash().Hex(), header.Hash().Hex()) 410 | } 411 | // Get header by hash 412 | headerH, err := ec.HeaderByHash(context.Background(), block.Hash()) 413 | if err != nil { 414 | t.Fatalf("unexpected error: %v", err) 415 | } 416 | if block.Header().Hash() != headerH.Hash() { 417 | t.Fatalf("HeaderByHash returned wrong header: want %v got %v", block.Header().Hash().Hex(), headerH.Hash().Hex()) 418 | } 419 | } 420 | 421 | func testStatusFunctions(t *testing.T, client *rpc.Client) { 422 | ec := NewClient(client) 423 | 424 | // Sync progress 425 | progress, err := ec.SyncProgress(context.Background()) 426 | if err != nil { 427 | t.Fatalf("unexpected error: %v", err) 428 | } 429 | if progress != nil { 430 | t.Fatalf("unexpected progress: %v", progress) 431 | } 432 | // NetworkID 433 | networkID, err := ec.NetworkID(context.Background()) 434 | if err != nil { 435 | t.Fatalf("unexpected error: %v", err) 436 | } 437 | if networkID.Cmp(big.NewInt(0)) != 0 { 438 | t.Fatalf("unexpected networkID: %v", networkID) 439 | } 440 | // SuggestGasPrice (should suggest 1 Gwei) 441 | gasPrice, err := ec.SuggestGasPrice(context.Background()) 442 | if err != nil { 443 | t.Fatalf("unexpected error: %v", err) 444 | } 445 | if gasPrice.Cmp(big.NewInt(1875000000)) != 0 { // 1 gwei tip + 0.875 basefee after a 1 gwei fee empty block 446 | t.Fatalf("unexpected gas price: %v", gasPrice) 447 | } 448 | 449 | // SuggestGasTipCap (should suggest 1 Gwei) 450 | gasTipCap, err := ec.SuggestGasTipCap(context.Background()) 451 | if err != nil { 452 | t.Fatalf("unexpected error: %v", err) 453 | } 454 | if gasTipCap.Cmp(big.NewInt(1000000000)) != 0 { 455 | t.Fatalf("unexpected gas tip cap: %v", gasTipCap) 456 | } 457 | } 458 | 459 | func testCallContract(t *testing.T, client *rpc.Client) { 460 | ec := NewClient(client) 461 | 462 | // EstimateGas 463 | msg := ethereum.CallMsg{ 464 | From: testAddr, 465 | To: &common.Address{}, 466 | Gas: 21000, 467 | Value: big.NewInt(1), 468 | } 469 | gas, err := ec.EstimateGas(context.Background(), msg) 470 | if err != nil { 471 | t.Fatalf("unexpected error: %v", err) 472 | } 473 | if gas != 21000 { 474 | t.Fatalf("unexpected gas price: %v", gas) 475 | } 476 | // CallContract 477 | if _, err := ec.CallContract(context.Background(), msg, big.NewInt(1)); err != nil { 478 | t.Fatalf("unexpected error: %v", err) 479 | } 480 | // PendingCallCOntract 481 | if _, err := ec.PendingCallContract(context.Background(), msg); err != nil { 482 | t.Fatalf("unexpected error: %v", err) 483 | } 484 | } 485 | 486 | func testAtFunctions(t *testing.T, client *rpc.Client) { 487 | ec := NewClient(client) 488 | // send a transaction for some interesting pending status 489 | _ = sendTransaction(ec) 490 | time.Sleep(100 * time.Millisecond) 491 | // Check pending transaction count 492 | pending, err := ec.PendingTransactionCount(context.Background()) 493 | if err != nil { 494 | t.Fatalf("unexpected error: %v", err) 495 | } 496 | if pending != 1 { 497 | t.Fatalf("unexpected pending, wanted 1 got: %v", pending) 498 | } 499 | // Query balance 500 | balance, err := ec.BalanceAt(context.Background(), testAddr, nil) 501 | if err != nil { 502 | t.Fatalf("unexpected error: %v", err) 503 | } 504 | penBalance, err := ec.PendingBalanceAt(context.Background(), testAddr) 505 | if err != nil { 506 | t.Fatalf("unexpected error: %v", err) 507 | } 508 | if balance.Cmp(penBalance) == 0 { 509 | t.Fatalf("unexpected balance: %v %v", balance, penBalance) 510 | } 511 | // NonceAt 512 | nonce, err := ec.NonceAt(context.Background(), testAddr, nil) 513 | if err != nil { 514 | t.Fatalf("unexpected error: %v", err) 515 | } 516 | penNonce, err := ec.PendingNonceAt(context.Background(), testAddr) 517 | if err != nil { 518 | t.Fatalf("unexpected error: %v", err) 519 | } 520 | if penNonce != nonce+1 { 521 | t.Fatalf("unexpected nonce: %v %v", nonce, penNonce) 522 | } 523 | // StorageAt 524 | storage, err := ec.StorageAt(context.Background(), testAddr, common.Hash{}, nil) 525 | if err != nil { 526 | t.Fatalf("unexpected error: %v", err) 527 | } 528 | penStorage, err := ec.PendingStorageAt(context.Background(), testAddr, common.Hash{}) 529 | if err != nil { 530 | t.Fatalf("unexpected error: %v", err) 531 | } 532 | if !bytes.Equal(storage, penStorage) { 533 | t.Fatalf("unexpected storage: %v %v", storage, penStorage) 534 | } 535 | // CodeAt 536 | code, err := ec.CodeAt(context.Background(), testAddr, nil) 537 | if err != nil { 538 | t.Fatalf("unexpected error: %v", err) 539 | } 540 | penCode, err := ec.PendingCodeAt(context.Background(), testAddr) 541 | if err != nil { 542 | t.Fatalf("unexpected error: %v", err) 543 | } 544 | if !bytes.Equal(code, penCode) { 545 | t.Fatalf("unexpected code: %v %v", code, penCode) 546 | } 547 | } 548 | 549 | func sendTransaction(ec *Client) error { 550 | // Retrieve chainID 551 | chainID, err := ec.ChainID(context.Background()) 552 | if err != nil { 553 | return err 554 | } 555 | // Create transaction 556 | tx := types.NewTransaction(0, common.Address{1}, big.NewInt(1), 22000, big.NewInt(params.InitialBaseFee), nil) 557 | signer := types.LatestSignerForChainID(chainID) 558 | signature, err := crypto.Sign(signer.Hash(tx).Bytes(), testKey) 559 | if err != nil { 560 | return err 561 | } 562 | signedTx, err := tx.WithSignature(signer, signature) 563 | if err != nil { 564 | return err 565 | } 566 | // Send transaction 567 | return ec.SendTransaction(context.Background(), signedTx) 568 | } -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /docs/images/jetbrains/jetbrains-variant-3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 15 | 16 | 17 | 18 | 19 | 20 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 55 | 56 | 57 | 60 | 62 | 64 | 65 | 66 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | ## ethclient 2 | 3 | ethclient is extend [go-ethereum](https://github.com/ethereum/go-ethereum) client. 4 | 5 | - Add `BalanceOf` for query token balance 6 | 7 | - Add `CalculatePoolAddressV2` `CalculatePoolAddressV3` for calculate uniswap pool address offline 8 | 9 | ## install 10 | 11 | ``` 12 | go get github.com/ackermanx/ethclient 13 | ``` 14 | 15 | ## usage 16 | Below is an example which shows some common use cases for ethclient. Check [ethclient_test.go](https://github.com/ackermanx/ethclient/blob/main/ethclient/ethclient_test.go) for more usage. 17 | 18 | ### get balance 19 | 20 | ```go 21 | package main 22 | 23 | import ( 24 | "context" 25 | "log" 26 | "time" 27 | "github.com/ackermanx/ethclient/ethclient" 28 | "github.com/ethereum/go-ethereum/common" 29 | ) 30 | 31 | func main() { 32 | var binanceMainnet = `https://bsc-dataseed.binance.org` 33 | 34 | ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) 35 | client, err := ethclient.DialContext(ctx, binanceMainnet) 36 | cancel() 37 | if err != nil { 38 | panic(err) 39 | } 40 | 41 | // get latest height 42 | ctx, cancel = context.WithTimeout(context.Background(), time.Second*5) 43 | blockNumber, err := client.BlockNumber(ctx) 44 | cancel() 45 | if err != nil { 46 | panic(err) 47 | } 48 | log.Println("latest block number: ", blockNumber) 49 | 50 | // get busd balance 51 | busdContractAddress := common.HexToAddress("0xe9e7cea3dedca5984780bafc599bd69add087d56") 52 | address := common.HexToAddress("0x0D022fA46e3124634c42219DF9587A91972c3930") 53 | balance, err := client.BalanceOf(address, busdContractAddress) 54 | if err != nil { 55 | panic(err) 56 | } 57 | 58 | log.Printf("address busd balance: %s\n", balance.String()) 59 | } 60 | ``` 61 | 62 | ### generate pool address offline 63 | 64 | ```go 65 | package main 66 | 67 | import ( 68 | "fmt" 69 | "math/big" 70 | "github.com/ackermanx/ethclient/swap" 71 | ) 72 | 73 | func main() { 74 | weth := "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" 75 | dai := "0x6b175474e89094c44da98b954eedeac495271d0f" 76 | pair, err := swap.CalculatePoolAddressV2(weth, dai) 77 | if err != nil { 78 | panic(err) 79 | } 80 | 81 | fmt.Printf("weth-dai pair address in uniswap v2: %s\n", pair.String()) 82 | 83 | fee := big.NewInt(3000) 84 | poolAddress, err := swap.CalculatePoolAddressV3(weth, dai, fee) 85 | if err != nil { 86 | panic(err) 87 | } 88 | fmt.Printf("weth-dai pool address in uniswap v3: %s\n", poolAddress.String()) 89 | } 90 | 91 | ``` 92 | -------------------------------------------------------------------------------- /examples/aes/aes.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/hex" 5 | "log" 6 | 7 | "github.com/ackermanx/ethclient/wallet" 8 | ) 9 | 10 | func main() { 11 | oriData := "hello world" 12 | key := "123443211234432112341234123412" 13 | encryptData, err := wallet.AesCBCEncrypt([]byte(oriData), []byte(key)) 14 | if err != nil { 15 | log.Fatal(err) 16 | } 17 | encryptDataHex := hex.EncodeToString(encryptData) 18 | log.Printf("aes crypto content: %s", encryptDataHex) 19 | 20 | encryptData, err = hex.DecodeString(encryptDataHex) 21 | if err != nil { 22 | log.Fatal(err) 23 | } 24 | 25 | decodeData, err := wallet.AesCBCDecrypt(encryptData, []byte(key)) 26 | if err != nil { 27 | log.Fatal(err) 28 | } 29 | log.Println(string(decodeData)) 30 | } 31 | -------------------------------------------------------------------------------- /examples/ethclient/ethclient.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "math/big" 8 | "time" 9 | 10 | "github.com/ackermanx/ethclient" 11 | "github.com/ackermanx/ethclient/abi" 12 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 13 | "github.com/ethereum/go-ethereum/common" 14 | ) 15 | 16 | var ( 17 | testKey = "e923f0f5beacbc2b7f43a85016421a2a5260f76f3c6a63c5740fdd7d1a17755f" 18 | ) 19 | 20 | func main() { 21 | var binanceMainnet = `https://data-seed-prebsc-1-s2.binance.org:8545` 22 | ctx, cancel := context.WithTimeout(context.Background(), time.Second*15) 23 | c, err := ethclient.DialContext(ctx, binanceMainnet) 24 | cancel() 25 | if err != nil { 26 | panic(err) 27 | } 28 | getLatestBlock(c) 29 | 30 | getTokenBalance(c) 31 | // build contract transfer 32 | gasFeeCap := big.NewInt(2000000000) 33 | gasTipCap := big.NewInt(1000000000) 34 | usdt := common.HexToAddress("0xe2ccf22450855c7eae0f1d15421d851ff6a95656") 35 | tx, err := c.BuildContractTx( 36 | testKey, "transfer", 37 | abi.ERC20Abi, 38 | &usdt, &bind.TransactOpts{From: common.HexToAddress("0x431beE0E54b49105964E11b9035A198A1D4735AD"), GasFeeCap: gasFeeCap, GasTipCap: gasTipCap}, 39 | common.HexToAddress("0x550f2A264299d7958D495023E1810064289A64C8"), 40 | big.NewInt(3000000), 41 | ) 42 | if err != nil { 43 | panic(err) 44 | } 45 | err = c.SendTransaction(context.Background(), tx) 46 | if err != nil { 47 | panic(err) 48 | } 49 | log.Println(tx.Hash().String()) 50 | 51 | } 52 | 53 | func getTokenBalance(c *ethclient.Client) { 54 | // get busd balance 55 | busdContractAddress := common.HexToAddress("0xed24fc36d5ee211ea25a80239fb8c4cfd80f12ee") 56 | address := "0xe96e6b50db659935878f6f3b0491B7F192cf5F59" 57 | bnbBalance, err := c.BalanceAt(context.Background(), common.HexToAddress(address), nil) 58 | if err != nil { 59 | panic(err) 60 | } 61 | fmt.Println("bnbBalance: ", bnbBalance.String()) 62 | balance, err := c.BalanceOf(address, busdContractAddress.String()) 63 | if err != nil { 64 | panic(err) 65 | } 66 | fmt.Printf("address busd balance: %s\n", balance.String()) 67 | } 68 | 69 | func getLatestBlock(c *ethclient.Client) { 70 | // get latest height 71 | ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) 72 | blockNumber, err := c.BlockNumber(ctx) 73 | cancel() 74 | if err != nil { 75 | panic(err) 76 | } 77 | fmt.Println("latest block number: ", blockNumber) 78 | } 79 | 80 | func sendMainToken(c *ethclient.Client) { 81 | // send bnb 82 | tx, err := c.BuildTransferTx(testKey, "0x38F32C2473a314d447d681D30e1C0f5D07194371", &bind.TransactOpts{ 83 | From: common.HexToAddress("0xe96e6b50db659935878f6f3b0491b7f192cf5f59"), 84 | Value: big.NewInt(20000000000000000), 85 | GasLimit: 21000, 86 | }) 87 | if err != nil { 88 | panic(err) 89 | } 90 | fmt.Println(tx.Hash().String()) 91 | err = c.SendTransaction(context.Background(), tx) 92 | if err != nil { 93 | panic(err) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /examples/pool-address/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math/big" 6 | 7 | "github.com/ackermanx/ethclient/uniswap" 8 | ) 9 | 10 | func main() { 11 | weth := "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" 12 | dai := "0x6b175474e89094c44da98b954eedeac495271d0f" 13 | pair, err := uniswap.CalculatePoolAddressV2(weth, dai) 14 | if err != nil { 15 | panic(err) 16 | } 17 | 18 | fmt.Printf("weth-dai pair address in uniswap v2: %s\n", pair.String()) 19 | 20 | fee := big.NewInt(3000) 21 | poolAddress, err := uniswap.CalculatePoolAddressV3(weth, dai, fee) 22 | if err != nil { 23 | panic(err) 24 | } 25 | fmt.Printf("weth-dai pool address in uniswap v3: %s\n", poolAddress.String()) 26 | } 27 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ackermanx/ethclient 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/btcsuite/btcd v0.21.0-beta 7 | github.com/btcsuite/btcutil v1.0.2 8 | github.com/ethereum/go-ethereum v1.10.25 9 | github.com/pkg/errors v0.9.1 10 | github.com/shopspring/decimal v1.2.0 11 | github.com/stretchr/testify v1.7.2 12 | github.com/tyler-smith/go-bip39 v1.1.0 13 | ) 14 | 15 | require ( 16 | github.com/VictoriaMetrics/fastcache v1.6.0 // indirect 17 | github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect 18 | github.com/cespare/xxhash/v2 v2.1.1 // indirect 19 | github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect 20 | github.com/davecgh/go-spew v1.1.1 // indirect 21 | github.com/deckarep/golang-set v1.8.0 // indirect 22 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect 23 | github.com/edsrzf/mmap-go v1.0.0 // indirect 24 | github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 // indirect 25 | github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff // indirect 26 | github.com/go-ole/go-ole v1.2.6 // indirect 27 | github.com/go-stack/stack v1.8.1 // indirect 28 | github.com/golang-jwt/jwt/v4 v4.3.0 // indirect 29 | github.com/golang/snappy v0.0.4 // indirect 30 | github.com/google/uuid v1.3.0 // indirect 31 | github.com/gorilla/websocket v1.5.0 // indirect 32 | github.com/hashicorp/go-bexpr v0.1.10 // indirect 33 | github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d // indirect 34 | github.com/holiman/bloomfilter/v2 v2.0.3 // indirect 35 | github.com/holiman/uint256 v1.2.0 // indirect 36 | github.com/huin/goupnp v1.0.3 // indirect 37 | github.com/jackpal/go-nat-pmp v1.0.2 // indirect 38 | github.com/mattn/go-colorable v0.1.8 // indirect 39 | github.com/mattn/go-isatty v0.0.12 // indirect 40 | github.com/mattn/go-runewidth v0.0.9 // indirect 41 | github.com/mitchellh/mapstructure v1.4.1 // indirect 42 | github.com/mitchellh/pointerstructure v1.2.0 // indirect 43 | github.com/olekukonko/tablewriter v0.0.5 // indirect 44 | github.com/pmezard/go-difflib v1.0.0 // indirect 45 | github.com/prometheus/tsdb v0.7.1 // indirect 46 | github.com/rjeczalik/notify v0.9.2 // indirect 47 | github.com/rs/cors v1.7.0 // indirect 48 | github.com/russross/blackfriday/v2 v2.1.0 // indirect 49 | github.com/shirou/gopsutil v3.21.11+incompatible // indirect 50 | github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4 // indirect 51 | github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect 52 | github.com/tklauser/go-sysconf v0.3.10 // indirect 53 | github.com/tklauser/numcpus v0.5.0 // indirect 54 | github.com/urfave/cli/v2 v2.10.2 // indirect 55 | github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect 56 | github.com/yusufpapurcu/wmi v1.2.2 // indirect 57 | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect 58 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect 59 | golang.org/x/sys v0.0.0-20220624220833-87e55d714810 // indirect 60 | golang.org/x/text v0.3.7 // indirect 61 | golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect 62 | gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect 63 | gopkg.in/yaml.v3 v3.0.1 // indirect 64 | ) 65 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 2 | github.com/VictoriaMetrics/fastcache v1.6.0 h1:C/3Oi3EiBCqufydp1neRZkqcwmEiuRT9c3fqvvgKm5o= 3 | github.com/VictoriaMetrics/fastcache v1.6.0/go.mod h1:0qHz5QP0GMX4pfmMA/zt5RgfNuXJrTP0zS7DqpHGGTw= 4 | github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= 5 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 6 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 7 | github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= 8 | github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= 9 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 10 | github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= 11 | github.com/btcsuite/btcd v0.21.0-beta h1:At9hIZdJW0s9E/fAz28nrz6AmcNlSVucCH796ZteX1M= 12 | github.com/btcsuite/btcd v0.21.0-beta/go.mod h1:ZSWyehm27aAuS9bvkATT+Xte3hjHZ+MRgMY/8NJ7K94= 13 | github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k= 14 | github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU= 15 | github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= 16 | github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= 17 | github.com/btcsuite/btcutil v1.0.2 h1:9iZ1Terx9fMIOtq1VrwdqfsATL9MC2l8ZrUY6YZ2uts= 18 | github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts= 19 | github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= 20 | github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= 21 | github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= 22 | github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= 23 | github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= 24 | github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= 25 | github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= 26 | github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk= 27 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= 28 | github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= 29 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 30 | github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= 31 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 32 | github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 33 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 34 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 35 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 36 | github.com/deckarep/golang-set v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsPppp4= 37 | github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo= 38 | github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= 39 | github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= 40 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= 41 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= 42 | github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= 43 | github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= 44 | github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= 45 | github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= 46 | github.com/ethereum/go-ethereum v1.10.25 h1:5dFrKJDnYf8L6/5o42abCE6a9yJm9cs4EJVRyYMr55s= 47 | github.com/ethereum/go-ethereum v1.10.25/go.mod h1:EYFyF19u3ezGLD4RqOkLq+ZCXzYbLoNDdZlMt7kyKFg= 48 | github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c= 49 | github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= 50 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 51 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= 52 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 53 | github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI= 54 | github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= 55 | github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0= 56 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 57 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 58 | github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA= 59 | github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= 60 | github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= 61 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 62 | github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= 63 | github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= 64 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 65 | github.com/golang-jwt/jwt/v4 v4.3.0 h1:kHL1vqdqWNfATmA0FNMdmZNMyZI1U6O31X4rlIPoBog= 66 | github.com/golang-jwt/jwt/v4 v4.3.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= 67 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 68 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 69 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 70 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 71 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 72 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 73 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 74 | github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 75 | github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= 76 | github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 77 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 78 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 79 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 80 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 81 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 82 | github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= 83 | github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 84 | github.com/hashicorp/go-bexpr v0.1.10 h1:9kuI5PFotCboP3dkDYFr/wi0gg0QVbSNz5oFRpxn4uE= 85 | github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0= 86 | github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs= 87 | github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= 88 | github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= 89 | github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= 90 | github.com/holiman/uint256 v1.2.0 h1:gpSYcPLWGv4sG43I2mVLiDZCNDh/EpGjSk8tmtxitHM= 91 | github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= 92 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 93 | github.com/huin/goupnp v1.0.3 h1:N8No57ls+MnjlB+JPiCVSOyy/ot7MJTqlo7rn+NYSqQ= 94 | github.com/huin/goupnp v1.0.3/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y= 95 | github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= 96 | github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= 97 | github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= 98 | github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= 99 | github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= 100 | github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= 101 | github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= 102 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= 103 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 104 | github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= 105 | github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 106 | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= 107 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 108 | github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= 109 | github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 110 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 111 | github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= 112 | github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 113 | github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjUEN1uBnDo34A= 114 | github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= 115 | github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= 116 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= 117 | github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= 118 | github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= 119 | github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= 120 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 121 | github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 122 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= 123 | github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= 124 | github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= 125 | github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= 126 | github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 127 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= 128 | github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= 129 | github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= 130 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 131 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 132 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 133 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 134 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 135 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 136 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 137 | github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 138 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 139 | github.com/prometheus/tsdb v0.7.1 h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA= 140 | github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= 141 | github.com/rjeczalik/notify v0.9.2 h1:MiTWrPj55mNDHEiIX5YUSKefw/+lCQVoAFmD6oQm5w8= 142 | github.com/rjeczalik/notify v0.9.2/go.mod h1:aErll2f0sUX9PXZnVNyeiObbmTlk5jnMoCa4QEjJeqM= 143 | github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= 144 | github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= 145 | github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= 146 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 147 | github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= 148 | github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= 149 | github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= 150 | github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= 151 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 152 | github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4 h1:Gb2Tyox57NRNuZ2d3rmvB3pcmbu7O1RS3m8WRx7ilrg= 153 | github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= 154 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 155 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 156 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 157 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 158 | github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= 159 | github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= 160 | github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= 161 | github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= 162 | github.com/tklauser/go-sysconf v0.3.10 h1:IJ1AZGZRWbY8T5Vfk04D9WOA5WSejdflXxP03OUqALw= 163 | github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk= 164 | github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ= 165 | github.com/tklauser/numcpus v0.5.0 h1:ooe7gN0fg6myJ0EKoTAf5hebTZrH52px3New/D9iJ+A= 166 | github.com/tklauser/numcpus v0.5.0/go.mod h1:OGzpTxpcIMNGYQdit2BYL1pvk/dSOaJWjKoflh+RQjo= 167 | github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8= 168 | github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U= 169 | github.com/urfave/cli/v2 v2.10.2 h1:x3p8awjp/2arX+Nl/G2040AZpOCHS/eMJJ1/a+mye4Y= 170 | github.com/urfave/cli/v2 v2.10.2/go.mod h1:f8iq5LtQ/bLxafbdBSLPPNsgaW0l/2fYYEHhAyPlwvo= 171 | github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= 172 | github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= 173 | github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= 174 | github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= 175 | golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 176 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 177 | golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 178 | golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 179 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 180 | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY= 181 | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 182 | golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 183 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 184 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 185 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 186 | golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 187 | golang.org/x/net v0.0.0-20220607020251-c690dde0001d h1:4SFsTMi4UahlKoloni7L4eYzhFRifURQLw+yv0QDCx8= 188 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 189 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 190 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= 191 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 192 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 193 | golang.org/x/sys v0.0.0-20180926160741-c2ed4eda69e7/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 194 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 195 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 196 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 197 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 198 | golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 199 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 200 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 201 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 202 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 203 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 204 | golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 205 | golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 206 | golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 207 | golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 208 | golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 209 | golang.org/x/sys v0.0.0-20220624220833-87e55d714810 h1:rHZQSjJdAI4Xf5Qzeh2bBc5YJIkPFVM6oDtMFYmgws0= 210 | golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 211 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 212 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 213 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 214 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= 215 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 216 | golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE= 217 | golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 218 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 219 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 220 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 221 | golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df h1:5Pf6pFKu98ODmgnpvkJ3kFUOQGGLIzLIkbzUHp47618= 222 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 223 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 224 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 225 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 226 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 227 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 228 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 229 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 230 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 231 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 232 | gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= 233 | gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= 234 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 235 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 236 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 237 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 238 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 239 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 240 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 241 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 242 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 243 | -------------------------------------------------------------------------------- /signer.go: -------------------------------------------------------------------------------- 1 | package ethclient 2 | 3 | import ( 4 | "errors" 5 | "math/big" 6 | 7 | "github.com/ethereum/go-ethereum/common" 8 | "github.com/ethereum/go-ethereum/core/types" 9 | ) 10 | 11 | // senderFromServer is a types.Signer that remembers the sender address returned by the RPC 12 | // server. It is stored in the transaction's sender address cache to avoid an additional 13 | // request in TransactionSender. 14 | type senderFromServer struct { 15 | addr common.Address 16 | blockhash common.Hash 17 | } 18 | 19 | var errNotCached = errors.New("sender not cached") 20 | 21 | func setSenderFromServer(tx *types.Transaction, addr common.Address, block common.Hash) { 22 | // Use types.Sender for side-effect to store our signer into the cache. 23 | _, err := types.Sender(&senderFromServer{addr, block}, tx) 24 | if err != nil { 25 | panic(err) 26 | } 27 | } 28 | 29 | func (s *senderFromServer) Equal(other types.Signer) bool { 30 | os, ok := other.(*senderFromServer) 31 | return ok && os.blockhash == s.blockhash 32 | } 33 | 34 | func (s *senderFromServer) Sender(tx *types.Transaction) (common.Address, error) { 35 | if s.blockhash == (common.Hash{}) { 36 | return common.Address{}, errNotCached 37 | } 38 | return s.addr, nil 39 | } 40 | 41 | func (s *senderFromServer) ChainID() *big.Int { 42 | panic("can't sign with senderFromServer") 43 | } 44 | func (s *senderFromServer) Hash(tx *types.Transaction) common.Hash { 45 | panic("can't sign with senderFromServer") 46 | } 47 | func (s *senderFromServer) SignatureValues(tx *types.Transaction, sig []byte) (R, S, V *big.Int, err error) { 48 | panic("can't sign with senderFromServer") 49 | } 50 | -------------------------------------------------------------------------------- /tests/abi_json_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "crypto/sha1" 5 | "strings" 6 | "testing" 7 | 8 | erc20 "github.com/ackermanx/ethclient/abi" 9 | "github.com/ethereum/go-ethereum/accounts/abi" 10 | "github.com/ethereum/go-ethereum/common" 11 | ) 12 | 13 | // go test -bench=. -cpu=4 -benchmem=true -run=none -memprofile mem.out -cpuprofile=cpu.prof 14 | // 查看mem.out pprof -http=:8080 mem.out 15 | func BenchmarkAddressToString(b *testing.B) { 16 | m := make(map[common.Address]abi.ABI) 17 | r := strings.NewReader(erc20.ERC20Abi) 18 | 19 | a := common.HexToAddress("0x431beE0E54b49105964E11b9035A198A1D4735AD") 20 | for i := 0; i < b.N; i++ { 21 | parsedAbi, ok := m[a] 22 | if !ok { 23 | p, err := abi.JSON(r) 24 | if err != nil { 25 | b.Fatal(err) 26 | } 27 | m[a] = p 28 | parsedAbi = p 29 | } 30 | _ = parsedAbi 31 | } 32 | } 33 | 34 | func BenchmarkSha1(b *testing.B) { 35 | m := make(map[string]abi.ABI) 36 | 37 | for i := 0; i < b.N; i++ { 38 | s := sha1.New() 39 | s.Write([]byte(erc20.ERC20Abi)) 40 | d := s.Sum(nil) 41 | key := string(d) 42 | parsedAbi, ok := m[key] 43 | if !ok { 44 | p, err := abi.JSON(strings.NewReader(erc20.ERC20Abi)) 45 | if err != nil { 46 | b.Fatal(err) 47 | } 48 | m[string(key)] = p 49 | parsedAbi = p 50 | } 51 | _ = parsedAbi 52 | } 53 | } 54 | 55 | func BenchmarkAbiJson(b *testing.B) { 56 | for i := 0; i < b.N; i++ { 57 | parsedAbi, err := abi.JSON(strings.NewReader(erc20.ERC20Abi)) 58 | if err != nil { 59 | b.Fatal(err) 60 | } 61 | _ = parsedAbi 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /uniswap/address.go: -------------------------------------------------------------------------------- 1 | package uniswap 2 | 3 | import ( 4 | "encoding/hex" 5 | "math/big" 6 | 7 | "github.com/ethereum/go-ethereum/accounts/abi" 8 | "github.com/ethereum/go-ethereum/common" 9 | "github.com/ethereum/go-ethereum/crypto" 10 | "github.com/pkg/errors" 11 | ) 12 | 13 | const ( 14 | FactoryAddrV3 = "0x1F98431c8aD98523631AE4a59f267346ea31F984" 15 | FactoryAddrV2 = "0x5c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f" 16 | ) 17 | 18 | var ( 19 | Address, _ = abi.NewType("address", "", nil) 20 | Uint24, _ = abi.NewType("uint24", "", nil) 21 | saltAbiArguments = abi.Arguments{ 22 | abi.Argument{ 23 | Name: "token0", 24 | Type: Address, 25 | Indexed: false, 26 | }, 27 | abi.Argument{ 28 | Name: "token1", 29 | Type: Address, 30 | Indexed: false, 31 | }, 32 | abi.Argument{ 33 | Name: "fee", 34 | Type: Uint24, 35 | Indexed: false, 36 | }, 37 | } 38 | PoolInitCodeV3, _ = hex.DecodeString("e34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54") 39 | PoolInitCodeV2, _ = hex.DecodeString("96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f") 40 | ) 41 | 42 | // CalculatePoolAddressV2 calculate uniswapV2 pool address offline from pool tokens 43 | func CalculatePoolAddressV2(token0, token1 string) (pairAddress common.Address, err error) { 44 | factoryAddr := common.HexToAddress(FactoryAddrV2) 45 | tkn0, tkn1 := sortAddressess(common.HexToAddress(token0), common.HexToAddress(token1)) 46 | 47 | msg := []byte{255} 48 | msg = append(msg, factoryAddr.Bytes()...) 49 | addrBytes := tkn0.Bytes() 50 | addrBytes = append(addrBytes, tkn1.Bytes()...) 51 | msg = append(msg, crypto.Keccak256(addrBytes)...) 52 | 53 | msg = append(msg, PoolInitCodeV2...) 54 | hash := crypto.Keccak256(msg) 55 | pairAddressBytes := big.NewInt(0).SetBytes(hash) 56 | pairAddressBytes = pairAddressBytes.Abs(pairAddressBytes) 57 | return common.BytesToAddress(pairAddressBytes.Bytes()), nil 58 | } 59 | 60 | func sortAddressess(tkn0, tkn1 common.Address) (common.Address, common.Address) { 61 | token0Rep := new(big.Int).SetBytes(tkn0.Bytes()) 62 | token1Rep := new(big.Int).SetBytes(tkn1.Bytes()) 63 | 64 | if token0Rep.Cmp(token1Rep) > 0 { 65 | tkn0, tkn1 = tkn1, tkn0 66 | } 67 | 68 | return tkn0, tkn1 69 | } 70 | 71 | // CalculatePoolAddressV3 calculate uniswapV3 pool address offline from pool tokens and fee 72 | func CalculatePoolAddressV3(tokenA, tokenB string, fee *big.Int) (poolAddress common.Address, err error) { 73 | tkn0, tkn1 := sortAddressess(common.HexToAddress(tokenA), common.HexToAddress(tokenB)) 74 | paramsPacked, err := saltAbiArguments.Pack(tkn0, tkn1, fee) 75 | if err != nil { 76 | err = errors.Wrap(err, "pack arguments") 77 | return 78 | } 79 | 80 | salt := crypto.Keccak256(paramsPacked) 81 | // "0xff" 82 | msg := []byte{255} 83 | msg = append(msg, common.HexToAddress(FactoryAddrV3).Bytes()...) 84 | msg = append(msg, salt...) 85 | msg = append(msg, PoolInitCodeV3...) 86 | 87 | hash := crypto.Keccak256(msg) 88 | return common.BytesToAddress(hash[12:]), nil 89 | } 90 | 91 | // CalculatePoolAddress calculate uniswapV2 like pool address from token and pool address and pool init code 92 | func CalculatePoolAddress(tokenA, tokenB, factoryAddr common.Address, poolInitCodeStr string) (poolAddr common.Address, err error) { 93 | poolInitCode, err := hex.DecodeString(poolInitCodeStr) 94 | if err != nil { 95 | err = errors.Wrap(err, "decode pool init code failed") 96 | return 97 | } 98 | 99 | tkn0, tkn1 := sortAddressess(tokenA, tokenB) 100 | msg := []byte{255} 101 | msg = append(msg, factoryAddr.Bytes()...) 102 | addrBytes := tkn0.Bytes() 103 | addrBytes = append(addrBytes, tkn1.Bytes()...) 104 | msg = append(msg, crypto.Keccak256(addrBytes)...) 105 | 106 | msg = append(msg, poolInitCode...) 107 | hash := crypto.Keccak256(msg) 108 | pairAddressBytes := big.NewInt(0).SetBytes(hash) 109 | pairAddressBytes = pairAddressBytes.Abs(pairAddressBytes) 110 | return common.BytesToAddress(pairAddressBytes.Bytes()), nil 111 | } 112 | -------------------------------------------------------------------------------- /uniswap/address_test.go: -------------------------------------------------------------------------------- 1 | package uniswap 2 | 3 | import ( 4 | "math/big" 5 | "testing" 6 | 7 | "github.com/ethereum/go-ethereum/common" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestCalculatePoolAddressV2(t *testing.T) { 12 | weth := "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" 13 | dai := "0x6b175474e89094c44da98b954eedeac495271d0f" 14 | pair, err := CalculatePoolAddressV2(weth, dai) 15 | 16 | if !assert.Equal(t, nil, err) { 17 | t.FailNow() 18 | } 19 | assert.Equal(t, common.HexToAddress("0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11"), pair) 20 | } 21 | 22 | func TestCalculatePoolAddressV3(t *testing.T) { 23 | weth := "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" 24 | dai := "0x6b175474e89094c44da98b954eedeac495271d0f" 25 | fee := big.NewInt(3000) 26 | pair, err := CalculatePoolAddressV3(weth, dai, fee) 27 | 28 | if !assert.Equal(t, nil, err) { 29 | t.FailNow() 30 | } 31 | assert.Equal(t, common.HexToAddress("0xC2e9F25Be6257c210d7Adf0D4Cd6E3E881ba25f8"), pair) 32 | } 33 | -------------------------------------------------------------------------------- /uniswap/multicall.go: -------------------------------------------------------------------------------- 1 | package uniswap 2 | 3 | import ( 4 | "math/big" 5 | "strings" 6 | 7 | "github.com/ackermanx/ethclient" 8 | "github.com/ethereum/go-ethereum/accounts/abi" 9 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 10 | "github.com/ethereum/go-ethereum/common" 11 | "github.com/pkg/errors" 12 | ) 13 | 14 | const ( 15 | MultiCallAddr = "0x5ba1e12693dc8f9c48aad8770482f4739beed696" 16 | MultiFragmentAbi = `[{"inputs":[{"internalType":"bool","name":"requireSuccess","type":"bool"},{"components":[{"internalType":"address","name":"target","type":"address"},{"internalType":"bytes","name":"callData","type":"bytes"}],"internalType":"struct Multicall2.Call[]","name":"calls","type":"tuple[]"}],"name":"tryAggregate","outputs":[{"components":[{"internalType":"bool","name":"success","type":"bool"},{"internalType":"bytes","name":"returnData","type":"bytes"}],"internalType":"struct Multicall2.Result[]","name":"returnData","type":"tuple[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"requireSuccess","type":"bool"},{"components":[{"internalType":"address","name":"target","type":"address"},{"internalType":"bytes","name":"callData","type":"bytes"}],"internalType":"struct Multicall2.Call[]","name":"calls","type":"tuple[]"}],"name":"tryBlockAndAggregate","outputs":[{"internalType":"uint256","name":"blockNumber","type":"uint256"},{"internalType":"bytes32","name":"blockHash","type":"bytes32"},{"components":[{"internalType":"bool","name":"success","type":"bool"},{"internalType":"bytes","name":"returnData","type":"bytes"}],"internalType":"struct Multicall2.Result[]","name":"returnData","type":"tuple[]"}],"stateMutability":"nonpayable","type":"function"}]` 17 | ) 18 | 19 | type Multicall2Call struct { 20 | Target common.Address 21 | CallData []byte 22 | } 23 | 24 | type Multicall2Result struct { 25 | Success bool 26 | ReturnData *big.Int 27 | } 28 | 29 | func MultiCall(client *ethclient.Client, methodName string, opts *bind.CallOpts, multiCallParam []Multicall2Call) (out []interface{}, err error) { 30 | 31 | out = make([]interface{}, 0) 32 | parsedAbi, err := abi.JSON(strings.NewReader(MultiFragmentAbi)) 33 | if err != nil { 34 | err = errors.Wrap(err, "parsed multi call abi") 35 | return 36 | } 37 | boundedContract := bind.NewBoundContract(common.HexToAddress(MultiCallAddr), parsedAbi, client, client, client) 38 | 39 | err = boundedContract.Call(opts, &out, methodName, multiCallParam) 40 | if err != nil { 41 | err = errors.Wrap(err, "call multi call") 42 | } 43 | return 44 | } 45 | -------------------------------------------------------------------------------- /uniswap/path.go: -------------------------------------------------------------------------------- 1 | package uniswap 2 | 3 | import ( 4 | "math/big" 5 | 6 | "github.com/ethereum/go-ethereum/common" 7 | "github.com/pkg/errors" 8 | ) 9 | 10 | const ( 11 | AddrSize = 20 12 | FeeSize = 3 13 | Offset = AddrSize + FeeSize 14 | DataSize = Offset + AddrSize 15 | ) 16 | 17 | // EncodePath encode path to bytes 18 | func EncodePath(path []common.Address, fees []int) (encoded []byte, err error) { 19 | if len(path) != len(fees)+1 { 20 | err = errors.New("path/fee lengths do not match") 21 | return 22 | } 23 | encoded = make([]byte, 0, len(fees)*Offset+AddrSize) 24 | for i := 0; i < len(fees); i++ { 25 | encoded = append(encoded, path[i].Bytes()...) 26 | feeBytes := big.NewInt(int64(fees[i])).Bytes() 27 | feeBytes = common.LeftPadBytes(feeBytes, 3) 28 | encoded = append(encoded, feeBytes...) 29 | } 30 | encoded = append(encoded, path[len(path)-1].Bytes()...) 31 | return 32 | } 33 | -------------------------------------------------------------------------------- /uniswap/path_test.go: -------------------------------------------------------------------------------- 1 | package uniswap 2 | 3 | import ( 4 | "encoding/hex" 5 | "testing" 6 | 7 | "github.com/ethereum/go-ethereum/common" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestEncodePath(t *testing.T) { 12 | weth := "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" 13 | dai := "0x6b175474e89094c44da98b954eedeac495271d0f" 14 | path := []common.Address{common.HexToAddress(weth), common.HexToAddress(dai)} 15 | encoded, err := EncodePath(path, []int{3000}) 16 | if err != nil { 17 | t.Fatal(err) 18 | } 19 | encodedPathHex := hex.EncodeToString(encoded) 20 | assert.Equal(t, "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000bb86b175474e89094c44da98b954eedeac495271d0f", encodedPathHex) 21 | } 22 | -------------------------------------------------------------------------------- /uniswap/sqrt_price_x96.go: -------------------------------------------------------------------------------- 1 | package uniswap 2 | 3 | import ( 4 | "math/big" 5 | 6 | "github.com/shopspring/decimal" 7 | ) 8 | 9 | var ( 10 | X96 = decimal.NewFromInt(2).Pow(decimal.NewFromInt(96)) 11 | ) 12 | 13 | // SqrtPriceX96ToPrice convert uniswap v3 sqrt price in x96 format to decimal.Decimal 14 | // zeroForOne true: price = token0/token1 false: price = token1/token0 15 | func SqrtPriceX96ToPrice(sqrtPriceX96 *big.Int, zeroForOne bool) (price decimal.Decimal) { 16 | d := decimal.NewFromBigInt(sqrtPriceX96, 0).Div(X96) 17 | p := d.Mul(d) 18 | 19 | if !zeroForOne { 20 | price = decimal.NewFromInt(1).Div(p) 21 | return 22 | } 23 | price = p 24 | return 25 | } 26 | -------------------------------------------------------------------------------- /uniswap/sqrt_price_x96_test.go: -------------------------------------------------------------------------------- 1 | package uniswap 2 | 3 | import ( 4 | "math/big" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestSqrtPX96ToPrice(t *testing.T) { 11 | // weth/dai 12 | wethPrice, _ := new(big.Int).SetString("1575977176149746651316824132", 10) 13 | 14 | price := SqrtPriceX96ToPrice(wethPrice, true) 15 | assert.Equal(t, "0.00039567688472264843148441298129", price.String()) 16 | } 17 | -------------------------------------------------------------------------------- /wallet/aes.go: -------------------------------------------------------------------------------- 1 | package wallet 2 | 3 | import ( 4 | "bytes" 5 | "crypto/aes" 6 | "crypto/cipher" 7 | 8 | "github.com/pkg/errors" 9 | ) 10 | 11 | func AesCBCEncrypt(data, key []byte) (encrypted []byte, err error) { 12 | 13 | if len(key) == 0 || len(key) > 32 { 14 | err = errors.New("key is empty or length is bigger than 32, key length must in (0,32]") 15 | return 16 | } 17 | 18 | if len(key) <= 16 { 19 | key = paddingLeft(key, '0', 16) 20 | } else if len(key) <= 24 { 21 | key = paddingLeft(key, '0', 24) 22 | } else { 23 | key = paddingLeft(key, '0', 32) 24 | } 25 | 26 | c, err := aes.NewCipher(key) 27 | if err != nil { 28 | // key length must be 16/24/32 29 | err = errors.Wrapf(err, "new cipher error, key length: %d", len(key)) 30 | return 31 | } 32 | 33 | // get block length 34 | blockSize := c.BlockSize() 35 | // padding data 36 | data = PKCS7Padding(data, blockSize) 37 | // crypt mode 38 | iv := key[:blockSize] 39 | blockMode := cipher.NewCBCEncrypter(c, iv) 40 | // create slice 41 | encrypted = make([]byte, len(data)) 42 | blockMode.CryptBlocks(encrypted, data) 43 | return 44 | } 45 | 46 | func AesCBCDecrypt(encryptedData, key []byte) (data []byte, err error) { 47 | 48 | if len(key) == 0 || len(key) > 32 { 49 | err = errors.New("key is empty or length is bigger than 32, key length must in (0,32]") 50 | return 51 | } 52 | 53 | if len(key) <= 16 { 54 | key = paddingLeft(key, '0', 16) 55 | } else if len(key) <= 24 { 56 | key = paddingLeft(key, '0', 24) 57 | } else { 58 | key = paddingLeft(key, '0', 32) 59 | } 60 | block, err := aes.NewCipher(key) 61 | if err != nil { 62 | // key length must be 16/24/32 63 | err = errors.Wrapf(err, "new cipher error, key length: %d", len(key)) 64 | return 65 | } 66 | 67 | iv := key[:block.BlockSize()] 68 | // crypt mode 69 | blockMode := cipher.NewCBCDecrypter(block, iv) 70 | // create slice 71 | data = make([]byte, len(encryptedData)) 72 | blockMode.CryptBlocks(data, encryptedData) 73 | data = PKCS7UnPadding(data) 74 | 75 | return 76 | } 77 | 78 | func paddingLeft(ori []byte, pad byte, length int) []byte { 79 | if len(ori) >= length { 80 | return ori[:length] 81 | } 82 | pads := bytes.Repeat([]byte{pad}, length-len(ori)) 83 | return append(pads, ori...) 84 | } 85 | 86 | func PKCS7UnPadding(plantText []byte) []byte { 87 | length := len(plantText) 88 | unPadding := int(plantText[length-1]) 89 | return plantText[:(length - unPadding)] 90 | } 91 | 92 | func PKCS7Padding(ciphertext []byte, blockSize int) []byte { 93 | padding := blockSize - len(ciphertext)%blockSize 94 | padtext := bytes.Repeat([]byte{byte(padding)}, padding) 95 | return append(ciphertext, padtext...) 96 | } 97 | -------------------------------------------------------------------------------- /wallet/aes_test.go: -------------------------------------------------------------------------------- 1 | package wallet 2 | 3 | import ( 4 | "encoding/hex" 5 | "testing" 6 | ) 7 | 8 | func TestAes(t *testing.T) { 9 | oriData := "hello world" 10 | key := "123443211234432112341234123412" 11 | encryptData, err := AesCBCEncrypt([]byte(oriData), []byte(key)) 12 | if err != nil { 13 | t.Fatal(err) 14 | } 15 | encryptDataHex := hex.EncodeToString(encryptData) 16 | t.Logf("aes crypto content: %s", encryptDataHex) 17 | 18 | encryptData, err = hex.DecodeString(encryptDataHex) 19 | if err != nil { 20 | t.Fatal(err) 21 | } 22 | 23 | decodeData, err := AesCBCDecrypt(encryptData, []byte(key)) 24 | if err != nil { 25 | t.Fatal(err) 26 | } 27 | t.Log(string(decodeData)) 28 | } 29 | -------------------------------------------------------------------------------- /wallet/hd.go: -------------------------------------------------------------------------------- 1 | package wallet 2 | 3 | import ( 4 | "crypto/ecdsa" 5 | "crypto/rand" 6 | "fmt" 7 | "github.com/btcsuite/btcd/chaincfg" 8 | "github.com/btcsuite/btcutil/hdkeychain" 9 | "github.com/ethereum/go-ethereum" 10 | "github.com/ethereum/go-ethereum/accounts" 11 | "github.com/ethereum/go-ethereum/common" 12 | "github.com/ethereum/go-ethereum/common/hexutil" 13 | "github.com/ethereum/go-ethereum/core/types" 14 | "github.com/ethereum/go-ethereum/crypto" 15 | "github.com/pkg/errors" 16 | "github.com/tyler-smith/go-bip39" 17 | "math/big" 18 | "sync" 19 | ) 20 | 21 | // DefaultRootDerivationPath is the root path to which custom derivation endpoints 22 | // are appended. As such, the first account will be at m/44'/60'/0'/0, the second 23 | // at m/44'/60'/0'/1, etc. 24 | var DefaultRootDerivationPath = accounts.DefaultRootDerivationPath 25 | 26 | // DefaultBaseDerivationPath is the base path from which custom derivation endpoints 27 | // are incremented. As such, the first account will be at m/44'/60'/0'/0, the second 28 | // at m/44'/60'/0'/1, etc 29 | var DefaultBaseDerivationPath = accounts.DefaultBaseDerivationPath 30 | 31 | // Wallet is the underlying wallet struct. 32 | type Wallet struct { 33 | mnemonic string 34 | masterKey *hdkeychain.ExtendedKey 35 | seed []byte 36 | paths map[common.Address]accounts.DerivationPath 37 | accounts []accounts.Account 38 | stateLock sync.RWMutex 39 | } 40 | 41 | func newWallet(seed []byte) (*Wallet, error) { 42 | masterKey, err := hdkeychain.NewMaster(seed, &chaincfg.MainNetParams) 43 | if err != nil { 44 | return nil, err 45 | } 46 | 47 | return &Wallet{ 48 | masterKey: masterKey, 49 | seed: seed, 50 | accounts: []accounts.Account{}, 51 | paths: map[common.Address]accounts.DerivationPath{}, 52 | }, nil 53 | } 54 | 55 | // NewFromMnemonic returns a new wallet from a BIP-39 mnemonic. 56 | func NewFromMnemonic(mnemonic string) (*Wallet, error) { 57 | if mnemonic == "" { 58 | return nil, errors.New("mnemonic is required") 59 | } 60 | 61 | if !bip39.IsMnemonicValid(mnemonic) { 62 | return nil, errors.New("mnemonic is invalid") 63 | } 64 | 65 | seed, err := NewSeedFromMnemonic(mnemonic) 66 | if err != nil { 67 | return nil, err 68 | } 69 | 70 | wallet, err := newWallet(seed) 71 | if err != nil { 72 | return nil, err 73 | } 74 | wallet.mnemonic = mnemonic 75 | 76 | return wallet, nil 77 | } 78 | 79 | // NewFromSeed returns a new wallet from a BIP-39 seed. 80 | func NewFromSeed(seed []byte) (*Wallet, error) { 81 | if len(seed) == 0 { 82 | return nil, errors.New("seed is required") 83 | } 84 | 85 | return newWallet(seed) 86 | } 87 | 88 | // Accounts implements accounts.Wallet, returning the list of accounts pinned to 89 | // the wallet. If self-derivation was enabled, the account list is 90 | // periodically expanded based on current chain state. 91 | func (w *Wallet) Accounts() []accounts.Account { 92 | // Attempt self-derivation if it's running 93 | // Return whatever account list we ended up with 94 | w.stateLock.RLock() 95 | defer w.stateLock.RUnlock() 96 | 97 | cpy := make([]accounts.Account, len(w.accounts)) 98 | copy(cpy, w.accounts) 99 | return cpy 100 | } 101 | 102 | // Contains implements accounts.Wallet, returning whether a particular account is 103 | // or is not pinned into this wallet instance. 104 | func (w *Wallet) Contains(account accounts.Account) bool { 105 | w.stateLock.RLock() 106 | defer w.stateLock.RUnlock() 107 | 108 | _, exists := w.paths[account.Address] 109 | return exists 110 | } 111 | 112 | // Unpin unpins account from list of pinned accounts. 113 | func (w *Wallet) Unpin(account accounts.Account) error { 114 | w.stateLock.RLock() 115 | defer w.stateLock.RUnlock() 116 | 117 | for i, acct := range w.accounts { 118 | if acct.Address.String() == account.Address.String() { 119 | w.accounts = removeAtIndex(w.accounts, i) 120 | delete(w.paths, account.Address) 121 | return nil 122 | } 123 | } 124 | 125 | return errors.New("account not found") 126 | } 127 | 128 | // Derive implements accounts.Wallet, deriving a new account at the specific 129 | // derivation path. If pin is set to true, the account will be added to the list 130 | // of tracked accounts. 131 | func (w *Wallet) Derive(path accounts.DerivationPath, pin bool) (accounts.Account, error) { 132 | // Try to derive the actual account and update its URL if successful 133 | w.stateLock.RLock() // Avoid device disappearing during derivation 134 | 135 | address, err := w.deriveAddress(path) 136 | 137 | w.stateLock.RUnlock() 138 | 139 | // If an error occurred or no pinning was requested, return 140 | if err != nil { 141 | return accounts.Account{}, err 142 | } 143 | 144 | account := accounts.Account{ 145 | Address: address, 146 | URL: accounts.URL{ 147 | Scheme: "", 148 | Path: path.String(), 149 | }, 150 | } 151 | 152 | if !pin { 153 | return account, nil 154 | } 155 | 156 | // Pinning needs to modify the state 157 | w.stateLock.Lock() 158 | defer w.stateLock.Unlock() 159 | 160 | if _, ok := w.paths[address]; !ok { 161 | w.accounts = append(w.accounts, account) 162 | w.paths[address] = path 163 | } 164 | 165 | return account, nil 166 | } 167 | 168 | // SelfDerive implements accounts.Wallet, trying to discover accounts that the 169 | // user used previously (based on the chain state), but ones that he/she did not 170 | // explicitly pin to the wallet manually. To avoid chain head monitoring, self 171 | // derivation only runs during account listing (and even then throttled). 172 | func (w *Wallet) SelfDerive(base []accounts.DerivationPath, chain ethereum.ChainStateReader) { 173 | // TODO: self derivation 174 | } 175 | 176 | // SignHash implements accounts.Wallet, which allows signing arbitrary data. 177 | func (w *Wallet) SignHash(account accounts.Account, hash []byte) ([]byte, error) { 178 | // Make sure the requested account is contained within 179 | path, ok := w.paths[account.Address] 180 | if !ok { 181 | return nil, accounts.ErrUnknownAccount 182 | } 183 | 184 | privateKey, err := w.derivePrivateKey(path) 185 | if err != nil { 186 | return nil, err 187 | } 188 | 189 | return crypto.Sign(hash, privateKey) 190 | } 191 | 192 | // SignTxEIP155 implements accounts.Wallet, which allows the account to sign an ERC-20 transaction. 193 | func (w *Wallet) SignTxEIP155(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { 194 | w.stateLock.RLock() // Comms have own mutex, this is for the state fields 195 | defer w.stateLock.RUnlock() 196 | 197 | // Make sure the requested account is contained within 198 | path, ok := w.paths[account.Address] 199 | if !ok { 200 | return nil, accounts.ErrUnknownAccount 201 | } 202 | 203 | privateKey, err := w.derivePrivateKey(path) 204 | if err != nil { 205 | return nil, err 206 | } 207 | 208 | // Sign the transaction and verify the sender to avoid hardware fault surprises 209 | signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), privateKey) 210 | if err != nil { 211 | return nil, err 212 | } 213 | 214 | msg, err := signedTx.AsMessage(types.NewEIP155Signer(chainID), nil) 215 | if err != nil { 216 | return nil, err 217 | } 218 | 219 | sender := msg.From() 220 | if sender != account.Address { 221 | return nil, fmt.Errorf("signer mismatch: expected %s, got %s", account.Address.Hex(), sender.Hex()) 222 | } 223 | 224 | return signedTx, nil 225 | } 226 | 227 | // SignTx implements accounts.Wallet, which allows the account to sign an Ethereum transaction. 228 | func (w *Wallet) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { 229 | w.stateLock.RLock() // Comms have own mutex, this is for the state fields 230 | defer w.stateLock.RUnlock() 231 | 232 | // Make sure the requested account is contained within 233 | path, ok := w.paths[account.Address] 234 | if !ok { 235 | return nil, accounts.ErrUnknownAccount 236 | } 237 | 238 | privateKey, err := w.derivePrivateKey(path) 239 | if err != nil { 240 | return nil, err 241 | } 242 | 243 | // Sign the transaction and verify the sender to avoid hardware fault surprises 244 | signedTx, err := types.SignTx(tx, types.HomesteadSigner{}, privateKey) 245 | if err != nil { 246 | return nil, err 247 | } 248 | 249 | msg, err := signedTx.AsMessage(types.HomesteadSigner{}, nil) 250 | if err != nil { 251 | return nil, err 252 | } 253 | 254 | sender := msg.From() 255 | if sender != account.Address { 256 | return nil, fmt.Errorf("signer mismatch: expected %s, got %s", account.Address.Hex(), sender.Hex()) 257 | } 258 | 259 | return signedTx, nil 260 | } 261 | 262 | // SignHashWithPassphrase implements accounts.Wallet, attempting 263 | // to sign the given hash with the given account using the 264 | // passphrase as extra authentication. 265 | func (w *Wallet) SignHashWithPassphrase(account accounts.Account, passphrase string, hash []byte) ([]byte, error) { 266 | return w.SignHash(account, hash) 267 | } 268 | 269 | // SignTxWithPassphrase implements accounts.Wallet, attempting to sign the given 270 | // transaction with the given account using passphrase as extra authentication. 271 | func (w *Wallet) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { 272 | return w.SignTx(account, tx, chainID) 273 | } 274 | 275 | // PrivateKey returns the ECDSA private key of the account. 276 | func (w *Wallet) PrivateKey(account accounts.Account) (*ecdsa.PrivateKey, error) { 277 | path, err := ParseDerivationPath(account.URL.Path) 278 | if err != nil { 279 | return nil, err 280 | } 281 | 282 | return w.derivePrivateKey(path) 283 | } 284 | 285 | // PrivateKeyBytes returns the ECDSA private key in bytes format of the account. 286 | func (w *Wallet) PrivateKeyBytes(account accounts.Account) ([]byte, error) { 287 | privateKey, err := w.PrivateKey(account) 288 | if err != nil { 289 | return nil, err 290 | } 291 | 292 | return crypto.FromECDSA(privateKey), nil 293 | } 294 | 295 | // PrivateKeyHex return the ECDSA private key in hex string format of the account. 296 | func (w *Wallet) PrivateKeyHex(account accounts.Account) (string, error) { 297 | privateKeyBytes, err := w.PrivateKeyBytes(account) 298 | if err != nil { 299 | return "", err 300 | } 301 | 302 | return hexutil.Encode(privateKeyBytes)[2:], nil 303 | } 304 | 305 | // PublicKey returns the ECDSA public key of the account. 306 | func (w *Wallet) PublicKey(account accounts.Account) (*ecdsa.PublicKey, error) { 307 | path, err := ParseDerivationPath(account.URL.Path) 308 | if err != nil { 309 | return nil, err 310 | } 311 | 312 | return w.derivePublicKey(path) 313 | } 314 | 315 | // PublicKeyBytes returns the ECDSA public key in bytes format of the account. 316 | func (w *Wallet) PublicKeyBytes(account accounts.Account) ([]byte, error) { 317 | publicKey, err := w.PublicKey(account) 318 | if err != nil { 319 | return nil, err 320 | } 321 | 322 | return crypto.FromECDSAPub(publicKey), nil 323 | } 324 | 325 | // PublicKeyHex return the ECDSA public key in hex string format of the account. 326 | func (w *Wallet) PublicKeyHex(account accounts.Account) (string, error) { 327 | publicKeyBytes, err := w.PublicKeyBytes(account) 328 | if err != nil { 329 | return "", err 330 | } 331 | 332 | return hexutil.Encode(publicKeyBytes)[4:], nil 333 | } 334 | 335 | // Address returns the address of the account. 336 | func (w *Wallet) Address(account accounts.Account) (common.Address, error) { 337 | publicKey, err := w.PublicKey(account) 338 | if err != nil { 339 | return common.Address{}, err 340 | } 341 | 342 | return crypto.PubkeyToAddress(*publicKey), nil 343 | } 344 | 345 | // AddressBytes returns the address in bytes format of the account. 346 | func (w *Wallet) AddressBytes(account accounts.Account) ([]byte, error) { 347 | address, err := w.Address(account) 348 | if err != nil { 349 | return nil, err 350 | } 351 | return address.Bytes(), nil 352 | } 353 | 354 | // AddressHex returns the address in hex string format of the account. 355 | func (w *Wallet) AddressHex(account accounts.Account) (string, error) { 356 | address, err := w.Address(account) 357 | if err != nil { 358 | return "", err 359 | } 360 | return address.Hex(), nil 361 | } 362 | 363 | // Path return the derivation path of the account. 364 | func (w *Wallet) Path(account accounts.Account) (string, error) { 365 | return account.URL.Path, nil 366 | } 367 | 368 | // SignData signs keccak256(data). The mimetype parameter describes the type of data being signed 369 | func (w *Wallet) SignData(account accounts.Account, mimeType string, data []byte) ([]byte, error) { 370 | // Make sure the requested account is contained within 371 | if !w.Contains(account) { 372 | return nil, accounts.ErrUnknownAccount 373 | } 374 | 375 | return w.SignHash(account, crypto.Keccak256(data)) 376 | } 377 | 378 | // SignDataWithPassphrase signs keccak256(data). The mimetype parameter describes the type of data being signed 379 | func (w *Wallet) SignDataWithPassphrase(account accounts.Account, passphrase, mimeType string, data []byte) ([]byte, error) { 380 | // Make sure the requested account is contained within 381 | if !w.Contains(account) { 382 | return nil, accounts.ErrUnknownAccount 383 | } 384 | 385 | return w.SignHashWithPassphrase(account, passphrase, crypto.Keccak256(data)) 386 | } 387 | 388 | // SignText requests the wallet to sign the hash of a given piece of data, prefixed 389 | // the needed details via SignHashWithPassphrase, or by other means (e.g. unlock 390 | // the account in a keystore). 391 | func (w *Wallet) SignText(account accounts.Account, text []byte) ([]byte, error) { 392 | // Make sure the requested account is contained within 393 | if !w.Contains(account) { 394 | return nil, accounts.ErrUnknownAccount 395 | } 396 | 397 | return w.SignHash(account, accounts.TextHash(text)) 398 | } 399 | 400 | // SignTextWithPassphrase implements accounts.Wallet, attempting to sign the 401 | // given text (which is hashed) with the given account using passphrase as extra authentication. 402 | func (w *Wallet) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) { 403 | // Make sure the requested account is contained within 404 | if !w.Contains(account) { 405 | return nil, accounts.ErrUnknownAccount 406 | } 407 | 408 | return w.SignHashWithPassphrase(account, passphrase, accounts.TextHash(text)) 409 | } 410 | 411 | // ParseDerivationPath parses the derivation path in string format into []uint32 412 | func ParseDerivationPath(path string) (accounts.DerivationPath, error) { 413 | return accounts.ParseDerivationPath(path) 414 | } 415 | 416 | // MustParseDerivationPath parses the derivation path in string format into 417 | // []uint32 but will panic if it can't parse it. 418 | func MustParseDerivationPath(path string) accounts.DerivationPath { 419 | parsed, err := accounts.ParseDerivationPath(path) 420 | if err != nil { 421 | panic(err) 422 | } 423 | 424 | return parsed 425 | } 426 | 427 | // NewMnemonic returns a randomly generated BIP-39 mnemonic using 128-256 bits of entropy. 428 | func NewMnemonic(bits int) (string, error) { 429 | entropy, err := bip39.NewEntropy(bits) 430 | if err != nil { 431 | return "", err 432 | } 433 | return bip39.NewMnemonic(entropy) 434 | } 435 | 436 | // NewMnemonicFromEntropy returns a BIP-39 mnemonic from entropy. 437 | func NewMnemonicFromEntropy(entropy []byte) (string, error) { 438 | return bip39.NewMnemonic(entropy) 439 | } 440 | 441 | // NewEntropy returns a randomly generated entropy. 442 | func NewEntropy(bits int) ([]byte, error) { 443 | return bip39.NewEntropy(bits) 444 | } 445 | 446 | // NewSeed returns a randomly generated BIP-39 seed. 447 | func NewSeed() ([]byte, error) { 448 | b := make([]byte, 64) 449 | _, err := rand.Read(b) 450 | return b, err 451 | } 452 | 453 | // NewSeedFromMnemonic returns a BIP-39 seed based on a BIP-39 mnemonic. 454 | func NewSeedFromMnemonic(mnemonic string) ([]byte, error) { 455 | if mnemonic == "" { 456 | return nil, errors.New("mnemonic is required") 457 | } 458 | 459 | return bip39.NewSeedWithErrorChecking(mnemonic, "") 460 | } 461 | 462 | // DerivePrivateKey derives the private key of the derivation path. 463 | func (w *Wallet) derivePrivateKey(path accounts.DerivationPath) (*ecdsa.PrivateKey, error) { 464 | var err error 465 | key := w.masterKey 466 | for _, n := range path { 467 | key, err = key.Child(n) 468 | if err != nil { 469 | return nil, err 470 | } 471 | } 472 | 473 | privateKey, err := key.ECPrivKey() 474 | privateKeyECDSA := privateKey.ToECDSA() 475 | if err != nil { 476 | return nil, err 477 | } 478 | 479 | return privateKeyECDSA, nil 480 | } 481 | 482 | // DerivePublicKey derives the public key of the derivation path. 483 | func (w *Wallet) derivePublicKey(path accounts.DerivationPath) (*ecdsa.PublicKey, error) { 484 | privateKeyECDSA, err := w.derivePrivateKey(path) 485 | if err != nil { 486 | return nil, err 487 | } 488 | 489 | publicKey := privateKeyECDSA.Public() 490 | publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) 491 | if !ok { 492 | return nil, errors.New("failed to get public key") 493 | } 494 | 495 | return publicKeyECDSA, nil 496 | } 497 | 498 | // DeriveAddress derives the account address of the derivation path. 499 | func (w *Wallet) deriveAddress(path accounts.DerivationPath) (common.Address, error) { 500 | publicKeyECDSA, err := w.derivePublicKey(path) 501 | if err != nil { 502 | return common.Address{}, err 503 | } 504 | 505 | address := crypto.PubkeyToAddress(*publicKeyECDSA) 506 | return address, nil 507 | } 508 | 509 | // removeAtIndex removes an account at index. 510 | func removeAtIndex(accts []accounts.Account, index int) []accounts.Account { 511 | return append(accts[:index], accts[index+1:]...) 512 | } 513 | -------------------------------------------------------------------------------- /wallet/hd_test.go: -------------------------------------------------------------------------------- 1 | package wallet 2 | 3 | import ( 4 | "encoding/hex" 5 | "testing" 6 | 7 | "github.com/ethereum/go-ethereum/accounts" 8 | "github.com/stretchr/testify/assert" 9 | "github.com/tyler-smith/go-bip39" 10 | ) 11 | 12 | func TestDeriveAddressFromPathAndSeed(t *testing.T) { 13 | seed := bip39.NewSeed("foo", "") 14 | 15 | encryptSeed, err := AesCBCEncrypt([]byte(seed), []byte("password")) 16 | if err != nil { 17 | t.Fatal(err) 18 | } 19 | encryptSeedInHex := hex.EncodeToString(encryptSeed) 20 | 21 | assert.Equal( 22 | t, 23 | "7704d7c96a90c8978994013fc79c7fc37c889719679a84976a5b7a5b46ec30201c3ee04288d697010fc9cf81e327d8734c8f30dbc1a638fc9e25f29d42653c84d764bd790bbed510e9270d7b3cfa205d", 24 | encryptSeedInHex) 25 | 26 | path := "m/44'/60'/0'/0/1" 27 | wallet, err := NewFromSeed(seed) 28 | if err != nil { 29 | t.Fatal(err) 30 | } 31 | 32 | derivaPath, err := accounts.ParseDerivationPath(path) 33 | if err != nil { 34 | t.Fatal(err) 35 | } 36 | addr, err := wallet.deriveAddress(derivaPath) 37 | if err != nil { 38 | t.Fatal(err) 39 | } 40 | assert.Equal(t, "0x38F32C2473a314d447d681D30e1C0f5D07194371", addr.String()) 41 | } 42 | --------------------------------------------------------------------------------