How to attach an encrypted message to a payment transaction inside a contract? How can I attach an encrypted message to a payment transaction from inside a contract? In release 2.2.0e it was possible by just calling the SendMoneyCall API with .messageToEncrypt:

SendMoneyCall sendMoneyCall = SendMoneyCall.create(&lt;chain id&gt;)
        .recipient(&lt;recipient account&gt;)
        .amountNQT(&lt;amount&gt;)
        .messageToEncrypt("test message");


this doesn’t seem to work in version 2.2.1.
Do I have to do it manually now? Like first encrypting and compressing the message and then adding it to the transaction via sendMoneyCall.message(“test message”)?
I've asked this question in ardors slack developer channel and post it here for documentation. After a small dialog in which I detailed the occurring error with a small example contract I got an explanation with a working solution:

About the messageToEncrypt parameter, the problem is that when a transaction is submitted by a contract it is first generated without a secretPhrase then signed and broadcasted. This means that message cannot be encrypted when the transaction is generated.
Therefore to submit an encrypted message in your contract you need to encrypt it yourself using the technique in sample contract IgnisArdorRates line 94:

   // Encrypt the message
   EncryptedData encryptedData = context.getConfig().encryptTo(Account.getPublicKey(context.getSenderId()), Convert.toBytes(response.toJSONString(), true), true);

   // Send a message back to the user who requested the information
   SendMessageCall sendMessageCall = SendMessageCall.create(context.getChainOfTransaction().getId()).recipient(context.getSenderId()).
           encryptedMessageData(encryptedData.getData()).
           encryptedMessageNonce(encryptedData.getNonce()).
           encryptedMessageIsPrunable(true);
   return context.createTransaction(sendMessageCall);


I've adapted my example contract according to this answer and post it here to share the solution:

example contract:

package com.jelurida.ardor.contracts;

import nxt.account.Account;
import nxt.addons.AbstractContract;
import nxt.addons.JO;
import nxt.addons.TransactionContext;
import nxt.crypto.EncryptedData;
import nxt.http.callers.SendMoneyCall;
import nxt.http.responses.TransactionResponse;
import nxt.util.Convert;

public class TestContract extends AbstractContract {


    @Override
    public JO processTransaction(TransactionContext context) {
        TransactionResponse transaction = context.getTransaction();
        EncryptedData encryptedData = context.getConfig().encryptTo(Account.getPublicKey(context.getSenderId()), Convert.toBytes("test message", true), true);

        SendMoneyCall sendMoneyCall = SendMoneyCall.create(transaction.getChainId())
                .recipient(transaction.getSender())
                .amountNQT(transaction.getAmount())
                .encryptedMessageData(encryptedData.getData())
                .encryptedMessageNonce(encryptedData.getNonce())
                .encryptedMessageIsPrunable(true);

        return context.createTransaction(sendMoneyCall);
    }
}


unit test:

package com.jelurida.ardor.contracts;

import nxt.addons.JO;
import nxt.blockchain.Block;
import nxt.blockchain.ChildTransaction;
import nxt.blockchain.FxtTransaction;
import nxt.messaging.PrunableEncryptedMessageAppendix;
import nxt.util.Convert;
import org.junit.Assert;
import org.junit.Test;

import static nxt.blockchain.ChildChain.IGNIS;

public class TestContractTest extends AbstractContractTest {

    @Test
    public void encryptedMessageTest() {
        String contractName = ContractTestHelper.deployContract(TestContract.class);

        JO messageJson = new JO();
        messageJson.put("contract", contractName);
        String message = messageJson.toJSONString();
        ContractTestHelper.bobPaysContract(message, IGNIS);
        generateBlock();

        Block lastBlock = getLastBlock();
        boolean transactionFound = false;
        for (FxtTransaction transaction : lastBlock.getFxtTransactions()) {
            for (ChildTransaction childTransaction : transaction.getSortedChildTransactions()) {

                transactionFound = true;
                Assert.assertEquals("test message", extractMessage(childTransaction));

            }
        }
        Assert.assertTrue(transactionFound);
    }

    private String extractMessage(ChildTransaction transaction) {
        PrunableEncryptedMessageAppendix appendix = (PrunableEncryptedMessageAppendix) transaction.getAppendages().stream().filter(a -&gt; a instanceof PrunableEncryptedMessageAppendix).findFirst().orElse(null);
        byte[] compressedData = appendix.getEncryptedData().decrypt(BOB.getSecretPhrase(), ALICE.getPublicKey());
        byte[] data = Convert.uncompress(compressedData);
        return Convert.toString(data, true);
    }


Hope it's helpful :).