9.5 KiB
Mempool
The mempool is the node's pending transaction area. Transactions enter the mempool after they are decoded, verified, stored in PostgreSQL, and relayed to connected peers.
Contractless uses mempool state during transaction validation. Pending balance changes, transaction fees, loan payments, swap tips, storage fees, and other unconfirmed effects are included in the checks so a wallet cannot normally spend the same confirmed balance twice while transactions are still waiting to be mined.
Contractless does not have replace-by-fee. There is no transaction replacement mechanism, no fee bumping mechanism, and no user-facing mempool removal command. A pending transaction either remains pending, becomes processed when included in a block, is restored during a short orphan rollback, or disappears when the node restarts and the live mempool is cleared.
Tools
This page covers:
lookup_mempool_tx_by_addresslookup_mempool_tx_by_signaturelookup_mempool_tx_count
On Windows, the compiled binaries usually end in .exe. For example, lookup_mempool_tx_count becomes lookup_mempool_tx_count.exe.
Important Behavior
Pending Balances
Most transaction verification paths call the shared balance check before accepting a transaction into the mempool.
That check compares the saved wallet balance against already pending mempool usage:
- Base coin transfers reserve the transfer amount plus the fee.
- Token and NFT transfers reserve the token/NFT amount and still reserve the base coin fee.
- Token creation, NFT creation, marketing records, vanity address transactions, storage keys, and storage values reserve their base coin fee.
- Swaps reserve each side's offered asset, tip, and base coin fee.
- Loan contracts reserve the lender's loan amount, the borrower's collateral, and the loan fee.
- Loan payments reserve the payment amount, tip, and fee.
- Collateral claims reserve the claim fee.
This is why a wallet balance in the GUI can differ from a confirmed-chain-only lookup: the wallet tries to show spendable state after pending transactions are considered.
No Replace-By-Fee
Once a transaction is accepted into a node's mempool, the protocol does not provide a way to replace it with another transaction by paying a higher fee.
If a user broadcasts a second transaction that tries to spend the same funds already reserved by a pending transaction, the normal validation result is rejection for insufficient available funds.
Race Conditions and Double-Spends
The intended rule is simple:
The first valid transaction to be accepted into the mempool or included in a valid block consumes the spendable balance. Later conflicting transactions fail validation.
There are two layers that matter:
-
Mempool admission
When a node receives a transaction, it verifies signatures, registered addresses, fees, duplicate transaction hashes, and available balance after pending mempool usage. If the first transaction is already in that node's mempool, a second transaction spending the same funds should fail the pending-balance check.
-
Block validation
When transactions are verified inside a block, Contractless uses a block-local balance reservation tracker. That tracker aggregates debits by address and asset across the whole block. If two transactions inside the same block try to spend the same funds, the block is rejected with an insufficient block-local funds error. The tracker also rejects duplicate transaction hashes and duplicate signatures inside the same block.
This means a confirmed double-spend should not be valid even if two conflicting transactions reach different nodes at nearly the same time.
There is one important implementation detail: most transaction submit paths do verify -> insert into mempool. That is not a single PostgreSQL transaction that locks the spender's balance across all transaction tables. Loan payments have an explicit submit lock because overpaying the same loan through concurrent pending payments was handled separately. Other transaction types rely on pending-balance checks at admission and the block-local reservation tracker at block validation.
So the practical behavior is:
- If the same node sees transaction A first, transaction B should usually fail when it arrives because A is already reserving the balance.
- If two different nodes receive conflicting transactions at the same time, both may briefly hold different pending transactions.
- Only one conflicting spend can become part of the accepted chain. A block containing both should be rejected, and a block containing the losing spend after the winning spend is already confirmed should fail validation.
If the project later needs stronger mempool convergence before mining, the submit path should be tightened so balance reservation and mempool insertion are atomic per spending address/asset across transaction types.
lookup_mempool_tx_count
lookup_mempool_tx_count asks a configured node for the number of rows currently stored in the mempool tables.
Usage
./lookup_mempool_tx_count
The tool asks for the wallet path and wallet decryption key for the node handshake. This lookup does not spend funds.
Example Return
{
"mempool_tx_count": 3
}
The count includes rows across all mempool tables. Processed rows may remain briefly after mining so they can be restored during short orphan rollbacks. Because of that, this count is a mempool table count, not always a strict "currently spendable pending transactions only" count.
lookup_mempool_tx_by_signature
lookup_mempool_tx_by_signature asks a configured node for one unprocessed mempool transaction by signature.
Usage
./lookup_mempool_tx_by_signature <signature>
Example:
./lookup_mempool_tx_by_signature 39a9b06ec11c9d01e08383f7591f76573afbd456e4a488e59f777f08f7dcd3...
The signature is passed as hex. The tool asks for the wallet path and wallet decryption key for the node handshake.
What It Searches
The lookup checks unprocessed rows in the mempool tables for:
- Transfers
- Token creation
- Token issuance
- Burns
- NFTs
- Marketing records
- Vanity addresses
- Swaps, by either signature
- Loan contracts, by either signature
- Loan payments
- Collateral claims
- Storage keys
- Storage values
Example Return
For a pending transfer:
{
"unsigned_transfer": {
"txtype": 2,
"time": 1783657720,
"value": 100000000,
"coin": "CLTC ",
"nft_series": 0,
"sender": "1439f758f93333734778a6459dc64b8d63a74c06.cltc",
"receiver": "7ba97ce531937388ef8bca4d8a4ca54cefbefb87.cltc",
"txfee": 1000000
},
"signature": "<signature>"
}
If the signature is not found, the tool prints:
Transaction not found in mempool.
lookup_mempool_tx_by_address
lookup_mempool_tx_by_address asks a configured node for unprocessed mempool transactions related to an address.
Usage
./lookup_mempool_tx_by_address <wallet_address>
Example:
./lookup_mempool_tx_by_address 1439f758f93333734778a6459dc64b8d63a74c06.cltc
The address can be a registered short address or vanity address. The tool resolves vanity input to the canonical short address before making the request.
The tool asks for the wallet path and wallet decryption key. It uses the wallet key for the authenticated node handshake and vanity resolution.
What It Returns
The response is a JSON object containing a transactions array.
{
"transactions": []
}
If matching pending transactions exist, each transaction is decoded into its transaction-specific JSON shape:
{
"transactions": [
{
"unsigned_transfer": {
"txtype": 2,
"time": 1783657720,
"value": 100000000,
"coin": "CLTC ",
"nft_series": 0,
"sender": "1439f758f93333734778a6459dc64b8d63a74c06.cltc",
"receiver": "7ba97ce531937388ef8bca4d8a4ca54cefbefb87.cltc",
"txfee": 1000000
},
"signature": "<signature>"
}
]
}
Address Matching
The mempool address lookup searches the fields that make sense for each transaction type:
- Transfer receiver
- Token creator
- Issue-token creator
- Burn address
- NFT creator
- Marketing advertiser
- Vanity address owner
- Swap sender1 and sender2
- Loan lender and borrower
- Loan payment address
- Collateral claim address
- Storage key address
- Storage value address
The lookup is mainly intended for pending visibility. For confirmed history, use the normal history and transaction lookup tools.
Mempool and Block Save Lifecycle
When a node mines a block, it selects unprocessed mempool rows by fee priority and time. Selected rows are streamed into the block payload and their balance effects are applied through the normal block save path.
When a block is accepted:
- Locally selected mempool rows are marked
processed=true. - Synced or discovered block transactions are marked processed by signature if matching local mempool rows exist.
- Processed rows are retained briefly so short orphan rollbacks can restore them.
- Cleanup trails the chain tip so recently processed rows are not deleted too early.
When an orphan rollback restores a block, recently processed mempool rows can be unmarked by signature and returned to pending status if they are still available in the local mempool tables.
Common Usage
Check the total mempool size:
./lookup_mempool_tx_count
Look up one pending transaction by signature:
./lookup_mempool_tx_by_signature <signature>
Look up pending transactions for an address:
./lookup_mempool_tx_by_address <wallet_address>