In the second Algorand Dapp Dev course, we’ll learn about and code asset management tools
Congratulations on making it to the second tutorial. Only the brave souls make it this far, so well done, and get yourself a coffee before tackling the next exciting Algorand challenge.
In this second blog post of our Algorand blockchain educational series for developers, you’ll learn how to create new assets on the Algorand blockchain and manage them in different ways. Here’s an overview of the skills you’ll learn in this tutorial:
- How to create an Algorand Standard Asset (ASA) and manage its supply?
- Preparing code for ASA
- Asset creation transaction
- How and why do you need to opt in for new ASAs?
- How to transfer assets?
- How to freeze an asset?
- How to revoke an asset using the clawback account and how to prevent it?
- How to change the configuration for an asset?
- Further resources on interacting with the indexer and Algod API
1. How to create an Algorand Standard Asset (ASA) and manage its supply (using goal CLI tool)?
In this section you’ll learn how to create an asset using code. First, let’s understand how we can use `goal` CLI tool to create an asset.
In the below example, the command creates a new asset using an account managed by the wallet in our sandbox. Make sure to use one of the default accounts that have been created on the startup of your sandbox. You can always list all possible accounts in your wallet using: `./sandbox goal account list`.
The command also allows you to define the total supply, a unit name (abbreviation), a full name for your asset, and a URL to point users to a website with more information about the asset. Don’t forget to specify the number of decimals. If you set the decimals property to `0`, you can’t send a transaction with `0.5` amount of this asset.
```
// Command
./sandbox goal asset create --creator HCNMMIL3MKYILOLUO74NF6OPCJU4RU7IE5PX6JYBIT5YHAMQIVO5YADHMU --total 1000000 --unitname bUSD --name "Balgorand USD" --asseturl "https://b-usd.com" --decimals 9
```
This is the resulting JSON for that asset:
```json
{
"asset": {
"created-at-round": 22691,
"deleted": false,
"index": 24,
"params": {
"clawback": "HCNMMIL3MKYILOLUO74NF6OPCJU4RU7IE5PX6JYBIT5YHAMQIVO5YADHMU",
"creator": "HCNMMIL3MKYILOLUO74NF6OPCJU4RU7IE5PX6JYBIT5YHAMQIVO5YADHMU",
"decimals": 9,
"default-frozen": false,
"freeze": "HCNMMIL3MKYILOLUO74NF6OPCJU4RU7IE5PX6JYBIT5YHAMQIVO5YADHMU",
"manager": "HCNMMIL3MKYILOLUO74NF6OPCJU4RU7IE5PX6JYBIT5YHAMQIVO5YADHMU",
"name": "Balgorand USD",
"name-b64": "QmFsZ29yYW5kIFVTRA==",
"reserve": "HCNMMIL3MKYILOLUO74NF6OPCJU4RU7IE5PX6JYBIT5YHAMQIVO5YADHMU",
"total": 1000000,
"unit-name": "bUSD",
"unit-name-b64": "YlVTRA==",
"url": "https://b-usd.com",
"url-b64": "aHR0cHM6Ly9iLXVzZC5jb20="
}
},
"current-round": 22693
}
```
Now, let’s use code to understand better what other properties we can change. The `goal asset create` command can be used for quickly creating an asset but doesn’t allow you to change specific properties.
From the Algorand documentation, you can learn that an asset has immutable and mutable asset parameters. These are the immutable parameters that you can’t change anymore after asset creation.
- Creator (required)
- AssetName (optional, but recommended)
- UnitName (optional, but recommended)
- Total (required)
- Decimals (required)
- DefaultFrozen (required): You can freeze an asset, so you can’t transfer it when it’s created
- URL (optional)
- MetaDataHash (optional): Used to include some extra data/information about the asset
Further, we have a list of mutable parameters that you can change after the asset has been created:
1. Manager Address: The manager account is the only account that can authorize transactions to re-configure or destroy an asset. In other words, this address can be used to send a “change config” transaction to change the mutable asset parameters.
2. Reserve Address: Specifying a reserve account signifies that non-minted assets will reside in that account instead of the default creator account. If you transfer assets out of the reserve account to the creator account, these asset units are “minted”. If you specify a new reserve address, you must make sure the new account has opted into the asset. Then, issue a transaction to transfer all assets to the new reserve.
3. Freeze Address: The freeze account is allowed to freeze or unfreeze the asset holdings for a specific account. When an account is frozen, it cannot send or receive the frozen asset. In traditional finance, freezing assets may be performed to restrict liquidation of company stock, to investigate suspected criminal activity, or to deny-list certain accounts. If the `DefaultFrozen` state is set to `True`, you can use the unfreeze action to authorize certain accounts to trade the asset (e.g. after passing KYC/AML checks).
4. Clawback Address: The clawback address represents an account that can transfer assets from and to any asset holder (assuming they have opted-in). Use this if you need the option to revoke assets from an account (like if they breach certain contractual obligations tied to holding the asset). In traditional finance, this sort of transaction is referred to as a clawback.
Below are some nuggets of information about ASA supply management.
ASA supply management
When you create an asset, you need to specify the total supply you want to create upfront. You must understand that you can’t change the supply anymore after creation. Therefore, if your project requires a dynamic supply, it’s recommended to mint a large supply and move assets between the creator and reserve address to increase or decrease the supply.
Also, it’s not possible to burn an asset. Instead, by moving tokens back to the reserve address, you take them out of the circulating supply.
Now, let’s use code to create and manage an asset in various ways.
2. Preparing code for ASA creation
To get started, we need to create a new JavaScript file (`.js`) and add the following code to create a connection object using the `algosdk`.
```js
const algosdk = require('algosdk');
const algodToken = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
const algodServer = 'http://localhost';
const algodPort = 4001;
let algodClient = new algosdk.Algodv2(algodToken, algodServer, algodPort);
```
Before we can start, let’s import some accounts to make it easier to follow this tutorial.
```js
const account = algosdk.mnemonicToSecretKey("cloth intact extend pull sad miss popular mansion lobster napkin space oyster correct warm miss neither confirm snow virtual evoke era lock amused abandon first");
console.log(account.addr) // HCNMMIL3MKYILOLUO74NF6OPCJU4RU7IE5PX6JYBIT5YHAMQIVO5YADHMU
const account2 = algosdk.mnemonicToSecretKey("canal enact luggage spring similar zoo couple stomach shoe laptop middle wonder eager monitor weather number heavy skirt siren purity spell maze warfare ability ten");
console.log(account2.addr) // ATTR6RUEHHBHXKUHT4GUOYWNBVDV2GJ5FHUWCSFZLHD55EVKZWOWSM7ABQ
```
Don’t forget to fund each account using `goal` from one of your pre-funded accounts in the sandbox.
```sh
./sandbox goal clerk send -a 5000000 -f <prefunded-account> -t HCNMMIL3MKYILOLUO74NF6OPCJU4RU7IE5PX6JYBIT5YHAMQIVO5YADHMU
./sandbox goal clerk send -a 5000000 -f <prefunded-account> -t ATTR6RUEHHBHXKUHT4GUOYWNBVDV2GJ5FHUWCSFZLHD55EVKZWOWSM7ABQ
```
Now that’s done, let’s create the “asset create” transaction.
3. Asset creation transaction
Let’s create a new asset. Add the following function to your code and don’t forget to add the line to call the function. You can find information about asset creation in the documentation.
First, the code retrieves the transaction parameters which we need for constructing the transaction. It includes the current’s network fee, ID of the network, genesis hash, and the begin and end round for which this transaction is valid. The `makeAssetCreateTxnWithSuggestedParams` needs this information to construct the transaction.
As you can see in the code, we can now define the mutable parameters as well, which wasn’t possible using `goal`. To keep things simple, let’s use the creator address for all other mutable fields. Our new asset is called `Alogrand USD`, `aUSD` in short.
The asset parameters reference can be found here in case you want more information.
Execute your file using the `node` command: `node myfile.js`.
```js
const firstAsset = async function() {
try {
let params = await algodClient.getTransactionParams().do();
console.log(params);
let txn = algosdk.makeAssetCreateTxnWithSuggestedParams(
account.addr, // from address
algosdk.encodeObj("first asset"), // note
1000000, // total supply
2, // decimals
false, // default frozen: we want to transact the asset
account.addr, // manager account
account.addr, // reserve account
account.addr, // freeze account
account.addr, // clawback account
"aUSD", // unit name
"Algorand USD", // asset name
"https://a-usd.com", // asset URL
undefined, // assetMetadatahash
params
);
let rawSignedTxn = txn.signTxn(account.sk)
let tx = await algodClient.sendRawTransaction(rawSignedTxn).do()
console.log("Asset Creation Txn : " + tx.txId);
// Wait for confirmation
let confirmedTxn = await waitForConfirmation(algodClient, tx.txId, 4);
//Get the completed Transaction
console.log("Transaction confirmed in round " + confirmedTxn["confirmed-round"]);
let accountInfo = await algodClient.accountInformation(account.addr).do();
console.log(accountInfo);
} catch (error) {
console.log("err", error);
}
}
firstAsset();
```
The output will look like this. You can see the `params` object being printed first. Then the transaction ID. And lastly, the new account status for our asset creator account `HCNMMIL3MKYILOLUO74NF6OPCJU4RU7IE5PX6JYBIT5YHAMQIVO5YADHMU`. Note the asset with ID `2` that has been added to the `assets` array.
To print this information, you can use the `accountInformation` function. Under the hood, it connects to the `/v2/accounts/<address>` endpoint at the Algod REST API. You can find information about this endpoint in the reference docs as well.
```
{
flatFee: false,
fee: 0,
firstRound: 1487,
lastRound: 2487,
genesisID: 'sandnet-v1',
genesisHash: '3D6NaXA4YFQkji+6cN2qrC7n53GWhPx1tTk34f+cpFQ='
}
Asset Creation Txn : NJBHV3OATULF5BFHUZZ7MLWPNXRGZNIVNXFW6HNZC323TVD23MHA
Transaction confirmed in round 1489
{
address: 'HCNMMIL3MKYILOLUO74NF6OPCJU4RU7IE5PX6JYBIT5YHAMQIVO5YADHMU',
amount: 19999019,
'amount-without-pending-rewards': 19999000,
'apps-local-state': [],
'apps-total-schema': { 'num-byte-slice': 0, 'num-uint': 0 },
assets: [
{
amount: 1000000,
'asset-id': 2,
creator: 'HCNMMIL3MKYILOLUO74NF6OPCJU4RU7IE5PX6JYBIT5YHAMQIVO5YADHMU',
'is-frozen': false
}
],
'created-apps': [],
'created-assets': [ { index: 2, params: [Object] } ],
'pending-rewards': 19,
'reward-base': 38,3
rewards: 19,
round: 1524,
status: 'Offline'
}
```
If you pass `undefined` or `””` for one of the mutable parameters, the address will be automatically set to `AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ` and can’t be changed anymore. Therefore, always set the mutable parameters unless in certain use cases where you want to prove that you don’t have any control over the reserve address. In other words, you can use this behaviour to prove that you don’t have control over the asset parameters, freezing/unfreezing, or supply. You can see an example below where the `clawback` address has been set to an empty string.
```json
{
"asset": {
"created-at-round": 1489,
"deleted": false,
"index": 2,
"params": {
"clawback": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ",
"creator": "HCNMMIL3MKYILOLUO74NF6OPCJU4RU7IE5PX6JYBIT5YHAMQIVO5YADHMU",
"decimals": 2,
"default-frozen": false,
"freeze": "HCNMMIL3MKYILOLUO74NF6OPCJU4RU7IE5PX6JYBIT5YHAMQIVO5YADHMU",
"manager": "HCNMMIL3MKYILOLUO74NF6OPCJU4RU7IE5PX6JYBIT5YHAMQIVO5YADHMU",
"name": "Algorand USD",
"name-b64": "QWxnb3JhbmQgVVNE",
"reserve": "HCNMMIL3MKYILOLUO74NF6OPCJU4RU7IE5PX6JYBIT5YHAMQIVO5YADHMU",
"total": 1000000,
"unit-name": "aUSD",
"unit-name-b64": "YVVTRA==",
"url": "https://a-usd.com",
"url-b64": "aHR0cHM6Ly9hLXVzZC5jb20="
}
},
"current-round": 21932
}
```
Next, let’s opt into the asset and send it to the other account.
4. Asset opt-in transaction
Before an account can receive an asset, it must opt-in to receive it. An opt-in transaction places an asset holding of 0 into the account and increases its minimum balance by 100,000 microAlgos (0.1 Algo). You can find more information about minimum balance requirements in the documentation. An opt-in transaction is simply an asset transfer with an amount set to 0, both to and from the account opting in. The following code illustrates this transaction.
Let’s add a new function, call it, and make sure to comment out the previous call to create the asset. We are using `account2` who wants to opt in to the asset created by `account`. Run the code with `node myfile.js`.
```js
const optInTransaction = async function () {
try {
let params = await algodClient.getTransactionParams().do();
const assetId = 2; // Note: change the asset ID if it was different for your new asset
const sender = account2.addr;
const recipient = sender; // transaction to yourself
// We set revocationTarget to undefined as this is not a clawback operation
let revocationTarget = undefined;
// CloseReaminerTo is set to undefined as we are not closing out an asset
let closeRemainderTo = undefined;
const amount = 0;
let optinTxn = algosdk.makeAssetTransferTxnWithSuggestedParams(
sender, recipient,
closeRemainderTo, revocationTarget,
amount, undefined,
assetId, params
);
let rawSignedTxn = optinTxn.signTxn(account2.sk);
let txId = optinTxn.txID().toString();
console.log("Signed transaction with txID: %s", txId);
// Submit the transaction
await algodClient.sendRawTransaction(rawSignedTxn).do();
// Wait for confirmation
let confirmedTxn = await waitForConfirmation(algodClient, txId, 4);
// Get the completed transaction
console.log("Transaction " + txId + " confirmed in round " + confirmedTxn["confirmed-round"]);
// Check if the new asset pops up in "account2"
let accountInfo = await algodClient.accountInformation(account2.addr).do();
console.log(accountInfo);
} catch (error) {
console.log("err", error);
}
}
// firstAsset();
optInTransaction();
```
The output looks like this. The new asset pops up in the account for `ATTR6RUEHHBHXKUHT4GUOYWNBVDV2GJ5FHUWCSFZLHD55EVKZWOWSM7ABQ` with a zero balance.
```sh
Signed transaction with txID: DTHVEVUT7DO6GTPTD6I5SAXG3QUO3HCJIRB7QAX5LEEX4VKVGF4Q
Transaction DTHVEVUT7DO6GTPTD6I5SAXG3QUO3HCJIRB7QAX5LEEX4VKVGF4Q confirmed in round 2157
{
address: 'ATTR6RUEHHBHXKUHT4GUOYWNBVDV2GJ5FHUWCSFZLHD55EVKZWOWSM7ABQ',
amount: 19998179,
'amount-without-pending-rewards': 19998179,
'apps-local-state': [],
'apps-total-schema': { 'num-byte-slice': 0, 'num-uint': 0 },
assets: [
{
amount: 0,
'asset-id': 2,
creator: 'HCNMMIL3MKYILOLUO74NF6OPCJU4RU7IE5PX6JYBIT5YHAMQIVO5YADHMU',
'is-frozen': false
}
],
'created-apps': [],
'created-assets': [],
'pending-rewards': 0,
'reward-base': 53,
rewards: 179,
round: 2157,
status: 'Offline'
}
```
You can also lookup the transaction itself in your browser using the printed transaction ID: `http://localhost:8980/v2/transactions/DTHVEVUT7DO6GTPTD6I5SAXG3QUO3HCJIRB7QAX5LEEX4VKVGF4Q`. The output looks like this:
```
{
"current-round": 2213,
"transaction": {
"asset-transfer-transaction": {
"amount": 0,
"asset-id": 2,
"close-amount": 0,
"receiver": "ATTR6RUEHHBHXKUHT4GUOYWNBVDV2GJ5FHUWCSFZLHD55EVKZWOWSM7ABQ"
},
"close-rewards": 0,
"closing-amount": 0,
"confirmed-round": 2157,
"fee": 1000,
"first-valid": 2155,
"genesis-hash": "3D6NaXA4YFQkji+6cN2qrC7n53GWhPx1tTk34f+cpFQ=",
"genesis-id": "sandnet-v1",
"id": "DTHVEVUT7DO6GTPTD6I5SAXG3QUO3HCJIRB7QAX5LEEX4VKVGF4Q",
"intra-round-offset": 0,
"last-valid": 3155,
"receiver-rewards": 0,
"round-time": 1635982969,
"sender": "ATTR6RUEHHBHXKUHT4GUOYWNBVDV2GJ5FHUWCSFZLHD55EVKZWOWSM7ABQ",
"sender-rewards": 19,
"signature": {
"sig": "oQoanZCBBkG2Bfk2k+KpOEALCfVDm5Mu+QiLIEMMo8XRQtKREVFSbijS7sjWUUf8umVTI9VwoEZgKeJSvBDNAQ=="
},
"tx-type": "axfer"
}
}
```
Ready? Let’s transfer some aUSD to `account2`.
5. Asset transfer transaction
You are ready to transfer some aUSD from the creator account to `account2`. To do so, you can use `makeAssetTransferTxnWithSuggestedParams`. Make sure to pass the correct assetId! Run the code with `node myfile.js`.
```js
const transferTransaction = async function () {
try {
let params = await algodClient.getTransactionParams().do();
const sender = account.addr;
const recipient = account2.addr;
let revocationTarget = undefined;
let closeRemainderTo = undefined;
const assetId = 2; // Correct asset ID?
const amount = 100; // Transfer 100 units of asset aUSD
const note = algosdk.encodeObj("gifting aUSD");
let transferTxn = algosdk.makeAssetTransferTxnWithSuggestedParams(
sender, recipient,
closeRemainderTo, revocationTarget,
amount, note,
assetId, params
);
let rawSignedTxn = transferTxn.signTxn(account.sk);
let txId = transferTxn.txID().toString();
console.log("Signed transaction with txID: %s", txId);
// Submit the transaction
await algodClient.sendRawTransaction(rawSignedTxn).do();
// Wait for confirmation
let confirmedTxn = await waitForConfirmation(algodClient, txId, 4);
// Get the completed Transaction
console.log("Transaction " + txId + " confirmed in round " + confirmedTxn["confirmed-round"]);
// Check if balance has increased
let accountInfo = await algodClient.accountInformation(account2.addr).do();
console.log(accountInfo);
} catch (error) {
console.log("err", error);
}
}
// firstAsset();
// optInTransaction();
transferTransaction();
```
The output looks like this in your shell. If everything is correct, you should have received 100 units of `aUSD` in `account2`.
```sh
Signed transaction with txID: XC5QKHGMQ6A3AKBKL6L3HEWH7W4MWPEZ6SUB6HG7C5HXELCMDPZQ
Transaction XC5QKHGMQ6A3AKBKL6L3HEWH7W4MWPEZ6SUB6HG7C5HXELCMDPZQ confirmed in round 2294
{
address: 'ATTR6RUEHHBHXKUHT4GUOYWNBVDV2GJ5FHUWCSFZLHD55EVKZWOWSM7ABQ',
amount: 19998255,
'amount-without-pending-rewards': 19998179,
'apps-local-state': [],
'apps-total-schema': { 'num-byte-slice': 0, 'num-uint': 0 },
assets: [
{
amount: 100,
'asset-id': 2,
creator: 'HCNMMIL3MKYILOLUO74NF6OPCJU4RU7IE5PX6JYBIT5YHAMQIVO5YADHMU',
'is-frozen': false
}
],
'created-apps': [],
'created-assets': [],
'pending-rewards': 76,
'reward-base': 57,
rewards: 255,
round: 2294,
status: 'Offline'
}
```
Next, let’s freeze the asset.
6. Asset freeze transaction
In this step, we want to prevent `account2` from moving any `aUSD` units. We can use the `manager address` (`account`: HCNMMIL3MKYILOLUO74NF6OPCJU4RU7IE5PX6JYBIT5YHAMQIVO5YADHMU) to send the freeze transaction. We can do this by setting the `freezeState` boolean to `true`. Run the code with `node myfile.js`.
```js
const freezeAsset = async function () {
try {
const assetId = 2; // Correct assetId?
let params = await algodClient.getTransactionParams().do();
// We set "account" as the freeze address so this one can freeze "account2"'s holdings of the aUSD asset
const from = account.addr;
const freezeTarget = account2.addr;
const freezeState = true;
let freezeTxn = algosdk.makeAssetFreezeTxnWithSuggestedParams(
from, algosdk.encodeObj("Failing KYC - freezing"),
assetId, freezeTarget,
freezeState, params
);
let rawSignedTxn = freezeTxn.signTxn(account.sk);
let txId = freezeTxn.txID().toString();
console.log("Signed transaction with txID: %s", txId);
// Submit the transaction and wait for confirmation
await algodClient.sendRawTransaction(rawSignedTxn).do();
let confirmedTxn = await waitForConfirmation(algodClient, txId, 4);
// Get the completed Transaction
console.log("Transaction " + txId + " confirmed in round " + confirmedTxn["confirmed-round"]);
// Check if state is changed to frozen in "assets"
let accountInfo = await algodClient.accountInformation(account2.addr).do();
console.log(accountInfo);
} catch (error) {
console.log("err", error);
}
}
// firstAsset();
// optInTransaction();
// transferTransaction();
freezeAsset();
```
The output in your console should look like this. As you can see, the `is-frozen` property for `aUSD` in `account2` has been changed to `true`.
```sh
Signed transaction with txID: Z6AURKO2K34U2SBVBQJL2S3WYJAEHI3MTRIYV3FQLBMPZ7LMY5FA
Transaction Z6AURKO2K34U2SBVBQJL2S3WYJAEHI3MTRIYV3FQLBMPZ7LMY5FA confirmed in round 18351
{
address: 'ATTR6RUEHHBHXKUHT4GUOYWNBVDV2GJ5FHUWCSFZLHD55EVKZWOWSM7ABQ',
amount: 20005874,
'amount-without-pending-rewards': 19998179,
'apps-local-state': [],
'apps-total-schema': { 'num-byte-slice': 0, 'num-uint': 0 },
assets: [
{
amount: 100,
'asset-id': 2,
creator: 'HCNMMIL3MKYILOLUO74NF6OPCJU4RU7IE5PX6JYBIT5YHAMQIVO5YADHMU',
'is-frozen': true
}
],
'created-apps': [],
'created-assets': [],
'pending-rewards': 7695,
'reward-base': 458,
rewards: 7874,
round: 18351,
status: 'Offline'
}
```
To confirm that we can’t spend, let’s try to send a transaction from `account2` to spend `aUSD` units. In the below code, we’ve only switched the sender and receiver for the “asset transfer” transaction. Execute your file using the `node` command: `node myfile.js`.
```js
const spendAssetAfterFreeze = async function () {
try {
let params = await algodClient.getTransactionParams().do();
const sender = account2.addr;
const recipient = account.addr;
let revocationTarget = undefined;
let closeRemainderTo = undefined;
const assetId = 2;
const amount = 50;
let transferTxn = algosdk.makeAssetTransferTxnWithSuggestedParams(
sender, recipient,
closeRemainderTo, revocationTarget,
amount, algosdk.encodeObj("spending aUSD"),
assetId, params
);
let rawSignedTxn = transferTxn.signTxn(account2.sk);
let txId = transferTxn.txID().toString();
console.log("Signed transaction with txID: %s", txId);
// Submit the transaction
await algodClient.sendRawTransaction(rawSignedTxn).do();
// Wait for confirmation
let confirmedTxn = await waitForConfirmation(algodClient, txId, 4);
//Get the completed Transaction
console.log("Transaction " + txId + " confirmed in round " + confirmedTxn["confirmed-round"]);
// Check if balance is up
let accountInfo = await algodClient.accountInformation(account2.addr).do();
console.log(accountInfo);
} catch (error) {
console.log("err", error);
}
}
// firstAsset();
// optInTransaction();
// transferTransaction();
// freezeAsset();
spendAssetAfterFreeze();
```
If you execute the code with the `node` command, you’ll get a `Error: Bad Request (status 400)` returned – you can find the error message on the `Response` object under the `text` property.
```js
const spendAssetAfterFreeze = async function () {
try {
let params = await algodClient.getTransactionParams().do();
const sender = account2.addr;
const recipient = account.addr;
let revocationTarget = undefined;
let closeRemainderTo = undefined;
const assetId = 2;
const amount = 50;
let transferTxn = algosdk.makeAssetTransferTxnWithSuggestedParams(
sender, recipient,
closeRemainderTo, revocationTarget,
amount, algosdk.encodeObj("spending aUSD"),
assetId, params
);
let rawSignedTxn = transferTxn.signTxn(account2.sk);
let txId = transferTxn.txID().toString();
console.log("Signed transaction with txID: %s", txId);
// Submit the transaction
await algodClient.sendRawTransaction(rawSignedTxn).do();
// Wait for confirmation
let confirmedTxn = await waitForConfirmation(algodClient, txId, 4);
//Get the completed Transaction
console.log("Transaction " + txId + " confirmed in round " + confirmedTxn["confirmed-round"]);
// Check if balance is up
let accountInfo = await algodClient.accountInformation(account2.addr).do();
console.log(accountInfo);
} catch (error) {
console.log("err", error);
}
}
// firstAsset();
// optInTransaction();
// transferTransaction();
// freezeAsset();
spendAssetAfterFreeze();
```
Here’s the message:
```sh
TransactionPool.Remember: transaction LOKXN44BQYEQ3GOITIQI7N2KKB2OJOZ7MAOOTGLCEYKEMQBWBS2Q: asset 2 frozen in ATTR6RUEHHBHXKUHT4GUOYWNBVDV2GJ5FHUWCSFZLHD55EVKZWOWSM7ABQ
```
In the next step, you can revoke the assets held by `account2`. Let’s try that out!
7. Asset revoke transaction
Let’s revoke assets from `account2` to the creator account of the aUSD asset. Revoking an asset for an account removes a specific number of the asset from the revoke target account. Revoking an asset from an account requires specifying an asset sender (the revoke target account) and an asset receiver (the account to transfer the funds back to). Many projects set the `clawback` address to `undefined` to make it impossible to revoke assets. However, in certain financial applications, we want to have this behavior. For instance, if someone fails the KYC or breaches guidelines, you want to revoke assets.
Let’s revoke 90 units of our asset from `account2`. Note that we are using the same function `makeAssetTransferTxnWithSuggestedParams`. Also, the `sender` and `recipient` variables are set to the creator address of the aUSD asset. You have to set the `sender` to the account address with the revocation right (clawback address), and the recipient is the creator account address. In our case, both are the same address.
Execute your file using the `node` command: `node myfile.js`.
```js
onst revokeTransaction = async function () {
try {
let params = await algodClient.getTransactionParams().do();
const assetId = 2;
const amount = 90;
const sender = account.addr; // account with clawback right
const recipient = account.addr; // account that created the asset (same account)
const revocationTarget = account2.addr; // property to define from who you want to claim back tokens
const closeRemainderTo = undefined; // we don't want to close the account - user can still use it
let transferTxn = algosdk.makeAssetTransferTxnWithSuggestedParams(
sender, recipient,
closeRemainderTo, revocationTarget,
amount, algosdk.encodeObj("retrieving aUSD"),
assetId, params
);
let rawSignedTxn = transferTxn.signTxn(account.sk);
console.log(transferTxn.toString())
let txId = transferTxn.txID().toString();
console.log("Signed transaction with txID: %s", txId);
// Submit the transaction
await algodClient.sendRawTransaction(rawSignedTxn).do();
// Wait for confirmation
let confirmedTxn = await waitForConfirmation(algodClient, txId, 4);
//Get the completed Transaction
console.log("Transaction " + txId + " confirmed in round " + confirmedTxn["confirmed-round"]);
// Check if balance is up
let accountInfo = await algodClient.accountInformation(account2.addr).do();
console.log(accountInfo);
} catch (error) {
console.log("err", error);
}
}
// firstAsset();
// optInTransaction();
// transferTransaction();
// freezeAsset();
// spendAssetAfterFreeze();
revokeTransaction();
```
You can use the exposed indexer API to verify if the balance has changed for `account2`. Request the following address in your browser or tool of preference. You can find information about the `/v2/accounts/<address>` endpoint in the Algorand reference documentation.
```
http://localhost:8980/v2/accounts/ATTR6RUEHHBHXKUHT4GUOYWNBVDV2GJ5FHUWCSFZLHD55EVKZWOWSM7ABQ
```
The assets array should look like this now:
```json
{
"assets": [
{
"amount": 10,
"asset-id": 2,
"creator": "",
"deleted": false,
"is-frozen": true
}
]
}
```
Next, let’s change the config for our asset.
8. Asset config change transaction
In this last step, I want to show you how you can change the configuration for a given asset. Remember that you can only change mutable asset parameters. You can use the function `makeAssetConfigTxnWithSuggestedParams` to change the configuration for an asset. It’s an `acfg` asset config transaction.
You need to pass all mutable parameters, however, you can change the value for the properties you want to change. Let’s set the freeze address to a different address. In this example, we used an account generated by the Algorand sandbox. Execute your file using the `node` command: `node myfile.js`.
```js
const changeConfigTransaction = async function () {
try {
let params = await algodClient.getTransactionParams().do();
const assetId = 2;
const from = account.addr;
const manager = from;
const reserve = from;
const freeze = "HCNMMIL3MKYILOLUO74NF6OPCJU4RU7IE5PX6JYBIT5YHAMQIVO5YADHMU"; // sandbox account
const clawback = from;
let configChangeTxn = algosdk.makeAssetConfigTxnWithSuggestedParams(
from,
algosdk.encodeObj("adding clawback address"),
assetId,
manager, reserve, freeze, clawback,
params
);
let rawSignedTxn = configChangeTxn.signTxn(account.sk);
let txId = configChangeTxn.txID().toString();
console.log("Signed transaction with txID: %s", txId);
// Submit the transaction
await algodClient.sendRawTransaction(rawSignedTxn).do();
// Wait for confirmation
let confirmedTxn = await waitForConfirmation(algodClient, txId, 4);
//Get the completed Transaction
console.log("Transaction " + txId + " confirmed in round " + confirmedTxn["confirmed-round"]);
} catch (error) {
console.log("err", error);
}
}
// createAccount();
// firstAsset();
// optInTransaction();
// transferTransaction();
// freezeAsset();
// spendAssetAfterFreeze();
// revokeTransaction();
changeConfigTransaction();
```
To verify if this `acfg` transaction was successful, you can use your browser again. This time, let’s use the `assets` endpoint to look up asset information for asset with ID `2`. Make sure to check if the `freeze` address has changed.
```
http://localhost:8980/v2/assets/2
```
You can also use the `goal` CLI tool to change individual parameters for your asset. You can try the below command to change the clawback address. You can find all options for the `goal asset config` command in the reference documentation for goal.
```sh
./sandbox goal asset config --assetid 2 --manager HCNMMIL3MKYILOLUO74NF6OPCJU4RU7IE5PX6JYBIT5YHAMQIVO5YADHMU --new-clawback 5FRKKWRG3UAJQNB7QIOWBW2JICZS4YUF2WFAETHGN3CBM4R3N27NY3T2KQ
```
This command probably won’t work for you. Goal uses a wallet that manages different accounts so you don’t have to deal with the private key/signing transactions. Therefore, we have to import the account (HCNMMIL3MKYILOLUO74NF6OPCJU4RU7IE5PX6JYBIT5YHAMQIVO5YADHMU) into our wallet so we can send the transaction using goal. You can import an account like this using the passphrase of `HCNM…`.
```sh
./sandbox goal account import -m "cloth intact extend pull sad miss popular mansion lobster napkin space oyster correct warm miss neither confirm snow virtual evoke era lock amused abandon first"
```
Try the `asset config` transaction again. Success?!
9. Interacting with the indexer and Algod API
Throughout this tutorial, you’ve learned how to interact with the indexer API. You can find more information about the possible endpoints in the reference docs. Here’s a quick overview.
- Indexer API docs
- `/v2/accounts/<account-id>` -> Retrieve account information
- `/v2/assets/<asset-id>` -> Look up information about specific asset
- `/v2/assets/<asset-id>/transactions` -> Look up transactions for an asset and you can further filter on address role (sender, receiver, `freeze-target`)
- `/v2/transactions/<tx-id>` -> Look up transaction details
- Algod API docs
- `/v2/transactions/params` -> Get parameters for constructing a new transaction
- `/v2/accounts/<address>` -> Returns the accounts status, balance and spendable amounts
- `/v2/accounts/{address}/transactions/pending` -> Used by the `waitForConfirmation` function. Get a list of unconfirmed transactions currently in the transaction pool by address.
- `POST /v2/teal/dryrun` -> Executes TEAL program(s) in context and returns debugging information about the execution.
Note: To use the Algod API, you need to pass the API token and the correct address. Here’s an example using cUrl:
```sh
curl -i -X GET \
-H "X-Algo-API-Token: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" \
'http://localhost:4001/v1/account/<address>'
```
Conclusion
This is the end of the second tutorial teaching you the possibilities of ASA management. You can find more information here on creating an NFT using Algorand Standard Assets. In short, you set the supply to `1` and the decimals to `0`. You can also create fractionalized NFTs.
Make sure to check out the following tutorial that covers PyTeal smart signature development.
Credits: Created by Tobias Schmidt, GitHub: @CrypBlockCode
Disclaimer — This is a sponsored article. DappRadar does not endorse any content or product on this page. DappRadar aims to provide accurate information, but readers should always do their own research before taking action. Articles by DappRadar can not be considered as investment advice.