Skip to main content

Global Variables

Global time lock variables​

Bitcoin Cash has support for different kinds of time locks, which can be used to specify time-based conditions inside Bitcoin Cash contracts. These time locks can be separated by three main attributes: location, targeting and metric. The location can be the transaction level or the contract level, but contract level locks also require you to use a corresponding transaction level lock. The targeting can be relative (e.g. at least 4 hours have passed) or absolute (e.g. the block number is at least 600,000). The metric can be blocks or seconds.

It can be difficult to fully grasp the intricacies of time locks, so if you're starting out it is recommended to start off with the simplest version: absolute block-based time locks. If you do want to dive into the more advanced uses of time locks, James Prestwich wrote the best article explaining time locks in Bitcoin that also fully applies to Bitcoin Cash.

tx.time​

tx.time is used to create an absolute time lock on the transaction. The value of tx.time can either represent the block number of the spending transaction or its timestamp. When comparing it with values below 500,000,000, it is treated as a blocknumber, while higher values are treated as a timestamp.

tx.time corresponds to the nLocktime field of the current transaction using the OP_CHECKLOCKTIMEVERIFY opcode. Because of this, tx.time may only appear in one form: as the left-hand side of a >= inside require.

contract Locked(int expiration) {
function claim() {
require(tx.time >= expiration);
}
}
note

To access the value of the nLocktime field for assignment, use the tx.locktime introspection variable.

Because of the way time locks work, a corresponding time lock needs to be added to the transaction. This can be done with the SolidCash SDK by calling setLocktime() on the TransactionBuilder instance.

this.age​

this.age is used to create a relative time lock on a UTXO. The value of this.age can either represent a number of blocks, or a number of chunks, which are 512 seconds. The corresponding transaction level time lock determines which of the two options is used.

this.age corresponds to the nSequence field of the current evaluated UTXO using the OP_CHECKSEQUENCEVERIFY opcode. Because of this, this.age may only appear in one form: as the left-hand side of a >= inside require.

contract Matured(int minAge) {
function claim() {
require(this.age >= minAge);
}
}
note

To access the value of the nSequence field for variable assignment, use the sequenceNumber introspection variable.

Because of the way time locks work, a corresponding time lock needs to be added to the transaction. Pass the raw BIP68 sequence through the addInput() sequence option. A block-based age uses the block count directly. A time-based age uses bit 22 plus a count of 512-second units; the value checked by this.age and the input sequence must use the same encoding:

const oneTimeUnit = (1 << 22) | 1; // one 512-second unit
builder.addInput(utxo, unlocker, { sequence: oneTimeUnit });

Introspection variables​

Introspection functionality is used to create covenant contracts. Covenants are a technique used to put constraints on spending the money inside a smart contract. The main use case of this is limiting the addresses where money can be sent and the amount sent. To explore the possible uses of covenants inside smart contracts, read the SolidCash Covenants Guide.

this.activeInputIndex​

bigint this.activeInputIndex

During the validation of a BCH transaction, every transaction input is evaluated in order, and the contract's code is evaluated in the context of the different inputs. this.activeInputIndex represents the index of the input that is currently being evaluated. This can be used in conjunction with the properties under tx.inputs.

this.activeBytecode​

bytes this.activeBytecode

During the validation of a BCH transaction, every transaction input is evaluated in order, and the contract's code is evaluated in the context of the different inputs. this.activeBytecode represents the contract bytecode of the input that is currently being evaluated.

this.categoryId and this.category​

bytes this.categoryId
bytes32 this.category

this.categoryId is the active contract's raw CashToken category introspection value. It uses consensus byte order and can include the trailing capability byte. this.category strips the capability and reverses the 32 category bytes, producing the display-order ID used by SDK category strings and this.tokens[...].

address(this) and balances​

address address(this)
bigint this.balance
bigint this.outState.balance
bigint addr.balance

address(this) is the locking bytecode of the input being evaluated. this.balance is the value of the contract's satoshi container in this transaction's inputs, and this.outState.balance is its value in the outputs. For any other address, addr.balance sums the values of this transaction's inputs locked to addr; it is not a chain-wide balance. A state variable named balance hides the built-in this.balance.

tx.version​

bigint tx.version

Represents the version of the current transaction. Different transaction versions can have differences in functionality. Currently only version 1 and 2 exist, where only version 2 has support for BIP68.

tx.locktime​

bigint tx.locktime

Represents the nLocktime field of the transaction. This is similar to the tx.time global variable but tx.time can only be used in require statements, not for variable declaration.

tip

The use case for tx.locktime is to read the nLocktime value and add to the local state. Example usage for this is demonstrated in the Sablier example.

tx.inputs​

Represents the list of inputs of the evaluated transaction. This is an array, and cannot be used on itself. You need to access an input with a specific index and specify the properties you want to access.

tx.inputs.length​

bigint tx.inputs.length

Represents the number of inputs in the transaction.

tx.inputs[i].value​

bigint tx.inputs[i].value

Represents the value of a specific input (in satoshis).

tx.inputs[i].lockingBytecode​

bytes tx.inputs[i].lockingBytecode

Represents the locking bytecode (scriptPubKey) of a specific input.

tx.inputs[i].unlockingBytecode​

bytes tx.inputs[i].unlockingBytecode

Represents the unlocking bytecode (scriptSig) of a specific input.

tx.inputs[i].outpointTransactionHash​

bytes32 tx.inputs[i].outpointTransactionHash

Represents the outpoint transaction hash where a specific input was initially locked.

tx.inputs[i].outpointIndex​

bigint tx.inputs[i].outpointIndex

Represents the outpoint index where a specific input was initially locked.

tx.inputs[i].sequenceNumber​

bigint tx.inputs[i].sequenceNumber

Represents the nSequence number of a specific input. This value of the input's sequenceNumber is only consensus-enforced as a relative timelock when using transaction version 2.

tx.inputs[i].tokenCategory​

bytes tx.inputs[i].tokenCategory

Represents the tokenCategory of a specific input. Returns 0x when that specific input contains no tokens. When the input contains an NFT with a capability, the 32-byte tokenCategory is concatenated together with 0x01 for a mutable NFT and 0x02 for a minting NFT.

caution

This is the raw consensus representation, not the display-order ID used by wallets, explorers, and SDK category strings. Split any capability byte and reverse the 32 category bytes before comparing the two representations.

tx.inputs[i].nftCommitment​

bytes tx.inputs[i].nftCommitment

Represents the NFT commitment data of a specific input.

tx.inputs[i].tokenAmount​

bigint tx.inputs[i].tokenAmount

Represents the amount of fungible tokens of a specific input. Maximum size for a tokenAmount is a 64-bit integer.

tx.outputs​

Represents the list of outputs of the evaluated transaction. This is an array, and cannot be used on itself. You need to access an output with a specific index and specify the properties you want to access.

tx.outputs.length​

bigint tx.outputs.length

Represents the number of outputs in the transaction.

tx.outputs[i].value​

bigint tx.outputs[i].value

Represents the value of a specific output (in satoshis).

tx.outputs[i].lockingBytecode​

bytes tx.outputs[i].lockingBytecode

Represents the locking bytecode (scriptPubKey) of a specific output.

tx.outputs[i].tokenCategory​

bytes tx.outputs[i].tokenCategory

Represents the tokenCategory of a specific output. Returns 0x when that specific output contains no tokens. When the output contains an NFT with a capability, the 32-byte tokenCategory is concatenated together with 0x01 for a mutable NFT and 0x02 for a minting NFT.

caution

This is the raw consensus representation, not the display-order ID used by wallets, explorers, and SDK category strings. Split any capability byte and reverse the 32 category bytes before comparing the two representations.

tx.outputs[i].nftCommitment​

bytes tx.outputs[i].nftCommitment

Represents the NFT commitment data of a specific output.

tx.outputs[i].tokenAmount​

bigint tx.outputs[i].tokenAmount

Represents the amount of fungible tokens of a specific output. Maximum size for a tokenAmount is a 64-bit integer.

Constructing locking bytecode​

One of the main use cases of covenants is enforcing transaction outputs (where money is sent). To assist with enforcing these outputs, there is a number of LockingBytecode objects that can be instantiated. These locking bytecodes can then be compared to the locking bytecodes of transaction outputs.

Example​

contract PinnedOutput(bytes20 pkh) {
function spend() {
bytes25 lockingBytecode = new LockingBytecodeP2PKH(pkh);
int value = 10_000;
require(tx.outputs[0].lockingBytecode == lockingBytecode);
require(tx.outputs[0].value == value);
}
}

LockingBytecodeP2PKH​

new LockingBytecodeP2PKH(bytes20 pkh): bytes25

Creates new P2PKH locking bytecode for the public key hash pkh.

LockingBytecodeP2SH20​

new LockingBytecodeP2SH20(bytes20 scriptHash): bytes23

Creates new P2SH20 locking bytecode for the script hash, where scriptHash is the hash160() of your script.

caution

Because the old P2SH20 addresses use hashes which are only 20-bytes in length, they are vulnerable to hash collision in situations where an attacker can introduce arbitrary data to a contract. To solve these security issues P2SH32 has been introduced. It is recommended to always use LockingBytecodeP2SH32 from now on.

LockingBytecodeP2SH32​

new LockingBytecodeP2SH32(bytes32 scriptHash): bytes35

Creates new P2SH32 locking bytecode for the script hash, where scriptHash is the hash256() of your script.

LockingBytecodeNullData​

new LockingBytecodeNullData(bytes[] chunks): bytes

Creates new OP_RETURN locking bytecode with chunks as its OP_RETURN data.

note

LockingBytecodeNullData outputs are provably unspendable, so any BCH sent there would be burned. For these outputs no dust-minimum is enforced so often LockingBytecodeNullData outputs have 0 BCH on them.

Message and origin variables​

These are the Solidity-compatible caller globals, reinterpreted for the UTXO model.

GlobalTypeDescription
msg.senderaddressLocking bytecode of the calling contract (its co-spent master NFT is checked), falling back to tx.origin for a direct spend
msg.valuebigintNet satoshi change in the contract's satoshi container (sum of matching outputs minus sum of matching inputs)
msg.sigbytes4The 4-byte selector of the current public or external function (a compile-time constant)
msg.databytesThis input's unlocking bytecode, with the redeem script stripped
tx.originaddressLocking bytecode of the first P2PKH input that holds no tokens

payable does not gate msg.value in general: only a view public or external function and a constructor that is not payable reject it. A pure function cannot read msg.sender, msg.value, tx.origin, tx.gasprice, or any block.* value. With evmCompat, msg.value is typed uint256.

caution

On Bitcoin Cash, sender identification is weaker than on an account-based chain: msg.sender is derived from the co-spent input set, which the transaction builder chooses, rather than from an authenticated call frame. Prefer checkSig / checkMultiSig for authentication and authorization. The compiler prints a warning for each read of msg.sender and tx.origin.

Block properties​

Bitcoin Cash Script has no opcode for block metadata, so the oracle-backed block.* reads slice the NFT commitment of one shared block-oracle covenant, which the reader co-spends and only its owner updates. The compiler adds no state and no update function to the reader.

GlobalTypeSourceNotes
block.numberuint32oracleThe height the oracle owner last wrote, at most the chain tip when written. Compiler-warned: it can lag the tip
block.timestampuint32oracleThe time the oracle owner last wrote, below the median time past when written. Compiler-warned: a deadline stays open until the owner updates it
block.chainiduint256baked constantThe low byte of the numeric chainId compiler option (chainId & 0xff, default 0). Convention: mainnet 0, chipnet 1, testnet4 2, regtest 255. Needs no oracle
block.difficultyuint32oracleThe raw nBits value. Not randomness
block.coinbaseaddressoracleFull locking bytecode of coinbase output 0
block.basefeeuint32derivedFloor(1000 * block.totalFees / block.size), i.e. the block's realized fee rate in sat/KB
block.sizeuint32oracleBlock size in bytes. A BCH-native extension
block.totalFeesuint32oracleSum of transaction fees in satoshis. A BCH-native extension
block.gaslimitintegerfixed shimAlways 1000000. Compiler-warned
block.blobbasefeeuint32aliasAliases block.basefee; BCH has no EIP-4844 blobs. Compiler-warned
block.prevrandaouint256transaction-local shimabs(int(sha256(input[0] prevout txid))). Compiler-warned
tx.gaspriceuint32derivedThe current transaction's realized fee rate in sat/KB
gasleft()integerfixed shimAlways 1000000. Compiler-warned
caution

block.prevrandao is not secure randomness. The transaction builder chooses which UTXO sits at input 0, so it fully controls the value. Use commit-reveal or a checkDataSig oracle instead.

block.gaslimit and gasleft() are meaningless constants that exist so ported gas-budget checks keep running. Do not branch on their values.

The oracle fields co-spend one shared block-info oracle UTXO read-only; the SDK adds that input. A live network provider needs its blockOracleCategory set, while MockNetworkProvider runs its own oracle.

Example​

contract TimeGated() {
int lastSeen;

function act() public {
require(block.number > 0);
require(block.timestamp > 0);
this.lastSeen = block.timestamp;
}
}
note

Non-oracle alternatives for time and height are usually preferable when they suffice: require(tx.time >= ...) (absolute, OP_CHECKLOCKTIMEVERIFY) and require(this.age >= ...) (relative, OP_CHECKSEQUENCEVERIFY). They need no oracle state and no co-spend.

Deployment, keeper operation, testing, trust assumptions, and the windowed hash protocol are covered in Block Data & Oracles.

blockhash and blobhash​

bytes32 blockhash(int blockNumber)
bytes32 blobhash(int index)

blockhash(n) reads a per-height (n, hash) oracle NFT of the contract's own category. It makes the contract stateful, so call it through StatefulContract; a missing (n, hash) NFT is an error, not a zero hash. blobhash(i) always returns zero: Bitcoin Cash has no EIP-4844 blobs, so it is a porting shim.

ABI encoding and decoding​

The abi.encode*, abi.decode, and schema-indexed abi.decodeAt functions support a compact native BCH format and an opt-in EVM-compatible format. Their exact framing, supported types, selector rules, and packed-encoding security caveat are documented in ABI Encoding & Call Data.

bytes.concat(...) concatenates bytes and string.concat(...) concatenates UTF-8 strings without adding ABI framing.

Type introspection​

type(T).member exposes compile-time information. See Types & Operators for the full table.

Globally available units​

An integer literal can take a suffix of either monetary or temporary units to add semantic value to these integers and to simplify arithmetic. When these units are used, the underlying integer is automatically multiplied by the value of the unit.

BCH Units​

The units sats, finney, bits and bitcoin can be used to denominate Bitcoin Cash amounts.

Example​

require(1 sats == 1);
require(1 finney == 10);
require(1 bits == 100);
require(1 bitcoin == 1e8);

Ethereum Units​

The Solidity units wei, gwei and ether are accepted so ported sources compile, with their standard values (1 ether == 1e18, 1 gwei == 1e9, 1 wei == 1).

Example​

require(1 wei == 1);
require(1 gwei == 1000000000);
require(1 ether == 1000000000000000000);
caution

These denominate ether, not Bitcoin Cash. Bitcoin Cash is denominated in satoshis, where 1 bitcoin == 1e8 sats, so an ether-denominated amount ported unchanged from Solidity will be off by ten orders of magnitude. Use sats / bitcoin for BCH amounts.

Time Units​

The units seconds, minutes, hours, days and weeks can be used to denote time.

caution

Be careful when using these units in precise calendar calculations though, because not every year equals 365 days and not even every minute has 60 seconds because of leap seconds.

Example​

require(1 seconds == 1);
require(1 minutes == 60 seconds);
require(1 hours == 60 minutes);
require(1 days == 24 hours);
require(1 weeks == 7 days);