Skip to main content

Transfer assets

Before transferring, you need to mint tokens to one of the previously created accounts. Let's use an example where we mint 100 TST tokens to the account with the ID 5E24...EAD2. Since the TST asset has six decimal points, we must pass 100,000,000 to the mint operation.

chr tx ft4.admin.mint \
5E2488889F72939DD4D0A034FB91893ACBF14C7EDBCEF2A9F5C621A07169EAD2 \
85506832C77AFDDB17DE1D175BAEE949C9248578E06CAC3EC7B59AA69C7C69B0 \
100000000L \
--await --secret ft4-admin.keypair
note

Please remember to replace 5E24...EAD2 with the ID of the account you registered in the last step and 8550...69B0 with the ID of the asset you want to use for the whole duration of this guide.

To verify the details of the asset, you can use the following query:

chr query ft4.get_asset_by_id 'asset_id=x"85506832C77AFDDB17DE1D175BAEE949C9248578E06CAC3EC7B59AA69C7C69B0"'

The query displays information about the asset, including the supply that has been minted.

To check the account balances, use the following command:

chr query ft4.get_asset_balances 'account_id=x"5E2488889F72939DD4D0A034FB91893ACBF14C7EDBCEF2A9F5C621A07169EAD2"' page_size=10 page_cursor=null

This command returns a list of balances for all the assets in the account. In this case, there is only one asset, and the output will show the amount of that asset held by the account.

If you want to check the balance of a specific token, you can use the following query:

chr query ft4.get_asset_balance 'account_id=x"5E2488889F72939DD4D0A034FB91893ACBF14C7EDBCEF2A9F5C621A071"' 'asset_id=x"85506832C77AFDDB17DE1D175BAEE949C9248578E06CAC3EC7B59AA69C7C69B0"'

Now that you have an account with assets, you can start experimenting with asset transfers. This could either be done via the cli, similar to how we have submitted transactions to the blockchain so far in the documentation or it could be done via the client.

To perform the call using the cli, make a transfer transaction and simply pass the --ft-auth flag and sign it with the user's keypair, like:

chr tx ft4.transfer \
'receiver_id=x"79C71AF3C9C951BED380F8ADAB2E407C15CC4A9EB942AA222D870136C45801CE"' \
'asset_id=x"85506832C77AFDDB17DE1D175BAEE949C9248578E06CAC3EC7B59AA69C7C69B0"' \
amount=10000000L \
--ft-auth --await --secret user.keypair

Authorizing an operation with FT4 is more involved than authorizing with plain postchain. However, when we pass the --ft-auth flag, that is being taken care of behind the scenes by the cli. The same thing could also be achieved by using the client library, as explained in the next section.

note

Currently, the cli only supports --ft4-auth. Support for the equivalent of --evm-auth might be added in the future, but for now, the only way to achieve EVM authentication is by using the client lib.

Setup the client code

You have already seen how to set up a connection, register an asset and an account.

In your Typescript code, you can use this knowledge to transfer an asset from one account to another. First, you need to retrieve the account with id 5E24...EAD2, which now holds 100 TST tokens.

You'll need to log into that account using the private key you have stored in user.keypair:

import { encryption } from "postchain-client";

const keyPair = encryption.makeKeyPair(
"9F11AB5B146A928519114E8104825D67E95474BD60F0A1ED60332669E232918D"
);

const accountId =
"5E2488889F72939DD4D0A034FB91893ACBF14C7EDBCEF2A9F5C621A07169EAD2";
caution

Be careful whenever you're using private keys for tests. Always use a fresh keypair every time you release something for production and rotate the keys as soon as you discover that they might have been exposed.

You can now log into your account:

const { getSession } = createKeyStoreInteractor(
client,
createInMemoryFtKeyStore(keyPair)
);

const session = await getSession(accountId);

It's time to define the details of this transfer:

const recipientId =
"79C71AF3C9C951BED380F8ADAB2E407C15CC4A9EB942AA222D870136C45801CE";
const assetId =
"85506832C77AFDDB17DE1D175BAEE949C9248578E06CAC3EC7B59AA69C7C69B0";
const amountToSend = createAmount(10, 6); // it will send 10 tokens, but it has 6 decimals!

You can safely transfer the assets.

await session.account.transfer(recipientId, assetId, amountToSend);

After running this code, you should have 90 TST tokens in the account. You can check it like we already did at the start of this guide, or you can have the script do that for you:

console.log(await session.account.getBalanceByAssetId(assetId));

Complete example

For this example to work out of the box, you must follow the instructions here. Make sure that you put the right private key in the marked code.

import {
createAmount,
createInMemoryFtKeyStore,
createKeyStoreInteractor,
createConnection,
op,
createSingleSigAuthDescriptorRegistration,
deriveAuthDescriptorId,
gtv,
} from "@chromia/ft4";
import { createClient, encryption } from "postchain-client";

const url = "http://localhost:7740";
const client = await createClient({
nodeUrlPool: url,
blockchainIid: 0,
});

//## EDIT HERE ##//
// Taken from ft4-admin.keypair:
const adminKeyPair = encryption.makeKeyPair(
"2AC313A8384F319058C578F0E46A9871EACE285EA9144166D80FACE635713D39"
);

// we can also create a new keypair:
const senderKeyPair = encryption.makeKeyPair();

/**
* In the docs, we used the chromia CLI to create assets and accounts.
* Here, we'll use the equivalent functions from the `postchain-client` library
*
*/

// Create an asset (the admin defined in chromia.yaml must sign)
await client.signAndSendUniqueTransaction(
op(
"ft4.admin.register_asset",
"TestAsset4",
"TST4",
6,
"http://url-to-asset-4-icon"
),
adminKeyPair
);

// Create a FT4 connection to ease interaction with FT4 primitives
const connection = createConnection(client);

// we need the asset ID
// (id might be null if no asset with the specified symbol is found, in a production dApp this should be handled properly)
const assetId = (await connection.getAssetBySymbol("TST4"))!.id;

// Create an object to hold registration info about our auth descriptor for the sender account
const authDesc = createSingleSigAuthDescriptorRegistration(
// Can modify the account and transfer
["A", "T"],
// who's the signer
senderKeyPair.pubKey,
// No limitations for this signer (this value can be omitted if it is supposed to be null)
null
);

/**
* This is equivalent to what we had in the docs
* https://docs.chromia.com/ft4/ft4-register-accounts#registering-with-ft4-admin-operation
*
* [
* 0, // single sig
* [
* ["A","T"], // can modify the account and transfer
* senderKeyPair.pubKey // who's the signer
* ],
* null // we don't want restrictions for this signer
* ]
*/

// converting it to gtv makes it possible to send it to postchain
const authDescGtv = gtv.authDescriptorRegistrationToGtv(authDesc);

// The ID of the account can be calculated from the
// Auth descriptor even before account creation
const senderId = deriveAuthDescriptorId(authDesc);

// Create the sender account
await client.signAndSendUniqueTransaction(
op("ft4.admin.register_account", authDescGtv),
adminKeyPair
);

// Mint 100 TST tokens to our account (sender)
// 100 with 6 decimal digits is 100.000000. remove the
// Decimal point and use that as amount
await client.signAndSendUniqueTransaction(
op("ft4.admin.mint", senderId, assetId, 100000000n),
adminKeyPair
);

/**
* This will only work if you followed steps 4 and 5 of
* docs.chromia.com/ft4/ft4-register-accounts#registering-with-ft4-admin-operation
*
const allAccounts = await client.query("get_all_accounts")
*
* If we have only one account on chain, then we can retrieve it
const senderId = allAccounts![0].id;
*/

// Let's create a receiver account too.
// You can also create the auth descriptor as it was in the docs.
const recipientAd = [
0,
[
["A", "T"],
encryption.makeKeyPair().pubKey, // random key
],
null,
];

await client.signAndSendUniqueTransaction(
op("ft4.admin.register_account", recipientAd),
adminKeyPair
);

// However, it is recommended to use the utility function,
// as you will need a registration object to derive the account id
const recipientId = deriveAuthDescriptorId(
createSingleSigAuthDescriptorRegistration(
recipientAd[1]![0],
recipientAd[1]![1]
)
);
const amountToSend = createAmount(10, 6); // it will send 10 tokens, but it has 6 decimals!

// we can now start the login process
const { getSession } = createKeyStoreInteractor(
client,
createInMemoryFtKeyStore(senderKeyPair)
);

const session = await getSession(senderId);

await session.account.transfer(recipientId, assetId, amountToSend);

console.log(await session.account.getBalanceByAssetId(assetId));