diff --git a/balance-transfer/typescript/.gitignore b/balance-transfer/typescript/.gitignore
new file mode 100644
index 0000000000..9f22bacf15
--- /dev/null
+++ b/balance-transfer/typescript/.gitignore
@@ -0,0 +1,4 @@
+node_modules
+package-lock.json
+dist
+types/fabric-client
\ No newline at end of file
diff --git a/balance-transfer/typescript/README.md b/balance-transfer/typescript/README.md
new file mode 100644
index 0000000000..6a1828d76c
--- /dev/null
+++ b/balance-transfer/typescript/README.md
@@ -0,0 +1,303 @@
+## Balance transfer
+
+This is a sample Node.js application written using typescript which demonstrates
+the **__fabric-client__** and **__fabric-ca-client__** Node.js SDK APIs for typescript.
+
+### Prerequisites and setup:
+
+* [Docker](https://www.docker.com/products/overview) - v1.12 or higher
+* [Docker Compose](https://docs.docker.com/compose/overview/) - v1.8 or higher
+* [Git client](https://git-scm.com/downloads) - needed for clone commands
+* **Node.js** v6.9.0 - 6.10.0 ( __Node v7+ is not supported__ )
+* [Download Docker images](http://hyperledger-fabric.readthedocs.io/en/latest/samples.html#binaries)
+
+```
+cd fabric-samples/balance-transfer/
+```
+
+Once you have completed the above setup, you will have provisioned a local network with the following docker container configuration:
+
+* 2 CAs
+* A SOLO orderer
+* 4 peers (2 peers per Org)
+
+#### Artifacts
+
+* Crypto material has been generated using the **cryptogen** tool from Hyperledger Fabric and mounted to all peers, the orderering node and CA containers. More details regarding the cryptogen tool are available [here](http://hyperledger-fabric.readthedocs.io/en/latest/build_network.html#crypto-generator).
+
+* An Orderer genesis block (genesis.block) and channel configuration transaction (mychannel.tx) has been pre generated using the **configtxgen** tool from Hyperledger Fabric and placed within the artifacts folder. More details regarding the configtxgen tool are available [here](http://hyperledger-fabric.readthedocs.io/en/latest/build_network.html#configuration-transaction-generator).
+
+## Running the sample program
+
+There are two options available for running the balance-transfer sample as shown below.
+
+### Option 1
+
+##### Terminal Window 1
+
+```
+cd fabric-samples/balance-transfer/typescript
+
+./runApp.sh
+
+```
+
+This performs the following steps:
+* lauches the required network on your local machine
+* installs the fabric-client and fabric-ca-client node modules
+* starts the node app on PORT 4000
+
+##### Terminal Window 2
+
+NOTE: In order for the following shell script to properly parse the JSON, you must install ``jq``.
+
+See instructions at [https://stedolan.github.io/jq/](https://stedolan.github.io/jq/).
+
+Test the APIs as follows:
+```
+cd fabric-samples/balance-transfer/typescript
+
+./testAPIs.sh
+
+```
+
+### Option 2 is a more manual approach
+
+##### Terminal Window 1
+
+* Launch the network using docker-compose
+
+```
+docker-compose -f artifacts/docker-compose.yaml up
+```
+##### Terminal Window 2
+
+* Install the fabric-client and fabric-ca-client node modules
+
+```
+npm install
+```
+
+*** NOTE - If running this before the new version of the node SDK is published which includes the typescript definition files, you will need to do the following:
+
+```
+cp types/fabric-client/index.d.tx node_modules/fabric-client/index.d.ts
+cp types/fabric-ca-client/index.d.tx node_modules/fabric-ca-client/index.d.ts
+```
+
+* Start the node app on PORT 4000
+
+```
+PORT=4000 ts-node app.ts
+```
+
+##### Terminal Window 3
+
+* Execute the REST APIs from the section [Sample REST APIs Requests](https://github.com/hyperledger/fabric-samples/tree/master/balance-transfer#sample-rest-apis-requests)
+
+## Sample REST APIs Requests
+
+### Login Request
+
+* Register and enroll new users in Organization - **Org1**:
+
+`curl -s -X POST http://localhost:4000/users -H "content-type: application/x-www-form-urlencoded" -d 'username=Jim&orgName=org1'`
+
+**OUTPUT:**
+
+```
+{
+ "success": true,
+ "secret": "RaxhMgevgJcm",
+ "message": "Jim enrolled Successfully",
+ "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0OTQ4NjU1OTEsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6Im9yZzEiLCJpYXQiOjE0OTQ4NjE5OTF9.yWaJhFDuTvMQRaZIqg20Is5t-JJ_1BP58yrNLOKxtNI"
+}
+```
+
+The response contains the success/failure status, an **enrollment Secret** and a **JSON Web Token (JWT)** that is a required string in the Request Headers for subsequent requests.
+
+### Create Channel request
+
+```
+curl -s -X POST \
+ http://localhost:4000/channels \
+ -H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0OTQ4NjU1OTEsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6Im9yZzEiLCJpYXQiOjE0OTQ4NjE5OTF9.yWaJhFDuTvMQRaZIqg20Is5t-JJ_1BP58yrNLOKxtNI" \
+ -H "content-type: application/json" \
+ -d '{
+ "channelName":"mychannel",
+ "channelConfigPath":"../artifacts/channel/mychannel.tx"
+}'
+```
+
+Please note that the Header **authorization** must contain the JWT returned from the `POST /users` call
+
+### Join Channel request
+
+```
+curl -s -X POST \
+ http://localhost:4000/channels/mychannel/peers \
+ -H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0OTQ4NjU1OTEsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6Im9yZzEiLCJpYXQiOjE0OTQ4NjE5OTF9.yWaJhFDuTvMQRaZIqg20Is5t-JJ_1BP58yrNLOKxtNI" \
+ -H "content-type: application/json" \
+ -d '{
+ "peers": ["peer1","peer2"]
+}'
+```
+### Install chaincode
+
+```
+curl -s -X POST \
+ http://localhost:4000/chaincodes \
+ -H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0OTQ4NjU1OTEsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6Im9yZzEiLCJpYXQiOjE0OTQ4NjE5OTF9.yWaJhFDuTvMQRaZIqg20Is5t-JJ_1BP58yrNLOKxtNI" \
+ -H "content-type: application/json" \
+ -d '{
+ "peers": ["peer1","peer2"],
+ "chaincodeName":"mycc",
+ "chaincodePath":"github.com/example_cc",
+ "chaincodeVersion":"v0"
+}'
+```
+
+### Instantiate chaincode
+
+```
+curl -s -X POST \
+ http://localhost:4000/channels/mychannel/chaincodes \
+ -H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0OTQ4NjU1OTEsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6Im9yZzEiLCJpYXQiOjE0OTQ4NjE5OTF9.yWaJhFDuTvMQRaZIqg20Is5t-JJ_1BP58yrNLOKxtNI" \
+ -H "content-type: application/json" \
+ -d '{
+ "chaincodeName":"mycc",
+ "chaincodeVersion":"v0",
+ "args":["a","100","b","200"]
+}'
+```
+
+### Invoke request
+
+```
+curl -s -X POST \
+ http://localhost:4000/channels/mychannel/chaincodes/mycc \
+ -H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0OTQ4NjU1OTEsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6Im9yZzEiLCJpYXQiOjE0OTQ4NjE5OTF9.yWaJhFDuTvMQRaZIqg20Is5t-JJ_1BP58yrNLOKxtNI" \
+ -H "content-type: application/json" \
+ -d '{
+ "fcn":"move",
+ "args":["a","b","10"]
+}'
+```
+**NOTE:** Ensure that you save the Transaction ID from the response in order to pass this string in the subsequent query transactions.
+
+### Chaincode Query
+
+```
+curl -s -X GET \
+ "http://localhost:4000/channels/mychannel/chaincodes/mycc?peer=peer1&fcn=query&args=%5B%22a%22%5D" \
+ -H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0OTQ4NjU1OTEsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6Im9yZzEiLCJpYXQiOjE0OTQ4NjE5OTF9.yWaJhFDuTvMQRaZIqg20Is5t-JJ_1BP58yrNLOKxtNI" \
+ -H "content-type: application/json"
+```
+
+### Query Block by BlockNumber
+
+```
+curl -s -X GET \
+ "http://localhost:4000/channels/mychannel/blocks/1?peer=peer1" \
+ -H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0OTQ4NjU1OTEsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6Im9yZzEiLCJpYXQiOjE0OTQ4NjE5OTF9.yWaJhFDuTvMQRaZIqg20Is5t-JJ_1BP58yrNLOKxtNI" \
+ -H "content-type: application/json"
+```
+
+### Query Transaction by TransactionID
+
+```
+curl -s -X GET http://localhost:4000/channels/mychannel/transactions/TRX_ID?peer=peer1 \
+ -H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0OTQ4NjU1OTEsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6Im9yZzEiLCJpYXQiOjE0OTQ4NjE5OTF9.yWaJhFDuTvMQRaZIqg20Is5t-JJ_1BP58yrNLOKxtNI" \
+ -H "content-type: application/json"
+```
+**NOTE**: Here the TRX_ID can be from any previous invoke transaction
+
+
+### Query ChainInfo
+
+```
+curl -s -X GET \
+ "http://localhost:4000/channels/mychannel?peer=peer1" \
+ -H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0OTQ4NjU1OTEsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6Im9yZzEiLCJpYXQiOjE0OTQ4NjE5OTF9.yWaJhFDuTvMQRaZIqg20Is5t-JJ_1BP58yrNLOKxtNI" \
+ -H "content-type: application/json"
+```
+
+### Query Installed chaincodes
+
+```
+curl -s -X GET \
+ "http://localhost:4000/chaincodes?peer=peer1&type=installed" \
+ -H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0OTQ4NjU1OTEsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6Im9yZzEiLCJpYXQiOjE0OTQ4NjE5OTF9.yWaJhFDuTvMQRaZIqg20Is5t-JJ_1BP58yrNLOKxtNI" \
+ -H "content-type: application/json"
+```
+
+### Query Instantiated chaincodes
+
+```
+curl -s -X GET \
+ "http://localhost:4000/chaincodes?peer=peer1&type=instantiated" \
+ -H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0OTQ4NjU1OTEsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6Im9yZzEiLCJpYXQiOjE0OTQ4NjE5OTF9.yWaJhFDuTvMQRaZIqg20Is5t-JJ_1BP58yrNLOKxtNI" \
+ -H "content-type: application/json"
+```
+
+### Query Channels
+
+```
+curl -s -X GET \
+ "http://localhost:4000/channels?peer=peer1" \
+ -H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0OTQ4NjU1OTEsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6Im9yZzEiLCJpYXQiOjE0OTQ4NjE5OTF9.yWaJhFDuTvMQRaZIqg20Is5t-JJ_1BP58yrNLOKxtNI" \
+ -H "content-type: application/json"
+```
+
+### Network configuration considerations
+
+You have the ability to change configuration parameters by either directly editing the network-config.json file or provide an additional file for an alternative target network. The app uses an optional environment variable "TARGET_NETWORK" to control the configuration files to use. For example, if you deployed the target network on Amazon Web Services EC2, you can add a file "network-config-aws.json", and set the "TARGET_NETWORK" environment to 'aws'. The app will pick up the settings inside the "network-config-aws.json" file.
+
+#### IP Address** and PORT information
+
+If you choose to customize your docker-compose yaml file by hardcoding IP Addresses and PORT information for your peers and orderer, then you MUST also add the identical values into the network-config.json file. The paths shown below will need to be adjusted to match your docker-compose yaml file.
+
+```
+ "orderer": {
+ "url": "grpcs://x.x.x.x:7050",
+ "server-hostname": "orderer0",
+ "tls_cacerts": "../artifacts/tls/orderer/ca-cert.pem"
+ },
+ "org1": {
+ "ca": "http://x.x.x.x:7054",
+ "peer1": {
+ "requests": "grpcs://x.x.x.x:7051",
+ "events": "grpcs://x.x.x.x:7053",
+ ...
+ },
+ "peer2": {
+ "requests": "grpcs://x.x.x.x:7056",
+ "events": "grpcs://x.x.x.x:7058",
+ ...
+ }
+ },
+ "org2": {
+ "ca": "http://x.x.x.x:8054",
+ "peer1": {
+ "requests": "grpcs://x.x.x.x:8051",
+ "events": "grpcs://x.x.x.x:8053",
+ ... },
+ "peer2": {
+ "requests": "grpcs://x.x.x.x:8056",
+ "events": "grpcs://x.x.x.x:8058",
+ ...
+ }
+ }
+
+```
+
+#### Discover IP Address
+
+To retrieve the IP Address for one of your network entities, issue the following command:
+
+```
+# The following will return the IP Address for peer0
+docker inspect peer0 | grep IPAddress
+```
+
+
This work is licensed under a Creative Commons Attribution 4.0 International License.
diff --git a/balance-transfer/typescript/api/chaincode.ts b/balance-transfer/typescript/api/chaincode.ts
new file mode 100644
index 0000000000..4c5fda6bd1
--- /dev/null
+++ b/balance-transfer/typescript/api/chaincode.ts
@@ -0,0 +1,89 @@
+/**
+ * Copyright 2017 Kapil Sachdeva All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import * as express from 'express';
+import log4js = require('log4js');
+const logger = log4js.getLogger('SampleWebApp');
+import hfc = require('fabric-client');
+import * as jwt from 'jsonwebtoken';
+import * as helper from '../lib/helper';
+import * as channelApi from '../lib/channel';
+import * as chainCodeApi from '../lib/chaincode';
+import { RequestEx } from '../interfaces';
+import { getErrorMessage } from './utils';
+
+export default function chainCodeHandlers(app: express.Application) {
+
+ async function installChainCode(req: RequestEx, res: express.Response) {
+ logger.debug('==================== INSTALL CHAINCODE ==================');
+
+ const peers = req.body.peers;
+ const chaincodeName = req.body.chaincodeName;
+ const chaincodePath = req.body.chaincodePath;
+ const chaincodeVersion = req.body.chaincodeVersion;
+
+ logger.debug('peers : ' + peers); // target peers list
+ logger.debug('chaincodeName : ' + chaincodeName);
+ logger.debug('chaincodePath : ' + chaincodePath);
+ logger.debug('chaincodeVersion : ' + chaincodeVersion);
+
+ if (!peers || peers.length === 0) {
+ res.json(getErrorMessage('\'peers\''));
+ return;
+ }
+ if (!chaincodeName) {
+ res.json(getErrorMessage('\'chaincodeName\''));
+ return;
+ }
+ if (!chaincodePath) {
+ res.json(getErrorMessage('\'chaincodePath\''));
+ return;
+ }
+ if (!chaincodeVersion) {
+ res.json(getErrorMessage('\'chaincodeVersion\''));
+ return;
+ }
+
+ const message = await chainCodeApi.installChaincode(
+ peers, chaincodeName, chaincodePath, chaincodeVersion, req.username, req.orgname);
+
+ res.send(message);
+ }
+
+ async function queryChainCode(req: RequestEx, res: express.Response) {
+ const peer = req.query.peer;
+ const installType = req.query.type;
+ // TODO: add Constnats
+ if (installType === 'installed') {
+ logger.debug(
+ '================ GET INSTALLED CHAINCODES ======================');
+ } else {
+ logger.debug(
+ '================ GET INSTANTIATED CHAINCODES ======================');
+ }
+
+ const message = await chainCodeApi.getInstalledChaincodes(
+ peer, installType, req.username, req.orgname);
+
+ res.send(message);
+ }
+
+ const API_ENDPOINT_CHAINCODE_INSTALL = '/chaincodes';
+ const API_ENDPOINT_CHAINCODE_QUERY = '/chaincodes';
+
+ app.post(API_ENDPOINT_CHAINCODE_INSTALL, installChainCode);
+ app.get(API_ENDPOINT_CHAINCODE_QUERY, queryChainCode);
+}
diff --git a/balance-transfer/typescript/api/channel.ts b/balance-transfer/typescript/api/channel.ts
new file mode 100644
index 0000000000..49a79c7212
--- /dev/null
+++ b/balance-transfer/typescript/api/channel.ts
@@ -0,0 +1,261 @@
+/**
+ * Copyright 2017 Kapil Sachdeva All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import * as express from 'express';
+import log4js = require('log4js');
+const logger = log4js.getLogger('SampleWebApp');
+import hfc = require('fabric-client');
+import * as jwt from 'jsonwebtoken';
+import * as helper from '../lib/helper';
+import * as channelApi from '../lib/channel';
+import { RequestEx } from '../interfaces';
+import { getErrorMessage } from './utils';
+
+export default function channelHandlers(app: express.Application) {
+
+ async function createNewChannel(req: RequestEx, res: express.Response) {
+ logger.info('<<<<<<<<<<<<<<<<< C R E A T E C H A N N E L >>>>>>>>>>>>>>>>>');
+ logger.debug('End point : /channels');
+
+ const channelName = req.body.channelName;
+ const channelConfigPath = req.body.channelConfigPath;
+
+ logger.debug('Channel name : ' + channelName);
+ // ../artifacts/channel/mychannel.tx
+ logger.debug('channelConfigPath : ' + channelConfigPath);
+
+ if (!channelName) {
+ res.json(getErrorMessage('\'channelName\''));
+ return;
+ }
+ if (!channelConfigPath) {
+ res.json(getErrorMessage('\'channelConfigPath\''));
+ return;
+ }
+
+ const response = await channelApi.createChannel(
+ channelName, channelConfigPath, req.username, req.orgname);
+
+ res.send(response);
+ }
+
+ async function joinChannel(req: RequestEx, res: express.Response) {
+ logger.info('<<<<<<<<<<<<<<<<< J O I N C H A N N E L >>>>>>>>>>>>>>>>>');
+
+ const channelName = req.params.channelName;
+ const peers = req.body.peers;
+ logger.debug('channelName : ' + channelName);
+ logger.debug('peers : ' + peers);
+ if (!channelName) {
+ res.json(getErrorMessage('\'channelName\''));
+ return;
+ }
+ if (!peers || peers.length === 0) {
+ res.json(getErrorMessage('\'peers\''));
+ return;
+ }
+
+ const message = await channelApi.joinChannel(channelName, peers, req.username, req.orgname);
+ res.send(message);
+ }
+
+ async function instantiateChainCode(req: RequestEx, res: express.Response) {
+ logger.debug('==================== INSTANTIATE CHAINCODE ==================');
+ const chaincodeName = req.body.chaincodeName;
+ const chaincodeVersion = req.body.chaincodeVersion;
+ const channelName = req.params.channelName;
+ const fcn = req.body.fcn;
+ const args = req.body.args;
+ logger.debug('channelName : ' + channelName);
+ logger.debug('chaincodeName : ' + chaincodeName);
+ logger.debug('chaincodeVersion : ' + chaincodeVersion);
+ logger.debug('fcn : ' + fcn);
+ logger.debug('args : ' + args);
+ if (!chaincodeName) {
+ res.json(getErrorMessage('\'chaincodeName\''));
+ return;
+ }
+ if (!chaincodeVersion) {
+ res.json(getErrorMessage('\'chaincodeVersion\''));
+ return;
+ }
+ if (!channelName) {
+ res.json(getErrorMessage('\'channelName\''));
+ return;
+ }
+ if (!args) {
+ res.json(getErrorMessage('\'args\''));
+ return;
+ }
+
+ const message = await channelApi.instantiateChainCode(
+ channelName, chaincodeName, chaincodeVersion, fcn, args, req.username, req.orgname);
+ res.send(message);
+ }
+
+ async function invokeChainCode(req: RequestEx, res: express.Response) {
+ logger.debug('==================== INVOKE ON CHAINCODE ==================');
+ const peers = req.body.peers;
+ const chaincodeName = req.params.chaincodeName;
+ const channelName = req.params.channelName;
+ const fcn = req.body.fcn;
+ const args = req.body.args;
+ logger.debug('channelName : ' + channelName);
+ logger.debug('chaincodeName : ' + chaincodeName);
+ logger.debug('fcn : ' + fcn);
+ logger.debug('args : ' + args);
+ if (!chaincodeName) {
+ res.json(getErrorMessage('\'chaincodeName\''));
+ return;
+ }
+ if (!channelName) {
+ res.json(getErrorMessage('\'channelName\''));
+ return;
+ }
+ if (!fcn) {
+ res.json(getErrorMessage('\'fcn\''));
+ return;
+ }
+ if (!args) {
+ res.json(getErrorMessage('\'args\''));
+ return;
+ }
+
+ const message = await channelApi.invokeChaincode(
+ peers, channelName, chaincodeName, fcn, args, req.username, req.orgname);
+
+ res.send(message);
+ }
+
+ async function queryChainCode(req: RequestEx, res: express.Response) {
+ const channelName = req.params.channelName;
+ const chaincodeName = req.params.chaincodeName;
+ let args = req.query.args;
+ const fcn = req.query.fcn;
+ const peer = req.query.peer;
+
+ logger.debug('channelName : ' + channelName);
+ logger.debug('chaincodeName : ' + chaincodeName);
+ logger.debug('fcn : ' + fcn);
+ logger.debug('args : ' + args);
+
+ if (!chaincodeName) {
+ res.json(getErrorMessage('\'chaincodeName\''));
+ return;
+ }
+ if (!channelName) {
+ res.json(getErrorMessage('\'channelName\''));
+ return;
+ }
+ if (!fcn) {
+ res.json(getErrorMessage('\'fcn\''));
+ return;
+ }
+ if (!args) {
+ res.json(getErrorMessage('\'args\''));
+ return;
+ }
+
+ args = args.replace(/'/g, '"');
+ args = JSON.parse(args);
+ logger.debug(args);
+
+ const message = await channelApi.queryChaincode(
+ peer, channelName, chaincodeName, args, fcn, req.username, req.orgname);
+
+ res.send(message);
+ }
+
+ async function queryByBlockNumber(req: RequestEx, res: express.Response) {
+ logger.debug('==================== GET BLOCK BY NUMBER ==================');
+ const blockId = req.params.blockId;
+ const peer = req.query.peer;
+ logger.debug('channelName : ' + req.params.channelName);
+ logger.debug('BlockID : ' + blockId);
+ logger.debug('Peer : ' + peer);
+ if (!blockId) {
+ res.json(getErrorMessage('\'blockId\''));
+ return;
+ }
+
+ const message = await channelApi.getBlockByNumber(peer, blockId, req.username, req.orgname);
+ res.send(message);
+ }
+
+ async function queryByTransactionId(req: RequestEx, res: express.Response) {
+ logger.debug(
+ '================ GET TRANSACTION BY TRANSACTION_ID ======================'
+ );
+ logger.debug('channelName : ' + req.params.channelName);
+ const trxnId = req.params.trxnId;
+ const peer = req.query.peer;
+ if (!trxnId) {
+ res.json(getErrorMessage('\'trxnId\''));
+ return;
+ }
+
+ const message = await channelApi.getTransactionByID(
+ peer, trxnId, req.username, req.orgname);
+
+ res.send(message);
+ }
+
+ async function queryChannelInfo(req: RequestEx, res: express.Response) {
+ logger.debug(
+ '================ GET CHANNEL INFORMATION ======================');
+ logger.debug('channelName : ' + req.params.channelName);
+ const peer = req.query.peer;
+
+ const message = await channelApi.getChainInfo(peer, req.username, req.orgname);
+
+ res.send(message);
+ }
+
+ async function queryChannels(req: RequestEx, res: express.Response) {
+ logger.debug('================ GET CHANNELS ======================');
+ logger.debug('peer: ' + req.query.peer);
+ const peer = req.query.peer;
+ if (!peer) {
+ res.json(getErrorMessage('\'peer\''));
+ return;
+ }
+
+ const message = await channelApi.getChannels(peer, req.username, req.orgname);
+ res.send(message);
+ }
+
+ const API_ENDPOINT_CHANNEL_CREATE = '/channels';
+ const API_ENDPOINT_CHANNEL_JOIN = '/channels/:channelName/peers';
+ const API_ENDPOINT_CHANNEL_INSTANTIATE_CHAINCODE = '/channels/:channelName/chaincodes';
+ const API_ENDPOINT_CHANNEL_INVOKE_CHAINCODE =
+ '/channels/:channelName/chaincodes/:chaincodeName';
+ const API_ENDPOINT_CHANNEL_QUERY_CHAINCODE = '/channels/:channelName/chaincodes/:chaincodeName';
+ const API_ENDPOINT_CHANNEL_QUERY_BY_BLOCKNUMBER = '/channels/:channelName/blocks/:blockId';
+ const API_ENDPOINT_CHANNEL_QUERY_BY_TRANSACTIONID
+ = '/channels/:channelName/transactions/:trxnId';
+ const API_ENDPOINT_CHANNEL_INFO = '/channels/:channelName';
+ const API_ENDPOINT_CHANNEL_QUERY = '/channels';
+
+ app.post(API_ENDPOINT_CHANNEL_CREATE, createNewChannel);
+ app.post(API_ENDPOINT_CHANNEL_JOIN, joinChannel);
+ app.post(API_ENDPOINT_CHANNEL_INSTANTIATE_CHAINCODE, instantiateChainCode);
+ app.post(API_ENDPOINT_CHANNEL_INVOKE_CHAINCODE, invokeChainCode);
+ app.get(API_ENDPOINT_CHANNEL_QUERY_CHAINCODE, queryChainCode);
+ app.get(API_ENDPOINT_CHANNEL_QUERY_BY_BLOCKNUMBER, queryByBlockNumber);
+ app.get(API_ENDPOINT_CHANNEL_QUERY_BY_TRANSACTIONID, queryByTransactionId);
+ app.get(API_ENDPOINT_CHANNEL_INFO, queryChannelInfo);
+ app.get(API_ENDPOINT_CHANNEL_QUERY, queryChannels);
+}
diff --git a/balance-transfer/typescript/api/index.ts b/balance-transfer/typescript/api/index.ts
new file mode 100644
index 0000000000..f49700e086
--- /dev/null
+++ b/balance-transfer/typescript/api/index.ts
@@ -0,0 +1,27 @@
+/**
+ * Copyright 2017 Kapil Sachdeva All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import * as express from 'express';
+import userHandlers from './users';
+import channelHandlers from './channel';
+import chainCodeHandlers from './chaincode';
+
+export default function entryPoint(app: express.Application) {
+ // various handlers
+ userHandlers(app);
+ channelHandlers(app);
+ chainCodeHandlers(app);
+}
diff --git a/balance-transfer/typescript/api/users.ts b/balance-transfer/typescript/api/users.ts
new file mode 100644
index 0000000000..d9462555dc
--- /dev/null
+++ b/balance-transfer/typescript/api/users.ts
@@ -0,0 +1,69 @@
+/**
+ * Copyright 2017 Kapil Sachdeva All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { RequestEx } from '../interfaces';
+import * as express from 'express';
+import log4js = require('log4js');
+const logger = log4js.getLogger('SampleWebApp');
+import hfc = require('fabric-client');
+import * as jwt from 'jsonwebtoken';
+import * as helper from '../lib/helper';
+import { getErrorMessage } from './utils';
+
+export default function userHandlers(app: express.Application) {
+
+ async function registerUser(req: RequestEx, res: express.Response) {
+ const username = req.body.username;
+ const orgName = req.body.orgName;
+
+ logger.debug('End point : /users');
+ logger.debug('User name : ' + username);
+ logger.debug('Org name : ' + orgName);
+
+ if (!username) {
+ res.json(getErrorMessage('\'username\''));
+ return;
+ }
+ if (!orgName) {
+ res.json(getErrorMessage('\'orgName\''));
+ return;
+ }
+ const token = jwt.sign({
+ exp: Math.floor(Date.now() / 1000) + parseInt(
+ hfc.getConfigSetting('jwt_expiretime'), 10),
+ username,
+ orgName
+ }, app.get('secret'));
+
+ const response = await helper.getRegisteredUsers(username, orgName);
+
+ if (response && typeof response !== 'string') {
+ res.json({
+ success: true,
+ token
+ });
+ } else {
+ res.json({
+ success: false,
+ message: response
+ });
+ }
+ }
+
+ const API_ENDPOINT_REGISTER_USER = '/users';
+
+ app.post(API_ENDPOINT_REGISTER_USER, registerUser);
+}
diff --git a/balance-transfer/typescript/api/utils.ts b/balance-transfer/typescript/api/utils.ts
new file mode 100644
index 0000000000..128545fe91
--- /dev/null
+++ b/balance-transfer/typescript/api/utils.ts
@@ -0,0 +1,23 @@
+/**
+ * Copyright 2017 Kapil Sachdeva All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export function getErrorMessage(field: string) {
+ const response = {
+ success: false,
+ message: field + ' field is missing or Invalid in the request'
+ };
+ return response;
+}
diff --git a/balance-transfer/typescript/app.ts b/balance-transfer/typescript/app.ts
new file mode 100644
index 0000000000..66e8680bf1
--- /dev/null
+++ b/balance-transfer/typescript/app.ts
@@ -0,0 +1,92 @@
+/**
+ * Copyright 2017 Kapil Sachdeva All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import log4js = require('log4js');
+import * as util from 'util';
+import * as http from 'http';
+import * as express from 'express';
+import * as jwt from 'jsonwebtoken';
+import * as bodyParser from 'body-parser';
+import expressJWT = require('express-jwt');
+// tslint:disable-next-line:no-var-requires
+const bearerToken = require('express-bearer-token');
+import cors = require('cors');
+import hfc = require('fabric-client');
+import * as helper from './lib/helper';
+import { RequestEx } from './interfaces';
+import api from './api';
+
+helper.init();
+
+const SERVER_HOST = process.env.HOST || hfc.getConfigSetting('host');
+const SERVER_PORT = process.env.PORT || hfc.getConfigSetting('port');
+
+const logger = log4js.getLogger('SampleWebApp');
+
+// create express App
+const app = express();
+
+app.options('*', cors());
+app.use(cors());
+app.use(bodyParser.json());
+app.use(bodyParser.urlencoded({
+ extended: false
+}));
+app.set('secret', 'thisismysecret');
+app.use(expressJWT({
+ secret: 'thisismysecret'
+}).unless({
+ path: ['/users']
+}));
+app.use(bearerToken());
+
+app.use((req: RequestEx, res, next) => {
+ if (req.originalUrl.indexOf('/users') >= 0) {
+ return next();
+ }
+
+ const token = req.token;
+ jwt.verify(token, app.get('secret'), (err: Error, decoded: any) => {
+ if (err) {
+ res.send({
+ success: false,
+ message: 'Failed to authenticate token. Make sure to include the ' +
+ 'token returned from /users call in the authorization header ' +
+ ' as a Bearer token'
+ });
+ return;
+ } else {
+ // add the decoded user name and org name to the request object
+ // for the downstream code to use
+ req.username = decoded.username;
+ req.orgname = decoded.orgName;
+ logger.debug(
+ util.format('Decoded from JWT token: username - %s, orgname - %s',
+ decoded.username, decoded.orgName));
+ return next();
+ }
+ });
+});
+
+// configure various routes
+api(app);
+
+const server = http.createServer(app);
+server.listen(SERVER_PORT);
+
+logger.info('****************** SERVER STARTED ************************');
+logger.info('************** http://' + SERVER_HOST + ':' + SERVER_PORT + ' ******************');
+server.timeout = 240000;
diff --git a/balance-transfer/typescript/app_config.json b/balance-transfer/typescript/app_config.json
new file mode 100644
index 0000000000..6406d66f22
--- /dev/null
+++ b/balance-transfer/typescript/app_config.json
@@ -0,0 +1,13 @@
+{
+ "host": "localhost",
+ "port": "4000",
+ "jwt_expiretime": "36000",
+ "channelName": "mychannel",
+ "CC_SRC_PATH": "../artifacts",
+ "keyValueStore": "/tmp/fabric-client-kvs",
+ "eventWaitTime": "30000",
+ "admins": [{
+ "username": "admin",
+ "secret": "adminpw"
+ }]
+}
diff --git a/balance-transfer/typescript/artifacts b/balance-transfer/typescript/artifacts
new file mode 120000
index 0000000000..70f9aabc31
--- /dev/null
+++ b/balance-transfer/typescript/artifacts
@@ -0,0 +1 @@
+../artifacts
\ No newline at end of file
diff --git a/balance-transfer/typescript/config.ts b/balance-transfer/typescript/config.ts
new file mode 100644
index 0000000000..1277eee48e
--- /dev/null
+++ b/balance-transfer/typescript/config.ts
@@ -0,0 +1,30 @@
+/**
+ * Copyright 2017 Kapil Sachdeva All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import * as util from 'util';
+
+let file = 'network-config%s.json';
+
+const env = process.env.TARGET_NETWORK;
+if (env) {
+ file = util.format(file, '-' + env);
+} else {
+ file = util.format(file, '');
+}
+
+export default {
+ networkConfigFile: file
+};
diff --git a/balance-transfer/typescript/interfaces.ts b/balance-transfer/typescript/interfaces.ts
new file mode 100644
index 0000000000..6acd2b1b6b
--- /dev/null
+++ b/balance-transfer/typescript/interfaces.ts
@@ -0,0 +1,23 @@
+/**
+ * Copyright 2017 Kapil Sachdeva All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import * as express from 'express';
+
+export interface RequestEx extends express.Request {
+ username?: any;
+ orgname?: any;
+ token?: any;
+}
diff --git a/balance-transfer/typescript/lib/chaincode.ts b/balance-transfer/typescript/lib/chaincode.ts
new file mode 100644
index 0000000000..70915abd07
--- /dev/null
+++ b/balance-transfer/typescript/lib/chaincode.ts
@@ -0,0 +1,148 @@
+/**
+ * Copyright 2017 Kapil Sachdeva All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import * as util from 'util';
+import * as fs from 'fs';
+import * as path from 'path';
+import * as helper from './helper';
+
+// tslint:disable-next-line:no-var-requires
+const config = require('../app_config.json');
+const logger = helper.getLogger('ChaincodeApi');
+
+function buildTarget(peer: string, org: string): Peer {
+ let target: Peer = null;
+ if (typeof peer !== 'undefined') {
+ const targets: Peer[] = helper.newPeers([peer], org);
+ if (targets && targets.length > 0) {
+ target = targets[0];
+ }
+ }
+
+ return target;
+}
+
+export async function installChaincode(
+ peers: string[], chaincodeName: string, chaincodePath: string,
+ chaincodeVersion: string, username: string, org: string) {
+
+ logger.debug(
+ '\n============ Install chaincode on organizations ============\n');
+
+ helper.setupChaincodeDeploy();
+
+ const channel = helper.getChannelForOrg(org);
+ const client = helper.getClientForOrg(org);
+
+ const admin = await helper.getOrgAdmin(org);
+
+ const request = {
+ targets: helper.newPeers(peers, org),
+ chaincodePath,
+ chaincodeId: chaincodeName,
+ chaincodeVersion
+ };
+
+ try {
+
+ const results = await client.installChaincode(request);
+
+ const proposalResponses = results[0];
+ const proposal = results[1];
+ let allGood = true;
+
+ proposalResponses.forEach((pr) => {
+ let oneGood = false;
+ if (pr.response && pr.response.status === 200) {
+ oneGood = true;
+ logger.info('install proposal was good');
+ } else {
+ logger.error('install proposal was bad');
+ }
+ allGood = allGood && oneGood;
+ });
+
+ if (allGood) {
+ logger.info(util.format(
+ 'Successfully sent install Proposal and received ProposalResponse: Status - %s',
+ proposalResponses[0].response.status));
+ logger.debug('\nSuccessfully Installed chaincode on organization ' + org +
+ '\n');
+ return 'Successfully Installed chaincode on organization ' + org;
+ } else {
+ logger.error(
+ // tslint:disable-next-line:max-line-length
+ 'Failed to send install Proposal or receive valid response. Response null or status is not 200. exiting...'
+ );
+ // tslint:disable-next-line:max-line-length
+ return 'Failed to send install Proposal or receive valid response. Response null or status is not 200. exiting...';
+ }
+
+ } catch (err) {
+ logger.error('Failed to send install proposal due to error: ' + err.stack ?
+ err.stack : err);
+ throw new Error('Failed to send install proposal due to error: ' + err.stack ?
+ err.stack : err);
+ }
+}
+
+export async function getInstalledChaincodes(
+ peer: string, type: string, username: string, org: string) {
+
+ const target = buildTarget(peer, org);
+ const channel = helper.getChannelForOrg(org);
+ const client = helper.getClientForOrg(org);
+
+ const user = await helper.getOrgAdmin(org);
+
+ try {
+
+ let response: ChaincodeQueryResponse = null;
+
+ if (type === 'installed') {
+ response = await client.queryInstalledChaincodes(target);
+ } else {
+ response = await channel.queryInstantiatedChaincodes(target);
+ }
+
+ if (response) {
+ if (type === 'installed') {
+ logger.debug('<<< Installed Chaincodes >>>');
+ } else {
+ logger.debug('<<< Instantiated Chaincodes >>>');
+ }
+
+ const details: string[] = [];
+ response.chaincodes.forEach((c) => {
+ logger.debug('name: ' + c.name + ', version: ' +
+ c.version + ', path: ' + c.path
+ );
+ details.push('name: ' + c.name + ', version: ' +
+ c.version + ', path: ' + c.path
+ );
+ });
+
+ return details;
+ } else {
+ logger.error('response is null');
+ return 'response is null';
+ }
+
+ } catch (err) {
+ logger.error('Failed to query with error:' + err.stack ? err.stack : err);
+ return 'Failed to query with error:' + err.stack ? err.stack : err;
+ }
+}
\ No newline at end of file
diff --git a/balance-transfer/typescript/lib/channel.ts b/balance-transfer/typescript/lib/channel.ts
new file mode 100644
index 0000000000..2cc474aa5f
--- /dev/null
+++ b/balance-transfer/typescript/lib/channel.ts
@@ -0,0 +1,599 @@
+/**
+ * Copyright 2017 Kapil Sachdeva All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import * as util from 'util';
+import * as fs from 'fs';
+import * as path from 'path';
+import * as helper from './helper';
+const logger = helper.getLogger('ChannelApi');
+// tslint:disable-next-line:no-var-requires
+const config = require('../app_config.json');
+
+const allEventhubs: EventHub[] = [];
+
+function buildTarget(peer: string, org: string): Peer {
+ let target: Peer = null;
+ if (typeof peer !== 'undefined') {
+ const targets: Peer[] = helper.newPeers([peer], org);
+ if (targets && targets.length > 0) {
+ target = targets[0];
+ }
+ }
+
+ return target;
+}
+
+// Attempt to send a request to the orderer with the sendCreateChain method
+export async function createChannel(
+ channelName: string, channelConfigPath: string, username: string, orgName: string) {
+
+ logger.debug('\n====== Creating Channel \'' + channelName + '\' ======\n');
+
+ const client = helper.getClientForOrg(orgName);
+ const channel = helper.getChannelForOrg(orgName);
+
+ // read in the envelope for the channel config raw bytes
+ const envelope = fs.readFileSync(path.join(__dirname, channelConfigPath));
+ // extract the channel config bytes from the envelope to be signed
+ const channelConfig = client.extractChannelConfig(envelope);
+
+ // Acting as a client in the given organization provided with "orgName" param
+ const admin = await helper.getOrgAdmin(orgName);
+
+ logger.debug(util.format('Successfully acquired admin user for the organization "%s"',
+ orgName));
+
+ // sign the channel config bytes as "endorsement", this is required by
+ // the orderer's channel creation policy
+ const signature = client.signChannelConfig(channelConfig);
+
+ const request = {
+ config: channelConfig,
+ signatures: [signature],
+ name: channelName,
+ orderer: channel.getOrderers()[0],
+ txId: client.newTransactionID()
+ };
+
+ try {
+ const response = await client.createChannel(request);
+
+ if (response && response.status === 'SUCCESS') {
+ logger.debug('Successfully created the channel.');
+ return {
+ success: true,
+ message: 'Channel \'' + channelName + '\' created Successfully'
+ };
+ } else {
+ logger.error('\n!!!!!!!!! Failed to create the channel \'' + channelName +
+ '\' !!!!!!!!!\n\n');
+ throw new Error('Failed to create the channel \'' + channelName + '\'');
+ }
+
+ } catch (err) {
+ logger.error('\n!!!!!!!!! Failed to create the channel \'' + channelName +
+ '\' !!!!!!!!!\n\n');
+ throw new Error('Failed to create the channel \'' + channelName + '\'');
+ }
+}
+
+export async function joinChannel(
+ channelName: string, peers: string[], username: string, org: string) {
+
+ // on process exit, always disconnect the event hub
+ const closeConnections = (isSuccess: boolean) => {
+ if (isSuccess) {
+ logger.debug('\n============ Join Channel is SUCCESS ============\n');
+ } else {
+ logger.debug('\n!!!!!!!! ERROR: Join Channel FAILED !!!!!!!!\n');
+ }
+ logger.debug('');
+
+ allEventhubs.forEach((hub) => {
+ console.log(hub);
+ if (hub && hub.isconnected()) {
+ hub.disconnect();
+ }
+ });
+ };
+
+ // logger.debug('\n============ Join Channel ============\n')
+ logger.info(util.format(
+ 'Calling peers in organization "%s" to join the channel', org));
+
+ const client = helper.getClientForOrg(org);
+ const channel = helper.getChannelForOrg(org);
+
+ const admin = await helper.getOrgAdmin(org);
+
+ logger.info(util.format('received member object for admin of the organization "%s": ', org));
+ const request = {
+ txId: client.newTransactionID()
+ };
+
+ const genesisBlock = await channel.getGenesisBlock(request);
+
+ const request2 = {
+ targets: helper.newPeers(peers, org),
+ txId: client.newTransactionID(),
+ block: genesisBlock
+ };
+
+ const eventhubs = helper.newEventHubs(peers, org);
+ eventhubs.forEach((eh) => {
+ eh.connect();
+ allEventhubs.push(eh);
+ });
+
+ const eventPromises: Array> = [];
+ eventhubs.forEach((eh) => {
+ const txPromise = new Promise((resolve, reject) => {
+ const handle = setTimeout(reject, parseInt(config.eventWaitTime, 10));
+ eh.registerBlockEvent((block: any) => {
+ clearTimeout(handle);
+ // in real-world situations, a peer may have more than one channels so
+ // we must check that this block came from the channel we asked the peer to join
+ if (block.data.data.length === 1) {
+ // Config block must only contain one transaction
+ const channel_header = block.data.data[0].payload.header.channel_header;
+ if (channel_header.channel_id === channelName) {
+ resolve();
+ } else {
+ reject();
+ }
+ }
+ });
+ });
+ eventPromises.push(txPromise);
+ });
+
+ const sendPromise = channel.joinChannel(request2);
+ const results = await Promise.all([sendPromise].concat(eventPromises));
+
+ logger.debug(util.format('Join Channel R E S P O N S E : %j', results));
+ if (results[0] && results[0][0] && results[0][0].response && results[0][0]
+ .response.status === 200) {
+ logger.info(util.format(
+ 'Successfully joined peers in organization %s to the channel \'%s\'',
+ org, channelName));
+ closeConnections(true);
+ const response = {
+ success: true,
+ message: util.format(
+ 'Successfully joined peers in organization %s to the channel \'%s\'',
+ org, channelName)
+ };
+ return response;
+ } else {
+ logger.error(' Failed to join channel');
+ closeConnections(false);
+ throw new Error('Failed to join channel');
+ }
+}
+
+export async function instantiateChainCode(
+ channelName: string, chaincodeName: string, chaincodeVersion: string,
+ functionName: string, args: string[], username: string, org: string) {
+
+ logger.debug('\n============ Instantiate chaincode on organization ' + org +
+ ' ============\n');
+
+ const channel = helper.getChannelForOrg(org);
+ const client = helper.getClientForOrg(org);
+
+ const admin = await helper.getOrgAdmin(org);
+ await channel.initialize();
+
+ const txId = client.newTransactionID();
+ // send proposal to endorser
+ const request = {
+ chaincodeId: chaincodeName,
+ chaincodeVersion,
+ args,
+ txId,
+ fcn: functionName
+ };
+
+ try {
+
+ const results = await channel.sendInstantiateProposal(request);
+
+ const proposalResponses = results[0];
+ const proposal = results[1];
+
+ let allGood = true;
+
+ proposalResponses.forEach((pr) => {
+ let oneGood = false;
+ if (pr.response && pr.response.status === 200) {
+ oneGood = true;
+ logger.info('install proposal was good');
+ } else {
+ logger.error('install proposal was bad');
+ }
+ allGood = allGood && oneGood;
+ });
+
+ if (allGood) {
+ logger.info(util.format(
+ // tslint:disable-next-line:max-line-length
+ 'Successfully sent Proposal and received ProposalResponse: Status - %s, message - "%s", metadata - "%s", endorsement signature: %s',
+ proposalResponses[0].response.status, proposalResponses[0].response.message,
+ proposalResponses[0].response.payload, proposalResponses[0].endorsement
+ .signature));
+
+ const request2 = {
+ proposalResponses,
+ proposal
+ };
+ // set the transaction listener and set a timeout of 30sec
+ // if the transaction did not get committed within the timeout period,
+ // fail the test
+ const deployId = txId.getTransactionID();
+ const ORGS = helper.getOrgs();
+
+ const eh = client.newEventHub();
+ const data = fs.readFileSync(path.join(__dirname, ORGS[org].peers['peer1'][
+ 'tls_cacerts'
+ ]));
+
+ eh.setPeerAddr(ORGS[org].peers['peer1']['events'], {
+ 'pem': Buffer.from(data).toString(),
+ 'ssl-target-name-override': ORGS[org].peers['peer1']['server-hostname']
+ });
+ eh.connect();
+
+ const txPromise: Promise = new Promise((resolve, reject) => {
+ const handle = setTimeout(() => {
+ eh.disconnect();
+ reject();
+ }, 30000);
+
+ eh.registerTxEvent(deployId, (tx, code) => {
+ // logger.info(
+ // 'The chaincode instantiate transaction has been committed on peer ' +
+ // eh._ep._endpoint.addr);
+
+ clearTimeout(handle);
+ eh.unregisterTxEvent(deployId);
+ eh.disconnect();
+
+ if (code !== 'VALID') {
+ logger.error(
+ 'The chaincode instantiate transaction was invalid, code = ' + code);
+ reject();
+ } else {
+ logger.info('The chaincode instantiate transaction was valid.');
+ resolve();
+ }
+ });
+ });
+
+ const sendPromise = channel.sendTransaction(request2);
+ const transactionResults = await Promise.all([sendPromise].concat([txPromise]));
+
+ const response = transactionResults[0];
+ if (response.status === 'SUCCESS') {
+ logger.info('Successfully sent transaction to the orderer.');
+ return 'Chaincode Instantiation is SUCCESS';
+ } else {
+ logger.error('Failed to order the transaction. Error code: ' + response.status);
+ return 'Failed to order the transaction. Error code: ' + response.status;
+ }
+
+ } else {
+ logger.error(
+ // tslint:disable-next-line:max-line-length
+ 'Failed to send instantiate Proposal or receive valid response. Response null or status is not 200. exiting...'
+ );
+ // tslint:disable-next-line:max-line-length
+ return 'Failed to send instantiate Proposal or receive valid response. Response null or status is not 200. exiting...';
+ }
+
+ } catch (err) {
+ logger.error('Failed to send instantiate due to error: ' + err.stack ? err
+ .stack : err);
+ return 'Failed to send instantiate due to error: ' + err.stack ? err.stack :
+ err;
+ }
+}
+
+export async function invokeChaincode(
+ peerNames: string[], channelName: string,
+ chaincodeName: string, fcn: string, args: string[], username: string, org: string) {
+
+ logger.debug(
+ util.format('\n============ invoke transaction on organization %s ============\n', org));
+
+ const client = helper.getClientForOrg(org);
+ const channel = helper.getChannelForOrg(org);
+ const targets = (peerNames) ? helper.newPeers(peerNames, org) : undefined;
+
+ const user = await helper.getRegisteredUsers(username, org);
+
+ const txId = client.newTransactionID();
+ logger.debug(util.format('Sending transaction "%j"', txId));
+ // send proposal to endorser
+ const request: ChaincodeInvokeRequest = {
+ chaincodeId: chaincodeName,
+ fcn,
+ args,
+ txId
+ };
+
+ if (targets) {
+ request.targets = targets;
+ }
+
+ try {
+
+ const results = await channel.sendTransactionProposal(request);
+
+ const proposalResponses = results[0];
+ const proposal = results[1];
+ let allGood = true;
+
+ proposalResponses.forEach((pr) => {
+ let oneGood = false;
+ if (pr.response && pr.response.status === 200) {
+ oneGood = true;
+ logger.info('transaction proposal was good');
+ } else {
+ logger.error('transaction proposal was bad');
+ }
+ allGood = allGood && oneGood;
+ });
+
+ if (allGood) {
+ logger.debug(util.format(
+ // tslint:disable-next-line:max-line-length
+ 'Successfully sent Proposal and received ProposalResponse: Status - %s, message - "%s", metadata - "%s", endorsement signature: %s',
+ proposalResponses[0].response.status, proposalResponses[0].response.message,
+ proposalResponses[0].response.payload, proposalResponses[0].endorsement
+ .signature));
+
+ const request2 = {
+ proposalResponses,
+ proposal
+ };
+
+ // set the transaction listener and set a timeout of 30sec
+ // if the transaction did not get committed within the timeout period,
+ // fail the test
+ const transactionID = txId.getTransactionID();
+ const eventPromises: Array> = [];
+
+ if (!peerNames) {
+ peerNames = channel.getPeers().map((peer) => {
+ return peer.getName();
+ });
+ }
+
+ const eventhubs = helper.newEventHubs(peerNames, org);
+
+ eventhubs.forEach((eh: EventHub) => {
+ eh.connect();
+
+ const txPromise = new Promise((resolve, reject) => {
+ const handle = setTimeout(() => {
+ eh.disconnect();
+ reject();
+ }, 30000);
+
+ eh.registerTxEvent(transactionID, (tx: string, code: string) => {
+ clearTimeout(handle);
+ eh.unregisterTxEvent(transactionID);
+ eh.disconnect();
+
+ if (code !== 'VALID') {
+ logger.error(
+ 'The balance transfer transaction was invalid, code = ' + code);
+ reject();
+ } else {
+ // logger.info(
+ // 'The balance transfer transaction has been committed on peer ' +
+ // eh._ep._endpoint.addr);
+ resolve();
+ }
+ });
+ });
+ eventPromises.push(txPromise);
+ });
+
+ const sendPromise = channel.sendTransaction(request2);
+ const results2 = await Promise.all([sendPromise].concat(eventPromises));
+
+ logger.debug(' event promise all complete and testing complete');
+
+ if (results2[0].status === 'SUCCESS') {
+ logger.info('Successfully sent transaction to the orderer.');
+ return txId.getTransactionID();
+ } else {
+ logger.error('Failed to order the transaction. Error code: ' + results2[0].status);
+ return 'Failed to order the transaction. Error code: ' + results2[0].status;
+ }
+ } else {
+ logger.error(
+ // tslint:disable-next-line:max-line-length
+ 'Failed to send Proposal or receive valid response. Response null or status is not 200. exiting...'
+ );
+ // tslint:disable-next-line:max-line-length
+ return 'Failed to send Proposal or receive valid response. Response null or status is not 200. exiting...';
+ }
+
+ } catch (err) {
+ logger.error('Failed to send transaction due to error: ' + err.stack ? err
+ .stack : err);
+ return 'Failed to send transaction due to error: ' + err.stack ? err.stack :
+ err;
+ }
+}
+
+export async function queryChaincode(
+ peer: string, channelName: string, chaincodeName: string,
+ args: string[], fcn: string, username: string, org: string) {
+
+ const channel = helper.getChannelForOrg(org);
+ const client = helper.getClientForOrg(org);
+ const target = buildTarget(peer, org);
+
+ const user = await helper.getRegisteredUsers(username, org);
+
+ const txId = client.newTransactionID();
+ // send query
+ const request: ChaincodeQueryRequest = {
+ chaincodeId: chaincodeName,
+ txId,
+ fcn,
+ args
+ };
+
+ if (target) {
+ request.targets = [target];
+ }
+
+ try {
+ const responsePayloads = await channel.queryByChaincode(request);
+
+ if (responsePayloads) {
+
+ responsePayloads.forEach((rp) => {
+ logger.info(args[0] + ' now has ' + rp.toString('utf8') +
+ ' after the move');
+ return args[0] + ' now has ' + rp.toString('utf8') +
+ ' after the move';
+ });
+
+ } else {
+ logger.error('response_payloads is null');
+ return 'response_payloads is null';
+ }
+ } catch (err) {
+ logger.error('Failed to send query due to error: ' + err.stack ? err.stack :
+ err);
+ return 'Failed to send query due to error: ' + err.stack ? err.stack : err;
+ }
+}
+
+export async function getBlockByNumber(
+ peer: string, blockNumber: string, username: string, org: string) {
+
+ const target = buildTarget(peer, org);
+ const channel = helper.getChannelForOrg(org);
+
+ const user = await helper.getRegisteredUsers(username, org);
+
+ try {
+
+ const responsePayloads = await channel.queryBlock(parseInt(blockNumber, 10), target);
+
+ if (responsePayloads) {
+ logger.debug(responsePayloads);
+ return responsePayloads; // response_payloads.data.data[0].buffer;
+ } else {
+ logger.error('response_payloads is null');
+ return 'response_payloads is null';
+ }
+
+ } catch (err) {
+ logger.error('Failed to query with error:' + err.stack ? err.stack : err);
+ return 'Failed to query with error:' + err.stack ? err.stack : err;
+ }
+}
+
+export async function getTransactionByID(
+ peer: string, trxnID: string, username: string, org: string) {
+
+ const target = buildTarget(peer, org);
+ const channel = helper.getChannelForOrg(org);
+
+ const user = await helper.getRegisteredUsers(username, org);
+
+ try {
+
+ const responsePayloads = await channel.queryTransaction(trxnID, target);
+
+ if (responsePayloads) {
+ logger.debug(responsePayloads);
+ return responsePayloads;
+ } else {
+ logger.error('response_payloads is null');
+ return 'response_payloads is null';
+ }
+
+ } catch (err) {
+ logger.error('Failed to query with error:' + err.stack ? err.stack : err);
+ return 'Failed to query with error:' + err.stack ? err.stack : err;
+ }
+}
+
+export async function getChainInfo(peer: string, username: string, org: string) {
+
+ const target = buildTarget(peer, org);
+ const channel = helper.getChannelForOrg(org);
+
+ const user = await helper.getRegisteredUsers(username, org);
+
+ try {
+
+ const blockChainInfo = await channel.queryInfo(target);
+
+ if (blockChainInfo) {
+ // FIXME: Save this for testing 'getBlockByHash' ?
+ logger.debug('===========================================');
+ logger.debug(blockChainInfo.currentBlockHash);
+ logger.debug('===========================================');
+ // logger.debug(blockchainInfo);
+ return blockChainInfo;
+ } else {
+ logger.error('blockChainInfo is null');
+ return 'blockChainInfo is null';
+ }
+
+ } catch (err) {
+ logger.error('Failed to query with error:' + err.stack ? err.stack : err);
+ return 'Failed to query with error:' + err.stack ? err.stack : err;
+ }
+}
+
+export async function getChannels(peer: string, username: string, org: string) {
+ const target = buildTarget(peer, org);
+ const channel = helper.getChannelForOrg(org);
+ const client = helper.getClientForOrg(org);
+
+ const user = await helper.getRegisteredUsers(username, org);
+
+ try {
+
+ const response = await client.queryChannels(target);
+
+ if (response) {
+ logger.debug('<<< channels >>>');
+ const channelNames: string[] = [];
+ response.channels.forEach((ci) => {
+ channelNames.push('channel id: ' + ci.channel_id);
+ });
+ return response;
+ } else {
+ logger.error('response_payloads is null');
+ return 'response_payloads is null';
+ }
+
+ } catch (err) {
+ logger.error('Failed to query with error:' + err.stack ? err.stack : err);
+ return 'Failed to query with error:' + err.stack ? err.stack : err;
+ }
+}
diff --git a/balance-transfer/typescript/lib/helper.ts b/balance-transfer/typescript/lib/helper.ts
new file mode 100644
index 0000000000..b2497aab2e
--- /dev/null
+++ b/balance-transfer/typescript/lib/helper.ts
@@ -0,0 +1,311 @@
+/**
+ * Copyright 2017 Kapil Sachdeva All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import log4js = require('log4js');
+import * as path from 'path';
+import * as fs from 'fs';
+import * as util from 'util';
+import config from '../config';
+import hfc = require('fabric-client');
+// tslint:disable-next-line:no-var-requires
+const copService = require('fabric-ca-client');
+
+const logger = log4js.getLogger('Helper');
+logger.setLevel('DEBUG');
+hfc.setLogger(logger);
+
+let ORGS: any;
+const clients = {};
+const channels = {};
+const caClients = {};
+
+function readAllFiles(dir: string) {
+ const files = fs.readdirSync(dir);
+ const certs: any = [];
+ files.forEach((fileName) => {
+ const filePath = path.join(dir, fileName);
+ const data = fs.readFileSync(filePath);
+ certs.push(data);
+ });
+ return certs;
+}
+
+function getKeyStoreForOrg(org: string) {
+ return hfc.getConfigSetting('keyValueStore') + '_' + org;
+}
+
+function setupPeers(channel: any, org: string, client: Client) {
+ for (const key in ORGS[org].peers) {
+ if (key) {
+ const data = fs.readFileSync(
+ path.join(__dirname, ORGS[org].peers[key]['tls_cacerts']));
+ const peer = client.newPeer(
+ ORGS[org].peers[key].requests,
+ {
+ 'pem': Buffer.from(data).toString(),
+ 'ssl-target-name-override': ORGS[org].peers[key]['server-hostname']
+ }
+ );
+ peer.setName(key);
+
+ channel.addPeer(peer);
+ }
+ }
+}
+
+function newOrderer(client: Client) {
+ const caRootsPath = ORGS.orderer.tls_cacerts;
+ const data = fs.readFileSync(path.join(__dirname, caRootsPath));
+ const caroots = Buffer.from(data).toString();
+ return client.newOrderer(ORGS.orderer.url, {
+ 'pem': caroots,
+ 'ssl-target-name-override': ORGS.orderer['server-hostname']
+ });
+}
+
+function getOrgName(org: string) {
+ return ORGS[org].name;
+}
+
+function getMspID(org: string) {
+ logger.debug('Msp ID : ' + ORGS[org].mspid);
+ return ORGS[org].mspid;
+}
+
+function newRemotes(names: string[], forPeers: boolean, userOrg: string) {
+ const client = getClientForOrg(userOrg);
+
+ const targets: any[] = [];
+ // find the peer that match the names
+ names.forEach((n) => {
+ if (ORGS[userOrg].peers[n]) {
+ // found a peer matching the name
+ const data = fs.readFileSync(
+ path.join(__dirname, ORGS[userOrg].peers[n]['tls_cacerts']));
+ const grpcOpts = {
+ 'pem': Buffer.from(data).toString(),
+ 'ssl-target-name-override': ORGS[userOrg].peers[n]['server-hostname']
+ };
+
+ if (forPeers) {
+ targets.push(client.newPeer(ORGS[userOrg].peers[n].requests, grpcOpts));
+ } else {
+ const eh = client.newEventHub();
+ eh.setPeerAddr(ORGS[userOrg].peers[n].events, grpcOpts);
+ targets.push(eh);
+ }
+ }
+ });
+
+ if (targets.length === 0) {
+ logger.error(util.format('Failed to find peers matching the names %s', names));
+ }
+
+ return targets;
+}
+
+async function getAdminUser(userOrg: string): Promise {
+ const users = hfc.getConfigSetting('admins');
+ const username = users[0].username;
+ const password = users[0].secret;
+
+ const client = getClientForOrg(userOrg);
+
+ const store = await hfc.newDefaultKeyValueStore({
+ path: getKeyStoreForOrg(getOrgName(userOrg))
+ });
+
+ client.setStateStore(store);
+
+ const user = await client.getUserContext(username, true);
+
+ if (user && user.isEnrolled()) {
+ logger.info('Successfully loaded member from persistence');
+ return user;
+ }
+
+ const caClient = caClients[userOrg];
+
+ const enrollment = await caClient.enroll({
+ enrollmentID: username,
+ enrollmentSecret: password
+ });
+
+ logger.info('Successfully enrolled user \'' + username + '\'');
+ const userOptions: UserOptions = {
+ username,
+ mspid: getMspID(userOrg),
+ cryptoContent: {
+ privateKeyPEM: enrollment.key.toBytes(),
+ signedCertPEM: enrollment.certificate
+ }
+ };
+
+ const member = await client.createUser(userOptions);
+ return member;
+}
+
+export function newPeers(names: string[], org: string) {
+ return newRemotes(names, true, org);
+}
+
+export function newEventHubs(names: string[], org: string) {
+ return newRemotes(names, false, org);
+}
+
+export function setupChaincodeDeploy() {
+ process.env.GOPATH = path.join(__dirname, hfc.getConfigSetting('CC_SRC_PATH'));
+}
+
+export function getOrgs() {
+ return ORGS;
+}
+
+export function getClientForOrg(org: string): Client {
+ return clients[org];
+}
+
+export function getChannelForOrg(org: string): Channel {
+ return channels[org];
+}
+
+export function init() {
+
+ hfc.addConfigFile(path.join(__dirname, config.networkConfigFile));
+ hfc.addConfigFile(path.join(__dirname, '../app_config.json'));
+
+ ORGS = hfc.getConfigSetting('network-config');
+
+ // set up the client and channel objects for each org
+ for (const key in ORGS) {
+ if (key.indexOf('org') === 0) {
+ const client = new hfc();
+
+ const cryptoSuite = hfc.newCryptoSuite();
+ // TODO: Fix it up as setCryptoKeyStore is only available for s/w impl
+ (cryptoSuite as any).setCryptoKeyStore(
+ hfc.newCryptoKeyStore({
+ path: getKeyStoreForOrg(ORGS[key].name)
+ }));
+
+ client.setCryptoSuite(cryptoSuite);
+
+ const channel = client.newChannel(hfc.getConfigSetting('channelName'));
+ channel.addOrderer(newOrderer(client));
+
+ clients[key] = client;
+ channels[key] = channel;
+
+ setupPeers(channel, key, client);
+
+ const caUrl = ORGS[key].ca;
+ caClients[key] = new copService(
+ caUrl, null /*defautl TLS opts*/, '' /* default CA */, cryptoSuite);
+ }
+ }
+}
+
+export async function getRegisteredUsers(
+ username: string, userOrg: string): Promise {
+
+ const client = getClientForOrg(userOrg);
+
+ const store = await hfc.newDefaultKeyValueStore({
+ path: getKeyStoreForOrg(getOrgName(userOrg))
+ });
+
+ client.setStateStore(store);
+ const user = await client.getUserContext(username, true);
+
+ if (user && user.isEnrolled()) {
+ logger.info('Successfully loaded member from persistence');
+ return user;
+ }
+
+ logger.info('Using admin to enroll this user ..');
+
+ // get the Admin and use it to enroll the user
+ const adminUser = await getAdminUser(userOrg);
+
+ const caClient = caClients[userOrg];
+ const secret = await caClient.register({
+ enrollmentID: username,
+ affiliation: userOrg + '.department1'
+ }, adminUser);
+
+ logger.debug(username + ' registered successfully');
+
+ const message = await caClient.enroll({
+ enrollmentID: username,
+ enrollmentSecret: secret
+ });
+
+ if (message && typeof message === 'string' && message.includes(
+ 'Error:')) {
+ logger.error(username + ' enrollment failed');
+ }
+ logger.debug(username + ' enrolled successfully');
+
+ const userOptions: UserOptions = {
+ username,
+ mspid: getMspID(userOrg),
+ cryptoContent: {
+ privateKeyPEM: message.key.toBytes(),
+ signedCertPEM: message.certificate
+ }
+ };
+
+ const member = await client.createUser(userOptions);
+ return member;
+}
+
+export function getLogger(moduleName: string) {
+ const moduleLogger = log4js.getLogger(moduleName);
+ moduleLogger.setLevel('DEBUG');
+ return moduleLogger;
+}
+
+export async function getOrgAdmin(userOrg: string): Promise {
+ const admin = ORGS[userOrg].admin;
+ const keyPath = path.join(__dirname, admin.key);
+ const keyPEM = Buffer.from(readAllFiles(keyPath)[0]).toString();
+ const certPath = path.join(__dirname, admin.cert);
+ const certPEM = readAllFiles(certPath)[0].toString();
+
+ const client = getClientForOrg(userOrg);
+ const cryptoSuite = hfc.newCryptoSuite();
+
+ if (userOrg) {
+ (cryptoSuite as any).setCryptoKeyStore(
+ hfc.newCryptoKeyStore({ path: getKeyStoreForOrg(getOrgName(userOrg)) }));
+ client.setCryptoSuite(cryptoSuite);
+ }
+
+ const store = await hfc.newDefaultKeyValueStore({
+ path: getKeyStoreForOrg(getOrgName(userOrg))
+ });
+
+ client.setStateStore(store);
+
+ return client.createUser({
+ username: 'peer' + userOrg + 'Admin',
+ mspid: getMspID(userOrg),
+ cryptoContent: {
+ privateKeyPEM: keyPEM,
+ signedCertPEM: certPEM
+ }
+ });
+}
diff --git a/balance-transfer/typescript/lib/network-config.json b/balance-transfer/typescript/lib/network-config.json
new file mode 100644
index 0000000000..2ec10ac91c
--- /dev/null
+++ b/balance-transfer/typescript/lib/network-config.json
@@ -0,0 +1,55 @@
+{
+ "network-config": {
+ "orderer": {
+ "url": "grpcs://localhost:7050",
+ "server-hostname": "orderer.example.com",
+ "tls_cacerts": "../artifacts/channel/crypto-config/ordererOrganizations/example.com/orderers/orderer.example.com/tls/ca.crt"
+ },
+ "org1": {
+ "name": "peerOrg1",
+ "mspid": "Org1MSP",
+ "ca": "https://localhost:7054",
+ "peers": {
+ "peer1": {
+ "requests": "grpcs://localhost:7051",
+ "events": "grpcs://localhost:7053",
+ "server-hostname": "peer0.org1.example.com",
+ "tls_cacerts": "../artifacts/channel/crypto-config/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt"
+ },
+ "peer2": {
+ "requests": "grpcs://localhost:7056",
+ "events": "grpcs://localhost:7058",
+ "server-hostname": "peer1.org1.example.com",
+ "tls_cacerts": "../artifacts/channel/crypto-config/peerOrganizations/org1.example.com/peers/peer1.org1.example.com/tls/ca.crt"
+ }
+ },
+ "admin": {
+ "key": "../artifacts/channel/crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/keystore",
+ "cert": "../artifacts/channel/crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/signcerts"
+ }
+ },
+ "org2": {
+ "name": "peerOrg2",
+ "mspid": "Org2MSP",
+ "ca": "https://localhost:8054",
+ "peers": {
+ "peer1": {
+ "requests": "grpcs://localhost:8051",
+ "events": "grpcs://localhost:8053",
+ "server-hostname": "peer0.org2.example.com",
+ "tls_cacerts": "../artifacts/channel/crypto-config/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt"
+ },
+ "peer2": {
+ "requests": "grpcs://localhost:8056",
+ "events": "grpcs://localhost:8058",
+ "server-hostname": "peer1.org2.example.com",
+ "tls_cacerts": "../artifacts/channel/crypto-config/peerOrganizations/org2.example.com/peers/peer1.org2.example.com/tls/ca.crt"
+ }
+ },
+ "admin": {
+ "key": "../artifacts/channel/crypto-config/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp/keystore",
+ "cert": "../artifacts/channel/crypto-config/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp/signcerts"
+ }
+ }
+ }
+}
diff --git a/balance-transfer/typescript/package.json b/balance-transfer/typescript/package.json
new file mode 100644
index 0000000000..69a6536911
--- /dev/null
+++ b/balance-transfer/typescript/package.json
@@ -0,0 +1,37 @@
+{
+ "name": "balance-transfer-typescript",
+ "version": "0.1.0",
+ "description": "The balance transfer sample written using typescript",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "author": "Kapil Sachdeva",
+ "license": "Apache-2.0",
+ "devDependencies": {
+ "@types/body-parser": "^1.16.5",
+ "@types/cors": "^2.8.1",
+ "@types/express-jwt": "0.0.37",
+ "@types/express-session": "^1.15.3",
+ "@types/jsonwebtoken": "^7.2.3",
+ "@types/log4js": "0.0.33",
+ "@types/node": "^8.0.33",
+ "express-bearer-token": "^2.1.0",
+ "jsonwebtoken": "^8.1.0",
+ "ts-node": "^3.3.0",
+ "tslint": "^5.6.0",
+ "tslint-microsoft-contrib": "^5.0.1",
+ "typescript": "^2.5.3"
+ },
+ "dependencies": {
+ "body-parser": "^1.18.2",
+ "cookie-parser": "^1.4.3",
+ "cors": "^2.8.4",
+ "express": "^4.16.1",
+ "express-jwt": "^5.3.0",
+ "express-session": "^1.15.6",
+ "fabric-ca-client": "^1.0.2",
+ "fabric-client": "^1.0.2",
+ "log4js": "^0.6.38"
+ }
+}
diff --git a/balance-transfer/typescript/runApp.sh b/balance-transfer/typescript/runApp.sh
new file mode 100755
index 0000000000..be79b9d4ac
--- /dev/null
+++ b/balance-transfer/typescript/runApp.sh
@@ -0,0 +1,71 @@
+#!/bin/bash
+#
+# Copyright IBM Corp. All Rights Reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+function dkcl(){
+ CONTAINER_IDS=$(docker ps -aq)
+ echo
+ if [ -z "$CONTAINER_IDS" -o "$CONTAINER_IDS" = " " ]; then
+ echo "========== No containers available for deletion =========="
+ else
+ docker rm -f $CONTAINER_IDS
+ fi
+ echo
+}
+
+function dkrm(){
+ DOCKER_IMAGE_IDS=$(docker images | grep "dev\|none\|test-vp\|peer[0-9]-" | awk '{print $3}')
+ echo
+ if [ -z "$DOCKER_IMAGE_IDS" -o "$DOCKER_IMAGE_IDS" = " " ]; then
+ echo "========== No images available for deletion ==========="
+ else
+ docker rmi -f $DOCKER_IMAGE_IDS
+ fi
+ echo
+}
+
+function restartNetwork() {
+ echo
+
+ #teardown the network and clean the containers and intermediate images
+ docker-compose -f ../artifacts/docker-compose.yaml down
+ dkcl
+ dkrm
+
+ #Cleanup the material
+ rm -rf /tmp/hfc-test-kvs_peerOrg* $HOME/.hfc-key-store/ /tmp/fabric-client-kvs_peerOrg*
+
+ #Start the network
+ docker-compose -f ../artifacts/docker-compose.yaml up -d
+ echo
+}
+
+function installNodeModules() {
+ echo
+ if [ -d node_modules ]; then
+ echo "============== node modules installed already ============="
+ else
+ echo "============== Installing node modules ============="
+ npm install
+ fi
+ copyIndex fabric-client/index.d.ts
+ copyIndex fabric-ca-client/index.d.ts
+ echo
+}
+
+function copyIndex() {
+ if [ ! -f node_modules/$1 ]; then
+ cp types/$1 node_modules/$1
+ fi
+}
+
+restartNetwork
+
+installNodeModules
+
+
+
+PORT=4000 ts-node app.ts
diff --git a/balance-transfer/typescript/testAPIs.sh b/balance-transfer/typescript/testAPIs.sh
new file mode 100755
index 0000000000..a720e53378
--- /dev/null
+++ b/balance-transfer/typescript/testAPIs.sh
@@ -0,0 +1,197 @@
+#!/bin/bash
+#
+# Copyright IBM Corp. All Rights Reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+jq --version > /dev/null 2>&1
+if [ $? -ne 0 ]; then
+ echo "Please Install 'jq' https://stedolan.github.io/jq/ to execute this script"
+ echo
+ exit 1
+fi
+starttime=$(date +%s)
+
+echo "POST request Enroll on Org1 ..."
+echo
+ORG1_TOKEN=$(curl -s -X POST \
+ http://localhost:4000/users \
+ -H "content-type: application/x-www-form-urlencoded" \
+ -d 'username=Jim&orgName=org1')
+echo $ORG1_TOKEN
+ORG1_TOKEN=$(echo $ORG1_TOKEN | jq ".token" | sed "s/\"//g")
+echo
+echo "ORG1 token is $ORG1_TOKEN"
+echo
+echo "POST request Enroll on Org2 ..."
+echo
+ORG2_TOKEN=$(curl -s -X POST \
+ http://localhost:4000/users \
+ -H "content-type: application/x-www-form-urlencoded" \
+ -d 'username=Barry&orgName=org2')
+echo $ORG2_TOKEN
+ORG2_TOKEN=$(echo $ORG2_TOKEN | jq ".token" | sed "s/\"//g")
+echo
+echo "ORG2 token is $ORG2_TOKEN"
+echo
+echo
+echo "POST request Create channel ..."
+echo
+curl -s -X POST \
+ http://localhost:4000/channels \
+ -H "authorization: Bearer $ORG1_TOKEN" \
+ -H "content-type: application/json" \
+ -d '{
+ "channelName":"mychannel",
+ "channelConfigPath":"../artifacts/channel/mychannel.tx"
+}'
+echo
+echo
+sleep 5
+echo "POST request Join channel on Org1"
+echo
+curl -s -X POST \
+ http://localhost:4000/channels/mychannel/peers \
+ -H "authorization: Bearer $ORG1_TOKEN" \
+ -H "content-type: application/json" \
+ -d '{
+ "peers": ["peer1","peer2"]
+}'
+echo
+echo
+
+echo "POST request Join channel on Org2"
+echo
+curl -s -X POST \
+ http://localhost:4000/channels/mychannel/peers \
+ -H "authorization: Bearer $ORG2_TOKEN" \
+ -H "content-type: application/json" \
+ -d '{
+ "peers": ["peer1","peer2"]
+}'
+echo
+echo
+
+echo "POST Install chaincode on Org1"
+echo
+curl -s -X POST \
+ http://localhost:4000/chaincodes \
+ -H "authorization: Bearer $ORG1_TOKEN" \
+ -H "content-type: application/json" \
+ -d '{
+ "peers": ["peer1", "peer2"],
+ "chaincodeName":"mycc",
+ "chaincodePath":"github.com/example_cc",
+ "chaincodeVersion":"v0"
+}'
+echo
+echo
+
+
+echo "POST Install chaincode on Org2"
+echo
+curl -s -X POST \
+ http://localhost:4000/chaincodes \
+ -H "authorization: Bearer $ORG2_TOKEN" \
+ -H "content-type: application/json" \
+ -d '{
+ "peers": ["peer1","peer2"],
+ "chaincodeName":"mycc",
+ "chaincodePath":"github.com/example_cc",
+ "chaincodeVersion":"v0"
+}'
+echo
+echo
+
+echo "POST instantiate chaincode on peer1 of Org1"
+echo
+curl -s -X POST \
+ http://localhost:4000/channels/mychannel/chaincodes \
+ -H "authorization: Bearer $ORG1_TOKEN" \
+ -H "content-type: application/json" \
+ -d '{
+ "chaincodeName":"mycc",
+ "chaincodeVersion":"v0",
+ "args":["a","100","b","200"]
+}'
+echo
+echo
+
+echo "POST invoke chaincode on peers of Org1 and Org2"
+echo
+TRX_ID=$(curl -s -X POST \
+ http://localhost:4000/channels/mychannel/chaincodes/mycc \
+ -H "authorization: Bearer $ORG1_TOKEN" \
+ -H "content-type: application/json" \
+ -d '{
+ "fcn":"move",
+ "args":["a","b","10"]
+}')
+echo "Transacton ID is $TRX_ID"
+echo
+echo
+
+echo "GET query chaincode on peer1 of Org1"
+echo
+curl -s -X GET \
+ "http://localhost:4000/channels/mychannel/chaincodes/mycc?peer=peer1&fcn=query&args=%5B%22a%22%5D" \
+ -H "authorization: Bearer $ORG1_TOKEN" \
+ -H "content-type: application/json"
+echo
+echo
+
+echo "GET query Block by blockNumber"
+echo
+curl -s -X GET \
+ "http://localhost:4000/channels/mychannel/blocks/1?peer=peer1" \
+ -H "authorization: Bearer $ORG1_TOKEN" \
+ -H "content-type: application/json"
+echo
+echo
+
+echo "GET query Transaction by TransactionID"
+echo
+curl -s -X GET http://localhost:4000/channels/mychannel/transactions/$TRX_ID?peer=peer1 \
+ -H "authorization: Bearer $ORG1_TOKEN" \
+ -H "content-type: application/json"
+echo
+echo
+
+echo "GET query ChainInfo"
+echo
+curl -s -X GET \
+ "http://localhost:4000/channels/mychannel?peer=peer1" \
+ -H "authorization: Bearer $ORG1_TOKEN" \
+ -H "content-type: application/json"
+echo
+echo
+
+echo "GET query Installed chaincodes"
+echo
+curl -s -X GET \
+ "http://localhost:4000/chaincodes?peer=peer1&type=installed" \
+ -H "authorization: Bearer $ORG1_TOKEN" \
+ -H "content-type: application/json"
+echo
+echo
+
+echo "GET query Instantiated chaincodes"
+echo
+curl -s -X GET \
+ "http://localhost:4000/chaincodes?peer=peer1&type=instantiated" \
+ -H "authorization: Bearer $ORG1_TOKEN" \
+ -H "content-type: application/json"
+echo
+echo
+
+echo "GET query Channels"
+echo
+curl -s -X GET \
+ "http://localhost:4000/channels?peer=peer1" \
+ -H "authorization: Bearer $ORG1_TOKEN" \
+ -H "content-type: application/json"
+echo
+echo
+
+echo "Total execution time : $(($(date +%s)-starttime)) secs ..."
diff --git a/balance-transfer/typescript/tsconfig.json b/balance-transfer/typescript/tsconfig.json
new file mode 100644
index 0000000000..7d6de87516
--- /dev/null
+++ b/balance-transfer/typescript/tsconfig.json
@@ -0,0 +1,27 @@
+{
+ "compilerOptions": {
+ "removeComments": false,
+ "preserveConstEnums": true,
+ "emitDecoratorMetadata": true,
+ "experimentalDecorators": true,
+ "sourceMap": true,
+ "declaration": true,
+ "noImplicitAny": true,
+ "noImplicitReturns": true,
+ "noImplicitThis": true,
+ "suppressImplicitAnyIndexErrors": true,
+ "moduleResolution": "node",
+ "module": "commonjs",
+ "target": "es6",
+ "outDir": "dist",
+ "baseUrl": ".",
+ "typeRoots": [
+ "types",
+ "node_modules/@types"
+ ]
+ },
+ "formatCodeOptions": {
+ "indentSize": 2,
+ "tabSize": 2
+ }
+}
\ No newline at end of file
diff --git a/balance-transfer/typescript/tslint.json b/balance-transfer/typescript/tslint.json
new file mode 100644
index 0000000000..9064616325
--- /dev/null
+++ b/balance-transfer/typescript/tslint.json
@@ -0,0 +1,38 @@
+{
+ "extends": "tslint:recommended",
+ "rulesDirectory": [
+ "tslint-microsoft-contrib"
+ ],
+ "rules": {
+ "trailing-comma": [false, {
+ "multiline": "always",
+ "singleline": "never"
+ }],
+ "interface-name": [false, "always-prefix"],
+ "no-console": [true,
+ "time",
+ "timeEnd",
+ "trace"
+ ],
+ "max-line-length": [
+ true,
+ 100
+ ],
+ "no-string-literal": false,
+ "no-use-before-declare": true,
+ "object-literal-sort-keys": false,
+ "ordered-imports": [false],
+ "quotemark": [
+ true,
+ "single",
+ "avoid-escape"
+ ],
+ "variable-name": [
+ true,
+ "allow-leading-underscore",
+ "allow-pascal-case",
+ "ban-keywords",
+ "check-format"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/balance-transfer/typescript/types/fabric-ca-client/index.d.ts b/balance-transfer/typescript/types/fabric-ca-client/index.d.ts
new file mode 100644
index 0000000000..e5c21a9134
--- /dev/null
+++ b/balance-transfer/typescript/types/fabric-ca-client/index.d.ts
@@ -0,0 +1,18 @@
+/**
+ * Copyright 2017 Kapil Sachdeva All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+declare module 'fabric-ca-client' {
+}
\ No newline at end of file
diff --git a/balance-transfer/typescript/types/fabric-client/index.d.ts b/balance-transfer/typescript/types/fabric-client/index.d.ts
new file mode 100644
index 0000000000..db17494df3
--- /dev/null
+++ b/balance-transfer/typescript/types/fabric-client/index.d.ts
@@ -0,0 +1,312 @@
+/**
+ * Copyright 2017 Kapil Sachdeva All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+declare enum Status {
+ UNKNOWN = 0,
+ SUCCESS = 200,
+ BAD_REQUEST = 400,
+ FORBIDDEN = 403,
+ NOT_FOUND = 404,
+ REQUEST_ENTITY_TOO_LARGE = 413,
+ INTERNAL_SERVER_ERROR = 500,
+ SERVICE_UNAVAILABLE = 503
+}
+
+type ChaicodeType = "golang" | "car" | "java";
+
+interface ProtoBufObject {
+ toBuffer(): Buffer;
+}
+
+interface KeyOpts {
+ ephemeral: boolean;
+}
+
+interface ConnectionOptions {
+
+}
+
+interface ConfigSignature extends ProtoBufObject {
+ signature_header: Buffer;
+ signature: Buffer;
+}
+
+interface ICryptoKey {
+ getSKI(): string;
+ isSymmetric(): boolean;
+ isPrivate(): boolean;
+ getPublicKey(): ICryptoKey;
+ toBytes(): string;
+}
+
+interface ICryptoKeyStore {
+ getKey(ski: string): Promise;
+ putKey(key: ICryptoKey): Promise;
+}
+
+interface IKeyValueStore {
+ getValue(name: string): Promise;
+ setValue(name: string, value: string): Promise;
+}
+
+interface IdentityFiles {
+ privateKey: string;
+ signedCert: string;
+}
+
+interface IdentityPEMs {
+ privateKeyPEM: string;
+ signedCertPEM: string;
+}
+
+interface UserOptions {
+ username: string;
+ mspid: string;
+ cryptoContent: IdentityFiles | IdentityPEMs;
+}
+
+interface ICryptoSuite {
+ decrypt(key: ICryptoKey, cipherText: Buffer, opts: any): Buffer;
+ deriveKey(key: ICryptoKey): ICryptoKey;
+ encrypt(key: ICryptoKey, plainText: Buffer, opts: any): Buffer;
+ getKey(ski: string): Promise;
+ generateKey(opts: KeyOpts): Promise;
+ hash(msg: string, opts: any): string;
+ importKey(pem: string, opts: KeyOpts): ICryptoKey | Promise;
+ sign(key: ICryptoKey, digest: Buffer): Buffer;
+ verify(key: ICryptoKey, signature: Buffer, digest: Buffer): boolean;
+}
+
+interface ChannelRequest {
+ name: string;
+ orderer: Orderer;
+ envelope?: Buffer;
+ config?: Buffer;
+ txId?: TransactionId;
+ signatures: ConfigSignature[];
+}
+
+interface TransactionRequest {
+ proposalResponses: ProposalResponse[];
+ proposal: Proposal;
+}
+
+interface BroadcastResponse {
+ status: string;
+}
+
+interface IIdentity {
+ serialize(): Buffer;
+ getMSPId(): string;
+ isValid(): boolean;
+ getOrganizationUnits(): string;
+ verify(msg: Buffer, signature: Buffer, opts: any): boolean;
+}
+
+interface ISigningIdentity {
+ sign(msg: Buffer, opts: any): Buffer;
+}
+
+interface ChaincodeInstallRequest {
+ targets: Peer[];
+ chaincodePath: string;
+ chaincodeId: string;
+ chaincodeVersion: string;
+ chaincodePackage?: Buffer;
+ chaincodeType?: ChaicodeType;
+}
+
+interface ChaincodeInstantiateUpgradeRequest {
+ targets?: Peer[];
+ chaincodeType?: string;
+ chaincodeId: string;
+ chaincodeVersion: string;
+ txId: TransactionId;
+ fcn?: string;
+ args?: string[];
+ 'endorsement-policy'?: any;
+}
+
+interface ChaincodeInvokeRequest {
+ targets?: Peer[];
+ chaincodeId: string;
+ txId: TransactionId;
+ fcn?: string;
+ args: string[];
+}
+
+interface ChaincodeQueryRequest {
+ targets?: Peer[];
+ chaincodeId: string;
+ txId: TransactionId;
+ fcn?: string;
+ args: string[];
+}
+
+interface ChaincodeInfo {
+ name: string;
+ version: string;
+ path: string;
+ input: string;
+ escc: string;
+ vscc: string;
+}
+
+interface ChannelInfo {
+ channel_id: string;
+}
+
+interface ChaincodeQueryResponse {
+ chaincodes: ChaincodeInfo[];
+}
+
+interface ChannelQueryResponse {
+ channels: ChannelInfo[];
+}
+
+interface OrdererRequest {
+ txId: TransactionId;
+}
+
+interface JoinChannelRequest {
+ txId: TransactionId;
+ targets: Peer[];
+ block: Buffer;
+}
+
+interface ResponseObject {
+ status: Status;
+ message: string;
+ payload: Buffer;
+}
+
+interface Proposal {
+ header: ByteBuffer;
+ payload: ByteBuffer;
+ extension: ByteBuffer;
+}
+
+interface Header {
+ channel_header: ByteBuffer;
+ signature_header: ByteBuffer;
+}
+
+interface ProposalResponse {
+ version: number;
+ timestamp: Date;
+ response: ResponseObject;
+ payload: Buffer;
+ endorsement: any;
+}
+
+type ProposalResponseObject = [Array, Proposal, Header];
+
+declare class Orderer {
+}
+
+declare class Peer {
+ setName(name: string): void;
+ getName(): string;
+}
+
+declare class EventHub {
+ connect(): void;
+ disconnect(): void;
+ getPeerAddr(): string;
+ setPeerAddr(url: string, opts: ConnectionOptions): void;
+ isconnected(): boolean;
+ registerBlockEvent(onEvent: (b: any) => void, onError?: (err: Error) => void): number;
+ registerTxEvent(txId: string, onEvent: (txId: any, code: string) => void, onError?: (err: Error) => void): void;
+ unregisterTxEvent(txId: string): void;
+}
+
+declare class Channel {
+ initialize(): Promise;
+ addOrderer(orderer: Orderer): void;
+ addPeer(peer: Peer): void;
+ getGenesisBlock(request: OrdererRequest): Promise;
+ getChannelConfig(): Promise;
+ joinChannel(request: JoinChannelRequest): Promise;
+ sendInstantiateProposal(request: ChaincodeInstantiateUpgradeRequest): Promise;
+ sendTransactionProposal(request: ChaincodeInvokeRequest): Promise;
+ sendTransaction(request: TransactionRequest): Promise;
+ queryByChaincode(request: ChaincodeQueryRequest): Promise;
+ queryBlock(blockNumber: number, target: Peer): Promise;
+ queryTransaction(txId: string, target: Peer): Promise;
+ queryInstantiatedChaincodes(target: Peer): Promise;
+ queryInfo(target: Peer): Promise;
+ getOrderers(): Orderer[];
+ getPeers(): Peer[];
+}
+
+declare abstract class BaseClient {
+ static setLogger(logger: any): void;
+ static addConfigFile(path: string): void;
+ static getConfigSetting(name: string, default_value?: any): any;
+ static newCryptoSuite(): ICryptoSuite;
+ static newCryptoKeyStore(obj?: { path: string }): ICryptoKeyStore;
+ static newDefaultKeyValueStore(obj?: { path: string }): Promise;
+ setCryptoSuite(suite: ICryptoSuite): void;
+ getCryptoSuite(): ICryptoSuite;
+}
+
+declare class TransactionId {
+ getTransactionID(): string;
+}
+
+interface UserConfig {
+ enrollmentID: string;
+ name: string
+ roles?: string[];
+ affiliation?: string;
+}
+
+declare class User {
+ isEnrolled(): boolean;
+ getName(): string;
+ getRoles(): string[];
+ setRoles(roles: string[]): void;
+ getAffiliation(): string;
+ setAffiliation(affiliation: string): void;
+ getIdentity(): IIdentity;
+ getSigningIdentity(): ISigningIdentity;
+ setCryptoSuite(suite: ICryptoSuite): void;
+ setEnrollment(privateKey: ICryptoKey, certificate: string, mspId: string): Promise;
+}
+
+declare class Client extends BaseClient {
+ isDevMode(): boolean;
+ getUserContext(name: string, checkPersistence: boolean): Promise | User;
+ setUserContext(user: User, skipPersistence?: boolean): Promise;
+ setDevMode(mode: boolean): void;
+ newOrderer(url: string, opts: ConnectionOptions): Orderer;
+ newChannel(name: string): Channel;
+ newPeer(url: string, opts: ConnectionOptions): Peer;
+ newEventHub(): EventHub;
+ newTransactionID(): TransactionId;
+ extractChannelConfig(envelope: Buffer): Buffer;
+ createChannel(request: ChannelRequest): Promise;
+ createUser(opts: UserOptions): Promise;
+ signChannelConfig(config: Buffer): ConfigSignature;
+ setStateStore(store: IKeyValueStore): void;
+ installChaincode(request: ChaincodeInstallRequest): Promise;
+ queryInstalledChaincodes(target: Peer): Promise;
+ queryChannels(target: Peer): Promise;
+}
+
+declare module 'fabric-client' {
+ export = Client;
+}