Skip to main content

Register accounts

You can create new accounts within the system through the account registration process. Users typically use the account registration framework, while admins have a specialized operation. Custom operations can also be defined for more specific use cases.

Account registration framework

As a dapp developer, you can configure your dapp to use the account registration framework provided by the FT4 library. This framework handles the account creation process securely and efficiently, offering different registration strategies to suit your needs.

Registration strategies

The account registration framework supports the following strategies:

  • Open: Anyone can call the register_account() operation to create an account without any restrictions.
  • Transfer strategy: Users must perform a transfer to the account address before they can create an account. The transfer strategy has three sub-strategies:
    • Open: The user can claim the entire deposit to their newly created account.
    • Fee: Part of the transferred assets is collected as a fee to the chain's fee account, and the user can only claim the remaining assets to their account.
    • Subscription: Similar to the fee strategy, but the user needs to periodically renew their subscription by paying the subscription fee to keep using their account.

To enable a specific strategy, import the corresponding module into your Rell file and configure it in the chromia.yml file. The modules are named as follows:

  • lib.ft4.core.accounts.strategies.open (open strategy)
  • lib.ft4.core.accounts.strategies.transfer.open (transfer open strategy)
  • lib.ft4.core.accounts.strategies.transfer.fee (transfer fee strategy)
  • lib.ft4.core.accounts.strategies.transfer.subscription (transfer subscription strategy)

Transfer strategy

In order to use any of the transfer strategies, they first need some moduleArgs configured in the chromia.yml file. We configure them under the key:

lib.ft4.core.accounts.strategies.transfer

Under that key, we need to, among other things, configure what chains are allowed to make transfers to our chain, and what assets we accept from them. If we did not do this, we would expose our app to DOS attacks and spam from chains that we do not trust. Keep this aspect in mind when configuring these values. An example configuration could look like this:

lib.ft4.core.accounts.strategies.transfer:
rules:
- sender_blockchain: # List of blockchain rids from which we will accept transfers
- x"08B02E0E14B634031FDF2ED3FD78E7410A5849CD28"
sender: * # Anyone on the specified blockchain can send us assets
recipient: * # They can send assets to anyone on this chain
asset: # List of assets that can be sent from this chain
- name: CHR # Name of the asset (id can also be used instead)
min_amount: 5L # If transfer is of less than this value, then the transfer will be rejected
timeout_days: 30 # After this many days, the sender is allowed to recall the transfer if it hasn't been claimed
strategy: # List of transfer strategies to enable
- "fee"
- "open"

In this simple example, we allow every user on a specific chromia blockchain to send at least 5 CHR to every user on our blockchain and if the recipient of the transfer does not exist, they can either create an account using the open strategy or the fee strategy. As we can see, this configuration accepts a list of blockchains which gives us extremely granular control over who can send assets and create accounts on our chain.

If we chose to only enable the open strategy, we would not have to configure anything else. However, the open strategy will in many cases not be the optimal spam protection strategy, as it would allow a spammer to create many accounts using only a small amount of assets. Therefore, it is recommended to use one of the other strategies, e.g., the fee strategy.

In order to configure the fee strategy, we need to take a few extra steps in addition to the ones we have above. Namely import the strategy in our main module.rell:

import lib.ft4.accounts.strategies.transfer.fee;

And then we also need to configure the strategy by listing what asset(s) are valid for making the payment, and how much of that asset the fee should be as well as where the payment should end up. We do this by adding a config for the corresponding key in our config file under the moduleArgs key:

lib.ft4.core.accounts.strategies.transfer.fee:
asset:
- id: x"b31ba66a11a28930d948c8f959cc306184096d1ee858542e765a139b3c79b1aa" # We can specify an asset by id
amount: 2L # How much of this asset to pay
- name: test1 # we can specify an asset by name, this will refer to an asset issued by this chain
amount: 1L
- issuing_blockchain_rid: x"6403ccac0c67f7cb6af78e5e15b3aaebb2b42370f0d12e099ed01fa5a068f9fb" # We can also specify assets issued by a different chain, even if the names are the same
name: test1
amount: 3L
fee_account: x"023c72addb4fdf09af94f0c94d7fe92a386a7e70cf8a1d85916386bb2535c7b1b1" # All fees will be collected into this account

In the above configuration, we allow the user to pay the fee using one of three different assets and depending on the asset, the amount is different. This could e.g., be used to handle relative value difference between different assets, or perhaps incentivise users to pay the fee with a certain asset. Lastly, we also configure the account in which the fees will be collected. This should typically be an account which is controlled by the dapp owner.

caution

The amount you configure for an asset here must be lower than the value you specify for min_amount for this asset, as otherwise the user might send an amount that is too low which will mean that the user will be prevented from creating an account until the transfer timeout is reached. They can then recall and redo the transfer with a correct amount.

When the user want to register an account with a transfer strategy, there first needs to be a transfer made to this account. Depending on if the user already have an account with enough asserts, they might be able to do this themselvs or perhaps have a friend do it for them, in which case it would act a bit like an invitation system. This transfer can be made from the client:

const {
createAmountFromBalance,
registerAccount,
registrationStrategy,
} = require("@chromia/ft4")
const { gtv } = require("@chromia/ft4")

// Get a list of assets that `account` can use to create an account with id `recipientId`
const connection = /* A connection to the blockchain */
const account = /* The authenticated account which we will use to send assets from */
const keystore = /* Keystore for the new account */
const recipientId = gtv.gtvHash(keystore.pubKey);
const allowedAssets = await connection.query(allowedAssets(connection.blockchainRid, account.id, recipientId));

// In a production environment, you probably want to check that `account` has enough balance of this asset before selecting it
const asset = allowedAssets[0];
const amount = createAmountFromBalance(asset.min_amount, asset.decimals);

// Transfer assets from the original account to the account to be created
await account.transfer(recipientId, asset.asset_id, amount);

// Register the account, the session that is returned can be used to interact with the newly created account
const { session } = await registerAccount(
connection.clientInformation,
keyStore,
registrationStrategy.transferFee(asset, authDescriptor)
);
info

If you want to see an example of account registration using transfer open strategy, you can explore the Transfer open strategy account registration demo.

Register with FT4 admin operation

In certain scenarios, you may need to create accounts directly from an admin account. The FT4 library provides an admin operation specifically for this purpose. However, as mentioned earlier, it's crucial to exercise caution when using the admin module in production environments due to the potential security risks it introduces.

The FT4 admin operation, ft4.admin.register_account, requires an auth descriptor as a parameter. An auth descriptor specifies who can access an account, what actions they can perform, and the lifetime of the auth descriptor. The access to an account is determined by a public key when FT authentication (native Chromia signatures) is used or an EVM account address when an EVM wallet is used for authentication.

The actions allowed by the auth descriptor are specified with authorization keys, and expiration rules determine the activation and validity duration of the auth descriptor. For more details, see the auth descriptor topic.

To create an account using the FT4 admin operation, follow these steps:

  1. Generate a new key pair for the account and retrieve the public key:

    chr keygen --file .chromia/user.keypair | grep pubkey
  2. Register the account using the FT4 admin operation:

    chr tx ft4.admin.register_account \
    '[0, [["A","T"], x"0351D4F299E3D33EC745C9F3C2F74934960F58411BE8BAE52A1E6EC8D0BA26AEDB"], null]' \
    --await --secret .chromia/ft4-admin.keypair

    In this command:

    • 0 represents a single signature auth descriptor.

    • A (account) and T (transfer) are auth flags defined in the FT library.

      note

      It's important to note that when calling FT account operations, such as ft4.add_auth_descriptor or ft4.delete_auth_descriptor, the authentication will fail unless the auth descriptor used has the A flag. Similarly, when calling the transfer operation, the transfer will be rejected if the auth descriptor doesn't have the T flag.

    • Replace 0351D4F299E3D33EC745C9F3C2F74934960F58411BE8BAE52A1E6EC8D0BA26AEDB with the generated public key for the user.

  3. You can add a query that returns all accounts to verify if the account exists. Add the following query to your code:

    query get_all_accounts() = accounts.account @* {} (.id);
  4. Update and wait for the blockchain to reflect the changes:

    chr node update
  5. You can now execute the query to retrieve all accounts:

    chr query get_all_accounts

    The output will display the account ID(s), such as:

    [x"5E2488889F72939DD4D0A034FB91893ACBF14C7EDBCEF2A9F5C621A07169EAD2"]

Register with a custom operation

If the provided account registration framework or admin operation does not meet your specific requirements, you can write a custom operation to handle account registration. By writing a custom operation, you have complete control over the account registration process, allowing you to tailor it to your specific requirements and security considerations.

tip

When writing a custom operation, it's recommended to follow best practices for secure coding, code reviews, and thorough testing to ensure the integrity and reliability of your account registration process.

When registering an account with a custom operation, it's essential to protect the operation to prevent potential spam attacks on the blockchain. In this example, we require users to provide a voucher during registration. We must create a register and an admin operation to enable vouchers to be added.

  1. Define the voucher entity and the add_voucher operation in your code:

    entity voucher {
    hash: byte_array;
    mutable is_used: boolean = false;
    }

    operation add_voucher(hash: byte_array) {
    admin.require_admin();
    create voucher(hash);
    }
  2. Define the register_account operation, which includes voucher validation and account creation:

    operation register_account(accounts.auth_descriptor, voucher_code: text) {
    // extract pubkey from auth descriptor
    val pubkey = byte_array.from_gtv(auth_descriptor.args[1]);

    // check if provided key is signer
    require(op_context.is_signer(pubkey), "Transaction needs to be signed by %s".format(pubkey));

    val hash = voucher_code.hash();
    val voucher = require(
    voucher @? { hash },
    "Provided voucher with code <%s> does not exist".format(voucher_code)
    );
    require(
    not voucher.is_used,
    "Provided voucher with code <%s> is already used".format(voucher_code)
    );
    voucher.is_used = true;
    accounts.create_account_with_auth(auth_descriptor);
    }
  3. After adding the code snippets to the appropriate sections in your main.rell file, update the blockchain to apply the changes.

  4. Generate a voucher hash using the chr repl command as follows:

    chr repl -c '"voucher_1".hash()'

    The output will be a hash like:

    x"E1E72D0C6C975815BD3259D81E67253D98CF90D888B4C7CB393C8CFB9043BAF3"
  5. Add the voucher hash to the blockchain using the admin operation:

    chr tx add_voucher \
    'x"E1E72D0C6C975815BD3259D81E67253D98CF90D888B4C7CB393C8CFB9043BAF3"' \
    --await --secret .chromia/ft4-admin.keypair
  6. Generate a new keypair for the user who'll register the account, and retrieve the public key:

    chr keygen --file .chromia/user-2.keypair | grep pubkey
  7. Register the account using the custom operation register_account:

    chr tx register_account \
    '[0, [["A", "T"], x"03772E03AE22835384164AA90E28C84F78C97D29A2635861DC3F7E32F0CC8FDF51"], null]' \
    voucher_1 \
    --await --secret .chromia/user-2.keypair

    Replace 03772E03AE22835384164AA90E28C84F78C97D29A2635861DC3F7E32F0CC8FDF51 with the generated public key for the user.

  8. To verify the successful account registration, execute the get_all_accounts query again. You should now see two account IDs or one if you didn't follow the first half of the guide:

    chr query get_all_accounts

    The output would include the account IDs:

    [x"5E2488889F72939DD4D0A034FB91893ACBF14C7EDBCEF2A9F5C621A07169EAD2", x"79C71AF3C9C951BED380F8ADAB2E407C15CC4A9EB942AA222D870136C45801CE"]

Using an auth server

To simplify the registration process and provide additional security measures, Chromia offers an auth server. The auth server acts as a backend application that allows you to rate-limit registrations to your blockchain and enforce specific criteria for account registration.

By leveraging the auth server, you can streamline the account registration process while maintaining control over the registration criteria and rate-limiting mechanisms. For more details on configuring and using the auth server, refer to the auth server topic.