Simple Token Distribution - Composable Functions
This tutorial shows how to build a token distribution transaction using the Composable Functions API.
Looking for the QuickTx version? Check the Simple Token Distribution 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 Token Distribution — for account setup, CSV format, and backend service
Define Expected Outputs
First we need to define the expected output. Let’s define our TxOutputBuilder to read our csv file and add all accumulate all outputs.
TxOutputBuilder txOutputBuilder = (txBuilderContext, list) -> {};
Scanner scanner = new Scanner(getFileFromResource("file1.csv"));
String policyId = "8e51398904a5d3fc129fbf4f1589701de23c7824d5c90fdb9490e15a";
String assetName = "434841524c4933";
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
String[] parts = line.split(",");
System.out.println(parts[0]);
System.out.println(parts[1]);
Tuple<String, String> policyAssetName = AssetUtil.getPolicyIdAndAssetName(policyId+assetName);
Output output = Output.builder()
.address(parts[0])
.qty((new BigDecimal(parts[1]).multiply(BigDecimal.valueOf(1000000.0))).toBigInteger())
.policyId(policyAssetName._1)
.assetName(policyAssetName._2).build();
txOutputBuilder = txOutputBuilder.and(output.outputBuilder());
}We Multiplied the quantity Value by the amount of decimals this specific token is registered under. in this case, for Charlie3 Token it is 6 zeros after the decimal point, Hence, we multiplied the quantity by a million.
Define transaction using TxBuilder and out-of-box composable functions
Line-1 Create TxBuilder from txOutputBuilder.
Line-2, 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-3, 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 builder = txOutputBuilder
.buildInputs(InputBuilders.createFromSender(senderAddress, senderAddress))
.andThen(BalanceTxBuilders.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.UtxoSupplier;
import com.bloxbean.cardano.client.api.exception.ApiException;
import com.bloxbean.cardano.client.api.model.ProtocolParams;
import com.bloxbean.cardano.client.api.model.Result;
import com.bloxbean.cardano.client.backend.api.BackendService;
import com.bloxbean.cardano.client.backend.api.DefaultUtxoSupplier;
import com.bloxbean.cardano.client.backend.api.TransactionService;
import com.bloxbean.cardano.client.backend.koios.Constants;
import com.bloxbean.cardano.client.backend.koios.KoiosBackendService;
import com.bloxbean.cardano.client.backend.model.TransactionContent;
import com.bloxbean.cardano.client.common.model.Networks;
import com.bloxbean.cardano.client.exception.CborSerializationException;
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.TxOutputBuilder;
import com.bloxbean.cardano.client.function.helper.BalanceTxBuilders;
import com.bloxbean.cardano.client.function.helper.InputBuilders;
import com.bloxbean.cardano.client.transaction.spec.Transaction;
import com.bloxbean.cardano.client.util.AssetUtil;
import com.bloxbean.cardano.client.util.JsonUtil;
import com.bloxbean.cardano.client.util.Tuple;
import java.io.File;
import java.io.FileNotFoundException;
import java.math.BigDecimal;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Scanner;
import static com.bloxbean.cardano.client.function.helper.SignerProviders.signerFrom;
public class Main {
private final BackendService backendService;
private final TransactionService transactionService;
private final UtxoSupplier utxoSupplier;
private final ProtocolParams protocolParams;
public Main() throws ApiException {
backendService = new KoiosBackendService(Constants.KOIOS_PREPROD_URL);
transactionService = backendService.getTransactionService();
utxoSupplier = new DefaultUtxoSupplier(backendService.getUtxoService());
protocolParams = backendService.getEpochService().getProtocolParameters().getValue();
}
public void buildAndDistributeToken() throws FileNotFoundException, URISyntaxException, CborSerializationException, ApiException {
String senderMnemonic = "<mnemonic>"; //TODO Seed Phrase
Account sender = new Account(Networks.testnet(), senderMnemonic);
String senderAddress = sender.baseAddress();
TxOutputBuilder txOutputBuilder = (txBuilderContext, list) -> {};
Scanner scanner = new Scanner(getFileFromResource("file1.txt"));
String policyId = "8e51398904a5d3fc129fbf4f1589701de23c7824d5c90fdb9490e15a";
String assetName = "434841524c4933";
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
String[] parts = line.split(",");
System.out.println(parts[0]);
System.out.println(parts[1]);
Tuple<String, String> policyAssetName = AssetUtil.getPolicyIdAndAssetName(policyId+assetName);
Output output = Output.builder()
.address(parts[0])
.qty((new BigDecimal(parts[1]).multiply(BigDecimal.valueOf(1000000.0))).toBigInteger())
.policyId(policyAssetName._1)
.assetName(policyAssetName._2).build();
txOutputBuilder = txOutputBuilder.and(output.outputBuilder());
}
TxBuilder builder = txOutputBuilder
.buildInputs(InputBuilders.createFromSender(senderAddress, senderAddress))
.andThen(BalanceTxBuilders.balanceTx(senderAddress, 1));
Transaction signedTransaction = TxBuilderContext.init(utxoSupplier,protocolParams)
.buildAndSign(builder, signerFrom(sender));
System.out.println(signedTransaction);
Result<String> result = backendService.getTransactionService().submitTransaction(signedTransaction.serialize());
System.out.println(result);
waitForTransaction(result);
}
public void waitForTransaction(Result<String> result) {
try {
if (result.isSuccessful()) { //Wait for transaction to be mined
int count = 0;
while (count < 60) {
Result<TransactionContent> txnResult = transactionService.getTransaction(result.getValue());
if (txnResult.isSuccessful()) {
System.out.println(JsonUtil.getPrettyJson(txnResult.getValue()));
break;
} else {
System.out.println("Waiting for transaction to be mined ....");
}
count++;
Thread.sleep(2000);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
private File getFileFromResource(String fileName) throws URISyntaxException {
ClassLoader classLoader = getClass().getClassLoader();
URL resource = classLoader.getResource(fileName);
if (resource == null) {
throw new IllegalArgumentException("file not found! " + fileName);
} else {
// failed if files have whitespaces or special characters
//return new File(resource.getFile());
return new File(resource.toURI());
}
}
public static void main(String[] args) throws CborSerializationException, ApiException, FileNotFoundException, URISyntaxException {
Main main = new Main();
main.buildAndDistributeToken();
}
}