TokenD JavaScript SDK

The TokenD JavaScript SDK facilitates client integration with the TokenD asset tokenizaton platform.

Table of content

  1. Platform Overview

  2. Javascript SDK

  3. Development Guide

  4. Troubleshooting

  5. Use cases

Platform Overview

There two ways to interact with TokenD platform:

  • By calling REST services(API)
  • By building, signing and submitting blockchain transactions

Every user has a keypair that is used to authorize requests and sign the blockchain transactions. The keypair public key is used to identify user within the system.

JavaScript SDK

Installation

npm install -S tokend-sdk

Webpack

If you use webpack as your build system you'll need to exclude the optional native module ed25519

  plugins: [
    new webpack.IgnorePlugin(/ed25519/)
  ]

You can also checkout package's webpack config.

Prebuilt Minified Scripts

The package also ships prebuilt minified scripts for browsers in the /dist folder.

<script type="text/javascript" src="https://<sdk-dist-url>"></script>
<script type="text/javascript">
  (async () => {
    let sdk = await Sdk.TokenD.create('https://<tokend-backend-url>')
    // ...
  })()
</script>

TokenD SDK

To get started create a TokenD SDK instance:

import { TokenD } from 'tokend-sdk'

let sdk = await TokenD.create('https://<tokend-backend-url>')

You can configure different environment setting such as proxy configuration via options.

Response Format

All HTTP responses share the following format:

{
  httpStatus: 200,

  // Flattened and camel-cased response data
  data: [
    {
      balanceId: 'BCTZM23JQ4RT5L643R2SUPM3VSR3AISVXWX56KNYJVZB2L4TNNDEFXDG',
      accountId: 'GD3EIROYAVQUVDCSZDYVTOQSK7LGWPNXVIIQ7W7D5A7UFEJVLVH22GNY',
      asset: 'ETH'
    },
    {
      balanceId: 'BBRL3IVE7QD4YGEWKVQRF5YVOK37PXNZZGR7ILZOYQ5SMZVRLFGOMISX',
      accountId: 'GCBUB6JILEXAFGE6VIGJMPQUQHFCM5N6JSREA65P23SG2YLEVLIOAJNU',
      asset: 'ETH'
    }
  ],

  // Response headers
  headers: {...},

  // Parsed links and relations
  fetchNext: () => {...},
  fetchPrev: () => {...},
  fetchAccount: () => {...}
}

The links and relations that are returned with the responses are converted into functions you can call on the returned object. For example you can use them for simple pagination through collections:

let page = await sdk.horizon.balances.getPage()
console.log('Page', page.data)

let prevPage = await page.fetchPrev()
console.log('Previous page', prevPage.data)

Errors

Common errors

Wrappers for error responses

All the error responses subclass ServerErrorBase and share the following format:

{
  httpStatus: 403,
  // Human readable title
  title: 'Forbidden',
  // Detailed explanation
  detail: 'Additional factor required.',
  // Additional relevant data
  meta: {
    factorId: 275,
    factorType: 'password',
    ...
  },
  // Raw unparsed error
  originalError: {...},
  // Retry request. Handy for 2FA handling
  retryRequest: () => {...}
}

Interceptors

SDK allows you to use request and response interceptors:

sdk.api.useRequestInterceptors(
  request => {
    // Track user's actions, transform request data, etc
  },
  err => {
    // Log, handle errors, retry requests, etc
  }
)

sdk.api.useResponseInterceptor(
  config => {
    // Parse and transform response data, show notifications, etc
  },
  err => {
    // Track errors, try to retry requests, show 2FA prompts, etc
  }
)

Wallets

Wallets hold user's keypair and account ID that are used to identify user, authorize access to the backend services and sign the blockhain transactions.

Create a wallet

let { wallet, recoverySeed } = await sdk.api.wallets.create(
  'my@email.com',
  'MyPassw0rd'
)

// Get the confirmation token from email
await sdk.api.wallets.verifyEmail(token)

Retrieve and use the wallet to sign requests

let wallet = await sdk.api.wallets.get('my@email.com', 'MyPassw0rd')
sdk.useWallet(wallet)

Change password

let updateWallet = await sdk.api.wallets.changePassword('MyNewPassw0rd')
sdk.useWallet(updatedWallet)

Recover the password

let recoveredWallet = await sdk.api.wallets.recovery(
  'my@email.com',
  recoverySeed,
  'MyNewPassw0rd'
)

API Server

API server is responsible for multiple activities:

  • Stores wallets that hold encrypted keypairs
  • Handles 2 factor auth
  • Stores private off-chain data, such as KYC data and documents

Resources

Errors

Horizon Server

Horizon server is the interface for interaction with the TokenD blockchain. It allows to submit transactions and query on-chain data.

Resources

Errors

Two Factor Auth

Some actions may require 2FA. Following snipet allows utilizes interceptors to handle and retry failed requests:

import { errors } from 'tokend-sdk'

sdk.api.useResponseInterceptor(
  config => config,
  err => {
    if (err instanceof errors.api.TFARequiredError) {
      // Handle 2FA
      if (err.meta.factorType === 'password') {
        // Show password promt to user...
        return sdk.api.factors.verifyPasswordFactorAndRetry(err, password)
      } else {
        // Shot TOTP prompt to user...
        return sdk.api.factors.verifyTotpFactorAndRetry(err, otp)
      }
    } else {
      return Promise.reject(err)
    }
  }
)

Transactions

Blockhain transactions must have:

  • Source - user's account ID
  • One or more operations
  • User's signature

Building and signing

import { base } from 'tokend-sdk'

let tx = new base.TransactionBuilder(sdk.wallet.accountId)
  .addOperation(base.Operation.payment(paymentParamsObject))
  .build();

tx.sign(sdk.wallet.keypair);

Submitting

let response = await sdk.horizon.transactions.submit(tx)

Handling XDR encoded fields in responses

The transaction endpoints will return some fields in raw XDR form. You can convert this XDR to JSON using the .fromXDR() method.

An example of re-writing the txHandler from above to print the XDR fields as JSON:

import { base } from 'tokend-sdk'

let envelope = response.data.envelopeXdr
console.log(base.xdr.TransactionEnvelope.fromXDR(envelope, 'base64'))

let result = response.data.resultXdr
console.log(base.xdr.TransactionResult.fromXDR(result, 'base64'))

let resultMeta = response.data.resultMetaXdr
console.log(base.xdr.TransactionMeta.fromXDR(resultMeta, 'base64'))

Development Guide

Transpiling

As for now some handy ES7 features need transpiler in both node.js and browser environments so the babel transpiler is used.

Build for node.js:

npm run build

Build for browsers:

npm run build:browser

Coding Style

SDK follows JavaScript Standard Style.

All public classes and functions must have JSDoc annotations.

Run linter to check for style violations:

npm run lint

Testing

Node.js tests:

npm test

Browser tests:

npm tests:browser

Test coverage:

npm run coverage

Building XDR Files

SDK repos includes xdr git submodule which contains raw .x XDR files.

To update the JS wrappers:

  1. Checkout the xdr submodule to desired commit
  2. Install Ruby v 2.5.0 if needed
  3. Install rake and bundler:
     gem install rake bundler
  4. Install xdrgen dependencies
     bundle
  5. Build the XDR wrappers:
     rake xdr:update

Generating Docs

HTML docs

npm run docs

Markdown docs

npm run docs:md

Troubleshooting

Problem With Installation on Windows

When installing js-sdk on windows, you might see an error that looks similar to the following:

error MSB8020: The build tools for v120 (Platform Toolset = 'v120 ') cannot be found. To build using the v120 build tools, please install v120 build tools.  Alternatively, you may upgrade to the current Visual Studio tools by selecting the Project menu or right-click the solution, and then selecting "Retarget solution"

To resolve this issue, you should upgrade your version of nodejs, node-gyp and then re-attempt to install the offending package using npm install -g --msvs_version=2015 ed25519. Afterwards, retry installing stellar-sdk as normal.

Use Cases

Creating your own token

TokenD JS SDK makes creation of tokens as simple as it's possible for your users. To start doing it on your own, follow next steps:

  1. First of all, import the SDK in your project and initiate it. You will need several modules described here:
    import { TokenD } from 'tokend-sdk'

    const sdk = await TokenD.create('https://<tokend-backend-url>')
    const base = sdk.base // the module for crafting transactions
    const horizon = sdk.horizon // the middleware for sending requests to horizon server
    const api = sdk.api // the middleware for sending requests to api server
  1. Your token may need the logotype. Let's suppose that your app have the file field, where user can upload the image:
    <input type="file" id="#token-logo">

Simply attach the listener to the field to handle image upload

    const field = document.getElementById('token-logo')
    field.addEventListener('change', handleImageUpload)
  1. Now to save the image in TokenD storage you need some magic:

After simply deriving raw file from field event

    async function handleImageUpload (event) {
        const file = event.target.files[0]
    }

tell the API you need the space for new image and get needed params for the file upload:

    async function handleImageUpload (event) {
        const file = event.target.files[0]
        const { url, formData } = await api.documents.create('general_public', file.type)
    }

formData object will look like described in API docs API documentation. You will need it for two things: uploading the file itself and saving it's storage key in the blockchain.

Now you've got all the necessary data for uploading your token logotype. You can use any http-client (we'll use axios here) to upload it to the storage using POST request

    import { axios } from 'axios'

    async function handleImageUpload (event) {
         const file = event.target.files[0]
         const { url, formData } = await api.documents.create('general_public', file.type)
         await axios.post(url, Object.assign(formData, {
            file: new Blob([file], { type: file.type })
         }))
         return formData.key
    }
  1. Now you can create the token itself. For doing this, create the operation:
    const operation = base.ManageAssetBuilder.assetCreationRequest({
      requestID: '0', // Request ID, if 0 - creates new, updates otherwise
      code: 'TKN', // Asset code
      preissuedAssetSigner: 'GBT3XFWQUHUTKZMI22TVTWRA7UHV2LIO2BIFNRCH3CXWPYVYPTMXMDGC', //  AccountID of keypair which will sign request for asset to be authrorized to be issued
      maxIssuanceAmount: '100000', //  Max amount can be issued of that asset
      policies: 0, // Asset policies
      initialPreissuedAmount: '100000', // Amount of pre issued tokens available after creation of the asset
      details: {
           name: 'My first token'
           logo: {
               key: key // the key you've derived before
           }
       }
    })
  }

Craft the transaction and sign it:

    const seed = 'SA4CAMSMX6CRAC4XPUPUDAC5VYSFQRWEEFDBVBEDIIRWNEHDYAX5OHMC' // is just an example, replace it with the actual one

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

    let tx = new base.TransactionBuilder(accountId)
      .addOperation(operation) // the previously created operation
      .build()

    tx.sign(keypair)

    const txResponse = await horizon.transactions.submit(tx) // returns promise, so you can handle response: