1

I am attempting to submit a transaction using Polkadot JS API and a signature that has been signed by Ledger. Upon passing it to send I get the Invalid Transaction: Transaction has a bad signature error.

The payload is generated roughly as follows:


const lastHeader = await api.rpc.chain.getHeader();

const payload = {
        specVersion: api.runtimeVersion.specVersion.toHex(),
        transactionVersion: api.runtimeVersion.transactionVersion.toHex(),
        address: submitAddress,
        blockHash: lastHeader.hash.toHex(),
        blockNumber: lastHeader.number.toHex(),
        genesisHash: api.genesisHash.toHex(),
        method: api.createType('Call', tx).toHex(),
        nonce: tx.nonce.toHex(),
        signedExtensions: Object.values(
          api.registry.metadata.extrinsic.signedExtensions.toHuman() || {}
        ).map((e: any) => e.identifier),
        tip: tx.tip.toHex(),
        version: tx.version,
};

const raw = api.registry.createType('ExtrinsicPayload', payload);

The Ledger device is successfully generating a signature, using @zondax/ledger-substrate-js's sign method.

I am then injecting this signature into the tx like so:

tx.addSignature(from, hexAddPrefix(signature.toString('hex')));

const unsub = await tx.send(({ status, events = [] }: AnyApi) => {
   ...
});

This is yielding an error: RpcError: 1010: Invalid Transaction: Transaction has a bad signature.

console logging tx.toHuman() after addSignature gives the following:

{
    "isSigned": true,
    "method": {
        "args": {
            "amount": "20,000,000,000",
            "pool_id": "15"
        },
        "method": "join",
        "section": "nominationPools"
    },
    "era": {
        "ImmortalEra": "0x00"
    },
    "nonce": "0",
    "signature": "0x4ce81c594615f5b0812b29f392081ae07fd65a142998fca1a2f9f81d67ded13098cd544689b401ca7c915b23ec6bf58a51aba7ba4eec51f7eb491f5af0c95b09",
    "signer": {
        "Id": "16F1ghy4bMr72BGyin7ryTxiQqJFJwpePsNJcZo1p6dUGVZT"
    },
    "tip": "0"
}

What is incorrect / missing in the above snippets that is yielding this error?

Thanks

1

1 Answer 1

2

Solution

Detailed explanations of the problem and great troubleshooting pointers can be found on the JS Apps issue here: https://github.com/polkadot-js/api/issues/5555

To summarise, ensure the entire payload is constructed using Polkadot JS types:

const lastHeader = await api.rpc.chain.getHeader();
const blockNumber = api.registry.createType(
  'BlockNumber',
  lastHeader.number.toNumber()
);
const method = api.createType('Call', tx);
const era = api.registry.createType('ExtrinsicEra', {
  current: lastHeader.number.toNumber(),
  period: 64,
});

const nonceRaw = api.query.system.account(address)?.nonce || 0;
const nonce = api.registry.createType('Compact<Index>', nonceRaw);

const payload = {
  specVersion: api.runtimeVersion.specVersion.toHex(),
  transactionVersion: api.runtimeVersion.transactionVersion.toHex(),
  address: address,
  blockHash: lastHeader.hash.toHex(),
  blockNumber: blockNumber.toHex(),
  era: era.toHex(),
  genesisHash: api.genesisHash.toHex(),
  method: method.toHex(),
  nonce: nonce.toHex(),
  signedExtensions: [
    'CheckNonZeroSender',
    'CheckSpecVersion',
    'CheckTxVersion',
    'CheckGenesis',
    'CheckMortality',
    'CheckNonce',
    'CheckWeight',
    'ChargeTransactionPayment',
  ],
  tip: api.registry.createType('Compact<Balance>', 0).toHex(),
  version: tx.version,
};
const raw = api.registry.createType('ExtrinsicPayload', payload, {
  version: payload.version,
});

When it comes to addSignature we need to pass this payload along with address and signature:

tx.addSignature(address, txSignature, txPayload);

const unsub = await tx.send(({ status, events = [] }: AnyApi) => {
   ...
});

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.