mcps-sdk-js
Version:
MCPS JavaScript SDK
345 lines (318 loc) • 10 kB
text/typescript
import {Client} from '../client';
import * as is from 'is_js';
import * as types from '../types';
import {SdkError} from '../errors';
import {Utils, Crypto} from '../utils';
/**
* Tx module allows you to sign or broadcast transactions
*
* @category Modules
* @since v0.17
*/
export class Tx {
/** @hidden */
private client: Client;
/** @hidden */
constructor(client: Client) {
this.client = client;
}
/**
* Build Tx
* @param msgs Msgs to be sent
* @param baseTx
* @returns unsignedTx
* @since v0.17
*/
buildTx(
msgs: any[],
baseTx: types.BaseTx,
): types.ProtoTx {
let msgList: types.Msg[] = msgs.map(msg => {
return this.createMsg(msg);
});
const unsignedTx: types.ProtoTx = this.client.auth.newStdTx(msgList, baseTx);
return unsignedTx;
}
/**
* generate StdTx from protoTxModel
* @param {[type]} protoTxModel:any instance of cosmos.tx.v1beta1.Tx
* @return {[type]} unsignedTx
*/
newStdTxFromProtoTxModel(protoTxModel:any):types.ProtoTx{
return types.ProtoTx.newStdTxFromProtoTxModel(protoTxModel);
}
/**
* Build, sign and broadcast the msgs
* @param msgs Msgs to be sent
* @param baseTx
* @returns
* @since v0.17
*/
async buildAndSend(
msgs: any[],
baseTx: types.BaseTx
) {
// Build Unsigned Tx
const unsignedTx: types.ProtoTx = this.buildTx(msgs, baseTx);
// Not supported in ibc-alpha
// const fee = await this.client.utils.toMinCoins(unsignedTx.value.fee.amount);
// unsignedTx.value.fee.amount = fee;
// Sign Tx
const signedTx = await this.sign(unsignedTx, baseTx);
// Broadcast Tx
return this.broadcast(signedTx, baseTx.mode);
}
/**
* Broadcast a tx
* @param signedTx The tx object with signatures
* @param mode Broadcast mode
* @returns
* @since v0.17
*/
broadcast(
signedTx: types.ProtoTx,
mode?: types.BroadcastMode
): Promise<types.TxResult> {
const txBytes = signedTx.getData();
switch (mode) {
case types.BroadcastMode.Commit:
return this.broadcastTxCommit(txBytes);
case types.BroadcastMode.Sync:
return this.broadcastTxSync(txBytes).then(response => {
return this.newTxResult(response);
});
default:
return this.broadcastTxAsync(txBytes).then(response => {
return this.newTxResult(response);
});
}
}
/**
* Single sign a transaction
*
* @param stdTx StdTx with no signatures
* @param baseTx baseTx.from && baseTx.password is requred
* @returns The signed tx
* @since v0.17
*/
async sign(
stdTx: types.ProtoTx,
baseTx: types.BaseTx,
): Promise<types.ProtoTx> {
if (is.empty(baseTx.from)) {
throw new SdkError(`baseTx.from of the key can not be empty`);
}
if (is.empty(baseTx.password)) {
throw new SdkError(`baseTx.password of the key can not be empty`);
}
if (!this.client.config.keyDAO.decrypt) {
throw new SdkError(`Decrypt method of KeyDAO not implemented`);
}
const keyObj = this.client.config.keyDAO.read(baseTx.from);
if (!keyObj) {
throw new SdkError(`Key with name '${baseTx.from}' not found`);
}
let accountNumber = baseTx.account_number??'0';
let sequence = baseTx.sequence || '0';
if (!baseTx.account_number || !baseTx.sequence) {
const account = await this.client.bank.queryAccount(keyObj.address);
if ( account.account_number ) { accountNumber = account.account_number }
if ( account.sequence ) { sequence = account.sequence }
}
// Query account info from block chain
const privKey = this.client.config.keyDAO.decrypt(keyObj.privKey, baseTx.password);
if (!stdTx.hasPubKey()) {
const pubKey = Crypto.getPublicKeyFromPrivateKey(privKey, baseTx.pubkeyType);
stdTx.setPubKey(pubKey, sequence || undefined);
}
const signature = Crypto.generateSignature(stdTx.getSignDoc(accountNumber || undefined, this.client.config.chainId).serializeBinary(), privKey, baseTx.pubkeyType);
stdTx.addSignature(signature);
return stdTx;
}
/**
* Single sign a transaction with signDoc
*
* @param signDoc from protobuf
* @param name Name of the key to sign the tx
* @param password Password of the key
* @param type pubkey Type
* @returns signature
* @since v0.17
*/
sign_signDoc(
signDoc: Uint8Array,
name: string,
password: string,
type:types.PubkeyType = types.PubkeyType.secp256k1
): string {
if (is.empty(name)) {
throw new SdkError(`Name of the key can not be empty`);
}
if (is.empty(password)) {
throw new SdkError(`Password of the key can not be empty`);
}
if (!this.client.config.keyDAO.decrypt) {
throw new SdkError(`Decrypt method of KeyDAO not implemented`);
}
const keyObj = this.client.config.keyDAO.read(name);
if (!keyObj) {
throw new SdkError(`Key with name '${name}' not found`);
}
const privKey = this.client.config.keyDAO.decrypt(keyObj.privKey, password);
const signature = Crypto.generateSignature(signDoc, privKey, type);
return signature;
}
/**
* Broadcast tx async
* @param txBytes The tx bytes with signatures
* @returns
*/
private broadcastTxAsync(
txBytes: Uint8Array
): Promise<types.ResultBroadcastTxAsync> {
return this.broadcastTx(txBytes, types.RpcMethods.BroadcastTxAsync);
}
/**
* Broadcast tx sync
* @param txBytes The tx bytes with signatures
* @returns The result object of broadcasting
*/
private broadcastTxSync(
txBytes: Uint8Array
): Promise<types.ResultBroadcastTxAsync> {
return this.broadcastTx(txBytes, types.RpcMethods.BroadcastTxSync);
}
/**
* Broadcast tx and wait for it to be included in a block.
* @param txBytes The tx bytes with signatures
* @returns The result object of broadcasting
*/
private broadcastTxCommit(txBytes: Uint8Array): Promise<types.TxResult> {
return this.client.rpcClient
.request<types.ResultBroadcastTx>(types.RpcMethods.BroadcastTxCommit, {
tx: Utils.bytesToBase64(txBytes),
})
.then(response => {
// Check tx error
if (response.check_tx && response.check_tx.code > 0) {
console.error(response.check_tx);
throw new SdkError(response.check_tx.log, response.check_tx.code);
}
// Deliver tx error
if (response.deliver_tx && response.deliver_tx.code > 0) {
console.error(response.deliver_tx);
throw new SdkError(response.deliver_tx.log, response.deliver_tx.code);
}
if (response.deliver_tx && response.deliver_tx.tags) {
response.deliver_tx.tags = Utils.decodeTags(response.deliver_tx.tags);
}
return this.newTxResult(response);
});
}
/**
* Broadcast tx sync or async
* @private
* @param signedTx The tx object with signatures
* @returns The result object of broadcasting
*/
private broadcastTx(
txBytes: Uint8Array,
method: string
): Promise<types.ResultBroadcastTxAsync> {
// Only accepts 'broadcast_tx_sync' and 'broadcast_tx_async'
if (
is.not.inArray(method, [
types.RpcMethods.BroadcastTxSync,
types.RpcMethods.BroadcastTxAsync,
])
) {
throw new SdkError(`Unsupported broadcast method: ${method}`);
}
return this.client.rpcClient
.request<types.ResultBroadcastTxAsync>(method, {
tx: Utils.bytesToBase64(txBytes),
})
.then(response => {
if (response.code && response.code > 0) {
throw new SdkError(response.log, response.code);
}
return response;
});
}
// private marshal(stdTx: types.Tx<types.StdTx>): types.Tx<types.StdTx> {
// const copyStdTx: types.Tx<types.StdTx> = stdTx;
// Object.assign(copyStdTx, stdTx);
// stdTx.value.msg.forEach((msg, idx) => {
// if (msg.marshal) {
// copyStdTx.value.msg[idx] = msg.marshal();
// }
// });
// return copyStdTx;
// }
private newTxResult(
txRespond:any,
): types.TxResult {
const txResult:any = { hash:txRespond.hash };
if (txRespond.height) { txResult.height = txRespond.height };
if (txRespond.deliver_tx) {
try{
txResult.log = JSON.parse(txRespond.deliver_tx.log);
}catch(e){
txResult.log = txRespond.deliver_tx.log;
}
txResult.info = txRespond.deliver_tx.info;
txResult.gas_wanted = txRespond.deliver_tx.gas_wanted;
txResult.gas_used = txRespond.deliver_tx.gas_used;
txResult.events = txRespond.deliver_tx.events;
}
return txResult;
}
/**
* create message
* @param {[type]} txMsg:{type:string, value:any} message
* @return {[type]} message instance of types.Msg
*/
createMsg(txMsg: { type: string, value: any }) {
let msg: any = {};
switch (txMsg.type) {
//bank
case types.TxType.MsgSend: {
msg = new types.MsgSend(txMsg.value)
break;
}
case types.TxType.MsgMultiSend: {
msg = new types.MsgMultiSend(txMsg.value)
break;
}
//contract
case types.TxType.MsgStoreCode: {
msg = new types.MsgStoreCode(txMsg.value)
break;
}
case types.TxType.MsgInstantiateContract: {
msg = new types.MsgInstantiateContract(txMsg.value)
break;
}
case types.TxType.MsgExecuteContract: {
msg = new types.MsgExecuteContract(txMsg.value)
break;
}
case types.TxType.MsgMigrateContract: {
msg = new types.MsgMigrateContract(txMsg.value)
break;
}
case types.TxType.MsgUpdateAdmin: {
msg = new types.MsgUpdateAdmin(txMsg.value)
break;
}
case types.TxType.MsgClearAdmin: {
msg = new types.MsgClearAdmin(txMsg.value)
break;
}
default: {
throw new Error("not exist tx type");
}
}
return msg;
}
}