TokenD JavaScript SDK
The TokenD JavaScript SDK facilitates client integration with the TokenD asset tokenizaton platform.
Table of content
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
- ApiError - base class for API errors
- BadRequestError
- NotAllowedError
- ForbiddenRequestError
- TFARequiredError
- VerificationRequiredError
- NotFoundError
- ConflictError
- InternalServerError
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
- HorizonError - base class for Horizon errors
- BadRequestError
- UnauthorizedError
- TFARequiredError
- NotFoundError
- InternalServerError
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:
- Checkout the
xdr
submodule to desired commit - Install Ruby v 2.5.0 if needed
- Install
rake
andbundler
:gem install rake bundler
- Install
xdrgen
dependenciesbundle
- 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:
- 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
- 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)
- 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
}
- 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: