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

com.wavesplatform.transaction.TransactionFactory.scala Maven / Gradle / Ivy

The newest version!
package com.wavesplatform.transaction

import com.wavesplatform.account.*
import com.wavesplatform.api.http.requests.*
import com.wavesplatform.api.http.requests.DataRequest.*
import com.wavesplatform.api.http.requests.InvokeExpressionRequest.*
import com.wavesplatform.api.http.requests.SponsorFeeRequest.*
import com.wavesplatform.api.http.versionReads
import com.wavesplatform.common.state.ByteStr
import com.wavesplatform.lang.ValidationError
import com.wavesplatform.lang.script.Script
import com.wavesplatform.lang.script.v1.ExprScript
import com.wavesplatform.transaction.Asset.IssuedAsset
import com.wavesplatform.transaction.TxValidationError.{GenericError, UnsupportedTransactionType, UnsupportedTypeAndVersion, WrongChain}
import com.wavesplatform.transaction.assets.*
import com.wavesplatform.transaction.assets.exchange.*
import com.wavesplatform.transaction.lease.{LeaseCancelTransaction, LeaseTransaction}
import com.wavesplatform.transaction.smart.{InvokeExpressionTransaction, InvokeScriptTransaction, SetScriptTransaction}
import com.wavesplatform.transaction.transfer.*
import com.wavesplatform.utils.Time
import com.wavesplatform.wallet.Wallet
import play.api.libs.json.{JsObject, JsValue}

object TransactionFactory {
  def transferAsset(request: TransferRequest, wallet: Wallet, time: Time): Either[ValidationError, TransferTransaction] =
    for {
      _  <- Either.cond(request.sender.nonEmpty, (), GenericError("invalid.sender"))
      tx <- transferAsset(request, wallet, request.sender.get, time)
    } yield tx

  def transferAsset(request: TransferRequest, wallet: Wallet, signerAddress: String, time: Time): Either[ValidationError, TransferTransaction] =
    for {
      _      <- Either.cond(request.sender.isDefined, (), GenericError("invalid.sender"))
      sender <- wallet.findPrivateKey(request.sender.get)
      tx     <- request.copy(timestamp = request.timestamp.orElse(Some(time.getTimestamp()))).toTxFrom(sender.publicKey)
      signer <- if (request.sender.get == signerAddress) Right(sender) else wallet.findPrivateKey(signerAddress)
      signedTx = tx.signWith(signer.privateKey)
    } yield signedTx

  def massTransferAsset(request: MassTransferRequest, wallet: Wallet, time: Time): Either[ValidationError, MassTransferTransaction] =
    massTransferAsset(request, wallet, request.sender, time)

  def massTransferAsset(
      request: MassTransferRequest,
      wallet: Wallet,
      signerAddress: String,
      time: Time
  ): Either[ValidationError, MassTransferTransaction] =
    for {
      sender    <- wallet.findPrivateKey(request.sender)
      signer    <- if (request.sender == signerAddress) Right(sender) else wallet.findPrivateKey(signerAddress)
      transfers <- MassTransferTransaction.parseTransfersList(request.transfers)
      tx <- MassTransferTransaction.signed(
        request.version.getOrElse(1.toByte),
        sender.publicKey,
        Asset.fromCompatId(request.assetId.map(s => ByteStr.decodeBase58(s).get)),
        transfers,
        request.fee,
        request.timestamp.getOrElse(time.getTimestamp()),
        request.attachment.getOrElse(ByteStr.empty),
        signer.privateKey
      )
    } yield tx

  def massTransferAsset(request: MassTransferRequest, sender: PublicKey): Either[ValidationError, MassTransferTransaction] =
    for {
      transfers <- MassTransferTransaction.parseTransfersList(request.transfers)
      tx <- MassTransferTransaction.create(
        request.version.getOrElse(1.toByte),
        sender,
        Asset.fromCompatId(request.assetId.map(s => ByteStr.decodeBase58(s).get)),
        transfers,
        request.fee,
        0,
        request.attachment.getOrElse(ByteStr.empty),
        Proofs.empty
      )
    } yield tx

  def setScript(request: SetScriptRequest, wallet: Wallet, time: Time): Either[ValidationError, SetScriptTransaction] =
    setScript(request, wallet, request.sender, time)

  def setScript(request: SetScriptRequest, wallet: Wallet, signerAddress: String, time: Time): Either[ValidationError, SetScriptTransaction] =
    for {
      sender <- wallet.findPrivateKey(request.sender)
      signer <- if (request.sender == signerAddress) Right(sender) else wallet.findPrivateKey(signerAddress)
      script <- request.script match {
        case None | Some("") => Right(None)
        case Some(s)         => Script.fromBase64String(s).map(Some(_))
      }
      tx <- SetScriptTransaction.signed(
        request.version.getOrElse(1.toByte),
        sender.publicKey,
        script,
        request.fee,
        request.timestamp.getOrElse(time.getTimestamp()),
        signer.privateKey
      )
    } yield tx

  def setScript(request: SetScriptRequest, sender: PublicKey): Either[ValidationError, SetScriptTransaction] =
    for {
      script <- request.script match {
        case None | Some("") => Right(None)
        case Some(s)         => Script.fromBase64String(s).map(Some(_))
      }
      tx <- SetScriptTransaction.create(request.version.getOrElse(1.toByte), sender, script, request.fee, 0, Proofs.empty)
    } yield tx

  def setAssetScript(
      request: SetAssetScriptRequest,
      wallet: Wallet,
      signerAddress: String,
      time: Time
  ): Either[ValidationError, SetAssetScriptTransaction] =
    for {
      sender <- wallet.findPrivateKey(request.sender)
      signer <- if (request.sender == signerAddress) Right(sender) else wallet.findPrivateKey(signerAddress)
      script <- request.script match {
        case None | Some("") => Right(None)
        case Some(s)         => Script.fromBase64String(s).map(Some(_))
      }
      tx <- SetAssetScriptTransaction.signed(
        request.version.getOrElse(1.toByte),
        sender.publicKey,
        IssuedAsset(ByteStr.decodeBase58(request.assetId).get),
        script,
        request.fee,
        request.timestamp.getOrElse(time.getTimestamp()),
        signer.privateKey
      )
    } yield tx

  def setAssetScript(request: SetAssetScriptRequest, sender: PublicKey): Either[ValidationError, SetAssetScriptTransaction] =
    for {
      script <- request.script match {
        case None | Some("") => Right(None)
        case Some(s)         => Script.fromBase64String(s).map(Some(_))
      }
      tx <- SetAssetScriptTransaction.create(
        request.version.getOrElse(1.toByte),
        sender,
        IssuedAsset(ByteStr.decodeBase58(request.assetId).get),
        script,
        request.fee,
        request.timestamp.getOrElse(0),
        Proofs.empty
      )
    } yield tx

  def lease(request: LeaseRequest, wallet: Wallet, time: Time): Either[ValidationError, LeaseTransaction] =
    for {
      _  <- Either.cond(request.sender.nonEmpty, (), GenericError("invalid.sender"))
      tx <- lease(request, wallet, request.sender.get, time)
    } yield tx

  def lease(request: LeaseRequest, wallet: Wallet, signerAddress: String, time: Time): Either[ValidationError, LeaseTransaction] =
    for {
      _      <- Either.cond(request.sender.isDefined, (), GenericError("invalid.sender"))
      sender <- wallet.findPrivateKey(request.sender.get)
      tx     <- request.copy(timestamp = request.timestamp.orElse(Some(time.getTimestamp()))).toTxFrom(sender.publicKey)
      signer <- if (request.sender.get == signerAddress) Right(sender) else wallet.findPrivateKey(signerAddress)
      signedTx = tx.signWith(signer.privateKey)
    } yield signedTx

  def leaseCancel(request: LeaseCancelRequest, wallet: Wallet, time: Time): Either[ValidationError, LeaseCancelTransaction] =
    for {
      _  <- Either.cond(request.sender.nonEmpty, (), GenericError("invalid.sender"))
      tx <- leaseCancel(request, wallet, request.sender.get, time)
    } yield tx

  def leaseCancel(request: LeaseCancelRequest, wallet: Wallet, signerAddress: String, time: Time): Either[ValidationError, LeaseCancelTransaction] =
    for {
      _      <- Either.cond(request.sender.isDefined, (), GenericError("invalid.sender"))
      sender <- wallet.findPrivateKey(request.sender.get)
      tx     <- request.copy(timestamp = request.timestamp.orElse(Some(time.getTimestamp()))).toTxFrom(sender.publicKey)
      signer <- if (request.sender.get == signerAddress) Right(sender) else wallet.findPrivateKey(signerAddress)
      signedTx = tx.signWith(signer.privateKey)
    } yield signedTx

  def createAlias(request: CreateAliasRequest, wallet: Wallet, time: Time): Either[ValidationError, CreateAliasTransaction] =
    for {
      _  <- Either.cond(request.sender.nonEmpty, (), GenericError("invalid.sender"))
      tx <- createAlias(request, wallet, request.sender.get, time)
    } yield tx

  def createAlias(request: CreateAliasRequest, wallet: Wallet, signerAddress: String, time: Time): Either[ValidationError, CreateAliasTransaction] =
    for {
      _      <- Either.cond(request.sender.isDefined, (), GenericError("invalid.sender"))
      sender <- wallet.findPrivateKey(request.sender.get)
      tx     <- request.copy(timestamp = request.timestamp.orElse(Some(time.getTimestamp()))).toTxFrom(sender.publicKey)
      signer <- if (request.sender.get == signerAddress) Right(sender) else wallet.findPrivateKey(signerAddress)
      signedTx = tx.signWith(signer.privateKey)
    } yield signedTx

  def exchange(request: ExchangeRequest, wallet: Wallet, time: Time): Either[ValidationError, ExchangeTransaction] =
    for {
      _  <- Either.cond(request.sender.nonEmpty, (), GenericError("invalid.sender"))
      tx <- exchange(request, wallet, request.sender.get, time)
    } yield tx

  def exchange(request: ExchangeRequest, wallet: Wallet, signerAddress: String, time: Time): Either[ValidationError, ExchangeTransaction] =
    for {
      _      <- Either.cond(request.sender.isDefined, (), GenericError("invalid.sender"))
      sender <- wallet.findPrivateKey(request.sender.get)
      tx     <- request.copy(timestamp = request.timestamp.orElse(Some(time.getTimestamp()))).toTxFrom(sender.publicKey)
      signer <- if (request.sender.get == signerAddress) Right(sender) else wallet.findPrivateKey(signerAddress)
      signedTx = tx.signWith(signer.privateKey)
    } yield signedTx

  def issue(request: IssueRequest, wallet: Wallet, time: Time): Either[ValidationError, IssueTransaction] =
    for {
      _  <- Either.cond(request.sender.nonEmpty, (), GenericError("invalid.sender"))
      tx <- issue(request, wallet, request.sender.get, time)
    } yield tx

  def issue(request: IssueRequest, wallet: Wallet, signerAddress: String, time: Time): Either[ValidationError, IssueTransaction] =
    for {
      _      <- Either.cond(request.sender.isDefined, (), GenericError("invalid.sender"))
      sender <- wallet.findPrivateKey(request.sender.get)
      tx     <- request.copy(timestamp = request.timestamp.orElse(Some(time.getTimestamp()))).toTxFrom(sender.publicKey)
      signer <- if (request.sender.get == signerAddress) Right(sender) else wallet.findPrivateKey(signerAddress)
      signedTx = tx.signWith(signer.privateKey)
    } yield signedTx

  def reissue(request: ReissueRequest, wallet: Wallet, time: Time): Either[ValidationError, ReissueTransaction] =
    for {
      _  <- Either.cond(request.sender.nonEmpty, (), GenericError("invalid.sender"))
      tx <- reissue(request, wallet, request.sender.get, time)
    } yield tx

  def reissue(request: ReissueRequest, wallet: Wallet, signerAddress: String, time: Time): Either[ValidationError, ReissueTransaction] =
    for {
      _      <- Either.cond(request.sender.isDefined, (), GenericError("invalid.sender"))
      sender <- wallet.findPrivateKey(request.sender.get)
      tx     <- request.copy(timestamp = request.timestamp.orElse(Some(time.getTimestamp()))).toTxFrom(sender.publicKey)
      signer <- if (request.sender.get == signerAddress) Right(sender) else wallet.findPrivateKey(signerAddress)
      signedTx = tx.signWith(signer.privateKey)
    } yield signedTx

  def burn(request: BurnRequest, wallet: Wallet, time: Time): Either[ValidationError, BurnTransaction] =
    for {
      _  <- Either.cond(request.sender.nonEmpty, (), GenericError("invalid.sender"))
      tx <- burn(request, wallet, request.sender.get, time)
    } yield tx

  def burn(request: BurnRequest, wallet: Wallet, signerAddress: String, time: Time): Either[ValidationError, BurnTransaction] =
    for {
      _      <- Either.cond(request.sender.isDefined, (), GenericError("invalid.sender"))
      sender <- wallet.findPrivateKey(request.sender.get)
      tx     <- request.copy(timestamp = request.timestamp.orElse(Some(time.getTimestamp()))).toTxFrom(sender.publicKey)
      signer <- if (request.sender.get == signerAddress) Right(sender) else wallet.findPrivateKey(signerAddress)
      signedTx = tx.signWith(signer.privateKey)
    } yield signedTx

  def data(request: DataRequest, wallet: Wallet, time: Time): Either[ValidationError, DataTransaction] =
    data(request, wallet, request.sender, time)

  def data(request: DataRequest, wallet: Wallet, signerAddress: String, time: Time): Either[ValidationError, DataTransaction] =
    for {
      sender <- wallet.findPrivateKey(request.sender)
      signer <- if (request.sender == signerAddress) Right(sender) else wallet.findPrivateKey(signerAddress)
      tx <- DataTransaction.signed(
        request.version,
        sender.publicKey,
        request.data,
        request.fee,
        request.timestamp.getOrElse(time.getTimestamp()),
        signer.privateKey
      )
    } yield tx

  def data(request: DataRequest, sender: PublicKey): Either[ValidationError, DataTransaction] =
    DataTransaction.create(request.version, sender, request.data, request.fee, request.timestamp.getOrElse(0), Proofs.empty)

  def invokeScript(request: InvokeScriptRequest, wallet: Wallet, time: Time): Either[ValidationError, InvokeScriptTransaction] =
    invokeScript(request, wallet, request.sender, time)

  def invokeScript(
      request: InvokeScriptRequest,
      wallet: Wallet,
      signerAddress: String,
      time: Time
  ): Either[ValidationError, InvokeScriptTransaction] =
    for {
      sender   <- wallet.findPrivateKey(request.sender)
      signer   <- if (request.sender == signerAddress) Right(sender) else wallet.findPrivateKey(signerAddress)
      contract <- AddressOrAlias.fromString(request.dApp)

      tx <- InvokeScriptTransaction
        .create(
          request.version.getOrElse(1.toByte),
          sender.publicKey,
          contract,
          request.call.map(fCallPart => InvokeScriptRequest.buildFunctionCall(fCallPart)),
          request.payment,
          request.fee,
          Asset.fromCompatId(request.feeAssetId.map(s => ByteStr.decodeBase58(s).get)),
          request.timestamp.getOrElse(time.getTimestamp()),
          Proofs.empty,
          request.chainId.getOrElse(AddressScheme.current.chainId)
        )
        .map(_.signWith(signer.privateKey))
    } yield tx

  def invokeScript(request: InvokeScriptRequest, sender: PublicKey): Either[ValidationError, InvokeScriptTransaction] =
    for {
      addressOrAlias <- AddressOrAlias.fromString(request.dApp)
      fcOpt = request.call.map(fCallPart => InvokeScriptRequest.buildFunctionCall(fCallPart))
      tx <- InvokeScriptTransaction.create(
        request.version.getOrElse(1.toByte),
        sender,
        addressOrAlias,
        fcOpt,
        request.payment,
        request.fee,
        Asset.fromCompatId(request.feeAssetId.map(s => ByteStr.decodeBase58(s).get)),
        request.timestamp.getOrElse(0),
        Proofs.empty,
        request.chainId.getOrElse(AddressScheme.current.chainId)
      )

    } yield tx

  def invokeExpression(request: InvokeExpressionRequest, sender: PublicKey): Either[ValidationError, InvokeExpressionTransaction] =
    for {
      expression <- Script.fromBase64String(request.expression).flatMap {
        case e: ExprScript => Right(e)
        case _             => Left(GenericError("Unexpected expression type for InvokeExpression"))
      }
      tx <- InvokeExpressionTransaction.create(
        request.version.getOrElse(1.toByte),
        sender,
        expression,
        request.fee,
        Asset.fromCompatId(request.feeAssetId.map(s => ByteStr.decodeBase58(s).get)),
        request.timestamp.getOrElse(0),
        Proofs.empty
      )
    } yield tx

  def sponsor(request: SponsorFeeRequest, wallet: Wallet, time: Time): Either[ValidationError, SponsorFeeTransaction] =
    sponsor(request, wallet, request.sender, time)

  def sponsor(request: SponsorFeeRequest, wallet: Wallet, signerAddress: String, time: Time): Either[ValidationError, SponsorFeeTransaction] =
    for {
      sender <- wallet.findPrivateKey(request.sender)
      signer <- if (request.sender == signerAddress) Right(sender) else wallet.findPrivateKey(signerAddress)
      assetId <- ByteStr
        .decodeBase58(request.assetId)
        .toEither
        .map(IssuedAsset(_))
        .left
        .map(_ => GenericError(s"Wrong Base58 string: ${request.assetId}"))
      tx <- SponsorFeeTransaction.signed(
        request.version.getOrElse(1.toByte),
        sender.publicKey,
        assetId,
        request.minSponsoredAssetFee,
        request.fee,
        request.timestamp.getOrElse(time.getTimestamp()),
        signer.privateKey
      )
    } yield tx

  def sponsor(request: SponsorFeeRequest, sender: PublicKey): Either[ValidationError, SponsorFeeTransaction] =
    for {
      assetId <- ByteStr
        .decodeBase58(request.assetId)
        .toEither
        .map(IssuedAsset(_))
        .left
        .map(_ => GenericError(s"Wrong Base58 string: ${request.assetId}"))
      tx <- SponsorFeeTransaction.create(
        request.version.getOrElse(1.toByte),
        sender,
        assetId,
        request.minSponsoredAssetFee,
        request.fee,
        request.timestamp.getOrElse(0),
        Proofs.empty
      )
    } yield tx

  def updateAssetInfo(
      request: UpdateAssetInfoRequest,
      wallet: Wallet,
      signerAddress: String,
      time: Time
  ): Either[ValidationError, UpdateAssetInfoTransaction] =
    for {
      sender <- request.sender match {
        case Some(sender) => wallet.findPrivateKey(sender)
        case None         => Left(GenericError("invalid.sender"))
      }
      signer <- if (request.sender.contains(signerAddress)) Right(sender) else wallet.findPrivateKey(signerAddress)
      tx     <- request.copy(timestamp = request.timestamp.orElse(Some(time.getTimestamp()))).toTxFrom(sender.publicKey)
    } yield {
      tx.signWith(signer.privateKey)
    }

  def fromSignedRequest(jsv: JsValue): Either[ValidationError, Transaction] = {
    import InvokeScriptRequest.*
    val chainId = (jsv \ "chainId").asOpt[Byte]
    val typeId  = (jsv \ "type").as[Byte]
    val version = (jsv \ "version").asOpt[Byte](versionReads).getOrElse(1.toByte)

    val pf: PartialFunction[TransactionType.TransactionType, Either[ValidationError, Transaction]] = {
      case TransactionType.Transfer         => jsv.as[TransferRequest].toTx
      case TransactionType.CreateAlias      => jsv.as[CreateAliasRequest].toTx
      case TransactionType.Lease            => jsv.as[LeaseRequest].toTx
      case TransactionType.LeaseCancel      => jsv.as[LeaseCancelRequest].toTx
      case TransactionType.Issue            => jsv.as[IssueRequest].toTx
      case TransactionType.Reissue          => jsv.as[ReissueRequest].toTx
      case TransactionType.Burn             => jsv.as[BurnRequest].toTx
      case TransactionType.MassTransfer     => jsv.as[SignedMassTransferRequest].toTx
      case TransactionType.Data             => jsv.as[SignedDataRequest].toTx
      case TransactionType.InvokeScript     => jsv.as[SignedInvokeScriptRequest].toTx
      case TransactionType.SetScript        => jsv.as[SignedSetScriptRequest].toTx
      case TransactionType.SetAssetScript   => jsv.as[SignedSetAssetScriptRequest].toTx
      case TransactionType.SponsorFee       => jsv.as[SignedSponsorFeeRequest].toTx
      case TransactionType.Exchange         => jsv.as[ExchangeRequest].toTx
      case TransactionType.UpdateAssetInfo  => jsv.as[SignedUpdateAssetInfoRequest].toTx
      case TransactionType.InvokeExpression => jsv.as[SignedInvokeExpressionRequest].toTx
    }

    if (chainId.exists(_ != AddressScheme.current.chainId)) {
      Left(WrongChain(AddressScheme.current.chainId, chainId.get))
    } else
      try pf(TransactionType(typeId))
      catch {
        case _: NoSuchElementException => Left(UnsupportedTypeAndVersion(typeId, version))
        case _: MatchError             => Left(UnsupportedTransactionType)
      }
  }

  def parseRequestAndSign(wallet: Wallet, signerAddress: String, time: Time, jsv: JsObject): Either[ValidationError, Transaction] = {
    import play.api.libs.json.*

    val typeId = (jsv \ "type").as[Byte]

    (jsv \ "version").validateOpt[Byte](versionReads) match {
      case JsError(errors) =>
        Left(GenericError(s"Wrong JSON: ${errors.mkString(", ")}"))
      case JsSuccess(value, _) =>
        val version = value getOrElse (1: Byte)
        val txJson  = jsv ++ Json.obj("version" -> version)

        try
          (TransactionType(typeId): @unchecked) match {
            case TransactionType.Transfer        => TransactionFactory.transferAsset(txJson.as[TransferRequest], wallet, signerAddress, time)
            case TransactionType.CreateAlias     => TransactionFactory.createAlias(txJson.as[CreateAliasRequest], wallet, signerAddress, time)
            case TransactionType.Lease           => TransactionFactory.lease(txJson.as[LeaseRequest], wallet, signerAddress, time)
            case TransactionType.LeaseCancel     => TransactionFactory.leaseCancel(txJson.as[LeaseCancelRequest], wallet, signerAddress, time)
            case TransactionType.Issue           => TransactionFactory.issue(txJson.as[IssueRequest], wallet, signerAddress, time)
            case TransactionType.Reissue         => TransactionFactory.reissue(txJson.as[ReissueRequest], wallet, signerAddress, time)
            case TransactionType.Burn            => TransactionFactory.burn(txJson.as[BurnRequest], wallet, signerAddress, time)
            case TransactionType.MassTransfer    => TransactionFactory.massTransferAsset(txJson.as[MassTransferRequest], wallet, signerAddress, time)
            case TransactionType.Data            => TransactionFactory.data(txJson.as[DataRequest], wallet, signerAddress, time)
            case TransactionType.InvokeScript    => TransactionFactory.invokeScript(txJson.as[InvokeScriptRequest], wallet, signerAddress, time)
            case TransactionType.SetScript       => TransactionFactory.setScript(txJson.as[SetScriptRequest], wallet, signerAddress, time)
            case TransactionType.SetAssetScript  => TransactionFactory.setAssetScript(txJson.as[SetAssetScriptRequest], wallet, signerAddress, time)
            case TransactionType.SponsorFee      => TransactionFactory.sponsor(txJson.as[SponsorFeeRequest], wallet, signerAddress, time)
            case TransactionType.UpdateAssetInfo => TransactionFactory.updateAssetInfo(txJson.as[UpdateAssetInfoRequest], wallet, signerAddress, time)
          } catch {
          case _: NoSuchElementException => Left(UnsupportedTypeAndVersion(typeId, version))
          case _: MatchError             => Left(UnsupportedTransactionType)
        }
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy