├── .github
└── workflows
│ └── scala.yml
├── .gitignore
├── .scalafmt.conf
├── CHANGELOG.md
├── CONTRIBUTORS.md
├── LICENSE
├── README.md
├── build.sbt
├── project
├── build.properties
└── plugins.sbt
└── src
├── main
├── resources
│ └── bitcoinGenesisTransaction.json
└── scala
│ ├── Codecs.scala
│ ├── Protocol.scala
│ ├── RPCClient.scala
│ ├── ZeroMQ.scala
│ ├── bitcoin
│ ├── Codecs.scala
│ ├── Instances.scala
│ ├── Protocol.scala
│ └── Syntax.scala
│ ├── ethereum
│ ├── Codecs.scala
│ ├── HexTools.scala
│ ├── Instances.scala
│ ├── Protocol.scala
│ ├── Syntax.scala
│ └── UInt256.scala
│ ├── examples
│ ├── bitcoin
│ │ ├── CatchupFromZero.scala
│ │ ├── GetAllTransactionsForBlock.scala
│ │ ├── GetBlockHash.scala
│ │ └── SubscribeToBlockUpdates.scala
│ ├── ethereum
│ │ ├── CatchupFromZero.scala
│ │ ├── GetEthereumBestBlock.scala
│ │ ├── GetEthereumBlockByHash.scala
│ │ ├── GetEthereumBlockByHeight.scala
│ │ ├── GetEthereumTransactionByHash.scala
│ │ └── GetReceiptByHash.scala
│ └── omni
│ │ └── OmniGetBlockTransactions.scala
│ └── omni
│ ├── Codecs.scala
│ ├── Instances.scala
│ ├── Protocol.scala
│ └── Syntax.scala
└── test
└── scala
├── bitcoin
└── ProtocolSpec.scala
└── ethereum
└── ProtocolSpec.scala
/.github/workflows/scala.yml:
--------------------------------------------------------------------------------
1 | name: Scala CI
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 |
8 | runs-on: ubuntu-latest
9 |
10 | steps:
11 | - uses: actions/checkout@v1
12 | - name: Set up JDK 1.8
13 | uses: actions/setup-java@v1
14 | with:
15 | java-version: 1.8
16 | - name: Run tests
17 | run: sbt test
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | project/target/*
2 | target/**
3 | *.class
4 | *.log
5 |
--------------------------------------------------------------------------------
/.scalafmt.conf:
--------------------------------------------------------------------------------
1 | version = "2.0.1"
2 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # 2.5.2
2 |
3 | * changed Omni schema, propertyid is now Long
4 |
5 | # 2.5.1
6 |
7 | * changed Ethereum syntax got rid of getNextBlockHash (not implemented yet)
8 |
9 | # 2.5.0
10 |
11 | * added Ethereum
12 | * added Ethereum block and transactions codec and syntax
13 |
14 | # 2.4.0
15 |
16 | * added Omni field confirmations
17 | * added Omni field invalidreason
18 |
19 | # 2.3.0
20 |
21 | * removed implicit config
22 | * add optional onRetryError: (hostId, exception) => IO.unit callback
23 |
24 | # 2.2.0
25 |
26 | * handle ConnectException with failover
27 |
28 | # 2.1.0
29 |
30 | * changed env flag for hosts BITCOIN_RPC_HOST -> BITCOIN_RPC_HOSTS
31 |
32 | # 2.0.0
33 |
34 | * added feature to add multiple hosts as fallback (not getNextBlockHash yet)
35 | * interface changed for instantiating Bitcoin etc. provide sequence of fallbacks
36 |
37 | # 1.22
38 |
39 | * added getNextBlockHash for Omni
40 | * added getBlockByHeight for Omni
41 |
42 | # 1.21
43 |
44 | * removed logback.xml from resources
45 |
46 | # 1.20
47 |
48 | * added omni methods
49 | * added new API interface, resources etc.
50 | * added EnvConfig for fast configuration
51 | * added optional parameters to make function
52 | * added various examples for usage
53 | * added more details in README.md
54 |
55 | # 1.7
56 |
57 | * added batching of requests
58 | * added getTransactions method
59 |
60 | # 1.6
61 |
62 | * added logging subsystem, log to DEBUG
63 |
64 | # 1.5
65 |
66 | * added input fields txid, vout, scriptSig for TransactionResponse inputs
67 | * changed coinbase fields to be optional
68 |
69 | # 1.4
70 |
71 | * added version and hash to TransactionResponse
72 |
73 | # 1.3
74 |
75 | * added getBlock by height
76 |
--------------------------------------------------------------------------------
/CONTRIBUTORS.md:
--------------------------------------------------------------------------------
1 | * Cesar Pantoja
2 | * Jendrik Poloczek
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # blockchain-rpc
2 |
3 | Moved to [https://github.com/jpzk/blockchain-rpc](https://github.com/jpzk/blockchain-rpc)
4 |
--------------------------------------------------------------------------------
/build.sbt:
--------------------------------------------------------------------------------
1 | lazy val commonSettings = Seq(
2 | organization := "io.tokenanalyst",
3 | version := "2.5.2",
4 | scalaVersion := "2.13.1",
5 | crossScalaVersions := Seq("2.13.1", "2.12.10"),
6 | organizationHomepage := Some(
7 | url("https://github.com/tokenanalyst/blockchain-rpc")
8 | ),
9 | description := "JSON RPC client for Bitcoin, Bitcoin-based, and Ethereum nodes"
10 | )
11 |
12 | lazy val `blockchain-rpc` = (project in file("."))
13 | .settings(commonSettings: _*)
14 | .settings(
15 | assemblyJarName in assembly := "blockchain-rpc.jar",
16 | publishMavenStyle := false,
17 | publishTo := {
18 | val nexus = "https://oss.sonatype.org/"
19 | if (isSnapshot.value)
20 | Some("snapshots" at nexus + "content/repositories/snapshots")
21 | else
22 | Some("releases" at nexus + "service/local/staging/deploy/maven2")
23 | }
24 | )
25 | .settings(
26 | libraryDependencies ++= Seq(
27 | "commons-codec" % "commons-codec" % "1.13",
28 | "com.typesafe.akka" %% "akka-actor" % "2.6.1"
29 | ) ++ http4s ++ json ++ zmq ++ cats ++ scalaTest
30 | )
31 |
32 | publishMavenStyle := true
33 |
34 | publishArtifact in Test := false
35 |
36 | pomIncludeRepository := { _ =>
37 | false
38 | }
39 |
40 | scmInfo := Some(
41 | ScmInfo(
42 | url("https://github.com/tokenanalyst/blockchain-rpc"),
43 | "scm:git@github.com:tokenanalyst/blockchain-rpc.git"
44 | )
45 | )
46 |
47 | pomExtra :=
48 | https://github.com/tokenanalyst/blockchain-rpc
49 |
50 |
51 | Apache License Version 2.0
52 | http://www.apache.org/licenses/LICENSE-2.0
53 | repo
54 |
55 |
56 |
57 |
58 | jpzk
59 | Jendrik Poloczek
60 | https://www.madewithtea.com
61 |
62 |
63 | CesarPantoja
64 | Cesar Pantoja
65 | https://twitter.com/chpanto
66 |
67 |
68 |
69 | val http4sVersion = "0.21.0-M5"
70 | val circeVersion = "0.12.0-M4"
71 | val scalaTestVersion = "3.1.0"
72 |
73 | lazy val http4s = Seq(
74 | "org.http4s" %% "http4s-dsl" % http4sVersion,
75 | "org.http4s" %% "http4s-blaze-server" % http4sVersion,
76 | "org.http4s" %% "http4s-blaze-client" % http4sVersion
77 | )
78 |
79 | lazy val json = Seq(
80 | "org.http4s" %% "http4s-circe" % http4sVersion,
81 | "io.circe" %% "circe-generic" % circeVersion,
82 | "io.circe" %% "circe-literal" % circeVersion,
83 | "io.circe" %% "circe-parser" % circeVersion
84 | )
85 | lazy val scalaTest = Seq(
86 | "org.scalatest" %% "scalatest" % scalaTestVersion % "test",
87 | )
88 |
89 | lazy val zmq = Seq(
90 | "org.zeromq" % "jeromq" % "0.5.1"
91 | )
92 |
93 | lazy val cats = Seq(
94 | "org.typelevel" %% "cats-effect" % "2.0.0"
95 | )
96 |
--------------------------------------------------------------------------------
/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=1.3.4
2 |
3 |
--------------------------------------------------------------------------------
/project/plugins.sbt:
--------------------------------------------------------------------------------
1 | addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.10")
2 | addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.5.1")
3 | addSbtPlugin("se.marcuslonnberg" % "sbt-docker" % "1.5.0")
4 | addSbtPlugin("com.frugalmechanic" % "fm-sbt-s3-resolver" % "0.19.0")
5 | addSbtPlugin("io.github.davidgregory084" % "sbt-tpolecat" % "0.1.8")
6 | addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.0")
--------------------------------------------------------------------------------
/src/main/resources/bitcoinGenesisTransaction.json:
--------------------------------------------------------------------------------
1 | {
2 | "txid": "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b",
3 | "hash": "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b",
4 | "version": 1,
5 | "locktime": 0,
6 | "size": 204,
7 | "vsize": 204,
8 | "weight": 816,
9 | "time": 1231006505,
10 | "blocktime": 1231006505,
11 | "blockhash": "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f",
12 | "hex": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000",
13 | "vin": [
14 | {
15 | "coinbase": "04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73",
16 | "sequence": 4294967295
17 | }
18 | ],
19 | "vout": [
20 | {
21 | "value": 50.00000000,
22 | "n": 0,
23 | "scriptPubKey": {
24 | "asm": "04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f OP_CHECKSIG",
25 | "hex": "4104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac",
26 | "reqSigs": 1,
27 | "type": "pubkey",
28 | "addresses": [
29 | "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"
30 | ]
31 | }
32 | }
33 | ]
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/scala/Codecs.scala:
--------------------------------------------------------------------------------
1 | package io.tokenanalyst.blockchainrpc
2 |
3 | import io.circe.{Decoder, Encoder, HCursor, Json}
4 |
5 | object Codecs {
6 | implicit def batchResponse[A <: RPCResponse: Decoder] =
7 | new Decoder[BatchResponse[A]] {
8 | def apply(a: HCursor): Decoder.Result[BatchResponse[A]] =
9 | a.as[Seq[A]].map(s => BatchResponse(s))
10 | }
11 |
12 | implicit def deriveCirceDecoder[A <: RPCResponse: Decoder] = new Decoder[A] {
13 | def apply(a: HCursor): Decoder.Result[A] = a.downField("result").as[A]
14 | }
15 |
16 | def requestFields(
17 | method: String,
18 | params: Iterable[Json]
19 | ): List[(String, Json)] = List(
20 | ("jsonrpc", Json.fromString("2.0")),
21 | ("id", Json.fromString("0")),
22 | ("method", Json.fromString(method)),
23 | ("params", Json.fromValues(params))
24 | )
25 |
26 | implicit def batchRequest[A <: RPCRequest](implicit encoder: RPCEncoder[A]) =
27 | new RPCEncoder[BatchRequest[A]] {
28 | final def apply(req: BatchRequest[A]): Json = {
29 | val jsons = req.seq.map { i =>
30 | encoder.apply(i)
31 | }
32 | Json.arr(jsons: _*)
33 | }
34 | }
35 |
36 | implicit def deriveCirceEncoder[A <: RPCRequest](
37 | implicit e: RPCEncoder[A]
38 | ) = new Encoder[A] {
39 | def apply(a: A): Json = e.apply(a)
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/scala/Protocol.scala:
--------------------------------------------------------------------------------
1 | /**
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 | package io.tokenanalyst.blockchainrpc
18 |
19 | import cats.effect.IO
20 | import io.circe.Json
21 |
22 | trait RPCResponse
23 | trait RPCRequest
24 |
25 | case class BatchResponse[A](seq: Seq[A]) extends RPCResponse
26 | case class BatchRequest[A](seq: Seq[A]) extends RPCRequest
27 |
28 | trait RPCEncoder[A] {
29 | def apply(a: A): Json
30 | }
31 |
32 | trait RPCDecoder[A] {
33 | def apply(a: A): Json
34 | }
35 |
36 | case class Config(
37 | hosts: Seq[String],
38 | port: Option[Int],
39 | username: Option[String],
40 | password: Option[String],
41 | zmqPort: Option[Int]
42 | )
43 |
44 | object Config {
45 | val PasswordEnv = "BLOCKCHAIN_RPC_PASSWORD"
46 | val UsernameEnv = "BLOCKCHAIN_RPC_USERNAME"
47 | val HostEnv = "BLOCKCHAIN_RPC_HOSTS"
48 | val PortEnv = "BLOCKCHAIN_RPC_PORT"
49 | val ZMQPortEnv = "BLOCKCHAIN_RPC_ZEROMQ_PORT"
50 |
51 | val fromEnv: Config = {
52 | Seq(HostEnv, PortEnv, UsernameEnv, PasswordEnv, ZMQPortEnv)
53 | .map(sys.env.get(_)) match {
54 | case Seq(None, _, _, _, _) =>
55 | throw new Exception("Pass at least BLOCKCHAIN_RPC_HOSTS.")
56 | case Seq(Some(h), port, user, pass, zmqPort) =>
57 | Config(
58 | h.split(",").toIndexedSeq,
59 | port.map(_.toInt),
60 | user,
61 | pass,
62 | zmqPort.map(_.toInt)
63 | )
64 | }
65 | }
66 | }
67 |
68 | sealed trait Blockchain
69 | case class Ethereum(client: RPCClient) extends Blockchain
70 | case class Bitcoin(client: RPCClient) extends Blockchain
71 | case class Omni(client: RPCClient) extends Blockchain
72 |
73 | object OmniMethods {
74 | trait ListBlockTransactions {
75 | def listBlockTransactions(omni: Omni, height: Long): IO[Seq[String]]
76 | }
77 | }
78 |
79 | object BasicMethods {
80 | trait GetNextBlockHash[A <: Blockchain] {
81 | def getNextBlockHash(a: A): IO[String]
82 | }
83 |
84 | trait GetBlockByHash[A <: Blockchain, B] {
85 | def getBlockByHash(a: A, hash: String): IO[B]
86 | }
87 |
88 | trait GetBlockByHeight[A <: Blockchain, B] {
89 | def getBlockByHeight(a: A, height: Long): IO[B]
90 | }
91 |
92 | trait GetBlockHash[A <: Blockchain] {
93 | def getBlockHash(a: A, height: Long): IO[String]
94 | }
95 |
96 | trait GetBestBlockHash[A <: Blockchain] {
97 | def getBestBlockHash(a: A): IO[String]
98 | }
99 |
100 | trait GetBestBlockHeight[A <: Blockchain] {
101 | def getBestBlockHeight(a: A): IO[Long]
102 | }
103 |
104 | trait GetTransactions[A <: Blockchain, B] {
105 | def getTransactions(a: A, hashes: Seq[String]): IO[B]
106 | }
107 |
108 | trait GetTransaction[A <: Blockchain, B] {
109 | def getTransaction(a: A, hash: String): IO[B]
110 | }
111 |
112 | trait EstimateSmartFee[A <: Blockchain, B] {
113 | def estimateSmartFee(a: A, height: Long): IO[B]
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/src/main/scala/RPCClient.scala:
--------------------------------------------------------------------------------
1 | /**
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 | package io.tokenanalyst.blockchainrpc
18 |
19 | import cats.effect.{ContextShift, IO, Resource}
20 | import io.circe.{Decoder, Encoder, Json}
21 | import org.http4s.circe.CirceEntityDecoder._
22 | import org.http4s.circe.CirceEntityEncoder._
23 | import org.http4s.client.Client
24 | import org.http4s.client.blaze.BlazeClientBuilder
25 | import org.http4s.client.dsl.Http4sClientDsl
26 | import org.http4s.dsl.io._
27 | import org.http4s.headers.{Authorization, _}
28 | import org.http4s.{BasicCredentials, MediaType, Request, Uri}
29 |
30 | import java.net.{ConnectException, SocketTimeoutException}
31 | import scala.concurrent.ExecutionContext
32 | import scala.concurrent.duration._
33 |
34 | object RPCClient {
35 |
36 | def bitcoin(
37 | hosts: Seq[String],
38 | port: Option[Int] = None,
39 | username: Option[String] = None,
40 | password: Option[String] = None,
41 | zmqPort: Option[Int] = None,
42 | onErrorRetry: (Int, Throwable) => IO[Unit] = (_,_) => IO.unit
43 | )(
44 | implicit ec: ExecutionContext,
45 | cs: ContextShift[IO]
46 | ): Resource[IO, Bitcoin] = {
47 | val config = Config(hosts, port, username, password, zmqPort)
48 | for (client <- make(config, onErrorRetry)) yield Bitcoin(client)
49 | }
50 |
51 | def ethereum(
52 | hosts: Seq[String],
53 | port: Option[Int] = None,
54 | username: Option[String] = None,
55 | password: Option[String] = None,
56 | zmqPort: Option[Int] = None,
57 | onErrorRetry: (Int, Throwable) => IO[Unit] = (_,_) => IO.unit
58 | )(
59 | implicit ec: ExecutionContext,
60 | cs: ContextShift[IO]
61 | ): Resource[IO, Ethereum] = {
62 | val config = Config(hosts, port, username, password, zmqPort)
63 | for (client <- make(config, onErrorRetry)) yield Ethereum(client)
64 | }
65 |
66 | def omni(
67 | hosts: Seq[String],
68 | port: Option[Int] = None,
69 | username: Option[String] = None,
70 | password: Option[String] = None,
71 | zmqPort: Option[Int] = None,
72 | onErrorRetry: (Int, Throwable) => IO[Unit] = (_,_) => IO.unit
73 | )(
74 | implicit ec: ExecutionContext,
75 | cs: ContextShift[IO]
76 | ): Resource[IO, Omni] = {
77 | val config = Config(hosts, port, username, password, zmqPort)
78 | for (client <- make(config, onErrorRetry)) yield Omni(client)
79 | }
80 |
81 | def make(config: Config, onErrorRetry: (Int, Throwable) => IO[Unit])(
82 | implicit ec: ExecutionContext,
83 | cs: ContextShift[IO]
84 | ): Resource[IO, RPCClient] = {
85 | for {
86 | client <- BlazeClientBuilder[IO](ec)
87 | .withConnectTimeout(5.seconds)
88 | .withRequestTimeout(2.minutes)
89 | .resource
90 | socket <- ZeroMQ.socket(
91 | config.hosts.head,
92 | config.zmqPort.getOrElse(28332)
93 | )
94 | } yield new RPCClient(client, socket, config, onErrorRetry)
95 | }
96 | }
97 |
98 | class RPCClient (
99 | client: Client[IO],
100 | zmq: ZeroMQ.Socket,
101 | config: Config,
102 | onErrorRetry: (Int, Throwable) => IO[Unit]
103 | ) extends Http4sClientDsl[IO] {
104 |
105 | // is blocking
106 | def nextBlockHash(): IO[String] = zmq.nextBlock()
107 |
108 | def request[A <: RPCRequest: Encoder, B <: RPCResponse: Decoder](
109 | request: A
110 | ): IO[B] = retry(config.hosts) { host =>
111 | for {
112 | req <- post(host, request)
113 | res <- client.expect[B](req)
114 | } yield res
115 | }
116 |
117 | def requestJson[A <: RPCRequest: Encoder](request: A): IO[Json] =
118 | retry(config.hosts) { host =>
119 | for {
120 | req <- post(host, request)
121 | res <- client.expect[Json](req)
122 | } yield res
123 | }
124 |
125 | private def post[A <: RPCRequest: Encoder](
126 | host: String,
127 | request: A
128 | ): IO[Request[IO]] = {
129 | val uri = Uri
130 | .fromString(s"http://${host}:${config.port.getOrElse(8332)}")
131 | .getOrElse(throw new Exception("Could not parse URL"))
132 | (config.username, config.password) match {
133 | case (Some(user), Some(pass)) =>
134 | POST(
135 | request,
136 | uri,
137 | Authorization(BasicCredentials(user, pass)),
138 | Accept(MediaType.application.json)
139 | )
140 | case _ =>
141 | POST(
142 | request,
143 | uri,
144 | Accept(MediaType.application.json)
145 | )
146 | }
147 | }
148 |
149 | def retry[A](fallbacks: Seq[String], current: Int = 0, max: Int = 10)(
150 | f: String => IO[A]
151 | ): IO[A] = {
152 | val hostId = current % fallbacks.size
153 | val handle = (e: Exception) => {
154 | if (current <= max) for {
155 | _ <- onErrorRetry(hostId, e)
156 | r <- retry(fallbacks, current + 1, max)(f)
157 | } yield r
158 | else
159 | IO.raiseError(new Exception(s"Running out of retries for: ${e}"))
160 | }
161 | f(fallbacks(hostId)).handleErrorWith {
162 | case e: org.http4s.client.UnexpectedStatus => handle(e)
163 | case e: ConnectException => handle(e)
164 | case e: SocketTimeoutException => handle(e)
165 | case e => IO.raiseError(e)
166 | }
167 | }
168 | }
169 |
--------------------------------------------------------------------------------
/src/main/scala/ZeroMQ.scala:
--------------------------------------------------------------------------------
1 | package io.tokenanalyst.blockchainrpc
2 |
3 | import java.io.Closeable
4 | import java.nio.ByteBuffer
5 |
6 | import cats.effect.{IO, Resource}
7 | import org.zeromq._
8 |
9 | object ZeroMQ {
10 | case class message(topic: String, body: String, sequence: Int)
11 |
12 | def messageFromZMsg(zMsg: ZMsg) = {
13 | val topic = zMsg.popString()
14 | val body = zMsg.popString()
15 | val seq = ByteBuffer.wrap(zMsg.pop().getData.reverse).getInt
16 | message(topic, body, seq)
17 | }
18 |
19 | class Socket(host: String, port: Int) extends Closeable {
20 | val context = new ZContext()
21 | val socket: ZMQ.Socket = context.createSocket(SocketType.SUB)
22 |
23 | //http://api.zeromq.org/2-1:zmq-setsockopt
24 | socket.setHWM(0)
25 | socket.subscribe("hashblock".map(_.toByte).toArray)
26 | socket.connect(f"tcp://$host:$port")
27 |
28 | def nextBlock(): IO[String] = IO {
29 | val msg = ZMsg.recvMsg(socket)
30 | messageFromZMsg(msg).body
31 | }
32 |
33 | override def close() = {
34 | println("closing...")
35 | context.close()
36 | }
37 | }
38 |
39 | def socket(host: String, port: Int): Resource[IO, Socket] =
40 | Resource.fromAutoCloseable(IO(new Socket(host, port)))
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/scala/bitcoin/Codecs.scala:
--------------------------------------------------------------------------------
1 | /**
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 | package io.tokenanalyst.blockchainrpc.bitcoin
18 |
19 | import io.circe.Json
20 | import io.tokenanalyst.blockchainrpc.Codecs._
21 | import io.tokenanalyst.blockchainrpc.RPCEncoder
22 | import io.tokenanalyst.blockchainrpc.bitcoin.Protocol._
23 |
24 | object Codecs {
25 |
26 | implicit val transactionRequest = new RPCEncoder[TransactionRequest] {
27 | final def apply(a: TransactionRequest): Json =
28 | Json.obj(
29 | requestFields(
30 | "getrawtransaction",
31 | Array(Json.fromString(a.hash), Json.fromInt(1))
32 | ): _*
33 | )
34 | }
35 |
36 | implicit val feeRequest = new RPCEncoder[FeeRequest] {
37 | final def apply(a: FeeRequest): Json =
38 | Json.obj(
39 | requestFields("estimatesmartfee", Array(Json.fromLong(a.block))): _*
40 | )
41 | }
42 |
43 | implicit val blockHashByHeightRequest =
44 | new RPCEncoder[BlockHashByHeightRequest] {
45 | override def apply(a: BlockHashByHeightRequest): Json =
46 | Json.obj(
47 | requestFields("getblockhash", Array(Json.fromLong(a.height))): _*
48 | )
49 | }
50 |
51 | implicit val bestBlockHashRequest = new RPCEncoder[BestBlockHashRequest] {
52 | final def apply(a: BestBlockHashRequest): Json =
53 | Json.obj(requestFields("getbestblockhash", Array[Json]()): _*)
54 | }
55 |
56 | implicit val blockHashRequest = new RPCEncoder[BlockHashRequest] {
57 | final def apply(a: BlockHashRequest): Json =
58 | Json.obj(
59 | requestFields("getblockhash", Array[Json](Json.fromLong(a.height))): _*
60 | )
61 | }
62 |
63 | implicit val blockRequest = new RPCEncoder[BlockRequest] {
64 | final def apply(a: BlockRequest): Json =
65 | Json.obj(requestFields("getblock", Array(Json.fromString(a.hash))): _*)
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/main/scala/bitcoin/Instances.scala:
--------------------------------------------------------------------------------
1 | /**
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 | package io.tokenanalyst.blockchainrpc.bitcoin
18 |
19 | import cats.effect.IO
20 | import io.circe.generic.auto._
21 | import io.tokenanalyst.blockchainrpc.BasicMethods._
22 | import io.tokenanalyst.blockchainrpc.Codecs._
23 | import io.tokenanalyst.blockchainrpc.bitcoin.Protocol._
24 | import io.tokenanalyst.blockchainrpc.bitcoin.Codecs._
25 | import io.tokenanalyst.blockchainrpc.{BatchRequest, BatchResponse, Bitcoin}
26 |
27 | import scala.collection.mutable.ListBuffer
28 |
29 | object Instances {
30 |
31 | implicit val getNextBlockHashInstance =
32 | new GetNextBlockHash[Bitcoin] {
33 | override def getNextBlockHash(a: Bitcoin): IO[String] =
34 | a.client.nextBlockHash()
35 | }
36 |
37 | implicit val getBlockByHashInstance =
38 | new GetBlockByHash[Bitcoin, BlockResponse] {
39 | override def getBlockByHash(
40 | a: Bitcoin,
41 | hash: String
42 | ): IO[BlockResponse] = {
43 | a.client.request[BlockRequest, BlockResponse](BlockRequest(hash))
44 | }
45 | }
46 |
47 | implicit val getBlockHashInstance = new GetBlockHash[Bitcoin] {
48 | override def getBlockHash(a: Bitcoin, height: Long): IO[String] =
49 | for {
50 | json <- a.client
51 | .requestJson[BlockHashRequest](BlockHashRequest(height))
52 | } yield json.asObject.get("result").get.asString.get
53 | }
54 |
55 | implicit val getBlockByHeightInstance =
56 | new GetBlockByHeight[Bitcoin, BlockResponse] {
57 | override def getBlockByHeight(
58 | a: Bitcoin,
59 | height: Long
60 | ): IO[BlockResponse] =
61 | for {
62 | hash <- getBlockHashInstance.getBlockHash(a, height)
63 | data <- getBlockByHashInstance.getBlockByHash(a, hash)
64 | } yield data
65 | }
66 |
67 | implicit val getBestBlockHashInstance =
68 | new GetBestBlockHash[Bitcoin] {
69 | override def getBestBlockHash(a: Bitcoin): IO[String] =
70 | for {
71 | json <- a.client
72 | .requestJson[BestBlockHashRequest](new BestBlockHashRequest)
73 | } yield json.asObject.get("result").get.asString.get
74 | }
75 |
76 | implicit val getBestBlockHeightInstance =
77 | new GetBestBlockHeight[Bitcoin] {
78 | override def getBestBlockHeight(a: Bitcoin): IO[Long] =
79 | for {
80 | hash <- getBestBlockHashInstance.getBestBlockHash(a)
81 | block <- getBlockByHashInstance.getBlockByHash(a, hash)
82 | } yield block.height
83 | }
84 |
85 | implicit val getTransactionsInstance =
86 | new GetTransactions[Bitcoin, BatchResponse[
87 | TransactionResponse
88 | ]] {
89 | override def getTransactions(
90 | a: Bitcoin,
91 | hashes: Seq[String]
92 | ): IO[BatchResponse[TransactionResponse]] = {
93 | val list = ListBuffer(hashes: _*)
94 | val genesisTransactionIndex =
95 | list.indexOf(Transactions.GenesisTransactionHash)
96 |
97 | if (genesisTransactionIndex >= 0) {
98 | list.remove(genesisTransactionIndex)
99 | }
100 |
101 | val result =
102 | a.client.request[BatchRequest[TransactionRequest], BatchResponse[
103 | TransactionResponse
104 | ]](
105 | BatchRequest[TransactionRequest](list.map(TransactionRequest.apply).toSeq)
106 | )
107 |
108 | if (genesisTransactionIndex >= 0) {
109 | for {
110 | batcResponse <- result
111 | listResult <- IO(ListBuffer(batcResponse.seq: _*))
112 | _ <- IO(
113 | listResult
114 | .insert(
115 | genesisTransactionIndex,
116 | Transactions.GenesisTransaction
117 | )
118 | )
119 | } yield BatchResponse(listResult.toSeq)
120 | } else {
121 | result
122 | }
123 | }
124 | }
125 |
126 | implicit val getTransactionInstance =
127 | new GetTransaction[Bitcoin, TransactionResponse] {
128 | override def getTransaction(
129 | a: Bitcoin,
130 | hash: String
131 | ): IO[TransactionResponse] =
132 | if (hash != Transactions.GenesisTransactionHash) {
133 | a.client.request[TransactionRequest, TransactionResponse](
134 | TransactionRequest(hash)
135 | )
136 | } else {
137 | IO(Transactions.GenesisTransaction)
138 | }
139 | }
140 |
141 | implicit val estimateSmartFeeInstance =
142 | new EstimateSmartFee[Bitcoin, FeeResponse] {
143 | override def estimateSmartFee(a: Bitcoin, height: Long): IO[FeeResponse] =
144 | a.client.request[FeeRequest, FeeResponse](FeeRequest(height))
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/src/main/scala/bitcoin/Protocol.scala:
--------------------------------------------------------------------------------
1 | /**
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 | package io.tokenanalyst.blockchainrpc.bitcoin
18 |
19 | import io.tokenanalyst.blockchainrpc.{RPCRequest, RPCResponse}
20 | import scala.io.Source
21 |
22 | object Protocol {
23 | case class FeeResponse(feerate: Double, blocks: Int) extends RPCResponse
24 | case class BlockHashResponse(hash: String) extends RPCResponse
25 | case class BlockResponse(
26 | height: Long,
27 | hash: String,
28 | previousblockhash: Option[String],
29 | nonce: Long,
30 | strippedsize: Long,
31 | merkleroot: String,
32 | version: Int,
33 | weight: Int,
34 | difficulty: Double,
35 | chainwork: String,
36 | bits: String,
37 | size: Long,
38 | mediantime: Long,
39 | time: Long,
40 | nTx: Int,
41 | tx: List[String]
42 | ) extends RPCResponse
43 |
44 | case class TransactionResponseVin(
45 | txid: Option[String],
46 | vout: Option[Int],
47 | scriptSig: Option[TransactionResponseScriptSig],
48 | coinbase: Option[String],
49 | sequence: Long
50 | )
51 |
52 | case class TransactionResponseScriptSig(asm: String, hex: String)
53 |
54 | case class TransactionResponseScript(
55 | asm: String,
56 | hex: String,
57 | reqSigs: Option[Int],
58 | `type`: String,
59 | addresses: Option[List[String]]
60 | )
61 |
62 | case class TransactionResponseVout(
63 | value: Double,
64 | n: Int,
65 | scriptPubKey: TransactionResponseScript
66 | )
67 |
68 | case class TransactionResponse(
69 | confirmations: Option[Int],
70 | blockhash: String,
71 | blocktime: Long,
72 | hash: String,
73 | hex: String,
74 | txid: String,
75 | time: Long,
76 | vsize: Int,
77 | size: Int,
78 | weight: Int,
79 | version: Int,
80 | vin: List[TransactionResponseVin],
81 | vout: List[TransactionResponseVout],
82 | locktime: Long
83 | ) extends RPCResponse
84 |
85 | case class FeeRequest(block: Long) extends RPCRequest
86 | case class BlockRequest(hash: String) extends RPCRequest
87 | case class BlockHashRequest(height: Long) extends RPCRequest
88 | case class TransactionRequest(hash: String) extends RPCRequest
89 | case class BestBlockHashRequest() extends RPCRequest
90 | case class BlockHashByHeightRequest(height: Long) extends RPCRequest
91 | }
92 |
93 | object Transactions {
94 | import io.circe.generic.auto._
95 | import io.circe.parser._
96 | import Protocol._
97 |
98 | lazy val GenesisTransactionHash =
99 | "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"
100 |
101 | lazy val GenesisTransaction =
102 | parse(Source.fromResource("bitcoinGenesisTransaction.json").mkString)
103 | .flatMap { json =>
104 | json.as[TransactionResponse]
105 | }
106 | .getOrElse(throw new Exception("Could not parse genesis"))
107 | }
108 |
--------------------------------------------------------------------------------
/src/main/scala/bitcoin/Syntax.scala:
--------------------------------------------------------------------------------
1 | /**
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 | package io.tokenanalyst.blockchainrpc.bitcoin
18 |
19 | import io.tokenanalyst.blockchainrpc.BasicMethods._
20 | import io.tokenanalyst.blockchainrpc.bitcoin.Instances._
21 | import io.tokenanalyst.blockchainrpc.bitcoin.Protocol._
22 | import io.tokenanalyst.blockchainrpc.{BatchResponse, Bitcoin}
23 |
24 | object Syntax {
25 | implicit class BitcoinOps(b: Bitcoin) {
26 |
27 | def getNextBlockHash() =
28 | implicitly[GetNextBlockHash[Bitcoin]].getNextBlockHash(b)
29 |
30 | def getBlockByHash(hash: String) =
31 | implicitly[GetBlockByHash[Bitcoin, BlockResponse]].getBlockByHash(b, hash)
32 |
33 | def getBlockByHeight(height: Long) =
34 | implicitly[GetBlockByHeight[Bitcoin, BlockResponse]]
35 | .getBlockByHeight(b, height)
36 |
37 | def getBlockHash(height: Long) =
38 | implicitly[GetBlockHash[Bitcoin]].getBlockHash(b, height)
39 |
40 | def getBestBlockHash() =
41 | implicitly[GetBestBlockHash[Bitcoin]].getBestBlockHash(b)
42 |
43 | def getBestBlockHeight() =
44 | implicitly[GetBestBlockHeight[Bitcoin]].getBestBlockHeight(b)
45 |
46 | def getTransactions(hashes: Seq[String]) =
47 | implicitly[GetTransactions[Bitcoin, BatchResponse[TransactionResponse]]]
48 | .getTransactions(b, hashes)
49 |
50 | def getTransaction(hash: String) =
51 | implicitly[GetTransaction[Bitcoin, TransactionResponse]]
52 | .getTransaction(b, hash)
53 |
54 | def estimateSmartFee(height: Long) =
55 | implicitly[EstimateSmartFee[Bitcoin, FeeResponse]]
56 | .estimateSmartFee(b, height)
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/main/scala/ethereum/Codecs.scala:
--------------------------------------------------------------------------------
1 | /**
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 | package io.tokenanalyst.blockchainrpc.ethereum
18 |
19 | import io.circe.Json
20 | import io.tokenanalyst.blockchainrpc.Codecs._
21 | import io.tokenanalyst.blockchainrpc.RPCEncoder
22 | import io.tokenanalyst.blockchainrpc.ethereum.Protocol._
23 |
24 | object Codecs {
25 |
26 | implicit val receiptRequest = new RPCEncoder[ReceiptRequest] {
27 | final def apply(a: ReceiptRequest): Json = Json.obj(
28 | requestFields(
29 | "eth_getTransactionReceipt",
30 | Array(Json.fromString(a.hash))
31 | ): _*
32 | )
33 | }
34 |
35 | implicit val transactionRequest = new RPCEncoder[TransactionRequest] {
36 | final def apply(a: TransactionRequest): Json = Json.obj(
37 | requestFields(
38 | "eth_getTransactionByHash",
39 | Array(Json.fromString(a.hash))
40 | ): _*
41 | )
42 | }
43 |
44 | implicit val bestBlockHeightRequest = new RPCEncoder[BestBlockHeightRequest] {
45 | final def apply(a: BestBlockHeightRequest): Json =
46 | Json.obj(requestFields("eth_blockNumber", Array[Json]()): _*)
47 | }
48 |
49 | implicit val blockByHeightRequest = new RPCEncoder[BlockByHeightRequest] {
50 | final def apply(a: BlockByHeightRequest): Json =
51 | Json.obj(
52 | requestFields(
53 | "eth_getBlockByNumber",
54 | Array[Json](
55 | Json.fromString(
56 | HexTools.toHexString(a.height)
57 | ),
58 | Json.fromBoolean(a.withTransactions)
59 | )
60 | ): _*
61 | )
62 | }
63 |
64 | implicit val blockByHashRequest = new RPCEncoder[BlockByHashRequest] {
65 | final def apply(a: BlockByHashRequest): Json =
66 | Json.obj(
67 | requestFields(
68 | "eth_getBlockByHash",
69 | Array(Json.fromString(a.hash), Json.fromBoolean(a.withTransactions))
70 | ): _*
71 | )
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/main/scala/ethereum/HexTools.scala:
--------------------------------------------------------------------------------
1 | package io.tokenanalyst.blockchainrpc.ethereum
2 |
3 | import org.apache.commons.codec.binary.Hex
4 |
5 | object HexTools {
6 |
7 | def parseQuantity(in: String): BigInt = {
8 | val cleaned = in.replaceFirst("^0x", in.length % 2 match {
9 | case 0 => ""
10 | case 1 => "0"
11 | })
12 | UInt256(Hex.decodeHex(cleaned)).toBigInt
13 | }
14 |
15 | def parseData(in: String): Array[Byte] = {
16 | Hex.decodeHex(in.replaceFirst("^0x", ""))
17 | }
18 |
19 | def toHexString(in: Long) = UInt256(in).toHexString
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/scala/ethereum/Instances.scala:
--------------------------------------------------------------------------------
1 | /**
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 | package io.tokenanalyst.blockchainrpc.ethereum
18 |
19 | import cats.effect.IO
20 | import io.circe.generic.auto._
21 | import io.tokenanalyst.blockchainrpc.BasicMethods._
22 | import io.tokenanalyst.blockchainrpc.Codecs._
23 | import io.tokenanalyst.blockchainrpc.ethereum.Codecs._
24 | import io.tokenanalyst.blockchainrpc.ethereum.Methods._
25 | import io.tokenanalyst.blockchainrpc.ethereum.Protocol._
26 | import io.tokenanalyst.blockchainrpc.{BatchRequest, BatchResponse, Ethereum}
27 |
28 | object Instances {
29 |
30 | implicit val getReceiptInstance =
31 | new GetReceipt[Ethereum, ReceiptResponse] {
32 | override def getReceipt(
33 | a: Ethereum,
34 | hash: String
35 | ): IO[ReceiptResponse] = {
36 | a.client.request[ReceiptRequest, ReceiptResponse](
37 | ReceiptRequest(hash)
38 | )
39 | }
40 | }
41 |
42 | implicit val getReceiptsInstance =
43 | new GetReceipts[Ethereum, BatchResponse[ReceiptResponse]] {
44 | override def getReceipts(
45 | a: Ethereum,
46 | hashes: Seq[String]
47 | ): IO[BatchResponse[ReceiptResponse]] = {
48 | a.client.request[
49 | BatchRequest[ReceiptRequest],
50 | BatchResponse[ReceiptResponse]
51 | ](
52 | BatchRequest[ReceiptRequest](hashes.map(ReceiptRequest.apply))
53 | )
54 | }
55 | }
56 |
57 | implicit val getBlockWithTransactionsByHashInstance =
58 | new GetBlockByHash[Ethereum, BlockWithTransactionsResponse] {
59 | override def getBlockByHash(
60 | a: Ethereum,
61 | hash: String
62 | ): IO[BlockWithTransactionsResponse] = {
63 | a.client.request[BlockByHashRequest, BlockWithTransactionsResponse](
64 | BlockByHashRequest(hash, true)
65 | )
66 | }
67 | }
68 |
69 | implicit val getBlockByHashInstance =
70 | new GetBlockByHash[Ethereum, BlockResponse] {
71 | override def getBlockByHash(
72 | a: Ethereum,
73 | hash: String
74 | ): IO[BlockResponse] = {
75 | a.client.request[BlockByHashRequest, BlockResponse](
76 | BlockByHashRequest(hash, false)
77 | )
78 | }
79 | }
80 |
81 | implicit val getBlockWithTransactionsByHeightInstance =
82 | new GetBlockByHeight[Ethereum, BlockWithTransactionsResponse] {
83 | override def getBlockByHeight(
84 | a: Ethereum,
85 | height: Long
86 | ): IO[BlockWithTransactionsResponse] =
87 | a.client.request[BlockByHeightRequest, BlockWithTransactionsResponse](
88 | BlockByHeightRequest(height, true)
89 | )
90 | }
91 |
92 | implicit val getBlockByHeightInstance =
93 | new GetBlockByHeight[Ethereum, BlockResponse] {
94 | override def getBlockByHeight(
95 | a: Ethereum,
96 | height: Long
97 | ): IO[BlockResponse] =
98 | a.client.request[BlockByHeightRequest, BlockResponse](
99 | BlockByHeightRequest(height, false)
100 | )
101 | }
102 |
103 | implicit val getBestBlockHeightInstance =
104 | new GetBestBlockHeight[Ethereum] {
105 | override def getBestBlockHeight(a: Ethereum): IO[Long] =
106 | for {
107 | json <- a.client
108 | .requestJson[BestBlockHeightRequest](new BestBlockHeightRequest)
109 | } yield HexTools
110 | .parseQuantity(json.asObject.get("result").get.asString.get)
111 | .toLong
112 | }
113 |
114 | implicit val getTransactionsInstance =
115 | new GetTransactions[Ethereum, BatchResponse[
116 | TransactionResponse
117 | ]] {
118 | override def getTransactions(
119 | a: Ethereum,
120 | hashes: Seq[String]
121 | ): IO[BatchResponse[TransactionResponse]] =
122 | a.client.request[
123 | BatchRequest[TransactionRequest],
124 | BatchResponse[TransactionResponse]
125 | ](
126 | BatchRequest[TransactionRequest](hashes.map(TransactionRequest.apply))
127 | )
128 | }
129 |
130 | implicit val getTransactionInstance =
131 | new GetTransaction[Ethereum, TransactionResponse] {
132 | override def getTransaction(
133 | a: Ethereum,
134 | hash: String
135 | ): IO[TransactionResponse] =
136 | a.client.request[TransactionRequest, TransactionResponse](
137 | TransactionRequest(hash)
138 | )
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/src/main/scala/ethereum/Protocol.scala:
--------------------------------------------------------------------------------
1 | /**
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 | package io.tokenanalyst.blockchainrpc.ethereum
18 |
19 | import cats.effect.IO
20 | import io.tokenanalyst.blockchainrpc.{Ethereum, RPCRequest, RPCResponse}
21 |
22 | object Methods {
23 |
24 | trait GetBlockWithTransactionsByHash[A <: Ethereum, B] {
25 | def getBlockWithTransactionsByHash(a: A, hash: String): IO[B]
26 | }
27 |
28 | trait GetBlockWithTransactionsByHeight[A <: Ethereum, B] {
29 | def getBlockWithTransactionsByHeight(a: A, height: Long): IO[B]
30 | }
31 |
32 | trait GetReceipt[A <: Ethereum, B] {
33 | def getReceipt(a: A, hash: String): IO[B]
34 | }
35 |
36 | trait GetReceipts[A <: Ethereum, B] {
37 | def getReceipts(a: A, hashes: Seq[String]): IO[B]
38 | }
39 | }
40 |
41 | object Protocol {
42 |
43 | type BlockWithTransactionsResponse =
44 | GenericBlockResponse[TransactionResponse]
45 |
46 | type BlockResponse = GenericBlockResponse[String]
47 |
48 | case class GenericBlockResponse[A](
49 | author: String,
50 | difficulty: String,
51 | extraData: String,
52 | gasLimit: String,
53 | gasUsed: String,
54 | hash: String,
55 | logsBloom: String,
56 | miner: String,
57 | mixHash: String,
58 | nonce: String,
59 | number: String,
60 | parentHash: String,
61 | receiptsRoot: String,
62 | sealFields: List[String],
63 | sha3Uncles: String,
64 | size: String,
65 | stateRoot: String,
66 | timestamp: String,
67 | totalDifficulty: String,
68 | transactions: List[A],
69 | transactionsRoot: String,
70 | uncles: List[String]
71 | ) extends RPCResponse
72 |
73 | case class TransactionResponse(
74 | blockHash: String,
75 | blockNumber: String,
76 | chainId: Option[String],
77 | from: String,
78 | gas: String,
79 | gasPrice: String,
80 | hash: String,
81 | input: String,
82 | nonce: String,
83 | publicKey: String,
84 | r: String,
85 | raw: String,
86 | s: String,
87 | v: String,
88 | standardV: String,
89 | to: Option[String],
90 | transactionIndex: String,
91 | value: String,
92 | condition: Option[String],
93 | creates: Option[String]
94 | ) extends RPCResponse
95 |
96 | case class ReceiptResponse(
97 | blockHash: String,
98 | blockNumber: String,
99 | contractAddress: Option[String],
100 | from: Option[String],
101 | to: Option[String],
102 | cumulativeGasUsed: String,
103 | gasUsed: Option[String],
104 | logs: List[LogResponse],
105 | logsBloom: String,
106 | status: Option[String],
107 | transactionHash: String,
108 | transactionIndex: String
109 | ) extends RPCResponse
110 |
111 | case class LogResponse(
112 | removed: Boolean,
113 | logIndex: Option[String],
114 | transactionIndex: Option[String],
115 | transactionHash: Option[String],
116 | blockHash: Option[String],
117 | blockNumber: Option[String],
118 | address: String,
119 | data: String,
120 | topics: List[String],
121 | transactionLogIndex: Option[String],
122 | `type`: String
123 | )
124 |
125 | case class BlockByHashRequest(hash: String, withTransactions: Boolean)
126 | extends RPCRequest
127 | case class BlockByHeightRequest(height: Long, withTransactions: Boolean)
128 | extends RPCRequest
129 | case class ReceiptRequest(hash: String) extends RPCRequest
130 | case class TransactionRequest(hash: String) extends RPCRequest
131 | case class BestBlockHeightRequest() extends RPCRequest
132 | }
133 |
--------------------------------------------------------------------------------
/src/main/scala/ethereum/Syntax.scala:
--------------------------------------------------------------------------------
1 | /**
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 | package io.tokenanalyst.blockchainrpc.ethereum
18 |
19 | import io.tokenanalyst.blockchainrpc.BasicMethods._
20 | import io.tokenanalyst.blockchainrpc.{BatchResponse, Ethereum}
21 | import io.tokenanalyst.blockchainrpc.ethereum.Instances._
22 | import io.tokenanalyst.blockchainrpc.ethereum.Methods._
23 | import io.tokenanalyst.blockchainrpc.ethereum.Protocol.{BlockResponse, _}
24 |
25 | object Syntax {
26 | implicit class EthereumOps(b: Ethereum) {
27 |
28 | def getReceiptByHash(hash: String) =
29 | implicitly[GetReceipt[Ethereum, ReceiptResponse]].getReceipt(b, hash)
30 |
31 | def getReceiptsByHash(hashes: Seq[String]) =
32 | implicitly[GetReceipts[Ethereum, BatchResponse[ReceiptResponse]]]
33 | .getReceipts(b, hashes)
34 |
35 | def getBlockByHeight(height: Long) =
36 | implicitly[GetBlockByHeight[Ethereum, BlockResponse]]
37 | .getBlockByHeight(b, height)
38 |
39 | def getBlockByHash(hash: String) =
40 | implicitly[GetBlockByHash[Ethereum, BlockResponse]]
41 | .getBlockByHash(b, hash)
42 |
43 | def getBestBlockHeight() =
44 | implicitly[GetBestBlockHeight[Ethereum]].getBestBlockHeight(b)
45 |
46 | def getTransaction(hash: String) =
47 | implicitly[GetTransaction[Ethereum, TransactionResponse]]
48 | .getTransaction(b, hash)
49 |
50 | def getTransactions(hashes: Seq[String]) =
51 | implicitly[GetTransactions[Ethereum, BatchResponse[TransactionResponse]]]
52 | .getTransactions(b, hashes)
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/main/scala/ethereum/UInt256.scala:
--------------------------------------------------------------------------------
1 | package io.tokenanalyst.blockchainrpc.ethereum
2 |
3 | import akka.util.ByteString
4 |
5 | // scalastyle:off number.of.methods
6 | object UInt256 {
7 |
8 |
9 | /** Size of UInt256 byte representation */
10 | val Size: Int = 32
11 |
12 | private val Modulus: BigInt = BigInt(2).pow(256)
13 |
14 | val MaxValue: UInt256 = new UInt256(Modulus - 1)
15 |
16 | val Zero: UInt256 = new UInt256(0)
17 |
18 | val One: UInt256 = new UInt256(1)
19 |
20 | val Two: UInt256 = new UInt256(2)
21 |
22 | def apply(bytes: ByteString): UInt256 = {
23 | require(bytes.length <= Size, s"Input byte array cannot be longer than $Size: ${bytes.length}")
24 | UInt256(bytes.foldLeft(BigInt(0)){(n, b) => (n << 8) + (b & 0xff)})
25 | }
26 |
27 | def apply(array: Array[Byte]): UInt256 =
28 | UInt256(ByteString(array))
29 |
30 | def apply(n: BigInt): UInt256 =
31 | new UInt256(boundBigInt(n))
32 |
33 | def apply(b: Boolean): UInt256 =
34 | if (b) One else Zero
35 |
36 | def apply(n: Long): UInt256 =
37 | apply(BigInt(n))
38 |
39 | implicit class BigIntAsUInt256(val bigInt: BigInt) extends AnyVal {
40 | def toUInt256: UInt256 = UInt256(bigInt)
41 | }
42 |
43 | implicit def uint256ToBigInt(uint: UInt256): BigInt = uint.toBigInt
44 |
45 | implicit def byte2UInt256(b: Byte): UInt256 = UInt256(b.longValue())
46 |
47 | implicit def int2UInt256(i: Int): UInt256 = UInt256(i.longValue())
48 |
49 | implicit def long2UInt256(l: Long): UInt256 = UInt256(l)
50 |
51 | implicit def bool2UInt256(b: Boolean): UInt256 = UInt256(b)
52 |
53 |
54 |
55 | private val Zeros: ByteString = ByteString(Array.fill[Byte](Size)(0))
56 |
57 | private def boundBigInt(n: BigInt): BigInt = (n % Modulus + Modulus) % Modulus
58 |
59 | private val MaxSignedValue: BigInt = BigInt(2).pow(Size * 8 - 1) - 1
60 | }
61 |
62 | /** Represents 256 bit unsigned integers with standard arithmetic, byte-wise operation and EVM-specific extensions */
63 | class UInt256 private (private val n: BigInt) extends Ordered[UInt256] {
64 |
65 | import UInt256._
66 | require(n >= 0 && n < Modulus, s"Invalid UInt256 value: $n")
67 |
68 |
69 | // byte-wise operations
70 |
71 | /** Converts a BigInt to a ByteString.
72 | * Output ByteString is padded with 0's from the left side up to UInt256.Size bytes.
73 | */
74 | lazy val bytes: ByteString = {
75 | val bs: ByteString = ByteString(n.toByteArray).takeRight(Size)
76 | val padLength: Int = Size - bs.length
77 | if (padLength > 0)
78 | Zeros.take(padLength) ++ bs
79 | else
80 | bs
81 | }
82 |
83 | /** Used for gas calculation for EXP opcode. See YP Appendix H.1 (220)
84 | * For n > 0: (n.bitLength - 1) / 8 + 1 == 1 + floor(log_256(n))
85 | *
86 | * @return Size in bytes excluding the leading 0 bytes
87 | */
88 | def byteSize: Int = if (isZero) 0 else (n.bitLength - 1) / 8 + 1
89 |
90 | def getByte(that: UInt256): UInt256 =
91 | if (that.n > 31) Zero else UInt256(bytes(that.n.toInt).toLong & 0xff)
92 |
93 | // standard arithmetic (note the use of new instead of apply where result is guaranteed to be within bounds)
94 | def &(that: UInt256): UInt256 = new UInt256(this.n & that.n)
95 |
96 | def |(that: UInt256): UInt256 = new UInt256(this.n | that.n)
97 |
98 | def ^(that: UInt256): UInt256 = new UInt256(this.n ^ that.n)
99 |
100 | def unary_- : UInt256 = UInt256(-n)
101 |
102 | def unary_~ : UInt256 = UInt256(~n)
103 |
104 | def +(that: UInt256): UInt256 = UInt256(this.n + that.n)
105 |
106 | def -(that: UInt256): UInt256 = UInt256(this.n - that.n)
107 |
108 | def *(that: UInt256): UInt256 = UInt256(this.n * that.n)
109 |
110 | def /(that: UInt256): UInt256 = new UInt256(this.n / that.n)
111 |
112 | def **(that: UInt256): UInt256 = UInt256(this.n.modPow(that.n, Modulus))
113 |
114 | def compare(that: UInt256): Int = this.n.compare(that.n)
115 |
116 | def min(that: UInt256): UInt256 = if (compare(that) < 0) this else that
117 |
118 | def max(that: UInt256): UInt256 = if (compare(that) > 0) this else that
119 |
120 | def isZero: Boolean = n == 0
121 |
122 |
123 |
124 | // EVM-specific arithmetic
125 | private lazy val signedN: BigInt = if (n > MaxSignedValue) n - Modulus else n
126 |
127 | private def zeroCheck(x: UInt256)(result: => BigInt): UInt256 =
128 | if (x.isZero) Zero else UInt256(result)
129 |
130 | def div(that: UInt256): UInt256 = zeroCheck(that) { new UInt256(this.n / that.n) }
131 |
132 | def sdiv(that: UInt256): UInt256 = zeroCheck(that) { UInt256(this.signedN / that.signedN) }
133 |
134 | def mod(that: UInt256): UInt256 = zeroCheck(that) { UInt256(this.n mod that.n) }
135 |
136 | def smod(that: UInt256): UInt256 = zeroCheck(that) { UInt256(this.signedN % that.signedN.abs) }
137 |
138 | def addmod(that: UInt256, modulus: UInt256): UInt256 = zeroCheck(modulus) { new UInt256((this.n + that.n) % modulus.n) }
139 |
140 | def mulmod(that: UInt256, modulus: UInt256): UInt256 = zeroCheck(modulus) { new UInt256((this.n * that.n) mod modulus.n) }
141 |
142 | def slt(that: UInt256): Boolean = this.signedN < that.signedN
143 |
144 | def sgt(that: UInt256): Boolean = this.signedN > that.signedN
145 |
146 | def signExtend(that: UInt256): UInt256 = {
147 | if (that.n < 0 || that.n > 31) {
148 | this
149 | } else {
150 | val idx = that.n.toByte
151 | val negative = n.testBit(idx * 8 + 7)
152 | val mask = (BigInt(1) << ((idx + 1) * 8)) - 1
153 | val newN = if (negative) n | (MaxValue ^ mask) else n & mask
154 | new UInt256(newN)
155 | }
156 | }
157 |
158 |
159 |
160 | //standard methods
161 | override def equals(that: Any): Boolean = {
162 | that match {
163 | case that: UInt256 => this.n.equals(that.n)
164 | case other => other == n
165 | }
166 | }
167 |
168 | override def hashCode: Int = n.hashCode()
169 |
170 | override def toString: String = toSignedDecString
171 |
172 | def toDecString: String =
173 | n.toString
174 |
175 | def toSignedDecString: String =
176 | signedN.toString
177 |
178 | def toHexString: String = {
179 | val hex = f"$n%x"
180 | //add zero if odd number of digits
181 | val extraZero = if (hex.length % 2 == 0) "" else "0"
182 | s"0x$extraZero$hex"
183 | }
184 |
185 | // conversions
186 | def toBigInt: BigInt = n
187 |
188 | /**
189 | * @return an Int with MSB=0, thus a value in range [0, Int.MaxValue]
190 | */
191 | def toInt: Int = n.intValue & Int.MaxValue
192 |
193 | /**
194 | * @return a Long with MSB=0, thus a value in range [0, Long.MaxValue]
195 | */
196 | def toLong: Long = n.longValue & Long.MaxValue
197 | }
198 |
--------------------------------------------------------------------------------
/src/main/scala/examples/bitcoin/CatchupFromZero.scala:
--------------------------------------------------------------------------------
1 | /**
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 | package io.tokenanalyst.blockchainrpc.examples.bitcoin
18 |
19 | import cats.effect.{ExitCode, IO, IOApp}
20 | import scala.concurrent.ExecutionContext.global
21 |
22 | import io.tokenanalyst.blockchainrpc.Bitcoin
23 | import io.tokenanalyst.blockchainrpc.{RPCClient, Config}
24 | import io.tokenanalyst.blockchainrpc.bitcoin.Syntax._
25 |
26 | object CatchupFromZero extends IOApp {
27 |
28 | def loop(rpc: Bitcoin, current: Long = 0L, until: Long = 10L): IO[Unit] =
29 | for {
30 | block <- rpc.getBlockByHeight(current)
31 | _ <- IO { println(block) }
32 | l <- if (current + 1 < until) loop(rpc, current + 1, until) else IO.unit
33 | } yield l
34 |
35 | def run(args: List[String]): IO[ExitCode] = {
36 | implicit val ec = global
37 | implicit val config = Config.fromEnv
38 | RPCClient
39 | .bitcoin(config.hosts, config.port, config.username, config.password)
40 | .use { rpc =>
41 | for {
42 | _ <- loop(rpc)
43 | } yield ExitCode(0)
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/main/scala/examples/bitcoin/GetAllTransactionsForBlock.scala:
--------------------------------------------------------------------------------
1 | /**
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 | package io.tokenanalyst.blockchainrpc.examples.bitcoin
18 |
19 | import cats.effect.{ExitCode, IO, IOApp}
20 | import scala.concurrent.ExecutionContext.global
21 |
22 | import io.tokenanalyst.blockchainrpc.{RPCClient, Config}
23 | import io.tokenanalyst.blockchainrpc.bitcoin.Syntax._
24 |
25 | object GetAllTransactionsFromBlock extends IOApp {
26 |
27 | def run(args: List[String]): IO[ExitCode] = {
28 | implicit val ec = global
29 | implicit val config = Config.fromEnv
30 | RPCClient
31 | .bitcoin(config.hosts, config.port, config.username, config.password)
32 | .use { rpc =>
33 | for {
34 | block <- rpc.getBlockByHash(
35 | "0000000000000000000759de6ab39c2d8fb01e4481ba581761ddc1d50a57358d"
36 | )
37 | txs <- rpc.getTransactions(block.tx)
38 | _ <- IO(println(s"Fetched ${txs.seq.size} transactions"))
39 | } yield ExitCode(0)
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/scala/examples/bitcoin/GetBlockHash.scala:
--------------------------------------------------------------------------------
1 | /**
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 | package io.tokenanalyst.blockchainrpc.examples.bitcoin
18 |
19 | import cats.effect.{ExitCode, IO, IOApp}
20 | import scala.concurrent.ExecutionContext.global
21 |
22 | import io.tokenanalyst.blockchainrpc.{RPCClient, Config}
23 | import io.tokenanalyst.blockchainrpc.bitcoin.Syntax._
24 |
25 | object GetBlockHash extends IOApp {
26 | def run(args: List[String]): IO[ExitCode] = {
27 | implicit val ec = global
28 | implicit val config = Config.fromEnv
29 | RPCClient
30 | .bitcoin(
31 | config.hosts,
32 | config.port,
33 | config.username,
34 | config.password,
35 | onErrorRetry = { (_, e: Throwable) => IO(println(e)) }
36 | )
37 | .use { bitcoin =>
38 | for {
39 | block <- bitcoin.getBlockByHash(
40 | "0000000000000000000759de6ab39c2d8fb01e4481ba581761ddc1d50a57358d"
41 | )
42 | _ <- IO { println(block) }
43 | } yield ExitCode(0)
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/main/scala/examples/bitcoin/SubscribeToBlockUpdates.scala:
--------------------------------------------------------------------------------
1 | /**
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 | package io.tokenanalyst.blockchainrpc.examples.bitcoin
18 |
19 | import cats.effect.{ExitCode, IO, IOApp}
20 | import scala.concurrent.ExecutionContext.global
21 |
22 | import io.tokenanalyst.blockchainrpc.RPCClient
23 | import io.tokenanalyst.blockchainrpc.bitcoin.Syntax._
24 |
25 | object SubscribeToBlockUpdates extends IOApp {
26 | def run(args: List[String]): IO[ExitCode] = {
27 | implicit val ec = global
28 | RPCClient
29 | .bitcoin(
30 | hosts = Seq("127.0.0.1"),
31 | username = Some("user"),
32 | password = Some("password")
33 | )
34 | .use { bitcoin =>
35 | for {
36 | hash <- bitcoin.getNextBlockHash()
37 | block <- bitcoin.getBlockByHash(hash)
38 | _ <- IO { println(block) }
39 | } yield ExitCode(0)
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/scala/examples/ethereum/CatchupFromZero.scala:
--------------------------------------------------------------------------------
1 | /**
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 | package io.tokenanalyst.blockchainrpc.examples.ethereum
18 |
19 | import cats.effect.{ExitCode, IO, IOApp}
20 | import io.tokenanalyst.blockchainrpc.ethereum.Syntax._
21 | import io.tokenanalyst.blockchainrpc.ethereum.HexTools
22 | import io.tokenanalyst.blockchainrpc.ethereum.Protocol.TransactionResponse
23 | import io.tokenanalyst.blockchainrpc.{BatchResponse, Config, Ethereum, RPCClient}
24 |
25 | import scala.concurrent.ExecutionContext.global
26 |
27 | object CatchupFromZero extends IOApp {
28 |
29 | def getReceipts(rpc: Ethereum, txs: Seq[String]) =
30 | for {
31 | receipts <- rpc.getReceiptsByHash(txs)
32 | _ <- IO(println(s"${receipts.seq.size} receipts"))
33 | } yield ()
34 |
35 | def loop(rpc: Ethereum, current: Long = 9000000L, until: Long = 9120000): IO[Unit] =
36 | for {
37 | block <- rpc.getBlockByHeight(current)
38 | _ <- IO { println(s"block ${HexTools.parseQuantity(block.number)} - ${block.hash}: ${block.transactions.size} transactions") }
39 | transactions <- if(block.transactions.nonEmpty) rpc.getTransactions(block.transactions) else IO.pure(BatchResponse[TransactionResponse](List()))
40 | _ <- IO(println(s"transactions: ${transactions.seq.size}"))
41 | _ <- if(block.transactions.nonEmpty) getReceipts(rpc, block.transactions) else IO.unit
42 | l <- if (current + 1 < until) loop(rpc, current + 1, until) else IO.unit
43 | } yield l
44 |
45 | def run(args: List[String]): IO[ExitCode] = {
46 | implicit val ec = global
47 | implicit val config = Config.fromEnv
48 | RPCClient
49 | .ethereum(
50 | config.hosts,
51 | config.port,
52 | config.username,
53 | config.password,
54 | onErrorRetry = { (_, e: Throwable) =>
55 | IO(println(e))
56 | }
57 | )
58 | .use { ethereum =>
59 | for {
60 | _ <- loop(ethereum)
61 | } yield ExitCode(0)
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/main/scala/examples/ethereum/GetEthereumBestBlock.scala:
--------------------------------------------------------------------------------
1 | /**
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 | package io.tokenanalyst.blockchainrpc.examples.ethereum
18 |
19 | import cats.effect.{ExitCode, IO, IOApp}
20 | import scala.concurrent.ExecutionContext.global
21 |
22 | import io.tokenanalyst.blockchainrpc.{RPCClient, Config}
23 | import io.tokenanalyst.blockchainrpc.ethereum.Syntax._
24 |
25 | object GetEthereumBestBlock extends IOApp {
26 | def run(args: List[String]): IO[ExitCode] = {
27 | implicit val ec = global
28 | implicit val config = Config.fromEnv
29 | RPCClient
30 | .ethereum(
31 | config.hosts,
32 | config.port,
33 | config.username,
34 | config.password,
35 | onErrorRetry = { (_, e: Throwable) =>
36 | IO(println(e))
37 | }
38 | )
39 | .use { ethereum =>
40 | for {
41 | height <- ethereum.getBestBlockHeight()
42 | _ <- IO { println(height) }
43 | } yield ExitCode(0)
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/main/scala/examples/ethereum/GetEthereumBlockByHash.scala:
--------------------------------------------------------------------------------
1 | /**
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 | package io.tokenanalyst.blockchainrpc.examples.ethereum
18 |
19 | import cats.effect.{ExitCode, IO, IOApp}
20 | import scala.concurrent.ExecutionContext.global
21 |
22 | import io.tokenanalyst.blockchainrpc.{RPCClient, Config}
23 | import io.tokenanalyst.blockchainrpc.ethereum.Syntax._
24 | import io.tokenanalyst.blockchainrpc.ethereum.HexTools
25 |
26 | object GetEthereumBlockByHash extends IOApp {
27 | def run(args: List[String]): IO[ExitCode] = {
28 |
29 |
30 | implicit val ec = global
31 | implicit val config = Config.fromEnv
32 | RPCClient
33 | .ethereum(
34 | config.hosts,
35 | config.port,
36 | config.username,
37 | config.password,
38 | onErrorRetry = { (_, e: Throwable) =>
39 | IO(println(e))
40 | }
41 | )
42 | .use { ethereum =>
43 | for {
44 | block <- ethereum.getBlockByHash(
45 | "0x3bad41c70c9efac92490e8a74ab816558bbdada0984f2bcfa4cb1522ddb3ca16"
46 | )
47 | _ <- IO { println(s"block ${HexTools.parseQuantity(block.number)}: $block") }
48 | } yield ExitCode(0)
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/main/scala/examples/ethereum/GetEthereumBlockByHeight.scala:
--------------------------------------------------------------------------------
1 | /**
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 | package io.tokenanalyst.blockchainrpc.examples.ethereum
18 |
19 | import cats.effect.{ExitCode, IO, IOApp}
20 | import scala.concurrent.ExecutionContext.global
21 |
22 | import io.tokenanalyst.blockchainrpc.{RPCClient, Config}
23 | import io.tokenanalyst.blockchainrpc.ethereum.Syntax._
24 |
25 | object GetEthereumBlockByHeight extends IOApp {
26 | def run(args: List[String]): IO[ExitCode] = {
27 | implicit val ec = global
28 | implicit val config = Config.fromEnv
29 | RPCClient
30 | .ethereum(
31 | config.hosts,
32 | config.port,
33 | config.username,
34 | config.password,
35 | onErrorRetry = { (_, e: Throwable) =>
36 | IO(println(e))
37 | }
38 | )
39 | .use { ethereum =>
40 | for {
41 | block <- ethereum.getBlockByHeight(
42 | 14
43 | )
44 | _ <- IO { println(block) }
45 | } yield ExitCode(0)
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/scala/examples/ethereum/GetEthereumTransactionByHash.scala:
--------------------------------------------------------------------------------
1 | /**
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 | package io.tokenanalyst.blockchainrpc.examples.ethereum
18 |
19 | import cats.effect.{ExitCode, IO, IOApp}
20 | import io.tokenanalyst.blockchainrpc.ethereum.Syntax._
21 | import io.tokenanalyst.blockchainrpc.{Config, RPCClient}
22 |
23 | import scala.concurrent.ExecutionContext.global
24 |
25 | object GetEthereumTransactionByHash extends IOApp {
26 | def run(args: List[String]): IO[ExitCode] = {
27 | implicit val ec = global
28 | implicit val config = Config.fromEnv
29 | RPCClient
30 | .ethereum(
31 | config.hosts,
32 | config.port,
33 | config.username,
34 | config.password,
35 | onErrorRetry = { (_, e: Throwable) =>
36 | IO(println(e))
37 | }
38 | )
39 | .use { ethereum =>
40 | for {
41 | tx <- ethereum.getTransaction(
42 | "0xe9e91f1ee4b56c0df2e9f06c2b8c27c6076195a88a7b8537ba8313d80e6f124e"
43 | )
44 | _ <- IO { println(tx) }
45 | } yield ExitCode(0)
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/scala/examples/ethereum/GetReceiptByHash.scala:
--------------------------------------------------------------------------------
1 | /**
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 | package io.tokenanalyst.blockchainrpc.examples.ethereum
18 |
19 | import cats.effect.{ExitCode, IO, IOApp}
20 | import io.tokenanalyst.blockchainrpc.ethereum.Syntax._
21 | import io.tokenanalyst.blockchainrpc.{Config, RPCClient}
22 |
23 | import scala.concurrent.ExecutionContext.global
24 |
25 | object GetReceiptByHash extends IOApp {
26 | def run(args: List[String]): IO[ExitCode] = {
27 | implicit val ec = global
28 | implicit val config = Config.fromEnv
29 | RPCClient
30 | .ethereum(
31 | config.hosts,
32 | config.port,
33 | config.username,
34 | config.password,
35 | onErrorRetry = { (_, e: Throwable) =>
36 | IO(println(e))
37 | }
38 | )
39 | .use { ethereum =>
40 | for {
41 | tx <- ethereum.getReceiptByHash(
42 | "0x218b632d932371478d1ae5a01620ebab1a2030f9dad6f8fba4a044ea6335a57e"
43 | )
44 | _ <- IO { println(tx) }
45 | } yield ExitCode(0)
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/scala/examples/omni/OmniGetBlockTransactions.scala:
--------------------------------------------------------------------------------
1 | /**
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | package io.tokenanalyst.blockchainrpc.examples.omni
19 |
20 | import cats.effect.{ExitCode, IO, IOApp}
21 | import io.tokenanalyst.blockchainrpc.{Config, RPCClient}
22 | import io.tokenanalyst.blockchainrpc.omni.Syntax._
23 |
24 | import scala.concurrent.ExecutionContext.global
25 |
26 | object OmniGetBlockTransactions extends IOApp {
27 | override def run(args: List[String]): IO[ExitCode] = {
28 | implicit val ec = global
29 | val config = Config.fromEnv
30 | RPCClient.omni(
31 | config.hosts,
32 | config.port,
33 | config.username,
34 | config.password,
35 | ).use { omni =>
36 | for {
37 | bestBlockHeight <- omni.getBestBlockHeight()
38 | _ <- IO(println(s"best block: $bestBlockHeight"))
39 | txs <- omni.listBlockTransactions(bestBlockHeight)
40 | values <- omni.getTransactions(txs)
41 | _ <- IO {println(s"sending addresses: ${values.seq.map(_.sendingaddress)}")}
42 | } yield ExitCode.Success
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/main/scala/omni/Codecs.scala:
--------------------------------------------------------------------------------
1 | /**
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 | package io.tokenanalyst.blockchainrpc.omni
18 |
19 | import io.circe.Json
20 | import io.tokenanalyst.blockchainrpc.Codecs._
21 | import io.tokenanalyst.blockchainrpc.RPCEncoder
22 | import io.tokenanalyst.blockchainrpc.omni.Protocol.BlockRequest
23 | import io.tokenanalyst.blockchainrpc.omni.Protocol.{
24 | BestBlockHashRequest,
25 | BlockTransactionsRequest,
26 | TransactionRequest,
27 | BlockHashRequest
28 | }
29 |
30 | object Codecs {
31 |
32 | implicit val listBlockTransactionsRequest =
33 | new RPCEncoder[BlockTransactionsRequest] {
34 | final def apply(a: BlockTransactionsRequest): Json = {
35 | Json.obj(
36 | requestFields(
37 | "omni_listblocktransactions",
38 | Array(Json.fromLong(a.height))
39 | ): _*
40 | )
41 | }
42 | }
43 |
44 | implicit val blockHashRequest = new RPCEncoder[BlockHashRequest] {
45 | final def apply(a: BlockHashRequest): Json =
46 | Json.obj(
47 | requestFields("getblockhash", Array[Json](Json.fromLong(a.height))): _*
48 | )
49 | }
50 |
51 | implicit val getTransactionRequest = new RPCEncoder[TransactionRequest] {
52 | final def apply(a: TransactionRequest): Json =
53 | Json.obj(
54 | requestFields(
55 | "omni_gettransaction",
56 | Array(Json.fromString(a.hash))
57 | ): _*
58 | )
59 | }
60 |
61 | implicit val bestBlockHashRequest = new RPCEncoder[BestBlockHashRequest] {
62 | final def apply(a: BestBlockHashRequest): Json =
63 | Json.obj(requestFields("getbestblockhash", Array[Json]()): _*)
64 | }
65 |
66 | implicit val blockRequest = new RPCEncoder[BlockRequest] {
67 | final def apply(a: BlockRequest): Json =
68 | Json.obj(requestFields("getblock", Array(Json.fromString(a.hash))): _*)
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/main/scala/omni/Instances.scala:
--------------------------------------------------------------------------------
1 | /**
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 | package io.tokenanalyst.blockchainrpc.omni
18 |
19 | import cats.effect.IO
20 | import io.circe.generic.auto._
21 | import io.tokenanalyst.blockchainrpc.Codecs._
22 | import io.tokenanalyst.blockchainrpc.OmniMethods._
23 | import io.tokenanalyst.blockchainrpc.BasicMethods._
24 | import io.tokenanalyst.blockchainrpc.omni.Codecs._
25 | import io.tokenanalyst.blockchainrpc.omni.Protocol._
26 | import io.tokenanalyst.blockchainrpc.{BatchRequest, BatchResponse, Omni}
27 |
28 | object Instances {
29 | implicit val getNextBlockHashInstance =
30 | new GetNextBlockHash[Omni] {
31 | override def getNextBlockHash(a: Omni): IO[String] =
32 | a.client.nextBlockHash()
33 | }
34 |
35 | implicit val listBlockTransactionsInstance =
36 | new ListBlockTransactions {
37 | override def listBlockTransactions(
38 | omni: Omni,
39 | height: Long
40 | ): IO[Seq[String]] =
41 | for {
42 | json <- omni.client.requestJson[BlockTransactionsRequest](
43 | BlockTransactionsRequest(height)
44 | )
45 | } yield json.asObject.get("result").get.asArray.get.map(_.asString.get)
46 | }
47 |
48 | implicit val getTransactionInstance =
49 | new GetTransaction[Omni, TransactionResponse] {
50 | override def getTransaction(
51 | omni: Omni,
52 | hash: String
53 | ): IO[TransactionResponse] = {
54 | for {
55 | res <- omni.client.request[TransactionRequest, TransactionResponse](
56 | TransactionRequest(hash)
57 | )
58 | } yield res
59 | }
60 | }
61 |
62 | implicit val getTransactionsInstance =
63 | new GetTransactions[Omni, BatchResponse[TransactionResponse]] {
64 | override def getTransactions(
65 | omni: Omni,
66 | hashes: Seq[String]
67 | ): IO[BatchResponse[TransactionResponse]] =
68 | for {
69 | res <- omni.client
70 | .request[BatchRequest[TransactionRequest], BatchResponse[
71 | TransactionResponse
72 | ]](
73 | BatchRequest[TransactionRequest](
74 | hashes.map(TransactionRequest.apply)
75 | )
76 | )
77 | } yield res
78 | }
79 |
80 | implicit val getBlockHashInstance = new GetBlockHash[Omni] {
81 | override def getBlockHash(a: Omni, height: Long): IO[String] =
82 | for {
83 | json <- a.client
84 | .requestJson[BlockHashRequest](BlockHashRequest(height))
85 | } yield json.asObject.get("result").get.asString.get
86 | }
87 |
88 | implicit val getBestBlockHashInstance = new GetBestBlockHash[Omni] {
89 | override def getBestBlockHash(omni: Omni): IO[String] =
90 | for {
91 | json <- omni.client
92 | .requestJson[BestBlockHashRequest](new BestBlockHashRequest)
93 | } yield json.asObject.get("result").get.asString.get
94 | }
95 |
96 | implicit val getBlockByHashInstance =
97 | new GetBlockByHash[Omni, BlockResponse] {
98 | override def getBlockByHash(
99 | a: Omni,
100 | hash: String
101 | ): IO[BlockResponse] = {
102 | a.client.request[BlockRequest, BlockResponse](BlockRequest(hash))
103 | }
104 | }
105 |
106 | implicit val getBlockByHeightInstance =
107 | new GetBlockByHeight[Omni, BlockResponse] {
108 | override def getBlockByHeight(
109 | a: Omni,
110 | height: Long
111 | ): IO[BlockResponse] =
112 | for {
113 | hash <- getBlockHashInstance.getBlockHash(a, height)
114 | data <- getBlockByHashInstance.getBlockByHash(a, hash)
115 | } yield data
116 | }
117 |
118 | implicit val getBestBlockHeightInstance =
119 | new GetBestBlockHeight[Omni] {
120 | override def getBestBlockHeight(a: Omni): IO[Long] =
121 | for {
122 | hash <- getBestBlockHashInstance.getBestBlockHash(a)
123 | block <- getBlockByHashInstance.getBlockByHash(a, hash)
124 | } yield block.height
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/src/main/scala/omni/Protocol.scala:
--------------------------------------------------------------------------------
1 | /**
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 | package io.tokenanalyst.blockchainrpc.omni
18 |
19 | import io.tokenanalyst.blockchainrpc._
20 |
21 | object Protocol {
22 | case class TransactionResponse(
23 | txid: String,
24 | block: Long,
25 | blockhash: String,
26 | confirmations: Int,
27 | positioninblock: Option[Int],
28 | version: Option[Int],
29 | blocktime: Long,
30 | valid: Option[Boolean],
31 | invalidreason: Option[String],
32 | type_int: Option[Int],
33 | `type`: Option[String],
34 | propertyid: Option[Long],
35 | amount: Option[Double],
36 | fee: Option[Double],
37 | sendingaddress: String,
38 | referenceaddress: Option[String]
39 | ) extends RPCResponse
40 |
41 | case class BlockResponse(
42 | height: Long,
43 | hash: String,
44 | previousblockhash: Option[String],
45 | nonce: Long,
46 | strippedsize: Long,
47 | merkleroot: String,
48 | version: Int,
49 | weight: Int,
50 | difficulty: Double,
51 | chainwork: String,
52 | bits: String,
53 | size: Long,
54 | mediantime: Long,
55 | time: Long,
56 | tx: List[String]
57 | ) extends RPCResponse
58 |
59 | case class BlockTransactionsRequest(height: Long) extends RPCRequest
60 | case class TransactionRequest(hash: String) extends RPCRequest
61 | case class BestBlockHashRequest() extends RPCRequest
62 | case class BlockRequest(hash: String) extends RPCRequest
63 | case class BlockHashRequest(height: Long) extends RPCRequest
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/src/main/scala/omni/Syntax.scala:
--------------------------------------------------------------------------------
1 | /**
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 | package io.tokenanalyst.blockchainrpc.omni
18 |
19 | import io.tokenanalyst.blockchainrpc.BatchResponse
20 | import io.tokenanalyst.blockchainrpc.Omni
21 | import io.tokenanalyst.blockchainrpc.BasicMethods._
22 | import io.tokenanalyst.blockchainrpc.OmniMethods._
23 | import io.tokenanalyst.blockchainrpc.omni.Instances._
24 |
25 | import Protocol._
26 |
27 | object Syntax {
28 | implicit class OmniOps(omni: Omni) {
29 | def listBlockTransactions(height: Long) =
30 | implicitly[ListBlockTransactions].listBlockTransactions(omni, height)
31 |
32 | def getTransaction(hash: String) =
33 | implicitly[GetTransaction[Omni, TransactionResponse]].getTransaction(omni, hash)
34 |
35 | def getTransactions(hashes: Seq[String]) =
36 | implicitly[GetTransactions[Omni, BatchResponse[TransactionResponse]]]
37 | .getTransactions(omni, hashes)
38 |
39 | def getNextBlockHash() =
40 | implicitly[GetNextBlockHash[Omni]].getNextBlockHash(omni)
41 |
42 | def getBlockByHeight(height: Long) =
43 | implicitly[GetBlockByHeight[Omni, BlockResponse]]
44 | .getBlockByHeight(omni, height)
45 |
46 | def getBestBlockHash() =
47 | implicitly[GetBestBlockHash[Omni]].getBestBlockHash(omni)
48 |
49 | def getBestBlockHeight() =
50 | implicitly[GetBestBlockHeight[Omni]].getBestBlockHeight(omni)
51 |
52 | def getBlockByHash(hash: String) =
53 | implicitly[GetBlockByHash[Omni, BlockResponse]].getBlockByHash(omni, hash)
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/test/scala/bitcoin/ProtocolSpec.scala:
--------------------------------------------------------------------------------
1 | /**
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 | package io.tokenanalyst.blockchainrpc.test.bitcoin
18 |
19 | import org.scalatest.matchers.should.Matchers
20 | import org.scalatest.flatspec.AnyFlatSpec
21 | import io.tokenanalyst.blockchainrpc.bitcoin.Protocol._
22 | import io.tokenanalyst.blockchainrpc.Codecs._
23 | import io.circe.generic.auto._
24 | import io.circe.parser.decode
25 |
26 | class ProtocolSpec extends AnyFlatSpec with Matchers {
27 |
28 | behavior of "Bitcoin protocol"
29 |
30 | it should "decode BlockResponse" in {
31 | val response =
32 | """
33 | {"result":{"hash":"00000000000000000009db93b0bd627158b665cd954cc274c056ad8b257c3f35","confirmations":1,"strippedsize":779713,"size":1654295,"weight":3993434,"height":607523,"version":536928256,"versionHex":"2000e000","merkleroot":"e1c20883d44eead043c4d8bf118520e61c72183145067d212af8fba319d67ba5","tx":["26de2820d5e15884a18c24426c6fafa6f527cca0f67a0266aa9737690b0bf3bc"],"time":1575992821,"mediantime":1575988911,"nonce":3271658646,"bits":"1715dbd2","difficulty":12876842089682.48,"chainwork":"00000000000000000000000000000000000000000ac014e49df992a6caa4cd1c","nTx":2273,"previousblockhash":"0000000000000000000280301806aa6a1e74ff64139f2dd03c3ac30802739c7a"},"error":null,"id":"curltest"}
34 | """
35 | val decoded = decode[BlockResponse](response)
36 | decoded shouldEqual (Right(
37 | BlockResponse(
38 | 607523,
39 | "00000000000000000009db93b0bd627158b665cd954cc274c056ad8b257c3f35",
40 | Some(
41 | "0000000000000000000280301806aa6a1e74ff64139f2dd03c3ac30802739c7a"
42 | ),
43 | 3271658646L,
44 | 779713,
45 | "e1c20883d44eead043c4d8bf118520e61c72183145067d212af8fba319d67ba5",
46 | 536928256,
47 | 3993434,
48 | 12876842089682.48,
49 | "00000000000000000000000000000000000000000ac014e49df992a6caa4cd1c",
50 | "1715dbd2",
51 | 1654295,
52 | 1575988911,
53 | 1575992821,
54 | 2273,
55 | List("26de2820d5e15884a18c24426c6fafa6f527cca0f67a0266aa9737690b0bf3bc")
56 | )
57 | ))
58 | }
59 |
60 | it should "decode TransactionResponse" in {
61 | val response =
62 | """
63 | {"result":{"txid":"3246fa6d081b32223a4097052b15594acc00372a9ce293b29f92086e19655888","hash":"20ed27407730078e01478e375910cd92ec4c99c0e1763a893df877e36a63f24f","version":1,"size":193,"vsize":111,"weight":442,"locktime":0,"vin":[{"txid":"eee52abb8ef949f0eaba5b331b4aa6de13601b00a93d99ed0cc75189be3d2a24","vout":1,"scriptSig":{"asm":"","hex":""},"txinwitness":["3045022100dc5b8d3643750a3b96defa6e452b268d25bdcaba9d75e3614aaa8f59c285bd3902206b44833a75111ce4bd487be86972def80db4ef8c2865e8b1c19cda5f9f7ee09101","035d6e3719f26bcc5381eca4e5c33ff3f9fd5b4e84450f863abbd83a584ccdc204"],"sequence":4294967295}],"vout":[{"value":0.00803580,"n":0,"scriptPubKey":{"asm":"OP_HASH160 ab974139080e159ac8af9fc0d9e2ef4885a1339d OP_EQUAL","hex":"a914ab974139080e159ac8af9fc0d9e2ef4885a1339d87","reqSigs":1,"type":"scripthash","addresses":["3HLJfiJLa5CnKQoAF3YeteidRjDMvY5upH"]}}],"hex":"01000000000101242a3dbe8951c70ced993da9001b6013dea64a1b335bbaeaf049f98ebb2ae5ee0100000000ffffffff01fc420c000000000017a914ab974139080e159ac8af9fc0d9e2ef4885a1339d8702483045022100dc5b8d3643750a3b96defa6e452b268d25bdcaba9d75e3614aaa8f59c285bd3902206b44833a75111ce4bd487be86972def80db4ef8c2865e8b1c19cda5f9f7ee0910121035d6e3719f26bcc5381eca4e5c33ff3f9fd5b4e84450f863abbd83a584ccdc20400000000","blockhash":"0000000000000000000e924f2fc7105362ce640d0865d10d314086c795d2cfde","confirmations":1,"time":1575996190,"blocktime":1575996190},"error":null,"id":"curltest"}
64 | """
65 | val decoded = decode[TransactionResponse](response)
66 | decoded shouldEqual Right(
67 | TransactionResponse(
68 | Some(1),
69 | "0000000000000000000e924f2fc7105362ce640d0865d10d314086c795d2cfde",
70 | 1575996190L,
71 | "20ed27407730078e01478e375910cd92ec4c99c0e1763a893df877e36a63f24f",
72 | "01000000000101242a3dbe8951c70ced993da9001b6013dea64a1b335bbaeaf049f98ebb2ae5ee0100000000ffffffff01fc420c000000000017a914ab974139080e159ac8af9fc0d9e2ef4885a1339d8702483045022100dc5b8d3643750a3b96defa6e452b268d25bdcaba9d75e3614aaa8f59c285bd3902206b44833a75111ce4bd487be86972def80db4ef8c2865e8b1c19cda5f9f7ee0910121035d6e3719f26bcc5381eca4e5c33ff3f9fd5b4e84450f863abbd83a584ccdc20400000000",
73 | "3246fa6d081b32223a4097052b15594acc00372a9ce293b29f92086e19655888",
74 | 1575996190L,
75 | 111,
76 | 193,
77 | 442,
78 | 1,
79 | List(
80 | TransactionResponseVin(
81 | Some(
82 | "eee52abb8ef949f0eaba5b331b4aa6de13601b00a93d99ed0cc75189be3d2a24"
83 | ),
84 | Some(1),
85 | Some(TransactionResponseScriptSig("", "")),
86 | None,
87 | 4294967295L
88 | )
89 | ),
90 | List(
91 | TransactionResponseVout(
92 | 0.00803580,
93 | 0,
94 | TransactionResponseScript(
95 | "OP_HASH160 ab974139080e159ac8af9fc0d9e2ef4885a1339d OP_EQUAL",
96 | "a914ab974139080e159ac8af9fc0d9e2ef4885a1339d87",
97 | Some(1),
98 | "scripthash",
99 | Some(List("3HLJfiJLa5CnKQoAF3YeteidRjDMvY5upH"))
100 | )
101 | )
102 | ),
103 | 0
104 | )
105 | )
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/src/test/scala/ethereum/ProtocolSpec.scala:
--------------------------------------------------------------------------------
1 | /**
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 | package io.tokenanalyst.blockchainrpc.test.ethereum
18 |
19 | import org.scalatest.matchers.should.Matchers
20 | import org.scalatest.flatspec.AnyFlatSpec
21 | import io.tokenanalyst.blockchainrpc.ethereum.Protocol.{BlockResponse, _}
22 | import io.tokenanalyst.blockchainrpc.Codecs._
23 | import io.circe.generic.auto._
24 | import io.circe.parser.decode
25 |
26 | class ProtocolSpec extends AnyFlatSpec with Matchers {
27 |
28 | behavior of "Ethereum protocol"
29 |
30 | it should """decode ReceiptResponse UA to UA Pre-byzantinium""" in {
31 | val response =
32 | """
33 | {
34 | "jsonrpc": "2.0",
35 | "result": {
36 | "blockHash": "0x3a1fba5abd9d41457944e91ed097e039b7b12d3d7ba324a3f422db2277a48e28",
37 | "blockNumber": "0xcb3d",
38 | "contractAddress": null,
39 | "cumulativeGasUsed": "0xab4d",
40 | "from": null,
41 | "gasUsed": null,
42 | "logs": [
43 | {
44 | "address": "0x5564886ca2c518d1964e5fcea4f423b41db9f561",
45 | "blockHash": null,
46 | "blockNumber": null,
47 | "data": "0x",
48 | "logIndex": null,
49 | "removed": false,
50 | "topics": [
51 | "0xa6697e974e6a320f454390be03f74955e8978f1a6971ea6730542e37b66179bc",
52 | "0x4d794669727374436f696e000000000000000000000000000000000000000000"
53 | ],
54 | "transactionHash": null,
55 | "transactionIndex": null,
56 | "transactionLogIndex": null,
57 | "type": "pending"
58 | }
59 | ],
60 | "logsBloom": "0x00000000000000000100000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000400000000000000000010",
61 | "root": "0x31ebb0ae686c616ea261dd67c1b433a78a8238add284d7b9e8edf3d0e4789ad5",
62 | "to": null,
63 | "transactionHash": "0x218b632d932371478d1ae5a01620ebab1a2030f9dad6f8fba4a044ea6335a57e",
64 | "transactionIndex": "0x0"
65 | },
66 | "id": 1
67 | }
68 | """
69 | val decoded = decode[ReceiptResponse](response)
70 | decoded shouldEqual Right(
71 | ReceiptResponse(
72 | "0x3a1fba5abd9d41457944e91ed097e039b7b12d3d7ba324a3f422db2277a48e28",
73 | "0xcb3d",
74 | None,
75 | None,
76 | None,
77 | "0xab4d",
78 | None,
79 | List(
80 | LogResponse(
81 | false,
82 | None,
83 | None,
84 | None,
85 | None,
86 | None,
87 | "0x5564886ca2c518d1964e5fcea4f423b41db9f561",
88 | "0x",
89 | List(
90 | "0xa6697e974e6a320f454390be03f74955e8978f1a6971ea6730542e37b66179bc",
91 | "0x4d794669727374436f696e000000000000000000000000000000000000000000"
92 | ),
93 | None,
94 | "pending"
95 | )
96 | ),
97 | "0x00000000000000000100000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000400000000000000000010",
98 | None,
99 | "0x218b632d932371478d1ae5a01620ebab1a2030f9dad6f8fba4a044ea6335a57e",
100 | "0x0"
101 | )
102 | )
103 | }
104 |
105 | it should """decode ReceiptResponse on contract Pre-byzantinium""" in {
106 | val response =
107 | """
108 | {
109 | "id": 1,
110 | "jsonrpc": "2.0",
111 | "result": {
112 | "blockHash": "0x67c0303244ae4beeec329e0c66198e8db8938a94d15a366c7514626528abfc8c",
113 | "blockNumber": "0x6914b0",
114 | "contractAddress": "0x471a8bf3fd0dfbe20658a97155388cec674190bf",
115 | "from": "0xc931d93e97ab07fe42d923478ba2465f2",
116 | "to": null,
117 | "cumulativeGasUsed": "0x158e33",
118 | "gasUsed": "0xba2e6",
119 | "logs": [],
120 | "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
121 | "root": null,
122 | "transactionHash": "0x444172bef57ad978655171a8af2cfd89baa02a97fcb773067aef7794d6913374",
123 | "transactionIndex": "0x4"
124 | }
125 | }
126 | """
127 |
128 | val decoded = decode[ReceiptResponse](response)
129 | decoded shouldEqual Right(
130 | ReceiptResponse(
131 | "0x67c0303244ae4beeec329e0c66198e8db8938a94d15a366c7514626528abfc8c",
132 | "0x6914b0",
133 | Some("0x471a8bf3fd0dfbe20658a97155388cec674190bf"),
134 | Some("0xc931d93e97ab07fe42d923478ba2465f2"),
135 | None,
136 | "0x158e33",
137 | Some("0xba2e6"),
138 | List(),
139 | "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
140 | None,
141 | "0x444172bef57ad978655171a8af2cfd89baa02a97fcb773067aef7794d6913374",
142 | "0x4"
143 | )
144 | )
145 | }
146 |
147 | it should "decode ReceiptResponse UA to UA Post-byzantinium" in {
148 | val response =
149 | """
150 | {
151 | "id": 1,
152 | "jsonrpc": "2.0",
153 | "result": {
154 | "blockHash": "0x67c0303244ae4beeec329e0c66198e8db8938a94d15a366c7514626528abfc8c",
155 | "blockNumber": "0x6914b0",
156 | "contractAddress": null,
157 | "from": "0xc931d93e97ab07fe42d923478ba2465f2",
158 | "to": "0xc931d93e97ab07fe42d923478ba2465f2",
159 | "cumulativeGasUsed": "0x158e33",
160 | "gasUsed": "0xba2e6",
161 | "logs": [],
162 | "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
163 | "root": null,
164 | "status": "0x1",
165 | "transactionHash": "0x444172bef57ad978655171a8af2cfd89baa02a97fcb773067aef7794d6913374",
166 | "transactionIndex": "0x4"
167 | }
168 | }
169 | """
170 |
171 | val decoded = decode[ReceiptResponse](response)
172 | decoded shouldEqual Right(
173 | ReceiptResponse(
174 | "0x67c0303244ae4beeec329e0c66198e8db8938a94d15a366c7514626528abfc8c",
175 | "0x6914b0",
176 | None,
177 | Some("0xc931d93e97ab07fe42d923478ba2465f2"),
178 | Some("0xc931d93e97ab07fe42d923478ba2465f2"),
179 | "0x158e33",
180 | Some("0xba2e6"),
181 | List(),
182 | "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
183 | Some("0x1"),
184 | "0x444172bef57ad978655171a8af2cfd89baa02a97fcb773067aef7794d6913374",
185 | "0x4"
186 | )
187 | )
188 | }
189 |
190 | it should "decode ReceiptResponse on contract Post-byzantinium" in {
191 | val response =
192 | """
193 | {
194 | "id": 1,
195 | "jsonrpc": "2.0",
196 | "result": {
197 | "blockHash": "0x67c0303244ae4beeec329e0c66198e8db8938a94d15a366c7514626528abfc8c",
198 | "blockNumber": "0x6914b0",
199 | "contractAddress": "0x471a8bf3fd0dfbe20658a97155388cec674190bf",
200 | "from": "0xc931d93e97ab07fe42d923478ba2465f2",
201 | "to": null,
202 | "cumulativeGasUsed": "0x158e33",
203 | "gasUsed": "0xba2e6",
204 | "logs": [],
205 | "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
206 | "root": null,
207 | "status": "0x1",
208 | "transactionHash": "0x444172bef57ad978655171a8af2cfd89baa02a97fcb773067aef7794d6913374",
209 | "transactionIndex": "0x4"
210 | }
211 | }
212 | """
213 |
214 | val decoded = decode[ReceiptResponse](response)
215 | decoded shouldEqual Right(
216 | ReceiptResponse(
217 | "0x67c0303244ae4beeec329e0c66198e8db8938a94d15a366c7514626528abfc8c",
218 | "0x6914b0",
219 | Some("0x471a8bf3fd0dfbe20658a97155388cec674190bf"),
220 | Some("0xc931d93e97ab07fe42d923478ba2465f2"),
221 | None,
222 | "0x158e33",
223 | Some("0xba2e6"),
224 | List(),
225 | "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
226 | Some("0x1"),
227 | "0x444172bef57ad978655171a8af2cfd89baa02a97fcb773067aef7794d6913374",
228 | "0x4"
229 | )
230 | )
231 | }
232 |
233 | it should "decode TransactionResponse" in {
234 | val response =
235 | """
236 | {"jsonrpc":"2.0","result":{"blockHash":"0x1d59ff54b1eb26b013ce3cb5fc9dab3705b415a67127a003c3e61eb445bb8df2","blockNumber":"0x5daf3b","chainId":"0x1","condition":null,"creates":null,"from":"0xa7d9ddbe1f17865597fbd27ec712455208b6b76d","gas":"0xc350","gasPrice":"0x4a817c800","hash":"0x88df016429689c079f3b2f6ad39fa052532c56795b733da78a91ebe6a713944b","input":"0x68656c6c6f21","nonce":"0x15","publicKey":"0xd200bc91709a525ba0b5ce99053f239d890a78327eb038a87bcd4f8166a30b825a4122f0998946a4b7c7996b93e2d4aff8cf9a3fcdaffffae0badf90a3f9a4df","r":"0x1b5e176d927f8e9ab405058b2d2457392da3e20f328b16ddabcebc33eaac5fea","raw":"0xf871158504a817c80082c35094f02c1c8e6114b1dbe8937a39260b5b0a374432bb870f3dbb761620008668656c6c6f2125a01b5e176d927f8e9ab405058b2d2457392da3e20f328b16ddabcebc33eaac5feaa04ba69724e8f69de52f0125ad8b3c5c2cef33019bac3249e2c0a2192766d1721c","s":"0x4ba69724e8f69de52f0125ad8b3c5c2cef33019bac3249e2c0a2192766d1721c","standardV":"0x0","to":"0xf02c1c8e6114b1dbe8937a39260b5b0a374432bb","transactionIndex":"0x41","v":"0x25","value":"0xf3dbb76162000"},"id":1}
237 | """
238 |
239 | val decoded = decode[TransactionResponse](response)
240 | decoded shouldEqual Right(
241 | TransactionResponse(
242 | "0x1d59ff54b1eb26b013ce3cb5fc9dab3705b415a67127a003c3e61eb445bb8df2",
243 | "0x5daf3b",
244 | Some("0x1"),
245 | "0xa7d9ddbe1f17865597fbd27ec712455208b6b76d",
246 | "0xc350",
247 | "0x4a817c800",
248 | "0x88df016429689c079f3b2f6ad39fa052532c56795b733da78a91ebe6a713944b",
249 | "0x68656c6c6f21",
250 | "0x15",
251 | "0xd200bc91709a525ba0b5ce99053f239d890a78327eb038a87bcd4f8166a30b825a4122f0998946a4b7c7996b93e2d4aff8cf9a3fcdaffffae0badf90a3f9a4df",
252 | "0x1b5e176d927f8e9ab405058b2d2457392da3e20f328b16ddabcebc33eaac5fea",
253 | "0xf871158504a817c80082c35094f02c1c8e6114b1dbe8937a39260b5b0a374432bb870f3dbb761620008668656c6c6f2125a01b5e176d927f8e9ab405058b2d2457392da3e20f328b16ddabcebc33eaac5feaa04ba69724e8f69de52f0125ad8b3c5c2cef33019bac3249e2c0a2192766d1721c",
254 | "0x4ba69724e8f69de52f0125ad8b3c5c2cef33019bac3249e2c0a2192766d1721c",
255 | "0x25",
256 | "0x0",
257 | Some("0xf02c1c8e6114b1dbe8937a39260b5b0a374432bb"),
258 | "0x41",
259 | "0xf3dbb76162000",
260 | None,
261 | None
262 | )
263 | )
264 | }
265 |
266 | it should "decode BlockResponse" in {
267 | val response =
268 | """
269 | {"jsonrpc":"2.0","result":{"author":"0x5a0b54d5dc17e0aadc383d2db43b0a0d3e029c4c","difficulty":"0x89e4c5695f464","extraData":"0x5050594520737061726b706f6f6c2d6574682d636e2d687a32","gasLimit":"0x9879a1","gasUsed":"0x0","hash":"0xd5e3eff1778c4735fb2a51c5c4e92bec32f9363ba23285d6d94e62c26b7c0884","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","miner":"0x5a0b54d5dc17e0aadc383d2db43b0a0d3e029c4c","mixHash":"0x1c0540069d772ce8fe02415a5988ad5b90b389a13d1a0643087ea75487007aef","nonce":"0xdf2bd7300001a5e8","number":"0x865801","parentHash":"0x65144a20fd4d1b2faa86d90d35b9cfcc87b67f48b21ad439167d80429b719258","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","sealFields":["0xa01c0540069d772ce8fe02415a5988ad5b90b389a13d1a0643087ea75487007aef","0x88df2bd7300001a5e8"],"sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","size":"0x21f","stateRoot":"0x4b909ee750967307134206db327f361b1877bffbdbd2c1100449dd4759c350c9","timestamp":"0x5db1e4c3","totalDifficulty":"0x2a7666ad70f09d1728f","transactions":[],"transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","uncles":[]},"id":1}
270 | """
271 | val decoded = decode[BlockResponse](response)
272 | decoded shouldEqual Right(
273 | new BlockResponse(
274 | "0x5a0b54d5dc17e0aadc383d2db43b0a0d3e029c4c",
275 | "0x89e4c5695f464",
276 | "0x5050594520737061726b706f6f6c2d6574682d636e2d687a32",
277 | "0x9879a1",
278 | "0x0",
279 | "0xd5e3eff1778c4735fb2a51c5c4e92bec32f9363ba23285d6d94e62c26b7c0884",
280 | "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
281 | "0x5a0b54d5dc17e0aadc383d2db43b0a0d3e029c4c",
282 | "0x1c0540069d772ce8fe02415a5988ad5b90b389a13d1a0643087ea75487007aef",
283 | "0xdf2bd7300001a5e8",
284 | "0x865801",
285 | "0x65144a20fd4d1b2faa86d90d35b9cfcc87b67f48b21ad439167d80429b719258",
286 | "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
287 | List(
288 | "0xa01c0540069d772ce8fe02415a5988ad5b90b389a13d1a0643087ea75487007aef",
289 | "0x88df2bd7300001a5e8"
290 | ),
291 | "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
292 | "0x21f",
293 | "0x4b909ee750967307134206db327f361b1877bffbdbd2c1100449dd4759c350c9",
294 | "0x5db1e4c3",
295 | "0x2a7666ad70f09d1728f",
296 | List(),
297 | "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
298 | List()
299 | )
300 | )
301 | }
302 | }
303 |
--------------------------------------------------------------------------------