Skip to Content
TutorialsLegacy TutorialsSimple Ada Transfer

Simple Ada Transfer - Composable Functions

This tutorial shows how to build a simple Ada transfer transaction using the Composable Functions API.

Looking for the QuickTx version? Check the Simple Ada Transfer tutorial for the recommended QuickTx approach.

Prerequisites

Before starting, make sure you have completed the setup steps:

Composable Functions Overview

A set of FunctionalInterface which can be used to implement composable functions. These functions can be used to build various different types of transactions. The library provides many useful out-of-box implementations of these functions to reduce boilerplate code. You can also write your own function and use it with existing functions.

The followings are the main FunctionalInterface

  • TxBuilder
  • TxOutputBuilder
  • TxInputBuilder
  • TxSigner

TxBuilder : This functional interface helps to transform a transaction object. The apply method in this interface takes a TxBuilderContext and a Transaction object as input parameters. The role of this function is to transform the input transaction object with additional attributes or update existing attributes.

TxOutputBuilder : This functional interface helps to build a TransactionOutput object and add that to the transaction output list. The accept method in this interface takes a TxBuilderContext and a list of TransactionOutput.

TxInputBuilder : This functional interface is responsible to build inputs from the expected outputs.

TxSigner : This interface is responsible to provide transaction signing capability.

Now we have everything to build and submit our first transfer transaction.

Define Expected Outputs

First we need to define the expected output. Let’s say we want to send 10 Ada to receiverAddress1 and 20 Ada to receiverAddress2.

Output output1 = Output.builder() .address(receiverAddress1) .assetName(LOVELACE) .qty(adaToLovelace(10)) .build(); Output output2 = Output.builder() .address(receiverAddress2) .assetName(LOVELACE) .qty(adaToLovelace(20)) .build();

Imports:

import static com.bloxbean.cardano.client.common.ADAConversionUtil.adaToLovelace; import static com.bloxbean.cardano.client.common.CardanoConstants.LOVELACE;

Create a transaction message metadata

Let’s create a CIP20  complaint metadata to add some random message to the transaction. This is not mandatory, but we will use this opportunity to see how a metadata can be added to a transaction.

MessageMetadata metadata = MessageMetadata.create() .add("First transfer transaction");

Define transaction using TxBuilder and out-of-box composable functions

**Line-1, Line-2 ** Create TxOutputBuilder from output1 and compose it with another TxOutputBuilder generated from output2.

Note: Check out various helper methods in com.bloxbean.cardano.client.function.helper.OutputBuilders to create TxOutputBuilder.

Line-3, Invoke TxOutputBuilder.buildInputs with a TxInputBuilder function. TxInputBuilder function builds required inputs based on the expected outputs.

You can select an appropriate composable function from InputBuilders helper class to get a TxInputBuilder. In the below example, InputBuilders.createFromSender(String sender, String changeAddress) out-of-box composable function is used.

Line-4, Use AuxDataProviders.metadataProvider(metadata) composable function to add metadata.

Line-5, Use BalanceTxBuilders.balanceTx composable function to balance the unbalanced transaction. It handles the followings to balance a transaction

  • Fee calculation
  • Adjust the outputs (if required)
TxBuilder txBuilder = output1.outputBuilder() .and(output2.outputBuilder()) .buildInputs(createFromSender(senderAddress, senderAddress)) .andThen(metadataProvider(metadata)) .andThen(balanceTx(senderAddress, 1));

Build and Sign the transaction

Line-1 & Line-2, Create UtxoSupplier & ProtocolParamsSupplier from the BackendService instance.

Line-4, Initialize TxBuilderContext using UtxoSupplier and ProtocolParamsSupplier.

Using TxBuilderContext you can customize few behaviors during transaction building.

For example: Select a different UtxoSelectionStrategy implementation

Line-5, Create TxSigner function using SignerProviders.signerFrom(Account... signers) and use it to build and sign the transaction.

UtxoSupplier utxoSupplier = new DefaultUtxoSupplier(backendService.getUtxoService()); ProtocolParamsSupplier protocolParamsSupplier = new DefaultProtocolParamsSupplier(backendService.getEpochService()); Transaction signedTransaction = TxBuilderContext.init(utxoSupplier, protocolParamsSupplier) .buildAndSign(txBuilder, signerFrom(senderAccount));

Submit the transaction to Cardano network

Now we are ready to submit the transaction to the network. In this example, we are going to submit this transaction through BackendService. Alternatively, you can submit the generated transaction using your own TransactionProcessor implementation.

Result<String> result = backendService.getTransactionService().submitTransaction(signedTransaction.serialize()); System.out.println(result);

If successful, result.isSuccessful() will return true.

Full Source Code

import com.bloxbean.cardano.client.account.Account; import com.bloxbean.cardano.client.api.ProtocolParamsSupplier; import com.bloxbean.cardano.client.api.UtxoSupplier; import com.bloxbean.cardano.client.api.model.Result; import com.bloxbean.cardano.client.backend.api.BackendService; import com.bloxbean.cardano.client.backend.api.DefaultProtocolParamsSupplier; import com.bloxbean.cardano.client.backend.api.DefaultUtxoSupplier; import com.bloxbean.cardano.client.backend.blockfrost.common.Constants; import com.bloxbean.cardano.client.backend.blockfrost.service.BFBackendService; import com.bloxbean.cardano.client.cip.cip20.MessageMetadata; import com.bloxbean.cardano.client.common.model.Networks; import com.bloxbean.cardano.client.function.Output; import com.bloxbean.cardano.client.function.TxBuilder; import com.bloxbean.cardano.client.function.TxBuilderContext; import com.bloxbean.cardano.client.function.helper.InputBuilders; import com.bloxbean.cardano.client.transaction.spec.Transaction; import static com.bloxbean.cardano.client.common.ADAConversionUtil.adaToLovelace; import static com.bloxbean.cardano.client.common.CardanoConstants.LOVELACE; import static com.bloxbean.cardano.client.function.helper.AuxDataProviders.metadataProvider; import static com.bloxbean.cardano.client.function.helper.BalanceTxBuilders.balanceTx; import static com.bloxbean.cardano.client.function.helper.InputBuilders.createFromSender; import static com.bloxbean.cardano.client.function.helper.SignerProviders.signerFrom; public class SimpleTransfer { public void transfer() throws Exception { //Sender account String senderMnemonic = "<24 words mnemonic>"; Account senderAccount = Account.createFromMnemonic(Networks.testnet(), senderMnemonic); String senderAddress = senderAccount.baseAddress(); //Addresses to receive ada String receiverAddress1 = "addr_test1qpjs693nk7makhcax3k7h0hkjyye2adwv3e300dkfwpqj8k2le4j5lg6gd773gdvs7jcnwdxvtztmxawwcdmvm0h870sardwde"; String receiverAddress2 = "addr_test1qzvy33rr24huuqv46ajex99hrcl0dauqcch7meznf4mdyd4sqwzjy5gaynruuwtdmwmdlnasa8t2g2t0fqmf8rhq3e6svxzum4"; // For Blockfrost String bf_projectId = "<Blockfrost Project Id>"; BackendService backendService = new BFBackendService(Constants.BLOCKFROST_PREVIEW_URL, bf_projectId); // For Koios // BackendService backendService = new KoiosBackendService(Constants.KOIOS_PREVIEW_URL); // For Yaci DevKit (Local Devnet) // BackendService backendService = // new BFBackendService("http://localhost:8080/api/v1/", "dummy-key"); // Define expected Outputs Output output1 = Output.builder() .address(receiverAddress1) .assetName(LOVELACE) .qty(adaToLovelace(10)) .build(); Output output2 = Output.builder() .address(receiverAddress2) .assetName(LOVELACE) .qty(adaToLovelace(20)) .build(); // Create a CIP20 message metadata MessageMetadata metadata = MessageMetadata.create() .add("First transfer transaction"); // Define TxBuilder TxBuilder txBuilder = output1.outputBuilder() .and(output2.outputBuilder()) .buildInputs(createFromSender(senderAddress, senderAddress)) .andThen(metadataProvider(metadata)) .andThen(balanceTx(senderAddress, 1)); UtxoSupplier utxoSupplier = new DefaultUtxoSupplier(backendService.getUtxoService()); ProtocolParamsSupplier protocolParamsSupplier = new DefaultProtocolParamsSupplier(backendService.getEpochService()); //Build and sign the transaction Transaction signedTransaction = TxBuilderContext.init(utxoSupplier, protocolParamsSupplier) .buildAndSign(txBuilder, signerFrom(senderAccount)); //Submit the transaction Result<String> result = backendService.getTransactionService().submitTransaction(signedTransaction.serialize()); System.out.println(result); } public static void main(String[] args) throws Exception { new SimpleTransfer().transfer(); } }
Last updated on