0.1.0 • Published 10 months ago

@inae/inae-js v0.1.0

Weekly downloads
-
License
MIT
Repository
-
Last release
10 months ago

Originally based on JS library for Cosmostation

Heavily modified by Hessegg

This is a preview of Javascript API for iNAE blockchain. This delivery is tailored for INAE blockchain.

At the moment this package contains samples on how to build transactions for the blockchain and how to make queries. The examples expect that:

  • the blockchain has Chain ID HostNet,
  • the address prefix is inae,
  • the platform coin is called INAE with normalized denom uinae
  • the blockchain node runs on the local host and provides REST API at port localhost:31012

Installation

To be written

NPM

The package is not published yet

npm install @inae/inae-js

Yarn

The package is not published yet

yarn add @inae/inae-js

Local

add to package.json

{
  "name" : "...",
  ...
  "dependencies" : {
    ...
    "@inae/inae-js": "file:../inae-js",
  }
}
# yarn env
$ yarn

# npm env
$ npm install 

Generate Protobuf

DON'T DO IT unless you know what are you doing. The generated files require manual tuning.

$ ./protocgen.sh

Import

import { Blockchain, Query } from "@inae/inae-js";

Examples

The package comes with two examples.

example/hostnet.query.js demonstrates how to make queries. Execute it with yarn:

yarn example.query

example/hostnet.send.js demonstrates how to issue a transaction that transfers funds between two accounts. Execute it with yarn:

yarn example.send

Usage

Query information

import { Blockchain, Query } from "@inae/inae-js";
const chainId = "HostNet";
// The rest server URL MUST NOT end with slash
const bc = new Blockchain("http://localhost:31012", chainId); 
const Q = new Query(bc)

The Q object provides method query(path: string) : Promize<any> that performs query specified by the path and returns the result as object.

You can find the query paths in the protobuf files named query.proto in ./proto and ./third_party/proto directories.

For example, ./proto/inae/convert/v1/query.proto defines the following query service:

service Query {
  rpc RatePair(QueryRatePairRequest) returns (QueryRatePairResponse) {
    option (google.api.http).get = "/inae/convert/v1/rate/pair/{from}/{to}";
  }
  rpc RateFrom(QueryRateFromRequest) returns (QueryRatesResponse) {
    option (google.api.http).get = "/inae/convert/v1/rate/from/{from}";
  }
  rpc RateTo(QueryRateToRequest) returns (QueryRatesResponse) {
    option (google.api.http).get = "/inae/convert/v1/rate/to/{to}";
  }
  rpc RateAll(QueryRateAllRequest) returns (QueryRatesResponse) {
    option (google.api.http).get = "/inae/convert/v1/rate/all";
  }
}

The query RatePair returns conversion rate from one token to another. The query path is defined by the option google.api.http: "/inae/convert/v1/rate/pair/{from}/{to}".

It is a template. For the actual query the placeholders should be substituted by the actual token symbols. E.g. the query path for the conversion rate from TEST token to INAE coin is "/inae/convert/v1/rate/pair/inae/test".

This query returns the value of the type QueryRatePairResponse:

message QueryRatePairResponse {
  ConvertPair pair = 1;
}

message ConvertPair{
    string from = 1;
    string to = 2;
    string rate = 3 [(gogoproto.customtype) = "Dec", (gogoproto.nullable) = false];
}

In JSON the response structure closly resembles the protobuf message.

const result = await Q.query("/inae/convert/v1/rate/pair/inae/test");
console.log(result)
 { pair: { from: 'test', to: 'inae', rate: '0.010000000000000000' } }

Predefined queries

Query class provides a number of predefined queries. See the source code src/query.js for more details.

Transactions

Transactions consist of messages, authentication information and signatures.

Messages convoy the business logic of the transaction, the action that blockchain nodes should execute upon receiving the transaction.

Authentication information provides data about who authorized the transaction by digitally signing it.

Signatures are the actual digital signatures of the transaction authorizers.

The messages could be found in the protobuf files named tx.proto in ./proto and ./third_party/proto directories.

One of the most important transaction message is MsgTransfer that tells the blockchain to transfer funds between accounts. It is defined in proto/inae/bankz/v1/tx.proto

package inae.bankz.v1;

// ...

// Msg defines the bank Msg service.
service Msg {
  rpc Transfer(MsgTransfer) returns (MsgTransferResponse) {
    option (google.api.http).post = "/inae/bankz/v1/transfer";
  };
  // ...
}

// MsgTransfer represents a message to send coins from one account to another.
message MsgTransfer {
  // ...
  string from_address = 1;
  string to_address = 3;
  cosmos.base.v1beta1.Coin coin = 4;
}
// MsgTransferResponse defines the Msg/Send response type.
message MsgTransferResponse {}

The package inae-js provides a library of Javascript types generated from Protobuf files:

import { message } from "@inae/inae-js/";

Example

Blockchain class implements basic blockhain-related operations, including keys derivation, transaction signing and broadcasting transactions to the blockchain.

messages provide classes generated from the protobuf files.

import { Blockchain, Query } from "@inae/inae-js";
import { message } from "@inae/inae-js";

Setting up the blockchain and account. The keys and the address are derived from the account mnemonics. In this example we use the mnemonic phrase for the account supply defined for the localhost demonet.

// [WARNING] This mnemonic is just for the demo purpose. DO NOT USE THIS MNEMONIC for your own wallet.
// Supply account on the Hostnet
const mnemonic = "also decide pole hello ready month distance favorite blush lawn theory flee hundred move outdoor fiscal online labor patch roof toast case cement hidden";

Setting up the blockchain adapter: chain ID, REST API URL, address prefix and key derivation path. Change in any of the parameters will break compatibility with the localhost demonet.

const chainId = "HostNet";
// The rest server URL MUST NOT end with slash
const bc = new Blockchain("http://localhost:31012", chainId); 
bc.setBech32MainPrefix("inae");
bc.setPath("m/44'/118'/0'/0/0");
const Q = new Query(bc);

Derive keys and address:

// Generate address, private and public keys
const address = bc.getAddress(mnemonic);
const privKey = bc.getECPairPriv(mnemonic);
// getPubKeyAny returns the public key wrapped into `any` protobuf type
const pubKeyAny = bc.getPubKeyAny(privKey);

Query the blockchain for the account information. It will be used for the digital signature.

// Load the account information from the blockchain
const account = await bc.getAccounts(address).then(obj => obj.account)

Create the transaction. The first step is to make the message of the type MsgTransfer.

// Destination for the transaction
const toAddress = "inae1ty659nurtr5cstcxuqmlrkukfw924mkx9dlwdc";

console.log("Sending 1 iNAE from ", address, " to ", toAddress)

// Load the account information from the blockchain
const account = await bc.getAccounts(address).then(obj => obj.account)

// Prepare the transaction for signing
// signDoc = (1)txBody + (2)authInfo

// ---------------------------------- (1)txBody ----------------------------------

// The action of the transaction is presented by the `MsgTransfer` message.
// The path `message.inae.bankz.v1` is derived from protobuf package name.
// See `./proto/inae/bankz/v1/tx.proto`:
// `package inae.banks.v1`;
const msgTransfer = new message.inae.bankz.v1.MsgTransfer({
	from_address: address,
	// account01 in the HostNet
	to_address: toAddress,
	coin: { denom: "uinae", amount: String(1000000) }		// 6 zeroes: 1000000 uinae = 1 INAE
});

// Wrap the message into `any`.
const msgSendAny = new message.google.protobuf.Any({
	type_url: "/inae.bankz.v1.MsgTransfer",
	value: message.inae.bankz.v1.MsgTransfer.encode(msgTransfer).finish()
});

const txBody = new message.cosmos.tx.v1beta1.TxBody({ messages: [msgSendAny], memo: "" });

The object txBody keeps the list of messages and optional memo note to be included in the transaction.

The procedure of making the signature doesn't require any knowledge about the messages. All transactions are signed in the same way.

// --------------------------------- (2)authInfo ---------------------------------

// Signer information hold the public key of the transaction sender
// Sequence it used to prevent replay attacks.
const signerInfo = new message.cosmos.tx.v1beta1.SignerInfo({
	public_key: pubKeyAny,
	mode_info: { single: { mode: message.cosmos.tx.signing.v1beta1.SignMode.SIGN_MODE_DIRECT } },
	sequence: account.sequence
});

// Reserved for future use. At the moment set zero (empty) fees
const feeValue = new message.cosmos.tx.v1beta1.Fee({
	amount: [{ denom: "uinae", amount: String(10000) }],
	// amount: [],
	gas_limit: 200000,
	payer: "",
	granter: ""
});

const authInfo = new message.cosmos.tx.v1beta1.AuthInfo({ signer_infos: [signerInfo], fee: feeValue });

// -------------------------------- sign --------------------------------

// The signature includes the account number to prevent signature collisions
const signedTxBytes = bc.sign(txBody, authInfo, account.account_number, privKey);

The method Blockchain.broadcast delivers the signed transaction to the blockchain.

The mode BROADCAST_MODE_BLOCK means wait until the transaction is included into a block. The transaction hash is accessible as response.tx_response.txhash.

// ----------------------- send to the blockchain ------------------------
const response = await bc.broadcast(signedTxBytes, "BROADCAST_MODE_BLOCK").then(response => response.json())
console.log(response)

This section of the example queries the blockchain about the transaction status. If the field txstatus.tx_response.code is zero, the transaction is handled and committed to the blockchain state. If processing the transaction resulted in an error this field is non-zero.

// --- Check the transaction status ---
const txid = response.tx_response.txhash;

if (response.tx_response.code != 0) {
	console.error("Transaction failed: reason:", response.tx_response.raw_log);
	if (response.tx_response.height == 0) {
		console.error("Transaction failed: invalid transaction, rejected");
	}
} else {

	// query the transaction
	const txstatus = await Q.tx(txid)

	if (txstatus.tx_response.code == 0) {
		console.log(`${txid}: success`);
	} else {
		const errorCode = txstatus.tx_response.code
		const errorCodeSpace = txstatus.tx_response.errorCodeSpace
		const errorInfo = txstatus.tx_response.info || "N/A"
		console.log(`${txid}: error: codespace ${errorCodeSpace}, error ${errorCode}: ${errorInfo}`);
	}
}