Skip to main content

Register accounts

Account registration allows you to create new accounts within the system. Users will normally be allowed to register accounts using the account registration framework, while the admin has a special operation that can be used too. Custom operations can also be defined to allow for more specific use cases.

Registering with FT4 admin operation

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're allowed to 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 information, see auth descriptor.

To register an account using FT authentication, follow these steps:

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

    chr keygen --save .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 the above command, 0 represents a single signature auth descriptor, while A (account) and T (transfer) are auth flags defined in the FT library. 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.

    In the above command, replace 0351D4F299E3D33EC745C9F3C2F74934960F58411BE8BAE52A1E6EC8D0BA26AEDB with the generated pubkey 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"]

Registering with a custom operation

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 would be a hash in the format:

    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 --save .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

    In the above command, replace 03772E03AE22835384164AA90E28C84F78C97D29A2635861DC3F7E32F0CC8FDF51 with the generated pubkey 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"]

Account registration framework

As a dapp developer, you can configure your dapp to make use of the account registration framework to allow users to create accounts. The framework allows for two main ways for users to create an account:

  • Open: In which anyone can simply call the register_account() operation to create an account.
  • Transfer: In which someone must perform a transfer to the account address before the user account can be created.

When using the transfer strategy, there are three sub-strategies:

  • Open: The user can claim the entire deposit to their newly created account.
  • Fee: Part of the transfered assets gets collected to the chain's fee account and user can only claim the remaining assets to their account.
  • Subscription: Similar to fee, but instead of assets being moved once on registration, the user needs to periodically renew their subscription by paying the subscription fee to keep using their account.

A dapp can use any number of the available strategies and they can be enabled by importing the corresponding module into your dapp. Furthermore, each strategy can be configured using their corresponding key in the config file. If a strategy is imported, then users can use that strategy to register an account. The modules are named as following:

  • 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)

Let us say that we want to configure our dapp to allow account creation on transfer via the fee strategy, then we could do that by for example importing that strategy in our main module.rell:

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

Then we 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.

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)
);

Registering with an auth server

To simplify the registration process, we've built a basic backend app that allows you to rate-limit registrations to your blockchain. This app is called auth server.

When the auth server is running, people can register an account by following specific criteria. For more details about configuring the server, see auth server.