Installation

1
2
3
$ npm i @trustology/trustvault-nodejs-sdk
# Or:
$ yarn add @trustology/trustvault-nodejs-sdk

Creating a TrustVault Client

1
2
3
4
5
6
7
8
9
10
11
// common
const { TrustVault } = require("@trustology/trustvault-nodejs-sdk");

// es6
import { TrustVault } from "@trustology/trustvault-nodejs-sdk";

// sandbox env
const trustVaultSandbox = new TrustVault({ apiKey: "<TRUST_VAULT_API_KEY>", environment: "sandbox" });

// production
const trustVault = new TrustVault({ apiKey: "<TRUST_VAULT_API_KEY>"});

Overview

The SDK wraps the TrustVault GraphQL interface and gives you easy access to core functionalities within the API.

The API can be configured against sandbox (ETH ropsten, BTC testnet3) or production environments.

The SDK is focussed on sub-wallets. This article will help on understanding the differences between wallets and sub-wallets.

If you use the SDK with an external instruction key the SDK provides callbacks for you to sign the correct data when creating a transaction or requesting a policy change on a wallet.

The SDK includes helper methods to ensure the data integrity including validating policy template, publicKey provenance, HMAC webhook payload and digests. This ensures that the digests that need signing contain the input data specified from the SDK. This helps prevents man-in-middle attacks.

It also includes a sample AWS KMS wrapper class for data signing.

The SDK requires an API key, please contact Bitpanda Custody to get one.

See TrustVault Node.js SDK in github

Sign callback

The function that will be called with the data that needs to be signed.

The sign callback is called after the request has been created, passing in the request data that needs to be signed as a parameter. The callback allows you to immediately sign the created request. In production environments it is strongly advised that you confirm the shaSignData that was passed in your sign callback is what you expect by re-creating the shaSignData using your original input values.

Sign callback is optional for replacePublicKeyInDefaultSchedule, sendBitcoin and sendEthereum methods. If the sign callback is not given, the created request will not be signed and will stay in AWAITING_SIGNATURES status, the request can then be signed on the iOS device. See request statuses below.

1
2
3
4
5
6
7
8
9
10
11
type SignCallback = (signDataBuffer: SignDataBuffer) => Promise<PublicKeySignaturePairBuffer>;
interface SignDataBuffer {
// The DER encoded transaction digest and the wallet path
signData: Buffer;
// The SHA256 digest of the signData
shaSignData: Buffer;
}
interface PublicKeySignaturePairBuffer {
publicKey: Buffer; // 65 bytes - (`04` prefixed DER encoded)
signature: Buffer; // 64 bytes - (r + s ~ 32 bytes + 32 bytes)
}

An example of a sign callback (AWS KMS implementation) can be found here

AWS KMS wrapper class (AwsKmsKeyStore)

This is an example of how you can use AWS KMS to sign transactions via the SDK. You should have your own AWS account, with your own AWS KMS instance, which should be securely locked down via IAM roles.

This sample class, AwsKmsKeyStore, shows how you can ensure that the correct key from the correct curve is used for signing. It has a method sign which is an implementation of the signCallback.

You will need to ensure that your code has the relevant AWS permissions to access your KMS key. The AwsKmsKeyStore implementation can be found here

Create a new sub-wallet

This allows you to create a new sub-wallet inside an existing wallet. You can use the get sub-wallets to find your existing walletIds. Since a walletId will never change (it can only be deleted). You may consider hard coding the walletId.

Please note: this method will return a Provenance Signature (trustVaultProvenanceSignature) of the signed PublicKey from the TrustVault API which will be checked against the TrustVault Master Public Key to ensure validity and will then generate the address on the client to ensure the address given (verifiedAddress) is correct.

1
2
3
const createResponse = await trustVault.createSubWallet({ name: "My subwallet", walletId: "c7e828ed-77a3-4907-b129-651c2377a929", subWalletType: "ETH" });

console.info(JSON.stringify(createResponse));

Returns

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"subWalletId": "c7e828ed-77a3-4907-b129-651c2377a929/ETH/1",
"receiveAddressDetails": {
"path": [
"0x8000002c",
"0x8000003c",
"0x80000001",
"0x0",
"0x0"
],
"publicKey": "0474567eac06c8b185483f8692ff6093793479449a886556febe9883ef350c4715051b0a6984b0bad4b8a2d9c538fe424db66b76e2f198a405d39007ed97313c6e",
"trustVaultProvenanceSignature": "00f61212deadc84f10f68bc1454a3bc297df09309fa0fcedaaceb2685e2bbfa2131db56585c739e94df30144854291cc44d77db64963c6a2f1f81a5835364227",
"verifiedAddress": "0x07EC8dead12323EF3DF095Ff1a10D88E0d17cA35"
}
}

Get a list of all sub-wallets

This allows you to page through all the sub-wallets. The default is to return 20 items per call. The following code will retrieve all sub-wallets associated with the API key by paging through them 10 at a time. Use with caution if you have hundreds of sub-wallets.

1
2
3
4
5
6
7
8
9
10
let nextToken;
do {
const subWallets: ResultConnection<SubWallet[]> = await trustVault.getSubWalletsConnection({
limit: 10, // retrieve 10 at a time
nextToken,
includeBalances: true // return the balances for each sub-wallet
});
// process the subWallets with subWallets.items
nextToken = subWallets.nextToken;
} while (nextToken);

Get a single sub-wallet

Retrieve the specific subWallets associated. Use this method to get a specific sub-wallet based on the subWalletId.

1
const subWallet = await trustVault.getSubWallet("0bee0ebb-f3b8-47af-a4aa-d7f1755d89e4/ETH/0");

Returns

1
2
3
4
5
6
7
8
9
10
11
12
{
"address": "0x829bd824b016326a401d083b33d092293333a830",
"name": "Test",
"subWalletId": "3623d274-337e-4cf6-b496-37a4b638f3c1/ETH/0",
"createdAt": "2020-04-01T15:43:45.000Z",
"updatedAt": "2020-04-01T15:43:45.000Z",
"chain": "ETHEREUM",
"publicKey": "04b2536695a94f0ce089471e8b98da134ed6eac1c10b07d5b57d90b26936aafe33ef137225a935f953ef99ab3a4cd0c7b4e881bef9deb4bb187b7da4c37b715e58",
"trustVaultPublicKeySignature": "df7fe7e1b3e2a40abf2dd0116714eeb0386cc6be4fad3df2e58464105f8af2f18e998cefe6894bea617ddf7cdc370a218ddeaf3b3c3308af22baf9e655391358",
"__typename": "BlockchainSubWallet",
"walletId": "3623d274-337e-4cf6-b496-37a4b638f3c1"
}

You can additionally, pass an options object to request balances of the sub-wallet. This will be slightly slower as it will retrieve balances of all assets

1
const subWallet = await trustVault.getSubWallet("0bee0ebb-f3b8-47af-a4aa-d7f1755d89e4/ETH/0", { includeBalances: true });

Send a Ripple Transaction

1
2
3
4
5
/* Instantiate TrustVault */
const trustVault = new TrustVault({ ... });

/* Send Ripple */
trustVault.sendRipple(destination, amount, subWalletId).then(console.info).catch(console.error);

Passing in a sign callback function as the last parameter to sendRipple(...) above will verify and sign the transaction as well.

Send a Bitcoin Transaction

Send a bitcoin transaction. This will still need to be signed by the wallet policy delegates.

1
const requestId = await trustVault.sendBitcoin(fromSubWalletId, toAddress, amount, speed, signCallback);

Send an Ethereum Transaction

Send an ethereum transaction. This will still need to be signed by the wallet policy delegates.

NB: Supported Assets are here

1
2
3
4
5
6
7
8
9
10
11
12
fromAddress = "0xbcc817f057950b0df41206c5d7125e6225cae18f" // your TrustVault address to send the transaction from 
toAddress = "0xbcc817f057950b0df41206c5d7125e6225cae18f" // the address to send the transaction to. If ERC-20, the recipient of the tokens, NOT the contract address
amount = "100000" //decimal string, for ETH integer in WEI, for ERC-20 the number of tokens in smallest unit.
asset = "ETH" // The asset to send eg. ETH or the ERC-20 assetSymbol from the supported assets (See above)
speed = undefined // "FAST" | "MEDIUM" | "SLOW" if you want TrustVault to calculate the fee or undefined to provide yourself
currency = "GBP" // The currency to return any valuation data in
signCallback = undefined // custom function you want to sign the data. undefined if you want to sign on iOS devices
gasPrice = "25000000000" // gasPrice in WEI if you wish to set your own
gasLimit = "21000" // gasLimit if you wish to set your own (undefined means `eth_estimateGas` will be used)

// Send a transaction with no callBack (use iOS to sign) and set the gasPrice to 25 GWEI with gasLimit of 21000 (Eth transfer only)
const requestId = await trustVault.sendEthereum(fromAddress, toAddress, amount, asset, speed, currency, signCallback, gasPrice, gasLimit)

Changing the external instruction key of a wallet

Change the external instruction key of a wallet. This is required if you wish to sign transactions yourself rather than on a registered iOS device. This method is useful for testing as it simply changes the policy to allow a single key to sign transactions.

1
const requestId = await trustVault.replacePublicKeyInDefaultSchedule(walletId, publicKey, signCallback);

With version 1.5.5 and above you can now generate more complex wallet delegate policy which may be more useful in a production environment.

The delegate schedule is an array of Schedules. Each Schedule is “OR”‘d by TrustVault. Inside each Schedule is an array of Clauses. Each Clause is “AND”‘d. Each Clause defines a quorum which is the minimum number of keys that must sign for the Clause to be satisfied.

The sample below shows a single Schedule which has 2 Clauses. Since Clauses are “AND”‘d, BOTH must be satisifed.

You can read this policy as:

1 of 2 AND 1 of 1.

i.e. If 1 of the 2 keys in the first Clause has signed AND the 1 key of the second Clause has signed, this policy is satisfied.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

try {
const newSchedule: DelegateScheduleArray = [
[
{
keys: [
"04ccabf090e0d428ef0be689b5e614fdfeec14e52fbbe95cc1e31cd4b3a083a5253c8642643bf2ba1e3fd8e08871570c5422ffedc2b5c87daf3b4b893096ae7671",
"046d8b72943e94b2fdadc3234aa7df461742a0f16729ff8e0f67751456b3f2e9e250956d53d98afda0ff8f733b375aafb8809cd85257b8e1da7203ecfb30f345e8"
],
quorumCount: 1
},
{
keys: [
"04742f39af8ea3c0c6fb1ead3f3d82e2e56a63d39d4817a13c65bbbd669a709415e3235f59f275c1c8b02ab616117c371943b4af6f2e6a7e6b880a268914d8e707"
],
quorumCount: 1
}
]
]
const re = await trustVault.createWalletPolicyChangeRequest("c7e828ed-77a3-4907-b129-651c2377a929", newSchedule)
} catch (e){
//
}

You can also pass a Sign callback to this method to immediately sign the request. Alternatively, you can wait for the webhook (POLICY_CHANGE_REQUEST_CREATED) to be received by your API and then sign the item from there. See below for signing the webhook request.

Note: Each delegate listed AND Bitpanda Custody MUST sign the change request before the policy can become active.

Validating and signing a webhook request

Validate the webhook, sign and submit the signature to TrustVault

1
2
3
TrustVault.validateWebhookSignature(req.body, "<YOUR_WEBHOOK_SECRET>", req.headers["X-Sha2-Signature"]);
const webhookMessage = JSON.parse(req.body);
const requestId = await trustVault.signAndSubmitSignature(webhookMessage, signCallback);

NOTE: validateWebhookSignature is a static method

Create a new Bitcoin Address

Create a new bitcoin address for the given subWalletId

1
const address = await trustVault.createBitcoinAddress(subWalletId);

Get request

Retrieve the request item associated with the given requestId. Use this method to query the status of your request.

1
const request = await trustVault.getRequest(requestId);

Returns

1
2
3
4
5
6
{
"requestId": "12b40615-ab28-5917-d576-5e05ab5d2944",
"type": "BTC_TRANSACTION",
"status": "SUBMITTED",
"transactionHash": "cc7fca4c8e6061427fbccc7c64670fd239ee3f943725d9896059e56b6d7d10db"
}

Cancel Request

Cancels a request item associated with the given requestId. If successful, the request will be in USER_CANCELLED status. Throws an error if the request is not in a state that can be cancelled (i.e. not AWAITING_SIGNATURES) See request statuses below

1
const success = await trustVault.cancelRequest(requestId)

You can also pass a second optional parameter that specifies the reason for cancelling the request.

1
const success = await trustVault.cancelRequest(requestId, "Reason for cancelling request")

Returns (boolean)

1
true

Request Statuses

Request Status Description
AWAITING_SIGNATURES the request is still awaiting signatures enough signature before it can be processed
SIGNED the request has received enough signatures to be processed
SUBMITTED the transaction request has been processed and submitted to the network
CONFIRMED the transaction request has been confirmed by the network (1+ confirmation)
PROCESSED the request has been successfully processed
USER_CANCELLED the request has been cancelled by the user
ERROR an error occurred when processing the request