diff --git a/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/domain/Address.java b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/domain/Address.java
new file mode 100644
index 00000000..4eda18c9
--- /dev/null
+++ b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/domain/Address.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (c) 2020 EmeraldPay Inc, All Rights Reserved.
+ * Copyright (c) 2016-2017 Infinitape Inc, All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.emeraldpay.dshackle.upstream.ethereum.domain;
+
+import io.emeraldpay.etherjar.hex.Hex32;
+import io.emeraldpay.etherjar.hex.HexData;
+import org.bouncycastle.jcajce.provider.digest.Keccak;
+import org.bouncycastle.util.encoders.Hex;
+
+import java.util.Arrays;
+import java.util.regex.Pattern;
+
+/**
+ * Ethereum Wallet address
+ */
+public class Address extends HexData {
+
+ private final static byte[] HEX_BYTES = "0123456789abcdef".getBytes();
+
+ public static final int SIZE_BYTES = 20;
+ public static final int SIZE_HEX = 2 + SIZE_BYTES * 2;
+
+ private static final byte[] EMPTY_12BYTES = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+
+ /**
+ * Use {@link Address#empty()}
+ */
+ @Deprecated
+ public static final Address EMPTY = Address.from("0x0000000000000000000000000000000000000000");
+
+ private static final Pattern CASE_INSENSITIVE_PATTERN = Pattern.compile("0x(?i:[0-9a-f]{40})");
+ private static final Pattern CASE_SENSITIVE_PATTERN = Pattern.compile("0x(?:[0-9a-f]{40}|[0-9A-F]{40})");
+
+ private Address(byte[] bytes) {
+ super(bytes, SIZE_BYTES);
+ }
+
+ /**
+ * Create address from the provided value
+ *
+ * @param value 20 byte data
+ * @return address
+ */
+ public static Address from(HexData value) {
+ if (value == null) {
+ throw new IllegalArgumentException("Null input value");
+ }
+ if (value.getSize() != SIZE_BYTES) {
+ throw new IllegalArgumentException("Invalid input length: " + value.getSize() + " != " + SIZE_BYTES);
+ }
+ return new Address(value.getBytes());
+ }
+
+ public static Address from(byte[] value) {
+ if (value == null) {
+ throw new IllegalArgumentException("Null input value");
+ }
+ if (value.length != SIZE_BYTES) {
+ throw new IllegalArgumentException("Invalid input length: " + value.length + " != " + SIZE_BYTES);
+ }
+ return new Address(value);
+ }
+
+ public static Address from(String value) {
+ if (value == null) {
+ throw new IllegalArgumentException("Null input value");
+ }
+ if (value.length() != SIZE_HEX) {
+ throw new IllegalArgumentException("Invalid input length: " + value.length() + " != " + SIZE_HEX);
+ }
+ return new Address(HexData.from(value).getBytes());
+ }
+
+ public static Address empty() {
+ return new Address(new byte[SIZE_BYTES]);
+ }
+
+ /**
+ * Extract address from Hex32 (i.e. from method call parameters, logs, etc). The extract method verifies
+ * that the input in fact contains only address, i.e. input has zeroes for initial non-address bytes.
+ *
+ * @param value a Hex 32
+ * @return address
+ */
+ public static Address extract(Hex32 value) {
+ if (value == null) {
+ throw new IllegalArgumentException("Null input value");
+ }
+ byte[] bytes = value.getBytes();
+ byte[] empty = new byte[Hex32.SIZE_BYTES - Address.SIZE_BYTES];
+ System.arraycopy(bytes, 0, empty, 0, empty.length);
+ if (!Arrays.equals(empty, EMPTY_12BYTES)) {
+ throw new IllegalArgumentException("Hex32 has non zero prefix for an Address");
+ }
+ byte[] address = new byte[Address.SIZE_BYTES];
+ System.arraycopy(bytes, Hex32.SIZE_BYTES - Address.SIZE_BYTES, address,0, address.length);
+ return new Address(address);
+ }
+
+ /**
+ * Validate address according to EIP 55.
+ *
+ * @param address a wallet address ('0x...')
+ * @return {@code true} if address correct or {@code false} otherwise
+ * @see EIP 55
+ */
+ public static boolean isValidAddress(String address) {
+ return CASE_INSENSITIVE_PATTERN.matcher(address).matches()
+ && (CASE_SENSITIVE_PATTERN.matcher(address).matches() || isValidChecksum(address));
+ }
+
+ public String toChecksumString() {
+ Keccak.Digest256 digest256 = new Keccak.Digest256();
+
+ byte[] hex = new byte[value.length * 2];
+ for(int i = 0, j = 0; i < value.length; i++){
+ hex[j++] = HEX_BYTES[(0xF0 & value[i]) >>> 4];
+ hex[j++] = HEX_BYTES[0x0F & value[i]];
+ }
+ digest256.update(hex);
+ String hash = Hex.toHexString(digest256.digest());
+
+ char[] plain = toHex().toCharArray();
+ char[] str = new char[hex.length + 2];
+ if (plain.length != str.length) {
+ throw new IllegalStateException("Hex representation has invalid length: " + plain.length + " != " + str.length);
+ }
+ str[0] = '0';
+ str[1] = 'x';
+ for (int i = 0; i < 40; i++) {
+ int dg = Character.digit(hash.charAt(i), 16);
+ if (dg > 7) {
+ str[i + 2] = Character.toUpperCase(plain[i + 2]);
+ } else {
+ str[i + 2] = plain[i + 2];
+ }
+ }
+ return new String(str);
+ }
+
+ @Override
+ public String toString() {
+ return toChecksumString();
+ }
+
+ /**
+ * Checks if the given string is an address with checksum (Keccak256).
+ *
+ * @param address a wallet address ('0x...')
+ * @return {@code true} if address with checksum
+ */
+ static boolean isValidChecksum(String address) {
+ Keccak.Digest256 digest256 = new Keccak.Digest256();
+
+ digest256.update(
+ address.substring(2).toLowerCase().getBytes());
+
+ String hash = Hex.toHexString(digest256.digest());
+
+ for (int i = 0; i < 40; i++) {
+ char ch = address.charAt(i + 2);
+ int dg = Character.digit(hash.charAt(i), 16);
+
+ if ((dg > 7 && Character.toUpperCase(ch) != ch)
+ || (dg <= 7 && Character.toLowerCase(ch) != ch))
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/domain/BlockHash.java b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/domain/BlockHash.java
new file mode 100644
index 00000000..0d1d98dd
--- /dev/null
+++ b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/domain/BlockHash.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2020 EmeraldPay Inc, All Rights Reserved.
+ * Copyright (c) 2016-2017 Infinitape Inc, All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.emeraldpay.dshackle.upstream.ethereum.domain;
+
+import io.emeraldpay.etherjar.hex.HexData;
+
+public class BlockHash extends HexData {
+
+ public static final int SIZE_BYTES = 32;
+ public static final int SIZE_HEX = 2 + SIZE_BYTES * 2;
+
+ public BlockHash(byte[] value) {
+ super(value, SIZE_BYTES);
+ }
+
+ public static BlockHash from(byte[] value) {
+ if (value == null) {
+ throw new IllegalArgumentException("Null Hash");
+ }
+ if (value.length != SIZE_BYTES) {
+ throw new IllegalArgumentException("Invalid Block Hash length: " + value.length);
+ }
+ return new BlockHash(value);
+ }
+
+ public static BlockHash from(String value) {
+ if (value == null) {
+ throw new IllegalArgumentException("Null Hash");
+ }
+ if (value.length() != SIZE_HEX) {
+ throw new IllegalArgumentException("Invalid Block Hash length: " + value.length());
+ }
+ return new BlockHash(HexData.from(value).getBytes());
+ }
+
+ public static BlockHash empty() {
+ return new BlockHash(new byte[SIZE_BYTES]);
+ }
+}
diff --git a/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/domain/Bloom.java b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/domain/Bloom.java
new file mode 100644
index 00000000..42402e9b
--- /dev/null
+++ b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/domain/Bloom.java
@@ -0,0 +1,131 @@
+package io.emeraldpay.dshackle.upstream.ethereum.domain;
+
+import io.emeraldpay.etherjar.hex.HexData;
+import org.bouncycastle.jcajce.provider.digest.Keccak;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+public class Bloom extends HexData {
+
+ public static final int SIZE_BYTES = 256;
+ public static final int SIZE_HEX = 2 + SIZE_BYTES * 2;
+
+ public Bloom(byte[] value) {
+ super(value, SIZE_BYTES);
+ }
+
+ public static Bloom from(HexData value) {
+ if (value == null) {
+ throw new IllegalArgumentException("Null input value");
+ }
+ if (value.getSize() != SIZE_BYTES) {
+ throw new IllegalArgumentException("Invalid input length: " + value.getSize() + " != " + SIZE_BYTES);
+ }
+ return new Bloom(value.getBytes());
+ }
+
+ public static Bloom from(String value) {
+ if (value == null) {
+ throw new IllegalArgumentException("Null input value");
+ }
+ if (value.length() != SIZE_HEX) {
+ throw new IllegalArgumentException("Invalid input length: " + value.length() + " != " + SIZE_HEX);
+ }
+ return new Bloom(HexData.from(value).getBytes());
+ }
+
+ public static Bloom empty() {
+ return new Bloom(new byte[SIZE_BYTES]);
+ }
+
+ public Bloom mergeWith(Bloom another) {
+ byte[] result = new byte[SIZE_BYTES];
+ for (int i = 0; i < SIZE_BYTES; i++) {
+ result[i] = (byte)(this.value[i] | another.value[i]);
+ }
+ return new Bloom(result);
+ }
+
+ public static Builder newBuilder() {
+ return new Builder();
+ }
+
+ public static int bytePosition(int bitpos) {
+ return bitpos >> 3;
+ }
+
+ public static int byteMask(int bytepos, int bitpos) {
+ int inbytepos = bitpos - (bytepos << 3);
+ return 1 << (inbytepos);
+ }
+
+ public static class Builder {
+ private final byte[] current = new byte[SIZE_BYTES];
+ private final Keccak.Digest256 keccak = new Keccak.Digest256();
+
+ public Builder add(HexData value) {
+ keccak.update(value.getBytes());
+ byte[] hash = keccak.digest();
+
+ for (int i = 0; i < 6; i+= 2) {
+ int high = hash[i] & 0x7;
+ int low = hash[i + 1] & 0xff;
+ int bitpos = (high << 8) + low;
+ int bytepos = bytePosition(bitpos);
+ int bytemask = byteMask(bytepos, bitpos);
+ current[SIZE_BYTES - bytepos - 1] |= bytemask & 0xff;
+ }
+ return this;
+ }
+
+ public Bloom build() {
+ return new Bloom(current);
+ }
+
+ public Filter buildFilter() {
+ List checks = new ArrayList<>();
+ for (int i = 0; i < SIZE_BYTES; i++) {
+ if (current[i] != 0) {
+ checks.add(new Filter.Check(i, current[i]));
+ }
+ }
+ return new Filter(checks.toArray(new Filter.Check[0]));
+ }
+ }
+
+ public static class Filter {
+ private final Check[] checks;
+
+ public Filter(Check[] checks) {
+ this.checks = checks;
+ }
+
+ public boolean isSet(Bloom bloom) {
+ for (Check check: checks) {
+ int actual = bloom.value[check.pos] & check.mask;
+ if (actual == 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public static class Check {
+ private final int pos;
+ private final byte mask;
+
+ public Check(int pos, byte mask) {
+ if (pos >= SIZE_BYTES) {
+ throw new IllegalArgumentException("Out of filter boundaries position: " + pos);
+ }
+ if (mask == 0) {
+ throw new IllegalArgumentException("Mask cannot be empty");
+ }
+ this.pos = pos;
+ this.mask = mask;
+ }
+ }
+ }
+}
diff --git a/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/domain/ChainId.java b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/domain/ChainId.java
new file mode 100644
index 00000000..66c4dbc1
--- /dev/null
+++ b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/domain/ChainId.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2020 EmeraldPay Inc, All Rights Reserved.
+ * Copyright (c) 2016-2017 Infinitape Inc, All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.emeraldpay.dshackle.upstream.ethereum.domain;
+
+/**
+ * CHAIN_ID for Replay Protection (EIP-155)
+ */
+public class ChainId {
+
+ private long value;
+
+ public ChainId(long value) {
+ this.value = value;
+ }
+
+ public long getValue() {
+ return value;
+ }
+
+ public String toHex() {
+ StringBuilder buf = new StringBuilder(4);
+ buf.append("0x");
+ if (value < 16) {
+ buf.append('0');
+ }
+ buf.append(Long.toHexString(value));
+ return buf.toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof ChainId)) return false;
+
+ ChainId chainId = (ChainId) o;
+
+ return value == chainId.value;
+ }
+
+ @Override
+ public int hashCode() {
+ return (int)value;
+ }
+}
diff --git a/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/domain/EventId.java b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/domain/EventId.java
new file mode 100644
index 00000000..599e7024
--- /dev/null
+++ b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/domain/EventId.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2021 EmeraldPay Inc, All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.emeraldpay.dshackle.upstream.ethereum.domain;
+
+import io.emeraldpay.etherjar.hex.Hex32;
+import io.emeraldpay.etherjar.hex.HexData;
+import org.bouncycastle.jcajce.provider.digest.Keccak;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Objects;
+
+/**
+ * A 32-byte long id representing a contract event, i.e. first item of the topics array in the log.
+ * Calculated as keccac256("EventName(types...)")
+ */
+public class EventId extends Hex32 {
+
+ public static final int SIZE_BYTES = Hex32.SIZE_BYTES;
+ public static final int SIZE_HEX = Hex32.SIZE_HEX;
+
+ private EventId(byte[] value) {
+ super(value);
+ }
+
+ public static EventId from(byte[] value) {
+ if (value == null)
+ throw new IllegalArgumentException("Null Hash");
+
+ if (value.length != SIZE_BYTES)
+ throw new IllegalArgumentException("Invalid EventId length: " + value.length);
+
+ return new EventId(value);
+ }
+
+ public static EventId from(String value) {
+ if (value == null)
+ throw new IllegalArgumentException("Null Hash");
+
+ if (value.length() != SIZE_HEX)
+ throw new IllegalArgumentException("Invalid EventId length: " + value.length());
+
+ return new EventId(HexData.from(value).getBytes());
+ }
+
+ public static EventId empty() {
+ return new EventId(new byte[SIZE_BYTES]);
+ }
+
+ public static EventId fromSignature(String name, String... types) {
+ return fromSignature(name, Arrays.asList(types));
+ }
+
+ public static EventId fromSignature(String name, Collection types) {
+ String sign = Objects.requireNonNull(name) +
+ '(' + String.join(",", Objects.requireNonNull(types)) + ')';
+ Keccak.Digest256 digest256 = new Keccak.Digest256();
+ digest256.update(sign.getBytes());
+ return from(digest256.digest());
+ }
+}
diff --git a/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/domain/Function.java b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/domain/Function.java
new file mode 100644
index 00000000..c2820105
--- /dev/null
+++ b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/domain/Function.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2020 EmeraldPay Inc, All Rights Reserved.
+ * Copyright (c) 2016-2017 Infinitape Inc, All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.emeraldpay.dshackle.upstream.ethereum.domain;
+
+import io.emeraldpay.etherjar.hex.HexData;
+
+/**
+ * An address, followed by a function selector.
+ */
+public class Function extends HexData {
+
+ public static final int SIZE_BYTES = 24;
+ public static final int SIZE_HEX = 2 + SIZE_BYTES * 2;
+
+ private Function(byte[] bytes) {
+ super(bytes, SIZE_BYTES);
+ }
+
+ public static Function from(byte[] value) {
+ if (value == null) {
+ throw new IllegalArgumentException("Null Function");
+ }
+ if (value.length != SIZE_BYTES) {
+ throw new IllegalArgumentException("Invalid Function length: " + value.length);
+ }
+ return new Function(value);
+ }
+
+ public static Function from(String value) {
+ if (value == null) {
+ throw new IllegalArgumentException("Null Function");
+ }
+ if (value.length() != SIZE_HEX) {
+ throw new IllegalArgumentException("Invalid Function length: " + value.length());
+ }
+ return new Function(HexData.from(value).getBytes());
+ }
+}
diff --git a/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/domain/MethodId.java b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/domain/MethodId.java
new file mode 100644
index 00000000..60651302
--- /dev/null
+++ b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/domain/MethodId.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2020 EmeraldPay Inc, All Rights Reserved.
+ * Copyright (c) 2016-2017 Infinitape Inc, All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.emeraldpay.dshackle.upstream.ethereum.domain;
+
+import io.emeraldpay.etherjar.hex.HexData;
+import org.bouncycastle.jcajce.provider.digest.Keccak;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Objects;
+
+/**
+ * The first four bytes of the call data for a function call specifies the function to be called.
+ *
+ *
It is the first (left, high-order in big-endian) four bytes of the Keccak256 (SHA-3) hash
+ * of the signature of the function.
+ *
+ * @see Function Selector
+ */
+public class MethodId extends HexData {
+
+ public static final int SIZE_BYTES = 4;
+ public static final int SIZE_HEX = 2 + SIZE_BYTES * 2;
+
+ public static MethodId fromSignature(String name, String... types) {
+ return fromSignature(name, Arrays.asList(types));
+ }
+
+ public static MethodId fromSignature(String name, Collection types) {
+ String sign = Objects.requireNonNull(name) +
+ '(' + String.join(",", Objects.requireNonNull(types)) + ')';
+
+ byte[] head = new byte[SIZE_BYTES];
+ Keccak.Digest256 digest256 = new Keccak.Digest256();
+
+ digest256.update(sign.getBytes());
+ System.arraycopy(digest256.digest(), 0, head, 0, SIZE_BYTES);
+
+ return from(head);
+ }
+
+ public static MethodId from(byte[] value) {
+ if (value == null)
+ throw new IllegalArgumentException("Null Hash");
+
+ if (value.length != SIZE_BYTES)
+ throw new IllegalArgumentException("Invalid MethodId length: " + value.length);
+
+ return new MethodId(value);
+ }
+
+ public static MethodId from(String value) {
+ if (value == null)
+ throw new IllegalArgumentException("Null Hash");
+
+ if (value.length() != SIZE_HEX)
+ throw new IllegalArgumentException("Invalid MethodId length: " + value.length());
+
+ return new MethodId(HexData.from(value).getBytes());
+ }
+
+ public static MethodId empty() {
+ return new MethodId(new byte[SIZE_BYTES]);
+ }
+
+ public static MethodId fromInput(HexData input) {
+ if (input == null) {
+ return null;
+ }
+ byte[] data = input.getBytes();
+ if (data.length < SIZE_BYTES) {
+ return null;
+ }
+ byte[] head = new byte[SIZE_BYTES];
+ System.arraycopy(data, 0, head, 0, SIZE_BYTES);
+ return new MethodId(head);
+ }
+
+ public MethodId(byte[] value) {
+ super(value, SIZE_BYTES);
+ }
+}
diff --git a/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/domain/Nonce.java b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/domain/Nonce.java
new file mode 100644
index 00000000..373f58fe
--- /dev/null
+++ b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/domain/Nonce.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2020 EmeraldPay Inc, All Rights Reserved.
+ * Copyright (c) 2016-2017 Infinitape Inc, All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.emeraldpay.dshackle.upstream.ethereum.domain;
+
+
+import io.emeraldpay.etherjar.hex.HexData;
+
+public class Nonce extends HexData {
+
+ public static final int SIZE_BYTES = 8;
+ public static final int SIZE_HEX = 2 + SIZE_BYTES * 2;
+
+ public Nonce(byte[] value) {
+ super(value, SIZE_BYTES);
+ }
+
+ public static Nonce from(byte[] value) {
+ if (value == null) {
+ throw new IllegalArgumentException("Null Hash");
+ }
+ if (value.length != SIZE_BYTES) {
+ throw new IllegalArgumentException("Invalid Nonce length: " + value.length);
+ }
+ return new Nonce(value);
+ }
+
+ public static Nonce from(String value) {
+ if (value == null) {
+ throw new IllegalArgumentException("Null Hash");
+ }
+ if (value.length() != SIZE_HEX) {
+ throw new IllegalArgumentException("Invalid Nonce length: " + value.length());
+ }
+ return new Nonce(HexData.from(value).getBytes());
+ }
+}
diff --git a/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/domain/TransactionId.java b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/domain/TransactionId.java
new file mode 100644
index 00000000..425c55f6
--- /dev/null
+++ b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/domain/TransactionId.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2020 EmeraldPay Inc, All Rights Reserved.
+ * Copyright (c) 2016-2017 Infinitape Inc, All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.emeraldpay.dshackle.upstream.ethereum.domain;
+
+import io.emeraldpay.etherjar.hex.HexData;
+
+/**
+ * Transaction Hash value
+ */
+public class TransactionId extends HexData {
+
+ public static final int SIZE_BYTES = 32;
+ public static final int SIZE_HEX = 2 + SIZE_BYTES * 2;
+
+ protected TransactionId(byte[] value) {
+ super(value, SIZE_BYTES);
+ }
+
+ /**
+ * Parse value from bytes representation. Value must be 32 bytes long.
+ *
+ * @param value bytes representation
+ * @return TransactionId
+ */
+ public static TransactionId from(byte[] value) {
+ if (value.length != SIZE_BYTES) {
+ throw new IllegalArgumentException("Invalid Tx length: " + value.length);
+ }
+ return new TransactionId(value);
+ }
+
+ /**
+ * Parse value from hex representation. Value must be 64 characters long.
+ *
+ * @param value bytes representation
+ * @return TransactionId
+ */
+ public static TransactionId from(String value) {
+ if (value == null) {
+ return null;
+ }
+ if (value.length() != SIZE_HEX) {
+ throw new IllegalArgumentException("Invalid Tx length: " + value.length());
+ }
+ return new TransactionId(HexData.from(value).getBytes());
+ }
+
+ public static TransactionId empty() {
+ return new TransactionId(new byte[SIZE_BYTES]);
+ }
+
+}
diff --git a/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/domain/TransactionRef.java b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/domain/TransactionRef.java
new file mode 100644
index 00000000..cdc58fa7
--- /dev/null
+++ b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/domain/TransactionRef.java
@@ -0,0 +1,14 @@
+package io.emeraldpay.dshackle.upstream.ethereum.domain;
+
+/**
+ * Transaction reference
+ */
+public interface TransactionRef {
+
+ /**
+ *
+ * @return hash of the transaction
+ */
+ TransactionId getHash();
+
+}
diff --git a/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/domain/TransactionSignature.java b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/domain/TransactionSignature.java
new file mode 100644
index 00000000..4403d828
--- /dev/null
+++ b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/domain/TransactionSignature.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2020 EmeraldPay Inc, All Rights Reserved.
+ * Copyright (c) 2016-2017 Infinitape Inc, All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.emeraldpay.dshackle.upstream.ethereum.domain;
+
+import io.emeraldpay.etherjar.hex.HexData;
+
+import java.util.Objects;
+
+/**
+ * Transaction signature with support of Replay Protection (EIP-155)
+ */
+public class TransactionSignature {
+
+ private ChainId chainId;
+
+ private HexData publicKey;
+ private HexData r;
+ private HexData s;
+ private Long v;
+
+ public TransactionSignature() {
+ }
+
+ public ChainId getChainId() {
+ return chainId;
+ }
+
+ public void setChainId(ChainId chainId) {
+ this.chainId = chainId;
+ }
+
+ public HexData getPublicKey() {
+ return publicKey;
+ }
+
+ public void setPublicKey(HexData publicKey) {
+ this.publicKey = publicKey;
+ }
+
+ public HexData getR() {
+ return r;
+ }
+
+ public void setR(HexData r) {
+ this.r = r;
+ }
+
+ public HexData getS() {
+ return s;
+ }
+
+ public void setS(HexData s) {
+ this.s = s;
+ }
+
+ public Long getV() {
+ return v;
+ }
+
+ public void setV(Long v) {
+ if (v == null || v < 0) {
+ throw new IllegalArgumentException("Invalid V: " + v);
+ }
+ this.v = v;
+ }
+
+ public ChainId getExtractedChainId() {
+ if (!isProtected()) {
+ return null;
+ }
+ return new ChainId((v - 35) / 2);
+ }
+
+ public Long getNormalizedV() {
+ if (chainId == null) {
+ return v;
+ }
+ return v - chainId.getValue() * 2 - 35 + 27;
+ }
+
+ public boolean isProtected() {
+ if (v == 27 || v == 28) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof TransactionSignature)) return false;
+ TransactionSignature that = (TransactionSignature) o;
+ return Objects.equals(chainId, that.chainId) && Objects.equals(publicKey, that.publicKey) && Objects.equals(r, that.r) && Objects.equals(s, that.s) && Objects.equals(v, that.v);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(chainId, s, v);
+ }
+}
diff --git a/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/domain/Wei.java b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/domain/Wei.java
new file mode 100644
index 00000000..21325347
--- /dev/null
+++ b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/domain/Wei.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (c) 2020 EmeraldPay Inc, All Rights Reserved.
+ * Copyright (c) 2016-2017 Infinitape Inc, All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.emeraldpay.dshackle.upstream.ethereum.domain;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.math.RoundingMode;
+import java.util.Objects;
+
+/**
+ * Wei amount.
+ */
+public class Wei implements Serializable, Comparable {
+
+ /**
+ * Wei denomination units.
+ */
+ public enum Unit {
+
+ WEI("wei", 0),
+ KWEI("Kwei", 3),
+ MWEI("Mwei", 6),
+ GWEI("Gwei", 9),
+ SZABO("szabo", 12),
+ FINNEY("finney", 15),
+ ETHER("ether", 18),
+ KETHER("Kether", 21),
+ METHER("Mether", 24);
+
+ private String name;
+
+ private int scale;
+
+ /**
+ * @param name a unit name
+ * @param scale a wei base multiplication factor expressed as a degree of power ten
+ */
+ Unit(String name, int scale) {
+ this.name = name;
+ this.scale = scale;
+ }
+
+ /**
+ * @return a unit name
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * @return a wei base multiplication factor expressed as a degree of power ten
+ */
+ public int getScale() {
+ return scale;
+ }
+ }
+
+ public final static Wei ZERO = new Wei();
+
+ /**
+ * @param val amount in {@link Unit#ETHER}
+ * @return corresponding amount in wei
+ * @see #ofUnits(double, Unit)
+ */
+ public static Wei ofEthers(double val) {
+ return ofUnits(val, Unit.ETHER);
+ }
+
+ /**
+ * @param num amount in {@link Unit#ETHER}
+ * @return corresponding amount in wei
+ * @see #ofUnits(BigDecimal, Unit)
+ */
+ public static Wei ofEthers(BigDecimal num) {
+ return ofUnits(num, Unit.ETHER);
+ }
+
+ /**
+ * @param val amount in some custom denomination {@link Unit}
+ * @param unit target unit
+ * @return corresponding amount in wei
+ */
+ public static Wei ofUnits(double val, Unit unit) {
+ return ofUnits(BigDecimal.valueOf(val), unit);
+ }
+
+ /**
+ * @param num amount in some custom denomination {@link Unit}
+ * @param unit target unit
+ * @return corresponding amount in wei
+ */
+ public static Wei ofUnits(BigDecimal num, Unit unit) {
+ return new Wei(num.scaleByPowerOfTen(unit.getScale()).toBigInteger());
+ }
+
+ /**
+ * Parse hex representation of the amount, like 0x100
+ *
+ * @param value string representation of the amount
+ * @return parsed value or exception
+ * @throws IllegalArgumentException if passed value is null or not hex
+ */
+ public static Wei from(String value) { //TODO rename to fromHex?
+ //TODO return null?
+ if (value == null) {
+ throw new IllegalArgumentException("Null Address");
+ }
+ if (!value.startsWith("0x") || value.length() <= 2) {
+ throw new IllegalArgumentException("Invalid hex format: " + value);
+ }
+ value = value.substring(2);
+ return new Wei(new BigInteger(value, 16));
+ }
+
+ private final BigInteger amount;
+
+ /**
+ * Create zero wei amount.
+ */
+ public Wei() {
+ this(BigInteger.ZERO);
+ }
+
+ /**
+ * @param val an amount in wei
+ */
+ public Wei(long val) {
+ this(BigInteger.valueOf(val));
+ }
+
+ /**
+ * @param num an amount in wei
+ */
+ public Wei(BigInteger num) {
+ this.amount = Objects.requireNonNull(num);
+ }
+
+ /**
+ * @return an amount in wei
+ */
+ public BigInteger getAmount() {
+ return amount;
+ }
+
+ public Wei plus(Wei another) {
+ Objects.requireNonNull(another);
+ return new Wei(amount.add(another.amount));
+ }
+
+ public Wei minus(Wei another) {
+ Objects.requireNonNull(another);
+ return new Wei(amount.subtract(another.amount));
+ }
+
+ public Wei multiply(Long multiplier) {
+ Objects.requireNonNull(multiplier);
+ return new Wei(amount.multiply(BigInteger.valueOf(multiplier)));
+ }
+
+ public Wei multiply(Integer multiplier) {
+ Objects.requireNonNull(multiplier);
+ return new Wei(amount.multiply(BigInteger.valueOf(multiplier)));
+ }
+
+ public Wei div(Long divider) {
+ Objects.requireNonNull(divider);
+ return new Wei(amount.divide(BigInteger.valueOf(divider)));
+ }
+
+ public Wei div(Integer divider) {
+ Objects.requireNonNull(divider);
+ return new Wei(amount.divide(BigInteger.valueOf(divider)));
+ }
+
+ /**
+ * @param decimalPlaces scale of the {@code BigDecimal} value to be returned
+ * @return corresponding amount in {@link Unit#ETHER}
+ * @see #toUnits(Unit, int)
+ */
+ public BigDecimal toEthers(int decimalPlaces) {
+ return toUnits(Unit.ETHER, decimalPlaces);
+ }
+
+ /**
+ * @return corresponding amount in {@link Unit#ETHER}
+ * @see #toUnits(Unit)
+ */
+ public BigDecimal toEthers() {
+ return toUnits(Unit.ETHER);
+ }
+
+ /**
+ * @param decimalPlaces scale of the {@code BigDecimal} value to be returned
+ * @param unit target unit
+ * @return corresponding amount in custom denomination {@link Unit}
+ * @see #toUnits(Unit, int)
+ */
+ public BigDecimal toUnits(Unit unit, int decimalPlaces) {
+ return toUnits(unit).setScale(decimalPlaces, RoundingMode.HALF_UP);
+ }
+
+ /**
+ * @return corresponding amount in custom denomination {@link Unit}
+ * @param unit target unit
+ * @see #toUnits(Unit)
+ */
+ public BigDecimal toUnits(Unit unit) {
+ return new BigDecimal(amount).scaleByPowerOfTen(-unit.getScale());
+ }
+
+ @Override
+ public int compareTo(Wei o) {
+ return amount.compareTo(o.amount);
+ }
+
+ @Override
+ public final int hashCode() {
+ return Objects.hash(getClass(), amount);
+ }
+
+ @Override
+ public final boolean equals(Object obj) {
+ if (!(obj instanceof Wei)) return false;
+
+ Wei other = (Wei) obj;
+
+ return Objects.equals(amount, other.amount);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s wei", amount.toString());
+ }
+
+ public String toHex() {
+ return "0x" + amount.toString(16);
+ }
+}
diff --git a/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/json/BlockJson.java b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/json/BlockJson.java
index 2463d8e0..1d35f942 100644
--- a/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/json/BlockJson.java
+++ b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/json/BlockJson.java
@@ -2,13 +2,11 @@ package io.emeraldpay.dshackle.upstream.ethereum.json;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
-import io.emeraldpay.etherjar.domain.Address;
-import io.emeraldpay.etherjar.domain.BlockHash;
-import io.emeraldpay.etherjar.domain.Bloom;
-import io.emeraldpay.etherjar.domain.Wei;
+import io.emeraldpay.dshackle.upstream.ethereum.domain.Address;
+import io.emeraldpay.dshackle.upstream.ethereum.domain.BlockHash;
+import io.emeraldpay.dshackle.upstream.ethereum.domain.Bloom;
+import io.emeraldpay.dshackle.upstream.ethereum.domain.Wei;
import io.emeraldpay.etherjar.hex.HexData;
-import io.emeraldpay.etherjar.rpc.json.TransactionJson;
-import io.emeraldpay.etherjar.rpc.json.TransactionRefJson;
import java.io.Serializable;
import java.math.BigInteger;
diff --git a/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/json/BlockJsonDeserializer.java b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/json/BlockJsonDeserializer.java
index 63b1df62..3eb50d24 100644
--- a/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/json/BlockJsonDeserializer.java
+++ b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/json/BlockJsonDeserializer.java
@@ -21,13 +21,10 @@ import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
-import io.emeraldpay.etherjar.domain.BlockHash;
-import io.emeraldpay.etherjar.domain.Bloom;
-import io.emeraldpay.etherjar.domain.TransactionId;
+import io.emeraldpay.dshackle.upstream.ethereum.domain.BlockHash;
+import io.emeraldpay.dshackle.upstream.ethereum.domain.Bloom;
+import io.emeraldpay.dshackle.upstream.ethereum.domain.TransactionId;
import io.emeraldpay.etherjar.hex.HexData;
-import io.emeraldpay.etherjar.rpc.json.EtherJsonDeserializer;
-import io.emeraldpay.etherjar.rpc.json.TransactionJsonDeserializer;
-import io.emeraldpay.etherjar.rpc.json.TransactionRefJson;
import java.io.IOException;
import java.time.Instant;
@@ -36,7 +33,7 @@ import java.util.List;
public class BlockJsonDeserializer extends EtherJsonDeserializer> {
- private TransactionJsonDeserializer transactionJsonDeserializer = new TransactionJsonDeserializer();
+ private final TransactionJsonDeserializer transactionJsonDeserializer = new TransactionJsonDeserializer();
@Override @SuppressWarnings("unchecked")
public BlockJson deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
diff --git a/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/json/BlockJsonSerializer.java b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/json/BlockJsonSerializer.java
index e8c0e04b..03c26c45 100644
--- a/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/json/BlockJsonSerializer.java
+++ b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/json/BlockJsonSerializer.java
@@ -17,12 +17,8 @@ package io.emeraldpay.dshackle.upstream.ethereum.json;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
-import io.emeraldpay.etherjar.domain.BlockHash;
-import io.emeraldpay.etherjar.domain.Wei;
-import io.emeraldpay.etherjar.rpc.json.EtherJsonSerializer;
-import io.emeraldpay.etherjar.rpc.json.TransactionJson;
-import io.emeraldpay.etherjar.rpc.json.TransactionJsonSerializer;
-import io.emeraldpay.etherjar.rpc.json.TransactionRefJson;
+import io.emeraldpay.dshackle.upstream.ethereum.domain.BlockHash;
+import io.emeraldpay.dshackle.upstream.ethereum.domain.Wei;
import java.io.IOException;
diff --git a/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/json/EtherJsonDeserializer.java b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/json/EtherJsonDeserializer.java
new file mode 100644
index 00000000..1482b04a
--- /dev/null
+++ b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/json/EtherJsonDeserializer.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 2020 EmeraldPay Inc, All Rights Reserved.
+ * Copyright (c) 2016-2017 Infinitape Inc, All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.emeraldpay.dshackle.upstream.ethereum.json;
+
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.NumericNode;
+import io.emeraldpay.dshackle.upstream.ethereum.domain.Address;
+import io.emeraldpay.dshackle.upstream.ethereum.domain.BlockHash;
+import io.emeraldpay.dshackle.upstream.ethereum.domain.TransactionId;
+import io.emeraldpay.dshackle.upstream.ethereum.domain.Wei;
+import io.emeraldpay.etherjar.hex.HexData;
+import io.emeraldpay.etherjar.hex.HexEncoding;
+
+import java.math.BigInteger;
+
+/**
+ * Utility class for Ethereum RPC JSON deserialization
+ */
+public abstract class EtherJsonDeserializer extends JsonDeserializer {
+
+ protected String getHexString(JsonNode node) {
+ if (node == null) {
+ return null;
+ }
+ String value = node.textValue();
+ if (value == null || value.length() == 0 || value.equals("0x")) {
+ return null;
+ }
+ return value;
+ }
+
+ protected String getHexString(JsonNode node, String name) {
+ return getHexString(node.get(name));
+ }
+
+ protected HexData getData(JsonNode node, String name) {
+ String value = getHexString(node, name);
+ if (value == null) return null;
+ return HexData.from(value);
+ }
+
+ protected BigInteger getQuantity(JsonNode node, String name) {
+ return getQuantity(node.get(name));
+ }
+
+ protected BigInteger getQuantity(JsonNode node) {
+ if (node instanceof NumericNode) {
+ return BigInteger.valueOf(node.longValue());
+ }
+ String value = getHexString(node);
+ if (value == null) return null;
+ if (!value.startsWith("0x")) {
+ return new BigInteger(value, 10);
+ }
+ return HexEncoding.fromHex(value);
+ }
+
+ protected Integer getInt(JsonNode node, String name) {
+ if (!node.has(name)) {
+ return null;
+ }
+ return getInt(node.get(name));
+ }
+
+ protected Integer getInt(JsonNode node) {
+ if (node instanceof NumericNode) {
+ return node.intValue();
+ }
+ String hex = getHexString(node);
+ if (hex == null || hex.length() <= 2) {
+ return null;
+ }
+ return Integer.parseInt(hex.substring(2), 16);
+ }
+
+ protected Long getLong(JsonNode node, String name) {
+ if (!node.has(name)) {
+ return null;
+ }
+ return getLong(node.get(name));
+ }
+
+ protected Long getLong(JsonNode node) {
+ if (node instanceof NumericNode) {
+ return node.longValue();
+ }
+ String hex = getHexString(node);
+ if (hex == null || hex.length() <= 2) {
+ return null;
+ }
+ return Long.parseLong(hex.substring(2), 16);
+ }
+
+ protected Address getAddress(JsonNode node, String name) {
+ String value = getHexString(node, name);
+ if (value == null) return null;
+ return Address.from(value);
+ }
+
+ protected TransactionId getTxHash(JsonNode node, String name) {
+ String value = getHexString(node, name);
+ if (value == null) return null;
+ return TransactionId.from(value);
+ }
+
+ protected Wei getWei(JsonNode node, String name) {
+ String value = getHexString(node, name);
+ if (value == null) return null;
+ return new Wei(HexEncoding.fromHex(value));
+ }
+
+ protected BlockHash getBlockHash(JsonNode node, String name) {
+ String value = getHexString(node, name);
+ if (value == null) return null;
+ return BlockHash.from(value);
+ }
+
+ protected Boolean getBoolean(JsonNode node, String name) {
+ if (!node.has(name)) {
+ return null;
+ }
+ return node.get(name).asBoolean();
+ }
+}
diff --git a/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/json/EtherJsonSerializer.java b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/json/EtherJsonSerializer.java
new file mode 100644
index 00000000..3c170397
--- /dev/null
+++ b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/json/EtherJsonSerializer.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2016-2019 Igor Artamonov, All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.emeraldpay.dshackle.upstream.ethereum.json;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import io.emeraldpay.dshackle.upstream.ethereum.domain.Wei;
+import io.emeraldpay.etherjar.hex.HexData;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.time.Instant;
+
+public abstract class EtherJsonSerializer extends JsonSerializer {
+
+ protected void writeField(JsonGenerator gen, String name, HexData value) throws IOException {
+ if (value == null) {
+ return;
+ }
+ gen.writeStringField(name, value.toHex());
+ }
+
+ protected void writeField(JsonGenerator gen, String name, Wei value) throws IOException {
+ if (value == null) {
+ return;
+ }
+ gen.writeStringField(name, value.toHex());
+ }
+
+ protected void writeField(JsonGenerator gen, String name, BigInteger value) throws IOException {
+ if (value == null) {
+ return;
+ }
+ gen.writeStringField(name, "0x"+value.toString(16));
+ }
+
+ protected void writeField(JsonGenerator gen, String name, Integer value) throws IOException {
+ if (value == null) {
+ return;
+ }
+ gen.writeStringField(name, "0x"+ Integer.toString(value,16));
+ }
+
+ protected void writeField(JsonGenerator gen, String name, Long value) throws IOException {
+ if (value == null) {
+ return;
+ }
+ gen.writeStringField(name, "0x"+ Long.toString(value,16));
+ }
+
+ protected void writeFieldNumber(JsonGenerator gen, String name, Integer value) throws IOException {
+ if (value == null) {
+ return;
+ }
+ gen.writeNumberField(name, value);
+ }
+
+ protected void writeField(JsonGenerator gen, String name, Instant value) throws IOException {
+ if (value == null) {
+ return;
+ }
+ gen.writeStringField(name, "0x"+ Long.toString(value.toEpochMilli() / 1000L, 16));
+ }
+
+ protected void writeField(JsonGenerator gen, String name, Boolean value) throws IOException {
+ if (value == null) {
+ return;
+ }
+ gen.writeBooleanField(name, value);
+ }
+}
diff --git a/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/json/TransactionCallJson.java b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/json/TransactionCallJson.java
new file mode 100644
index 00000000..9451dab1
--- /dev/null
+++ b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/json/TransactionCallJson.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) 2020 EmeraldPay Inc, All Rights Reserved.
+ * Copyright (c) 2016-2017 Infinitape Inc, All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.emeraldpay.dshackle.upstream.ethereum.json;
+
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import io.emeraldpay.dshackle.upstream.ethereum.domain.Address;
+import io.emeraldpay.dshackle.upstream.ethereum.domain.Wei;
+import io.emeraldpay.etherjar.hex.HexData;
+
+import java.io.Serializable;
+
+@JsonSerialize(using = TransactionCallJsonSerializer.class)
+public class TransactionCallJson implements Serializable {
+
+ private Address from;
+ private Address to;
+ private Long gas;
+ private Wei gasPrice;
+ private Wei value;
+ private HexData data;
+ private Long nonce;
+
+ public TransactionCallJson() {
+ }
+
+ public TransactionCallJson(Address to, HexData data) {
+ this.to = to;
+ this.data = data;
+ }
+
+ public TransactionCallJson(Address from, Address to, Wei value) {
+ this.from = from;
+ this.to = to;
+ this.value = value;
+ }
+
+ public TransactionCallJson(Address from, Address to, Long gas, Wei value, HexData data) {
+ this.from = from;
+ this.to = to;
+ this.gas = gas;
+ this.value = value;
+ this.data = data;
+ }
+
+ public Address getFrom() {
+ return from;
+ }
+
+ public void setFrom(Address from) {
+ this.from = from;
+ }
+
+ public Address getTo() {
+ return to;
+ }
+
+ public void setTo(Address to) {
+ this.to = to;
+ }
+
+ public Long getGas() {
+ return gas;
+ }
+
+ public void setGas(Long gas) {
+ this.gas = gas;
+ }
+
+ public Wei getGasPrice() {
+ return gasPrice;
+ }
+
+ public void setGasPrice(Wei gasPrice) {
+ this.gasPrice = gasPrice;
+ }
+
+ public Wei getValue() {
+ return value;
+ }
+
+ public void setValue(Wei value) {
+ this.value = value;
+ }
+
+ public HexData getData() {
+ return data;
+ }
+
+ public void setData(HexData data) {
+ this.data = data;
+ }
+
+ public Long getNonce() {
+ return nonce;
+ }
+
+ public void setNonce(Long nonce) {
+ this.nonce = nonce;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof TransactionCallJson)) return false;
+
+ TransactionCallJson that = (TransactionCallJson) o;
+
+ if (from != null ? !from.equals(that.from) : that.from != null) return false;
+ if (!to.equals(that.to)) return false;
+ if (gas != null ? !gas.equals(that.gas) : that.gas != null) return false;
+ if (gasPrice != null ? !gasPrice.equals(that.gasPrice) : that.gasPrice != null) return false;
+ if (value != null ? !value.equals(that.value) : that.value != null) return false;
+ if (nonce != null ? !nonce.equals(that.nonce) : that.nonce != null) return false;
+ return data != null ? data.equals(that.data) : that.data == null;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = from != null ? from.hashCode() : 0;
+ result = 31 * result + to.hashCode();
+ result = 31 * result + (data != null ? data.hashCode() : 0);
+ return result;
+ }
+}
diff --git a/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/json/TransactionCallJsonSerializer.java b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/json/TransactionCallJsonSerializer.java
new file mode 100644
index 00000000..3a9b87cf
--- /dev/null
+++ b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/json/TransactionCallJsonSerializer.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2020 EmeraldPay Inc, All Rights Reserved.
+ * Copyright (c) 2016-2017 Infinitape Inc, All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.emeraldpay.dshackle.upstream.ethereum.json;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import io.emeraldpay.etherjar.hex.HexEncoding;
+
+import java.io.IOException;
+import java.math.BigInteger;
+
+public class TransactionCallJsonSerializer extends JsonSerializer {
+
+ @Override
+ public void serialize(TransactionCallJson value, JsonGenerator gen, SerializerProvider serializers)
+ throws IOException, JsonProcessingException
+ {
+ if (value == null) {
+ gen.writeNull();
+ return;
+ }
+ gen.writeStartObject();
+ if (value.getFrom() != null) {
+ gen.writeFieldName("from");
+ gen.writeString(value.getFrom().toHex());
+ }
+ if (value.getTo() != null) {
+ gen.writeFieldName("to");
+ gen.writeString(value.getTo().toHex());
+ }
+ if (value.getGas() != null) {
+ gen.writeFieldName("gas");
+ gen.writeString(HexEncoding.toHex(BigInteger.valueOf(value.getGas())));
+ }
+ if (value.getGasPrice() != null) {
+ gen.writeFieldName("gasPrice");
+ gen.writeString(HexEncoding.toHex(value.getGasPrice().getAmount()));
+ }
+ if (value.getValue() != null) {
+ gen.writeFieldName("value");
+ gen.writeString(HexEncoding.toHex(value.getValue().getAmount()));
+ }
+ if (value.getData() != null) {
+ gen.writeFieldName("data");
+ gen.writeString(value.getData().toHex());
+ }
+ if (value.getNonce() != null) {
+ gen.writeFieldName("nonce");
+ gen.writeString(HexEncoding.toHex(value.getNonce()));
+ }
+ gen.writeEndObject();
+ }
+}
diff --git a/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/json/TransactionJson.java b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/json/TransactionJson.java
new file mode 100644
index 00000000..2505ce72
--- /dev/null
+++ b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/json/TransactionJson.java
@@ -0,0 +1,334 @@
+/*
+ * Copyright (c) 2020 EmeraldPay Inc, All Rights Reserved.
+ * Copyright (c) 2016-2017 Infinitape Inc, All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.emeraldpay.dshackle.upstream.ethereum.json;
+
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import io.emeraldpay.dshackle.upstream.ethereum.domain.Address;
+import io.emeraldpay.dshackle.upstream.ethereum.domain.BlockHash;
+import io.emeraldpay.dshackle.upstream.ethereum.domain.TransactionRef;
+import io.emeraldpay.dshackle.upstream.ethereum.domain.TransactionSignature;
+import io.emeraldpay.dshackle.upstream.ethereum.domain.Wei;
+import io.emeraldpay.etherjar.hex.Hex32;
+import io.emeraldpay.etherjar.hex.HexData;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+@JsonDeserialize(using = TransactionJsonDeserializer.class)
+@JsonSerialize(using = TransactionJsonSerializer.class)
+public class TransactionJson extends TransactionRefJson implements TransactionRef, Serializable {
+
+ /**
+ * the number of transactions made by the sender prior to this one.
+ */
+ private Long nonce;
+
+ /**
+ * hash of the block where this transaction was in. null when its pending.
+ */
+ private BlockHash blockHash;
+
+ /**
+ * block number where this transaction was in. null when its pending.
+ */
+ private Long blockNumber;
+
+ /**
+ * position in the block. null when its pending.
+ */
+ private Long transactionIndex;
+
+ /**
+ * address of the sender.
+ */
+ private Address from;
+
+ /**
+ * address of the receiver. null when its a contract creation transaction.
+ */
+ private Address to;
+
+ /**
+ * Address of a contract created from that transaction
+ */
+ private Address creates;
+
+ /**
+ * value transferred in Wei.
+ */
+ private Wei value;
+
+ /**
+ * gas price provided by the sender in Wei.
+ */
+ private Wei gasPrice;
+ private Wei maxFeePerGas;
+ private Wei maxPriorityFeePerGas;
+
+ /**
+ * gas provided by the sender.
+ */
+ private Long gas;
+
+ /**
+ * the data send along with the transaction.
+ */
+ private HexData input;
+
+ private TransactionSignature signature;
+
+ /**
+ * Transaction type
+ * @see EIP-2718: Typed Transaction Envelope
+ */
+ private int type = 0;
+
+ private Integer chainId;
+
+ private List accessList;
+
+ public Long getNonce() {
+ return nonce;
+ }
+
+ public void setNonce(Long nonce) {
+ this.nonce = nonce;
+ }
+
+ public BlockHash getBlockHash() {
+ return blockHash;
+ }
+
+ public void setBlockHash(BlockHash blockHash) {
+ this.blockHash = blockHash;
+ }
+
+ public Long getBlockNumber() {
+ return blockNumber;
+ }
+
+ public void setBlockNumber(Long blockNumber) {
+ this.blockNumber = blockNumber;
+ }
+
+ public Long getTransactionIndex() {
+ return transactionIndex;
+ }
+
+ public void setTransactionIndex(Long transactionIndex) {
+ this.transactionIndex = transactionIndex;
+ }
+
+ public Address getFrom() {
+ return from;
+ }
+
+ public void setFrom(Address from) {
+ this.from = from;
+ }
+
+ public Address getTo() {
+ return to;
+ }
+
+ public void setTo(Address to) {
+ this.to = to;
+ }
+
+ public Wei getValue() {
+ return value;
+ }
+
+ public void setValue(Wei value) {
+ this.value = value;
+ }
+
+ public Wei getGasPrice() {
+ return gasPrice;
+ }
+
+ public void setGasPrice(Wei gasPrice) {
+ this.gasPrice = gasPrice;
+ }
+
+ public Wei getMaxFeePerGas() {
+ return maxFeePerGas;
+ }
+
+ public void setMaxFeePerGas(Wei maxFeePerGas) {
+ this.maxFeePerGas = maxFeePerGas;
+ }
+
+ public Wei getMaxPriorityFeePerGas() {
+ return maxPriorityFeePerGas;
+ }
+
+ public void setMaxPriorityFeePerGas(Wei maxPriorityFeePerGas) {
+ this.maxPriorityFeePerGas = maxPriorityFeePerGas;
+ }
+
+ public Integer getChainId() {
+ return chainId;
+ }
+
+ public void setChainId(Integer chainId) {
+ this.chainId = chainId;
+ }
+
+ public Long getGas() {
+ return gas;
+ }
+
+ public void setGas(Long gas) {
+ this.gas = gas;
+ }
+
+ public HexData getInput() {
+ return input;
+ }
+
+ public void setInput(HexData input) {
+ this.input = input;
+ }
+
+ public TransactionSignature getSignature() {
+ return signature;
+ }
+
+ public void setSignature(TransactionSignature signature) {
+ this.signature = signature;
+ }
+
+ public Address getCreates() {
+ return creates;
+ }
+
+ public void setCreates(Address creates) {
+ this.creates = creates;
+ }
+
+ public int getType() {
+ return type;
+ }
+
+ public void setType(int type) {
+ this.type = type;
+ }
+
+ public List getAccessList() {
+ return accessList;
+ }
+
+ public void setAccessList(List accessList) {
+ this.accessList = accessList;
+ }
+
+ public void addAccess(Access access) {
+ if (this.accessList == null) {
+ this.accessList = new ArrayList<>();
+ }
+ accessList.add(access);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof TransactionJson)) return false;
+
+ TransactionJson that = (TransactionJson) o;
+
+ if (!Objects.equals(getHash(), that.getHash())) return false;
+ if (!Objects.equals(nonce, that.nonce)) return false;
+ if (!Objects.equals(blockHash, that.blockHash)) return false;
+ if (!Objects.equals(blockNumber, that.blockNumber)) return false;
+ if (!Objects.equals(transactionIndex, that.transactionIndex)) return false;
+ if (!Objects.equals(from, that.from)) return false;
+ if (!Objects.equals(to, that.to)) return false;
+ if (!Objects.equals(value, that.value)) return false;
+ if (!Objects.equals(gasPrice, that.gasPrice)) return false;
+ if (!Objects.equals(gas, that.gas)) return false;
+ if (!Objects.equals(input, that.input)) return false;
+ if (!Objects.equals(creates, that.creates)) return false;
+ if (type != that.type) return false;
+ if (!Objects.equals(maxFeePerGas, that.maxFeePerGas)) return false;
+ if (!Objects.equals(maxPriorityFeePerGas, that.maxPriorityFeePerGas)) return false;
+ if (!Objects.equals(accessList, that.accessList)) return false;
+ return Objects.equals(signature, that.signature);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = super.hashCode();
+ result = 31 * result + type;
+ result = 31 * result + (from != null ? from.hashCode() : 0);
+ result = 31 * result + (blockHash != null ? blockHash.hashCode() : 0);
+ return result;
+ }
+
+ public static class Access {
+ private Address address;
+ private List storageKeys;
+
+ public Access() {
+ storageKeys = Collections.emptyList();
+ }
+
+ public Access(Address address) {
+ this();
+ this.address = address;
+ }
+
+ public Access(Address address, List storageKeys) {
+ this.address = address;
+ this.storageKeys = storageKeys;
+ }
+
+ public Address getAddress() {
+ return address;
+ }
+
+ public void setAddress(Address address) {
+ this.address = address;
+ }
+
+ public List getStorageKeys() {
+ return storageKeys;
+ }
+
+ public void setStorageKeys(List storageKeys) {
+ this.storageKeys = storageKeys;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof Access)) return false;
+ Access that = (Access) o;
+ return Objects.equals(address, that.address) && Objects.equals(storageKeys, that.storageKeys);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(address);
+ }
+ }
+}
diff --git a/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/json/TransactionJsonDeserializer.java b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/json/TransactionJsonDeserializer.java
new file mode 100644
index 00000000..7f3bc27b
--- /dev/null
+++ b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/json/TransactionJsonDeserializer.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2020 EmeraldPay Inc, All Rights Reserved.
+ * Copyright (c) 2016-2017 Infinitape Inc, All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.emeraldpay.dshackle.upstream.ethereum.json;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonNode;
+import io.emeraldpay.dshackle.upstream.ethereum.domain.Address;
+import io.emeraldpay.dshackle.upstream.ethereum.domain.ChainId;
+import io.emeraldpay.dshackle.upstream.ethereum.domain.TransactionSignature;
+import io.emeraldpay.etherjar.hex.Hex32;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class TransactionJsonDeserializer extends EtherJsonDeserializer {
+
+ @Override
+ public TransactionJson deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
+ JsonNode node = jp.readValueAsTree();
+ return deserialize(node);
+ }
+
+ public TransactionJson deserialize(JsonNode node) {
+ TransactionJson tx = new TransactionJson();
+ tx.setHash(getTxHash(node, "hash"));
+ tx.setNonce(getLong(node, "nonce"));
+ tx.setBlockHash(getBlockHash(node, "blockHash"));
+ Integer type = getInt(node, "type");
+ if (type != null) {
+ tx.setType(type);
+ }
+ Integer chainId = getInt(node, "chainId");
+ if (chainId != null) {
+ tx.setChainId(chainId);
+ }
+ Long blockNumber = getLong(node, "blockNumber");
+ if (blockNumber != null) {
+ tx.setBlockNumber(blockNumber);
+ }
+ Long txIndex = getLong(node, "transactionIndex");
+ if (txIndex != null) {
+ tx.setTransactionIndex(txIndex);
+ }
+ tx.setFrom(getAddress(node, "from"));
+ tx.setTo(getAddress(node, "to"));
+ tx.setValue(getWei(node, "value"));
+ tx.setGasPrice(getWei(node, "gasPrice"));
+ tx.setMaxFeePerGas(getWei(node, "maxFeePerGas"));
+ tx.setMaxPriorityFeePerGas(getWei(node, "maxPriorityFeePerGas"));
+ tx.setGas(getLong(node, "gas"));
+ tx.setInput(getData(node, "input"));
+
+ if (node.has("r") && node.has("v") && node.has("s")) {
+ TransactionSignature signature = new TransactionSignature();
+
+ if (node.hasNonNull("networkId")) {
+ signature.setChainId(new ChainId(node.get("networkId").intValue()));
+ }
+ signature.setR(getData(node, "r"));
+ signature.setS(getData(node, "s"));
+ signature.setV(getLong(node, "v"));
+ signature.setPublicKey(getData(node, "publicKey"));
+
+ tx.setSignature(signature);
+ }
+
+ if (node.has("accessList")) {
+ List accessList = new ArrayList<>();
+ for (JsonNode access: node.get("accessList")) {
+ Address address = getAddress(access, "address");
+ if (access.has("storageKeys")) {
+ List storageKeys = new ArrayList<>();
+ for (JsonNode storageKey: access.get("storageKeys")) {
+ storageKeys.add(Hex32.from(storageKey.textValue()));
+ }
+ accessList.add(new TransactionJson.Access(address, storageKeys));
+ } else {
+ accessList.add(new TransactionJson.Access(address));
+ }
+ }
+ tx.setAccessList(accessList);
+ }
+
+ return tx;
+ }
+}
diff --git a/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/json/TransactionJsonSerializer.java b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/json/TransactionJsonSerializer.java
new file mode 100644
index 00000000..4f37ff73
--- /dev/null
+++ b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/json/TransactionJsonSerializer.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2016-2019 Igor Artamonov, All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.emeraldpay.dshackle.upstream.ethereum.json;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import io.emeraldpay.dshackle.upstream.ethereum.domain.TransactionSignature;
+import io.emeraldpay.etherjar.hex.Hex32;
+
+import java.io.IOException;
+import java.util.List;
+
+public class TransactionJsonSerializer extends EtherJsonSerializer {
+ @Override
+ public void serialize(TransactionJson value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
+ gen.writeStartObject();
+ writeField(gen, "hash", value.getHash());
+ writeField(gen, "nonce", value.getNonce());
+ writeField(gen, "blockHash", value.getBlockHash());
+ writeField(gen, "blockNumber", value.getBlockNumber());
+ if (value.getType() != 0) {
+ writeField(gen, "type", value.getType());
+ }
+ writeField(gen, "chainId", value.getChainId());
+ writeField(gen, "maxFeePerGas", value.getMaxFeePerGas());
+ writeField(gen, "maxPriorityFeePerGas", value.getMaxPriorityFeePerGas());
+ if (value.getTransactionIndex() != null) {
+ writeField(gen, "transactionIndex", value.getTransactionIndex().intValue());
+ }
+ writeField(gen, "from", value.getFrom());
+ if (value.getTo() == null) {
+ gen.writeNullField("to");
+ } else {
+ writeField(gen, "to", value.getTo());
+ }
+ writeField(gen, "creates", value.getCreates());
+ writeField(gen, "value", value.getValue());
+ writeField(gen, "gasPrice", value.getGasPrice());
+ writeField(gen, "gas", value.getGas());
+ writeField(gen, "input", value.getInput());
+ TransactionSignature signature = value.getSignature();
+ if (signature != null) {
+ if (signature.getChainId() != null) {
+ writeField(gen, "networkId", signature.getChainId().getValue());
+ }
+ writeField(gen, "r", signature.getR());
+ writeField(gen, "s", signature.getS());
+ if (signature.getV() != null) {
+ writeField(gen, "v", signature.getV().longValue());
+ }
+ writeField(gen, "publicKey", signature.getPublicKey());
+ }
+ List accessList = value.getAccessList();
+ if (accessList != null) {
+ gen.writeFieldName("accessList");
+ gen.writeStartArray();
+ for (TransactionJson.Access access: accessList) {
+ gen.writeStartObject();
+ writeField(gen, "address", access.getAddress());
+ gen.writeFieldName("storageKeys");
+ gen.writeStartArray();
+ List storageKeys = access.getStorageKeys();
+ if (storageKeys != null) {
+ for (Hex32 key : storageKeys) {
+ gen.writeString(key.toHex());
+ }
+ }
+ gen.writeEndArray();
+ gen.writeEndObject();
+ }
+ gen.writeEndArray();
+ }
+ gen.writeEndObject();
+ }
+}
diff --git a/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/json/TransactionJsonSnapshot.java b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/json/TransactionJsonSnapshot.java
index 2ea55976..afb31b19 100644
--- a/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/json/TransactionJsonSnapshot.java
+++ b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/json/TransactionJsonSnapshot.java
@@ -1,10 +1,9 @@
package io.emeraldpay.dshackle.upstream.ethereum.json;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
-import io.emeraldpay.etherjar.domain.BlockHash;
-import io.emeraldpay.etherjar.domain.TransactionRef;
-import io.emeraldpay.etherjar.domain.Wei;
-import io.emeraldpay.etherjar.rpc.json.TransactionRefJson;
+import io.emeraldpay.dshackle.upstream.ethereum.domain.BlockHash;
+import io.emeraldpay.dshackle.upstream.ethereum.domain.TransactionRef;
+import io.emeraldpay.dshackle.upstream.ethereum.domain.Wei;
import java.io.Serializable;
diff --git a/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/json/TransactionJsonSnapshotDeserializer.java b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/json/TransactionJsonSnapshotDeserializer.java
index 36add7d1..a833d7bf 100644
--- a/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/json/TransactionJsonSnapshotDeserializer.java
+++ b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/json/TransactionJsonSnapshotDeserializer.java
@@ -4,7 +4,6 @@ import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
-import io.emeraldpay.etherjar.rpc.json.EtherJsonDeserializer;
import java.io.IOException;
diff --git a/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/json/TransactionLogJson.java b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/json/TransactionLogJson.java
new file mode 100644
index 00000000..365a7eba
--- /dev/null
+++ b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/json/TransactionLogJson.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (c) 2020 EmeraldPay Inc, All Rights Reserved.
+ * Copyright (c) 2016-2017 Infinitape Inc, All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.emeraldpay.dshackle.upstream.ethereum.json;
+
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import io.emeraldpay.dshackle.upstream.ethereum.domain.Address;
+import io.emeraldpay.dshackle.upstream.ethereum.domain.BlockHash;
+import io.emeraldpay.dshackle.upstream.ethereum.domain.TransactionId;
+import io.emeraldpay.dshackle.upstream.ethereum.domain.TransactionRef;
+import io.emeraldpay.etherjar.hex.Hex32;
+import io.emeraldpay.etherjar.hex.HexData;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.Objects;
+
+@JsonDeserialize(using = TransactionLogJsonDeserializer.class)
+@JsonSerialize(using = TransactionLogJsonSerializer.class)
+public class TransactionLogJson implements TransactionRef, Serializable {
+
+ /**
+ * true when the log was removed, due to a chain reorganization. false if its a valid log.
+ */
+ private Boolean removed;
+
+ /**
+ * log index position in the block. null when its pending log.
+ */
+ private Long logIndex;
+
+ /**
+ * transactions index position log was created from. null when its pending log.
+ */
+ private Long transactionIndex;
+
+ /**
+ * hash of the transactions this log was created from. null when its pending log.
+ */
+ private TransactionId transactionHash;
+
+ /**
+ * hash of the block where this log was in. null when its pending. null when its pending log.
+ */
+ private BlockHash blockHash;
+
+ /**
+ * the block number where this log was in. null when its pending. null when its pending log.
+ */
+ private Long blockNumber;
+
+ /**
+ * address from which this log originated.
+ */
+ private Address address;
+
+ /**
+ * contains one or more 32 Bytes non-indexed arguments of the log.
+ */
+ private HexData data;
+
+ /**
+ * Array of 0 to 4 32 Bytes DATA of indexed log arguments.
+ *
+ * In solidity: The first topic is the hash of the signature of the event (e.g. Deposit(address,bytes32,uint256)),
+ * except you declared the event with the anonymous specifier.
+ * @see io.emeraldpay.etherjar.domain.EventId
+ */
+ private List topics;
+
+ public Boolean getRemoved() {
+ return removed;
+ }
+
+ public void setRemoved(Boolean removed) {
+ this.removed = removed;
+ }
+
+ public Long getLogIndex() {
+ return logIndex;
+ }
+
+ public void setLogIndex(Long logIndex) {
+ this.logIndex = logIndex;
+ }
+
+ public Long getTransactionIndex() {
+ return transactionIndex;
+ }
+
+ public void setTransactionIndex(Long transactionIndex) {
+ this.transactionIndex = transactionIndex;
+ }
+
+ public TransactionId getTransactionHash() {
+ return transactionHash;
+ }
+
+ public void setTransactionHash(TransactionId transactionHash) {
+ this.transactionHash = transactionHash;
+ }
+
+ public BlockHash getBlockHash() {
+ return blockHash;
+ }
+
+ public void setBlockHash(BlockHash blockHash) {
+ this.blockHash = blockHash;
+ }
+
+ public Long getBlockNumber() {
+ return blockNumber;
+ }
+
+ public void setBlockNumber(Long blockNumber) {
+ this.blockNumber = blockNumber;
+ }
+
+ public Address getAddress() {
+ return address;
+ }
+
+ public void setAddress(Address address) {
+ this.address = address;
+ }
+
+ public HexData getData() {
+ return data;
+ }
+
+ public void setData(HexData data) {
+ this.data = data;
+ }
+
+ public List getTopics() {
+ return topics;
+ }
+
+ public void setTopics(List topics) {
+ this.topics = topics;
+ }
+
+ @Override
+ public TransactionId getHash() {
+ return transactionHash;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof TransactionLogJson)) return false;
+
+ TransactionLogJson that = (TransactionLogJson) o;
+
+ if (!Objects.equals(removed, that.removed)) return false;
+ if (!Objects.equals(logIndex, that.logIndex)) return false;
+ if (!Objects.equals(transactionIndex, that.transactionIndex))
+ return false;
+ if (!Objects.equals(transactionHash, that.transactionHash))
+ return false;
+ if (!Objects.equals(blockHash, that.blockHash)) return false;
+ if (!Objects.equals(blockNumber, that.blockNumber)) return false;
+ if (!Objects.equals(address, that.address)) return false;
+ if (!Objects.equals(data, that.data)) return false;
+ return Objects.equals(topics, that.topics);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = logIndex != null ? logIndex.hashCode() : 0;
+ result = 31 * result + (transactionHash != null ? transactionHash.hashCode() : 0);
+ result = 31 * result + (blockHash != null ? blockHash.hashCode() : 0);
+ return result;
+ }
+}
diff --git a/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/json/TransactionLogJsonDeserializer.java b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/json/TransactionLogJsonDeserializer.java
new file mode 100644
index 00000000..b43ca964
--- /dev/null
+++ b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/json/TransactionLogJsonDeserializer.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2020 EmeraldPay Inc, All Rights Reserved.
+ * Copyright (c) 2016-2017 Infinitape Inc, All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.emeraldpay.dshackle.upstream.ethereum.json;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonNode;
+import io.emeraldpay.etherjar.hex.Hex32;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class TransactionLogJsonDeserializer extends EtherJsonDeserializer {
+
+ @Override
+ public TransactionLogJson deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
+ JsonNode node = jp.readValueAsTree();
+ return deserialize(node);
+ }
+
+ public TransactionLogJson deserialize(JsonNode node) {
+ TransactionLogJson log = new TransactionLogJson();
+
+ log.setAddress(getAddress(node, "address"));
+ log.setBlockHash(getBlockHash(node, "blockHash"));
+ log.setBlockNumber(getLong(node, "blockNumber"));
+ log.setData(getData(node, "data"));
+ log.setLogIndex(getLong(node, "logIndex"));
+ List topics = new ArrayList<>();
+ for (JsonNode topic: node.get("topics")) {
+ topics.add(Hex32.from(topic.textValue()));
+ }
+ log.setTopics(topics);
+ log.setTransactionHash(getTxHash(node, "transactionHash"));
+ log.setTransactionIndex(getLong(node, "transactionIndex"));
+ log.setRemoved(getBoolean(node, "removed"));
+
+ return log;
+ }
+}
diff --git a/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/json/TransactionLogJsonSerializer.java b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/json/TransactionLogJsonSerializer.java
new file mode 100644
index 00000000..d29b8a72
--- /dev/null
+++ b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/json/TransactionLogJsonSerializer.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2021 EmeraldPay Inc, All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.emeraldpay.dshackle.upstream.ethereum.json;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import io.emeraldpay.etherjar.hex.HexData;
+
+import java.io.IOException;
+
+public class TransactionLogJsonSerializer extends EtherJsonSerializer {
+
+
+ @Override
+ public void serialize(TransactionLogJson value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
+ gen.writeStartObject();
+ writeField(gen, "address", value.getAddress());
+ writeField(gen, "blockHash", value.getBlockHash());
+ writeField(gen, "blockNumber", value.getBlockNumber());
+ writeField(gen, "data", value.getData());
+ writeField(gen, "logIndex", value.getLogIndex());
+
+ gen.writeFieldName("topics");
+ gen.writeStartArray();
+ if (value.getTopics() != null) {
+ for (HexData topic : value.getTopics()) {
+ gen.writeString(topic.toString());
+ }
+ }
+ gen.writeEndArray();
+
+ writeField(gen, "transactionHash", value.getHash());
+ writeField(gen, "transactionIndex", value.getTransactionIndex());
+ writeField(gen, "removed", value.getRemoved());
+ gen.writeEndObject();
+ }
+}
diff --git a/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/json/TransactionReceiptJson.java b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/json/TransactionReceiptJson.java
new file mode 100644
index 00000000..2279d674
--- /dev/null
+++ b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/json/TransactionReceiptJson.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright (c) 2020 EmeraldPay Inc, All Rights Reserved.
+ * Copyright (c) 2016-2017 Infinitape Inc, All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.emeraldpay.dshackle.upstream.ethereum.json;
+
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import io.emeraldpay.dshackle.upstream.ethereum.domain.Address;
+import io.emeraldpay.dshackle.upstream.ethereum.domain.BlockHash;
+import io.emeraldpay.dshackle.upstream.ethereum.domain.Bloom;
+import io.emeraldpay.dshackle.upstream.ethereum.domain.TransactionId;
+import io.emeraldpay.dshackle.upstream.ethereum.domain.TransactionRef;
+import io.emeraldpay.dshackle.upstream.ethereum.domain.Wei;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.Objects;
+
+@JsonDeserialize(using = TransactionReceiptJsonDeserializer.class)
+@JsonSerialize(using = TransactionReceiptJsonSerializer.class)
+public class TransactionReceiptJson implements TransactionRef, Serializable {
+
+ /**
+ * hash of the transaction
+ */
+ private TransactionId transactionHash;
+
+ /**
+ * position in the block
+ */
+ private Long transactionIndex;
+
+ /**
+ * hash of the block where this transaction was in.
+ */
+ private BlockHash blockHash;
+
+ /**
+ * block number where this transaction was in
+ */
+ private Long blockNumber;
+
+ /**
+ * total amount of gas used when this transaction was executed in the block.
+ */
+ private Long cumulativeGasUsed;
+
+ /**
+ * Sender
+ */
+ private Address from;
+
+ /**
+ * Target address
+ */
+ private Address to;
+
+ /**
+ * amount of gas used by this specific transaction alone.
+ */
+ private Long gasUsed;
+
+ /**
+ * The contract address created, if the transaction was a contract creation, otherwise null.
+ */
+ private Address contractAddress;
+
+ /**
+ * Array of log objects, which this transaction generated.
+ */
+ private List logs;
+
+ private Bloom logsBloom;
+
+ /**
+ * Optinal tx status. 0 if failed, 1 if successfull
+ */
+ private Integer status;
+
+ private Wei effectiveGasPrice;
+
+ /**
+ * Transaction type
+ * @see EIP-2718: Typed Transaction Envelope
+ */
+ private int type = 0;
+
+ public TransactionId getTransactionHash() {
+ return transactionHash;
+ }
+
+ public void setTransactionHash(TransactionId transactionHash) {
+ this.transactionHash = transactionHash;
+ }
+
+ public Long getTransactionIndex() {
+ return transactionIndex;
+ }
+
+ public void setTransactionIndex(Long transactionIndex) {
+ this.transactionIndex = transactionIndex;
+ }
+
+ public BlockHash getBlockHash() {
+ return blockHash;
+ }
+
+ public void setBlockHash(BlockHash blockHash) {
+ this.blockHash = blockHash;
+ }
+
+ public Long getBlockNumber() {
+ return blockNumber;
+ }
+
+ public void setBlockNumber(Long blockNumber) {
+ this.blockNumber = blockNumber;
+ }
+
+ public Long getCumulativeGasUsed() {
+ return cumulativeGasUsed;
+ }
+
+ public void setCumulativeGasUsed(Long cumulativeGasUsed) {
+ this.cumulativeGasUsed = cumulativeGasUsed;
+ }
+
+ public Address getFrom() {
+ return from;
+ }
+
+ public void setFrom(Address from) {
+ this.from = from;
+ }
+
+ public Address getTo() {
+ return to;
+ }
+
+ public void setTo(Address to) {
+ this.to = to;
+ }
+
+ public Long getGasUsed() {
+ return gasUsed;
+ }
+
+ public void setGasUsed(Long gasUsed) {
+ this.gasUsed = gasUsed;
+ }
+
+ public Address getContractAddress() {
+ return contractAddress;
+ }
+
+ public void setContractAddress(Address contractAddress) {
+ this.contractAddress = contractAddress;
+ }
+
+ public List getLogs() {
+ return logs;
+ }
+
+ public void setLogs(List logs) {
+ this.logs = logs;
+ }
+
+ public Bloom getLogsBloom() {
+ return logsBloom;
+ }
+
+ public void setLogsBloom(Bloom logsBloom) {
+ this.logsBloom = logsBloom;
+ }
+
+ public Integer getStatus() {
+ return status;
+ }
+
+ public void setStatus(Integer status) {
+ this.status = status;
+ }
+
+ public Wei getEffectiveGasPrice() {
+ return effectiveGasPrice;
+ }
+
+ public void setEffectiveGasPrice(Wei effectiveGasPrice) {
+ this.effectiveGasPrice = effectiveGasPrice;
+ }
+
+ public int getType() {
+ return type;
+ }
+
+ public void setType(int type) {
+ this.type = type;
+ }
+
+ @Override
+ public TransactionId getHash() {
+ return transactionHash;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof TransactionReceiptJson)) return false;
+
+ TransactionReceiptJson that = (TransactionReceiptJson) o;
+
+ if (!Objects.equals(transactionHash, that.transactionHash)) return false;
+ if (!Objects.equals(transactionIndex, that.transactionIndex)) return false;
+ if (!Objects.equals(from, that.from)) return false;
+ if (!Objects.equals(to, that.to)) return false;
+ if (!Objects.equals(blockHash, that.blockHash)) return false;
+ if (!Objects.equals(blockNumber, that.blockNumber)) return false;
+ if (!Objects.equals(cumulativeGasUsed, that.cumulativeGasUsed)) return false;
+ if (!Objects.equals(gasUsed, that.gasUsed)) return false;
+ if (!Objects.equals(contractAddress, that.contractAddress)) return false;
+ if (!Objects.equals(logsBloom, that.logsBloom)) return false;
+ return Objects.equals(logs, that.logs);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = transactionHash != null ? transactionHash.hashCode() : 0;
+ result = 31 * result + (blockHash != null ? blockHash.hashCode() : 0);
+ return result;
+ }
+}
diff --git a/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/json/TransactionReceiptJsonDeserializer.java b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/json/TransactionReceiptJsonDeserializer.java
new file mode 100644
index 00000000..7ab3f117
--- /dev/null
+++ b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/json/TransactionReceiptJsonDeserializer.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2020 EmeraldPay Inc, All Rights Reserved.
+ * Copyright (c) 2016-2017 Infinitape Inc, All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.emeraldpay.dshackle.upstream.ethereum.json;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonNode;
+import io.emeraldpay.dshackle.upstream.ethereum.domain.Bloom;
+import io.emeraldpay.etherjar.hex.HexData;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class TransactionReceiptJsonDeserializer extends EtherJsonDeserializer {
+
+ private TransactionLogJsonDeserializer transactionLogJsonDeserializer = new TransactionLogJsonDeserializer();
+
+ @Override
+ public TransactionReceiptJson deserialize(JsonParser jp, DeserializationContext ctxt)
+ throws IOException, JsonProcessingException {
+ JsonNode node = jp.readValueAsTree();
+ TransactionReceiptJson receipt = new TransactionReceiptJson();
+
+ receipt.setBlockHash(getBlockHash(node, "blockHash"));
+ receipt.setBlockNumber(getLong(node, "blockNumber"));
+ receipt.setContractAddress(getAddress(node, "contractAddress"));
+ receipt.setFrom(getAddress(node, "from"));
+ receipt.setTo(getAddress(node, "to"));
+ receipt.setCumulativeGasUsed(getLong(node, "cumulativeGasUsed"));
+ receipt.setGasUsed(getLong(node, "gasUsed"));
+ receipt.setEffectiveGasPrice(getWei(node, "effectiveGasPrice"));
+ receipt.setTransactionHash(getTxHash(node, "transactionHash"));
+ receipt.setTransactionIndex(getLong(node, "transactionIndex"));
+ HexData logsBloom = getData(node, "logsBloom");
+ if (logsBloom != null) {
+ receipt.setLogsBloom(Bloom.from(logsBloom));
+ }
+
+ List logs = new ArrayList<>();
+ if (node.hasNonNull("logs")) {
+ for (JsonNode log: node.get("logs")) {
+ logs.add(transactionLogJsonDeserializer.deserialize(log));
+ }
+ }
+ receipt.setLogs(logs);
+
+ Integer status = getInt(node, "status");
+ if (status != null) {
+ receipt.setStatus(status);
+ }
+ Integer type = getInt(node, "type");
+ if (type != null) {
+ receipt.setType(type);
+ }
+
+ return receipt;
+ }
+}
diff --git a/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/json/TransactionReceiptJsonSerializer.java b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/json/TransactionReceiptJsonSerializer.java
new file mode 100644
index 00000000..2986a941
--- /dev/null
+++ b/src/main/java/io/emeraldpay/dshackle/upstream/ethereum/json/TransactionReceiptJsonSerializer.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2021 EmeraldPay Inc, All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.emeraldpay.dshackle.upstream.ethereum.json;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+
+import java.io.IOException;
+
+public class TransactionReceiptJsonSerializer extends EtherJsonSerializer {
+
+ @Override
+ public void serialize(TransactionReceiptJson value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
+
+ gen.writeStartObject();
+ writeField(gen, "blockHash", value.getBlockHash());
+ writeField(gen, "blockNumber", value.getBlockNumber());
+ writeField(gen, "contractAddress", value.getContractAddress());
+ writeField(gen, "from", value.getFrom());
+ writeField(gen, "to", value.getTo());
+ writeField(gen, "cumulativeGasUsed", value.getCumulativeGasUsed());
+ writeField(gen, "gasUsed", value.getGasUsed());
+ writeField(gen, "effectiveGasPrice", value.getEffectiveGasPrice());
+ writeField(gen, "transactionHash", value.getTransactionHash());
+ writeField(gen, "transactionIndex", value.getTransactionIndex());
+ writeField(gen, "logsBloom", value.getLogsBloom());
+
+ gen.writeFieldName("logs");
+ gen.writeStartArray();
+ if (value.getLogs() != null) {
+ JsonSerializer