Docs

author_submitExtrinsic - Kusama RPC Method

Submit a signed extrinsic to Kusama for inclusion in a block. Broadcast transactions for token transfers, staking operations, governance votes, and smart contract calls.

Submits a fully signed extrinsic to Kusama for inclusion in a future block. The extrinsic enters the transaction pool and is propagated to other nodes. This is the primary method for broadcasting any on-chain operation, including balance transfers, staking, governance, and pallet interactions.

Why Kusama? Build on Polkadot's canary network for real-world testing with live economic conditions with 7-day governance cycles (vs 1 month on Polkadot), lower bonding requirements, live KSM token economy, and first-to-market feature testing.

When to Use This Method

author_submitExtrinsic is essential for experimental dApp developers, parachain teams, and early adopters validating new features:

  • Token Transfers -- Send native tokens or assets between accounts on Kusama
  • Staking and Governance -- Submit staking nominations, validator operations, and governance votes for parachain experimentation, early feature deployment, and production-grade testing with real value
  • Smart Contract Interaction -- Call ink! or EVM smart contracts deployed on the chain
  • Automated Systems -- Build bots, keepers, and automated transaction pipelines that submit extrinsics programmatically

Code Examples

Common Use Cases

1. Transfer with Fee Pre-Check

Verify fees and balance before submitting a transfer:

JavaScript
import { ApiPromise, WsProvider, Keyring } from '@polkadot/api';

async function safeTransfer(api, sender, recipient, amount) {
  const transfer = api.tx.balances.transferKeepAlive(recipient, amount);

  // Step 1: Estimate fee
  const info = await transfer.paymentInfo(sender.address);
  const fee = info.partialFee.toBigInt();
  console.log(`Estimated fee: ${info.partialFee.toHuman()}`);

  // Step 2: Check balance
  const account = await api.query.system.account(sender.address);
  const free = account.data.free.toBigInt();
  const existentialDeposit = api.consts.balances.existentialDeposit.toBigInt();
  const totalCost = BigInt(amount) + fee;

  if (free - totalCost < existentialDeposit) {
    throw new Error(`Insufficient balance. Need ${totalCost}, have ${free}`);
  }

  // Step 3: Submit
  const hash = await transfer.signAndSend(sender);
  console.log(`Submitted: ${hash.toHex()}`);
  return hash;
}

2. Batch Transaction Submission

Submit multiple operations in a single extrinsic:

JavaScript
async function submitBatch(api, sender, calls) {
  const batch = api.tx.utility.batchAll(calls);

  // Estimate total fee
  const info = await batch.paymentInfo(sender.address);
  console.log(`Batch fee: ${info.partialFee.toHuman()} for ${calls.length} calls`);

  // Submit with event tracking
  return new Promise((resolve, reject) => {
    batch.signAndSend(sender, ({ status, events, dispatchError }) => {
      if (dispatchError) {
        if (dispatchError.isModule) {
          const decoded = api.registry.findMetaError(dispatchError.asModule);
          reject(new Error(`${decoded.section}.${decoded.name}: ${decoded.docs.join(' ')}`));
        } else {
          reject(new Error(dispatchError.toString()));
        }
      }

      if (status.isFinalized) {
        const successEvents = events.filter(({ event }) =>
          api.events.system.ExtrinsicSuccess.is(event)
        );
        resolve({
          blockHash: status.asFinalized.toHex(),
          success: successEvents.length > 0,
          events: events.length
        });
      }
    });
  });
}

// Usage: batch multiple transfers
const calls = [
  api.tx.balances.transferKeepAlive(recipient1, amount1),
  api.tx.balances.transferKeepAlive(recipient2, amount2),
  api.tx.balances.transferKeepAlive(recipient3, amount3)
];

const result = await submitBatch(api, sender, calls);

3. Nonce Management for Sequential Transactions

Submit multiple transactions in rapid succession with correct nonce handling:

JavaScript
async function submitSequential(api, sender, extrinsics) {
  // Get the starting nonce
  let nonce = await api.rpc.system.accountNextIndex(sender.address);

  const hashes = [];
  for (const ext of extrinsics) {
    const hash = await ext.signAndSend(sender, { nonce });
    hashes.push(hash.toHex());
    console.log(`Submitted with nonce ${nonce}: ${hash.toHex()}`);
    nonce = nonce.addn(1);
  }

  return hashes;
}

Error Handling

Common errors and solutions:

Error CodeDescriptionSolution
1010Invalid TransactionCheck signature, nonce, era, and that the extrinsic is properly SCALE-encoded
1010 (bad signature)Signature verification failedEnsure the signing key matches the sender account and the correct genesis hash was used
1010 (outdated)Nonce too lowThe nonce has already been used -- query the current nonce and rebuild the extrinsic
1010 (stale)Mortality period expiredThe extrinsic era has passed -- rebuild with a current era or use an immortal era
1012Transaction pool fullThe node's pool is at capacity -- retry later or increase tip
-32603Internal errorNode may be syncing or experiencing issues -- check node health
  • author_pendingExtrinsics -- Check the transaction pool for pending extrinsics
  • payment_queryInfo -- Estimate fees before submitting
  • system_accountNextIndex -- Get the next valid nonce for an account
  • state_call -- Call runtime APIs (e.g., for nonce via AccountNonceApi)
  • chain_getBlock -- Verify extrinsic inclusion in a block