TokenD (Release 3.0)

What is TokenD?

TokenD is a white label platform that consolidates the experience gained by Distributed Lab in building the production-ready tokenization solutions. It allows you to issue, transfer, and exchange your assets with high level of privacy, security, and auditability while following regulations of your jurisdiction. TokenD is designed for enterprises who are willing to take advantage of tokenization or experiment with the blockchain technology without the need of maintaining the expensive team of blockchain experts and within the shortest time to market.

Why we built TokenD?

  • There are no out-of-box operating solutions available.
  • Average delivery time for MVP was ~1 year.
  • 70% of development work is the same for all projects.
  • Basic features require changes on the blockchain level.
  • High entry threshold for new developers.

How TokenD differs from other platforms?

Feature Set R3 Corda Hyperledger Fabric Ethereum TokenD
Consensus RAFT Kafka pBFT FBA
Smart Contracts
Private Transactions
External System
Integration toolkit
Web/iOS/Android Wallet
Commercial-scale funds
management kit
Role-based Access Control
Vanilla Regulatory
compliance framework

External System Integration toolkit

  • Consistent sequence of all operations occurring in the system with a set of changes applied to the ledger.
  • SDKs, intuitive REST API.
  • Unique ID tracking for crucial operations.
  • Reference implementation of external systems integration.

Web/iOS/Android Wallet

  • Fully featured opensource native web, iOS and Android apps.
  • Secure on device key storage;
  • Designed to ease customization;
  • Native SDKs to build your own implementation;

Commercial-scale funds management kit

  • Tasks based issuance and withdrawal requests designed for secure integrations with external systems;
  • Secure offline app for tokens issuance authorization;
  • On-chain crowdfunding module with several price finding algorithms;
  • Decentralized exchange;

Role-based Access Control & Multi-signature

  • Define on-chain user access rights to follow regulations;
  • Tasks based approach to review critical user’s operations in the system;
  • Define signers rules to distribute rights among admins of the system to minimize risks of fraud;
  • Use multi-signature for decentralized decision making.

Vanilla Regulatory compliance framework

  • Manage deposit, transfer, withdrawal limits to follow regulation and/or secure the system;
  • Tasks based approach for user roles transition;
  • GDPR compilate off-chain personal data storage with guaranty of integrity;
  • Direct user-admin communication to resolve issues.

Try the demo

TokenD Developer Edition

Quick way for running TokenD development environment with Docker. There are known issues and it's not intended for any kind of production use. If you are interested in running TokenD please reach us at dev@distributedlab.com.

Requirements

  • Docker
  • Compose
  • Linux/MacOS, Windows support is experimental and has known issues.

Bootstraping your environment

# drop any persistent state to make sure you are working with clean install
$ docker-compose down -v
# spin everything up
$ docker-compose up -d
# wait while environment initialization is complete
$ docker-compose logs -f initscripts

Now you should be able to access web client at http://localhost:8060 and admin dashboard at http://localhost:8070 default seed for login is SAMJKTZVW5UOHCDK5INYJNORF2HRKYI72M5XSZCBYAHQHR34FFR4Z6G4

These instructions are just a guideline for what you should generally do. You may modify provided docker-compose.yml to accommodate your needs.

Architecture

TokenD is a highly modular system built using the microservices architecture. TokenD can be divided into two parts: DLT-based logic (node) responsible for the key functionalities such as tokens management and distribution, rights management, etc.; auxiliary modules, which interconnect DLT with external systems, store user data, etc. A detailed overview of these modules and their connections is specified in figure below.

Architecture of TokenD system

Node

Node is a key component of the platform. It processes transactions, manages history, and provides an easy to use API to access the blockchain data. It consists of two modules:

  • Core — a replicated state machine that maintains a local copy of
    cryptographic ledger and processes transactions against it in consensus with a set of peers. It implements the federated consensus protocol and is responsible for tokens accounting and roles management. Documentation for the API is available here.

  • Horizon is the client-facing REST API server. It acts as an interface between the core and applications that want to access the network. It allows submitting transactions to the network, checking the status of accounts, and viewing transaction history. Documentation is available here.

Identity Service (KYC Storage)

Identity Service is a GDPR compliant module which stores data collected during the KYC (know your customer) procedure. To access the data, a user or admin needs to provide the digital signature, which is verified against the most recent state of the ledger. Such an approach provides a high level of security. This module also stores client-side-encrypted private keys of users. This prevents a malicious actor from getting access to the accounts of the system even having a full access to the storage. Documentation for this module is available here.

Web, iOS, Android wallets

Web, iOS, Android wallets are client facing applications that provide a wide range of functionalities: from storage of encrypted private keys on the device, to token transfers, withdrawals, and trading. They interact with the core of the system directly through the Horizon module and signs all transactions and requests locally. Such an approach ensures that users’ private keys are safe even in case of MITM (man in the middle) attacks.

PSIM

PSIM (Payment Services Integration Module) is a set of modules that play the role of a bridge between TokenD and cryptocurrencies’ public blockchains, banks, payment gateways, exchanges. They reflect corresponding operations like deposit, withdrawal and exchange rate changes in another system. Reference implementation of integration with CoinPayments is available here.

Licensing

TokenD is licensed by a Business Administrators. Trial version includes 2 Business Administrator available for 600,000 blocks (equal to ~30 days). Transactions signed by the Business Administrators at the expiration of the license are not accepted. Business Administrator is a role in the TokenD system that is responsible for setting up business rules. Only Business Administrator can perform the following operations:

  • add a user into the system;
  • manage roles and rules in the system;
  • set user roles in the system (confirm successful completion of the KYC (Know Your Customer) procedure);
  • set up fees for user operations;
  • set up rules for the token creation, review token creation requests;
  • set up rules for the creation of crowd investing campaigns;
  • list/delist particular asset pairs on a decentralized exchange;
  • set up limits and review limit requests created by users;
  • access protected data provided by users;
  • access history of all operations performed in the system;
  • create/update/delete Business Administrators.

  • Business Administrator shouldn't necessarily be responsible for performing all the operations specified above. Access can be granted to a subset of these operations.
  • During the system setup, one Business Administrator account is created by default with the rights to manage other Business Administrators, their rules and roles.

Pricing for Startups

  • 5 Business Administrators – 1000 EUR/month
  • 20 Business Administrators – 2000 EUR/month

Other

Pre Issuance

To ensure high level of security TokenD allows token issuers to easily manage amount of tokens available for issuance in the system. During token creation, it is possible to specify preissuanceAssetSigner. It is recommended to generate the keypair for preissuanceAssetSigner on the offline device. There are only two ways to change this key: by signing the corresponding transaction with current preissuanceAssetSigner of the token, or by creating the fork of the network. PreIssuanceRequest created by non admin account requires additional approval from the admin of the system. To be able to perform token issuance of certain amount, available_for_issuance must be equal or greater then that amount. There are two ways to make tokens available for issuance:

  • specify amount available for issuance on token creation via initialPreissuedAmount;
  • authorize amount to be issued via CreatePreIssuanceRequestOp operation.

Offline issuance application, which allows you to securely create new preissuanceAssetSigner and pre authorize tokens to be issued, is available here:

Guides

About

Guides will mostly cover the logic of submitting transactions, so here are a few statements about transactions you may want to know before starting to work with TokenD SDK and TokenD API:

  • Transaction MAY contain one or more operations
  • Transaction MUST have a source - the ID of the existing account in the TokenD system
  • Transaction MUST be signed with any valid ed25519 keypair
  • Preventing transactions from one network to be sent to another is achieved via "Network Passphrase" that should be unique for every TokenD Network and is being set during deployment

Javascript SDK exposes the ApiCaller and Wallet classes that will simplify the process of crafting/signing the transactions.

Let's assume our user has the TokenD account with ID - GD2XNPQCN6A6IRWJDADOVAL4YMDPYB6VTQRIXW7MVQAHCNBMO46KBCIC and one of it's signers has the secret seed - SBW7DY3ZEYGGWQJG7JOPYXDIQFZYAC4DKIO75VINZXPIWWIQIJDCDCMZ. It's important to know that the seed of the signer and the account ID MAY NOT correspond to one key pair - the list of account signers can be changed, while any account ID is permanent.

To submit the transaction to the TokenD network, we should create an ApiCaller instance and provide it a Wallet instance that we want to be used for signing transactions.

  import { Wallet, ApiCaller } from '@tokend/js-sdk'

  const wallet = new Wallet(
    "", // email param, usually be ommited here
    "SBW7DY3ZEYGGWQJG7JOPYXDIQFZYAC4DKIO75VINZXPIWWIQIJDCDCMZ", // the secret seed of the signer
    "GD2XNPQCN6A6IRWJDADOVAL4YMDPYB6VTQRIXW7MVQAHCNBMO46KBCIC", // the ID of the account
  )
  const api = ApiCaller.getInstance('http://tokend-backend.com')
  api.useWallet(wallet)
  api.usePassphrase("Our network passphrase")

Now api will automatically sign any transaction and set it's source from the Wallet we've provided.

  await api.postOperations(op1, op2, op3)

Create Account

First of all, we need the accountID to identify the account:

  import { base } from '@tokend/js-sdk'

  const keypair = base.Keypair.random()
  const accountId = keypair.accountId()

Then we need to add the signer to our account. For simplicity, we'll use the keypair we've created as a first signer of the account.

  const signerPublicKey = accountId

Hovewer, any keypair can be used, and even more, every account can have multiple signers.

After generating the account ID and the keypair of account's first signer, we can create the operation that can be submitted to the TokenD system:

  const operation = base.CreateAccountBuilder.createAccount({
    roleID: SOME_PREDEFINED_ACCOUNT_ROLE_ID,
    destination: accountId,
    signersData: {
      roleID: SOME_PREDEFINED_SIGNER_ROLE_ID,
      publicKey: signerPublicKey,
      weight: 1000,
      identity: 1,
      details: {}
    }
  })

  // The detailed description of fields is provided via js-doc in the sources.

  await api.postOperations(operation)

Issue Asset

The asset issuance is performed to the specific balance, not the account. So, if we need to issue 5 example assets (EXT) to the account with ID GBGEKARFDSXC6XJHTFPTKLONELARRWIHEVDR2TPBIPUAMFZCV5JLVBAA, we need to know the EXT balance ID from this account:

  const { data: account } = await api.get('/v3/accounts', {
    include: ['balances', 'balances.state'],
  })
  const extBalanceId = account.balances.find(b => b.asset.code === 'EXT')

If such balance doesn't exist, we can create one with the help of ManageBalance operation:

  const operation = base.operation.ManageBalance({
    destination: 'GBGEKARFDSXC6XJHTFPTKLONELARRWIHEVDR2TPBIPUAMFZCV5JLVBAA',
    action: base.xdr.ManageBalanceAction.createUnique()
    asset: 'EXT',
  })

  await api.postOperations(operation)

Now we have the balance exists and can issue EXT asset.

The issuance itself is performed in 2 steps - requesting the issuance and approving the request

To create the issuance request, use the CreateIssuanceBuilder exposed by SDK:

  const operation = base.CreateIssuanceRequestBuilder.createIssuanceRequest({
    asset: 'EXT',
    amount: '100.000000',
    receiver: 'BBKVOTHCUDI4X5MFYNQN7YEAJYY7OPS3HO7J3BBESPQCV23MXW7LLMKR',
    reference: 'Some unique random string',
    creatorDetails: {
      foo: 'bar'
    }
  })

  await api.postOperations(operation)

Before doing so, we can manage the key/value storage to define the tasks should be set, once request is created.

  const key = 'issuance_tasks:EXT' // or 'issuance_tasks:*' to spread the rule for every asset
  const operation = base.ManageKeyValueBuilder.putKeyValue({
    key,
    value: 8,
  })

Setting the value to 8 means that the request will be created with 8 pending tasks. 8 is a bitmask and every approving review can remove any amount of bits. Once there is no pending tasks for the request, it becomes approved. We can also make all the requests become automatically approved by setting the value to 0.

To review the issuance request, use the ReviewRequestBuilder exposed by SDK:

  const requestId = 10
  const { data: request } = await api.getWithSignature(`/v3/requests/${requestId}`)

  const operation = base.ReviewRequestBuilder.reviewRequest({
    requestID: requestId,
    requestHash: request.hash,
    requestType: request.xdrType.value,
    action: xdr.ReviewRequestOpAction.approve().value
    reason: '',
    reviewDetails: {
      tasksToAdd: 0,
      tasksToRemove: 8, // depends on our configuration
    },
  })

  await api.postOperations(operation)

Manage Poll

This is an experimental API. Experimental APIs may be changed or removed without notice.

Before creating the poll entry, you may want to configure the system to define what type of voters can participate in the poll. For doing so, you can map the permissionType of the poll with one or many account rules.

To create poll request, use the ManagePollBuilder exposed by SDK:

Create poll request:

  const SAMPLE_PERMISSION_TYPE = 4

  const operation = base.ManagePollBuilder.createPollCreationRequest({
    permissionType: SAMPLE_PERMISSION_TYPE,
    numberOfChoises: 2,
    pollType: base.xdr.PollType.singleChoice().value,
    creatorDetails: {
      'foo': 'bar'
    },
    endTime: 1553427080,
    startTime: 1553254282,
    voteConfirmationRequired: false,
    resultProviderId: 'GDWYBLSZIXTTBGWRWBAYVFE6ZDL5GBS3ILOCLVXFB3NX4FFJJDGQUKLB',
    allTasks: 1,
  })

  await api.postOperations(operation)

Detailed description of params is provided via js-doc in sources or in format of [XDR definitions][create-poll-request]

Until approved or permanently rejected, request can be cancelled by it's creator. To cancel the request, use the same ManagePollBuilder:

  const operation = base.ManagePollBuilder.cancelPollCreationRequest({
    requestId: '10',
  })

  await api.postOperations(operation)

Detailed description of params is provided via js-doc in sources or in format of [XDR definitions][cancel-poll-request]

After request is created, it can be reviewed by admin using ReviewRequestBuilder. For example, to approve request, submit the following operation:

  const requestId = 10
  const { data: request } = await api.getWithSignature(`/v3/requests/${requestId}`)

  const operation = base.ReviewRequestBuilder.reviewRequest({
    requestID: requestId,
    requestHash: request.hash,
    requestType: request.xdrType.value,
    action: xdr.ReviewRequestOpAction.approve().value
    reason: '',
    reviewDetails: {
      tasksToAdd: 0,
      tasksToRemove: 1, // depends on configuration, to fully approve request remove all pending tasks 
    },
  })

  await api.postOperations(operation)

Detailed description of params is provided via js-doc in sources or in format of [XDR definitions][review-request]

After following this steps, the poll should be created and ready to accept votes after startTime.

After endTime has passed any votes will be rejected, and poll needs to be closed explicitly by providing voting result.

To resolve poll as passed, submit the following operation:

  const pollId = 5
  const operation = base.ManagePollBuilder.managePoll({
    pollID: pollId,
    result: base.xdr.PollResult.passed().value,
  })

  await api.postOperations(operation)

If poll has failed, submit the following operation:

  const pollId = 5
  const operation = base.ManagePollBuilder.finishPoll({
    pollID: pollId,
    result: base.xdr.PollResult.failed().value,
  })

  await api.postOperations(operation)

Detailed description of params is provided via js-doc in sources or in format of [XDR definitions][manage-poll]

Vote in poll

This is an experimental API. Experimental APIs may be changed or removed without notice. If the poll is approved and started, accounts can start casting votes for the choices the poll suggests. To manage votes, SDK exposes ManageVoteBuilder for building operations.

To cast a vote in single-choice poll for the 10th option:

  const operation = base.ManageVoteBuilder.createSingleVote({
    pollId: 5,
    choice: 10,
  })

  await api.postOperations(operation)

To cast a vote in multiple-choice poll:

  const operation = base.ManageVoteBuilder.createMultipleVotes({
    pollId: 6,
    choices: [10,11,12],
  })

  await api.postOperations(operation)

Detailed description of params is provided via js-doc in sources or in format of XDR definitions

Votes can also be recalled. Same operation is used for both single and multi-choise polls:

  const operation = base.ManageVoteBuilder.removeVote({
    pollId: 5,
  })

  await api.postOperations(operation)

Detailed description of params is provided via js-doc in sources or in format of XDR definitions