All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.wavesplatform.transaction.assets.exchange.OrderJson.scala Maven / Gradle / Ivy

The newest version!
package com.wavesplatform.transaction.assets.exchange

import com.wavesplatform.account.PublicKey
import com.wavesplatform.common.state.ByteStr
import com.wavesplatform.common.utils.Base58
import com.wavesplatform.crypto.SignatureLength
import com.wavesplatform.transaction.Asset.Waves
import com.wavesplatform.transaction.assets.exchange.OrderPriceMode.{AssetDecimals, FixedDecimals}
import com.wavesplatform.transaction.{Asset, Proofs, TxExchangeAmount, TxMatcherFee, TxOrderPrice, TxVersion}
import com.wavesplatform.utils.{EthEncoding, byteStrFormat}
import play.api.libs.json.*

import scala.util.{Failure, Success}

object OrderJson {
  import play.api.libs.functional.syntax.*
  import play.api.libs.json.Reads.*

  implicit val byteArrayReads: Reads[Array[Byte]] = {
    case JsString(s) =>
      Base58.tryDecodeWithLimit(s) match {
        case Success(bytes) => JsSuccess(bytes)
        case Failure(_)     => JsError(JsPath, JsonValidationError("error.incorrect.base58"))
      }
    case _ => JsError(JsPath, JsonValidationError("error.expected.jsstring"))
  }

  implicit val optionByteArrayReads: Reads[Option[Array[Byte]]] = {
    case JsString(s) if s.isEmpty => JsSuccess(Option.empty[Array[Byte]])
    case JsString(s) if s.nonEmpty =>
      Base58.tryDecodeWithLimit(s) match {
        case Success(bytes) => JsSuccess(Some(bytes))
        case Failure(_)     => JsError(Seq(JsPath() -> Seq(JsonValidationError("error.incorrect.base58"))))
      }
    case _ => JsError(Seq(JsPath() -> Seq(JsonValidationError("error.expected.jsstring"))))
  }

  implicit lazy val accountPublicKeyReads: Reads[PublicKey] = Reads {
    case JsString(s) =>
      Base58.tryDecodeWithLimit(s) match {
        case Success(bytes) if PublicKey.isValidSize(bytes.length) => JsSuccess(PublicKey(bytes))
        case _                                                     => JsError(Seq(JsPath() -> Seq(JsonValidationError("error.incorrectAccount"))))
      }
    case _ => JsError(Seq(JsPath() -> Seq(JsonValidationError("error.expected.jsstring"))))
  }

  def readOrderV1V2(
      sender: PublicKey,
      matcher: PublicKey,
      assetPair: AssetPair,
      orderType: OrderType,
      amount: TxExchangeAmount,
      price: TxOrderPrice,
      timestamp: Long,
      expiration: Long,
      matcherFee: TxMatcherFee,
      signature: Option[Array[Byte]],
      proofs: Option[Array[Array[Byte]]],
      version: Option[Byte]
  ): Order = {

    val proofsValue =
      proofs
        .map(p => Proofs(p.map(ByteStr.apply).toList))
        .orElse(signature.map(s => Proofs(List(ByteStr(s)))))
        .getOrElse(Proofs.empty)

    val versionValue: Byte = version.getOrElse(if (proofsValue.proofs.size == 1 && proofsValue.proofs.head.arr.length == SignatureLength) 1 else 2)
    Order(
      versionValue,
      OrderAuthentication.OrderProofs(sender, proofsValue),
      matcher,
      assetPair,
      orderType,
      amount,
      price,
      timestamp,
      expiration,
      matcherFee,
      priceMode = OrderPriceMode.Default
    )
  }

  def readOrderV3V4(
      sender: Option[PublicKey],
      matcher: PublicKey,
      assetPair: AssetPair,
      orderType: OrderType,
      amount: TxExchangeAmount,
      price: TxOrderPrice,
      timestamp: Long,
      expiration: Long,
      matcherFee: TxMatcherFee,
      signature: Option[Array[Byte]],
      proofs: Option[Array[Array[Byte]]],
      version: TxVersion,
      matcherFeeAssetId: Asset,
      eip712Signature: Option[Array[Byte]],
      priceMode: OrderPriceMode,
      attachment: Option[ByteStr]
  ): Order = {
    val senderCredentials = eip712Signature match {
      case Some(value) =>
        OrderAuthentication.Eip712Signature(ByteStr(value))

      case None =>
        val proofsValue = proofs
          .map(p => Proofs(p.map(ByteStr.apply).toIndexedSeq))
          .orElse(signature.map(s => Proofs(ByteStr(s))))
          .getOrElse(Proofs.empty)

        OrderAuthentication.OrderProofs(
          sender.getOrElse(throw new IllegalArgumentException("Either senderPublicKey or eip712Signature should be provided")),
          proofsValue
        )
    }

    Order(
      version,
      senderCredentials,
      matcher,
      assetPair,
      orderType,
      amount,
      price,
      timestamp,
      expiration,
      matcherFee,
      matcherFeeAssetId,
      priceMode,
      attachment
    )
  }

  val assetReads: Reads[Asset] = Asset.assetReads(true)

  implicit val assetPairReads: Reads[AssetPair] = {
    val r = (JsPath \ "amountAsset").readWithDefault[Asset](Waves)(assetReads) and
      (JsPath \ "priceAsset").readWithDefault[Asset](Waves)(assetReads)
    r(AssetPair(_, _))
  }

  implicit val orderTypeReads: Reads[OrderType] =
    JsPath.read[String].map(OrderType.apply)

  implicit val priceModeReads: Reads[OrderPriceMode] =
    JsPath.read[String].flatMapResult {
      case "assetDecimals" => JsSuccess(AssetDecimals)
      case "fixedDecimals" => JsSuccess(FixedDecimals)
      case other           => JsError(s"Unexpected order price mode: $other")
    }

  private val orderV1V2Reads: Reads[Order] = {
    val r = (JsPath \ "senderPublicKey").read[PublicKey](accountPublicKeyReads) and
      (JsPath \ "matcherPublicKey").read[PublicKey](accountPublicKeyReads) and
      (JsPath \ "assetPair").read[AssetPair] and
      (JsPath \ "orderType").read[OrderType] and
      (JsPath \ "amount").read[Long].map(TxExchangeAmount.from).flatMapResult {
        case Right(amount) => JsSuccess(amount)
        case _             => JsError(TxExchangeAmount.errMsg)
      } and
      (JsPath \ "price").read[Long].map(TxOrderPrice.from).flatMapResult {
        case Right(price) => JsSuccess(price)
        case _            => JsError(TxOrderPrice.errMsg)
      } and
      (JsPath \ "timestamp").read[Long] and
      (JsPath \ "expiration").read[Long] and
      (JsPath \ "matcherFee").read[Long].map(TxMatcherFee.from).flatMapResult {
        case Right(fee) => JsSuccess(fee)
        case _          => JsError(TxMatcherFee.errMsg)
      } and
      (JsPath \ "signature").readNullable[Array[Byte]] and
      (JsPath \ "proofs").readNullable[Array[Array[Byte]]] and
      (JsPath \ "version").readNullable[Byte]
    r(readOrderV1V2 _)
  }

  private val orderV3V4Reads: Reads[Order] = {
    val r = (JsPath \ "senderPublicKey").readNullable[PublicKey](accountPublicKeyReads) and
      (JsPath \ "matcherPublicKey").read[PublicKey](accountPublicKeyReads) and
      (JsPath \ "assetPair").read[AssetPair] and
      (JsPath \ "orderType").read[OrderType] and
      (JsPath \ "amount").read[Long].map(TxExchangeAmount.from).flatMapResult {
        case Right(amount) => JsSuccess(amount)
        case _             => JsError(TxExchangeAmount.errMsg)
      } and
      (JsPath \ "price").read[Long].map(TxOrderPrice.from).flatMapResult {
        case Right(price) => JsSuccess(price)
        case _            => JsError(TxOrderPrice.errMsg)
      } and
      (JsPath \ "timestamp").read[Long] and
      (JsPath \ "expiration").read[Long] and
      (JsPath \ "matcherFee").read[Long].map(TxMatcherFee.from).flatMapResult {
        case Right(fee) => JsSuccess(fee)
        case _          => JsError(TxMatcherFee.errMsg)
      } and
      (JsPath \ "signature").readNullable[Array[Byte]] and
      (JsPath \ "proofs").readNullable[Array[Array[Byte]]] and
      (JsPath \ "version").read[Byte] and
      (JsPath \ "matcherFeeAssetId").readNullable[Asset].map(_.getOrElse(Waves)) and
      (JsPath \ "eip712Signature")
        .readNullable[String]
        .map(_.map(EthEncoding.toBytes)) and
      (JsPath \ "priceMode")
        .readWithDefault[OrderPriceMode](OrderPriceMode.Default) and
      (JsPath \ "attachment").readNullable[ByteStr]
    r(readOrderV3V4 _)
  }

  implicit val orderReads: Reads[Order] = {
    case jsOrder @ JsObject(map) =>
      map.getOrElse("version", JsNumber(1)) match {
        case JsNumber(n) if n == 1 || n == 2 => orderV1V2Reads.reads(jsOrder)
        case JsNumber(n) if n >= 3           => orderV3V4Reads.reads(jsOrder)
        case v                               => JsError(s"Invalid version: $v")
      }
    case invalidOrder => JsError(s"Order object expected: $invalidOrder")
  }

  implicit val orderFormat: Format[Order] = Format(orderReads, Writes[Order](_.json()))
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy