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:
- Account & Backend Provider Setup — create accounts and backend service
- Simple Ada Transfer — for network selection, account creation, faucet, and backend service setup
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.OutputBuildersto createTxOutputBuilder.
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
TxBuilderContextyou can customize few behaviors during transaction building.For example: Select a different
UtxoSelectionStrategyimplementation
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();
}
}