Create Publication

We are looking for publications that demonstrate building dApps or smart contracts!
See the full list of Gitcoin bounties that are eligible for rewards.

Tutorial Thumbnail
Beginner · 1 hour

Working with ASA using Java

This tutorial demonstrates the steps involved in creating a basic Algorand Standard Asset (ASA). This tutorial also covers Asset Configuration, Opt-In, Asset Transfer, Asset Freeze, Asset Revoke, and Asset Destroy. You will need three accounts total for this tutorial.

Completed code for this tutorial can be found here AssetExample.java.

Requirements

Optional:

Background

In addition to creating an asset, this tutorial covers, Manage Roles, Opt-In, Transfer, Freeze, Clawback and Destroy functions.

Steps

Part 1: Create an Algorand Standard Asset

Algorand Standard Assets (ASA) allow developers to create fungible and non-fungible assets with a few method calls. ASA’s are highly customizable with parameters that allow developers to define total issuance of an asset, name of an asset, units of an asset, as well as access controls privileges over an asset.

There are a few items to be aware of before getting started with assets as documented in the ASA feature guide overview on the developer portal.

Also, see more information on decimals and fees.

Note: Copy off the AssetID from this part as it will be used in subsequent parts.

Step 1-1. Instantiate Algod Wrapper

In this step, we are passing in the token, server, and port values from a local node or local sandbox instance. You can also connect to remote node using a third-party serice.

// utility function to connect to a node
private AlgodClient connectToNetwork() {
    final String ALGOD_API_ADDR = "localhost";
    final int ALGOD_PORT = 4001;
    final String ALGOD_API_TOKEN = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
    // instantiate the algod wrapper
    AlgodClient client = new AlgodClient(ALGOD_API_ADDR, ALGOD_PORT, ALGOD_API_TOKEN);
    return client;
}

Step 1-2. Recover, define and print Accounts, utility functions and structures

Hardcoding and recovering accounts in this way is not advised, for security purposes, but is sufficient for this part.

Note: If you have not already done so, use this tutorial to Create Accounts

This step will use TestNet accounts that have been pre-created in the Create Accounts tutorial. Be sure to dispense Algos to these accounts before continuing, using the TestNet Dispenser.

package com.algorand.javatest.assets;
import java.math.BigInteger;
import com.algorand.algosdk.v2.client.common.AlgodClient;
import com.algorand.algosdk.account.Account;
import com.algorand.algosdk.v2.client.model.*;
import org.json.JSONArray;
import org.json.JSONObject;
import com.algorand.algosdk.v2.client.common.*;
import com.algorand.algosdk.algod.client.ApiException;
import com.algorand.algosdk.crypto.Address;
import com.algorand.algosdk.transaction.SignedTransaction;
import com.algorand.algosdk.transaction.Transaction;
import com.algorand.algosdk.util.Encoder;

// Show Creating, modifying, sending and listing assets
public class AssetExample {
public AlgodClient client = null;
// utility function to connect to a node
private AlgodClient connectToNetwork() {
    final String ALGOD_API_ADDR = "localhost";
    final int ALGOD_PORT = 4001;
    final String ALGOD_API_TOKEN = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
    AlgodClient client = new AlgodClient(ALGOD_API_ADDR, ALGOD_PORT, ALGOD_API_TOKEN);
    return client;
}
// utility function to print created asset
public void printCreatedAsset(Account account, Long assetID) throws Exception {
    if (client == null)
        this.client = connectToNetwork();
    String accountInfo = client.AccountInformation(account.getAddress()).execute().toString();
    JSONObject jsonObj = new JSONObject(accountInfo.toString());
    JSONArray jsonArray = (JSONArray) jsonObj.get("created-assets");
    if (jsonArray.length() > 0) {
        try {
            for (Object o : jsonArray) {
                JSONObject ca = (JSONObject) o;
                Integer myassetIDInt = (Integer) ca.get("index");
                if (assetID.longValue() == myassetIDInt.longValue()) {
                    System.out.println("Created Asset Info: " + ca.toString(2)); // pretty print
                    break;
                }
            }
        } catch (Exception e) {
            throw (e);
        }
    }
}
// utility function to print asset holding
public void printAssetHolding(Account account, Long assetID) throws Exception {
    if (client == null)
        this.client = connectToNetwork();
    String accountInfo = client.AccountInformation(account.getAddress()).execute().toString();
    JSONObject jsonObj = new JSONObject(accountInfo.toString());
    JSONArray jsonArray = (JSONArray) jsonObj.get("assets");
    if (jsonArray.length() > 0) {
        try {
            for (Object o : jsonArray) {
                JSONObject ca = (JSONObject) o;
                Integer myassetIDInt = (Integer) ca.get("asset-id");
                if (assetID.longValue() == myassetIDInt.longValue()) {
                    System.out.println("Asset Holding Info: " + ca.toString(2)); // pretty print
                    break;
                }
            }
        } catch (Exception e) {
            throw (e);
        }
    }
}

// utility function to wait on a transaction to be confirmed
public void waitForConfirmation(String txID) throws Exception {
    if (client == null)
        this.client = connectToNetwork();

    Long lastRound = client.GetStatus().execute().body().lastRound;

    while (true) {
        try {
            // Check the pending tranactions
            Response<PendingTransactionResponse> pendingInfo = client.PendingTransactionInformation(txID).execute();
            if (pendingInfo.body().confirmedRound != null && pendingInfo.body().confirmedRound > 0) {
                // Got the completed Transaction
                System.out.println(
                        "Transaction " + txID + " confirmed in round " + pendingInfo.body().confirmedRound);
                break;
            }
            lastRound++;
            client.WaitForBlock(lastRound).execute();
        } catch (Exception e) {
            throw (e);
        }
    }
}

// Utility function for sending a raw signed transaction to the network
public String submitTransaction(SignedTransaction signedTx) throws Exception {
    try {
        // Msgpack encode the signed transaction
        byte[] encodedTxBytes = Encoder.encodeToMsgPack(signedTx);
        String id = client.RawTransaction().rawtxn(encodedTxBytes).execute().body().txId;
        ;
        return (id);
    } catch (ApiException e) {
        throw (e);
    }
}
public void assetExample() throws Exception {
if (client == null)
this.client = connectToNetwork();
// recover example accounts

final String account1_mnemonic = <var>your-25-word-mnemonic</var>
final String account2_mnemonic = <var>your-25-word-mnemonic</var>
final String account3_mnemonic = <var>your-25-word-mnemonic</var>
Account acct1 = new Account(account1_mnemonic);
Account acct2 = new Account(account2_mnemonic);
Account acct3 = new Account(account3_mnemonic);
System.out.println("Account1: " + acct1.getAddress());
System.out.println("Account2: " + acct2.getAddress());
System.out.println("Account3: " + acct3.getAddress());
}
public static void main(String args[]) throws Exception {
AssetExample ex = new AssetExample();
ex.assetExample();
}
}
// resource https://developer.algorand.org/docs/features/asa/


Learn More
- Connect to an Algorand Node
- Algorand Node Setup
- Algorand Standalone Account Creation Methods

Step 1-3. Define Asset Create Transaction Parameters

We need to define the Create Asset Transaction parameters.

Note - Paste all remaining snippets at the end of the assetExample method.

// get changing network parameters for each transaction
TransactionParametersResponse params = client.TransactionParams().execute().body();
params.fee = (long) 1000;

// Create the Asset:
BigInteger assetTotal = BigInteger.valueOf(10000);
boolean defaultFrozen = false;
String unitName = "myunit";
String assetName = "my longer asset name";
String url = "http://this.test.com";
String assetMetadataHash = "16efaa3924a6fd9d3a4824799a4ac65d";
Address manager = acct2.getAddress();
Address reserve = acct2.getAddress();
Address freeze = acct2.getAddress();
Address clawback = acct2.getAddress();
Integer decimals = 0;


Learn More
- Algorand Asset Parameters
- Common Transaction Fields

Step 1-4. Create an ASA transaction object

Use the AssetCreateTransactionBuilder() transaction method to create an Asset.

Transaction tx = Transaction.AssetCreateTransactionBuilder().sender(acct1.getAddress()).assetTotal(assetTotal)
    .assetDecimals(decimals).assetUnitName(unitName).assetName(assetName).url(url)
    .metadataHashUTF8(assetMetadataHash).manager(manager).reserve(reserve).freeze(freeze)
    .defaultFrozen(defaultFrozen).clawback(clawback).suggestedParams(params).build();

Step 1-5. Sign Transaction

// Sign the Transaction with creator account
SignedTransaction signedTx = acct1.signTransaction(tx);

Step 1-6. Send Create ASA Transaction to the Blockchain and print the Tx ID

Long assetID = null;
try {
    String id = submitTransaction(signedTx);
    System.out.println("Transaction ID: " + id);
    waitForConfirmation(id);
    // Read the transaction
    PendingTransactionResponse pTrx = client.PendingTransactionInformation(id).execute().body();
    // Now that the transaction is confirmed we can get the assetID
    assetID = pTrx.assetIndex;
    System.out.println("AssetID = " + assetID);         
    printCreatedAsset(acct1, assetID);
    printAssetHolding(acct1, assetID);
} catch (Exception e) {
    e.printStackTrace();
    return;
}

Step 1-7. Check the transaction on a block explorer

Once you’ve completed these steps your output should look like this:

Account1:7JXDK6Q7RAXLOLCT6CSZ36LELOHRUHQXJHZUMAKQKF6Z3IGNG65XK5AVSE 
Account2:AK6Q33PDO4RJZQPHEMODC6PUE5AR2UD4FBU6TNEJOU4UR4KC6XL5PWW5K4 
Account3:IWR4CLLCN2TIVX2QPVVKVR5ER5OZGMWAV5QB2UIPYMPKBPLJZX4C37C4AA

Transaction ID:
XNROI2EMWEN427TM3VGKK7Y35UDI5RY4EDQ7RTMZT2STQVS2ONMA
Transaction
XNROI2EMWEN427TM3VGKK7Y35UDI5RY4EDQ7RTMZT2STQVS2ONMA confirmed
in round 10671117 AssetID=13202871
Created Asset Info:
{
  "index": 13202871,
  "params": {
    "clawback": "AK6Q33PDO4RJZQPHEMODC6PUE5AR2UD4FBU6TNEJOU4UR4KC6XL5PWW5K4",
    "default-frozen": false,
    "creator": "7JXDK6Q7RAXLOLCT6CSZ36LELOHRUHQXJHZUMAKQKF6Z3IGNG65XK5AVSE",
    "total": 10000,
    "unit-name": "myunit",
    "freeze": "AK6Q33PDO4RJZQPHEMODC6PUE5AR2UD4FBU6TNEJOU4UR4KC6XL5PWW5K4",
    "manager": "AK6Q33PDO4RJZQPHEMODC6PUE5AR2UD4FBU6TNEJOU4UR4KC6XL5PWW5K4",
    "decimals": 0,
    "name": "my longer asset name",
    "reserve": "AK6Q33PDO4RJZQPHEMODC6PUE5AR2UD4FBU6TNEJOU4UR4KC6XL5PWW5K4",
    "metadata-hash": "MTZlZmFhMzkyNGE2ZmQ5ZDNhNDgyNDc5OWE0YWM2NWQ=",
    "url": "http://this.test.com"
  }
}
Asset Holding Info:
{
  "amount": 10000,
  "creator": "7JXDK6Q7RAXLOLCT6CSZ36LELOHRUHQXJHZUMAKQKF6Z3IGNG65XK5AVSE",
  "asset-id": 13202871,
  "is-frozen": false
}

You can check the asset creation transaction on a block explorer for reference.

Note: Copy off the AssetID , as it will be used in subsequent steps.


Learn More
- Algorand Block Explorers

Step 1-8. Complete Example

package com.algorand.javatest.assets;

import java.math.BigInteger;
import com.algorand.algosdk.v2.client.common.AlgodClient;
import com.algorand.algosdk.account.Account;
import com.algorand.algosdk.v2.client.model.*;
import org.json.JSONArray;
import org.json.JSONObject;
import com.algorand.algosdk.v2.client.common.*;
import com.algorand.algosdk.algod.client.ApiException;
import com.algorand.algosdk.crypto.Address;
import com.algorand.algosdk.transaction.SignedTransaction;
import com.algorand.algosdk.transaction.Transaction;
import com.algorand.algosdk.util.Encoder;

// Show Creating, modifying, sending and listing assets

public class AssetExample {

public AlgodClient client = null;

// utility function to connect to a node
private AlgodClient connectToNetwork() {
    // final String ALGOD_API_ADDR = "<var>algod-address</var>";
    // final String ALGOD_API_TOKEN = "<var>algod-token</var>";
    final String ALGOD_API_ADDR = "localhost";
    final int ALGOD_PORT = 4001;
    final String ALGOD_API_TOKEN = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
    // instantiate the algod wrapper
    AlgodClient client = new AlgodClient(ALGOD_API_ADDR, ALGOD_PORT, ALGOD_API_TOKEN);
    return client;
}

// utility function to print created asset
public void printCreatedAsset(Account account, Long assetID) throws Exception {
    if (client == null)
        this.client = connectToNetwork();
    String accountInfo = client.AccountInformation(account.getAddress()).execute().toString();
    JSONObject jsonObj = new JSONObject(accountInfo.toString());
    JSONArray jsonArray = (JSONArray) jsonObj.get("created-assets");
    if (jsonArray.length() > 0) {
        try {
            for (Object o : jsonArray) {
                JSONObject ca = (JSONObject) o;
                Integer myassetIDInt = (Integer) ca.get("index");
                if (assetID.longValue() == myassetIDInt.longValue()) {
                    System.out.println("Created Asset Info: " + ca.toString(2)); // pretty print
                    break;
                }
            }
        } catch (Exception e) {
            throw (e);
        }
    }
}

// utility function to print asset holding
public void printAssetHolding(Account account, Long assetID) throws Exception {
    if (client == null)
        this.client = connectToNetwork();
    String accountInfo = client.AccountInformation(account.getAddress()).execute().toString();
    JSONObject jsonObj = new JSONObject(accountInfo.toString());
    JSONArray jsonArray = (JSONArray) jsonObj.get("assets");
    if (jsonArray.length() > 0) {
        try {
            for (Object o : jsonArray) {
                JSONObject ca = (JSONObject) o;
                Integer myassetIDInt = (Integer) ca.get("asset-id");
                if (assetID.longValue() == myassetIDInt.longValue()) {
                    System.out.println("Asset Holding Info: " + ca.toString(2)); // pretty print
                    break;
                }
            }
        } catch (Exception e) {
            throw (e);
        }
    }
}

// utility function to wait on a transaction to be confirmed

public void waitForConfirmation(String txID) throws Exception {
    if (client == null)
        this.client = connectToNetwork();

    Long lastRound = client.GetStatus().execute().body().lastRound;

    while (true) {
        try {
            // Check the pending tranactions
            Response<PendingTransactionResponse> pendingInfo = client.PendingTransactionInformation(txID).execute();
            if (pendingInfo.body().confirmedRound != null && pendingInfo.body().confirmedRound > 0) {
                // Got the completed Transaction
                System.out.println(
                        "Transaction " + txID + " confirmed in round " + pendingInfo.body().confirmedRound);
                break;
            }
            lastRound++;
            client.WaitForBlock(lastRound).execute();
        } catch (Exception e) {
            throw (e);
        }
    }
}


// Utility function for sending a raw signed transaction to the network
public String submitTransaction(SignedTransaction signedTx) throws Exception {
    try {
        // Msgpack encode the signed transaction
        byte[] encodedTxBytes = Encoder.encodeToMsgPack(signedTx);
        String id = client.RawTransaction().rawtxn(encodedTxBytes).execute().body().txId;
        ;
        return (id);
    } catch (ApiException e) {
        throw (e);
    }
}

public void assetExample() throws Exception {
    if (client == null)
        this.client = connectToNetwork();
    // recover example accounts


    final String account1_mnemonic = <var>your-25-word-mnemonic</var>
    final String account2_mnemonic = <var>your-25-word-mnemonic</var>
    final String account3_mnemonic = <var>your-25-word-mnemonic</var>

    Account acct1 = new Account(account1_mnemonic);
    Account acct2 = new Account(account2_mnemonic);
    Account acct3 = new Account(account3_mnemonic);
    System.out.println("Account1: " + acct1.getAddress());
    System.out.println("Account2: " + acct2.getAddress());
    System.out.println("Account3: " + acct3.getAddress());

    // CREATE ASSET
    // get changing network parameters for each transaction
    TransactionParametersResponse params = client.TransactionParams().execute().body();
    params.fee = (long) 1000;

    // Create the Asset:
    BigInteger assetTotal = BigInteger.valueOf(10000);
    boolean defaultFrozen = false;
    String unitName = "myunit";
    String assetName = "my longer asset name";
    String url = "http://this.test.com";
    String assetMetadataHash = "16efaa3924a6fd9d3a4824799a4ac65d";
    Address manager = acct2.getAddress();
    Address reserve = acct2.getAddress();
    Address freeze = acct2.getAddress();
    Address clawback = acct2.getAddress();
    Integer decimals = 0;
    Transaction tx = Transaction.AssetCreateTransactionBuilder().sender(acct1.getAddress()).assetTotal(assetTotal)
            .assetDecimals(decimals).assetUnitName(unitName).assetName(assetName).url(url)
            .metadataHashUTF8(assetMetadataHash).manager(manager).reserve(reserve).freeze(freeze)
            .defaultFrozen(defaultFrozen).clawback(clawback).suggestedParams(params).build();

    // Sign the Transaction with creator account
    SignedTransaction signedTx = acct1.signTransaction(tx);
    Long assetID = null;
    try {
        String id = submitTransaction(signedTx);
        System.out.println("Transaction ID: " + id);
        waitForConfirmation(id);
        // Read the transaction
        PendingTransactionResponse pTrx = client.PendingTransactionInformation(id).execute().body();
        // Now that the transaction is confirmed we can get the assetID
        assetID = pTrx.assetIndex;
        System.out.println("AssetID = " + assetID);
        printCreatedAsset(acct1, assetID);
        printAssetHolding(acct1, assetID);

    } catch (Exception e) {
        e.printStackTrace();
        return;
    }

}

public static void main(String args[]) throws Exception {

    AssetExample ex = new AssetExample();
    ex.assetExample();
}
}

// Copy off the AssetID, as it will be used in subsequent steps.

Note: Copy off the AssetID, as it will be used in subsequent steps.

Part 2: Change Manager Role

This part demonstrates the steps involved in changing / reconfiguring an asset, specifically the asset manager role. The code for the prior part can be commented out that creates an asset, as we will use the assetID generated in all of the following parts. There is a limit of 1000 assets per account for opt-in and creation.

Note:
Paste all remaining code on this and subsequent parts at the end of the assetExample method.

Requirements

  • The AssetID of an Asset created in Part 1.

Background

ASA’s are highly customizable. Of the many characteristics, an asset has certain privileges associated with it including manger, freeze, clawback and reserve functionality. The manager of an asset is the only Algorand account that can destroy an asset and is the only account that can reconfigure the other admin roles of an asset. All other parameters are locked for the life of the asset.

Asset reconfiguration allows the address specified as manager to change any of the special addresses for the asset, such as the reserve address. To keep an address the same, it must be re-specified in each new configuration transaction. Supplying an empty address is the same as turning the associated feature off for this asset. Once a special address is set to the empty address, it can never change again. For example, if an asset configuration transaction specifying clawback=”” were issued, the associated asset could never be revoked from asset holders, and clawback=”” would be true for all time. The strictEmptyAddressChecking argument can help with this behavior: when set to its default true, AssetConfigureTransactionBuilder() will throw an error if any undefined management addresses are passed.

Step 2-1. Create Asset Configuration Transaction

Paste in your assetID from the Part 1. Get changing network parameters for each transaction. Paste code into the bottom of the assetExample method. The manager property needs to be defined for an asset configuration transaction to a different account to become the new manager of the asset (Account 1). Note that AssetConfigureTransactionBuilder() transaction needs to be authorized by the existing manager of the asset (Account 2). The remaining roles are all re-assigned to Account 2.

Long assetID = Long.valueOf(your assetid);
// OPT-IN
// Opt in to Receiving the Asset
// get changing network parameters for each transaction
params = client.TransactionParams().execute().body();
params.fee = (long) 1000;
// configuration changes must be done by
// the manager account - changing manager of the asset
tx = Transaction.AssetConfigureTransactionBuilder().sender(acct2.getAddress()).assetIndex(assetID)
    .manager(acct1.getAddress()).reserve(reserve).freeze(freeze).clawback(clawback).suggestedParams(params)
    .build();


Learn More
- Asset Configuration

Step 2-2. Sign Transaction

// The transaction must be signed by the current manager account
signedTx = acct3.signTransaction(tx);

Step 2-3. Send Transaction to the network

// send the transaction to the network and
try {
    String id = submitTransaction(signedTx);
    System.out.println("Transaction ID: " + id);
    waitForConfirmation(signedTx.transactionID);
    // We can now list the account information for acct3
    // and see that it can accept the new asset
    System.out.println("Account 3 = " + acct3.getAddress().toString());
    printAssetHolding(acct3, assetID);
} catch (Exception e) {
    e.printStackTrace();
    return;
}

Step 2-4. Check the transaction on the block explorer

Once you’ve completed these steps you’re output should look something like this:

Transaction AJLLC4DNOM72FWIIT3ED77ZB5E2L2BRYXRFC3XX3KVKOYZ3OY5DQ confirmed in round 10671459
AssetID = 13202871

Created Asset Info: {
  "index": 13202871,
  "params": {
    "clawback": "AK6Q33PDO4RJZQPHEMODC6PUE5AR2UD4FBU6TNEJOU4UR4KC6XL5PWW5K4",
    "default-frozen": false,
    "creator": "7JXDK6Q7RAXLOLCT6CSZ36LELOHRUHQXJHZUMAKQKF6Z3IGNG65XK5AVSE",
    "total": 10000,
    "unit-name": "myunit",
    "freeze": "AK6Q33PDO4RJZQPHEMODC6PUE5AR2UD4FBU6TNEJOU4UR4KC6XL5PWW5K4",
    "manager": "7JXDK6Q7RAXLOLCT6CSZ36LELOHRUHQXJHZUMAKQKF6Z3IGNG65XK5AVSE",
    "decimals": 0,
    "name": "my longer asset name",
    "reserve": "AK6Q33PDO4RJZQPHEMODC6PUE5AR2UD4FBU6TNEJOU4UR4KC6XL5PWW5K4",
    "metadata-hash": "MTZlZmFhMzkyNGE2ZmQ5ZDNhNDgyNDc5OWE0YWM2NWQ=",
    "url": "http://this.test.com"
  }
}

You can check the asset config transaction on a block explorer for reference.


Learn More
- Algorand Block Explorers

Step 2-5. Complete Example

Paste this code at the bottom of the assetExample method.

// CHANGE MANAGER
// Change Asset Configuration:
assetID = Long.valueOf((your asset id));
// get changing network parameters for each transaction
params = client.TransactionParams().execute().body();
params.fee = (long) 1000;
// configuration changes must be done by
// the manager account - changing manager of the asset

tx = Transaction.AssetConfigureTransactionBuilder().sender(acct2.getAddress()).assetIndex(assetID)
        .manager(acct1.getAddress()).reserve(reserve).freeze(freeze).clawback(clawback).suggestedParams(params)
        .build();

// the transaction must be signed by the current manager account
signedTx = acct2.signTransaction(tx);
// send the transaction to the network
try {
    String id = submitTransaction(signedTx);
    System.out.println("Transaction ID: " + id);
    waitForConfirmation(signedTx.transactionID);
    // the manager should now be the same as the creator
    System.out.println("AssetID = " + assetID);
    printCreatedAsset(acct1, assetID);

} catch (Exception e) {
    e.printStackTrace();
    return;
}
// resource https://developer.algorand.org/docs/features/asa/

Part 3: Opt-In

This part demonstrates the steps involved in “opting” in to receive an Algorand Standard Asset (ASA) To receive an Algorand asset, you must explicitly “opt-in” to receive the asset using the method AssetAcceptTransactionBuilder(). This effectivly sends a 0 amount of the asset to yourself (to the account wanting to receive the asset).


Learn More
- Minimum Account Balance Requirement

Background

Holding an asset increases the size of your balance record. When you hold an asset, your balance record has an entry in its Assets map. map AssetIndex => AssetHolding . The balance record is the row in the database associated with an Algorand account. So having an asset increases the minimum balance requirement for your account by 0.1 ALGO’s. Having someone else increase your minimum balance would be a massive security compromise, so an account must elect to increase the size of their own balance record.

Step 3-1. Create Opt-In Transaction

An asset can be reference by its assetName or its assetID which is generated when you create the asset. Given that the assetName is not unique, it is always recommended to reference an asset by its assetID, which is a unique value. This value was derived in the Create an Asset step.

Opt-In to receiving an asset, and update the fee.

// copy assetID from Create an Asset step 
// and replace valueOf
Long assetID = Long.valueOf(your asset ID);

// OPT-IN
// Opt in to Receiving the Asset
// get changing network parameters for each transaction
params = client.TransactionParams().execute().body();
params.fee = (long) 1000;
// configuration changes must be done by
// the manager account - changing manager of the asset
tx = Transaction.AssetAcceptTransactionBuilder().acceptingAccount(acct3.getAddress()).assetIndex(assetID)
        .suggestedParams(params).build();

Step 3-2. Sign Transaction

The transaction must be signed by the account wishing to opt-in to the asset, in this case - Account 3.

// The transaction must be signed by the current manager account
signedTx = acct3.signTransaction(tx);

Step 3-3. Send the Transaction to the network

Send the transaction to the network and print off account information.

// send the transaction to the network and
try {
    String id = submitTransaction(signedTx);
    System.out.println("Transaction ID: " + id);
    waitForConfirmation(signedTx.transactionID);
    // We can now list the account information for acct3
    // and see that it can accept the new asset
    System.out.println("Account 3 = " + acct3.getAddress().toString());
    printAssetHolding(acct3, assetID);
} catch (Exception e) {
    e.printStackTrace();
    return;
}

Step 3-4. Check the transaction on a block explorer

Once you’ve completed these steps your output should look something like this:

Transaction JQNTO5D2HHMLZI3ZLUFIXGDEVBJ42JRT7VBIK4HUAPJLTXB62RFA confirmed in round 10688582
Account 3 = IWR4CLLCN2TIVX2QPVVKVR5ER5OZGMWAV5QB2UIPYMPKBPLJZX4C37C4AA
Asset Holding Info: {
  "amount": 0,
  "creator": "7JXDK6Q7RAXLOLCT6CSZ36LELOHRUHQXJHZUMAKQKF6Z3IGNG65XK5AVSE",
  "asset-id": 13202871,
  "is-frozen": false
}

You can check the opt-in transaction on a block explorer for reference.


Learn More
- Algorand Block Explorers

Step 3-5. Complete Example

This example assumes that the asset has already been created in Part 1. If the asset has not been created, it will throw an error. Paste this into the bottom of the assetExample method.

// OPT-IN
// Opt in to Receiving the Asset
// assetID = Long.valueOf((your asset id));
// get changing network parameters for each transaction
params = client.TransactionParams().execute().body();
params.fee = (long) 1000;
// configuration changes must be done by
// the manager account - changing manager of the asset
tx = Transaction.AssetAcceptTransactionBuilder().acceptingAccount(acct3.getAddress()).assetIndex(assetID)
        .suggestedParams(params).build();
// The transaction must be signed by the current manager account
signedTx = acct3.signTransaction(tx);
// send the transaction to the network and
try {
    String id = submitTransaction(signedTx);
    System.out.println("Transaction ID: " + id);
    waitForConfirmation(signedTx.transactionID);
    // We can now list the account information for acct3
    // and see that it can accept the new asset
    System.out.println("Account 3 = " + acct3.getAddress().toString());
    printAssetHolding(acct3, assetID);
} catch (Exception e) {
    e.printStackTrace();
    return;
}
}

Part 4: Transfer

This part demonstrates the steps involved in transferring an asset from one account to another. Transfers are authorized by the account that holds the asset to be transferred. Asset transfers are analogous to standard payment transactions but for Algorand Standard Assets.

Background

Transferring an asset allows users to transact with assets after they have issued asset acceptance transactions. The optional closeRemainderTo argument can be used to stop transacting with a particular asset. Now that the opt-in has been done in the step, assets can be transferred.

Step 4-1. Create Asset Transfer Transaction

This code has Account 1 sending 10 assets to Account 3. Set assetID, AssetAmount, sender and receiver.
Use this method to transfer assets:

AssetTransferTransactionBuilder(

// TRANSFER ASSET
// Transfer the Asset:
assetID = Long.valueOf((your asset id));
// get changing network parameters for each transaction
params = client.TransactionParams().execute().body();
params.fee = (long) 1000;
// set asset xfer specific parameters
BigInteger assetAmount = BigInteger.valueOf(10);
Address sender = acct1.getAddress();
Address receiver = acct3.getAddress();
tx = Transaction.AssetTransferTransactionBuilder().sender(sender).assetReceiver(receiver)
        .assetAmount(assetAmount).assetIndex(assetID).suggestedParams(params).build();


Learn More
- Transferring an Asset

Step 4-2. Sign Transfer Transaction

The transaction must be signed by the sender account.

// The transaction must be signed by the sender account
signedTx = acct1.signTransaction(tx);

Step 4-3. Send Transfer Transaction and Print Account Information

Submit the transaction and list the account amount for acct3.
You should see that it now has 10 of the new asset.

// send the transaction to the network
try {
    String id = submitTransaction(signedTx);
    System.out.println("Transaction ID: " + id);
    waitForConfirmation(signedTx.transactionID);
    // list the account information for acct1 and acct3
    System.out.println("Account 3  = " + acct3.getAddress().toString());
    printAssetHolding(acct3, assetID);
    System.out.println("Account 1  = " + acct1.getAddress().toString());
    printAssetHolding(acct1, assetID);
} catch (Exception e) {
    e.printStackTrace();
    return;
}

Step 4-4. Check the transaction on a block explorer

Once you’ve completed these steps your output should look something like this:

Transaction 36J6TZ2CEZ656SNCTKD2I7NY362DEMOAA6LJSLF3NNCDMIFPFXGA confirmed in round 10688860
Account 3  = IWR4CLLCN2TIVX2QPVVKVR5ER5OZGMWAV5QB2UIPYMPKBPLJZX4C37C4AA
Asset Holding Info: {
  "amount": 10,
  "creator": "7JXDK6Q7RAXLOLCT6CSZ36LELOHRUHQXJHZUMAKQKF6Z3IGNG65XK5AVSE",
  "asset-id": 13202871,
  "is-frozen": false
}
Account 1  = 7JXDK6Q7RAXLOLCT6CSZ36LELOHRUHQXJHZUMAKQKF6Z3IGNG65XK5AVSE
Asset Holding Info: {
  "amount": 9990,
  "creator": "7JXDK6Q7RAXLOLCT6CSZ36LELOHRUHQXJHZUMAKQKF6Z3IGNG65XK5AVSE",
  "asset-id": 13202871,
  "is-frozen": false
}

Check the transaction on a block explorer for reference.


Learn More
- Algorand Block Explorers

Step 4-5. Complete Example

This example assumes that the receiver account has already opted in to receiving the asset in the prior Part. If the account has not already opted in for this asset, it will throw an error. Paste this code at the end of the assetExample method.

// TRANSFER ASSET
// Transfer the Asset:
assetID = Long.valueOf((your asset id));
// get changing network parameters for each transaction
params = client.TransactionParams().execute().body();
params.fee = (long) 1000;
// set asset xfer specific parameters
BigInteger assetAmount = BigInteger.valueOf(10);
Address sender = acct1.getAddress();
Address receiver = acct3.getAddress();
tx = Transaction.AssetTransferTransactionBuilder().sender(sender).assetReceiver(receiver)
        .assetAmount(assetAmount).assetIndex(assetID).suggestedParams(params).build();
// The transaction must be signed by the sender account
signedTx = acct1.signTransaction(tx);
// send the transaction to the network
try {
    String id = submitTransaction(signedTx);
    System.out.println("Transaction ID: " + id);
    waitForConfirmation(signedTx.transactionID);
    // list the account information for acct1 and acct3
    System.out.println("Account 3  = " + acct3.getAddress().toString());
    printAssetHolding(acct3, assetID);
    System.out.println("Account 1  = " + acct1.getAddress().toString());
    printAssetHolding(acct1, assetID);
} catch (Exception e) {
    e.printStackTrace();
    return;
}

Part 5: Freeze

This part demonstrates how to freeze an asset holding of a particular account. Freezing an asset means that the asset can no longer be sent to or from that account.

An example use case for this functionality is if you suspect fraudulent activity related to your asset, you can issue a freeze transaction against the offending account’s asset holding while you take the time to investigate. If the account checks out okay, you can issue a follow-up transaction to unfreeze the account so they can resume trade.

Note that the sender of a freeze or unfreeze transaction must be the Freeze Manager, which is specified in the asset’s on-chain configuration. Read more about Asset Freeze Transactions in the docs.

Background

Algorand Standard Assets are built on layer-1 and benefit from the same speed, ease of use, and security as the Algorand blockchain’s native token. Read all about Algorand Standard Assets in the docs.

One of the characteristics of an ASA is the ability to make it freezable, or not. Making an asset freezable means that an account that has an asset in its balance record can be made frozen for that particular asset and will not be able to make asset transfer transactions with that asset. The corresponding account that would trigger a freeze transaction is called the freeze address. If the asset is created without a freeze address, then the asset is forever “un-freezable.”

Note: A frozen account can always close out to the asset creator.

Step 5-1. Create Freeze Transaction

In this first step, we need to define a freezeTarget as well as a freezeState . This asset was made “freezable” when we first created the asset. Setting an address to the freeze parameter in the makeAssetCreateTxn() method makes the asset “freezable.” Setting the freeze address parameter to “”, would make the asset unfreezable and that characteristic cannot be changed retroactively. In this example we will freeze account3 from transacting with an asset. The freeze transaction is sent from the freeze account, Which in this example is account 2. Call AssetFreezeTransactionBuilder to freeze an account.

// FREEZE
// Freeze the Asset:
assetID = Long.valueOf((your asset id));
// get changing network parameters for each transaction
params = client.TransactionParams().execute().body();
params.fee = (long) 1000;
// The asset was created and configured to allow freezing an account
// set asset specific parameters
boolean freezeState = true;
// The sender should be freeze account
tx = Transaction.AssetFreezeTransactionBuilder().sender(acct2.getAddress()).freezeTarget(acct3.getAddress())
        .freezeState(freezeState).assetIndex(assetID).suggestedParams(params).build();


Learn More
- Asset Freeze Transaction

Step 5-2. Sign Freeze Transaction

The freeze transaction needs to be signed by the freeze account (Account 2).

// The transaction must be signed by the freeze account
signedTx = acct2.signTransaction(tx);

Step 5-3. Send Freeze Transaction and Print Account Information

Broadcast the freeze transaction to the blockchain.

// send the transaction to the network
try {
    String id = submitTransaction(signedTx);
    System.out.println("Transaction ID: " + id);
    waitForConfirmation(signedTx.transactionID);
    System.out.println("Account 3 = " + acct3.getAddress().toString());
    printAssetHolding(acct3, assetID);

} catch (Exception e) {
    e.printStackTrace();
    return;
}

Step 5-4. Check the Transaction on a Block Explorer

At this point, Account 3 should have all of its asset holdings frozen and should not be able to trigger a spend transaction of the asset that was frozen. Once you’ve completed these steps you’re output should look something like this:

Transaction HDKOXPTODDIXW4PDMENSCW3NVGJFE2FJDBT75BNZSONRNMWUTKRA confirmed in round 10689223
Account 3 = IWR4CLLCN2TIVX2QPVVKVR5ER5OZGMWAV5QB2UIPYMPKBPLJZX4C37C4AA
Asset Holding Info: {
  "amount": 10,
  "creator": "7JXDK6Q7RAXLOLCT6CSZ36LELOHRUHQXJHZUMAKQKF6Z3IGNG65XK5AVSE",
  "asset-id": 13202871,
  "is-frozen": true
}

You can check the freeze transaction on a block explorer for reference.


Learn More
- Algorand Block Explorers

Step 5-5. Complete Example

This example assumes that the freezeTarget account has an asset transferred to it. Paste this code at the end of the assetExample method.

// FREEZE
// Freeze the Asset:
assetID = Long.valueOf((your asset id));
// get changing network parameters for each transaction
params = client.TransactionParams().execute().body();
params.fee = (long) 1000;
// The asset was created and configured to allow freezing an account
// set asset specific parameters
boolean freezeState = true;
// The sender should be freeze account
tx = Transaction.AssetFreezeTransactionBuilder().sender(acct2.getAddress()).freezeTarget(acct3.getAddress())
        .freezeState(freezeState).assetIndex(assetID).suggestedParams(params).build();
// The transaction must be signed by the freeze account
signedTx = acct2.signTransaction(tx);
// send the transaction to the network
try {
    String id = submitTransaction(signedTx);
    System.out.println("Transaction ID: " + id);
    waitForConfirmation(signedTx.transactionID);
    System.out.println("Account 3 = " + acct3.getAddress().toString());
    printAssetHolding(acct3, assetID);

} catch (Exception e) {
    e.printStackTrace();
    return;
}

Part 6: Revoke

This part demonstrates the steps involved in revoking an asset. Asset revocation is also referred to as clawback functionality. Revoking an asset allows an asset’s revocation manager to transfer assets on behalf of another user. It will only work when issued by the asset’s revocation manager.

When an asset is created, the parameter in the AssetCreateTransactionBuilder() that allows an asset to be “revocable” is called clawback address. If that parameter is set to “”, the asset is “un-revocable” and cannot be retroactively changed to being “revocable”.


Learn More
- Asset Parameters

Background

One of the characteristics of an ASA is the ability to make the asset revocable or not. This functionality is called clawback in the code. The corresponding account that would trigger a revoke transaction is called the clawback address.

Note: A creator account can clawback from a frozen account.

Step 6-1. Create Revoke Transaction

In this step, we need to define a revocation target. In this case, we are revoking assets from Account 3, who’s assets were previously frozen in the prior Part. We are not closing out the asset, so the assetCloseTo parameter is not used. AssetClawbackTransactionBuilder() is the function used to clawback. The clawback address (Account 2) revokes 10 assets from Account 3 and places it back with Account 1.

// REVOKE (or clawback)
// Revoke the asset:
// The asset was also created with the ability for it to be revoked by
// clawbackaddress.
assetID = Long.valueOf((your asset id));
// get changing network parameters for each transaction
params = client.TransactionParams().execute().body();
params.fee = (long) 1000;

// set asset specific parameters
assetAmount = BigInteger.valueOf(10); 
tx = Transaction.AssetClawbackTransactionBuilder().sender(acct2.getAddress())
        .assetClawbackFrom(acct3.getAddress()).assetReceiver(acct1.getAddress()).assetAmount(assetAmount)
        .assetIndex(assetID).suggestedParams(params).build();

Step 6-2. Sign Revoke Asset Transaction

Account 2 is the clawback address, meaning that this is the account that approves and sends the transaction and also pays the 1000 microAlgo fee.

// The transaction must be signed by the clawback account
signedTx = acct2.signTransaction(tx);

Step 6-3. Send Revoke Asset Transaction and Print Account Information

// send the transaction to the network and
// wait for the transaction to be confirmed
try {
    String id = submitTransaction(signedTx);
    System.out.println("Transaction ID: " + id);
    waitForConfirmation(signedTx.transactionID);
    // list the account information for acct1 and acct3
    System.out.println("Account 3  = " + acct3.getAddress().toString());
    printAssetHolding(acct3, assetID);
    System.out.println("Account 1  = " + acct1.getAddress().toString());
    printAssetHolding(acct1, assetID);
} catch (Exception e) {
    e.printStackTrace();
    return;
}

Step 6-4. Check the transaction on a block explorer

Once you’ve completed these steps you’re output should look something like this:

Transaction J3I5IYB2FB22BOXMME3CB57RMNQBOKY454WIKEKDTN444WBBY5NQ confirmed in round 10689433
Account 3  = IWR4CLLCN2TIVX2QPVVKVR5ER5OZGMWAV5QB2UIPYMPKBPLJZX4C37C4AA
Asset Holding Info: {
  "amount": 0,
  "creator": "7JXDK6Q7RAXLOLCT6CSZ36LELOHRUHQXJHZUMAKQKF6Z3IGNG65XK5AVSE",
  "asset-id": 13202871,
  "is-frozen": true
}
Account 1  = 7JXDK6Q7RAXLOLCT6CSZ36LELOHRUHQXJHZUMAKQKF6Z3IGNG65XK5AVSE
Asset Holding Info: {
  "amount": 10000,
  "creator": "7JXDK6Q7RAXLOLCT6CSZ36LELOHRUHQXJHZUMAKQKF6Z3IGNG65XK5AVSE",
  "asset-id": 13202871,
  "is-frozen": false
}

Step 6-5. Complete Example

Paste this code at the end of the assetExample method.

// REVOKE (or clawback)
// Revoke the asset:
// The asset was also created with the ability for it to be revoked by
// clawbackaddress.
Long assetID = Long.valueOf((13202871));
// get changing network parameters for each transaction
TransactionParametersResponse params = client.TransactionParams().execute().body();
params.fee = (long) 1000;

// set asset specific parameters
BigInteger assetAmount = BigInteger.valueOf(10);
Transaction tx = Transaction.AssetClawbackTransactionBuilder().sender(acct2.getAddress())
        .assetClawbackFrom(acct3.getAddress()).assetReceiver(acct1.getAddress()).assetAmount(assetAmount)
        .assetIndex(assetID).suggestedParams(params).build();
// The transaction must be signed by the clawback account
SignedTransaction signedTx = acct2.signTransaction(tx);
// send the transaction to the network and
// wait for the transaction to be confirmed
try {
    String id = submitTransaction(signedTx);
    System.out.println("Transaction ID: " + id);
    waitForConfirmation(signedTx.transactionID);
    // list the account information for acct1 and acct3
    System.out.println("Account 3  = " + acct3.getAddress().toString());
    printAssetHolding(acct3, assetID);
    System.out.println("Account 1  = " + acct1.getAddress().toString());
    printAssetHolding(acct1, assetID);
} catch (Exception e) {
    e.printStackTrace();
    return;
}

// resource https://developer.algorand.org/docs/features/asa/

Part 7: Destroy

This part demonstrates the steps involved in destroying an asset. In order to trigger a destroy asset transaction, the original creator of the asset must be in possession (must have in its balance record) all units of the asset.


Learn More

Destroy an Asset

Step 7-1. Create Destroy Asset Transaction

The only parameter that needs to be defined when conducting an asset destroy operation is the sender address, which needs to be the manager address of the asset. With all assets back in the creator’s account, from the prior Part, the manager (Account 1) destroys the asset.

// DESTROY

// Destroy the Asset:
// All assets should now be back in
// creators account
assetID = Long.valueOf((your asset id));
// get changing network parameters for each transaction
params = client.TransactionParams().execute().body();
params.fee = (long) 1000;

// set destroy asset specific parameters
// The manager must sign and submit the transaction
tx = Transaction.AssetDestroyTransactionBuilder().sender(acct1.getAddress()).assetIndex(assetID)
        .suggestedParams(params).build();

Step 7-2. Sign Destroy Asset Transaction

The transaction must be signed by the manager.

// The transaction must be signed by the manager account
signedTx = acct1.signTransaction(tx);

Step 7-3. Send Destroy Asset Transaction and Print Account Information

// send the transaction to the network
try {
    String id = submitTransaction(signedTx);
    System.out.println("Transaction ID: " + id);
    waitForConfirmation(signedTx.transactionID);
    // We list the account information for acct1
    // and check that the asset is no longer exist
    System.out.println("Account 3 must do a transaction for an amount of 0, ");
    System.out.println("with a assetCloseTo to the creator account, to clear it from its accountholdings");
    System.out.println("Account 1  = " + acct1.getAddress().toString());            
    System.out.println("Nothing should print after this, Account 1 asset is sucessfully deleted");
    printAssetHolding(acct1, assetID);
    printCreatedAsset(acct1, assetID);
} catch (Exception e) {
    e.printStackTrace();
    return;
}

Step 7-4. Check the transaction on the block explorer

Your output should look similar to this:

Transaction RXKAPK2HJAHPEQOCZYGOCDUMI7F7VKC4GI74FKS7AX4OKQNQCLOQ confirmed in round 10689618
Account 3 must do a transaction for an amount of 0, 
with a assetCloseTo to the creator account, to clear it from its accountholdings
Account 1  = 7JXDK6Q7RAXLOLCT6CSZ36LELOHRUHQXJHZUMAKQKF6Z3IGNG65XK5AVSE
Nothing should print after this, Account 1 asset is sucessfully deleted

Notice that although the asset was destroyed, the asset id and associated metadata still exists in the account balance record. When you destroy an asset, the global parameters associated with that asset (manager addresses, name, etc.) are deleted from the creator’s balance record. However, holdings are not deleted automatically – users still need to close out of the deleted asset. This is necessary for technical reasons because we can’t have a single transaction touch potentially thousands of accounts (all the holdings that would need to be deleted).


Learn More
- Algorand Block Explorers

Step 7-5. Complete Example

// DESTROY

// Destroy the Asset:
// All assets should now be back in
// creators account
Long assetID = Long.valueOf((13202871));
// get changing network parameters for each transaction
TransactionParametersResponse params = client.TransactionParams().execute().body();
params.fee = (long) 1000;

// set destroy asset specific parameters
// The manager must sign and submit the transaction
Transaction tx = Transaction.AssetDestroyTransactionBuilder().sender(acct1.getAddress()).assetIndex(assetID)
        .suggestedParams(params).build();
// The transaction must be signed by the manager account
SignedTransaction signedTx = acct1.signTransaction(tx);
// send the transaction to the network
try {
    String id = submitTransaction(signedTx);
    System.out.println("Transaction ID: " + id);
    waitForConfirmation(signedTx.transactionID);
    // We list the account information for acct1
    // and check that the asset is no longer exist
    System.out.println("Account 3 must do a transaction for an amount of 0, ");
    System.out.println("with a assetCloseTo to the creator account, to clear it from its accountholdings");
    System.out.println("Account 1  = " + acct1.getAddress().toString());
    System.out.println("Nothing should print after this, Account 1 asset is sucessfully deleted");
    printAssetHolding(acct1, assetID);
    printCreatedAsset(acct1, assetID);
} catch (Exception e) {
    e.printStackTrace();
    return;
}

// resource https://developer.algorand.org/docs/features/asa/

Completed code for this tutorial can be found here AssetExample.java.