Burrow has a JavaScript API for communicating with a Hyperledger Burrow server, which implements the GRPC spec.
- Burrow version 0.20 or higher
- Node.js version 7 or higher
You can check the installed version of Node.js with the command:
$ node --version
If your distribution of Linux has a version older than 6 then you can update it.
$ yarn install @hyperledger/burrow
You will need to know the : of the burrow instance you wish to connect to. If running locally this will be 'localhost' and the default port, which is '10997'. DO NOT INCLUDE A PROTOCOL.
The main class is Burrow
. A standard Burrow
instance is created like this:
const monax = require('@monax/burrow');
var burrowURL = "<IP address>:<PORT>"; // localhost:10997 if running locally on default port
var account = 'ABCDEF01234567890123'; // address of the account to use for signing, hex string representation
var options = {objectReturn: true};
var burrow = monax.createInstance(burrowURL, account, options);
The parameters for createInstance
is the server URL as a string or as an object {host:<IP Address>, port:<PORT>}
. An account in the form of a hex-encoded address must be provided.
Note: the instance of burrow you are connecting to must have the associated key (if you want local signing you should be running a local node of burrow. Other local signing options might be made available at a later point).
And finally an optional options object. Allowed options are:
- objectReturn: If True, communicating with contracts an object returns an object of the form:
{values:{...}, raw:[]}
where the values objects attempts to name the returns based on the abi and the raw is the decoded array of return values. If False just the array of decoded return values is returned.
There are bindings for all the GRPC methods. All functions are on the form function(param1, param2, ... [, callback])
, where the callback is a function on the form function(error, data)
. The data
object is the same as you would get by calling the corresponding RPC method directly. If no callback is provided, a promise will be returned instead. If calling a response streaming GRPC call, the callback is not optional and will be called with data
anytime it is recieved.
The structure of the library is such that there are lower-level access to the GRPC services and higher level wrappers of these services. The structure of the library is outlined below
The table below links to the reference schema for either the protobuf files governing the component or the Javascript interface.
Component Name | Accessor |
---|---|
Transactions | Burrow.transact |
Queries | Burrow.query |
EventStream | Burrow.eventStream |
Events | Burrow.events |
NameReg | Burrow.namereg |
| Contracts | Burrow.contracts |
Burrow provides access to three GRPC services; Transactions, Queries, and ExecutionEvents in the form of automatically generated code endpoints. Below details how to access these methods and links to the request and return objects defined in the protobuf specs. The format for all calls is function(object[, callback])
The callback is optional for non-streaming endpoints in which case a promise will be returned.
In Burrow an account must already exist in order to be used as a input account (the sender of a transaction). An account can be created once the chain is running (accounts at genesis can be described in the genesis document in the accounts section) in the following ways:
- Issuing a
SendTx
to the address to be created (see below) - where the input account must have both theSend
andCreateAccount
permission. - Sending value to the address to created from a smart contract using the CALL opcode - where the caller contract must have the
CreateAccount
permission. - Issuing a
GovTx
where accounts can be created/updated in bulk provided the input has theRoot
permission.
The conventional way to create an new account to use as an input for transactions is the following:
First create a key - you will want to create an account for which you have access to the private key controlling that account (as defined by the address of the public key):
# Create a new key against the key store of a locally running Burrow (or burrow keys standalone server):
$ address=$(burrow keys gen -n --name NewKey)
# The address will be printed to stdout so the above captures it in $address, you can also list named keys:
$ burrow keys list
Address:"6075EADD0C7A33EE6153F3FA1B21E4D80045FCE2" KeyName:"NewKey"
Note creating the key does not create the account - it just generates a key pair in your local key store (it is not in anyway known the blockchain network).
Now we would like to use a SendTx
to create the address, here's how to do that in Javscript:
// Using account and burrow defined in snippet from [Usage](#usage)
// Address we want to create
var addressToCreate = "6075EADD0C7A33EE6153F3FA1B21E4D80045FCE2"
// The amount we send is arbitrary
var amount = 20
client.transact.SendTxSync(
{
Inputs: [{
Address: Buffer.from(account, 'hex'),
Amount: amount
}],
Outputs: [{
Address: Buffer.from(addressToCreate, 'hex'),
Amount: amount
}]
})
.then(txe => console.log(txe))
.catch(err => console.error(err))
The return txe
(short for TxExecution
) logged to the console in the then
block contains the history and fingerprint of the SendTx
execution. You can see an example of this in basic app.
Here is an example of usage in setting and getting a name:
var setPayload = {
Input: {
Address: Buffer.from(account, 'hex'),
Amount: 50000
},
Name: "DOUG",
Data: "Marmot",
Fee: 5000
}
var getPayload = {Name: "DOUG"}
// Using a callback
client.transact.NameTxSync(setPayload, function (error, data) {
if (error) throw error; // or something more sensible
// data object contains detailed information of the transaction execution.
// Get a name this time using a promise
client.query.GetName(getPayload)
.then((data) => {
console.log(data);
}) // should print "Marmot"
.catch((error) => {
throw error;
})
})
burrow.transact
provides access to the burrow GRPC service rpctransact
. As a GRPC wrapper all the endpoints take a data argument and an optional callback. The format of the data object is specified in the rpctransact protobuf file. A note on RPC naming, any method which ends in Sync
will wait until the transaction generated is included in a block. Any Async
method will return a receipt of the transaction immediately but does not guarantee it has been included. Sim
methods request that the transaction be simulated and the result returned as if it had been executed. SIMULATED CALLS DO NOT GET COMMITTED AND DO NOT CHANGE STATE.
Method | Passed | Returns |
---|---|---|
burrow.transact.BroadcastTxSync | TxEnvelopeParam | TxExecution |
burrow.transact.BroadcastTxASync | TxEnvelopeParam | Receipt |
burrow.transact.SignTx | TxEnvelopeParam | TxEnvelope |
burrow.transact.FormulateTx | PayloadParam | TxEnvelope |
burrow.transact.CallTxSync | CallTx | TxExecution |
burrow.transact.CallTxAsync | CallTx | Receipt |
burrow.transact.CallTxSim | CallTx | TxExecution |
burrow.transact.SendTxSync | SendTx | TxExecution |
burrow.transact.SendTxAsync | SendTx | Receipt |
burrow.transact.NameTxSync | NameTx | TxExecution |
burrow.transact.NameTxAsync | NameTx | Receipt |
Burrow.query
provides access to the burrow GRPC service rpcquery
. As a GRPC wrapper all the endpoints take a data argument and an optional callback. The format of the data object is specified in the protobuf files. Note that "STREAM" functions take a callback function(error, data)
which is mandatory and is called any time data is returned. For list Accounts the queryable tags are Address, PublicKey, Sequence, Balance, Code, Permissions (Case sensitive). As an example you can get all accounts with a balance greater than 1000 by burrow.query.ListAccounts('Balance > 1000', callback)
. Multiple tag criteria can be combined using 'AND' and 'OR' for an example of a combined query see here. Similarly for ListNames, the avaible tags are Name, Data, Owner and Exires (once again case sensitive) use is identical to List accounts.
Method | Passed | Returns | Notes |
---|---|---|---|
burrow.query.GetAccount | GetAccountParam | ConcreteAccount | |
burrow.query.ListAccounts | ListAccountsParam | ConcreteAccount | STREAM |
burrow.query.GetNameParam | GetNameParam | Entry | |
burrow.query.ListNames | ListNamesParam | Entry | STREAM |
NB: When listening to contract events it is easier to use the contract interface (described below)
Burrow.executionEvents
provides access to the burrow GRPC service ExecutionEvents
. As a GRPC wrapper all the endpoints take a data argument and an optional callback. The format of the data object is specified in the protobuf files. Note that "STREAM" functions take a callback function(error, data)
which is mandatory and is called any time data is returned.
Method | Passed | Returns | Notes |
---|---|---|---|
burrow.executionEvents.GetBlock | GetBlockRequest | BlockExecution | |
burrow.executionEvents.GetBlocks | BlocksRequest | BlockExecution | STREAM |
burrow.executionEvents.GetTx | GetTxRequest | TxExecution | |
burrow.executionEvents.GetTxs | BlocksRequest | GetTxsResponse | STREAM |
burrow.executionEvents.GetEvents | BlocksRequest | GetEventsResponse | STREAM |
In addition to direct access to the grpc services, the burrow object also provides access to three higher level components which wrap the low level access for convenience. These are .namereg
, .events
, and .contracts
. All high level components use the account provided during creation of the burrow instance for constructing transactions. Component contracts
is the most important of the three, as events and namereg are really just helpful wrappers.
burrow.namereg
is a convenience wrapper for setting and getting entries from the name registry.
burrow.namereg.get(name[,callback])
Gets an entry stored at the name. It returns a promise if callback not provided.
String
- Name you wish to retrieve from the nameregfunction
- (optional) Function to call upon completion of formfunction(error, data)
.
Object
- The return data object is of the form:
{
Name: (registered name) (string)
Owner: (address of name owner) (buffer)
Data: (stored data) (string)
Expires: (block at which entry expires) (int)
}
burrow.namereg.set(name, data, lease[, callback])
Sets an entry in the namereg. It returns a promise if callback not provided.
String
- The name you wish to registerString
- The string data you wish to store at the registered name (longer string = larger fee)int
- The number of blocks to register the name for (more blocks = larger fee)function
- (optional) Function to call upon completion of formfunction(error, data)
.
TxExecution
- The return data object is a TxExecution.
// Using a callback
client.namereg.set("DOUG", "Marmot", 5000, function (error, data) {
if (error) throw error; // or something more sensible
// data object contains detailed information of the transaction execution.
// Get a name this time using a promise
client.namereg.get("DOUG")
.then((data) => {
console.log(data);
}) // Should print "Marmot"
.catch((error) => {
throw error;
})
})
Note: this example is nearly identical to the example above except that the objects are not explicitly constructed by you.
burrow.events
contains convenience wrappers for streaming executionEvents.
burrow.events.listen(query, options, callback)
Listens to execution events which satisfy the filter query.
String
- a pegjs querystring for filtering the returned events see here for grammar specificationObject
- Currently unused. pass{}
function
- Signature offunction(error, data)
mandatory
GetEventsResponse
- The return data object is a GetEventsResponse
burrow.events.subContractEvents(address, signature, options, callback)
Listens to EVM event executions from specific contract.
String
- hex string of the contract address of interestString
- event abi signatureObject
- Currently unused. pass{}
function
- Signature offunction(error, data)
mandatory.
GetEventsResponse
- The return data object is a GetEventsResponse
burrow.contracts
is arguably the most important component of the burrow it exposes two functions, .deploy
and .new
both of which return a Contract interface object (sometimes refered to as contract object). The difference between them is that new
simply creates an interface to a contract and deploy
will first create an instance and then deploy a copy of it to the blockchain.
burrow.contracts.deploy(abi, bytecode, params... [, callback])
Deploys a contract and returns a contract interface either to the callback or a promise once deploy is successful. It returns a promise if callback not provided.
When the contract interface object is created via deploy, the default address is set to the address of the deployed contract (which can be accessed as contract.address). This interface object can still be used as a generic interface but care must be taken to use the .at()
and .atSim()
versions of functions.
Object
- the object corresponding to the json ABI of the contract you wish to interface with.String
- Hex encoded string of bytecode of the contract to deployparams
- arguments to the constructor function (if there are any)function
- (optional) Format offunction(error, contract)
where contract is the contract interface object.
Object
- The return data object is a contract interface, which refers to the contract which is deployed at contract.address
. (This functionality used to be called new
.)
burrow.contracts.address(address)
Returns a new contract interface object, without having to pass in the ABI. The ABI is retrieved from burrow; the contract must have been deployed with burrow deploy 0.28.0 or later.
String
- Hex encoded address of the default contract you want the interface to access
Object
- The return data object is a contract interface.
burrow.contracts.new(abi, [bytecode[, address]])
Returns a new contract interface object. All you really need to create an interface is the abi, however you can also include the bytecode of the contract. If you do so you can create new contracts of this type by calling contract._constructor(...)
which will deploy a new contract and return its address. If you provide an address, then this will be the default contract address used however you can also omit this at be sure to use the .at()
and .atSim()
versions of functions. Also note you must provide bytecode is you wish to provide address, though bytecode argument can be null.
Object
- the object corresponding to the json ABI of the contract you wish to interface with.String
- Hex encoded string of bytecode of the contract to deployString
- (optional) Hex encoded address of the default contract you want the interface to access
Object
- The return data object is a contract interface.
The contract interface object allows for easy access of solidity contract function calls and subscription to events. When created, javascript functions for all functions specified in the abi are generated. All of these functions have the same form Contract.functionname(params...[, callback])
, where params
are the arguments to the contract constructor. Arguments of the "bytes" type should be properly hex encoded before passing, to avoid improper encoding. If a callback is not provided a promise is returned.
Note: if the burrow object was created with
{objectReturn: True}
the return from these function calls is formatted as{values:{...}, raw:[]}
otherwise an array of decoded values is provided. The values object names the decoded values according to the abi spec, if a return value has no name it won't be included in the values object and must be retrieved from its position on the raw array.
In the case of a REVERT op-code being called in the contract function call, an error will be passed with the revert string as the .message
field. These errors can be distinguished from other errors as the .code
field will be ERR_EXECUTION_REVERT
.
In addition to the standard function call, there are three other forms: contract.functionname.sim
, contract.functionname.at
, contract.functionname.atSim
.
contract.functionname.sim(params... [, callback])
The "Sim" forms will force a simulated call so that does not change state. Although, the data returned is identical to what would have been returned if the call had been submitted. Useful for querying data or checking if a transaction passes some tests.
params
- the arguments to the function (if there are any)function
- (optional) Function to call upon completion of formfunction(error, data)
.
contract.functionname.at(address, params... [, callback])
The "at" forms allow you to specify which contract you wish to submit the transaction to. This allows you to use a single contract interface instance to access any contract with the same abi. Useful if for example there is a factory contract on the chain and you wish to connect to any of its children. The at forms MUST be used if a default address was not provided or created.
String
- Hex encoded address of the default contract you want the interface to accessparams
- the arguments to the function (if there are any)function
- (optional) Function to call upon completion of formfunction(error, data)
.
contract.functionname.at(address, params... [, callback])
String
- Hex encoded address of the default contract you want the interface to accessparams
- the arguments to the function (if there are any)function
- (optional) Function to call upon completion of formfunction(error, data)
contract._constructor(params... [, callback])
Deploys a new contract from the same interface (no need to create a new interface object via .deploy). Once completed it will return the created contract's address.
params
- the arguments to the function (if there are any)function
- (optional) Function to call upon completion of formfunction(error, data)
.
String
- The return data String is the created contract's address.
Occassionally you may wish to encode the parameters to a function call but not actually make the call. The most common use of this is in forwarding contracts which take pre-encoded arguments along with function signature bytes and then call another function passing that data for specifying the call.
The Contract interface object supports this use case through Contract.functionname.encode(...args)
which will return a hex string with the encoded arguments. This functionality is also available through Monax.utils.encode(abi, functionname, ...args)
. In addition the complement also exists, Contract.functionname.decode(data)
will produce the return object as if the data was just returned from a call.
contract.eventname(callback)
The contract interface object exposes subscription to Solidity events under event's name. where the provided callback with be passed an error and data of the form:
function
- Function to call upon completion of formfunction(error, data)
. The data object has the following form:
{
event: [fulleventname],
address: [address of contract emitting event],
args: {argname: argvalue}
}
contract.eventname.at(address, callback)
Similarly to functions' contract it is possible to start listening to a non-default contract address.
String
- hex string of the contract address of interestfunction
- Function to call upon completion of formfunction(error, data)
. The data object has the following form:
{
event: [fulleventname],
address: [address of contract emitting event],
args: {argname: argvalue}
}
The following contract is a simple contract which takes a "name" to the constructor and also has a function getName
which returns the name.
pragma solidity ^0.4.18;
contract SimpleContract {
string private name;
function SimpleContract(string _newName) public {
name = _newName;
}
function getName() public constant returns (string thename) {
return name;
}
}
Here I provide an example of communicating to the contract above from start to finish:
const monax = require('@monax/burrow');
const assert = require('assert');
var burrowURL = "<IP address>:<PORT>"; // localhost:10997 if running locally on default port
var account = 'ABCDEF01234567890123'; // address of the account to use for signing, hex string representation
var options = {objectReturn: false};
var burrow = monax.createInstance(burrowURL, account, options);
// Get the contractABIJSON from somewhere such as solc
var abi = json.parse(contractABIJSON) // Get the contractABIJSON from somewhere such as solc
var bytecode = contractBytecode // Get this from somewhere such as solc
// I'm going to use new to create a contract interface followed by a double direct call to the _constructor to deploy two contracts
const contract = burrow.contracts.new(abi, bytecode);
return Promise.all( // Deployment of two contracts
[
contract._constructor('contract1'),
contract._constructor('contract2')
]
).then( ([address1, address2]) => { // Collection of contracts' addresses
console.log(address1 + " - contract1");
console.log(address2 + " - contract2");
return Promise.all( // Execution of getName functions
[
contract.getName.at(address1), // Using the .at() to specify the second deployed contract
contract.getName.at(address2)
]
).then( ([name1, name2]) => { // Collection of contracts' names
console.log(address1 + " - " + name1);
assert.equal(name1, 'contract1');
console.log(address2 + " - " + name2);
assert.equal(name2, 'contract2');
});
});