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
- How to set up the FT4 library: Client setup guide
- How to set up the Postchain JS/TS client: Postchain JS/TS client
- Detailed documentation: FT4 library overview
- Repository: FT4 library GitLab
- Memo integration guide: Memo integration guide
Required Dependencies
Install the following packages using npm:
npm install @chromia/ft4@1.1.1 postchain-client@1.22.0
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://system.chromaway.com:7740",
"https://chromia.validatrium.club",
"https://chromia-mainnet-systemnode-1.stakin-nodes.com:7740",
"https://chroma.node.monster:7741",
"https://chromia.mainnet-system.nodeops.ninja:7740",
];
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";
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();
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:7740",
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);
When creating offline transactions, make sure to:
- Keep your private keys secure
- Verify all transaction parameters before signing
- Double-check the blockchain RID and node URLs
- Test the transaction on a testnet before using it on mainnet
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:7740",
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);