Logo
New RPC users get 35% off their first monthView the offer
OnFinality Learn
Infrastructure & Operations13 min read

Polkadot Extrinsic Pool: Reading Pending Transactions over RPC

A reproducible RPC playbook for reading a Substrate node's transaction pool, pairing author_pendingExtrinsics with system_accountNextIndex, and diagnosing stuck extrinsics.

TL;DR

A Substrate node's transaction pool is not an EVM mempool: each extrinsic is validated against a runtime-provided validity, so it can be ready, future, in-block, or dropped for a stale nonce, a banned tag, or an expired mortality era. author_pendingExtrinsics returns the extrinsics currently in the pool as SCALE-encoded hex in no guaranteed order, so you must decode them against runtime metadata rather than reading them as addresses. system_accountNextIndex returns the next nonce the account should use, which reflects what the pool has queued plus what is already included, while the on-chain System.Account nonce reflects only what is included. Comparing the two, and re-reading the pool each poll, is the cheapest way to separate a genuinely waiting extrinsic from one that was rejected or dropped. This article documents the read path, the three failure signatures, and a bounded diagnostic loop you can run against your own endpoint.

Why the Substrate Transaction Pool Is Not an EVM Mempool

On an account-based EVM chain, a transaction typically enters a mempool and waits for inclusion; the pool is largely a holding area ordered by fee. Substrate works differently. When an extrinsic is submitted, the node asks the runtime to validate it, and the runtime returns a validity verdict that includes a priority, a set of tags, and a longevity (era) window. The pool then classifies the extrinsic as ready, future, in-block, or it drops it entirely. This means an extrinsic can be rejected at the pool boundary for reasons that have nothing to do with gas price.

The practical consequence is that a caller who only polls its own submission hash is blind to most of what happens. The extrinsic may be sitting in the future queue behind a nonce gap, it may have been dropped because its mortality era expired, or it may have been banned because a sibling extrinsic with the same tag failed. None of those states are visible from the submission response alone. The authoritative description of this model is in the Substrate transaction format documentation, which defines the nonce, mortality, and validity fields that the pool reasons about.

Reading the pool directly, rather than inferring from your own submission, is therefore the correct diagnostic posture. The pool read tells you what the node currently holds; the account index tells you what nonce the node expects next. Together they answer the question that submission tutorials never do: is my extrinsic waiting, included, or gone?

  • Ready: valid now and eligible for inclusion in the next block.
  • Future: valid only after an earlier nonce is included, so it waits behind a gap.
  • In-block: already selected into a block being built or recently imported.
  • Dropped: removed because it was invalid, stale, or banned by a failed sibling.

Reading the Pool with author_pendingExtrinsics

The primary read is author_pendingExtrinsics, documented in the polkadot.js JSON-RPC reference for the author methods. It returns the extrinsics currently in the pool as an array of SCALE-encoded hex strings. It does not return addresses, nonces, or human-readable call data, and the array order is not guaranteed to reflect inclusion order. Treat the response as opaque bytes that must be decoded against the runtime metadata of the exact chain you are querying.

Because the encoding is SCALE, decoding requires the chain's metadata, which is why this read pairs naturally with a decoding step. If you need to turn the hex into a signer, a nonce, and a call, the companion article on decoding Polkadot extrinsic submission errors covers the decode path in detail. For pool reads specifically, the fields you care about are the signer account, the nonce, and the era, because those three drive every diagnosis below.

A minimal curl call is enough to confirm the method is reachable on your endpoint before you build anything on top of it. The response is a JSON-RPC 2.0 result object whose result is an array of hex strings; an empty array means the pool is currently empty, which is a valid and common state on a quiet chain.

curl -s https://your-polkadot-endpoint.example \
  -H 'Content-Type: application/json' \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "author_pendingExtrinsics",
    "params": []
  }'

system_accountNextIndex and the On-Chain Nonce

The second read is system_accountNextIndex, which takes an SS58 address and returns the next nonce that account should use. Its semantics are the crux of the whole diagnostic. The on-chain nonce, read from System.Account storage, reflects what has been included in a block. system_accountNextIndex reflects what the pool has queued plus what is already included. While a transaction is pending, these two values legitimately differ, and that difference is not an error.

This is the cheapest way to distinguish a stuck transaction from a never-submitted one. If accountNextIndex is greater than the on-chain nonce, the node believes it has queued work for that account. If the two are equal, the node has nothing queued, which means your extrinsic was either never accepted or has already been dropped. You can read the on-chain nonce through the storage path described in Polkadot state_queryStorageAt storage changes, or through any client that exposes System.Account.

A subtlety worth internalizing: accountNextIndex is a pool-aware view, so it can move backward if a queued extrinsic is dropped. Do not cache it and assume monotonicity. Re-read it on every poll alongside the pool, and record both values with a timestamp so you can see the transition when a drop occurs.

curl -s https://your-polkadot-endpoint.example \
  -H 'Content-Type: application/json' \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "system_accountNextIndex",
    "params": ["15oF4uVJwmo4TdGW7VfQxNLavjCXviqxT9S1MgbjMNHr6Sp5"]
  }'

The Three Failure Signatures and How to Disambiguate Them

Once you have the pool contents and the account index, three signatures cover almost every stuck-extrinsic report. The first is a genuinely waiting extrinsic: it appears in author_pendingExtrinsics, and its decoded nonce equals the on-chain nonce, meaning it is next in line and simply has not been included yet. This is normal and resolves on its own unless the chain is stalled, which you can check with Polkadot system health and sync state monitoring.

The second is a rejected or dropped extrinsic: it is absent from the pool and absent from the chain. This is the signature that confuses callers most, because the submission call may have returned success. The extrinsic was accepted into the pool and later removed, most often because its mortality era expired or because a sibling with the same tag failed and the tag was banned. The third is a dispatch failure: the extrinsic is included in a block but its event shows a failure, which is a runtime execution problem rather than a pool problem, and belongs to the dispatch-error decode path.

Disambiguation is mechanical once you have all three reads. Presence in the pool plus nonce equality means wait. Absence from both pool and chain means re-submit with a fresh era after checking the nonce. Inclusion with a failed event means decode the dispatch error and fix the call.

  • Waiting: in pool, decoded nonce equals on-chain nonce.
  • Dropped or rejected: absent from pool, absent from chain, accountNextIndex may have reverted.
  • Dispatch failure: included in a block, failure event present, see the dispatch-error article.
  • Nonce gap: in pool as future, decoded nonce greater than on-chain nonce plus one.

Mortality, the Stale Tag, and Silent Disappearance

Mortality is the usual reason a long-queued extrinsic silently disappears. Every extrinsic carries an era that bounds how many blocks it remains valid for. When that window passes, the pool drops the extrinsic, and because the drop is not an error response to your original submission, nothing tells you. The extrinsic simply stops appearing in author_pendingExtrinsics, and accountNextIndex reverts toward the on-chain nonce.

The Substrate transaction format documentation defines the era and the validity model that produces this behavior. The operational takeaway is that a long-lived pending extrinsic is not a stable state; it has a deadline. If you are building a retry loop, the era is the clock you must respect, and the pool read is how you observe the deadline passing. A mortal extrinsic that has been pending across many blocks is a candidate for expiry, not for patience.

The stale tag is related but distinct: it is applied when an extrinsic is no longer valid at the time the pool re-validates it, which can happen for reasons beyond era expiry, including a nonce that has since been consumed by another path. Either way, the observable is the same: the extrinsic leaves the pool without a corresponding inclusion.

A Bounded Diagnostic Loop with a Results Table

The reliable pattern is a bounded loop that records a small tuple per extrinsic and re-reads both the pool and the account index on each poll. Record the extrinsic hash, the decoded nonce, the first-seen timestamp, the current accountNextIndex, and the on-chain nonce. Stop after a fixed number of polls or a fixed wall-clock budget so the loop cannot run forever against a stalled chain.

Run this against your own endpoint and fill in the table with your own observations. Do not rely on numbers from any article, including this one; pool behavior is chain-specific and load-specific. The table is a measurement instrument, not a benchmark.

  • Results Table columns: poll tag, timestamp, pool size, extrinsic hash, decoded nonce, accountNextIndex, on-chain nonce, verdict.
  • Verdict values: waiting, future, dropped, included-ok, included-failed.
  • Fill the table from your own endpoint; treat any published numbers as illustrative only.
const ENDPOINT = 'https://your-polkadot-endpoint.example';
const ADDRESS = '15oF4uVJwmo4TdGW7VfQxNLavjCXviqxT9S1MgbjMNHr6Sp5';

async function rpc(method, params) {
  const res = await fetch(ENDPOINT, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ jsonrpc: '2.0', id: 1, method, params })
  });
  const json = await res.json();
  if (json.error) throw new Error(JSON.stringify(json.error));
  return json.result;
}

async function poll(tag) {
  const [pool, nextIndex] = await Promise.all([
    rpc('author_pendingExtrinsics', []),
    rpc('system_accountNextIndex', [ADDRESS])
  ]);
  console.log(JSON.stringify({
    tag,
    at: new Date().toISOString(),
    poolSize: pool.length,
    accountNextIndex: nextIndex
  }));
}

(async () => {
  for (let i = 0; i < 10; i++) {
    await poll('poll-' + i);
    await new Promise(r => setTimeout(r, 6000));
  }
})();

Decoding Pool Entries Against Runtime Metadata

author_pendingExtrinsics returns hex, and hex is only meaningful once decoded against the metadata of the chain you queried. A common mistake is to attempt to parse the hex as an address or as a JSON object; it is neither. The decode yields the signer, the nonce, the era, the tip, and the call, and it is the nonce and era that drive the diagnostics above.

If you are already decoding submission errors, you have most of the machinery. The decoding Polkadot extrinsic submission errors article walks through the metadata-driven decode, and the same approach applies to pool entries. The only difference is that pool entries are unsigned from your perspective until you decode the signer, whereas submission errors arrive in the context of a submission you already made.

One operational note: metadata changes across runtime upgrades. A decoder pinned to an old metadata version can misread fields after an upgrade. Pin your decode to the metadata returned by state_getMetadata at the time of the read, or refresh it on a schedule.

Pairing Pool Reads with Fee and Weight Context

Pool state and fee estimation are separate concerns, but they interact when an extrinsic is future-queued behind a gap. If you are deciding whether to bump the tip or re-submit, the weight-to-fee context matters, and the Polkadot weight-to-fee estimation with payment_queryInfo article covers how to price a call before submission. Pool reads tell you whether the call is waiting; payment_queryInfo tells you what it will cost to replace it.

Do not conflate the two. A high tip does not move a future-queued extrinsic past a nonce gap; the gap must be filled first. The pool read is what reveals the gap, and the account index is what confirms it. Fee context only becomes relevant once the extrinsic is ready and you are competing for inclusion.

Limitations and Tradeoffs of Pool Reads

Pool reads are point-in-time and node-local. author_pendingExtrinsics reflects the pool of the specific node you queried, not a network-wide view. Two nodes behind the same load balancer can return different pool contents, and a node that is syncing may return a pool that does not reflect the current chain head. This is documented behavior of the method, not a defect, but it means you should not treat a single read as authoritative for the network.

The method also returns no ordering guarantee and no metadata about why an extrinsic is in the pool. You cannot tell from the response alone whether an entry is ready or future; you infer that from the nonce comparison. And because the pool is bounded by node configuration, a busy node may evict entries under pressure, which is another path to silent disappearance that looks identical to mortality expiry from the outside.

Finally, provider behavior varies. Some managed endpoints restrict or rate-limit author_* methods, and some expose only a subset. Treat the availability of author_pendingExtrinsics as documented-but-provider-dependent, and verify it against your own endpoint before building a dependency on it.

  • Node-local, not network-wide: two endpoints can disagree.
  • No ordering guarantee and no ready/future label in the response.
  • Pool eviction under pressure is indistinguishable from expiry without extra context.
  • author_* availability and rate limits vary by provider.

Troubleshooting Common Pool-Read Problems

If author_pendingExtrinsics returns an error rather than an array, the method is likely unavailable or restricted on that endpoint. Confirm the method name and parameters against the polkadot.js JSON-RPC reference, then test against a different endpoint. If the array is empty but you expected entries, check whether the node is fully synced; a syncing node may not have the pool state you expect, which is covered in Polkadot system health and sync state monitoring.

If accountNextIndex and the on-chain nonce are equal but you believe an extrinsic is pending, the extrinsic is not in the pool. Re-check the era and the nonce you signed with. A nonce below the on-chain nonce is a stale nonce and will be rejected at the pool boundary. A nonce far above the on-chain nonce creates a gap and parks the extrinsic in the future queue.

If the pool contains entries you cannot decode, your metadata is likely stale or from the wrong chain. Refresh metadata and retry. If decoding succeeds but the nonce looks wrong, verify you are reading the signer field and not an offset into the call data.

Next Steps for Production Monitoring

The natural next step is to turn the bounded loop into a monitored signal. Emit the tuple (extrinsic hash, nonce, first-seen, accountNextIndex, on-chain nonce) to your metrics system, and alert on the transition from waiting to dropped rather than on the absolute pool size. Pool size alone is noisy; the transition is the actionable event.

For endpoint selection and method availability, start from the Polkadot RPC endpoints (RPC Assistant) page and the Polkadot network page. If you are sizing this for production traffic, the RPC pricing and API service pages describe how managed access is structured. More infrastructure playbooks like this one live in the OnFinality Learn hub.

A reasonable production posture is: poll the pool and the account index on a fixed interval, decode against freshly fetched metadata, classify each tracked extrinsic into one of the three signatures, and route dispatch failures to the decode path. That covers the read side of the pool completely, and leaves only the submission and fee side to the companion articles.

Never Worry about Infrastructure Again

OnFinality takes away the heavy lifting of DevOps so you can build smarter and faster.

Get Started