Raw sign Cosmos staking transaction

I am trying to send an ATOM staking transaction for raw signing in the following way.
I have a formed transaction in json:

         "delegatorAddress": ...
        "validatorAddress": ...

as well as its encoded version:


I am forming a request to create a transaction in fireblocks dev sandbox:

"extra ="{
            "content" : tx["encodedBody"]
status, id = fireblocks.create_transaction(
  tx_type = 'RAW',
  asset_id = 'ATOM_COS_TEST',
  source=TransferPeerPath(VAULT_ACCOUNT, "0"),
  extra_parameters = extra

The transaction is sent, but when processing the transaction, an error message appears:
Too many characters

The raw signing transaction contains at least one message with over 32 bytes. Fireblocks does not allow a message with this length for this encryption protocol.

Can you please tell me what the problem is? Am I forming the request incorrectly, or do I need to pass different parameters in the content?

Hi @xor777,

My name is Alon and I am part of the Solutions Engineering team at Fireblocks.

For ATOM we recommend that you their @cosmjs SDK and their custom signer, like this:

import { encodeSecp256k1Signature } from "@cosmjs/amino";
import { AccountData, DirectSignResponse, makeSignBytes, OfflineDirectSigner } from "@cosmjs/proto-signing";
import { SignDoc } from "cosmjs-types/cosmos/tx/v1beta1/tx";
import { FireblocksSDK, PeerType, TransactionArguments, TransactionOperation } from "fireblocks-sdk";
import { sha256 } from "js-sha256";
import {inspect} from 'util';

export default class ATOMFireblocksSigner implements OfflineDirectSigner{

    private assetId: string;
    private note: string;

    constructor(private fbks: FireblocksSDK, private vaId: string, private testnet: boolean = false){
        this.assetId = testnet ? "ATOM_COS_TEST" : "ATOM_COS";
        this.note = undefined;

    async getAccounts(): Promise<AccountData[]> {
        const pubkey = await this.fbks.getPublicKeyInfoForVaultAccount({
            assetId: this.assetId,
            vaultAccountId: parseInt(this.vaId),
            compressed: true,
            change: 0,
            addressIndex: 0
        const addr = (await this.fbks.getDepositAddresses(this.vaId, this.assetId)).filter(x=>x.type==='Permanent')[0].address;

        return new Promise((r)=>{
                        address: addr,
                        pubkey: Uint8Array.from(Buffer.from(pubkey.publicKey, 'hex')),
                        algo: 'secp256k1',
    async signDirect (signerAddress : string , signDoc :SignDoc){
        // derived from directsecp256k1hdwallet code, important part is to hash the message and then sign it.
        const signBytes = makeSignBytes(signDoc);
        const msgHash = sha256.create().update(signBytes).hex()
        let txPayload: TransactionArguments = {
            assetId: this.assetId,
            source: {
                type: PeerType.VAULT_ACCOUNT,
                id: `${this.vaId}`
            operation: TransactionOperation.RAW,
            extraParameters: {
                rawMessageData: {
                    messages:   [
                            content: msgHash

        // Wait for TX to finish here

        const pubkey = (await this.getAccounts())[0].pubkey;
        const sig = encodeSecp256k1Signature(pubkey, Uint8Array.from(Buffer.from(tx.signedMessages![0].signature.fullSig, 'hex')));
        this.note = undefined;
        return {
            signed: signDoc,
            signature: sig

    setNote(note: string){
        this.note = note;

When it comes to usage within the ATOM SDK, this is the approach you can use:

const stargateClient = await SigningStargateClient.connectWithSigner(

Where rpcUrl is the RPC URL and the signer is an instance of ATOMFireblocksSigner mentioned above.