Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Stellar support #8

Merged
merged 3 commits into from
Aug 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package onlydust.com.marketplace.kernel.model.blockchain;

import lombok.EqualsAndHashCode;
import lombok.NonNull;

import java.util.regex.Pattern;

import static onlydust.com.marketplace.kernel.exception.OnlyDustException.badRequest;

@EqualsAndHashCode(callSuper = true)
public abstract class Base32Hash extends Hash {
private static final Pattern BASE32_PATTERN = Pattern.compile("[A-Z2-7]+");

protected Base32Hash(final int maxSize, final @NonNull String hash) {
super(maxSize, hash);

if (!BASE32_PATTERN.matcher(hash).matches())
throw badRequest("Provided hash is not base 32");
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
package onlydust.com.marketplace.kernel.model.blockchain;

public enum Blockchain {
ETHEREUM, OPTIMISM, STARKNET, APTOS;
ETHEREUM, OPTIMISM, STARKNET, APTOS, STELLAR;

public String pretty() {
return switch (this) {
case ETHEREUM -> "Ethereum";
case OPTIMISM -> "Optimism";
case STARKNET -> "StarkNet";
case APTOS -> "Aptos";
case STELLAR -> "Stellar";
};
}
}
Original file line number Diff line number Diff line change
@@ -1,58 +1,26 @@
package onlydust.com.marketplace.kernel.model.blockchain;

import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.NonNull;

import java.util.regex.Pattern;

import static onlydust.com.marketplace.kernel.exception.OnlyDustException.badRequest;

@EqualsAndHashCode
public abstract class Hash {
final @NonNull String inner;

protected Hash(final int maxByteCount, final @NonNull String hash) {
final var validator = Validator.of(maxByteCount);
validator.check(hash);
private final String inner;

this.inner = validator.sanitize(hash);
}
protected Hash(final int maxSize, final @NonNull String hash) {
if (hash.isEmpty())
throw badRequest("Provided hash should not be empty");

@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof Hash hash))
return false;
if (hash.length() > maxSize)
throw badRequest("Provided hash should be less than %d characters".formatted(maxSize));

return inner.equalsIgnoreCase(hash.inner);
}

@Override
public int hashCode() {
return inner.hashCode();
this.inner = hash;
}

@Override
public String toString() {
return inner;
}

@AllArgsConstructor(staticName = "of")
@EqualsAndHashCode
private static class Validator {
private static final Pattern HEX_PATTERN = Pattern.compile("^0[xX][a-fA-F0-9]+$");
private final int maxByteCount;

public void check(final @NonNull String hash) {
if (!HEX_PATTERN.matcher(hash).matches()) throw badRequest("Provided hash is not hexadecimal");
if (hash.length() < 3) throw badRequest("Provided hash is too short");
if (hash.length() > maxByteCount * 2 + 2)
throw badRequest("Provided hash should be less than %d bytes".formatted(maxByteCount));
}

public String sanitize(String hash) {
return "0x" + (hash.length() % 2 == 0 ? "" : "0") + hash.substring(2);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package onlydust.com.marketplace.kernel.model.blockchain;

import lombok.EqualsAndHashCode;
import lombok.NonNull;

import java.util.regex.Pattern;

import static onlydust.com.marketplace.kernel.exception.OnlyDustException.badRequest;

@EqualsAndHashCode(callSuper = true)
public abstract class HexHash extends Hash {
private static final Pattern HEX_PATTERN = Pattern.compile("[0-9a-fA-F]+");

protected HexHash(final int maxByteCount, final @NonNull String hash) {
super(maxByteCount * 2, sanitize(hash));

if (!HEX_PATTERN.matcher(hash).matches())
throw badRequest("Provided hash is not hexadecimal");
}

private static String sanitize(final @NonNull String hash) {
return (hash.length() % 2 == 0 ? "" : "0") + hash;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package onlydust.com.marketplace.kernel.model.blockchain;

import lombok.EqualsAndHashCode;
import lombok.NonNull;

import static java.lang.Math.min;
import static onlydust.com.marketplace.kernel.exception.OnlyDustException.badRequest;

@EqualsAndHashCode(callSuper = true)
public abstract class PrefixedHexHash extends HexHash {
private final String prefix;

protected PrefixedHexHash(final int maxByteCount, final @NonNull String hash) {
this(maxByteCount, "0x", hash);
}

protected PrefixedHexHash(final int maxByteCount, final @NonNull String prefix, final @NonNull String hash) {
super(maxByteCount, hash.substring(min(hash.length(), prefix.length())));
this.prefix = prefix;

final var hashPrefix = hash.substring(0, prefix.length());
if (!hashPrefix.equalsIgnoreCase(prefix))
throw badRequest("Provided hash should start with %s".formatted(prefix));
}

@Override
public String toString() {
return prefix + super.toString();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package onlydust.com.marketplace.kernel.model.blockchain;

import onlydust.com.marketplace.kernel.model.blockchain.stellar.StellarAccountId;
import onlydust.com.marketplace.kernel.model.blockchain.stellar.StellarContractAddress;
import onlydust.com.marketplace.kernel.model.blockchain.stellar.StellarExpert;
import onlydust.com.marketplace.kernel.model.blockchain.stellar.StellarTransaction;

public interface Stellar {
BlockExplorer<StellarTransaction.Hash> BLOCK_EXPLORER = new StellarExpert();

static StellarTransaction.Hash transactionHash(String value) {
return new StellarTransaction.Hash(value);
}

static StellarAccountId accountId(String value) {
return StellarAccountId.of(value);
}

static StellarContractAddress contractAddress(final String address) {
return StellarContractAddress.of(address);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

import lombok.EqualsAndHashCode;
import lombok.NonNull;
import onlydust.com.marketplace.kernel.model.blockchain.Hash;
import onlydust.com.marketplace.kernel.model.blockchain.PrefixedHexHash;

@EqualsAndHashCode(callSuper = true)
public class AptosAccountAddress extends Hash {
public class AptosAccountAddress extends PrefixedHexHash {
private static final int MAX_BYTE_COUNT = 32;

public AptosAccountAddress(final @NonNull String address) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@

import lombok.EqualsAndHashCode;
import lombok.NonNull;
import onlydust.com.marketplace.kernel.model.blockchain.Hash;
import onlydust.com.marketplace.kernel.model.blockchain.PrefixedHexHash;

@EqualsAndHashCode(callSuper = true)
public class AptosCoinType extends Hash {
public class AptosCoinType extends PrefixedHexHash {
private static final int MAX_BYTE_COUNT = 32;

@NonNull String coinType;
@NonNull
String coinType;

public AptosCoinType(final @NonNull String coinType) {
super(MAX_BYTE_COUNT, coinType.split(":")[0]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@

import lombok.EqualsAndHashCode;
import lombok.NonNull;
import onlydust.com.marketplace.kernel.model.blockchain.PrefixedHexHash;

import java.time.ZonedDateTime;

public record AptosTransaction(Hash hash, ZonedDateTime timestamp) {
@EqualsAndHashCode(callSuper = true)
public static class Hash extends onlydust.com.marketplace.kernel.model.blockchain.Hash {
public static class Hash extends PrefixedHexHash {
private static final int MAX_BYTE_COUNT = 32;

public Hash(final @NonNull String address) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

import lombok.EqualsAndHashCode;
import lombok.NonNull;
import onlydust.com.marketplace.kernel.model.blockchain.Hash;
import onlydust.com.marketplace.kernel.model.blockchain.PrefixedHexHash;

@EqualsAndHashCode(callSuper = true)
public class EvmAccountAddress extends Hash {
public class EvmAccountAddress extends PrefixedHexHash {
private static final int MAX_BYTE_COUNT = 20;

public EvmAccountAddress(final @NonNull String address) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

import lombok.EqualsAndHashCode;
import lombok.NonNull;
import onlydust.com.marketplace.kernel.model.blockchain.Hash;
import onlydust.com.marketplace.kernel.model.blockchain.PrefixedHexHash;

@EqualsAndHashCode(callSuper = true)
public class EvmContractAddress extends Hash {
public class EvmContractAddress extends PrefixedHexHash {
private static final int MAX_BYTE_COUNT = 20;

public EvmContractAddress(final @NonNull String address) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@

import lombok.EqualsAndHashCode;
import lombok.NonNull;
import onlydust.com.marketplace.kernel.model.blockchain.PrefixedHexHash;

import java.time.ZonedDateTime;

public record EvmTransaction(Hash hash, ZonedDateTime timestamp) {

@EqualsAndHashCode(callSuper = true)
public static class Hash extends onlydust.com.marketplace.kernel.model.blockchain.Hash {
public static class Hash extends PrefixedHexHash {
private static final int MAX_BYTE_COUNT = 32;

public Hash(final @NonNull String address) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

import lombok.EqualsAndHashCode;
import lombok.NonNull;
import onlydust.com.marketplace.kernel.model.blockchain.Hash;
import onlydust.com.marketplace.kernel.model.blockchain.PrefixedHexHash;

@EqualsAndHashCode(callSuper = true)
public class StarknetAccountAddress extends Hash {
public class StarknetAccountAddress extends PrefixedHexHash {
private static final int MAX_BYTE_COUNT = 32;

public StarknetAccountAddress(final @NonNull String address) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

import lombok.EqualsAndHashCode;
import lombok.NonNull;
import onlydust.com.marketplace.kernel.model.blockchain.Hash;
import onlydust.com.marketplace.kernel.model.blockchain.PrefixedHexHash;

@EqualsAndHashCode(callSuper = true)
public class StarknetContractAddress extends Hash {
public class StarknetContractAddress extends PrefixedHexHash {
private static final int MAX_BYTE_COUNT = 32;

public StarknetContractAddress(final @NonNull String address) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@

import lombok.EqualsAndHashCode;
import lombok.NonNull;
import onlydust.com.marketplace.kernel.model.blockchain.PrefixedHexHash;

import java.time.ZonedDateTime;

public record StarknetTransaction(onlydust.com.marketplace.kernel.model.blockchain.Hash hash, ZonedDateTime timestamp) {
public record StarknetTransaction(Hash hash, ZonedDateTime timestamp) {
@EqualsAndHashCode(callSuper = true)
public static class Hash extends onlydust.com.marketplace.kernel.model.blockchain.Hash {
public static class Hash extends PrefixedHexHash {
private static final int MAX_BYTE_COUNT = 32;

public Hash(final @NonNull String address) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package onlydust.com.marketplace.kernel.model.blockchain.stellar;

import lombok.NonNull;
import onlydust.com.marketplace.kernel.model.blockchain.Base32Hash;

public class StellarAccountId extends Base32Hash {
private static final int MAX_SIZE = 56;

private StellarAccountId(final @NonNull String accountId) {
super(MAX_SIZE, accountId);
}

public static StellarAccountId of(final @NonNull String accountId) {
return new StellarAccountId(accountId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package onlydust.com.marketplace.kernel.model.blockchain.stellar;

import lombok.NonNull;
import onlydust.com.marketplace.kernel.model.blockchain.Base32Hash;

public class StellarContractAddress extends Base32Hash {
private static final int MAX_SIZE = 56;

private StellarContractAddress(final @NonNull String accountId) {
super(MAX_SIZE, accountId);
}

public static StellarContractAddress of(final @NonNull String accountId) {
return new StellarContractAddress(accountId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package onlydust.com.marketplace.kernel.model.blockchain.stellar;

import onlydust.com.marketplace.kernel.model.blockchain.BlockExplorer;

import java.net.URI;

public class StellarExpert implements BlockExplorer<StellarTransaction.Hash> {
private static final String BASE_URL = "https://stellar.expert/explorer/public";

@Override
public URI url(StellarTransaction.Hash transactionHash) {
return URI.create(BASE_URL + "/tx/" + transactionHash);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package onlydust.com.marketplace.kernel.model.blockchain.stellar;

import lombok.EqualsAndHashCode;
import lombok.NonNull;
import onlydust.com.marketplace.kernel.model.blockchain.HexHash;

import java.time.ZonedDateTime;

public record StellarTransaction(Hash hash, ZonedDateTime timestamp) {
@EqualsAndHashCode(callSuper = true)
public static class Hash extends HexHash {
private static final int MAX_BYTE_COUNT = 32;

public Hash(final @NonNull String address) {
super(MAX_BYTE_COUNT, address);
}
}
}
Loading