diff --git a/daml/Main.daml b/daml/Main.daml index 94fb5b6..8a13296 100644 --- a/daml/Main.daml +++ b/daml/Main.daml @@ -11,42 +11,42 @@ import DA.Optional initialize : Script [Party] initialize = do - operator <- allocateParty "Operator" - operatorId <- validateUserId "operator" - alice <- allocateParty "Alice" - aliceId <- validateUserId "alice" - bob <- allocateParty "Bob" - bobId <- validateUserId "bob" - eve <- allocateParty "Eve" - eveId <- validateUserId "eve" - operatorCid <- submit operator do - createCmd Operator with operator = operator, address = "" - orgCid <- submit operator do - exerciseCmd operatorCid Operator_CreateOrganization - with orgName = "Topl" - membershipOfferCid <- submit operator do - exerciseCmd orgCid Organization_InviteMember - with - invitee = alice - membershipAcceptance <- submit alice do - exerciseCmd membershipOfferCid Membershp_Accept - submit operator do - exerciseCmd membershipAcceptance AddUserToOrganization - someOrg <- queryContractKey @Organization operator (operator, "Topl") - membershipOfferCid <- submit operator do - exerciseCmd (fromSome someOrg)._1 Organization_InviteMember - with invitee = bob - membershipAcceptance <- submit bob do - exerciseCmd membershipOfferCid Membershp_Accept - submit operator do - exerciseCmd membershipAcceptance AddUserToOrganization - userInvitationCid <- submit operator do - exerciseCmd operatorCid Operator_InviteUser - with user = alice - aliceUserCid <- submit alice do - exerciseCmd userInvitationCid UserInvitation_Accept - createUser (Daml.Script.User aliceId (Some alice)) [CanActAs alice] - createUser (Daml.Script.User operatorId (Some operator)) [CanActAs operator] - createUser (Daml.Script.User bobId (Some bob)) [CanActAs bob] - createUser (Daml.Script.User eveId (Some eve)) [CanActAs eve] - pure [alice] \ No newline at end of file + -- operator <- allocateParty "operator" + -- operatorId <- validateUserId "operator" + -- alice <- allocateParty "Alice" + -- aliceId <- validateUserId "alice" + -- bob <- allocateParty "Bob" + -- bobId <- validateUserId "bob" + -- eve <- allocateParty "Eve" + -- eveId <- validateUserId "eve" + -- operatorCid <- submit operator do + -- createCmd Operator with operator = operator, address = "AUANVY6RqbJtTnQS1AFTQBjXMFYDknhV8NEixHFLmeZynMxVbp64" + -- orgCid <- submit operator do + -- exerciseCmd operatorCid Operator_CreateOrganization + -- with orgName = "Topl" + -- membershipOfferCid <- submit operator do + -- exerciseCmd orgCid Organization_InviteMember + -- with + -- invitee = alice + -- membershipAcceptance <- submit alice do + -- exerciseCmd membershipOfferCid Membershp_Accept + -- submit operator do + -- exerciseCmd membershipAcceptance AddUserToOrganization + -- someOrg <- queryContractKey @Organization operator (operator, "Topl") + -- membershipOfferCid <- submit operator do + -- exerciseCmd (fromSome someOrg)._1 Organization_InviteMember + -- with invitee = bob + -- membershipAcceptance <- submit bob do + -- exerciseCmd membershipOfferCid Membershp_Accept + -- submit operator do + -- exerciseCmd membershipAcceptance AddUserToOrganization + -- userInvitationCid <- submit operator do + -- exerciseCmd operatorCid Operator_InviteUser + -- with user = alice + -- aliceUserCid <- submit alice do + -- exerciseCmd userInvitationCid UserInvitation_Accept + -- createUser (Daml.Script.User aliceId (Some alice)) [CanActAs alice] + -- createUser (Daml.Script.User operatorId (Some operator)) [CanActAs operator] + -- createUser (Daml.Script.User bobId (Some bob)) [CanActAs bob] + -- createUser (Daml.Script.User eveId (Some eve)) [CanActAs eve] + pure [] \ No newline at end of file diff --git a/daml/Tests/Asset.daml b/daml/Tests/Asset.daml index d86442d..62a3bd4 100644 --- a/daml/Tests/Asset.daml +++ b/daml/Tests/Asset.daml @@ -18,7 +18,6 @@ module Tests.Asset where exerciseCmd orgId Organization_CreateAsset with requestor = alice version = 1 - issuerAddress = org.address shortName = "Wheat" return (operator, alice, bob, org) -- now we create an asset creator @@ -40,6 +39,7 @@ module Tests.Asset where unsignedAssetMintingCid <- submit operator do exerciseCmd assetMintingRequestCid MintingRequest_Accept with txToSign = "ZZZZ" + boxNonce = 123 signedAssetMintingCreated <- submit operator do exerciseCmd unsignedAssetMintingCid UnsignedMinting_Sign with signedMintTx = "WWWWW" @@ -75,6 +75,7 @@ module Tests.Asset where unsignedAssetTransferCid <- submit operator do exerciseCmd assetTransferRequestCid AssetTransferRequest_Accept with txToSign = "ZZZZ" + newBoxNonce = 1234 signedAssetTransferCreated <- submit operator do exerciseCmd unsignedAssetTransferCid UnsignedAssetTransfer_Sign with signedTx = "WWWWW" diff --git a/daml/Topl/Asset.daml b/daml/Topl/Asset.daml index 2074cf0..b439da5 100644 --- a/daml/Topl/Asset.daml +++ b/daml/Topl/Asset.daml @@ -40,6 +40,7 @@ module Topl.Asset where choice MintingRequest_Accept : UnsignedAssetMintingCid with txToSign : Text + boxNonce : Int controller operator do create UnsignedAssetMinting with @@ -66,6 +67,7 @@ module Topl.Asset where quantity : Int someCommitRoot : Optional Text someMetadata : Optional Text + boxNonce : Int fee : Int mintTxToSign : Text where @@ -99,6 +101,7 @@ module Topl.Asset where quantity : Int someCommitRoot : Optional Text someMetadata : Optional Text + boxNonce : Int fee : Int mintTxToSign : Text signedMintTx : Text @@ -122,6 +125,11 @@ module Topl.Asset where controller operator do return () + choice SignedAssetMinting_Fail : SignedAssetMintingCid + with reason : Text + controller operator + do + create this with sendStatus = FailedToSend with .. choice SignedAssetMinting_Confirm : SignedAssetMintingCid with txId : Text @@ -150,15 +158,18 @@ module Topl.Asset where quantity : Int someCommitRoot : Optional Text someMetadata : Optional Text + boxNonce : Int fee : Int where signatory operator, requestor choice AssetTransferRequest_Accept : UnsignedAssetTransferRequestCid with txToSign : Text + newBoxNonce : Int controller operator do create UnsignedAssetTransferRequest with txToSign = txToSign + boxNonce = newBoxNonce .. choice AssetTransferRequest_Reject : () controller operator @@ -182,6 +193,7 @@ module Topl.Asset where quantity : Int someCommitRoot : Optional Text someMetadata : Optional Text + boxNonce : Int fee : Int txToSign : Text where @@ -217,6 +229,7 @@ module Topl.Asset where quantity : Int someCommitRoot : Optional Text someMetadata : Optional Text + boxNonce : Int fee : Int txToSign : Text signedTx : Text diff --git a/daml/Topl/Organization.daml b/daml/Topl/Organization.daml index cbefef5..bfcdd53 100644 --- a/daml/Topl/Organization.daml +++ b/daml/Topl/Organization.daml @@ -106,6 +106,7 @@ module Topl.Organization where quantity = signedAssetMinting.quantity someMetadata = signedAssetMinting.someMetadata someCommitRoot = signedAssetMinting.someCommitRoot + boxNonce = signedAssetMinting.boxNonce .. nonconsuming choice Organization_AddSignedAssetTransfer: AssetIouCid with @@ -124,6 +125,7 @@ module Topl.Organization where quantity = signedAssetTransfer.quantity someMetadata = signedAssetTransfer.someMetadata someCommitRoot = signedAssetTransfer.someCommitRoot + boxNonce = signedAssetTransfer.boxNonce .. @@ -153,7 +155,6 @@ module Topl.Organization where newOrg Organization_CreateAsset with requestor = head members version = assetCode.version - issuerAddress = assetCode.issuerAddress shortName = assetCode.shortName))) (create this with wouldBeMembers = [], members = members, assetCodes = []) assetCodes @@ -171,7 +172,6 @@ module Topl.Organization where with requestor : Party version : Int - issuerAddress : Text shortName : Text controller requestor do @@ -179,7 +179,6 @@ module Topl.Organization where let asset = AssetCode with version = version - issuerAddress = issuerAddress shortName = shortName create AssetCreator with operator = operator @@ -242,7 +241,8 @@ module Topl.Organization where quantity : Int someMetadata : Optional Text assetCode : AssetCode - someCommitRoot : Optional Text + someCommitRoot : Optional Text + boxNonce : Int where signatory operator, organization.members diff --git a/daml/Topl/Utils.daml b/daml/Topl/Utils.daml index c69cf0a..b0c6022 100644 --- a/daml/Topl/Utils.daml +++ b/daml/Topl/Utils.daml @@ -16,7 +16,6 @@ data SendStatus = New data AssetCode = AssetCode with version : Int - issuerAddress : Text shortName : Text deriving (Eq, Show) diff --git a/pom.xml b/pom.xml index abcf142..a9ed3db 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ co.topl.daml topl-daml-api jar - 1.0.0 + 1.0.0-SNAPSHOT UTF-8 @@ -195,10 +195,10 @@ - run-alice + run-asset-operator - alice + assetoperator @@ -208,7 +208,7 @@ exec-maven-plugin 1.6.0 - co.topl.daml.AliceMain + co.topl.daml.AssetOperatorMain ${ledgerhost} ${ledgerport} @@ -218,6 +218,40 @@ ${keyfilepassword} + + + run-demo-asset-operator + + java + + + + + + + + + run-alice + + + alice + + + + + + org.codehaus.mojo + exec-maven-plugin + 1.6.0 + + co.topl.daml.AliceMain + + ${ledgerhost} + ${ledgerport} + ${keyfilename} + ${key1filepassword} + + run-demo-alice diff --git a/src/main/java/co/topl/daml/AliceMain.java b/src/main/java/co/topl/daml/AliceMain.java index 62db726..3a6386e 100644 --- a/src/main/java/co/topl/daml/AliceMain.java +++ b/src/main/java/co/topl/daml/AliceMain.java @@ -12,14 +12,14 @@ import com.daml.ledger.javaapi.data.Transaction; import com.daml.ledger.rxjava.DamlLedgerClient; import com.daml.ledger.rxjava.UserManagementClient; -import co.topl.daml.processors.UnsignedTransferProcessor; +import co.topl.daml.polys.processors.UnsignedTransferProcessor; import akka.actor.ActorSystem; import co.topl.client.Provider; import akka.http.javadsl.model.Uri; import io.reactivex.Flowable; -import co.topl.daml.processors.DamlAppContext; -import co.topl.daml.processors.ToplContext; +import co.topl.daml.DamlAppContext; +import co.topl.daml.ToplContext; public class AliceMain { diff --git a/src/main/java/co/topl/daml/AssetOperatorMain.java b/src/main/java/co/topl/daml/AssetOperatorMain.java new file mode 100644 index 0000000..af20956 --- /dev/null +++ b/src/main/java/co/topl/daml/AssetOperatorMain.java @@ -0,0 +1,85 @@ +package co.topl.daml; + +import java.util.Collections; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.daml.ledger.javaapi.data.FiltersByParty; +import com.daml.ledger.javaapi.data.GetUserRequest; +import com.daml.ledger.javaapi.data.LedgerOffset; +import com.daml.ledger.javaapi.data.NoFilter; +import com.daml.ledger.javaapi.data.Transaction; +import com.daml.ledger.rxjava.DamlLedgerClient; +import com.daml.ledger.rxjava.UserManagementClient; +import co.topl.daml.assets.processors.AssetMintingRequestProcessor; +import co.topl.daml.assets.processors.UnsignedMintingRequestProcessor; +import co.topl.daml.assets.processors.SignedMintingRequestProcessor; +import co.topl.daml.assets.processors.AssetTransferRequestProcessor; +import co.topl.daml.assets.processors.UnsignedAssetTransferRequestProcessor; +import co.topl.daml.assets.processors.SignedAssetTransferRequestProcessor; +import akka.actor.ActorSystem; +import co.topl.client.Provider; +import akka.http.javadsl.model.Uri; + +import io.reactivex.Flowable; +import co.topl.daml.DamlAppContext; +import co.topl.daml.ToplContext; + +public class AssetOperatorMain { + + // FIXME: Divide into smaller methods. + + // constants for referring to users with access to the parties + public static final String OPERATOR_USER = "operator"; + + // application id used for sending commands + private static final String APP_ID = "OperatorMainApp"; + + private static final Logger logger = LoggerFactory.getLogger(OperatorMain.class); + + public static void main(String[] args) { + // FIXME: add more robust handling of parameters. + if (args.length < 4) { + System.err.println("Usage: HOST PORT PROJECTID APIKEY KEYFILENAME KEYFILEPASSWORD"); + System.exit(-1); + } + String host = args[0]; + int port = Integer.parseInt(args[1]); + String projectId = args[2]; + String apiKey = args[3]; + String keyfile = args[4]; + String password = args[5]; + DamlLedgerClient client = DamlLedgerClient.newBuilder(host, port).build(); + client.connect(); + UserManagementClient userManagementClient = client.getUserManagementClient(); + + String operatorParty = userManagementClient.getUser(new GetUserRequest(OPERATOR_USER)).blockingGet().getUser() + .getPrimaryParty().get(); + Flowable transactions = client.getTransactionsClient().getTransactions( + LedgerOffset.LedgerEnd.getInstance(), + new FiltersByParty(Collections.singletonMap(operatorParty, NoFilter.instance)), true); + Uri uri = Uri.create("http://localhost:9085"); + DamlAppContext damlAppContext = new DamlAppContext(APP_ID, operatorParty, client); + ToplContext toplContext = new ToplContext(ActorSystem.create(), new Provider.PrivateTestNet(uri.asScala(), "")); + AssetMintingRequestProcessor assetMintingRequestProcessor = new AssetMintingRequestProcessor(damlAppContext, + toplContext); + transactions.forEach(assetMintingRequestProcessor::processTransaction); + UnsignedMintingRequestProcessor unsignedMintingRequestProcessor = new UnsignedMintingRequestProcessor( + damlAppContext, toplContext, keyfile, password); + transactions.forEach(unsignedMintingRequestProcessor::processTransaction); + SignedMintingRequestProcessor signedMintingRequestProcessor = new SignedMintingRequestProcessor(damlAppContext, + toplContext); + transactions.forEach(signedMintingRequestProcessor::processTransaction); + + AssetTransferRequestProcessor assetTransferRequestProcessor = new AssetTransferRequestProcessor(damlAppContext, + toplContext); + transactions.forEach(assetTransferRequestProcessor::processTransaction); + UnsignedAssetTransferRequestProcessor unsignedTransferRequestProcessor = new UnsignedAssetTransferRequestProcessor( + damlAppContext, toplContext, keyfile, password); + transactions.forEach(unsignedTransferRequestProcessor::processTransaction); + SignedAssetTransferRequestProcessor signedTransferRequestProcessor = new SignedAssetTransferRequestProcessor( + damlAppContext, toplContext); + transactions.forEach(signedTransferRequestProcessor::processTransaction); + } +} diff --git a/src/main/java/co/topl/daml/OperatorMain.java b/src/main/java/co/topl/daml/OperatorMain.java index e52a55b..ed7fb9d 100644 --- a/src/main/java/co/topl/daml/OperatorMain.java +++ b/src/main/java/co/topl/daml/OperatorMain.java @@ -12,15 +12,16 @@ import com.daml.ledger.javaapi.data.Transaction; import com.daml.ledger.rxjava.DamlLedgerClient; import com.daml.ledger.rxjava.UserManagementClient; -import co.topl.daml.processors.TransferProcessor; -import co.topl.daml.processors.SignedTransferProcessor; +import co.topl.daml.polys.processors.TransferRequestProcessor; +import co.topl.daml.polys.processors.SignedTransferProcessor; +import co.topl.daml.assets.processors.AssetMintingRequestProcessor; import akka.actor.ActorSystem; import co.topl.client.Provider; import akka.http.javadsl.model.Uri; import io.reactivex.Flowable; -import co.topl.daml.processors.DamlAppContext; -import co.topl.daml.processors.ToplContext; +import co.topl.daml.DamlAppContext; +import co.topl.daml.ToplContext; public class OperatorMain { @@ -54,9 +55,12 @@ public static void main(String[] args) { DamlAppContext damlAppContext = new DamlAppContext(APP_ID, operatorParty, client); ToplContext toplContext = new ToplContext(ActorSystem.create(), new Provider.ValhallaTestNet(uri.asScala(), apiKey)); - TransferProcessor transferProcessor = new TransferProcessor(damlAppContext, toplContext); + TransferRequestProcessor transferProcessor = new TransferRequestProcessor(damlAppContext, toplContext); transactions.forEach(transferProcessor::processTransaction); SignedTransferProcessor signedTransferProcessor = new SignedTransferProcessor(damlAppContext, toplContext); transactions.forEach(signedTransferProcessor::processTransaction); + AssetMintingRequestProcessor assetMintingRequestProcessor = new AssetMintingRequestProcessor(damlAppContext, + toplContext); + transactions.forEach(assetMintingRequestProcessor::processTransaction); } } diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index ca03174..b857a2f 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -12,6 +12,6 @@ - + \ No newline at end of file diff --git a/src/main/scala/co/topl/daml/processors/AbstractProcessor.scala b/src/main/scala/co/topl/daml/AbstractProcessor.scala similarity index 95% rename from src/main/scala/co/topl/daml/processors/AbstractProcessor.scala rename to src/main/scala/co/topl/daml/AbstractProcessor.scala index 657a91c..d3df87d 100644 --- a/src/main/scala/co/topl/daml/processors/AbstractProcessor.scala +++ b/src/main/scala/co/topl/daml/AbstractProcessor.scala @@ -1,12 +1,12 @@ -package co.topl.daml.processors +package co.topl.daml +import com.daml.ledger.javaapi.data.Command +import com.daml.ledger.javaapi.data.CreatedEvent import com.daml.ledger.javaapi.data.Transaction -import io.reactivex.Single import com.google.protobuf.Empty +import io.reactivex.Single import java.util.stream -import com.daml.ledger.javaapi.data.CreatedEvent -import com.daml.ledger.javaapi.data.Command import scala.concurrent.ExecutionContext abstract class AbstractProcessor(damlAppContext: DamlAppContext, toplContext: ToplContext) { diff --git a/src/main/scala/co/topl/daml/processors/Context.scala b/src/main/scala/co/topl/daml/Context.scala similarity index 90% rename from src/main/scala/co/topl/daml/processors/Context.scala rename to src/main/scala/co/topl/daml/Context.scala index 0d853e7..0f7a80b 100644 --- a/src/main/scala/co/topl/daml/processors/Context.scala +++ b/src/main/scala/co/topl/daml/Context.scala @@ -1,4 +1,4 @@ -package co.topl.daml.processors +package co.topl.daml import co.topl.client.Provider import com.daml.ledger.rxjava.DamlLedgerClient diff --git a/src/main/scala/co/topl/daml/assets/processors/AssetMintingRequestProcessor.scala b/src/main/scala/co/topl/daml/assets/processors/AssetMintingRequestProcessor.scala new file mode 100644 index 0000000..ee57393 --- /dev/null +++ b/src/main/scala/co/topl/daml/assets/processors/AssetMintingRequestProcessor.scala @@ -0,0 +1,161 @@ +package co.topl.daml.assets.processors + +import cats.data.EitherT +import cats.data.NonEmptyChain +import co.topl.akkahttprpc.implicits.client.rpcToClient +import co.topl.attestation.AddressCodec.implicits._ +import co.topl.attestation.PublicKeyPropositionCurve25519._ +import co.topl.attestation._ +import co.topl.daml.AbstractProcessor +import co.topl.daml.DamlAppContext +import co.topl.daml.ToplContext +import co.topl.daml.api.model.topl.asset.AssetMintingRequest +import co.topl.daml.processEventAux +import co.topl.daml.utf8StringToLatin1ByteArray +import co.topl.modifier.box.AssetCode +import co.topl.modifier.box.AssetValue +import co.topl.modifier.box.SecurityRoot +import co.topl.modifier.transaction.AssetTransfer +import co.topl.modifier.transaction.builder.BoxSelectionAlgorithms +import co.topl.rpc.ToplRpc +import co.topl.rpc.implicits.client._ +import co.topl.utils.IdiomaticScalaTransition.implicits.toValidatedOps +import co.topl.utils.Int128 +import co.topl.utils.StringDataTypes +import co.topl.utils.StringDataTypes.Base58Data +import co.topl.utils.StringDataTypes.Latin1Data +import com.daml.ledger.javaapi.data.Command +import com.daml.ledger.javaapi.data.CreatedEvent +import org.slf4j.LoggerFactory +import scodec.bits.ByteVector + +import java.util.stream +import scala.collection.JavaConverters._ +import scala.collection.immutable.ListMap +import scala.concurrent.Await + +import ToplRpc.Transaction.RawAssetTransfer +import co.topl.modifier.transaction.serialization.AssetTransferSerializer +import co.topl.modifier.box.SimpleValue + +class AssetMintingRequestProcessor( + damlAppContext: DamlAppContext, + toplContext: ToplContext +) extends AbstractProcessor(damlAppContext, toplContext) { + + val logger = LoggerFactory.getLogger(classOf[AssetMintingRequestProcessor]) + + import toplContext.provider._ + + // FIXME: improve readbility by breaking into smaller methods + def processEvent( + workflowsId: String, + event: CreatedEvent + ): stream.Stream[Command] = processEventAux(AssetMintingRequest.TEMPLATE_ID, event) { + val mintingRequestContract = + AssetMintingRequest.Contract.fromCreatedEvent(event).id + val assetMintingRequest = + AssetMintingRequest.fromValue( + event.getArguments() + ) + val address = assetMintingRequest.from.asScala.toSeq + .map(Base58Data.unsafe) + .map(_.decodeAddress.getOrThrow()) + val params = + ToplRpc.NodeView.Balances + .Params( + address.toList + ) + val balances = ToplRpc.NodeView.Balances.rpc(params) + import scala.concurrent.duration._ + import scala.language.postfixOps + Await.result( + balances.fold( + failure => { + logger.info("Failed to obtain raw transaction from server.") + logger.debug("Error: {}", failure) + stream.Stream.of( + mintingRequestContract + .exerciseTransferRequest_Reject() + ) + }, + balance => { + val to = + ( + Base58Data.unsafe(assetMintingRequest.to.get(0)._1).decodeAddress.getOrThrow(), + SimpleValue( + balance.values.map(_.Boxes.PolyBox.head.value.quantity).head - Int128(assetMintingRequest.fee) + ) + ) :: assetMintingRequest.to.asScala.toSeq + .map(x => + ( + Base58Data.unsafe(x._1).decodeAddress.getOrThrow(), + AssetValue( + Int128(x._2.intValue()), + AssetCode( + assetMintingRequest.assetCode.version.toByte, + Base58Data + .unsafe(assetMintingRequest.from.get(0)) + .decodeAddress + .getOrThrow(), + Latin1Data.fromData( + utf8StringToLatin1ByteArray(assetMintingRequest.assetCode.shortName) + ) + ), + assetMintingRequest.someCommitRoot + .map(x => SecurityRoot.fromBase58(Base58Data.unsafe(x))) + .orElse(SecurityRoot.empty), + assetMintingRequest.someMetadata + .map(x => + Option( + Latin1Data.fromData( + utf8StringToLatin1ByteArray(x) + ) + ) + ) + .orElse(None) + ) + ) + ) + .toList + + val assetTransfer = AssetTransfer( + balance.values.toList.flatMap(_.Boxes.PolyBox).map(x => (address.head, x.nonce)).toIndexedSeq, + to.toIndexedSeq, + ListMap(), + Int128( + assetMintingRequest.fee + ), + System.currentTimeMillis(), + None, + true + ) + val mintingRequest = AssetTransferSerializer.toBytes( + assetTransfer + ) + val encodedTx = + ByteVector( + mintingRequest + ).toBase58 + logger.info("Successfully generated raw transaction for contract {}.", mintingRequestContract.contractId) + import io.circe.syntax._ + logger.info("The returned json: {}", mintingRequest.asJson) + logger.debug( + "Encoded transaction: {}", + encodedTx + ) + + stream.Stream.of( + mintingRequestContract + .exerciseMintingRequest_Accept( + encodedTx, + assetTransfer.newBoxes.toList.reverse.head.nonce + ) + ) + } + ), + 3 second + ) + } + +} diff --git a/src/main/scala/co/topl/daml/assets/processors/AssetTransferRequestProcessor.scala b/src/main/scala/co/topl/daml/assets/processors/AssetTransferRequestProcessor.scala new file mode 100644 index 0000000..d2dcd4d --- /dev/null +++ b/src/main/scala/co/topl/daml/assets/processors/AssetTransferRequestProcessor.scala @@ -0,0 +1,171 @@ +package co.topl.daml.assets.processors + +import cats.data.EitherT +import cats.data.NonEmptyChain +import co.topl.akkahttprpc.implicits.client.rpcToClient +import co.topl.attestation.AddressCodec.implicits._ +import co.topl.attestation.PublicKeyPropositionCurve25519._ +import co.topl.attestation._ +import co.topl.daml.AbstractProcessor +import co.topl.daml.DamlAppContext +import co.topl.daml.ToplContext +import co.topl.daml.processEventAux +import co.topl.daml.utf8StringToLatin1ByteArray +import co.topl.modifier.box.AssetCode +import co.topl.modifier.box.AssetValue +import co.topl.modifier.box.SecurityRoot +import co.topl.modifier.transaction.AssetTransfer +import co.topl.modifier.transaction.builder.BoxSelectionAlgorithms +import co.topl.rpc.ToplRpc +import co.topl.rpc.implicits.client._ +import co.topl.utils.IdiomaticScalaTransition.implicits.toValidatedOps +import co.topl.utils.Int128 +import co.topl.utils.StringDataTypes +import co.topl.utils.StringDataTypes.Base58Data +import co.topl.utils.StringDataTypes.Latin1Data +import com.daml.ledger.javaapi.data.Command +import com.daml.ledger.javaapi.data.CreatedEvent +import org.slf4j.LoggerFactory +import scodec.bits.ByteVector + +import java.util.stream +import scala.collection.JavaConverters._ +import scala.collection.immutable.ListMap +import scala.concurrent.Await + +import ToplRpc.Transaction.RawAssetTransfer +import co.topl.modifier.transaction.serialization.AssetTransferSerializer +import co.topl.modifier.box.SimpleValue +import co.topl.daml.api.model.topl.asset.AssetTransferRequest + +class AssetTransferRequestProcessor( + damlAppContext: DamlAppContext, + toplContext: ToplContext +) extends AbstractProcessor(damlAppContext, toplContext) { + + val logger = LoggerFactory.getLogger(classOf[AssetTransferRequestProcessor]) + + import toplContext.provider._ + + def processEvent( + workflowsId: String, + event: CreatedEvent + ): stream.Stream[Command] = processEventAux(AssetTransferRequest.TEMPLATE_ID, event) { + val transferRequestContract = + AssetTransferRequest.Contract.fromCreatedEvent(event).id + val assetTransferRequest = + AssetTransferRequest.fromValue( + event.getArguments() + ) + val address = assetTransferRequest.from.asScala.toSeq + .map(Base58Data.unsafe) + .map(_.decodeAddress.getOrThrow()) + val params = + ToplRpc.NodeView.Balances + .Params( + address.toList + ) + val balances = ToplRpc.NodeView.Balances.rpc(params) + + import scala.concurrent.duration._ + import scala.language.postfixOps + Await.result( + balances.fold( + failure => { + logger.info("Failed to obtain raw transaction from server.") + logger.debug("Error: {}", failure) + stream.Stream.of( + transferRequestContract + .exerciseAssetTransferRequest_Reject() + ) + }, + balance => { + val to = + ( + Base58Data.unsafe(assetTransferRequest.to.get(0)._1).decodeAddress.getOrThrow(), + SimpleValue( + balance.values.map(_.Boxes.PolyBox.head.value.quantity).head - Int128(assetTransferRequest.fee) + ) + ) :: assetTransferRequest.to.asScala.toSeq + .map(x => + ( + Base58Data.unsafe(x._1).decodeAddress.getOrThrow(), + AssetValue( + Int128(x._2.intValue()), + AssetCode( + assetTransferRequest.assetCode.version.toByte, + Base58Data + .unsafe(assetTransferRequest.from.get(0)) + .decodeAddress + .getOrThrow(), + Latin1Data.fromData( + utf8StringToLatin1ByteArray(assetTransferRequest.assetCode.shortName) + ) + ), + assetTransferRequest.someCommitRoot + .map(x => SecurityRoot.fromBase58(Base58Data.unsafe(x))) + .orElse(SecurityRoot.empty), + assetTransferRequest.someMetadata + .map(x => + Option( + Latin1Data.fromData( + utf8StringToLatin1ByteArray(x) + ) + ) + ) + .orElse(None) + ) + ) + ) + .toList + + val assetTransfer = AssetTransfer( + balance.values.toList + .flatMap(_.Boxes.PolyBox) + .map(x => (address.head, x.nonce)) + .toIndexedSeq + .++( + balance.values.toList + .flatMap(_.Boxes.AssetBox) + .filter(_.nonce == assetTransferRequest.boxNonce) + .map(x => (address.head, x.nonce)) + .toIndexedSeq + ), + to.toIndexedSeq, + ListMap(), + Int128( + assetTransferRequest.fee + ), + System.currentTimeMillis(), + None, + false + ) + val transferRequest = AssetTransferSerializer.toBytes( + assetTransfer + ) + val encodedTx = + ByteVector( + transferRequest + ).toBase58 + logger.info("Successfully generated raw transaction for contract {}.", transferRequestContract.contractId) + import io.circe.syntax._ + logger.info("The returned json: {}", transferRequest.asJson) + logger.debug( + "Encoded transaction: {}", + encodedTx + ) + + stream.Stream.of( + transferRequestContract + .exerciseAssetTransferRequest_Accept( + encodedTx, + assetTransfer.newBoxes.toList.reverse.head.nonce + ) + ) + } + ), + 3 second + ) + } + +} diff --git a/src/main/scala/co/topl/daml/assets/processors/SignedAssetTransferRequestProcessor.scala b/src/main/scala/co/topl/daml/assets/processors/SignedAssetTransferRequestProcessor.scala new file mode 100644 index 0000000..9574018 --- /dev/null +++ b/src/main/scala/co/topl/daml/assets/processors/SignedAssetTransferRequestProcessor.scala @@ -0,0 +1,161 @@ +package co.topl.daml.assets.processors + +import cats.data.EitherT +import cats.implicits._ +import co.topl.akkahttprpc.InvalidParametersError +import co.topl.akkahttprpc.RpcClientFailure +import co.topl.akkahttprpc.RpcErrorFailure +import co.topl.akkahttprpc.implicits.client._ +import co.topl.attestation.Address +import co.topl.attestation.AddressCodec.implicits._ +import co.topl.attestation.keyManagement.KeyRing +import co.topl.attestation.keyManagement.KeyfileCurve25519 +import co.topl.attestation.keyManagement.KeyfileCurve25519Companion +import co.topl.attestation.keyManagement.PrivateKeyCurve25519 +import co.topl.client.Brambl +import co.topl.daml.AbstractProcessor +import co.topl.daml.DamlAppContext +import co.topl.daml.ToplContext +import co.topl.daml.api.model.da.types +import co.topl.daml.api.model.topl.asset.SignedAssetMinting +import co.topl.daml.api.model.topl.asset.SignedAssetTransfer +import co.topl.daml.api.model.topl.asset.SignedAssetTransfer_Confirm +import co.topl.daml.api.model.topl.organization.Organization +import co.topl.daml.api.model.topl.transfer.SignedTransfer +import co.topl.daml.api.model.topl.transfer.UnsignedTransfer +import co.topl.daml.api.model.topl.utils.SendStatus +import co.topl.daml.api.model.topl.utils.sendstatus.Confirmed +import co.topl.daml.api.model.topl.utils.sendstatus.FailedToSend +import co.topl.daml.api.model.topl.utils.sendstatus.Pending +import co.topl.daml.api.model.topl.utils.sendstatus.Sent +import co.topl.daml.processEventAux +import co.topl.modifier.transaction.serialization.AssetTransferSerializer +import co.topl.rpc.ToplRpc +import co.topl.rpc.implicits.client._ +import co.topl.utils.StringDataTypes +import com.daml.ledger.javaapi.data.Command +import com.daml.ledger.javaapi.data.CreatedEvent +import io.circe.DecodingFailure +import io.circe.parser.parse +import io.circe.syntax._ +import org.slf4j.LoggerFactory +import scodec.bits._ + +import java.io.File +import java.time.Instant +import java.util.Optional +import java.util.stream +import scala.concurrent.Await +import scala.concurrent.Future +import scala.io.Source + +class SignedAssetTransferRequestProcessor( + damlAppContext: DamlAppContext, + toplContext: ToplContext +) extends AbstractProcessor(damlAppContext, toplContext) { + + implicit val networkPrefix = toplContext.provider.networkPrefix + implicit val jsonDecoder = co.topl.modifier.transaction.Transaction.jsonDecoder + + val logger = LoggerFactory.getLogger(classOf[SignedAssetTransferRequestProcessor]) + import toplContext.provider._ + + private def lift[E, A](a: Either[E, A]) = EitherT[Future, E, A](Future(a)) + + private def handlePending( + signedTransferRequest: SignedAssetTransfer, + signedTransferRequestContract: SignedAssetTransfer.ContractId + ): stream.Stream[Command] = { + val result = for { + transactionAsBytes <- + lift( + ByteVector + .fromBase58(signedTransferRequest.signedTx) + .map(_.toArray) + .toRight(RpcErrorFailure(InvalidParametersError(DecodingFailure("Invalid signed tx from base 58", Nil)))) + ) + _ = logger.debug("transactionAsBytes = {}", transactionAsBytes) + signedTx <- lift( + AssetTransferSerializer + .parseBytes(transactionAsBytes) + .toEither + .left + .map(_ => RpcErrorFailure(InvalidParametersError(DecodingFailure("Invalid bytes for transaction", Nil)))) + ) + _ = logger.debug("from address = {}", signedTx.from.head._1.toString()) + broadcastResult <- ToplRpc.Transaction.BroadcastTx.rpc(ToplRpc.Transaction.BroadcastTx.Params(signedTx)) + } yield broadcastResult + import scala.concurrent.duration._ + import scala.language.postfixOps + Await + .result(result.value, 3 second) + .fold( + failure => { + logger.info("Failed to broadcast transaction to server.") + logger.debug("Error: {}", failure) + // FIXME: error handling + stream.Stream.of( + signedTransferRequestContract + .exerciseSignedAssetTransfer_Fail("Failed broadcast to server") + ) + }, + success => { + logger.info("Successfully broadcasted transaction to network.") + logger.debug( + "Server answer: {}", + success.asJson + ) + stream.Stream.of( + signedTransferRequestContract + .exerciseSignedAssetTransfer_Sent(new Sent(Instant.now(), damlAppContext.appId, Optional.empty())) + ) + } + ) + } + + def processEvent( + workflowsId: String, + event: CreatedEvent + ): stream.Stream[Command] = processEventAux(SignedAssetTransfer.TEMPLATE_ID, event) { + val signedTransferRequestContract = + SignedAssetTransfer.Contract.fromCreatedEvent(event).id + val signedTransferRequest = + SignedAssetTransfer.fromValue( + event.getArguments() + ) + if (signedTransferRequest.sendStatus.isInstanceOf[Pending]) { + handlePending(signedTransferRequest, signedTransferRequestContract) + } else if (signedTransferRequest.sendStatus.isInstanceOf[FailedToSend]) { + logger.error("Failed to send contract.") + stream.Stream.of( + signedTransferRequestContract + .exerciseSignedAssetTransfer_Archive() + ) + } else if (signedTransferRequest.sendStatus.isInstanceOf[Sent]) { + logger.info("Successfully sent.") + stream.Stream.of( + signedTransferRequestContract + .exerciseSignedAssetTransfer_Confirm( + new SignedAssetTransfer_Confirm( + (signedTransferRequest.sendStatus.asInstanceOf[Sent]).txHash.orElseGet(() => ""), + 1 + ) + ) + ) + } else if (signedTransferRequest.sendStatus.isInstanceOf[Confirmed]) { + logger.info("Successfully confirmed.") + + stream.Stream.of( + Organization + .byKey(new types.Tuple2(signedTransferRequest.operator, signedTransferRequest.someOrgName.get())) + .exerciseOrganization_AddSignedAssetTransfer(signedTransferRequestContract) + ) + } else { + stream.Stream.of( + signedTransferRequestContract + .exerciseSignedAssetTransfer_Archive() + ) + } + } + +} diff --git a/src/main/scala/co/topl/daml/assets/processors/SignedMintingRequestProcessor.scala b/src/main/scala/co/topl/daml/assets/processors/SignedMintingRequestProcessor.scala new file mode 100644 index 0000000..5506223 --- /dev/null +++ b/src/main/scala/co/topl/daml/assets/processors/SignedMintingRequestProcessor.scala @@ -0,0 +1,166 @@ +package co.topl.daml.assets.processors + +import cats.data.EitherT +import cats.implicits._ +import co.topl.akkahttprpc.InvalidParametersError +import co.topl.akkahttprpc.RpcClientFailure +import co.topl.akkahttprpc.RpcErrorFailure +import co.topl.akkahttprpc.implicits.client._ +import co.topl.attestation.Address +import co.topl.attestation.AddressCodec.implicits._ +import co.topl.attestation.keyManagement.KeyRing +import co.topl.attestation.keyManagement.KeyfileCurve25519 +import co.topl.attestation.keyManagement.KeyfileCurve25519Companion +import co.topl.attestation.keyManagement.PrivateKeyCurve25519 +import co.topl.client.Brambl +import co.topl.daml.AbstractProcessor +import co.topl.daml.DamlAppContext +import co.topl.daml.ToplContext +import co.topl.daml.api.model.da.types +import co.topl.daml.api.model.topl.asset.SignedAssetMinting +import co.topl.daml.api.model.topl.asset.SignedAssetMinting_Confirm +import co.topl.daml.api.model.topl.organization.Organization +import co.topl.daml.api.model.topl.transfer.SignedTransfer +import co.topl.daml.api.model.topl.transfer.UnsignedTransfer +import co.topl.daml.api.model.topl.utils.SendStatus +import co.topl.daml.api.model.topl.utils.sendstatus.Confirmed +import co.topl.daml.api.model.topl.utils.sendstatus.FailedToSend +import co.topl.daml.api.model.topl.utils.sendstatus.Pending +import co.topl.daml.api.model.topl.utils.sendstatus.Sent +import co.topl.daml.processEventAux +import co.topl.modifier.transaction.serialization.AssetTransferSerializer +import co.topl.rpc.ToplRpc +import co.topl.rpc.implicits.client._ +import co.topl.utils.StringDataTypes +import com.daml.ledger.api.v1.CommandsOuterClass +import com.daml.ledger.api.v1.TransactionFilterOuterClass +import com.daml.ledger.api.v1.ValueOuterClass.Identifier +import com.daml.ledger.javaapi.data.Command +import com.daml.ledger.javaapi.data.CreatedEvent +import com.daml.ledger.javaapi.data.TransactionFilter +import io.circe.DecodingFailure +import io.circe.parser.parse +import io.circe.syntax._ +import org.slf4j.LoggerFactory +import scodec.bits._ + +import java.io.File +import java.time.Instant +import java.util.Optional +import java.util.stream +import scala.concurrent.Await +import scala.concurrent.Future +import scala.io.Source + +class SignedMintingRequestProcessor( + damlAppContext: DamlAppContext, + toplContext: ToplContext +) extends AbstractProcessor(damlAppContext, toplContext) { + + implicit val networkPrefix = toplContext.provider.networkPrefix + implicit val jsonDecoder = co.topl.modifier.transaction.Transaction.jsonDecoder + + val logger = LoggerFactory.getLogger(classOf[SignedMintingRequestProcessor]) + import toplContext.provider._ + + private def lift[E, A](a: Either[E, A]) = EitherT[Future, E, A](Future(a)) + + private def handlePending( + signedMintingRequest: SignedAssetMinting, + signedMintingRequestContract: SignedAssetMinting.ContractId + ): stream.Stream[Command] = { + val result = for { + transactionAsBytes <- + lift( + ByteVector + .fromBase58(signedMintingRequest.signedMintTx) + .map(_.toArray) + .toRight(RpcErrorFailure(InvalidParametersError(DecodingFailure("Invalid signed tx from base 58", Nil)))) + ) + _ = logger.debug("transactionAsBytes = {}", transactionAsBytes) + signedTx <- lift( + AssetTransferSerializer + .parseBytes(transactionAsBytes) + .toEither + .left + .map(_ => RpcErrorFailure(InvalidParametersError(DecodingFailure("Invalid bytes for transaction", Nil)))) + ) + _ = logger.debug("from address = {}", signedTx.from.head._1.toString()) + broadcastResult <- ToplRpc.Transaction.BroadcastTx.rpc(ToplRpc.Transaction.BroadcastTx.Params(signedTx)) + } yield broadcastResult + import scala.concurrent.duration._ + import scala.language.postfixOps + Await + .result(result.value, 3 second) + .fold( + failure => { + logger.info("Failed to broadcast transaction to server.") + logger.debug("Error: {}", failure) + // FIXME: error handling + stream.Stream.of( + signedMintingRequestContract + .exerciseSignedAssetMinting_Fail("Failed broadcast to server") + ) + }, + success => { + logger.info("Successfully broadcasted transaction to network.") + logger.debug( + "Server answer: {}", + success.asJson + ) + stream.Stream.of( + signedMintingRequestContract + .exerciseSignedAssetMinting_Sent( + new Sent(Instant.now(), damlAppContext.appId, Optional.of(success.id.toString())) + ) + ) + } + ) + } + + def processEvent( + workflowsId: String, + event: CreatedEvent + ): stream.Stream[Command] = processEventAux(SignedAssetMinting.TEMPLATE_ID, event) { + val signedMintingRequestContract = + SignedAssetMinting.Contract.fromCreatedEvent(event).id + val signedMintingRequest = + SignedAssetMinting.fromValue( + event.getArguments() + ) + if (signedMintingRequest.sendStatus.isInstanceOf[Pending]) { + handlePending(signedMintingRequest, signedMintingRequestContract) + } else if (signedMintingRequest.sendStatus.isInstanceOf[FailedToSend]) { + logger.error("Failed to send contract.") + stream.Stream.of( + signedMintingRequestContract + .exerciseSignedAssetMinting_Archive() + ) + } else if (signedMintingRequest.sendStatus.isInstanceOf[Sent]) { + logger.info("Successfully sent.") + stream.Stream.of( + signedMintingRequestContract + .exerciseSignedAssetMinting_Confirm( + new SignedAssetMinting_Confirm( + (signedMintingRequest.sendStatus.asInstanceOf[Sent]).txHash.orElseGet(() => ""), + 1 + ) + ) + ) + } else if (signedMintingRequest.sendStatus.isInstanceOf[Confirmed]) { + logger.info("Successfully confirmed.") + + stream.Stream.of( + Organization + .byKey(new types.Tuple2(signedMintingRequest.operator, signedMintingRequest.someOrgName.get())) + .exerciseOrganization_AddSignedAssetMinting(signedMintingRequestContract) + ) + } else { + stream.Stream.of( + signedMintingRequestContract + .exerciseSignedAssetMinting_Archive() + ) + } + } + +} diff --git a/src/main/scala/co/topl/daml/assets/processors/UnsignedAssetTransferRequestProcessor.scala b/src/main/scala/co/topl/daml/assets/processors/UnsignedAssetTransferRequestProcessor.scala new file mode 100644 index 0000000..4951a7a --- /dev/null +++ b/src/main/scala/co/topl/daml/assets/processors/UnsignedAssetTransferRequestProcessor.scala @@ -0,0 +1,104 @@ +package co.topl.daml.assets.processors + +import cats.data.EitherT +import co.topl.akkahttprpc.InvalidParametersError +import co.topl.akkahttprpc.RpcClientFailure +import co.topl.akkahttprpc.RpcErrorFailure +import co.topl.attestation.Address +import co.topl.attestation.AddressCodec.implicits._ +import co.topl.attestation.keyManagement.KeyRing +import co.topl.attestation.keyManagement.KeyfileCurve25519 +import co.topl.attestation.keyManagement.KeyfileCurve25519Companion +import co.topl.attestation.keyManagement.PrivateKeyCurve25519 +import co.topl.client.Brambl +import co.topl.daml.AbstractProcessor +import co.topl.daml.DamlAppContext +import co.topl.daml.ToplContext +import co.topl.daml.api.model.topl.asset.UnsignedAssetMinting +import co.topl.daml.api.model.topl.transfer.UnsignedTransfer + +import co.topl.daml.processEventAux +import co.topl.utils.StringDataTypes +import com.daml.ledger.javaapi.data.Command +import com.daml.ledger.javaapi.data.CreatedEvent +import io.circe.DecodingFailure +import io.circe.parser.parse +import org.slf4j.LoggerFactory +import scodec.bits._ + +import java.io.File +import java.util.stream +import scala.concurrent.Future +import scala.io.Source +import co.topl.modifier.transaction.serialization.AssetTransferSerializer +import co.topl.daml.api.model.topl.asset.UnsignedAssetTransferRequest + +class UnsignedAssetTransferRequestProcessor( + damlAppContext: DamlAppContext, + toplContext: ToplContext, + fileName: String, + password: String +) extends AbstractProcessor(damlAppContext, toplContext) { + + implicit val networkPrefix = toplContext.provider.networkPrefix + + val logger = LoggerFactory.getLogger(classOf[UnsignedAssetTransferRequestProcessor]) + + val keyRing: KeyRing[PrivateKeyCurve25519, KeyfileCurve25519] = + KeyRing.empty[PrivateKeyCurve25519, KeyfileCurve25519]()( + toplContext.provider.networkPrefix, + PrivateKeyCurve25519.secretGenerator, + KeyfileCurve25519Companion + ) + + def processEvent( + workflowsId: String, + event: CreatedEvent + ): stream.Stream[Command] = processEventAux(UnsignedAssetTransferRequest.TEMPLATE_ID, event) { + val unsidgnedTransferRequestContract = + UnsignedAssetTransferRequest.Contract.fromCreatedEvent(event).id + val unsidgnedTransferRequest = + UnsignedAssetTransferRequest.fromValue( + event.getArguments() + ) + val keyfile = Source.fromFile(new File(fileName)).getLines().mkString("").mkString + (for { + jsonKey <- parse(keyfile) + address <- Brambl.importCurve25519JsonToKeyRing(jsonKey, password, keyRing) + msg2Sign <- ByteVector + .fromBase58(unsidgnedTransferRequest.txToSign) + .map(_.toArray) + .toRight(RpcErrorFailure(InvalidParametersError(DecodingFailure("Invalid contract", Nil)))) + rawTx <- AssetTransferSerializer.parseBytes(msg2Sign).toEither + signedTx <- Right { + val signFunc = (addr: Address) => keyRing.generateAttestation(addr)(rawTx.messageToSign) + logger.debug("listOfAddresses = {}", keyRing.addresses) + val signatures = keyRing.addresses.map(signFunc).reduce(_ ++ _) + rawTx.copy(attestation = signatures) + } + } yield signedTx).fold( + failure => { + logger.info("Failed to sign transaction.") + logger.debug("Error: {}", failure) + stream.Stream.of( + unsidgnedTransferRequestContract + .exerciseUnsignedAssetTransfer_Archive() + ) + }, + signedTx => { + val signedTxString = ByteVector(AssetTransferSerializer.toBytes(signedTx)).toBase58 + logger.info("Successfully signed transaction for contract {}.", unsidgnedTransferRequestContract.contractId) + logger.debug("signedTx = {}", signedTx) + logger.debug( + "Encoded transaction: {}", + signedTxString + ) + stream.Stream.of( + unsidgnedTransferRequestContract + .exerciseUnsignedAssetTransfer_Sign(signedTxString) + ) + } + ) + } + +} diff --git a/src/main/scala/co/topl/daml/assets/processors/UnsignedMintingRequestProcessor.scala b/src/main/scala/co/topl/daml/assets/processors/UnsignedMintingRequestProcessor.scala new file mode 100644 index 0000000..18456b5 --- /dev/null +++ b/src/main/scala/co/topl/daml/assets/processors/UnsignedMintingRequestProcessor.scala @@ -0,0 +1,103 @@ +package co.topl.daml.assets.processors + +import cats.data.EitherT +import co.topl.akkahttprpc.InvalidParametersError +import co.topl.akkahttprpc.RpcClientFailure +import co.topl.akkahttprpc.RpcErrorFailure +import co.topl.attestation.Address +import co.topl.attestation.AddressCodec.implicits._ +import co.topl.attestation.keyManagement.KeyRing +import co.topl.attestation.keyManagement.KeyfileCurve25519 +import co.topl.attestation.keyManagement.KeyfileCurve25519Companion +import co.topl.attestation.keyManagement.PrivateKeyCurve25519 +import co.topl.client.Brambl +import co.topl.daml.AbstractProcessor +import co.topl.daml.DamlAppContext +import co.topl.daml.ToplContext +import co.topl.daml.api.model.topl.asset.UnsignedAssetMinting +import co.topl.daml.api.model.topl.transfer.UnsignedTransfer + +import co.topl.daml.processEventAux +import co.topl.utils.StringDataTypes +import com.daml.ledger.javaapi.data.Command +import com.daml.ledger.javaapi.data.CreatedEvent +import io.circe.DecodingFailure +import io.circe.parser.parse +import org.slf4j.LoggerFactory +import scodec.bits._ + +import java.io.File +import java.util.stream +import scala.concurrent.Future +import scala.io.Source +import co.topl.modifier.transaction.serialization.AssetTransferSerializer + +class UnsignedMintingRequestProcessor( + damlAppContext: DamlAppContext, + toplContext: ToplContext, + fileName: String, + password: String +) extends AbstractProcessor(damlAppContext, toplContext) { + + implicit val networkPrefix = toplContext.provider.networkPrefix + + val logger = LoggerFactory.getLogger(classOf[UnsignedMintingRequestProcessor]) + + val keyRing: KeyRing[PrivateKeyCurve25519, KeyfileCurve25519] = + KeyRing.empty[PrivateKeyCurve25519, KeyfileCurve25519]()( + toplContext.provider.networkPrefix, + PrivateKeyCurve25519.secretGenerator, + KeyfileCurve25519Companion + ) + + def processEvent( + workflowsId: String, + event: CreatedEvent + ): stream.Stream[Command] = processEventAux(UnsignedAssetMinting.TEMPLATE_ID, event) { + val unsidgnedMintingRequestContract = + UnsignedAssetMinting.Contract.fromCreatedEvent(event).id + val unsidgnedMintingRequest = + UnsignedAssetMinting.fromValue( + event.getArguments() + ) + val keyfile = Source.fromFile(new File(fileName)).getLines().mkString("").mkString + (for { + jsonKey <- parse(keyfile) + address <- Brambl.importCurve25519JsonToKeyRing(jsonKey, password, keyRing) + msg2Sign <- ByteVector + .fromBase58(unsidgnedMintingRequest.mintTxToSign) + .map(_.toArray) + .toRight(RpcErrorFailure(InvalidParametersError(DecodingFailure("Invalid contract", Nil)))) + rawTx <- AssetTransferSerializer.parseBytes(msg2Sign).toEither + signedTx <- Right { + val signFunc = (addr: Address) => keyRing.generateAttestation(addr)(rawTx.messageToSign) + logger.debug("listOfAddresses = {}", keyRing.addresses) + val signatures = keyRing.addresses.map(signFunc).reduce(_ ++ _) + rawTx.copy(attestation = signatures) + } + } yield signedTx).fold( + failure => { + logger.info("Failed to sign transaction.") + logger.debug("Error: {}", failure) + stream.Stream.of( + unsidgnedMintingRequestContract + .exerciseUnsignedMinting_Archive() + ) + }, + signedTx => { + val signedTxString = ByteVector(AssetTransferSerializer.toBytes(signedTx)).toBase58 + logger.info("Successfully signed transaction for contract {}.", unsidgnedMintingRequestContract.contractId) + logger.debug("signedTx = {}", signedTx) + logger.debug( + "Encoded transaction: {}", + signedTxString + ) + stream.Stream.of( + unsidgnedMintingRequestContract + .exerciseUnsignedMinting_Sign(signedTxString) + ) + } + ) + } + +} diff --git a/src/main/scala/co/topl/daml/operator/AssetIouProcessor.scala b/src/main/scala/co/topl/daml/operator/AssetIouProcessor.scala new file mode 100644 index 0000000..003a7f8 --- /dev/null +++ b/src/main/scala/co/topl/daml/operator/AssetIouProcessor.scala @@ -0,0 +1,34 @@ +package co.topl.daml.operator + +import co.topl.daml.DamlAppContext +import co.topl.daml.ToplContext +import co.topl.daml.AbstractProcessor +import com.daml.ledger.javaapi.data.CreatedEvent + +import java.util.stream +import com.daml.ledger.javaapi.data.Command +import co.topl.daml.processEventAux +import co.topl.daml.api.model.topl.organization.AssetIou +import co.topl.daml.api.model.da.types + +class AssetIouProcessor( + damlAppContext: DamlAppContext, + toplContext: ToplContext, + lambda: java.util.function.BiConsumer[AssetIou, AssetIou.ContractId] +) extends AbstractProcessor(damlAppContext, toplContext) { + + def processEvent( + workflowsId: String, + event: CreatedEvent + ): stream.Stream[Command] = processEventAux(AssetIou.TEMPLATE_ID, event) { + val assetIouContract = + AssetIou.Contract.fromCreatedEvent(event).id + val assetIou = + AssetIou.fromValue( + event.getArguments() + ) + lambda.accept(assetIou, assetIouContract) + stream.Stream.empty() + } + +} diff --git a/src/main/scala/co/topl/daml/operator/MembershipAcceptanceProcessor.scala b/src/main/scala/co/topl/daml/operator/MembershipAcceptanceProcessor.scala new file mode 100644 index 0000000..851de47 --- /dev/null +++ b/src/main/scala/co/topl/daml/operator/MembershipAcceptanceProcessor.scala @@ -0,0 +1,31 @@ +package co.topl.daml.operator + +import co.topl.daml.DamlAppContext +import co.topl.daml.ToplContext +import co.topl.daml.AbstractProcessor +import com.daml.ledger.javaapi.data.CreatedEvent + +import java.util.stream +import com.daml.ledger.javaapi.data.Command +import co.topl.daml.processEventAux +import co.topl.daml.api.model.topl.organization.MembershipAcceptance + +class MembershipAcceptanceProcessor( + damlAppContext: DamlAppContext, + toplContext: ToplContext +) extends AbstractProcessor(damlAppContext, toplContext) { + + def processEvent( + workflowsId: String, + event: CreatedEvent + ): stream.Stream[Command] = processEventAux(MembershipAcceptance.TEMPLATE_ID, event) { + val membershipAcceptanceContract = + MembershipAcceptance.Contract.fromCreatedEvent(event).id + + stream.Stream.of( + membershipAcceptanceContract + .exerciseAddUserToOrganization() + ) + } + +} diff --git a/src/main/scala/co/topl/daml/operator/MembershipOfferProcessor.scala b/src/main/scala/co/topl/daml/operator/MembershipOfferProcessor.scala new file mode 100644 index 0000000..b44778c --- /dev/null +++ b/src/main/scala/co/topl/daml/operator/MembershipOfferProcessor.scala @@ -0,0 +1,32 @@ +package co.topl.daml.operator + +import co.topl.daml.DamlAppContext +import co.topl.daml.ToplContext +import co.topl.daml.AbstractProcessor +import com.daml.ledger.javaapi.data.CreatedEvent + +import java.util.stream +import com.daml.ledger.javaapi.data.Command +import co.topl.daml.processEventAux +import co.topl.daml.api.model.topl.organization.MembershipAcceptance +import co.topl.daml.api.model.topl.organization.MembershipOffer + +class MembershipOfferProcessor( + damlAppContext: DamlAppContext, + toplContext: ToplContext +) extends AbstractProcessor(damlAppContext, toplContext) { + + def processEvent( + workflowsId: String, + event: CreatedEvent + ): stream.Stream[Command] = processEventAux(MembershipOffer.TEMPLATE_ID, event) { + val membershipOfferContract = + MembershipOffer.Contract.fromCreatedEvent(event).id + + stream.Stream.of( + membershipOfferContract + .exerciseMembershp_Accept() + ) + } + +} diff --git a/src/main/scala/co/topl/daml/processors/package.scala b/src/main/scala/co/topl/daml/package.scala similarity index 90% rename from src/main/scala/co/topl/daml/processors/package.scala rename to src/main/scala/co/topl/daml/package.scala index 990af84..e3c0f97 100644 --- a/src/main/scala/co/topl/daml/processors/package.scala +++ b/src/main/scala/co/topl/daml/package.scala @@ -1,4 +1,4 @@ -package co.topl.daml +package co.topl import cats.data.EitherT import scala.concurrent.Future @@ -14,7 +14,7 @@ import com.daml.ledger.rxjava.LedgerClient import java.util.UUID import io.reactivex.subjects.SingleSubject -package object processors { +package object daml { type RpcErrorOr[T] = EitherT[Future, RpcClientFailure, T] @@ -49,4 +49,9 @@ package object processors { ) } else return SingleSubject.create() } + + def utf8StringToLatin1ByteArray(str: String) = str.zipWithIndex + .map(e => str.codePointAt(e._2).toByte) + .toArray + } diff --git a/src/main/scala/co/topl/daml/processors/SignedTransferProcessor.scala b/src/main/scala/co/topl/daml/polys/processors/SignedTransferProcessor.scala similarity index 91% rename from src/main/scala/co/topl/daml/processors/SignedTransferProcessor.scala rename to src/main/scala/co/topl/daml/polys/processors/SignedTransferProcessor.scala index 3ff4733..aff89c2 100644 --- a/src/main/scala/co/topl/daml/processors/SignedTransferProcessor.scala +++ b/src/main/scala/co/topl/daml/polys/processors/SignedTransferProcessor.scala @@ -1,41 +1,48 @@ -package co.topl.daml.processors +package co.topl.daml.polys.processors -import java.util.stream -import com.daml.ledger.javaapi.data.Command -import com.daml.ledger.javaapi.data.CreatedEvent -import co.topl.daml.api.model.topl.transfer.UnsignedTransfer -import co.topl.attestation.keyManagement.{KeyRing, KeyfileCurve25519, KeyfileCurve25519Companion, PrivateKeyCurve25519} -import co.topl.client.Brambl +import cats.data.EitherT +import cats.implicits._ +import co.topl.akkahttprpc.InvalidParametersError import co.topl.akkahttprpc.RpcClientFailure +import co.topl.akkahttprpc.RpcErrorFailure +import co.topl.akkahttprpc.implicits.client._ import co.topl.attestation.Address -import io.circe.parser.parse -import scala.io.Source -import java.io.File -import cats.data.EitherT -import co.topl.utils.StringDataTypes import co.topl.attestation.AddressCodec.implicits._ -import scodec.bits._ -import co.topl.akkahttprpc.RpcErrorFailure -import co.topl.akkahttprpc.InvalidParametersError -import io.circe.DecodingFailure -import scala.concurrent.Future +import co.topl.attestation.keyManagement.KeyRing +import co.topl.attestation.keyManagement.KeyfileCurve25519 +import co.topl.attestation.keyManagement.KeyfileCurve25519Companion +import co.topl.attestation.keyManagement.PrivateKeyCurve25519 +import co.topl.client.Brambl +import co.topl.daml.AbstractProcessor +import co.topl.daml.DamlAppContext +import co.topl.daml.ToplContext +import co.topl.daml.api.model.topl.transfer.SignedTransfer +import co.topl.daml.api.model.topl.transfer.UnsignedTransfer +import co.topl.daml.api.model.topl.utils.SendStatus +import co.topl.daml.api.model.topl.utils.sendstatus.FailedToSend +import co.topl.daml.api.model.topl.utils.sendstatus.Pending +import co.topl.daml.api.model.topl.utils.sendstatus.Sent +import co.topl.daml.processEventAux import co.topl.modifier.transaction.PolyTransfer import co.topl.modifier.transaction.serialization.PolyTransferSerializer -import org.slf4j.LoggerFactory -import co.topl.daml.api.model.topl.transfer.SignedTransfer import co.topl.rpc.ToplRpc - -import co.topl.akkahttprpc.implicits.client._ import co.topl.rpc.implicits.client._ +import co.topl.utils.StringDataTypes +import com.daml.ledger.javaapi.data.Command +import com.daml.ledger.javaapi.data.CreatedEvent +import io.circe.DecodingFailure +import io.circe.parser.parse import io.circe.syntax._ -import cats.implicits._ -import scala.concurrent.Await -import co.topl.daml.api.model.topl.utils.sendstatus.Pending -import co.topl.daml.api.model.topl.utils.sendstatus.FailedToSend -import co.topl.daml.api.model.topl.utils.SendStatus -import co.topl.daml.api.model.topl.utils.sendstatus.Sent +import org.slf4j.LoggerFactory +import scodec.bits._ + +import java.io.File import java.time.Instant import java.util.Optional +import java.util.stream +import scala.concurrent.Await +import scala.concurrent.Future +import scala.io.Source class SignedTransferProcessor( damlAppContext: DamlAppContext, diff --git a/src/main/scala/co/topl/daml/processors/TransferProcessor.scala b/src/main/scala/co/topl/daml/polys/processors/TransferRequestProcessor.scala similarity index 94% rename from src/main/scala/co/topl/daml/processors/TransferProcessor.scala rename to src/main/scala/co/topl/daml/polys/processors/TransferRequestProcessor.scala index 73a2aea..d810e85 100644 --- a/src/main/scala/co/topl/daml/processors/TransferProcessor.scala +++ b/src/main/scala/co/topl/daml/polys/processors/TransferRequestProcessor.scala @@ -1,4 +1,4 @@ -package co.topl.daml.processors +package co.topl.daml.polys.processors import com.daml.ledger.rxjava.DamlLedgerClient import io.reactivex.Single @@ -40,13 +40,17 @@ import io.reactivex.subjects.SingleSubject import com.google.protobuf.Empty import org.slf4j.LoggerFactory import co.topl.modifier.transaction.serialization.PolyTransferSerializer +import co.topl.daml.DamlAppContext +import co.topl.daml.ToplContext +import co.topl.daml.AbstractProcessor +import co.topl.daml.processEventAux -class TransferProcessor( +class TransferRequestProcessor( damlAppContext: DamlAppContext, toplContext: ToplContext ) extends AbstractProcessor(damlAppContext, toplContext) { - val logger = LoggerFactory.getLogger(classOf[TransferProcessor]) + val logger = LoggerFactory.getLogger(classOf[TransferRequestProcessor]) import toplContext.provider._ diff --git a/src/main/scala/co/topl/daml/processors/UnsignedTransferProcessor.scala b/src/main/scala/co/topl/daml/polys/processors/UnsignedTransferProcessor.scala similarity index 89% rename from src/main/scala/co/topl/daml/processors/UnsignedTransferProcessor.scala rename to src/main/scala/co/topl/daml/polys/processors/UnsignedTransferProcessor.scala index a2388c0..ac7c0e5 100644 --- a/src/main/scala/co/topl/daml/processors/UnsignedTransferProcessor.scala +++ b/src/main/scala/co/topl/daml/polys/processors/UnsignedTransferProcessor.scala @@ -1,27 +1,35 @@ -package co.topl.daml.processors +package co.topl.daml.polys.processors -import java.util.stream -import com.daml.ledger.javaapi.data.Command -import com.daml.ledger.javaapi.data.CreatedEvent -import co.topl.daml.api.model.topl.transfer.UnsignedTransfer -import co.topl.attestation.keyManagement.{KeyRing, KeyfileCurve25519, KeyfileCurve25519Companion, PrivateKeyCurve25519} -import co.topl.client.Brambl +import cats.data.EitherT +import co.topl.akkahttprpc.InvalidParametersError import co.topl.akkahttprpc.RpcClientFailure +import co.topl.akkahttprpc.RpcErrorFailure import co.topl.attestation.Address -import io.circe.parser.parse -import scala.io.Source -import java.io.File -import cats.data.EitherT -import co.topl.utils.StringDataTypes import co.topl.attestation.AddressCodec.implicits._ -import scodec.bits._ -import co.topl.akkahttprpc.RpcErrorFailure -import co.topl.akkahttprpc.InvalidParametersError -import io.circe.DecodingFailure -import scala.concurrent.Future +import co.topl.attestation.keyManagement.KeyRing +import co.topl.attestation.keyManagement.KeyfileCurve25519 +import co.topl.attestation.keyManagement.KeyfileCurve25519Companion +import co.topl.attestation.keyManagement.PrivateKeyCurve25519 +import co.topl.client.Brambl +import co.topl.daml.AbstractProcessor +import co.topl.daml.DamlAppContext +import co.topl.daml.ToplContext +import co.topl.daml.api.model.topl.transfer.UnsignedTransfer +import co.topl.daml.processEventAux import co.topl.modifier.transaction.PolyTransfer import co.topl.modifier.transaction.serialization.PolyTransferSerializer +import co.topl.utils.StringDataTypes +import com.daml.ledger.javaapi.data.Command +import com.daml.ledger.javaapi.data.CreatedEvent +import io.circe.DecodingFailure +import io.circe.parser.parse import org.slf4j.LoggerFactory +import scodec.bits._ + +import java.io.File +import java.util.stream +import scala.concurrent.Future +import scala.io.Source class UnsignedTransferProcessor( damlAppContext: DamlAppContext,