UNPKG

mcps-sdk-js

Version:

MCPS JavaScript SDK

345 lines (318 loc) 10 kB
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; } }