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

com.wavesplatform.api.http.TransactionJsonSerializer.scala Maven / Gradle / Ivy

The newest version!
package com.wavesplatform.api.http

import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.databind.{JsonSerializer, SerializerProvider}
import com.wavesplatform.account.{Address, AddressOrAlias}
import com.wavesplatform.api.common.TransactionMeta
import com.wavesplatform.api.http.StreamSerializerUtils.*
import com.wavesplatform.api.http.TransactionJsonSerializer.*
import com.wavesplatform.api.http.TransactionsApiRoute.{ApplicationStatus, LeaseStatus, TxMetaEnriched}
import com.wavesplatform.common.state.ByteStr
import com.wavesplatform.database.protobuf.EthereumTransactionMeta
import com.wavesplatform.database.protobuf.EthereumTransactionMeta.Payload
import com.wavesplatform.features.BlockchainFeatures
import com.wavesplatform.lang.v1.compiler.Terms
import com.wavesplatform.lang.v1.compiler.Terms.{ARR, CONST_BOOLEAN, CONST_BYTESTR, CONST_LONG, CONST_STRING, CaseObj, EVALUATED, EXPR, FAIL, FUNCTION_CALL}
import com.wavesplatform.lang.v1.serialization.SerdeV1
import com.wavesplatform.protobuf.transaction.PBAmounts
import com.wavesplatform.state.InvokeScriptResult.{AttachedPayment, Burn, Call, ErrorMessage, Invocation, Issue, Lease, LeaseCancel, Reissue, SponsorFee}
import com.wavesplatform.state.{Blockchain, DataEntry, InvokeScriptResult, LeaseDetails, TxMeta}
import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves}
import com.wavesplatform.transaction.lease.{LeaseCancelTransaction, LeaseTransaction}
import com.wavesplatform.transaction.serialization.impl.InvokeScriptTxSerializer
import com.wavesplatform.transaction.smart.InvokeScriptTransaction
import com.wavesplatform.transaction.smart.InvokeScriptTransaction.Payment
import com.wavesplatform.transaction.transfer.MassTransferTransaction
import com.wavesplatform.transaction.{Asset, PBSince, Transaction}
import com.wavesplatform.utils.EthEncoding
import play.api.libs.json.*
import play.api.libs.json.JsonConfiguration.Aux

final case class TransactionJsonSerializer(blockchain: Blockchain) {

  val assetSerializer: JsonSerializer[Asset] =
    (value: Asset, gen: JsonGenerator, serializers: SerializerProvider) => {
      value match {
        case Waves           => gen.writeNull()
        case IssuedAsset(id) => gen.writeString(id.toString)
      }
    }

  def evaluatedSerializer(numbersAsString: Boolean): JsonSerializer[EVALUATED] =
    (value: EVALUATED, gen: JsonGenerator, serializers: SerializerProvider) => {
      gen.writeStartObject()
      value match {
        case CONST_LONG(num) =>
          gen.writeStringField("type", "Int")
          gen.writeNumberField("value", num, numbersAsString)
        case CONST_BYTESTR(bs) =>
          gen.writeStringField("type", "ByteVector")
          gen.writeStringField("value", bs.toString)
        case CONST_STRING(str) =>
          gen.writeStringField("type", "String")
          gen.writeStringField("value", str)
        case CONST_BOOLEAN(b) =>
          gen.writeStringField("type", "Boolean")
          gen.writeBooleanField("value", b)
        case CaseObj(caseType, fields) =>
          gen.writeStringField("type", caseType.name)
          gen.writeValueField("value") { gen =>
            gen.writeStartObject()
            fields.foreach { case (key, value) =>
              gen.writeValueField(key, value)(evaluatedSerializer(numbersAsString), serializers)
            }
            gen.writeEndObject()
          }
        case ARR(xs) =>
          gen.writeStringField("type", "Array")
          gen.writeArrayField("value", xs)(evaluatedSerializer(numbersAsString), serializers)
        case FAIL(reason) =>
          gen.writeStringField("error", reason)
        case _ =>
      }
      gen.writeEndObject()
    }

  def funcCallSerializer(numbersAsString: Boolean): JsonSerializer[FUNCTION_CALL] = new JsonSerializer[FUNCTION_CALL] {
    override def serialize(funcCall: FUNCTION_CALL, gen: JsonGenerator, serializers: SerializerProvider): Unit = {
      gen.writeStartObject()
      gen.writeStringField("function", funcCall.function.funcName)
      gen.writeArrayField("args") { out =>
        funcCall.args.foreach {
          case Terms.ARR(elements) =>
            gen.writeStartObject()
            gen.writeStringField("type", "list")
            out.writeArrayField("value")(out => elements.foreach(e => writeSingleArg(e, out)))
            gen.writeEndObject()
          case other => writeSingleArg(other, out)
        }
      }
      gen.writeEndObject()
    }

    def writeSingleArg(arg: EXPR, gen: JsonGenerator): Unit = {
      gen.writeStartObject()
      arg match {
        case CONST_LONG(num) =>
          gen.writeStringField("type", "integer")
          gen.writeNumberField("value", num, numbersAsString)
        case CONST_BOOLEAN(bool) =>
          gen.writeStringField("type", "boolean")
          gen.writeBooleanField("value", bool)
        case CONST_BYTESTR(bytes) =>
          gen.writeStringField("type", "binary")
          gen.writeStringField("value", bytes.base64)
        case CONST_STRING(str) =>
          gen.writeStringField("type", "string")
          gen.writeStringField("value", str)
        case ARR(_) =>
          gen.writeStringField("type", "list")
          gen.writeStringField("value", "unsupported")
        case arg => throw new NotImplementedError(s"Not supported: $arg")
      }
      gen.writeEndObject()
    }
  }

  val leaseStatusSerializer: JsonSerializer[LeaseStatus] =
    (status: LeaseStatus, gen: JsonGenerator, serializers: SerializerProvider) => {
      if (status == LeaseStatus.active) gen.writeString("active") else gen.writeString("canceled")
    }

  def leaseRefSerializer(numbersAsString: Boolean): JsonSerializer[LeaseRef] =
    (l: LeaseRef, gen: JsonGenerator, serializers: SerializerProvider) => {
      gen.writeStartObject()
      gen.writeStringField("id", l.id.toString)
      l.originTransactionId.fold(gen.writeNullField("originTransactionId"))(txId => gen.writeStringField("originTransactionId", txId.toString))
      l.sender.fold(gen.writeNullField("sender"))(sender => gen.writeStringField("sender", sender.toString))
      l.recipient.fold(gen.writeNullField("recipient"))(recipient => gen.writeStringField("recipient", recipient.toString))
      l.amount.fold(gen.writeNullField("amount"))(amount => gen.writeNumberField("amount", amount, numbersAsString))
      l.height.fold(gen.writeNullField("height"))(height => gen.writeNumberField("height", height, numbersAsString))
      gen.writeStringField("status", if (l.status == LeaseStatus.active) "active" else "canceled")
      l.cancelHeight.fold(gen.writeNullField("cancelHeight"))(ch => gen.writeNumberField("cancelHeight", ch, numbersAsString))
      l.cancelTransactionId.fold(gen.writeNullField("cancelTransactionId"))(cti => gen.writeStringField("cancelTransactionId", cti.toString))
      gen.writeEndObject()
    }

  def leaseSerializer(numbersAsString: Boolean): JsonSerializer[Lease] =
    (l: Lease, gen: JsonGenerator, serializers: SerializerProvider) => {
      leaseRefSerializer(numbersAsString).serialize(leaseIdToLeaseRef(l.id, Some(l.recipient), Some(l.amount)), gen, serializers)
    }

  def leaseCancelSerializer(numbersAsString: Boolean): JsonSerializer[LeaseCancel] =
    (lc: LeaseCancel, gen: JsonGenerator, serializers: SerializerProvider) => {
      leaseRefSerializer(numbersAsString).serialize(leaseIdToLeaseRef(lc.id), gen, serializers)
    }

  def paymentSerializer(numbersAsString: Boolean): JsonSerializer[Payment] =
    (p: Payment, gen: JsonGenerator, serializers: SerializerProvider) => {
      gen.writeStartObject()
      gen.writeNumberField("amount", p.amount, numbersAsString)
      gen.writeValueField("assetId")(assetSerializer.serialize(p.assetId, _, serializers))
      gen.writeEndObject()
    }

  def attachedPaymentSerializer(numbersAsString: Boolean): JsonSerializer[AttachedPayment] =
    (p: AttachedPayment, gen: JsonGenerator, serializers: SerializerProvider) => {
      gen.writeStartObject()
      gen.writeValueField("assetId")(assetSerializer.serialize(p.assetId, _, serializers))
      gen.writeNumberField("amount", p.amount, numbersAsString)
      gen.writeEndObject()
    }

  def isrPaymentSerializer(numbersAsString: Boolean): JsonSerializer[InvokeScriptResult.Payment] =
    (p: InvokeScriptResult.Payment, gen: JsonGenerator, serializers: SerializerProvider) => {
      gen.writeStartObject()
      gen.writeStringField("address", p.address.toString)
      gen.writeValueField("asset")(assetSerializer.serialize(p.asset, _, serializers))
      gen.writeNumberField("amount", p.amount, numbersAsString)
      gen.writeEndObject()
    }

  def issueSerializer(numbersAsString: Boolean): JsonSerializer[Issue] =
    (issue: Issue, gen: JsonGenerator, serializers: SerializerProvider) => {
      gen.writeStartObject()
      gen.writeStringField("assetId", issue.id.toString)
      issue.compiledScript.foreach(sc => gen.writeStringField("compiledScript", sc.toString))
      gen.writeNumberField("decimals", issue.decimals, numbersAsString)
      gen.writeStringField("description", issue.description)
      gen.writeBooleanField("isReissuable", issue.isReissuable)
      gen.writeStringField("name", issue.name)
      gen.writeNumberField("quantity", issue.quantity, numbersAsString)
      gen.writeNumberField("nonce", issue.nonce, numbersAsString)
      gen.writeEndObject()
    }

  def reissueSerializer(numbersAsString: Boolean): JsonSerializer[Reissue] =
    (r: Reissue, gen: JsonGenerator, serializers: SerializerProvider) => {
      gen.writeStartObject()
      gen.writeStringField("assetId", r.assetId.toString)
      gen.writeBooleanField("isReissuable", r.isReissuable)
      gen.writeNumberField("quantity", r.quantity, numbersAsString)
      gen.writeEndObject()
    }

  def burnSerializer(numbersAsString: Boolean): JsonSerializer[Burn] =
    (b: Burn, gen: JsonGenerator, serializers: SerializerProvider) => {
      gen.writeStartObject()
      gen.writeStringField("assetId", b.assetId.toString)
      gen.writeNumberField("quantity", b.quantity, numbersAsString)
      gen.writeEndObject()
    }

  def sponsorFeeSerializer(numbersAsString: Boolean): JsonSerializer[SponsorFee] =
    (s: SponsorFee, gen: JsonGenerator, serializers: SerializerProvider) => {
      gen.writeStartObject()
      gen.writeStringField("assetId", s.assetId.toString)
      s.minSponsoredAssetFee.foreach(fee => gen.writeNumberField("minSponsoredAssetFee", fee, numbersAsString))
      gen.writeEndObject()
    }

  def callSerializer(numbersAsString: Boolean): JsonSerializer[Call] =
    (c: Call, gen: JsonGenerator, serializers: SerializerProvider) => {
      gen.writeStartObject()
      gen.writeStringField("function", c.function)
      gen.writeArrayField("args", c.args)(evaluatedSerializer(numbersAsString), serializers)
      gen.writeEndObject()
    }

  def invocationSerializer(numbersAsString: Boolean): JsonSerializer[Invocation] =
    (inv: Invocation, gen: JsonGenerator, serializers: SerializerProvider) => {
      gen.writeStartObject()
      gen.writeStringField("dApp", inv.dApp.toString)
      gen.writeValueField("call")(callSerializer(numbersAsString).serialize(inv.call, _, serializers))
      gen.writeArrayField("payment", inv.payments)(attachedPaymentSerializer(numbersAsString), serializers)
      gen.writeValueField("stateChanges")(invokeScriptResultSerializer(numbersAsString).serialize(inv.stateChanges, _, serializers))
      gen.writeEndObject()
    }

  val errorMessageSerializer: JsonSerializer[ErrorMessage] =
    (err: ErrorMessage, gen: JsonGenerator, serializers: SerializerProvider) => {
      gen.writeStartObject()
      gen.writeNumberField("code", err.code, false)
      gen.writeStringField("text", err.text)
      gen.writeEndObject()
    }

  def invokeScriptResultSerializer(numbersAsString: Boolean): JsonSerializer[InvokeScriptResult] =
    (isr: InvokeScriptResult, gen: JsonGenerator, serializers: SerializerProvider) => {
      gen.writeStartObject()
      gen.writeArrayField("data", isr.data)(DataEntry.dataEntrySerializer(numbersAsString), serializers)
      gen.writeArrayField("transfers", isr.transfers)(isrPaymentSerializer(numbersAsString), serializers)
      gen.writeArrayField("issues", isr.issues)(issueSerializer(numbersAsString), serializers)
      gen.writeArrayField("reissues", isr.reissues)(reissueSerializer(numbersAsString), serializers)
      gen.writeArrayField("burns", isr.burns)(burnSerializer(numbersAsString), serializers)
      gen.writeArrayField("sponsorFees", isr.sponsorFees)(sponsorFeeSerializer(numbersAsString), serializers)
      gen.writeArrayField("leases", isr.leases)(leaseSerializer(numbersAsString), serializers)
      gen.writeArrayField("leaseCancels", isr.leaseCancels)(leaseCancelSerializer(numbersAsString), serializers)
      gen.writeArrayField("invokes", isr.invokes)(invocationSerializer(numbersAsString), serializers)
      isr.error.foreach(err => gen.writeValueField("error")(errorMessageSerializer.serialize(err, _, serializers)))
      gen.writeEndObject()
    }

  def txMetaJsonSerializer(address: Address, isBlockV5: Int => Boolean, numbersAsString: Boolean): JsonSerializer[TxMetaEnriched] =
    (txMeta: TxMetaEnriched, gen: JsonGenerator, serializers: SerializerProvider) => {
      txMeta.meta match {
        case TransactionMeta.Invoke(height, tx: InvokeScriptTransaction, status, spentComplexity, invokeScriptResult) =>
          gen.writeStartObject()
          gen.writeNumberField("type", tx.tpe.id, numbersAsString)
          gen.writeStringField("id", tx.id().toString)
          gen.writeNumberField("fee", tx.assetFee._2, numbersAsString)
          tx.feeAssetId match {
            case IssuedAsset(id) => gen.writeStringField("feeAssetId", id.toString)
            case Asset.Waves => gen.writeNullField("feeAssetId")
          }
          gen.writeNumberField("timestamp", tx.timestamp, numbersAsString)
          gen.writeNumberField("version", tx.version, numbersAsString)
          if (PBSince.affects(tx)) gen.writeNumberField("chainId", tx.chainId, numbersAsString)
          gen.writeStringField("sender", tx.sender.toAddress(tx.chainId).toString)
          gen.writeStringField("senderPublicKey", tx.sender.toString)
          gen.writeArrayField("proofs")(gen => tx.proofs.proofs.foreach(p => gen.writeString(p.toString)))
          gen.writeStringField("dApp", tx.dApp.toString)
          gen.writeArrayField("payment", tx.payments)(paymentSerializer(numbersAsString), serializers)
          gen.writeValueField("call")(funcCallSerializer(numbersAsString).serialize(tx.funcCall, _, serializers))
          gen.writeNumberField("height", height.toInt, numbersAsString)
          val appStatus =
            if (isBlockV5(height)) {
              Some(applicationStatusFromTxStatus(status))
            } else
              None
          appStatus.foreach(s => gen.writeStringField("applicationStatus", s))
          gen.writeNumberField("spentComplexity", spentComplexity, numbersAsString)
          invokeScriptResult.fold(gen.writeNullField("stateChanges"))(isr =>
            gen.writeValueField("stateChanges")(invokeScriptResultSerializer(numbersAsString).serialize(isr, _, serializers))
          )
          gen.writeEndObject()
        case TransactionMeta.Ethereum(height, tx, status, spentComplexity, Some(EthereumTransactionMeta(Payload.Invocation(i), _)), isr) =>
          val functionCallEi = SerdeV1.deserializeFunctionCall(i.functionCall.toByteArray).toOption
          val payments       = i.payments.map(p => InvokeScriptTransaction.Payment(p.amount, PBAmounts.toVanillaAssetId(p.assetId)))

          gen.writeStartObject()
          gen.writeNumberField("type", tx.tpe.id, numbersAsString)
          gen.writeStringField("id", tx.id().toString)
          gen.writeNumberField("fee", tx.assetFee._2, numbersAsString)
          gen.writeStringField("feeAssetId", null)
          gen.writeNumberField("timestamp", tx.timestamp, numbersAsString)
          gen.writeNumberField("version", 1, numbersAsString)
          gen.writeNumberField("chainId", tx.chainId, numbersAsString)
          gen.writeStringField("bytes", EthEncoding.toHexString(tx.bytes()))
          gen.writeStringField("sender", tx.senderAddress().toString)
          gen.writeStringField("senderPublicKey", tx.signerPublicKey().toString)
          gen.writeNumberField("height", height.toInt, numbersAsString)
          val appStatus =
            if (isBlockV5(height)) {
              Some(applicationStatusFromTxStatus(status))
            } else
              None
          appStatus.foreach(s => gen.writeStringField("applicationStatus", s))
          gen.writeNumberField("spentComplexity", spentComplexity, numbersAsString)
          gen.writeObjectFieldStart("payload")
          gen.writeStringField("type", "invocation")
          gen.writeStringField("dApp", Address(EthEncoding.toBytes(tx.underlying.getTo)).toString)
          functionCallEi.fold(gen.writeNullField("call"))(fc =>
            gen.writeValueField("call")(funcCallSerializer(numbersAsString).serialize(fc, _, serializers))
          )
          gen.writeArrayField("payment", payments)(paymentSerializer(numbersAsString), serializers)
          isr.fold(gen.writeNullField("stateChanges"))(isr =>
            gen.writeValueField("stateChanges")(invokeScriptResultSerializer(numbersAsString).serialize(isr, _, serializers))
          )
          gen.writeEndObject()
          gen.writeEndObject()
        case meta @ TransactionMeta.Default(_, mtt: MassTransferTransaction, _, _) if mtt.sender.toAddress != address =>
          /** Produces compact representation for large transactions by stripping unnecessary data. Currently implemented for MassTransfer transaction
            * only.
            */
          jsObjectSerializer(numbersAsString).serialize(
            mtt.compactJson(address, txMeta.aliases.getOrElse(Set.empty)) ++ transactionMetaJson(meta),
            gen,
            serializers
          )
        case other =>
          jsObjectSerializer(numbersAsString).serialize(other.transaction.json() ++ transactionMetaJson(other), gen, serializers)
      }
    }

  def jsObjectSerializer(numbersAsString: Boolean): JsonSerializer[JsObject] = new JsonSerializer[JsObject] {
    override def serialize(jsObj: JsObject, gen: JsonGenerator, serializers: SerializerProvider): Unit = {
      gen.writeStartObject()
      jsObj.fields.foreach { case (key, value) => encodeField(key, value, gen, serializers) }
      gen.writeEndObject()
    }

    private def encodeField(key: String, jsValue: JsValue, gen: JsonGenerator, serializers: SerializerProvider): Unit = {
      jsValue match {
        case n: JsNumber =>
          gen.writeNumberField(key, n.value, numbersAsString)
        case b: JsBoolean =>
          gen.writeBooleanField(key, b.value)
        case s: JsString =>
          gen.writeStringField(key, s.value)
        case a: JsArray =>
          gen.writeArrayField(key)(out => a.value.foreach(encodeArrayElem(_, out, serializers)))
        case o: JsObject =>
          gen.writeValueField(key)(serialize(o, _, serializers))
        case _ =>
          gen.writeNullField(key)
      }
    }

    private def encodeArrayElem(jsValue: JsValue, gen: JsonGenerator, serializers: SerializerProvider): Unit = {
      jsValue match {
        case n: JsNumber =>
          gen.writeNumber(n.value.bigDecimal)
        case b: JsBoolean =>
          gen.writeBoolean(b.value)
        case s: JsString =>
          gen.writeString(s.value)
        case a: JsArray =>
          gen.writeStartArray()
          a.value.foreach(encodeArrayElem(_, gen, serializers))
          gen.writeEndArray()
        case o: JsObject =>
          serialize(o, gen, serializers)
        case _ =>
          gen.writeNull()
      }
    }
  }

  def transactionMetaJson(meta: TransactionMeta): JsObject = {
    val specificInfo = meta.transaction match {
      case lease: LeaseTransaction =>
        import com.wavesplatform.api.http.TransactionsApiRoute.LeaseStatus.*
        Json.obj("status" -> (if (blockchain.leaseDetails(lease.id()).exists(_.isActive)) active else canceled))

      case leaseCancel: LeaseCancelTransaction =>
        Json.obj("lease" -> leaseIdToLeaseRef(leaseCancel.leaseId))

      case _ => JsObject.empty
    }

    val stateChanges = meta match {
      case i: TransactionMeta.Invoke =>
        Json.obj("stateChanges" -> i.invokeScriptResult)

      case e: TransactionMeta.Ethereum =>
        val payloadJson: JsObject = e.meta
          .map(_.payload)
          .collect {
            case Payload.Invocation(i) =>
              val functionCallEi = SerdeV1.deserializeFunctionCall(i.functionCall.toByteArray).map(InvokeScriptTxSerializer.functionCallToJson)
              val payments       = i.payments.map(p => InvokeScriptTransaction.Payment(p.amount, PBAmounts.toVanillaAssetId(p.assetId)))
              Json.obj(
                "type"         -> "invocation",
                "dApp"         -> Address(EthEncoding.toBytes(e.transaction.underlying.getTo)),
                "call"         -> functionCallEi.toOption,
                "payment"      -> payments,
                "stateChanges" -> e.invokeScriptResult
              )

            case Payload.Transfer(t) =>
              val (asset, amount) = PBAmounts.toAssetAndAmount(t.getAmount)
              Json.obj(
                "type"      -> "transfer",
                "recipient" -> Address(t.publicKeyHash.toByteArray),
                "asset"     -> asset,
                "amount"    -> amount
              )
          }
          .getOrElse(JsObject.empty)
        Json.obj("payload" -> payloadJson)

      case _ => JsObject.empty
    }

    Seq(
      TransactionJsonSerializer.height(meta.height),
      metaJson(TxMeta(meta.height, meta.status, meta.spentComplexity)),
      stateChanges,
      specificInfo
    ).reduce(_ ++ _)
  }

  def transactionWithMetaJson(meta: TransactionMeta): JsObject =
    meta.transaction.json() ++ transactionMetaJson(meta)

  def unconfirmedTxExtendedJson(tx: Transaction): JsObject = tx match {
    case leaseCancel: LeaseCancelTransaction =>
      leaseCancel.json() ++ Json.obj("lease" -> leaseIdToLeaseRef(leaseCancel.leaseId))

    case t => t.json()
  }

  def metaJson(m: TxMeta): JsObject =
    TransactionJsonSerializer.applicationStatus(isBlockV5(m.height), m.status) ++ Json.obj("spentComplexity" -> m.spentComplexity)

  private[this] def isBlockV5(height: Int): Boolean = blockchain.isFeatureActivated(BlockchainFeatures.BlockV5, height)

  // Extended lease format. Overrides default
  private[this] def leaseIdToLeaseRef(
      leaseId: ByteStr,
      recipientParamOpt: Option[AddressOrAlias] = None,
      amountOpt: Option[Long] = None
  ): LeaseRef = {
    val detailsOpt           = blockchain.leaseDetails(leaseId)
    val txMetaOpt            = detailsOpt.flatMap(d => blockchain.transactionMeta(d.sourceId))
    val recipientOpt         = recipientParamOpt.orElse(detailsOpt.map(_.recipientAddress))
    val resolvedRecipientOpt = recipientOpt.flatMap(r => blockchain.resolveAlias(r).toOption)

    val statusOpt = detailsOpt.map(_.status)
    val status    = LeaseStatus(statusOpt.contains(LeaseDetails.Status.Active))
    val statusDataOpt = statusOpt.map {
      case LeaseDetails.Status.Active                  => (None, None)
      case LeaseDetails.Status.Cancelled(height, txId) => (Some(height), txId)
      case LeaseDetails.Status.Expired(height)         => (Some(height), None)
    }

    LeaseRef(
      leaseId,
      detailsOpt.map(_.sourceId),
      detailsOpt.map(_.sender.toAddress),
      resolvedRecipientOpt,
      amountOpt orElse detailsOpt.map(_.amount.value),
      txMetaOpt.map(_.height),
      status,
      statusDataOpt.flatMap(_._1),
      statusDataOpt.flatMap(_._2)
    )
  }

  private[http] implicit val leaseWrites: OWrites[InvokeScriptResult.Lease] =
    LeaseRef.jsonWrites.contramap((l: InvokeScriptResult.Lease) => leaseIdToLeaseRef(l.id, Some(l.recipient), Some(l.amount)))

  private[http] implicit val leaseCancelWrites: OWrites[InvokeScriptResult.LeaseCancel] =
    LeaseRef.jsonWrites.contramap((l: InvokeScriptResult.LeaseCancel) => leaseIdToLeaseRef(l.id))

  // To override nested InvokeScriptResult writes
  private[http] implicit val invocationWrites: OWrites[InvokeScriptResult.Invocation] = (i: InvokeScriptResult.Invocation) =>
    Json.obj(
      "dApp"         -> i.dApp,
      "call"         -> i.call,
      "payment"      -> i.payments,
      "stateChanges" -> invokeScriptResultWrites.writes(i.stateChanges)
    )

  private[http] implicit val invokeScriptResultWrites: OWrites[InvokeScriptResult] = {
    import InvokeScriptResult.{issueFormat, reissueFormat, burnFormat, sponsorFeeFormat}
    Json.writes[InvokeScriptResult]
  }
}

object TransactionJsonSerializer {
  def applicationStatus(isBlockV5: Boolean, status: TxMeta.Status): JsObject =
    if (isBlockV5)
      Json.obj("applicationStatus" -> applicationStatusFromTxStatus(status))
    else
      JsObject.empty

  def applicationStatusFromTxStatus(status: TxMeta.Status): String =
    status match {
      case TxMeta.Status.Succeeded => ApplicationStatus.Succeeded
      case TxMeta.Status.Failed    => ApplicationStatus.ScriptExecutionFailed
      case TxMeta.Status.Elided    => ApplicationStatus.Elided
    }

  def height(height: Int): JsObject =
    Json.obj("height" -> height)

  final case class LeaseRef(
      id: ByteStr,
      originTransactionId: Option[ByteStr],
      sender: Option[Address],
      recipient: Option[Address],
      amount: Option[Long],
      height: Option[Int],
      status: LeaseStatus,
      cancelHeight: Option[Int],
      cancelTransactionId: Option[ByteStr]
  )

  object LeaseRef {
    import com.wavesplatform.utils.byteStrFormat
    implicit val config: Aux[Json.MacroOptions] = JsonConfiguration(optionHandlers = OptionHandlers.WritesNull)
    implicit val jsonWrites: OWrites[LeaseRef]  = Json.writes[LeaseRef]
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy