Polkadot transaction fees are computed by the transaction-payment pallet as the sum of a base fee, a length fee, and a weight fee. The weight fee is derived from the computed weight of the extrinsic, which depends on execution and the current state, and is multiplied by a dynamic fee multiplier that rises when blocks are full and decays when they are not. The payment_queryInfo RPC method returns a partial fee estimate for a fully constructed and signed extrinsic, while payment_queryFeeDetails provides a per-component breakdown. Because the estimate is computed against the current state and multiplier, it is a prediction, not a guarantee; clients should re-estimate close to submission and be aware of batch and tip handling.
Polkadot Fee Estimation and the Transaction-Payment Pallet
Polkadot transaction fees are calculated by the transaction-payment pallet, which sums three components: a base fee, a length fee proportional to the encoded size of the extrinsic, and a weight fee derived from the computed weight of the call. This formula is documented in the Polkadot Developer Documentation's Calculate Transaction Fees page, which is the authoritative source for the fee components and the pallet that computes them.
The base fee is a fixed amount per extrinsic, the length fee scales with the byte length of the encoded extrinsic, and the weight fee is the product of the computed weight and a weight-to-fee conversion factor, further multiplied by a dynamic fee multiplier. The multiplier adjusts based on block fullness: it rises when blocks are consistently full and decays when they are not, as described in the Polkadot documentation on transaction fees.
Because the weight component depends on execution and the current state, the total fee cannot be predicted from the call alone. This is why the RPC methods payment_queryInfo and payment_queryFeeDetails exist: they simulate the extrinsic against the current state to return an estimate.
- Base fee: fixed per extrinsic.
- Length fee: proportional to encoded extrinsic size.
- Weight fee: computed weight × weight-to-fee factor × dynamic multiplier.
Weight V2: Two-Dimensional Weight and Its Impact on Fees
Weight V2 introduced a two-dimensional model with refTime (reference time) and proofSize (proof size). refTime represents the computational time, while proofSize represents the size of the proof required for the operation. Both dimensions are used to compute the weight fee, as documented in the Substrate documentation on weights and the Polkadot documentation on Weight V2.
The weight of a call is not known until it is executed, because it depends on the storage reads and writes performed. For example, a transfer may have a different weight depending on whether the recipient account already exists. Therefore, the weight component of the fee cannot be predicted from the call alone without execution.
The payment_queryInfo method simulates the extrinsic to compute the weight and returns the partial fee. This is why it requires a fully constructed and signed extrinsic: the signature is part of the encoded extrinsic, and the weight calculation may depend on the signature length and the call data.
- refTime: computational time.
- proofSize: size of the proof.
- Weight is determined at execution, not from the call alone.
payment_queryInfo: Request Parameters and Response Fields
The payment_queryInfo method accepts a single parameter: the fully constructed and signed extrinsic as a hexadecimal string. According to the Polkadot.js API JSON-RPC reference, the method returns a partial fee and, where present, the weight of the extrinsic. The partial fee is the estimated fee for the extrinsic, excluding any tip.
Calling payment_queryInfo with an unsigned call or a call before signing fails because the extrinsic hex must include the signature. The method simulates the extrinsic as it would be included in a block, and the signature is part of that simulation. If you pass an unsigned extrinsic, the method will return an error, typically indicating that the extrinsic is invalid or cannot be decoded.
The response includes the partial fee as a string representing the amount in the smallest unit (e.g., Planck for DOT). It may also include the weight, which is useful for understanding the weight component. However, the partial fee already includes the weight fee, so you do not need to compute it separately.
- Parameter: signed extrinsic hex.
- Returns: partial fee (string) and optionally weight.
- Fails if extrinsic is unsigned or malformed.
payment_queryFeeDetails: Per-Component Fee Breakdown
The payment_queryFeeDetails method provides a richer response than payment_queryInfo: it returns the fee broken down into its components: base, len, adjustedWeight, and tip. This allows a client to see exactly where the cost comes from, as documented in the Polkadot.js API JSON-RPC reference.
The adjustedWeight component is the weight fee after applying the dynamic fee multiplier. The tip is the optional additional amount the sender included to prioritize the transaction. The base and len components are the fixed and length fees, respectively.
Using payment_queryFeeDetails is recommended when you need to understand the fee composition or when you want to display a breakdown to users. It accepts the same parameter as payment_queryInfo: the signed extrinsic hex.
- Returns: base, len, adjustedWeight, tip.
- adjustedWeight includes the dynamic multiplier.
- Same parameter as payment_queryInfo.
State-Dependent Estimates and the Dynamic Fee Multiplier
The weight fee is multiplied by a dynamic fee multiplier that adjusts based on block fullness. When blocks are full, the multiplier rises, increasing the weight fee; when blocks are not full, it decays. This mechanism is described in the Polkadot documentation on transaction fees.
Because the multiplier changes over time, two estimates for the same extrinsic at different times will differ. An estimate obtained when the multiplier is low may be lower than the fee actually charged if the multiplier rises before the transaction is included. Conversely, if the multiplier decays, the actual fee may be lower than the estimate.
The multiplier is stored in the transaction-payment pallet's storage and can be read at a specific block using state queries. This allows a client to determine whether the quoted fee is at a normal or elevated multiplier. For more on reading storage at a block, see Polkadot state at a block with state_queryStorageAt.
- Multiplier rises when blocks are full, decays when not.
- Estimates vary with the multiplier at the time of query.
- Read multiplier from storage to assess fee level.
# payment_queryInfo takes a signed extrinsic hex; read the multiplier for the same block.
curl -s -X POST "$POLKADOT_RPC" -H 'Content-Type: application/json' --data '{"jsonrpc":"2.0","id":1,"method":"payment_queryInfo","params":["0x<signed-extrinsic-hex>"]}'
# The transaction-payment fee multiplier raises the weight component when blocks are full.
curl -s -X POST "$POLKADOT_RPC" -H 'Content-Type: application/json' --data '{"jsonrpc":"2.0","id":2,"method":"state_getStorage","params":["0x<twox64_concat(\"TransactionPayment\", \"NextFeeMultiplier\")>"]}'Reading the Fee Multiplier from Transaction-Payment Storage
The fee multiplier is stored in the transaction-payment pallet under the storage item NextFeeMultiplier. You can query it at a specific block using the state_getStorage RPC method or via the polkadot.js API. The value is a fixed-point number representing the multiplier.
To read the multiplier, you need the storage key for NextFeeMultiplier. You can obtain it from the metadata using the Substrate state_getMetadata and runtime versions guide. Alternatively, the polkadot.js API provides a convenient way to query it.
Knowing the multiplier helps interpret the estimate: if the multiplier is high, the weight fee is elevated, and the estimate may be higher than usual. If it is low, the estimate is closer to the base and length fees.
- Storage item: NextFeeMultiplier.
- Query via state_getStorage or polkadot.js API.
- High multiplier indicates elevated weight fee.
Runnable Node.js Example: Estimating Fees with @polkadot/api
The following Node.js script uses @polkadot/api to connect to a Polkadot RPC endpoint, build a transfer extrinsic, estimate its fee with payment_queryInfo, read the fee multiplier, and compare the estimate to a batch of the same calls. It assumes you have a funded account and a valid endpoint.
The script first constructs the extrinsic, signs it, and then calls payment_queryInfo with the signed extrinsic hex. It also queries the NextFeeMultiplier storage item. Finally, it builds a batch of the same transfer and estimates its fee to show that the batch fee is not simply the sum of per-call estimates.
const { ApiPromise, WsProvider, Keyring } = require('@polkadot/api');
async function main() {
const provider = new WsProvider('wss://rpc.polkadot.io');
const api = await ApiPromise.create({ provider });
const keyring = new Keyring({ type: 'sr25519' });
const alice = keyring.addFromUri('//Alice');
const transfer = api.tx.balances.transferKeepAlive('14E5nqKAp3oAJcmzgZhUD2RcptBeUBScxKHgJKU4HPNcKVf3', 1000000000);
const signed = await transfer.signAsync(alice);
const hex = signed.toHex();
const info = await api.rpc.payment.queryInfo(hex);
console.log('Partial fee:', info.partialFee.toString());
console.log('Weight:', info.weight.toString());
const multiplier = await api.query.transactionPayment.nextFeeMultiplier();
console.log('Next fee multiplier:', multiplier.toString());
const batch = api.tx.utility.batchAll([transfer, transfer]);
const signedBatch = await batch.signAsync(alice);
const batchInfo = await api.rpc.payment.queryInfo(signedBatch.toHex());
console.log('Batch partial fee:', batchInfo.partialFee.toString());
await api.disconnect();
}
main().catch(console.error);Results Table: Measuring Estimates Against Your Own Endpoint
To validate fee estimation against your own endpoint, fill in the following table with data from your own tests. Use a consistent extrinsic (e.g., a transfer) and record the estimate returned by payment_queryInfo, the weight, the multiplier at the time of estimation, and the realized fee after inclusion. This will help you understand how the estimate compares to the actual fee in your environment.
Run the test multiple times at different block heights to observe the effect of the multiplier. Note that the realized fee is the actual fee deducted from the sender's account, which you can find in the transaction payment event or by comparing balances before and after inclusion.
- Estimate returned (partial fee):
- Weight:
- Multiplier at time of estimate:
- Realized fee after inclusion:
- Difference (realized - estimate):
Failure Modes and Troubleshooting
Common failure modes include: 'payment_queryInfo returned an error' for an unsigned or malformed extrinsic; an estimate that is lower than the fee actually charged because the multiplier rose; a batch whose fee is not the sum of per-call estimates; and endpoints that do not expose the payment namespace. The latter varies by provider, so check your provider's documentation.
If you receive an error for an unsigned extrinsic, ensure you sign the extrinsic before calling payment_queryInfo. If the estimate is lower than the actual fee, re-estimate closer to submission and consider adding a tip to prioritize inclusion. For batches, the fee is computed for the entire batch as a single extrinsic, so it is not the sum of individual estimates; use payment_queryInfo on the batch extrinsic itself.
If your endpoint does not support the payment namespace, you may need to switch to a provider that does. OnFinality's Polkadot RPC endpoints (RPC Assistant) can help you find a suitable endpoint. For more on handling dispatch errors, see Decoding Polkadot extrinsic dispatch errors.
- Unsigned extrinsic: sign before calling.
- Multiplier rise: re-estimate close to submission.
- Batch fee: estimate the batch extrinsic, not individual calls.
- Endpoint support: varies by provider.
Limitations and Tradeoffs of Fee Estimation
The fee estimate is a prediction against the current state and the current fee multiplier. It is not a guarantee of the fee that will be charged. The actual fee depends on the state at the block in which the transaction is included, which may differ from the state at the time of estimation.
Tip handling: the partial fee returned by payment_queryInfo does not include the tip. If you include a tip, the total fee will be higher. The tip is added to the fee and is not subject to the multiplier.
Because the estimate is state-dependent, it is advisable to re-estimate close to submission, especially during periods of high network activity. For a broader overview of Polkadot RPC methods, see the OnFinality Learn hub and the Polkadot RPC endpoints (RPC Assistant).
- Estimate is not a guarantee.
- Tip is not included in partial fee.
- Re-estimate close to submission.
Next Steps: Integrating Fee Estimation into Your Application
To integrate fee estimation into your application, use payment_queryInfo or payment_queryFeeDetails to get an estimate before submitting a transaction. Display the estimate to users, but make it clear that it is an estimate and may change. Consider reading the fee multiplier to provide context on whether fees are currently elevated.
For production use, ensure your RPC endpoint supports the payment namespace. OnFinality provides reliable Polkadot RPC endpoints and an API service that can be used for fee estimation. For pricing details, see RPC pricing.
To deepen your understanding of Polkadot RPC, explore related guides such as Reading Polkadot extrinsics and events at a block and Polkadot GRANDPA finality and justifications.
- Use payment_queryInfo or payment_queryFeeDetails.
- Read multiplier for context.
- Choose an endpoint that supports the payment namespace.