Wallet Default nLockTime Practices

Quick Community Ask

I need community help testing which BCH wallets/services set transaction locktime to the current block height. If a wallet continues setting nLockTime to current height, it will break when we activate faster blocks (CHIP-2025-03). Wallets that set it to 0 will be unaffected. To mitigate, we first need a list of wallets and their behavior.

How to test:

  1. Send a small transaction with the wallet or service you want to check.
  2. Open the TXID on bchexplorer.cash and look at “Locktime” under Details.
  3. Report the wallet or service name and whether Locktime = 0 or equals the current block height (or something else).

Report by replying to this post with something like Wallet: MyWallet; nLockTime: 0;.

Here’s a start:

Wallet nLockTime
Electron Cash height
BCHN height
Cashonize 0
Selene 0
mainnet.cash 0
Cauldron 42
Paytaca 0
Trezor Suite 0
Ledger Live 0
Trust Wallet 0
Thorwallet 0

I will edit updates into this table.


Background: Because of Peter Todd’s anti‑fee‑sniping patch, some wallets set nLocktime = tip_height . Our faster‑blocks upgrade normalizes locktime to 10‑minute ticks, so those transactions would only become valid many blocks later than expected (they’d be locked far into the future). Changing the default to 0 before activation prevents breakage. Wallets like Cashonize and Selene already do this; Electron Cash and BCHN do not.

The nLocktime = tip_height default was supposed to guard against hypothetical fee sniping issues but really this is anti-user: why should a user care if his TX gets into an earlier block? There was no analysis done about which conditions could even result in these scenarios. If it’s still a concern, forward-compatible way is to set it to MTP, but just setting it to 0 is simpler, and many wallets already do exactly that.

2 Likes

A Peter Todd classic.

The amount of small and large problems he has directly had a hand in introducing into Bitcoin is truly incredible.

1 Like

It seems the swap function for futurebitcoin.cash is setting nLockTime irrespective of direction, but I fixed that on the second iteration.

Wallet function nLockTime
mainnet-js * 0
FBCH place nLockTime
FBCH redeem nLockTime
Vox Future/place 0
Vox Future/redeem nLockTime

Unspent v0-2 don’t use nLockTime.

FBCH-1000000 is in April 2027, I’ll probably sunset the old future dapp by then.

1 Like

This is at the app layer, right? You could update the app to set nLockTime = ticks / 600, right? Then it will continue working correctly. The CHIP maintains nLockTime semantics as idealized 10-minute “chunks”. Now it takes exactly 1 block to move evaluated locktime +1. Post-CHIP it will take exactly 10 blocks to move evaluated locktime +1.

Unspent v0-3 and FBCH only use block times. That behavior isn’t changing correct?

1 Like

You mean MTP? Correct, MTP will just continue to work as-is, although it will get closer to real time (6 blocks deep will become 6 minutes instead of 60).

From contract’s point of view, nLockTime will continue to work as-is even in height mode: the value it sees will mean number of 10-min “chunks” since genesis. E.g. suppose we activate faster blocks at height 1M (first 1-min block will be 1,000,001).

Block count Max. accepted locktime: “height” normalized to 10 minutes
999,999 999,999
1,000,000 1,000,000
1,000,001 1,000,000 (this block added only +1 min, so locktime evaluates against 1M)
1,000,009 1,000,000
1,000,010 1,000,001 (here we accumulated 10 x 1 min blocks, so locktime evaluation finally does +1)
1,000,011 1,000,001

Suppose a contract requires locktime be incremented +1 on every spend. It will continue to work exactly the same as before (can be spent once every 10 min).

But a wallet that blindly sets nLockTime = current height will be setting locktime further into the future than it thinks, so these off-chain calculations will need updating.

Bitcoin is a timestamp service. The base unit of time has always been blocks.

I don’t see an inherent issue if the base timestamp unit is granulated into 600 ticks.

There can be 10 sub-blocks per block, or 20.

The tick after 1,003,670 could be 1003670,060 or 1,003,670.166, but if it’s 1,003,671, we’re essentially scaling the base time unit.

If the base timestamp unit is scaled, mid-project, that will permanently bork the nomenclature for FBCH and height based BIP65/68 generally.

If someone is holding FBCH-2e6 until 2046, will explorers and wallets report the block time to be 2M or 11M?

If someone wants to HODL coins in a vault until 2046, do they specify 2M or 11M as the target block height?

Do the tickers for the current set of 5800 Future series still align with what block explorers and wallets say the current time is?

At 1,100,000, what will fulcrum say the time is?

Given the resistance current wallet developers have to implementing DNS-resolved BCMR, and the number of wallet developers in the space beholden to a centralized influence/funding source, it seems like the current active crop of developers might just default to using the new heights as equivalent with the old heights to eff the UI and Dev-UX for height based BIP65/68.

I would expect everyone obsessed with dollar swaps to bork the nomenclature and UI for the native currency markets to the greatest extent possible. It’s not really an issue that can be fixed with a CHIP, because a nomenclature can’t be forced on everyone if their paychecks depend on them not using it.

1 Like

I have sent and received BCH transactions using Trezor Suite, Ledger Live, Trust Wallet, and Thorwallet. In all cases, the transactions show the same default values nLockTime = 0

2 Likes

Yeah, from Script API PoV, our faster blocks will look like sub-division. It will take 10x 1-minute blocks to move the nLockTime unlock threshold for +1, where before it took 1x 10-minute block to move it for +1.

Unlocking will be consistent with 10-min based scale, but anyone setting nLockTime on new TXs will need to know this detail, so instead of just using raw height (which is actually block count, and post-activation it will be a mix of 10-minute and 1-minute blocks) they will have to make use of ticks; and if they want current tip then set nLockTime to ticks / 600.

Contracts that internally calculate the offset will automatically get it right because they don’t even know the real height, they just know the last set nLockTime - which will continue to be in 10-min units.

This way, blockchain will remain internally consistent; but external agents who construct new TXs need to be aware of nLockTime's unit.

For this example, they will need to specify 2M as the nLockTime even though block count will actually reach 11M. The change is in off-chain calculation: you can’t just read a height from block explorer and offset it by timeDelta/600 because 1 unit of height won’t anymore match 1 unit of 10min.

So you need to know either the tick or the number of 10-min chunks, and then you can calculate the height-based nLockTime for your target date:

(current_tick + (target_unix_time - current_unix_time)) / 600

Wdym by “time”? Explorers etc. report time as block’s timestamp, so nothing changes there. The “height” field will continue to expose the real height (11M in the above example), but we’ll be adding ticks to BCHN APIs, and I expect to Fulcrum’s too, and at least some explorers (who report output of BCHN’s API); so if you’re interested in chain progression in uniform units then you can always do ticks / 600 to get the number of 10-min “chunks” since genesis.

I’m more optimistic: if we provide a nice ticks API, downstream consumers can easily adapt to nLockTime = ticks / 600.

1 Like

This:

In this paper, we propose a solution to the double-spending problem using a peer-to-peer distributed timestamp server to generate computational proof of the chronological order of transaction.

So although many people prefer a Gregorian calendar, and many developer’s assume every timestamp is a UNIX epoch, on bitcoin, time is a block height, and the duration is/was about 10 minutes.

So the CHIP could increase the granularity of the base time unit and have ‘block’ 1,100,000 occur at the same time as the no change case. But if the chip mixes old heights and new heights, then 1,100,000old will occur around 1,055,000new, and anyone using the old time units after the upgrade will have to do a little bit of arithmetic to convert between the two time scales.

BitJson is under the impression there’s only one contract dev using the native timestamps of the server. But I think there are some earlier contracts that use them too. I think Badgers and HODL also use them.

I’d greatly prefer if the time after activation was incremented by 0.1 instead of a whole number. But if nobody else wants to keep nomenclature for the native timestamps, I’m not going to fight about it.