Postchain FT

Background

Postchain FT is a module which implements tranferrable tokens. This library provides a way to create FT transactions and send them to a Postchain node, and also query information from Postchain node.

Model

Accounts

Users work with FT through accounts. Each account has its own balance, history and it specifies authorization necessary to transfer tokens.

Account descriptor (account_desc) is a byte array which describes the account:

  1. account type (simple, multi-sig, …)
  2. public key(s) associated with the account
  3. other parameters

Account ID is a hash of account descriptor, which serves as a short ID used to reference the account.

Account needs to be registered in the system before it can used.

FT can work in two modes:

  • permissionless, where anybody can register an account
  • permissioned, where only specially designated party can register an account

The later mode creates an opportunity to introduce identity checks, KYC/AML compliance, etc.

Assets and issuers

FT supports multiple different assets, such as currencies, to exist within the system. (Currently a list of assets is specified in the configuration at the time blockchain in created.)

Asset identifier is a string. For example, “USD”. If there is a need to create different non-fungible USD tokens, we can just give them longer names, e.g.:

  • “USD_LHV” are USD tokens created by LHV bank
  • “USD_Citi” are USD tokens created by Citi bank

Or you can associate numeric codes, e.g. “001” will be USD from LHV bank.

Each asset has a list of issuers. Issuers can create new tokens. Each issuer has a special account which tracks token issuance, that account value will be negative. Thus total amount of tokens in the system is always zero. Tokens can be sent back to issuer’s account to destroy/redeem them.

Amounts is specified in “atoms”, i.e. smallest units. For example, for US dollar the smallest unit is cent, thus all amounts are in cents.

GTX

FT module uses GTX – generalized transaction format. A GTX transaction includes a list of operations to perform. Each operation is specified by the name of the operation and alist of arguments e.g.:

register(xxx)
issue(issuerID, "USD", 1000, receiverID)

GTX is encoded using ASN.1 DER.

Tutorial

Initialize library

We will assume node.js environment. Library can also work in browser, but we currently do not provide browser bundle. Instead we recommend using browserify, webpack, etc.

When you create a FTClient instance, you need to pass it Postchain client API URL. (Alternatively, one can pass Postchain restClient)

const FTClient = require('ft-client');
const ftClient = new FTClient("http://localhost:7741");

If library is used to create and sign transaction offline, null can be passed instead.

Create user keys

ft-client currently lacks means to create cryptographic keys. FT uses secp256k1 digital signatures – same as Bitcoin and Ethereum – so existing libraries can be used.

Note: we use libsecp256k1 data format for public and private keys and signatures. Compact 64-byte signature format is used.

    const secp256k1 = require('secp256k1');
    const randomBytes
require('crypto').randomBytes;

    function makeKeyPair () {
        let privKey;
        do {
           privKey = randomBytes(32);
        } while (!secp256k1.privateKeyVerify(privKey));
        const pubKey = secp256k1.publicKeyCreate(privKey);
        return {pubKey, privKey};
    }

Create user account

To be able to use FT, user needs to create and register an account. Here’s a typical process:

  1. Create account descriptor from user’s public key.
  2. Create an account ID.
  3. Pass the account descriptor to an authority which can register accounts, or self-register if FT is used in permissionless mode.
  4. Somebody calls register operation.

E.g.

const user1 = makeKeyPair();
const user1.account_desc = ftClient.makeSimpleAccountDesc(user1.pubKey);
const user1.account_id = ftClient.makeAccountID(user1.account_desc);
const tx = ftClient.makeTransaction([user1.pubKey]);
tx.register(user1.account_desc);
tx.sign(user1.privKey, user1.pubKey);
tx.submitAndWaitConfirmation().then(...)

Creating transactions

Code above creates and submits a transaction which register a user. Let’s consider it in more details:

  1. First you need to call ftClient.makeTransaction and supply it a list of signers, i.e. public keys which will sign this transaction.
  2. Then you call operations, e.g. tx.register(…) or tx.send(…). One transaction can include multiple operations, e.g. you can register a user and issue some tokens to him in one transaction.
  3. Then you sign a transaction by calling tx.sign(privKey), optionally also passing public key.
  4. After transaction is signed by all signers, it can be submitted. Usually you want to wait until it’s processed, thus you call tx.submitAndWaitConfirmation(), which returns a promise.

Issuing tokens

tx.issue(issuerID, "USD", 100000, user1.account_id);

This would issue 1000.00 USD to user1. Transaction must be signed by USD issuer.

Sending tokens

tx.send({
    from: user1.account_id,
    to: user2.account_id,
    amount: 1000,
    assetID: USD
    })

This would send 10 USD from user1 to user2. Needs to be signed by user1.

send is a shorthand function. Low-level operation called xfer supports multiple inputs and outputs, as well as multiple currencies. E.g. the send operation above can also be specified as

tx.xfer([[user1.account_id, "USD", 1000]],
    [[user2.account_id, "USD", 1000]]);

You can atomically change dollars for euros this way:

tx.xfer([[user1.account_id, "USD", 1000],
    [user2.account_id, "EUR", 500]],
    [[user2.account_id, "USD", 1000],
    [user1.account_id, "EUR", 500]]);

This transaction must be signed by both user1 and user2.

Standalone signing

Sometimes you might want to sign a transaction on a different devices. To do this:

  1. Get a digest for signing: tx.getDigestForSigning()
  2. Sign digest using secp256k1.
  3. Call tx.addSignature(pubKey, signature)

Query data

  1. ftClient.getBalance(account_id, asset_id), returns a promise for a number
  2. ftClient.getHistory(account_id, asset_id), returns a promise for a list of objects

Example:

[ { delta: '100000',
    tx_rid: '30b8029dc4da1a3dae2fcfb6355092db394ef4e48503907ac3d4bf35b0945ba1',
    op_index: 2 },
    { delta: '-5000',
    tx_rid: 'ee9d01bf81037cfaf0ce4f1c4f464db6c1cc111a74d7225c8f318e8b6cbd42cc',
    op_index: 0 } ]