Skip to main content

Step 3: Deposits, withdrawals, and transaction monitoring

Handling deposits, withdrawals, and monitoring transactions are essential components of integrating with Chromia's Economy Chain. These operations are streamlined using the FT4 library, which provides a comprehensive toolkit for interacting with the Chromia ecosystem.

The FT4 library is designed to help developers build real-world applications within Chromia by offering out-of-the-box functionality for:

  • Account creation and access management: Simplifies user account setup and integrates with familiar external signature solutions.
  • Asset management: Enables issuance, allocation, transfers, and tracing of asset activities, both within a single chain and across chains in the Chromia ecosystem.

Using the FT4 library, you can efficiently manage deposits and withdrawals while monitoring transaction activities within the blockchain network.

Additional resources​

Required Dependencies​

Install the following packages using npm:

npm install @chromia/ft4@1.1.1 postchain-client@1.22.0
note

Make sure you have Node.js version 16 or higher installed on your system.

Using the FT4 library example​

Setting up the client​

Start by configuring the client to interact with the Chromia blockchain. Use the postchain-client library to create a generic client and the @chromia/ft4 library to establish FT4 connections.

import { createClient } from "postchain-client";
import { createConnection } from "@chromia/ft4";

// All IDs are supposed to be 32bytes, or 64-character hex strings - account ID, blockchain RID, asset ID, etc.

// Blockchain connection details
const NODE_URL_POOL = [
https://chr.bbbnnnbbb.net:443
https://chroma.node.monster:7741
https://chromia.01node.com:7740
https://chromia-api.hashkey.cloud:7740
https://chromia-mainnet-1.dappradar.com:7740
https://chromia-mainnet.caliber.build:443
https://chromia-mainnet.everstake.one:7740
https://chromia-mainnet-replica.everstake.one:7740
https://chromia-mainnet-systemnode-1.stakin-nodes.com:7740
https://chromia-mainnet-system-node.asymm.ventures:7740
https://chromia.mainnet-system.nodeops.ninja:7740
https://chromia-mainnet-system-replica.dwellir.com:443
https://chromia-mainnet.w3coins.io:7740
https://chromia.nocturnallabs.org:7740
https://chromia-sp.bwarelabs.com:7740
https://chromia-system-node.moca-services.xyz:7740
https://chromia.validatrium.club
https://chromina-node.stablelab.xyz:7740
https://dapps0.chromaway.com
https://mainnet-dapp1.sunube.net:7740
https://replica0.chromaway.com
https://sys-main.chromia.coinhall.org:7740
https://system.chromaway.com
];
// Note: Port 7740 can also be used as an alternative
const BLOCKCHAIN_RID = "15C0CA99BEE60A3B23829968771C50E491BD00D2E3AE448580CD48A8D71E7BBA"; // Mainnet Economy chain BRID

// Create a generic client for calling queries and operations
const client = await createClient({
directoryNodeUrlPool: NODE_URL_POOL,
blockchainRid: BLOCKCHAIN_RID,
});

// Advanced client for FT4 interaction
const connection = createConnection(client);

Accessing an account​

Retrieve an account using its ID to access account-related data. This account remains read-only and can only fetch information, not perform operations like transferring funds.

import { Account } from "@chromia/ft4";

// Access an account by its ID:
const accountId = "2aafb9bc1b75a0cb12850959ca148175491aee03aff5a53a11af595c579ddec1";

const myAccount: Account | null = await connection.getAccountById(accountId);

if (myAccount == null) throw "Account not found";
tip

The getAccountById function helps you verify whether an account exists before you initiate a transfer. By performing this check, you identify if the recipient account already exists or if you need to send a minimum of 10 CHR to create the account.

Retrieving transaction history​

Fetch transaction history in batches to handle large datasets efficiently. Use pagination to retrieve a specified number of entries for better control over the data.

import { TransferHistoryType } from "@chromia/ft4";

// Retrieve the latest 200 transactions in which the account participated:
const { data: first200Entries, nextCursor } = await myAccount.getTransferHistory(
200,//Maximum number of entries to fetch (default and cap: 200).
{ transferHistoryType: TransferHistoryType.Received } // Filter for received transactions.
);

// Retrieve 50 more transactions starting from the last fetched entry:
const { data: other50Entries, nextCursor: nextNextCursor } = await myAccount.getTransferHistory(
50,//Number of entries to fetch.
{ transferHistoryType: TransferHistoryType.Received },//Filter for received transactions.
nextCursor//Cursor to start from the last position in the previous query.
);

// Fetch 200 additional transactions:
const { data: other200Entries, nextCursor: thirdCursor } = await myAccount.getTransferHistory(
200,
{ transferHistoryType: TransferHistoryType.Received },
nextNextCursor//Start from the 250th entry.
);

Retrieving transfer details​

Access detailed information about a specific transfer, transaction, and block.

const aTransfer = first200Entries[0];

// Retrieve details about a specific transfer:
const info = await connection.getTransferDetails(aTransfer.transactionId, aTransfer.opIndex);

// Retrieve transaction details:
const transactionInfo = await client.getTransactionInfo(aTransfer.transactionId);

// Retrieve transaction status:
const transactionStatus = await client.getTransactionStatus(aTransfer.transactionId);

// Retrieve block details:
const blockInfo = await client.getBlockInfo(transactionInfo.blockRid.toString("hex"));

Retrieving account balances​

Check the balance of an account for all assets or focus on a specific asset to obtain precise information.

// Retrieve account balances (supports pagination):
await myAccount.getBalances();

// Retrieve the balance of a specific token:
await myAccount.getBalanceByAssetId("asset-id-as-hex-string"); // Returns null if balance is zero.

Setting up a key store and transferring funds​

Create a key store to handle transaction signing and transfer funds using a read-write account. Utilize utility functions to manage token amounts effectively.

import {
createInMemoryFtKeyStore,
createInMemoryEvmKeyStore,
createKeyStoreInteractor,
createAmount,
createAmountFromBalance,
Session,
} from "@chromia/ft4";

// Set up a key store for signing transactions:
const keystore = createInMemoryFtKeyStore({
privKey: Buffer.from("my-privkey-as-hex-string", "hex"),
pubKey: Buffer.from("my-pubkey-as-hex-string", "hex"),
});

// Set up a EVM key store for signing transactions:
const keystoreEVM = createInMemoryEvmKeyStore({
privKey: Buffer.from("my-privkey-as-hex-string", "hex"),
pubKey: Buffer.from("my-pubkey-as-hex-string", "hex"),
});

// Retrieve accounts accessible by this key pair:
const { getAccounts, getSession } = createKeyStoreInteractor(client, keystore);
const accounts = await getAccounts();

// If you only have one account linked to the keypair, you can access it as:
const readonlyAccount = accounts[0]; // This account is still read-only.

// Access a read-write account:
const session: Session = await getSession("my-account-id-as-hex-string");
const account = session.account;

// Transfer funds to another account:
account.transfer(
"receiver-id-as-hex-string",//Recipient account ID.
"asset-id-as-hex-string",//Asset being transferred.
createAmount(5, 6)//Amount: 5 units with 6 decimals (5000000n).
);

// Use `createAmountFromBalance` if the amount is already a bigint:
createAmountFromBalance(5000000n, 6);

Offline transactions​

Prepare a transaction without sending it immediately. This approach is not suitable when the environment lacks blockchain access. It could be useful, however, whenever there should be a delay between the transaction being built and the actual sending. For a completely offline transaction process (except for the sending step), refer to section 8.

import { transactionBuilder } from "@chromia/ft4";

// Build a transaction without sending it:
await transactionBuilder(account.authenticator, client)
.add({
name: "ft4.transfer",
args: [
Buffer.from("receiver-id-as-hex-string", "hex"),
Buffer.from("asset-id-as-hex-string"),
createAmount(5, 6).value,//or 5000000n
],
})
.build();
tip

You can find detailed information about operation names and arguments at: FT4 Rell Documentation

Sending offline transactions​

Send a pre-built transaction by specifying transaction details and using a signer for authorization.

import { Transaction } from "postchain-client";

// The following information is required:
// - Blockchain RID
// - Account ID
// - Authentication descriptor ID

const blockchainRid = Buffer.from("id-as-hex", "hex");
const accountId = Buffer.from("id-as-hex", "hex");
const authDescriptorId = Buffer.from("id-as-hex", "hex");

const transaction: Transaction = {
operations: [
{ name: "ft4.ft_auth", args: [accountId, authDescriptorId] },
{
name: "ft4.transfer",
args: [
/* Values for recipient, asset and amount as above. Buffer, Buffer, bigint. */
],
},
],
signers: [
Buffer.from("pubkey-as-hex", "hex"),//The signer for the auth descriptor defined above.
],
};

// To send the transaction on a connected machine:
client.signAndSendUniqueTransaction(transaction, signatureProvider);

Offline transaction creation and signing​

For more complex scenarios where you need full control over transaction creation and signing, you can use the following approach. This example demonstrates how to create a transaction with multiple operations, including memo, authentication and transfer.

import { gtx, gtv, newSignatureProvider, createClient } from "postchain-client";
import { createAmount, deriveAuthDescriptorId, createSingleSigAuthDescriptorRegistration } from "@chromia/ft4";

// Define your keypair
const privateKey = "my-privkey-as-hex-string";
const publicKey = "my-pubkey-as-hex-string";

const keypair = {
privKey: Buffer.from(privateKey, "hex"),
pubKey: Buffer.from(publicKey, "hex"),
};

// Initialize the client
const chromiaClient = await createClient({
nodeUrlPool: "https://system.chromaway.com",
blockchainRid: "15C0CA99BEE60A3B23829968771C50E491BD00D2E3AE448580CD48A8D71E7BBA",
});

// Define transaction parameters
const assetId = Buffer.from("asset-id-as-hex-string", "hex");
const toAddress = Buffer.from("receiver-id-as-hex-string", "hex");
const brid = Buffer.from("blockchain-rid-as-hex-string", "hex");

// Create signature provider
const sigProv = newSignatureProvider(keypair);

// Create authentication descriptor and derive IDs
const authDescriptor = createSingleSigAuthDescriptorRegistration(["A", "T"], keypair.pubKey, null);
const accountId = gtv.gtvHash(authDescriptor.args.signer);
const authDescriptorId = deriveAuthDescriptorId(authDescriptor);

// Create and build the transaction
let tx = gtx.emptyGtx(brid);

// Add memo operation
tx = gtx.addTransactionToGtx("memo", ["100346"], tx);

// Add authentication operation
tx = gtx.addTransactionToGtx("ft4.ft_auth", [accountId, authDescriptorId], tx);

// Add transfer operation
tx = gtx.addTransactionToGtx("ft4.transfer", [toAddress, assetId, createAmount(1000, 2).value], tx);

// Add NOP operation
tx = gtx.addTransactionToGtx(
"nop",
[7], /random number
tx
);

// Add signer and sign the transaction
gtx.addSignerToGtx(keypair.pubKey, tx);
tx = await gtx.sign(tx, sigProv);

// Serialize and send the transaction
const encodedSignedTx = gtx.serialize(tx);
const signedTx = await chromiaClient.sendTransaction(encodedSignedTx);
tip

When creating offline transactions, make sure to:

  1. Keep your private keys secure
  2. Verify all transaction parameters before signing
  3. Double-check the blockchain RID and node URLs
  4. Test the transaction on a testnet before using it on

Offline account registration​

You can also register a new account offline using the following approach.

import { gtx, newSignatureProvider } from "postchain-client";
import { createSingleSigAuthDescriptorRegistration, gtv as gtvft4 } from "@chromia/ft4";

// Define your keypair
const privkey = "my-privkey-as-hex-string";
const pubkey = "my-pubkey-as-hex-string";

const keypair = {
privKey: Buffer.from(privkey, "hex"),
pubKey: Buffer.from(pubkey, "hex"),
};

// Initialize the client
const chromiaClient = await createClient({
nodeUrlPool: "https://system.chromaway.com",
blockchainRid: "15C0CA99BEE60A3B23829968771C50E491BD00D2E3AE448580CD48A8D71E7BBA",
});

// Define transaction parameters
const assetId = Buffer.from("asset-id-as-hex-string", "hex");
const brid = Buffer.from("blockchain-rid-as-hex-string", "hex");

// Create signature provider
const sigProv = newSignatureProvider(keypair);

// Create authentication descriptor
const authDescriptor = createSingleSigAuthDescriptorRegistration(["A", "T"], keypair.pubKey, null);
const ras_params = gtvft4.authDescriptorRegistrationToGtv(authDescriptor);

// Create and build the transaction
let tx = gtx.emptyGtx(brid);

// Add transfer fee operation
tx = gtx.addTransactionToGtx("ft4.ras_transfer_fee", [assetId, ras_params, null], tx);

// Add account registration operation
tx = gtx.addTransactionToGtx("ft4.register_account", [], tx);

// Add NOP operation
tx = gtx.addTransactionToGtx("nop", [Buffer.from([0])], tx);

// Add signer and sign the transaction
gtx.addSignerToGtx(keypair.pubKey, tx);
tx = await gtx.sign(tx, sigProv);

// Serialize and send the transaction
const encodedSignedTx = gtx.serialize(tx);
const signedTx = await chromiaClient.sendTransaction(encodedSignedTx);