In this section, we will go through the steps required to do a simple token distribution from a sender account to 5 receiver addresses.
First we need to select a network for our transaction. You can choose one of the available public test network.
- Preprod
- Preview
Similarly, choose a backend provider to interact with Cardano blockchain. You can select either Koios or Blockfrost as backend provider.
Please check dependencies page to find the required dependency for your selected backend provider.
:::info
For Blockfrost as backend provider, you need to first create an account on blockfrost.io and get
a Project Id
for the selected network.
For Koios backend provider, you don't need any registration.
:::
We need only one sender account for this example. As we are going to use one of the test network, the following code will generate one testnet address.
Account senderAccount = new Account(Networks.testnet());
String senderAddress = senderAccount.baseAddress();
String senderMnemonic = senderAccount.mnemonic();
If you already have mnemonic for an existing account, you can create a sender account from the mnemonic. For this example, we just need sender account's mnemonic.
String senderMnemonic = "<24 words mnemonic>";
Account senderAccount = new Account(Networks.testnet(), senderMnemonic);
The Account created need to have some Fungible Tokens so we can distribute them to other addresses. In This Example let's assume we have some Charlie3 Tokens already in possession in our Sender Account Base Address. Unlike other account-based blockchains, Cardano supports multiple outputs in a single transaction. So let's define a csv file with our receiving addresses and amount of tokens per address.
addr_test1qxz3zrldxkkwlawrdx2w8ycsnz578r5nrrj96d7e2na9n32cd7gh928plcchqknd3duaxpl5zu86g5gqkd3npv58vvgs8tgtk0,114.0
addr_test1q8ggrexl20slswsnlrct7wm4uhl48eu563rkl75sv3453murys96l34r0lvxd5576q7w806fd8qq3swv45hka0uehkls4vxjka,547.2
addr_test1qyx6htpm9smwvg2w3f4eldtpq3p5ty38perhaafw86gfhgqa4dnr2pglwk0wgejy788uss82tkfxs78qnd0uleds7a9qkadf08,91.2
addr_test1qxzgdkjhepeytjyls3u2ed2x9cfvd4pm40lwsjgk53hm0n7m9j088cqfhvm934lnlsprhwq2c3ha4hl72cs3ul057p2swdlz5u,15.2
addr_test1q9hp9mja7fjyh9hy8mkstn6uuxtn98z3fgryy75qh8hpmhp3hdmcfrjy06m5f7ht8mecgegjt8jerm6l8jwdxcvjuxgsl907rj,15.2
For Blockfrost :
Use the correct Blockfrost url for the selected network and project id to create an instance of BackendService.
String bfProjectId = "preprod...";
BackendService backendService =
new BFBackendService(Constants.BLOCKFROST_PREPROD_URL, bfProjectId);
Note: You can find Blockfrost urls for the supported networks in com.bloxbean.cardano.client.backend.blockfrost.common.Constants
.
or,
For Koios :
BackendService backendService = new KoiosBackendService(KOIOS_TESTNET_URL);
Note: You can find other Koios urls in com.bloxbean.cardano.client.backend.koios.Constants
Let's start with a brief introduction about composable functions.
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.
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());
}
:::info Note: 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. :::
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 FeeCalculators.feeCalculator(senderAddress, 1)
composable function to calculate fee needed in this transaction.
Line-4, Use ChangeOutputAdjustments.adjustChangeOutput(senderAddress, 1)
composable function to adjust change output in a Transaction to meet min ada requirement.
TxBuilder builder = txOutputBuilder
.buildInputs(InputBuilders.createFromSender(senderAddress, senderAddress))
.andThen(FeeCalculators.feeCalculator(senderAddress, 1))
.andThen(ChangeOutputAdjustments.adjustChangeOutput(senderAddress, 1));
Line-1 & Line-2, Create UtxoSupplier
& ProtocolParamsSupplier
from the BackendService
instance.
Line-4, Initialize TxBuilderContext
using UtxoSupplier
and ProtocolParamsSupplier
.
:::info
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));
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.
https://github.com/adabox-aio/airdrop/blob/master/src/main/java/io/adabox/airdrop/Main.java