I’m trying to create a transaction to interact with a cosmos based application (MESG https://github.com/mesg-foundation/engine).
Most of the libraries to sign are using the cosmos APIs but I would like to directly use tendermint broadcast_tx_[a]sync
.
I’ve tried different libraries like cosmosjs
or cosmos-api
but they have issues running on node (because of buffers) or relying on cosmos apis.
The workflow is the following:
- import an account from a seed
- create a message with the transfer (type: cosmos-sdk/MsgSend)
- sign the transaction with the private key of the account
- create the transaction
- encode the transaction with amino.js
- broadcast the transaction to tendermint
broadcast_tx_sync
For some reason, my transaction is not correctly decoded.
{
jsonrpc: '2.0',
id: '',
result: {
code: 2,
data: '',
log: '{"codespace":"sdk","code":2,"message":"error decoding transaction"}',
hash: '30F7FEC03609C0A841414186A9AA5E4020B67CF46F1991ABDEEEFB4DA297390F'
}
}
Here is the actual script running
const bip39 = require('bip39')
const bip32 = require('bip32')
const bech32 = require('bech32')
const secp256k1 = require('secp256k1')
const CryptoJS = require('crypto-js')
const crypto = require('crypto')
const fetch = require('node-fetch')
const { marshalTx } = require('@tendermint/amino-js')
const DEFAULT_HD_PATH = "m/44'/470'/0'/0/0" // MESG hd path
const DEFAULT_BECH_PREFIX = 'mesgtest' // MESG bech32 prefix
const MNEMONIC = '...'
const TO = 'mesgtest...'
const accountFromSeed = (mnemonic, bech32Prefix = DEFAULT_BECH_PREFIX, hdPath = DEFAULT_HD_PATH) => {
if (!bip39.validateMnemonic(mnemonic)) throw new Error('invalid mnemonic')
const seed = bip39.mnemonicToSeedSync(mnemonic)
const masterKey = bip32.fromSeed(seed)
const cosmosHD = masterKey.derivePath(hdPath)
const privateKey = cosmosHD.privateKey
const publicKey = Buffer.from(secp256k1.publicKeyCreate(privateKey, true), 'hex')
const message = CryptoJS.enc.Hex.parse(publicKey.toString('hex'))
const address = CryptoJS.RIPEMD160(CryptoJS.SHA256(message)).toString()
const words = bech32.toWords(Buffer.from(address, 'hex'))
const cosmosAddress = bech32.encode(bech32Prefix, words)
return {
cosmosAddress,
publicKey: publicKey,
privateKey: privateKey
}
}
function deterministicObj(obj) {
if (Array.isArray(obj)) return obj.map(deterministicObj)
if (typeof obj !== `object`) return obj
const empty = x => x === undefined || x === null
return Object.keys(obj)
.sort()
.reduce((prev, current) => empty(obj[current])
? prev
: {
...prev,
[current]: deterministicObj(obj[current])
}, {})
}
function getPubKeyBase64(ecpairPriv) {
const pubKeyByte = secp256k1.publicKeyCreate(ecpairPriv);
return Buffer.from(pubKeyByte, 'binary').toString('base64');
}
async function main() {
const account = accountFromSeed(MNEMONIC)
const signMessage = {
msgs: [
{
type: `cosmos-sdk/MsgSend`,
value: {
fromAddress: account.cosmosAddress,
toAddress: TO,
amounts: [
{ denom: 'atto', amount: '1000' }
]
}
}
],
fee: { amount: [{ denom: 'atto', amount: '1' }], gas: '21906' },
signatures: [],
memo: '1',
sequence: 100,
account_number: 0,
chain_id: 'mesg-testnet-01'
}
const hash = crypto.createHash('sha256').update(JSON.stringify(deterministicObj(signMessage))).digest('hex')
const buf = Buffer.from(hash, 'hex')
const signObj = secp256k1.ecdsaSign(buf, account.privateKey)
const signatureBase64 = Buffer.from(signObj.signature, 'binary').toString('base64')
const signedTx = {
type: 'auth/StdTx',
value: {
msg: signMessage.msgs,
fee: signMessage.fee,
memo: signMessage.memo,
signatures: [
{
signature: signatureBase64,
pub_key: {
type: 'tendermint/PubKeySecp256k1',
value: getPubKeyBase64(account.privateKey)
}
}
]
}
}
console.log(JSON.stringify(signedTx))
const tx = deterministicObj(signedTx)
const encodedTx = '0x' + Buffer.from(marshalTx(tx), 'binary').toString('hex')
console.log(encodedTx)
const res = await fetch(`http://localhost:26657/broadcast_tx_sync?tx=${encodedTx}`)
console.log(await res.json())
}
main()
Any feedback would be appreciated. If you also have good libraries that work correctly in node to replace some of the boilerplate that would be great.
Thanks