public
errorShots
.idea
+.DS_Store
"babel-generator": "6.26.0",
"babel-helpers": "6.24.1",
"babel-messages": "6.23.0",
+ "babel-register": "6.26.0",
"babel-runtime": "6.26.0",
"babel-template": "6.26.0",
"babel-traverse": "6.26.0",
"private": "0.1.8",
"slash": "1.0.0",
"source-map": "0.5.7"
+ },
+ "dependencies": {
+ "babel-register": {
+ "version": "6.26.0",
+ "resolved": "http://registry.npm.taobao.org/babel-register/download/babel-register-6.26.0.tgz",
+ "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=",
+ "requires": {
+ "babel-core": "6.26.0",
+ "babel-runtime": "6.26.0",
+ "core-js": "2.5.3",
+ "home-or-tmp": "2.0.0",
+ "lodash": "4.17.4",
+ "mkdirp": "0.5.1",
+ "source-map-support": "0.4.18"
+ }
+ }
}
},
"babel-register": {
"check-error": "1.0.2"
}
},
- "chain-sdk": {
- "version": "1.2.1",
- "resolved": "http://registry.npm.taobao.org/chain-sdk/download/chain-sdk-1.2.1.tgz",
- "integrity": "sha1-YbAHdLIlfyc/70LHO1xye/HyJJw=",
- "requires": {
- "btoa": "1.1.2",
- "fetch-ponyfill": "3.0.2",
- "uuid": "3.0.1"
- },
- "dependencies": {
- "uuid": {
- "version": "3.0.1",
- "resolved": "http://registry.npm.taobao.org/uuid/download/uuid-3.0.1.tgz",
- "integrity": "sha1-ZUS7ot/ajBzxfmKaOjBeK7H+5sE="
- }
- }
- },
"chalk": {
"version": "1.1.3",
"resolved": "http://registry.npm.taobao.org/chalk/download/chalk-1.1.3.tgz",
"Safari >= 8"
],
"dependencies": {
+ "btoa": "^1.1.2",
+ "fetch-ponyfill": "~3.0.2",
+ "uuid": "~2.0.2",
"babel-polyfill": "~6.16.0",
"bootstrap-sass": "~3.3.7",
- "chain-sdk": "~1.2.1",
"classnames": "~2.2.5",
- "fetch-ponyfill": "~3.0.2",
"lodash": "~4.17.4",
"moment": "~2.14.1",
"moment-timezone": "~0.5.5",
"redux-form": "~5.3.2",
"redux-thunk": "~2.1.0",
"reselect": "^3.0.0",
- "sha.js": "^2.4.8",
- "uuid": "~2.0.2"
+ "sha.js": "^2.4.8"
},
"devDependencies": {
"autoprefixer": "~6.7.7",
export default connect(
(state) => ({
authOk: !state.core.requireClientToken || state.core.validToken,
- configKnown: state.core.configKnown,
- configured: state.core.configured,
+ configKnown: true,
+ configured: true,
onTestnet: state.core.onTestnet,
}),
(dispatch) => ({
export default connect(
(state) => ({
canLogOut: state.core.requireClientToken,
- connected: state.core.connected,
+ connected: true,
showDropwdown: state.app.dropdownState == 'open',
}),
(dispatch) => ({
const fetchCoreInfo = (options = {}) => {
return (dispatch) => {
return chainClient().config.info()
- .then((info) => dispatch(updateInfo(info)))
+ .then((info) => {
+ dispatch(updateInfo(info))
+ })
.catch((err) => {
if (options.throw || !err.status) {
throw err
clearSession,
logIn: (token) => (dispatch) => {
dispatch(setClientToken(token))
+ debugger
return dispatch(fetchCoreInfo({throw: true}))
.then(() => dispatch({type: 'USER_LOG_IN'})
)
import React from 'react'
-import { Connection } from 'chain-sdk'
+import { Connection } from 'sdk'
class RawJsonButton extends React.Component {
showRawJson(item){
return
}
- const pageNumber = parseInt(state.location.query.page || 1)
- if (pageNumber == 1) {
- store.dispatch(actions[type].fetchPage(query, pageNumber, { refresh: true }))
- } else {
- store.dispatch(actions[type].fetchPage(query, pageNumber))
- }
+ store.dispatch(actions[type].fetchAll())
}
const childRoutes = []
name: options.name || humanize(type + 's'),
indexRoute: {
component: List,
- onEnter: (nextState, replace) => { loadPage(nextState, replace) },
+ onEnter: (nextState, replace) => {
+ debugger
+ loadPage(nextState, replace)
+ },
onChange: (_, nextState, replace) => { loadPage(nextState, replace) }
},
childRoutes: childRoutes
--- /dev/null
+const shared = require('../shared')
+
+/**
+ * Access tokens are `name:secret-token` pairs that can be granted one or more
+ * policies for accessing Chain Core features. See {@link module:AuthorizationGrantsApi the
+ * access control API} for more info.
+ *
+ * More info: {@link https://chain.com/docs/core/learn-more/authentication-and-authorization}
+ * @typedef {Object} AccessToken
+ * @global
+ *
+ * @property {String} id
+ * User specified, unique identifier.
+ *
+ * @property {String} token
+ * Only returned in the response from {@link AccessTokensApi~create}.
+ *
+ * @property {String} createdAt
+ * Timestamp of token creation, RFC3339 formatted.
+ *
+ * @property {String} type
+ * DEPRECATED. Do not use in 1.2 or later. Either 'client' or 'network'.
+ */
+
+/**
+ * API for interacting with {@link AccessToken access tokens}.
+ *
+ * More info: {@link https://chain.com/docs/core/learn-more/authentication-and-authorization}
+ * @module AccessTokensApi
+ */
+const accessTokens = (client) => {
+ return {
+ /**
+ * Create a new access token.
+ *
+ * @param {Object} params - Parameters for access token creation.
+ * @param {String} params.id - User specified, unique identifier.
+ * @param {String} params.type - DEPRECATED. Do not use in 1.2 or later. Either 'client' or 'network'.
+ * @param {objectCallback} [callback] - Optional callback. Use instead of Promise return value as desired.
+ * @returns {Promise<AccessToken>} Newly created access token.
+ */
+ create: (params, cb) =>
+ shared.create(client, '/create-access-token', params, {skipArray: true, cb}),
+
+ /**
+ * Get one page of access tokens sorted by descending creation time,
+ * optionally filtered by type.
+ *
+ * @param {Object} params={} - Filter and pagination information.
+ * @param {String} params.type - Type of access tokens to return.
+ * @param {Number} params.pageSize - Number of items to return in result set.
+ * @param {pageCallback} [callback] - Optional callback. Use instead of Promise return value as desired.
+ * @returns {Promise<Page<AccessToken>>} Requested page of results.
+ */
+ query: (params, cb) => shared.query(client, 'accessTokens', '/list-access-tokens', params, {cb}),
+
+ /**
+ * Request all access tokens matching the specified query, calling the
+ * supplied processor callback with each item individually.
+ *
+ * @param {Object} params={} - Filter and pagination information.
+ * @param {String} params.type - Type of access tokens to return.
+ * @param {Number} params.pageSize - Number of items to return in result set.
+ * @param {QueryProcessor<AccessToken>} processor - Processing callback.
+ * @param {objectCallback} [callback] - Optional callback. Use instead of Promise return value as desired.
+ * @returns {Promise} A promise resolved upon processing of all items, or
+ * rejected on error.
+ */
+ queryAll: (params, processor, cb) => shared.queryAll(client, 'list-access-tokens', params, processor, cb),
+
+ /**
+ * Delete the specified access token.
+ *
+ * @param {String} id - Access token ID.
+ * @param {objectCallback} [callback] - Optional callback. Use instead of Promise return value as desired.
+ * @returns {Promise<Object>} Success message or error.
+ */
+ delete: (id, cb) => shared.tryCallback(
+ client.request('/delete-access-token', {id: id}),
+ cb
+ ),
+ }
+}
+
+module.exports = accessTokens
--- /dev/null
+const shared = require('../shared')
+
+/**
+ * An account is an object in Chain Core that tracks ownership of assets on a
+ * blockchain by creating and tracking control programs.
+ *
+ * More info: {@link https://chain.com/docs/core/build-applications/accounts}
+ * @typedef {Object} Account
+ * @global
+ *
+ * @property {String} id
+ * Unique account identifier.
+ *
+ * @property {String} alias
+ * User specified, unique identifier.
+ *
+ * @property {Key[]} keys
+ * The list of keys used to create control programs under the account.
+ * Signatures from these keys are required for spending funds held in the account.
+ *
+ * @property {Number} quorum
+ * The number of keys required to sign transactions for the account.
+ *
+ * @property {Object} tags
+ * User-specified tag structure for the account.
+ */
+
+/**
+ * A receiver is an object that wraps an account control program with additional
+ * payment information, such as expiration dates.
+ *
+ * <br/></br>
+ * More info: {@link https://chain.com/docs/core/build-applications/control-programs}
+ * @typedef {Object} Receiver
+ * @global
+ *
+ * @property {String} controlProgram
+ * The underlying control program that will be used in transactions paying to this receiver.
+ *
+ * @property {String} expiresAt
+ * Timestamp indicating when the receiver will cease to be valid, RFC3339 formatted.
+ */
+
+/**
+ * API for interacting with {@link Account accounts}.
+ *
+ * More info: {@link https://chain.com/docs/core/build-applications/accounts}
+ * @module AccountsApi
+ */
+const accountsAPI = (client) => {
+ /**
+ * @typedef {Object} createRequest
+ *
+ * @property {String} [alias]
+ * User specified, unique identifier.
+ *
+ * @property {String[]} rootXpubs
+ * The list of keys used to create control programs under the account.
+ *
+ * @property {Number} quorum
+ * The number of keys required to sign transactions for the account.
+ *
+ * @property {Object} [tags]
+ * User-specified tag structure for the account.
+ */
+
+ /**
+ * @typedef {Object} updateTagsRequest
+ *
+ * @property {String} [id]
+ * The account ID. Either the ID or alias must be specified, but not both.
+ *
+ * @property {String} [alias]
+ * The account alias. Either the ID or alias must be specified, but not both.
+ *
+ * @property {Object} tags
+ * A new set of tags, which will replace the existing tags.
+ */
+
+ /**
+ * @typedef {Object} createReceiverRequest
+ *
+ * @property {String} [accountAlias]
+ * The unique alias of the account. accountAlias or accountId must be
+ * provided.
+ *
+ * @property {String} [accountId]
+ * The unique ID of the account. accountAlias or accountId must be
+ * provided.
+ *
+ * @property {String} [expiresAt]
+ * An RFC3339 timestamp indicating when the receiver will cease to be valid.
+ * Defaults to 30 days in the future.
+ */
+
+ return {
+ /**
+ * Create a new account.
+ *
+ * @param {module:AccountsApi~createRequest} params - Parameters for account creation.
+ * @param {objectCallback} [callback] - Optional callback. Use instead of Promise return value as desired.
+ * @returns {Promise<Account>} Newly created account.
+ */
+ create: (params, cb) => shared.create(client, '/create-account', params, {cb}),
+
+ /**
+ * Create multiple new accounts.
+ *
+ * @param {module:AccountsApi~createRequest[]} params - Parameters for creation of multiple accounts.
+ * @param {batchCallback} [callback] - Optional callback. Use instead of Promise return value as desired.
+ * @returns {Promise<BatchResponse<Account>>} Newly created accounts.
+ */
+ createBatch: (params, cb) => shared.createBatch(client, '/create-account', params, {cb}),
+
+ /**
+ * Update account tags.
+ *
+ * @param {module:AccountsApi~updateTagsRequest} params - Parameters for updating account tags.
+ * @param {objectCallback} [cb] - Optional callback. Use instead of Promise return value as desired.
+ * @returns {Promise<Object>} Success message.
+ */
+ updateTags: (params, cb) => shared.singletonBatchRequest(client, '/update-account-tags', params, cb),
+
+ /**
+ * Update tags for multiple assets.
+ *
+ * @param {module:AccountsApi~updateTagsRequest[]} params - Parameters for updating account tags.
+ * @param {batchCallback} [cb] - Optional callback. Use instead of Promise return value as desired.
+ * @returns {Promise<BatchResponse<Object>>} A batch of success responses and/or errors.
+ */
+ updateTagsBatch: (params, cb) => shared.batchRequest(client, '/update-account-tags', params, cb),
+
+ /**
+ * Get one page of accounts matching the specified query.
+ *
+ * @param {Object} params={} - Filter and pagination information.
+ * @param {String} params.filter - Filter string, see {@link https://chain.com/docs/core/build-applications/queries}.
+ * @param {Array<String|Number>} params.filterParams - Parameter values for filter string (if needed).
+ * @param {Number} params.pageSize - Number of items to return in result set.
+ * @param {pageCallback} [callback] - Optional callback. Use instead of Promise return value as desired.
+ * @returns {Promise<Page<Account>>} Requested page of results.
+ */
+ query: (params, cb) => shared.query(client, 'accounts', '/list-accounts', params, {cb}),
+
+ /**
+ * Request all accounts matching the specified query, calling the
+ * supplied processor callback with each item individually.
+ *
+ * @param {Object} params={} - Filter and pagination information.
+ * @param {String} params.filter - Filter string, see {@link https://chain.com/docs/core/build-applications/queries}.
+ * @param {Array<String|Number>} params.filterParams - Parameter values for filter string (if needed).
+ * @param {Number} params.pageSize - Number of items to return in result set.
+ * @param {QueryProcessor<Account>} processor - Processing callback.
+ * @param {objectCallback} [callback] - Optional callback. Use instead of Promise return value as desired.
+ * @returns {Promise} A promise resolved upon processing of all items, or
+ * rejected on error.
+ */
+ queryAll: (params, processor, cb) => shared.queryAll(client, 'accounts', params, processor, cb),
+
+ /**
+ * Create a new receiver under the specified account.
+ *
+ * @param {module:AccountsApi~createReceiverRequest} params - Parameters for receiver creation.
+ * @param {objectCallback} [callback] - Optional callback. Use instead of Promise return value as desired.
+ * @returns {Promise<Receiver>} Newly created receiver.
+ */
+ createReceiver: (params, cb) => shared.create(client, '/create-account-receiver', params, {cb}),
+
+ /**
+ * Create multiple receivers under the specified accounts.
+ *
+ * @param {module:AccountsApi~createReceiverRequest[]} params - Parameters for creation of multiple receivers.
+ * @param {batchCallback} [callback] - Optional callback. Use instead of Promise return value as desired.
+ * @returns {Promise<BatchResponse<Receiver>>} Newly created receivers.
+ */
+ createReceiverBatch: (params, cb) => shared.createBatch(client, '/create-account-receiver', params, {cb}),
+ }
+}
+
+module.exports = accountsAPI
--- /dev/null
+const shared = require('../shared')
+
+/**
+ * An asset is a type of value that can be issued on a blockchain. All units of
+ * a given asset are fungible. Units of an asset can be transacted directly
+ * between parties without the involvement of the issuer.
+ *
+ * More info: {@link https://chain.com/docs/core/build-applications/assets}
+ * @typedef {Object} Asset
+ * @global
+ *
+ * @property {String} id
+ * Globally unique identifier of the asset.
+ * Asset version 1 specifies the asset id as the hash of:
+ * - the asset version
+ * - the asset's issuance program
+ * - the core's VM version
+ * - the hash of the network's initial block
+ *
+ * @property {String} alias
+ * User specified, unique identifier.
+ *
+ * @property {String} issuanceProgram
+ *
+ * @property {Key[]} keys
+ * The list of keys used to issue units of the asset.
+ *
+ * @property {Number} quorum
+ * The number of signatures required to issue new units of the asset.
+ *
+ * @property {Object} defintion
+ * User-specified, arbitrary/unstructured data visible across
+ * blockchain networks. Version 1 assets specify the definition in their
+ * issuance programs, rendering the definition immutable.
+ *
+ * @property {Object} tags
+ * User-specified tag structure for the asset.
+ */
+
+/**
+ * API for interacting with {@link Asset assets}.
+ *
+ * More info: {@link https://chain.com/docs/core/build-applications/assets}
+ * @module AssetsApi
+ */
+const assetsAPI = (client) => {
+ /**
+ * @typedef {Object} createRequest
+ *
+ * @property {String} [alias]
+ * User specified, unique identifier.
+ *
+ * @property {String[]} rootXpubs
+ * The list of keys used to create the issuance program for the asset.
+ *
+ * @property {Number} quorum
+ * The number of keys required to issue units of the asset.
+ *
+ * @property {Object} [tags]
+ * User-specified, arbitrary/unstructured data local to the asset's originating core.
+ *
+ * @property {Object} [defintion]
+ * User-specified, arbitrary/unstructured data visible across blockchain networks.
+ */
+
+ /**
+ * @typedef {Object} updateTagsRequest
+ *
+ * @property {String} [id]
+ * The asset ID. Either the ID or alias must be specified, but not both.
+ *
+ * @property {String} [alias]
+ * The asset alias. Either the ID or alias must be specified, but not both.
+ *
+ * @property {Object} [tags]
+ * A new set of tags, which will replace the existing tags.
+ */
+
+ return {
+ /**
+ * Create a new asset.
+ *
+ * @param {module:AssetsApi~createRequest} params - Parameters for asset creation.
+ * @param {objectCallback} [callback] - Optional callback. Use instead of Promise return value as desired.
+ * @returns {Promise<Asset>} Newly created asset.
+ */
+ create: (params, cb) => shared.create(client, '/create-asset', params, {cb}),
+
+ /**
+ * Create multiple new assets.
+ *
+ * @param {module:AssetsApi~createRequest[]} params - Parameters for creation of multiple assets.
+ * @param {batchCallback} [callback] - Optional callback. Use instead of Promise return value as desired.
+ * @returns {Promise<BatchResponse<Asset>>} Newly created assets.
+ */
+ createBatch: (params, cb) => shared.createBatch(client, '/create-asset', params, {cb}),
+
+ /**
+ * Update asset tags.
+ *
+ * @param {module:AssetsApi~updateTagsRequest} params - Parameters for updating asset tags.
+ * @param {objectCallback} [cb] - Optional callback. Use instead of Promise return value as desired.
+ * @returns {Promise<Object>} Success message.
+ */
+ updateTags: (params, cb) => shared.singletonBatchRequest(client, '/update-asset-tags', params, cb),
+
+ /**
+ * Update tags for multiple assets.
+ *
+ * @param {module:AssetsApi~updateTagsRequest[]} params - Parameters for updating asset tags.
+ * @param {batchCallback} [cb] - Optional callback. Use instead of Promise return value as desired.
+ * @returns {Promise<BatchResponse<Object>>} A batch of success responses and/or errors.
+ */
+ updateTagsBatch: (params, cb) => shared.batchRequest(client, '/update-asset-tags', params, cb),
+
+ /**
+ * Get one page of assets matching the specified query.
+ *
+ * @param {Object} params={} - Filter and pagination information.
+ * @param {String} params.filter - Filter string, see {@link https://chain.com/docs/core/build-applications/queries}.
+ * @param {Array<String|Number>} params.filterParams - Parameter values for filter string (if needed).
+ * @param {Number} params.pageSize - Number of items to return in result set.
+ * @param {pageCallback} [callback] - Optional callback. Use instead of Promise return value as desired.
+ * @returns {Promise<Page<Asset>>} Requested page of results.
+ */
+ query: (params, cb) => shared.query(client, 'assets', '/list-assets', params, {cb}),
+
+ /**
+ * Request all assets matching the specified query, calling the
+ * supplied processor callback with each item individually.
+ *
+ * @param {Object} params={} - Filter and pagination information.
+ * @param {String} params.filter - Filter string, see {@link https://chain.com/docs/core/build-applications/queries}.
+ * @param {Number} params.pageSize - Number of items to return in result set.
+ * @param {Array<String|Number>} params.filterParams - Parameter values for filter string (if needed).
+ * @param {QueryProcessor<Asset>} processor - Processing callback.
+ * @param {objectCallback} [callback] - Optional callback. Use instead of Promise return value as desired.
+ * @returns {Promise} A promise resolved upon processing of all items, or
+ * rejected on error.
+ */
+ queryAll: (params, processor, cb) => shared.queryAll(client, 'assets', params, processor, cb),
+ }
+}
+
+module.exports = assetsAPI
--- /dev/null
+const shared = require('../shared')
+const util = require('../util')
+
+/**
+ * Authorization grants provide a mapping from guard objects (access tokens or X509
+ * certificates) to a list of predefined Chain Core access policies.
+ *
+ * * **client-readwrite**: full access to the Client API
+ * * **client-readonly**: access to read-only Client endpoints
+ * * **monitoring**: access to monitoring-specific endpoints
+ * * **crosscore**: access to the cross-core API, including fetching blocks and
+ * submitting transactions to the generator, but not including block signing
+ * * **crosscore-signblock**: access to the cross-core API's block singing
+ * functionality
+ *
+ * More info: {@link https://chain.com/docs/core/learn-more/authentication-and-authorization}
+ * @typedef {Object} AuthorizationGrant
+ * @global
+ *
+ * @property {String} guardType
+ * Type of credential, either 'access_token' or 'x509'.
+ *
+ * @property {Object} guardData
+ * Data used by the guard to identity incoming credentials.
+ *
+ * If guardType is 'access_token', you should provide an instance of
+ * {@link module:AuthorizationGrantsApi~AccessTokenGuardData}, which identifies access tokens by ID.
+ *
+ * If guardType is 'x509', you should provide an instance of {@link module:AuthorizationGrantsApi~X509GuardData},
+ * which identifies x509 certificates based on kev-value pairs in specified
+ * certificate fields.
+ *
+ * @property {String} policy
+ * Authorization single policy to attach to specific grant.
+ *
+ * @property {Boolean} protected
+ * Whether the grant can be deleted. Only used for internal purposes.
+ *
+ * @property {String} createdAt
+ * Time of grant creation, RFC3339 formatted.
+ */
+
+/**
+ * API for interacting with {@link AuthorizationGrant access grants}.
+ *
+ * More info: {@link https://chain.com/docs/core/learn-more/authentication-and-authorization}
+ * @module AuthorizationGrantsApi
+ */
+const authorizationGrants = (client) => ({
+ /**
+ * @typedef {Object} AccessTokenGuardData
+ *
+ * @property {String} id
+ * Unique identifier of an access token
+ */
+
+ /**
+ * @typedef {Object} X509GuardData
+ * x509 certificates are identified by their Subject attribute. You can
+ * configure the guard by specifying values for the Subject's sub-attributes,
+ * such as CN or OU. If a certificate's Subject contains all of the
+ * sub-attribute values specified in the guard, the guard will produce a
+ * positive match.
+ *
+ * @property {Object} subject - Object identifying key-value pairs in the subject field.
+ * @property {(String|Array)} subject.C - Country attribute
+ * @property {(String|Array)} subject.O - Organization attribute
+ * @property {(String|Array)} subject.OU - Organizational Unit attribute
+ * @property {(String|Array)} subject.L - Locality attribute
+ * @property {(String|Array)} subject.ST - State/Province attribute
+ * @property {(String|Array)} subject.STREET - Street Address attribute
+ * @property {(String|Array)} subject.POSTALCODE - Postal Code attribute
+ * @property {String} subject.SERIALNUMBER - Serial Number attribute
+ * @property {String} subject.CN - Common Name attribute
+ */
+
+ /**
+ * Create a new access grant.
+ *
+ * @param {Object} params - Parameters for access grant creation.
+ * @param {String} params.guardType - Type of credential to guard with, either 'access_token' or 'x509'.
+ * @param {Object} params.guardData - Object containing data needed to identify the incoming credential.
+ * @param {String} params.policy - Authorization polciy to attach to specific grant. See {@link AuthorizationGrant} for a list of available policiies.
+ * @param {objectCallback} [callback] - Optional callback. Use instead of Promise return value as desired.
+ * @returns {Promise<Object>} Success message or error.
+ */
+ create: (params, cb) => {
+ params = Object.assign({}, params)
+ if (params.guardType == 'x509') {
+ params.guardData = util.sanitizeX509GuardData(params.guardData)
+ }
+
+ return shared.create(
+ client,
+ '/create-authorization-grant',
+ params,
+ {skipArray: true, cb}
+ )
+ },
+
+ /**
+ * Delete the specfiied access grant.
+ *
+ * @param {Object} params - Parameters for access grant deletion.
+ * @param {String} params.guardType - Type of credential to delete, either 'access_token' or 'x509'.
+ * @param {Object} params.guardData - Object containing data needed to identify the credential to be removed.
+ * @param {String} params.policy - Authorization policy to remove. See {@link AuthorizationGrant} for a list of available policiies.
+ * @param {objectCallback} [callback] - Optional callback. Use instead of Promise return value as desired.
+ * @returns {Promise<Object>} Success message or error.
+ */
+ delete: (params, cb) => shared.tryCallback(
+ client.request('/delete-authorization-grant', params),
+ cb
+ ),
+
+ /**
+ * Get all access grants.
+ *
+ * @param {pageCallback} [callback] - Optional callback. Use instead of Promise return value as desired.
+ * @returns {Promise<Array<AuthorizationGrant>>} Requested page of results.
+ */
+ list: (cb) =>
+ shared.query(client, 'accessTokens', '/list-authorization-grants', {}, {cb}),
+})
+
+module.exports = authorizationGrants
--- /dev/null
+const shared = require('../shared')
+
+/**
+ * Any balance on the blockchain is simply a summation of unspent outputs.
+ * Unlike other queries in Chain Core, balance queries do not return Chain Core
+ * objects, only simple sums over the amount fields in a specified list of
+ * unspent output objects
+ *
+ * More info: {@link https://chain.com/docs/core/build-applications/queries}
+ * @typedef {Object} Balance
+ * @global
+ *
+ * @property {Number} amount
+ * Sum of the unspent outputs.
+ *
+ * @property {Object} sumBy
+ * List of parameters on which to sum unspent outputs.
+ */
+
+/**
+* API for interacting with {@link Balance balances}.
+ *
+ * More info: {@link https://chain.com/docs/core/build-applications/queries}
+ * @module BalancesApi
+ */
+const balancesAPI = (client) => {
+ return {
+ /**
+ * Get one page of balances matching the specified query.
+ *
+ * @param {Object} params={} - Filter and pagination information.
+ * @param {String} params.filter - Filter string, see {@link https://chain.com/docs/core/build-applications/queries}.
+ * @param {Array<String|Number>} params.filterParams - Parameter values for filter string (if needed).
+ * @param {Array<String>} params.sumBy - List of unspent output attributes to sum by.
+ * @param {Integer} params.timestamp - A millisecond Unix timestamp. By using this parameter, you can perform queries that reflect the state of the blockchain at different points in time.
+ * @param {Number} params.pageSize - Number of items to return in result set.
+ * @param {pageCallback} [callback] - Optional callback. Use instead of Promise return value as desired.
+ * @returns {Promise<Page<Balance>>} Requested page of results.
+ */
+ query: (params, cb) => shared.query(client, 'balances', '/list-balances', params, {cb}),
+
+ /**
+ * Request all balances matching the specified query, calling the
+ * supplied processor callback with each item individually.
+ *
+ * @param {Object} params={} - Filter and pagination information.
+ * @param {String} params.filter - Filter string, see {@link https://chain.com/docs/core/build-applications/queries}.
+ * @param {Array<String|Number>} params.filterParams - Parameter values for filter string (if needed).
+ * @param {Array<String>} params.sumBy - List of unspent output attributes to sum by.
+ * @param {Integer} params.timestamp - A millisecond Unix timestamp. By using this parameter, you can perform queries that reflect the state of the blockchain at different points in time.
+ * @param {Number} params.pageSize - Number of items to return in result set.
+ * @param {QueryProcessor<Balance>} processor - Processing callback.
+ * @param {objectCallback} [callback] - Optional callback. Use instead of Promise return value as desired.
+ * @returns {Promise} A promise resolved upon processing of all items, or
+ * rejected on error.
+ */
+ queryAll: (params, processor, cb) => shared.queryAll(client, 'balances', params, processor, cb),
+ }
+}
+
+module.exports = balancesAPI
--- /dev/null
+const shared = require('../shared')
+
+/**
+ * Basic information about the configuration of Chain Core, as well as any
+ * errors encountered when updating the local state of the blockchain
+ *
+ * More info: {@link https://chain.com/docs/core/get-started/configure}
+ * @typedef {Object} CoreInfo
+ *
+ * @property {Object} snapshot
+ * @property {Number} snapshot.attempt
+ * @property {Number} snapshot.height
+ * @property {Number} snapshot.size
+ * @property {Number} snapshot.downloaded
+ * @property {Boolean} snapshot.inProgress
+ *
+ * @property {Boolean} isConfigured
+ * Whether the core has been configured.
+ *
+ * @property {String} configuredAt
+ * RFC3339 timestamp reflecting when the core was configured.
+ *
+ * @property {Boolean} isSigner
+ * Whether the core is configured as a block signer.
+ *
+ * @property {Boolean} isGenerator
+ * Whether the core is configured as the blockchain generator.
+ *
+ * @property {String} generatorUrl
+ * URL of the generator.
+ *
+ * @property {String} generatorAccessToken
+ * The access token used to connect to the generator.
+ *
+ * @property {String} blockchainId
+ * Hash of the initial block.
+ *
+ * @property {Number} blockHeight
+ * Height of the blockchain in the local core.
+ *
+ * @property {Number} generatorBlockHeight
+ * Height of the blockchain in the generator
+ *
+ * @property {String} generatorBlockHeightFetchedAt
+ * RFC3339 timestamp reflecting the last time generator_block_height was updated.
+ *
+ * @property {Boolean} isProduction
+ * Whether the core is running in production mode.
+ *
+ * @property {Number} crosscoreRpcVersion
+ * The cross-core API version supported by this core.
+ *
+ * @property {Number} networkRpcVersion
+ * DEPRECATED. Do not use in 1.2 or greater. Superseded by {@link crosscoreRpcVersion}.
+ *
+ * @property {String} coreId
+ * A random identifier for the core, generated during configuration.
+ *
+ * @property {String} version
+ * The release version of the cored binary.
+ *
+ * @property {String} buildCommit
+ * Git SHA of build source.
+ *
+ * @property {String} buildDate
+ * Unixtime (as string) of binary build.
+ *
+ * @property {Object} buildConfig
+ * Features enabled or disabled in this build of Chain Core.
+ *
+ * @property {Boolean} buildConfig.isLocalhostAuth
+ * Whether any request from the loopback device (localhost) should be
+ * automatically authenticated and authorized, without additional
+ * credentials.
+ *
+ * @property {Boolean} buildConfig.isMockHsm
+ * Whether the MockHSM API is enabled.
+ *
+ * @property {Boolean} buildConfig.isReset
+ * Whether the core reset API call is enabled.
+ *
+ * @property {Boolean} buildConfig.isPlainHttp
+ * Whether non-TLS HTTP requests (http://...) are allowed.
+ *
+ * @property {Object} health
+ * Blockchain error information.
+ */
+
+/**
+ * Chain Core can be configured as a new blockchain network, or as a node in an
+ * existing blockchain network.
+ *
+ * More info: {@link https://chain.com/docs/core/get-started/configure}
+ * @module ConfigApi
+ */
+const configAPI = (client) => {
+ return {
+ /**
+ * Reset specified Chain Core.
+ *
+ * @param {Boolean} everything - If `true`, all objects including access tokens and
+ * MockHSM keys will be deleted. If `false`, then access tokens
+ * and MockHSM keys will be preserved.
+ * @param {objectCallback} [callback] - Optional callback. Use instead of Promise return value as desired.
+ * @returns {Promise} Promise resolved on success.
+ */
+ reset: (everything = false, cb) => shared.tryCallback(
+ client.request('/reset', {everything: everything}),
+ cb
+ ),
+
+ /**
+ * Configure specified Chain Core.
+ *
+ * @param {Object} opts - options for configuring Chain Core.
+ * @param {Boolean} opts.isGenerator - Whether the local core will be a block generator
+ * for the blockchain; i.e., you are starting a new blockchain on
+ * the local core. `false` if you are connecting to a
+ * pre-existing blockchain.
+ * @param {String} opts.generatorUrl - A URL for the block generator. Required if
+ * `isGenerator` is false.
+ * @param {String} opts.generatorAccessToken - An access token provided by administrators
+ * of the block generator. Required if `isGenerator` is false.
+ * @param {String} opts.blockchainId - The unique ID of the generator's blockchain.
+ * Required if `isGenerator` is false.
+ * @param {objectCallback} [callback] - Optional callback. Use instead of Promise return value as desired.
+ * @returns {Promise} Promise resolved on success.
+ */
+ configure: (opts = {}, cb) => shared.tryCallback(
+ client.request('/configure', opts),
+ cb
+ ),
+
+ /**
+ * Get info on specified Chain Core.
+ *
+ * @param {objectCallback} [callback] - Optional callback. Use instead of Promise return value as desired.
+ * @returns {Promise<CoreInfo>} Requested info of specified Chain Core.
+ */
+ info: (cb) => shared.tryCallback(
+ client.request('/info'),
+ cb
+ ),
+ }
+}
+
+module.exports = configAPI
--- /dev/null
+const shared = require('../shared')
+
+/**
+ * @class
+ * In order to issue or transfer asset units on a blockchain, a transaction is
+ * created in Chain Core and sent to the HSM for signing. The HSM signs the
+ * transaction without ever revealing the private key. Once signed, the
+ * transaction can be submitted to the blockchain successfully.
+ *
+ * More info: {@link https://chain.com/docs/core/build-applications/keys}
+ */
+class HsmSigner {
+
+ /**
+ * Create a new HSM signer object.
+ *
+ * @returns {HsmSigner}
+ */
+ constructor() {
+ this.signers = {}
+ }
+
+ /**
+ * addKey - Add a new key/signer pair to the HSM signer.
+ *
+ * @param {Object|String} key - An object with an xpub key, or an xpub as a string.
+ * @param {Connection} connection - Authenticated connection to a specific HSM instance.
+ * @returns {void}
+ */
+ addKey(key, connection) {
+ const id = `${connection.baseUrl}-${connection.token || 'noauth'}`
+ let signer = this.signers[id]
+ if (!signer) {
+ signer = this.signers[id] = {
+ connection: connection,
+ xpubs: []
+ }
+ }
+
+ signer.xpubs.push(typeof key == 'string' ? key : key.xpub)
+ }
+
+ /**
+ * sign - Sign a single transaction.
+ *
+ * @param {Object} template - A single transaction template.
+ * @param {objectCallback} [callback] - Optional callback. Use instead of Promise return value as desired.
+ * @returns {Object} Transaction template with all possible signatures added.
+ */
+ sign(template, cb) {
+ let promise = Promise.resolve(template)
+
+ // Return early if no signers
+ if (Object.keys(this.signers).length == 0) {
+ return shared.tryCallback(promise, cb)
+ }
+
+ for (let signerId in this.signers) {
+ const signer = this.signers[signerId]
+
+ promise = promise.then(nextTemplate =>
+ signer.connection.request('/sign-transaction', {
+ transactions: [nextTemplate],
+ xpubs: signer.xpubs
+ })
+ ).then(resp => resp[0])
+ }
+
+ return shared.tryCallback(promise, cb)
+ }
+
+ /**
+ * signBatch - Sign a batch of transactions.
+ *
+ * @param {Array<Object>} templates Array of transaction templates.
+ * @param {objectCallback} [callback] - Optional callback. Use instead of Promise return value as desired.
+ * @returns {BatchResponse} Tranasaction templates with all possible signatures
+ * added, as well as errors.
+ */
+ signBatch(templates, cb) {
+ templates = templates.filter((template) => template != null)
+ let promise = Promise.resolve(templates)
+
+ // Return early if no signers
+ if (Object.keys(this.signers).length == 0) {
+ return shared.tryCallback(promise.then(() => new shared.BatchResponse(templates)), cb)
+ }
+
+ let originalIndex = [...Array(templates.length).keys()]
+ const errors = []
+
+ for (let signerId in this.signers) {
+ const nextTemplates = []
+ const nextOriginalIndex = []
+ const signer = this.signers[signerId]
+
+ promise = promise.then(txTemplates =>
+ signer.connection.request('/sign-transaction', {
+ transactions: txTemplates,
+ xpubs: signer.xpubs
+ }).then(resp => {
+ const batchResponse = new shared.BatchResponse(resp)
+
+ batchResponse.successes.forEach((template, index) => {
+ nextTemplates.push(template)
+ nextOriginalIndex.push(originalIndex[index])
+ })
+
+ batchResponse.errors.forEach((error, index) => {
+ errors[originalIndex[index]] = error
+ })
+
+ originalIndex = nextOriginalIndex
+ return nextTemplates
+ })
+ )
+ }
+
+ return shared.tryCallback(promise.then(txTemplates => {
+ const resp = []
+ txTemplates.forEach((item, index) => {
+ resp[originalIndex[index]] = item
+ })
+
+ errors.forEach((error, index) => {
+ if (error != null) {
+ resp[index] = error
+ }
+ })
+
+ return new shared.BatchResponse(resp)
+ }), cb)
+ }
+}
+
+module.exports = HsmSigner
--- /dev/null
+const uuid = require('uuid')
+const shared = require('../shared')
+
+ /**
+ * Cryptographic private keys are the primary authorization mechanism on a
+ * blockchain. For development environments, Chain Core provides a convenient
+ * MockHSM.
+ *
+ * More info: {@link https://chain.com/docs/core/build-applications/keys}
+ *
+ * @typedef {Object} MockHsmKey
+ * @global
+ *
+ * @property {String} alias
+ * User specified, unique identifier of the key.
+ *
+ * @property {String} xpub
+ * Hex-encoded string representation of the key.
+ */
+
+/**
+ * API for interacting with {@link MockHsmKey MockHSM keys}.
+ *
+ * More info: {@link https://chain.com/docs/core/build-applications/keys}
+ * @module MockHsmKeysApi
+ */
+
+const mockHsmKeysAPI = (client) => {
+ return {
+ /**
+ * Create a new MockHsm key.
+ *
+ * @param {Object} [params={}] - Parameters for MockHSM key creation.
+ * @param {String} params.alias - User specified, unique identifier.
+ * @param {objectCallback} [callback] - Optional callback. Use instead of Promise return value as desired.
+ * @returns {Promise<MockHsmKey>} Newly created MockHSM key.
+ */
+ create: (params, cb) => {
+ let body = Object.assign({ clientToken: uuid.v4() }, params)
+ return shared.tryCallback(
+ client.request('/mockhsm/create-key', body).then(data => data),
+ cb
+ )
+ },
+
+ /**
+ * Get one page of MockHsm keys, optionally filtered to specified aliases.
+ *
+ * <b>NOTE</b>: The <code>filter</code> parameter of {@link Query} is unavailable for the MockHSM.
+ *
+ * @param {Object} params={} - Filter and pagination information.
+ * @param {Array.<string>} params.aliases - List of requested aliases, max 200.
+ * @param {Number} params.pageSize - Number of items to return in result set.
+ * @param {pageCallback} [callback] - Optional callback. Use instead of Promise return value as desired.
+ * @returns {Promise<Page<MockHsmKey>>} Requested page of results.
+ */
+ query: (params, cb) => {
+ if (Array.isArray(params.aliases) && params.aliases.length > 0) {
+ params.pageSize = params.aliases.length
+ }
+
+ return shared.query(client, 'mockHsm.keys', '/mockhsm/list-keys', params, {cb})
+ },
+
+ /**
+ * Request all MockHsm keys matching the specified query, calling the
+ * supplied processor callback with each item individually.
+ *
+ * <b>NOTE</b>: The <code>filter</code> parameter of {@link Query} is unavailable for the MockHSM.
+ *
+ * @param {Object} params={} - Filter and pagination information.
+ * @param {Array.<string>} params.aliases - List of requested aliases, max 200.
+ * @param {Number} params.pageSize - Number of items to return in result set.
+ * @param {QueryProcessor<MockHsmKey>} processor - Processing callback.
+ * @param {objectCallback} [callback] - Optional callback. Use instead of Promise return value as desired.
+ * @returns {Promise} A promise resolved upon processing of all items, or
+ * rejected on error.
+ */
+ queryAll: (params, processor, cb) => shared.queryAll(client, 'mockHsm.keys', params, processor, cb),
+ }
+}
+
+module.exports = mockHsmKeysAPI
--- /dev/null
+const shared = require('../shared')
+
+const uuid = require('uuid')
+
+/**
+ * Hardcoding value of (2 ** 63) - 1 since JavaScript rounds this value up,
+ * which causes issues when attempting to query TransactionFeed.
+ * @ignore
+ */
+const MAX_BLOCK_HEIGHT = '9223372036854775807'
+
+/**
+ * @class
+ * A single transaction feed that can be consumed. See {@link TransactionFeeds}
+ * for actions to create TransactionFeed objects.
+ *
+ * More info: {@link https://chain.com/docs/core/build-applications/real-time-transaction-processing}
+ *
+ * @property {String} id
+ * Unique transaction feed identifier.
+ *
+ * @property {String} alias
+ * User specified, unique identifier.
+ *
+ * @property {String} filter
+ * @property {String} after
+ */
+class TransactionFeed {
+ /**
+ * Called once for every item received via the transaction feed.
+ *
+ * @callback FeedProcessor
+ * @param {Object} item - Item to process.
+ * @param {function(Boolean)} next - Continue to the next item when it becomes
+ * available. Passing true to this callback
+ * will update the feed to acknowledge that
+ * the current item was consumed.
+ * @param {function(Boolean)} done - Terminate the processing loop. Passing
+ * true to this callback will update the
+ * feed to acknowledge that the current item
+ * was consumed.
+ * @param {function(Error)} fail - Terminate the processing loop due to an
+ * application-level error. This callback
+ * accepts an optional error argument. The
+ * feed will not be updated, and the current
+ * item will not be acknowledged.
+ */
+
+ /**
+ * Create a new transaction feed consumer.
+ *
+ * @param {Object} feed - API response from {@link module:TransactionFeedsApi}
+ * `create` or `get` call.
+ * @param {Client} client - Configured Chain client object
+ * @returns {TransactionFeed}
+ */
+ constructor(feed, client) {
+ this.id = feed['id']
+ this.alias = feed['alias']
+ this.after = feed['after']
+ this.filter = feed['filter']
+
+ let nextAfter
+
+ const ack = () => client.request('/update-transaction-feed', {
+ id: this.id,
+ after: nextAfter,
+ previousAfter: this.after
+ }).then(() => { this.after = nextAfter })
+
+ const query = params => client.transactions.query(params)
+
+ /**
+ * Process items returned from a transaction feed in real-time.
+ *
+ * @param {FeedProcessor} consumer - Called once with each item to do any
+ * desired processing. The callback can
+ * optionally choose to terminate the loop.
+ * @param {Number} [timeout=86400] - Number of seconds to wait before
+ * closing connection.
+ * @param {objectCallback} [callback] - Optional callback. Use instead of Promise return value as desired.
+ */
+ this.consume = (consumer, ...args) => {
+ let timeout = 24*60*60
+ let cb
+ switch (args.length) {
+ case 0:
+ // promise with default timeout
+ break
+ case 1:
+ if (args[0] instanceof Function) {
+ cb = args[0]
+ } else {
+ timeout = args[0]
+ }
+ break
+ case 2:
+ timeout = args[0]
+ cb = args[1]
+ break
+ default:
+ throw new Error('Invalid arguments')
+ }
+
+ const promise = new Promise((resolve, reject) => {
+ let queryArgs = {
+ filter: this.filter,
+ after: this.after,
+ timeout: (timeout * 1000),
+ ascendingWithLongPoll: true,
+ }
+
+ const nextPage = () => {
+ query(queryArgs).then(page => {
+ let index = 0
+ let prevItem
+
+ const done = shouldAck => {
+ let p
+ if (shouldAck) {
+ p = ack(prevItem)
+ } else {
+ p = Promise.resolve()
+ }
+ p.then(resolve).catch(reject)
+ }
+
+ const next = shouldAck => {
+ let p
+ if (shouldAck && prevItem) {
+ p = ack(prevItem)
+ } else {
+ p = Promise.resolve()
+ }
+
+ p.then(() => {
+ if (index >= page.items.length) {
+ queryArgs = page.next
+ nextPage()
+ return
+ }
+
+ prevItem = page.items[index]
+ nextAfter = `${prevItem.blockHeight}:${prevItem.position}-${MAX_BLOCK_HEIGHT}`
+ index++
+
+ // Pass the next item to the consumer, as well as three loop
+ // operations:
+ //
+ // - next(shouldAck): maybe ack, then continue/long-poll to next item.
+ // - done(shouldAck): maybe ack, then terminate the loop by fulfilling the outer promise.
+ // - fail(err): terminate the loop by rejecting the outer promise.
+ // Use this if you want to bubble an async error up to
+ // the outer promise catch function.
+ //
+ // The consumer can also terminate the loop by returning a promise
+ // that will reject.
+
+ let res = consumer(prevItem, next, done, reject)
+ if (res && typeof res.catch === 'function') {
+ res.catch(reject)
+ }
+ }).catch(reject) // fail consume loop on ack failure, or on thrown exceptions from "then" function
+ }
+
+ next()
+ }).catch(reject) // fail consume loop on query failure
+ }
+
+ nextPage()
+ })
+
+ return shared.tryCallback(promise, cb)
+ }
+ }
+}
+
+/**
+ * You can use transaction feeds to process transactions as they arrive on the
+ * blockchain. This is helpful for real-time applications such as notifications
+ * or live-updating interfaces.
+ *
+ * More info: {@link https://chain.com/docs/core/build-applications/real-time-transaction-processing}
+ * @module TransactionFeedsApi
+ */
+const transactionFeedsAPI = (client) => {
+ return {
+ /**
+ * Create a new transaction feed.
+ *
+ * @param {Object} params - Parameters for creating Transaction Feeds.
+ * @param {String} params.alias - A unique alias for the transaction feed.
+ * @param {String} params.filter - A valid filter string for the `/list-transactions`
+ * endpoint. The transaction feed will be composed of future
+ * transactions that match the filter.
+ * @param {objectCallback} [callback] - Optional callback. Use instead of Promise return value as desired.
+ * @returns {Promise<TransactionFeed>} Newly created transaction feed
+ */
+ create: (params, cb) => {
+ let body = Object.assign({ clientToken: uuid.v4() }, params)
+ return shared.tryCallback(
+ client.request('/create-transaction-feed', body).then(data => new TransactionFeed(data, client)),
+ cb
+ )
+ },
+
+ /**
+ * Get single transaction feed given an id/alias.
+ *
+ * @param {Object} params - Parameters to get single Transaction Feed.
+ * @param {String} params.id - The unique ID of a transaction feed. Either `id` or
+ * `alias` is required.
+ * @param {String} params.alias - The unique alias of a transaction feed. Either `id` or
+ * `alias` is required.
+ * @param {objectCallback} [callback] - Optional callback. Use instead of Promise return value as desired.
+ * @returns {Promise<TransactionFeed>} Requested transaction feed object
+ */
+ get: (params, cb) => shared.tryCallback(
+ client.request('/get-transaction-feed', params).then(data => new TransactionFeed(data, client)),
+ cb
+ ),
+
+ /**
+ * Delete a transaction feed given an id/alias.
+ *
+ * @param {Object} params - Parameters to delete single Transaction Feed.
+ * @param {String} params.id - The unique ID of a transaction feed. Either `id` or
+ * `alias` is required.
+ * @param {String} params.alias - The unique alias of a transaction feed. Either `id` or
+ * `alias` is required.
+ * @param {objectCallback} [callback] - Optional callback. Use instead of Promise return value as desired.
+ * @return {Promise} Promise resolved on success
+ */
+ delete: (params, cb) => shared.tryCallback(
+ client.request('/delete-transaction-feed', params).then(data => data),
+ cb
+ ),
+
+
+ /**
+ * Get one page of transaction feeds.
+ *
+ * @param {Object} params={} - Pagination information.
+ * @param {Number} params.pageSize - Number of items to return in result set.
+ * @param {pageCallback} [callback] - Optional callback. Use instead of Promise return value as desired.
+ * @returns {Promise<Page<TransactionFeed>>} Requested page of results.
+ */
+ query: (params, cb) => shared.query(client, 'transactionFeeds', '/list-transaction-feeds', params, {cb}),
+
+ /**
+ * Request all transaction feeds matching the specified query, calling the
+ * supplied processor callback with each item individually.
+ *
+ * @param {Object} params={} - Pagination information.
+ * @param {Number} params.pageSize - Number of items to return in result set.
+ * @param {QueryProcessor<TransactionFeed>} processor - Processing callback.
+ * @param {objectCallback} [callback] - Optional callback. Use instead of Promise return value as desired.
+ * @returns {Promise} A promise resolved upon processing of all items, or
+ * rejected on error.
+ */
+ queryAll: (params, processor, cb) => shared.queryAll(client, 'transactionFeeds', params, processor, cb),
+ }
+}
+
+module.exports = transactionFeedsAPI
--- /dev/null
+const shared = require('../shared')
+const errors = require('../errors')
+
+// TODO: replace with default handler in requestSingle/requestBatch variants
+function checkForError(resp) {
+ if ('code' in resp) {
+ throw errors.create(
+ errors.types.BAD_REQUEST,
+ errors.formatErrMsg(resp, ''),
+ {body: resp}
+ )
+ }
+ return resp
+}
+
+/**
+ * A blockchain consists of an immutable set of cryptographically linked
+ * transactions. Each transaction contains one or more actions.
+ *
+ * More info: {@link https://chain.com/docs/core/build-applications/transaction-basics}
+ * @typedef {Object} Transaction
+ * @global
+ *
+ * @property {String} id
+ * Unique transaction identifier.
+ *
+ * @property {String} timestamp
+ * Time of transaction, RFC3339 formatted.
+ *
+ * @property {String} blockId
+ * Unique identifier, or block hash, of the block containing a transaction.
+ *
+ * @property {Number} blockHeight
+ * Height of the block containing a transaction.
+ *
+ * @property {Number} position
+ * Position of a transaction within the block.
+ *
+ * @property {Object} referenceData
+ * User specified, unstructured data embedded within a transaction.
+ *
+ * @property {Boolean} isLocal
+ * A flag indicating one or more inputs or outputs are local.
+ *
+ * @property {TransactionInput[]} inputs
+ * List of specified inputs for a transaction.
+ *
+ * @property {TransactionOutput[]} outputs
+ * List of specified outputs for a transaction.
+ */
+
+/**
+ * @typedef {Object} TransactionInput
+ * @global
+ *
+ * @property {String} type
+ * The type of the input. Possible values are "issue", "spend".
+ *
+ * @property {String} assetId
+ * The id of the asset being issued or spent.
+ *
+ * @property {String} assetAlias
+ * The alias of the asset being issued or spent (possibly null).
+ *
+ * @property {Hash} assetDefinition
+ * The definition of the asset being issued or spent (possibly null).
+ *
+ * @property {Hash} assetTags
+ * The tags of the asset being issued or spent (possibly null).
+ *
+ * @property {Boolean} assetIsLocal
+ * A flag indicating whether the asset being issued or spent is local.
+ *
+ * @property {Integer} amount
+ * The number of units of the asset being issued or spent.
+ *
+ * @property {String} spentOutputId
+ * The id of the output consumed by this input. ID is nil if this is an issuance input.
+ *
+ * @property {String} accountId
+ * The id of the account transferring the asset (possibly null if the
+ * input is an issuance or an unspent output is specified).
+ *
+ * @property {String} accountAlias
+ * The alias of the account transferring the asset (possibly null if the
+ * input is an issuance or an unspent output is specified).
+ *
+ * @property {String} accountTags
+ * The tags associated with the account (possibly null).
+ *
+ * @property {String} issuanceProgram
+ * A program specifying a predicate for issuing an asset (possibly null
+ * if input is not an issuance).
+ *
+ * @property {Object} referenceData
+ * User specified, unstructured data embedded within an input (possibly null).
+ *
+ * @property {Boolean} isLocal
+ * A flag indicating if the input is local.
+ */
+
+/**
+ * Each new transaction in the blockchain consumes some unspent outputs and
+ * creates others. An output is considered unspent when it has not yet been used
+ * as an input to a new transaction. All asset units on a blockchain exist in
+ * the unspent output set.
+ *
+ * More info: {@link https://chain.com/docs/core/build-applications/unspent-outputs}
+ * @typedef {Object} TransactionOutput
+ * @global
+ *
+ * @property {String} id
+ * The id of the output.
+ *
+ * @property {String} type
+ * The type of the output. Possible values are "control" and "retire".
+ *
+ * @property {String} purpose
+ * The purpose of the output. Possible values are "receive" and "change".
+ *
+ * @property {Number} position
+ * The output's position in a transaction's list of outputs.
+ *
+ * @property {String} assetId
+ * The id of the asset being issued or spent.
+ *
+ * @property {String} assetAlias
+ * The alias of the asset being issued or spent (possibly null).
+ *
+ * @property {Hash} assetDefinition
+ * The definition of the asset being issued or spent (possibly null).
+ *
+ * @property {Hash} assetTags
+ * The tags of the asset being issued or spent (possibly null).
+ *
+ * @property {Boolean} assetIsLocal
+ * A flag indicating whether the asset being issued or spent is local.
+ *
+ * @property {Integer} amount
+ * The number of units of the asset being issued or spent.
+ *
+ * @property {String} accountId
+ * The id of the account transferring the asset (possibly null).
+ *
+ * @property {String} accountAlias
+ * The alias of the account transferring the asset (possibly null).
+ *
+ * @property {String} accountTags
+ * The tags associated with the account (possibly null).
+ *
+ * @property {String} controlProgram
+ * The control program which must be satisfied to transfer this output.
+ *
+ * @property {Object} referenceData
+ * User specified, unstructured data embedded within an input (possibly null).
+ *
+ * @property {Boolean} isLocal
+ * A flag indicating if the input is local.
+ */
+
+/**
+ * @class
+ * A convenience class for building transaction template objects.
+ */
+class TransactionBuilder {
+ /**
+ * constructor - return a new object used for constructing a transaction.
+ */
+ constructor() {
+ this.actions = []
+
+
+ /**
+ * If true, build the transaction as a partial transaction.
+ * @type {Boolean}
+ */
+ this.allowAdditionalActions = false
+
+ /**
+ * Base transaction provided by a third party.
+ * @type {Object}
+ */
+ this.baseTransaction = null
+ }
+
+ /**
+ * Add an action that issues assets.
+ *
+ * @param {Object} params - Action parameters.
+ * @param {String} params.assetId - Asset ID specifying the asset to be issued.
+ * You must specify either an ID or an alias.
+ * @param {String} params.assetAlias - Asset alias specifying the asset to be issued.
+ * You must specify either an ID or an alias.
+ * @param {String} params.amount - Amount of the asset to be issued.
+ */
+ issue(params) {
+ this.actions.push(Object.assign({}, params, {type: 'issue'}))
+ }
+
+ /**
+ * Add an action that controls assets with an account specified by identifier.
+ *
+ * @param {Object} params - Action parameters.
+ * @option params [String] :assetId Asset ID specifying the asset to be controlled.
+ * You must specify either an ID or an alias.
+ * @param {String} params.assetAlias - Asset alias specifying the asset to be controlled.
+ * You must specify either an ID or an alias.
+ * @param {String} params.accountId - Account ID specifying the account controlling the asset.
+ * You must specify either an ID or an alias.
+ * @param {String} params.accountAlias - Account alias specifying the account controlling the asset.
+ * You must specify either an ID or an alias.
+ * @param {Number} params.amount - Amount of the asset to be controlled.
+ */
+ controlWithAccount(params) {
+ this.actions.push(Object.assign({}, params, {type: 'control_account'}))
+ }
+
+ /**
+ * Add an action that controls assets with a receiver.
+ *
+ * @param {Object} params - Action parameters.
+ * @param {Object} params.receiver - The receiver object in which assets will be controlled.
+ * @param {String} params.assetId - Asset ID specifying the asset to be controlled.
+ * You must specify either an ID or an alias.
+ * @param {String} params.assetAlias - Asset alias specifying the asset to be controlled.
+ * You must specify either an ID or an alias.
+ * @param {Number} params.amount - Amount of the asset to be controlled.
+ */
+ controlWithReceiver(params) {
+ this.actions.push(Object.assign({}, params, {type: 'control_receiver'}))
+ }
+
+ /**
+ * Add an action that spends assets from an account specified by identifier.
+ *
+ * @param {Object} params - Action parameters.
+ * @param {String} params.assetId - Asset ID specifying the asset to be spent.
+ * You must specify either an ID or an alias.
+ * @param {String} params.assetAlias - Asset alias specifying the asset to be spent.
+ * You must specify either an ID or an alias.
+ * @param {String} params.accountId - Account ID specifying the account spending the asset.
+ * You must specify either an ID or an alias.
+ * @param {String} params.accountAlias - Account alias specifying the account spending the asset.
+ * You must specify either an ID or an alias.
+ * @param {Number} params.amount - Amount of the asset to be spent.
+ */
+ spendFromAccount(params) {
+ this.actions.push(Object.assign({}, params, {type: 'spend_account'}))
+ }
+
+ /**
+ * Add an action that spends an unspent output.
+ *
+ * @param {Object} params - Action parameters.
+ * @param {String} params.outputId - ID of the transaction output to be spent.
+ */
+ spendUnspentOutput(params) {
+ this.actions.push(Object.assign({}, params, {type: 'spend_account_unspent_output'}))
+ }
+
+ /**
+ * Add an action that retires units of an asset.
+ *
+ * @param {Object} params - Action parameters.
+ * @param {String} params.assetId - Asset ID specifying the asset to be retired.
+ * You must specify either an ID or an alias.
+ * @param {String} params.assetAlias - Asset alias specifying the asset to be retired.
+ * You must specify either an ID or an alias.
+ * @param {Number} params.amount - Amount of the asset to be retired.
+ */
+ retire(params) {
+ this.actions.push(Object.assign({}, params, {type: 'retire'}))
+ }
+
+ /**
+ * transactionReferenceData - Sets the transaction-level reference data. May
+ * only be used once per transaction.
+ *
+ * @param {Object} referenceData - User specified, unstructured data to
+ * be embedded in a transaction.
+ */
+ transactionReferenceData(referenceData) {
+ this.actions.push({
+ type: 'set_transaction_reference_data',
+ referenceData
+ })
+ }
+}
+
+/**
+ * API for interacting with {@link Transaction transactions}.
+ *
+ * More info: {@link https://chain.com/docs/core/build-applications/transaction-basics}
+ * @module TransactionsApi
+ */
+const transactionsAPI = (client) => {
+ /**
+ * Processing callback for building a transaction. The instance of
+ * {@link TransactionBuilder} modified in the function is used to build a transaction
+ * in Chain Core.
+ *
+ * @callback builderCallback
+ * @param {TransactionBuilder} builder
+ */
+
+ // TODO: implement finalize
+ const finalize = (template, cb) => shared.tryCallback(
+ Promise.resolve(template),
+ cb
+ )
+
+ // TODO: implement finalizeBatch
+ const finalizeBatch = (templates, cb) => shared.tryCallback(
+ Promise.resolve(new shared.BatchResponse(templates)),
+ cb
+ )
+
+ return {
+ /**
+ * Get one page of transactions matching the specified query.
+ *
+ * @param {Object} params={} - Filter and pagination information.
+ * @param {String} params.filter - Filter string, see {@link https://chain.com/docs/core/build-applications/queries}.
+ * @param {Array<String|Number>} params.filterParams - Parameter values for filter string (if needed).
+ * @param {Number} params.startTime - A Unix timestamp in milliseconds. When specified, only transactions with a block time greater than the start time will be returned.
+ * @param {Number} params.endTime - A Unix timestamp in milliseconds. When specified, only transactions with a block time less than the start time will be returned.
+ * @param {Number} params.timeout - A time in milliseconds after which a server timeout should occur. Defaults to 1000 (1 second).
+ * @param {Number} params.pageSize - Number of items to return in result set.
+ * @param {pageCallback} [callback] - Optional callback. Use instead of Promise return value as desired.
+ * @returns {Promise<Page<Transaction>>} Requested page of results.
+ */
+ query: (params, cb) => shared.query(client, 'transactions', '/list-transactions', params, {cb}),
+
+ /**
+ * Request all transactions matching the specified query, calling the
+ * supplied processor callback with each item individually.
+ *
+ * @param {Object} params={} - Filter and pagination information.
+ * @param {String} params.filter - Filter string, see {@link https://chain.com/docs/core/build-applications/queries}.
+ * @param {Array<String|Number>} params.filterParams - Parameter values for filter string (if needed).
+ * @param {Number} params.startTime - A Unix timestamp in milliseconds. When specified, only transactions with a block time greater than the start time will be returned.
+ * @param {Number} params.endTime - A Unix timestamp in milliseconds. When specified, only transactions with a block time less than the start time will be returned.
+ * @param {Number} params.timeout - A time in milliseconds after which a server timeout should occur. Defaults to 1000 (1 second).
+ * @param {Number} params.pageSize - Number of items to return in result set.
+ * @param {QueryProcessor<Transaction>} processor - Processing callback.
+ * @param {objectCallback} [callback] - Optional callback. Use instead of Promise return value as desired.
+ * @returns {Promise} A promise resolved upon processing of all items, or
+ * rejected on error.
+ */
+ queryAll: (params, processor, cb) => shared.queryAll(client, 'transactions', params, processor, cb),
+
+ /**
+ * Build an unsigned transaction from a set of actions.
+ *
+ * @param {module:TransactionsApi~builderCallback} builderBlock - Function that adds desired actions
+ * to a given builder object.
+ * @param {objectCallback} [callback] - Optional callback. Use instead of Promise return value as desired.
+ * @returns {Promise<Object>} Unsigned transaction template, or error.
+ */
+ build: (builderBlock, cb) => {
+ const builder = new TransactionBuilder()
+
+ try {
+ builderBlock(builder)
+ } catch (err) {
+ return Promise.reject(err)
+ }
+
+ return shared.tryCallback(
+ client.request('/build-transaction', [builder]).then(resp => checkForError(resp[0])),
+ cb
+ )
+ },
+
+ /**
+ * Build multiple unsigned transactions from multiple sets of actions.
+ *
+ * @param {Array<module:TransactionsApi~builderCallback>} builderBlocks - Functions that add desired actions
+ * to a given builder object, one
+ * per transaction.
+ * @param {objectCallback} [callback] - Optional callback. Use instead of Promise return value as desired.
+ * @returns {Promise<BatchResponse>} Batch of unsigned transaction templates, or errors.
+ */
+ buildBatch: (builderBlocks, cb) => {
+ const builders = []
+ for (let i in builderBlocks) {
+ const b = new TransactionBuilder()
+ try {
+ builderBlocks[i](b)
+ } catch (err) {
+ return Promise.reject(err)
+ }
+ builders.push(b)
+ }
+
+ return shared.createBatch(client, '/build-transaction', builders, {cb})
+ },
+
+ /**
+ * sign - Sign a single transaction.
+ *
+ * @param {Object} template - A single transaction template.
+ * @param {objectCallback} [callback] - Optional callback. Use instead of Promise return value as desired.
+ * @returns {Object} Transaction template with all possible signatures added.
+ */
+ sign: (template, cb) => finalize(template)
+ .then(finalized => client.signer.sign(finalized, cb)),
+
+ /**
+ * signBatch - Sign a batch of transactions.
+ *
+ * @param {Array<Object>} templates Array of transaction templates.
+ * @param {objectCallback} [callback] - Optional callback. Use instead of Promise return value as desired.
+ * @returns {BatchResponse} Tranasaction templates with all possible signatures
+ * added, as well as errors.
+ */
+ signBatch: (templates, cb) => finalizeBatch(templates)
+ // TODO: merge batch errors from finalizeBatch
+ .then(finalized => client.signer.signBatch(finalized.successes, cb)),
+
+ /**
+ * Submit a signed transaction to the blockchain.
+ *
+ * @param {Object} signed - A fully signed transaction template.
+ * @returns {Promise<Object>} Transaction ID of the successful transaction, or error.
+ */
+ submit: (signed, cb) => shared.tryCallback(
+ client.request('/submit-transaction', {transactions: [signed]}).then(resp => checkForError(resp[0])),
+ cb
+ ),
+
+ /**
+ * Submit multiple signed transactions to the blockchain.
+ *
+ * @param {Array<Object>} signed - An array of fully signed transaction templates.
+ * @returns {Promise<BatchResponse>} Batch response of transaction IDs, or errors.
+ */
+ submitBatch: (signed, cb) => shared.tryCallback(
+ client.request('/submit-transaction', {transactions: signed})
+ .then(resp => new shared.BatchResponse(resp)),
+ cb
+ ),
+ }
+}
+
+module.exports = transactionsAPI
--- /dev/null
+const shared = require('../shared')
+
+/**
+ * Each new transaction in the blockchain consumes some unspent outputs and
+ * creates others. An output is considered unspent when it has not yet been used
+ * as an input to a new transaction. All asset units on a blockchain exist in
+ * the unspent output set.
+ *
+ * More info: {@link https://chain.com/docs/core/build-applications/unspent-outputs}
+ * @typedef {Object} UnspentOutput
+ * @global
+ *
+ * @property {String} id
+ * Unique transaction identifier.
+ *
+ * @property {String} type
+ * The type of the output. Possible values are "control" and "retire".
+ *
+ * @property {String} purpose
+ * The purpose of the output. Possible values are "receive" and "change".
+ *
+ * @property {String} transactionId
+ * The transaction containing the output.
+ *
+ * @property {Number} position
+ * The output's position in a transaction's list of outputs.
+ *
+ * @property {String} assetId
+ * The id of the asset being issued or spent.
+ *
+ * @property {String} assetAlias
+ * The alias of the asset being issued or spent (possibly null).
+ *
+ * @property {Object} assetDefinition
+ * The definition of the asset being issued or spent (possibly null).
+ *
+ * @property {Object} assetTags
+ * The tags of the asset being issued or spent (possibly null).
+ *
+ * @property {Boolean} assetIsLocal
+ * A flag indicating whether the asset being issued or spent is local.
+ *
+ * @property {Number} amount
+ * The number of units of the asset being issued or spent.
+ *
+ * @property {String} accountId
+ * The id of the account transferring the asset (possibly null).
+ *
+ * @property {String} accountAlias
+ * The alias of the account transferring the asset (possibly null).
+ *
+ * @property {Object} accountTags
+ * The tags associated with the account (possibly null).
+ *
+ * @property {String} controlProgram
+ * The control program which must be satisfied to transfer this output.
+ *
+ * @property {Object} referenceData
+ * User specified, unstructured data embedded within an input (possibly null).
+ *
+ * @property {Boolean} isLocal
+ * A flag indicating if the input is local.
+ */
+
+/**
+ * API for interacting with {@link UnspentOutput unspent outputs}.
+ *
+ * More info: {@link https://chain.com/docs/core/build-applications/unspent-outputs}
+ * @module UnspentOutputsApi
+ */
+const unspentOutputsAPI = (client) => {
+ return {
+ /**
+ * Get one page of unspent outputs matching the specified query.
+ *
+ * @param {Object} params={} - Filter and pagination information.
+ * @param {String} params.filter - Filter string, see {@link https://chain.com/docs/core/build-applications/queries}.
+ * @param {Array<String|Number>} params.filterParams - Parameter values for filter string (if needed).
+ * @param {Integer} params.timestamp - A millisecond Unix timestamp. By using this parameter, you can perform queries that reflect the state of the blockchain at different points in time.
+ * @param {Number} params.pageSize - Number of items to return in result set.
+ * @param {pageCallback} [callback] - Optional callback. Use instead of Promise return value as desired.
+ * @returns {Promise<Page<UnspentOutput>>} Requested page of results.
+ */
+ query: (params, cb) => shared.query(client, 'unspentOutputs', '/list-unspent-outputs', params, {cb}),
+
+ /**
+ * Request all unspent outputs matching the specified query, calling the
+ * supplied processor callback with each item individually.
+ *
+ * @param {Object} params={} - Filter and pagination information.
+ * @param {String} params.filter - Filter string, see {@link https://chain.com/docs/core/build-applications/queries}.
+ * @param {Array<String|Number>} params.filterParams - Parameter values for filter string (if needed).
+ * @param {Integer} params.timestamp - A millisecond Unix timestamp. By using this parameter, you can perform queries that reflect the state of the blockchain at different points in time.
+ * @param {Number} params.pageSize - Number of items to return in result set.
+ * @param {QueryProcessor<UnspentOutput>} processor - Processing callback.
+ * @param {objectCallback} [callback] - Optional callback. Use instead of Promise return value as desired.
+ * @returns {Promise} A promise resolved upon processing of all items, or
+ * rejected on error.
+ */
+ queryAll: (params, processor, cb) => shared.queryAll(client, 'unspentOutputs', params, processor, cb),
+ }
+}
+
+module.exports = unspentOutputsAPI
--- /dev/null
+const Connection = require('./connection')
+const authorizationGrantsAPI = require('./api/authorizationGrants')
+const accessTokensAPI = require('./api/accessTokens')
+const accountsAPI = require('./api/accounts')
+const assetsAPI = require('./api/assets')
+const balancesAPI = require('./api/balances')
+const configAPI = require('./api/config')
+const hsmSigner = require('./api/hsmSigner')
+const mockHsmKeysAPI = require('./api/mockHsmKeys')
+const transactionsAPI = require('./api/transactions')
+const transactionFeedsAPI = require('./api/transactionFeeds')
+const unspentOutputsAPI = require('./api/unspentOutputs')
+
+/**
+ * The Chain API Client object is the root object for all API interactions.
+ * To interact with Chain Core, a Client object must always be instantiated
+ * first.
+ * @class
+ */
+class Client {
+ /**
+ * constructor - create a new Chain client object capable of interacting with
+ * the specified Chain Core.
+ *
+ * Passing a configuration object is the preferred way of calling this constructor.
+ * However, to support code written for 1.1 and older, the constructor supports passing
+ * in a string URL and an optional string token as the first and second parameter, respectively.
+ *
+ * @param {Object} opts - Plain JS object containing configuration options.
+ * @param {String} opts.url - Chain Core URL.
+ * @param {String} opts.accessToken - Chain Core access token.
+ * @returns {Client}
+ */
+ constructor(opts = {}) {
+ // If the first argument is a string,
+ // support the deprecated constructor params.
+ if (typeof opts === 'string') {
+ opts = {
+ url: arguments[0],
+ accessToken: arguments[1] || ''
+ }
+ }
+ opts.url = opts.url || 'http://localhost:9888'
+ this.connection = new Connection(opts.url, opts.accessToken, opts.agent)
+ this.signer = new hsmSigner()
+
+ /**
+ * API actions for access tokens
+ * @type {module:AccessTokensApi}
+ */
+ this.accessTokens = accessTokensAPI(this)
+
+ /**
+ * API actions for access control grants
+ * @type {module:AuthorizationGrantsApi}
+ */
+ this.authorizationGrants = authorizationGrantsAPI(this)
+
+ /**
+ * API actions for accounts
+ * @type {module:AccountsApi}
+ */
+ this.accounts = accountsAPI(this)
+
+ /**
+ * API actions for assets.
+ * @type {module:AssetsApi}
+ */
+ this.assets = assetsAPI(this)
+
+ /**
+ * API actions for balances.
+ * @type {module:BalancesApi}
+ */
+ this.balances = balancesAPI(this)
+
+ /**
+ * API actions for config.
+ * @type {module:ConfigApi}
+ */
+ this.config = configAPI(this)
+
+ /**
+ * @property {module:MockHsmKeysApi} keys API actions for MockHSM keys.
+ * @property {Connection} signerConnection MockHSM signer connection.
+ */
+ this.mockHsm = {
+ keys: mockHsmKeysAPI(this),
+ signerConnection: new Connection(`${opts.url}/mockhsm`, opts.accessToken, opts.agent)
+ }
+
+ /**
+ * API actions for transactions.
+ * @type {module:TransactionsApi}
+ */
+ this.transactions = transactionsAPI(this)
+
+ /**
+ * API actions for transaction feeds.
+ * @type {module:TransactionFeedsApi}
+ */
+ this.transactionFeeds = transactionFeedsAPI(this)
+
+ /**
+ * API actions for unspent outputs.
+ * @type {module:UnspentOutputsApi}
+ */
+ this.unspentOutputs = unspentOutputsAPI(this)
+ }
+
+
+ /**
+ * Submit a request to the stored Chain Core connection.
+ *
+ * @param {String} path
+ * @param {object} [body={}]
+ * @returns {Promise}
+ */
+ request(path, body = {}) {
+ return this.connection.request(path, body)
+ }
+}
+
+module.exports = Client
--- /dev/null
+// FIXME: Microsoft Edge has issues returning errors for responses
+// with a 401 status. We should add browser detection to only
+// use the ponyfill for unsupported browsers.
+const { fetch } = require('fetch-ponyfill')()
+const errors = require('./errors')
+const btoa = require('btoa')
+
+const blacklistAttributes = [
+ 'after',
+ 'asset_tags',
+ 'asset_definition',
+ 'account_tags',
+ 'next',
+ 'reference_data',
+ 'tags',
+]
+
+const snakeize = (object) => {
+ for(let key in object) {
+ let value = object[key]
+ let newKey = key
+
+ // Skip all-caps keys
+ if (/^[A-Z]+$/.test(key)) {
+ continue
+ }
+
+ if (/[A-Z]/.test(key)) {
+ newKey = key.replace(/([A-Z])/g, v => `_${v.toLowerCase()}`)
+ delete object[key]
+ }
+
+ if (typeof value == 'object' && blacklistAttributes.indexOf(newKey) == -1) {
+ value = snakeize(value)
+ }
+
+ object[newKey] = value
+ }
+
+ return object
+}
+
+const camelize = (object) => {
+ for (let key in object) {
+ let value = object[key]
+ let newKey = key
+
+ if (/_/.test(key)) {
+ newKey = key.replace(/([_][a-z])/g, v => v[1].toUpperCase())
+ delete object[key]
+ }
+
+ if (typeof value == 'object' && blacklistAttributes.indexOf(key) == -1) {
+ value = camelize(value)
+ }
+
+ object[newKey] = value
+ }
+
+ return object
+}
+
+/**
+ * @class
+ * Connection information for an instance of Chain Core.
+ */
+class Connection {
+ /**
+ * constructor - create a new Chain client object capable of interacting with
+ * the specified Chain Core.
+ *
+ * @param {String} baseUrl Chain Core URL.
+ * @param {String} token Chain Core client token for API access.
+ * @param {String} agent https.Agent used to provide TLS config.
+ * @returns {Client}
+ */
+ constructor(baseUrl, token = '', agent) {
+ this.baseUrl = baseUrl
+ this.token = token || ''
+ this.agent = agent
+ }
+
+ /**
+ * Submit a request to the specified Chain Core.
+ *
+ * @param {String} path
+ * @param {object} [body={}]
+ * @returns {Promise}
+ */
+ request(path, body = {}) {
+ if (!body) {
+ body = {}
+ }
+
+ // Convert camelcased request body field names to use snakecase for API
+ // processing.
+ const snakeBody = snakeize(body) // Ssssssssssss
+
+ let req = {
+ method: 'POST',
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+
+ // TODO(jeffomatic): The Fetch API has inconsistent behavior between
+ // browser implementations and polyfills.
+ //
+ // - For Edge: we can't use the browser's fetch API because it doesn't
+ // always returns a WWW-Authenticate challenge to 401s.
+ // - For Safari/Chrome: using fetch-ponyfill (the polyfill) causes
+ // console warnings if the user agent string is provided.
+ //
+ // For now, let's not send the UA string.
+ //'User-Agent': 'chain-sdk-js/0.0'
+ },
+ body: JSON.stringify(snakeBody)
+ }
+
+ if (this.token) {
+ req.headers['Authorization'] = `Basic ${btoa(this.token)}`
+ }
+
+ if (this.agent) {
+ req.agent = this.agent
+ }
+
+ return fetch(this.baseUrl + path, req).catch((err) => {
+ throw errors.create(
+ errors.types.FETCH,
+ 'Fetch error: ' + err.toString(),
+ {sourceError: err}
+ )
+ }).then((resp) => {
+ if (resp.status == 204) {
+ return { status: 204 }
+ }
+
+ return resp.json().catch(() => {
+ throw errors.create(
+ errors.types.JSON,
+ 'Could not parse JSON response',
+ {response: resp, status: resp.status}
+ )
+ }).then((body) => {
+ if (resp.status / 100 == 2) {
+ return body
+ }
+
+ // Everything else is a status error.
+ let errType = null
+ if (resp.status == 401) {
+ errType = errors.types.UNAUTHORIZED
+ } else if (resp.status == 404) {
+ errType = errors.types.NOT_FOUND
+ } else if (resp.status / 100 == 4) {
+ errType = errors.types.BAD_REQUEST
+ } else {
+ errType = errors.types.SERVER
+ }
+
+ throw errors.create(
+ errType,
+ errors.formatErrMsg(body, null),
+ {
+ response: resp,
+ status: resp.status,
+ body: body,
+ requestId: null
+ }
+ )
+ }).then((body) => {
+ // After processing the response, convert snakecased field names to
+ // camelcase to match language conventions.
+ return camelize(body)
+ })
+ })
+ }
+}
+
+Connection.snakeize = snakeize
+Connection.camelize = camelize
+
+module.exports = Connection
--- /dev/null
+const lib = {
+ create: function(type, message, props = {}) {
+ let err
+ if (props.body) {
+ err = lib.newBatchError(props.body, props.requestId)
+ } else {
+ err = new Error(message)
+ }
+
+ err = Object.assign(err, props, {
+ chainClientError: true,
+ type: type,
+ })
+ return err
+ },
+
+ isChainError: function(err) {
+ return err && !!err.chainClientError
+ },
+
+ isBatchError: function (v) {
+ return v && v.code && !v.stack
+ },
+
+ newBatchError: function (body, requestId = false) {
+ let err = new Error(lib.formatErrMsg(body, requestId))
+ err.code = body.code
+ err.chainMessage = body.message
+ err.detail = body.detail
+ err.requestId = requestId
+ err.resp = body.resp
+ return err
+ },
+
+ // TODO: remove me in favor of ErrorBanner.jsx rendering
+ formatErrMsg: function(body, requestId) {
+ let tokens = []
+
+ if (typeof body.code === 'string' && body.code.length > 0) {
+ tokens.push('Code: ' + body.code)
+ }
+
+ tokens.push('Message: ' + body.message)
+
+ if (typeof body.detail === 'string' && body.detail.length > 0) {
+ tokens.push('Detail: ' + body.detail)
+ }
+
+ if (requestId) {
+ tokens.push('Request-ID: ' + requestId)
+ }
+
+ return tokens.join(' ')
+ },
+
+ types: {
+ FETCH: 'FETCH',
+ CONNECTIVITY: 'CONNECTIVITY',
+ JSON: 'JSON',
+ UNAUTHORIZED: 'UNAUTHORIZED',
+ NOT_FOUND: 'NOT_FOUND',
+ BAD_REQUEST: 'BAD_REQUEST',
+ SERVER_ERROR: 'SERVER_ERROR',
+ }
+}
+
+module.exports = lib
--- /dev/null
+const Client = require('./client')
+const Connection = require('./connection')
+
+module.exports = {
+ Client,
+ Connection,
+}
--- /dev/null
+/**
+ * @callback pageCallback
+ * @param {error} error
+ * @param {Page} page - Requested page of results.
+ */
+
+/**
+ * @class
+ * One page of results returned from an API request. With any given page object,
+ * the next page of results in the query set can be requested.
+ */
+class Page {
+
+ /**
+ * Create a page object
+ *
+ * @param {Object} data API response for a single page of data.
+ * @param {Client} client Chain Client.
+ * @param {String} memberPath key-path pointing to module implementing the
+ * desired `query` method.
+ */
+ constructor(data, client, memberPath) {
+ /**
+ * Array of Chain Core objects. Available types are documented in the
+ * {@link global global namespace}.
+ *
+ * @type {Array}
+ */
+ this.items = []
+
+ /**
+ * Object representing the query for the immediate next page of results. Can
+ * be passed without modification to the `query` method that generated the
+ * Page object containing it.
+ * @type {Object}
+ */
+ this.next = {}
+
+
+ /**
+ * Indicator that there are more results to load if true.
+ * @type {Boolean}
+ */
+ this.lastPage = false
+
+ Object.assign(this, data)
+
+ this.client = client
+ this.memberPath = memberPath
+ }
+
+ /**
+ * Fetch the next page of data for the query specified in this object.
+ *
+ * @param {objectCallback} [callback] - Optional callback. Use instead of Promise return value as desired.
+ * @returns {Promise<Page>} A promise resolving to a Page object containing
+ * the requested results.
+ */
+ nextPage(cb) {
+ let queryOwner = this.client
+ this.memberPath.split('.').forEach((member) => {
+ queryOwner = queryOwner[member]
+ })
+
+ return queryOwner.query(this.next, cb)
+ }
+}
+
+module.exports = Page
--- /dev/null
+const uuid = require('uuid')
+const errors = require('./errors')
+const Page = require('./page')
+
+/**
+ * @callback objectCallback
+ * @param {error} error
+ * @param {Object} object - Object response from API.
+ */
+
+/**
+ * @callback batchCallback
+ * @param {error} error
+ * @param {BatchResponse} batchResponse - Newly created objects (and errors).
+ */
+
+/**
+ * Called once for each item in the result set.
+ *
+ * @callback QueryProcessor
+ * @param {Object} item - Item to process.
+ * @param {function} next - Call to proceed to the next item for processing.
+ * @param {function(err)} done - Call to terminate iteration through the result
+ * set. Accepts an optional error argument which
+ * will be passed to the promise rejection or
+ * callback depending on async calling style.
+ */
+
+ /**
+ * @typedef {Object} Key
+ * @global
+ *
+ * @property {String} rootXpub
+ * @property {String} accountXpub
+ * @property {String[]} accountDerivationPath
+ */
+
+/**
+ * @class
+ */
+class BatchResponse {
+ /**
+ * constructor
+ *
+ * @param {Array<Object>} resp - List of items which are objects or errors.
+ */
+ constructor(resp) {
+ /**
+ * Items from the input array which were successfully processed. This value
+ * is a sparsely populated array, maintaining the indexes of the items as
+ * they were originally submitted.
+ * @type {Array<Object>}
+ */
+ this.successes = []
+
+ /**
+ * Items from the input array which resulted in an error. This value
+ * is a sparsely populated array, maintaining the indexes of the items as
+ * they were originally submitted.
+ * @type {Array<Object>}
+ */
+ this.errors = []
+
+ resp.forEach((item, index) => {
+ if (item.code) {
+ this.errors[index] = item
+ } else {
+ this.successes[index] = item
+ }
+ })
+
+ /**
+ * Original input array
+ * @type {Array<Object>}
+ */
+ this.response = resp
+ }
+}
+
+const tryCallback = (promise, cb) => {
+ if (typeof cb !== 'function') return promise
+
+ return promise.then(value => {
+ setTimeout(() => cb(null, value), 0)
+ }, error => {
+ setTimeout(() => cb(error, null), 0)
+ })
+}
+
+const batchRequest = (client, path, params, cb) => {
+ return tryCallback(
+ client.request(path, params).then(resp => new BatchResponse(resp)),
+ cb
+ )
+}
+
+module.exports = {
+ batchRequest,
+
+ singletonBatchRequest: (client, path, params = {}, cb) => {
+ return tryCallback(
+ batchRequest(client, path, [params]).then(batch => {
+ if (batch.errors[0]) {
+ throw errors.newBatchError(batch.errors[0])
+ }
+ return batch.successes[0]
+ }),
+ cb
+ )
+ },
+
+ create: (client, path, params = {}, opts = {}) => {
+ const object = Object.assign({ clientToken: uuid.v4() }, params)
+ let body = object
+ if (!opts.skipArray) {
+ body = [body]
+ }
+
+ return tryCallback(
+ client.request(path, body).then(data => {
+ if (errors.isBatchError(data[0])) throw errors.newBatchError(data[0])
+
+ if (Array.isArray(data)) return data[0]
+ return data
+ }),
+ opts.cb
+ )
+ },
+
+ createBatch: (client, path, params = [], opts = {}) => {
+ params = params.map((item) =>
+ Object.assign({ clientToken: uuid.v4() }, item))
+
+ return tryCallback(
+ client.request(path, params).then(resp => new BatchResponse(resp)),
+ opts.cb
+ )
+ },
+
+ query: (client, memberPath, path, params = {}, opts = {}) => {
+ return tryCallback(
+ client.request(path, params).then(data => new Page(data, client, memberPath)),
+ opts.cb
+ )
+ },
+
+ /*
+ * NOTE: Requires query to be implemented on client for the specified member.
+ */
+ queryAll: (client, memberPath, params, processor = () => {}, cb) => {
+ let nextParams = params
+
+ let queryOwner = client
+ memberPath.split('.').forEach((member) => {
+ queryOwner = queryOwner[member]
+ })
+
+ const promise = new Promise((resolve, reject) => {
+ const done = (err) => {
+ if (cb) {
+ cb(err)
+ return
+ } else if (err) {
+ reject(err)
+ }
+
+ resolve()
+ }
+
+ const nextPage = () => {
+ queryOwner.query(nextParams).then(page => {
+ let index = 0
+ let item
+
+ const next = () => {
+ if (index >= page.items.length) {
+ if (page.lastPage) {
+ done()
+ } else {
+ nextParams = page.next
+ nextPage()
+ }
+ return
+ }
+
+ item = page.items[index]
+ index++
+
+ // Pass the next item to the processor, as well as two loop
+ // operations:
+ //
+ // - next(): Continue to next item
+ // - done(err): Then terminate the loop by fulfilling the outer promise
+ //
+ // The process can also terminate the loop by returning a promise
+ // that will reject.
+
+ let res = processor(item, next, done)
+ if (res && typeof res.catch === 'function') {
+ res.catch(reject)
+ }
+ }
+
+ next()
+ }).catch(reject) // fail processor loop on query failure
+ }
+
+ nextPage()
+ })
+
+ return tryCallback(promise, cb)
+ },
+
+ tryCallback,
+ BatchResponse,
+}
--- /dev/null
+const x509SubjectAttributes = {
+ C: {array: true},
+ O: {array: true},
+ OU: {array: true},
+ L: {array: true},
+ ST: {array: true},
+ STREET: {array: true},
+ POSTALCODE: {array: true},
+ SERIALNUMBER: {array: false},
+ CN: {array: false},
+}
+
+const sanitizeX509GuardData = guardData => {
+ const keys = Object.keys(guardData)
+ if (keys.length !== 1 || keys[0].toLowerCase() !== 'subject') {
+ throw new Error('X509 guard data must contain exactly one key, "subject"')
+ }
+
+ const newSubject = {}
+ const oldSubject = guardData[keys[0]]
+ for (let k in oldSubject) {
+ const attrib = x509SubjectAttributes[k.toUpperCase()]
+ if (!attrib) {
+ throw new Error(`X509 guard data contains invalid subject attribute: ${k}`)
+ }
+
+ let v = oldSubject[k]
+ if (!attrib.array && Array.isArray(v)) {
+ throw new Error(`X509 guard data contains invalid array for attribute ${k}: ${v.toString()}`)
+ } else if (attrib.array && !Array.isArray(v)) {
+ newSubject[k] = [v]
+ } else {
+ newSubject[k] = v
+ }
+ }
+
+ return {subject: newSubject}
+}
+
+module.exports = {
+ sanitizeX509GuardData,
+}
/* global process */
-import chainSdk from 'chain-sdk'
+import chainSdk from 'sdk'
import { store } from 'app'
import { useRouterHistory } from 'react-router'
apiHost = window.location.origin
basename = '/dashboard'
} else {
- apiHost = process.env.API_URL || 'http://localhost:3000/api'
+ apiHost = process.env.API_URL || 'http://localhost:9888'
basename = ''
}