World Chain Specs
About World Chain
World Chain is a blockchain designed for humans. Prioritizing scalability and accessibility for real users, World Chain provides the rails for a frictionless onchain UX.
Site Navigation
Navigate this site using the sidebar on the left, the search icon found at the top of this page, or the left/right navigation buttons found to the sides of each page.
Priority Blockspace for Humans
Priority Blockspace for Humans introduces a new transaction ordering policy on World Chain that grants verified World ID holders top-of-block priority, reducing friction and making transactions fairer for real users.
Where bots create congestion, PBH is a highway for humans.
PBH Architecture
World Chain is an OP Stack chain that enables Priority Blockspace for Humans (PBH) through the World Chain Builder. World Chain leverages rollup-boost to support external block production, allowing the builder to propose PBH blocks to the sequencer while remaining fully compatible with the OP Stack.
Block Production on the OP Stack
The Engine API defines the communication protocol between the Consensus Layer (CL) and the Execution Layer (EL) and is responsible for orchestrating block production on the OP Stack. Periodically, the sequencer's consensus client will send a fork choice update (FCU) to its execution client, signaling for a new block to be built. After a series of API calls between the CL and EL, the EL will return a new ExecutionPayload
containing a newly constructed block. The CL will then advance the unsafe head of the chain and peer the new block to other nodes in the network.
sequenceDiagram box OP Stack Sequencer participant sequencer-cl as Sequencer CL participant sequencer-el as Sequencer EL end box Network participant peers-cl as Peers end Note over sequencer-cl: FCU with Attributes sequencer-cl->>sequencer-el: engine_forkChoiceUpdatedV3(ForkChoiceState, Attrs) sequencer-el-->>sequencer-cl: {payloadStatus: {status: VALID, ...}, payloadId: PayloadId} sequencer-el->>sequencer-el: Build execution payload sequencer-cl->>sequencer-el: engine_getPayloadV3(PayloadId) sequencer-el-->>sequencer-cl: {executionPayload, blockValue} sequencer-cl->>peers-cl: Propagate new block
For a detailed look at how block production works on the OP Stack, see the OP Stack specs.
Rollup Boost
rollup-boost
is a block building sidecar for OP Stack chains, enabling external block production while remaining fully compatible with the OP Stack. rollup-boost
acts as an intermediary between the sequencer's consensus and execution client. When sequencer-cl
sends a new FCU to rollup-boost
, the request will be multiplexed to both the sequencer's execution client and external block builders signaling that a new block should be built.
When the sequencer is ready to propose a new block, op-node
will send an engine_getPayload
request to rollup-boost
which is forwarded to the default execution client and external block builders.
Once rollup-boost
receives the built block from external builder, it will then validate the block by sending it to the sequencer's execution client via engine_newPayload
. If the external block is valid, it is returned to the sequencer's op-node
, otherwise rollup-boost
will return the fallback block. Note that rollup-boost
will always fallback to the default execution client's block in the case that the external builder does not respond in time or returns an invalid block.
sequenceDiagram box Sequencer participant sequencer-cl as Sequencer CL participant rollup-boost participant sequencer-el as Sequencer EL end box Builder participant builder-el as Builder EL end Note over sequencer-cl: FCU with Attributes sequencer-cl->>rollup-boost: engine_forkChoiceUpdatedV3(..., Attrs) Note over rollup-boost: Forward FCU rollup-boost->>builder-el: engine_forkChoiceUpdatedV3(..., Attrs) rollup-boost->>sequencer-el: engine_forkChoiceUpdatedV3(..., Attrs) sequencer-el-->>rollup-boost: {payloadId: PayloadId} rollup-boost-->>sequencer-cl: {payloadId: PayloadId} Note over sequencer-cl: Get Payload sequencer-cl->>rollup-boost: engine_getPayloadV3(PayloadId) Note over rollup-boost: Forward Get Payload rollup-boost->>sequencer-el: engine_getPayloadV3(PayloadId) rollup-boost->>builder-el: engine_getPayloadV3(PayloadId) builder-el-->>rollup-boost: {executionPayload, blockValue} sequencer-el-->>rollup-boost: {executionPayload, blockValue} Note over rollup-boost, sequencer-el: Validate builder block rollup-boost->>sequencer-el: engine_newPayloadV3(ExecutionPayload) sequencer-el->>rollup-boost: {status: VALID, ...} Note over rollup-boost: Propose exectuion payload rollup-boost->>sequencer-cl: {executionPayload, blockValue} Note over sequencer-cl: Propagate new block
In addition to Engine API requests, rollup-boost
will proxy all RPC calls from the sequencer op-node
to its local execution client. The following RPC calls will also be forwarded to external builders:
miner_*
- The Miner API is used to notify execution clients of changes in effective gas price, extra data, and DA throttling requests from the batcher.
eth_sendRawTransaction*
- Forwards transactions the sequencer receives to the builder for block building.
Block Production on World Chain
World Chain leverages rollup-boost
to enable external block production and integrates the World Chain Builder as a block builder in the network. The World Chain Builder implements a custom block ordering policy (ie. PBH) to provide priority inclusion for transactions with a valid World ID proof. Note that the custom ordering policy adheres to the OP Stack spec.
Each block has a "PBH blockspace capacity", which determines how many PBH transactions will be included in the block. Blocks on World Chain will always reserve a percentage of blockspace for non-PBH transactions to ensure inclusion for automated systems and non-verified users. If there are not enough pending PBH transactions to fill the entirety of PBH blockspace, standard transactions will be used to fill the remainder of the block.
Tx Hash | Fee |
---|---|
0xaaaa | $0.04 |
0xbbbb | $0.04 |
0xcccc | $0.03 |
0xdddd | $0.03 |
0xeeee | $0.03 |
0x2222 | $0.02 |
0x3333 | $0.02 |
0x4444 | $0.02 |
0x5555 | $0.01 |
0x6666 | $0.01 |
Tx Hash | Fee |
---|---|
0x3333 | $0.02 |
0x4444 | $0.02 |
0x5555 | $0.01 |
0x6666 | $0.01 |
0xaaaa | $0.04 |
0xbbbb | $0.04 |
0xcccc | $0.03 |
0xdddd | $0.03 |
0xeeee | $0.03 |
0x2222 | $0.02 |
If the amount of pending PBH transactions exceed the PBH blockspace capacity, the remaining PBH transactions will carry over to the next block. PBH transactions aim to provide verified users with faster, cheaper transaction inclusion, especially during network congestion. Note that transactions within PBH blockspace are ordered by priority fee.
In the event that the block builder is offline, rollup-boost
will fallback to the block built by the default execution client with standard OP Stack ordering rules.
PBH Transactions
The World Chain Builder introduces the concept of PBH transactions, which are standard OP transactions that target the PBHEntryPoint and includes a PBHPayload encoded in the tx calldata.
PBH 4337 UserOps
The PBHEntryPoint
contract also provides priority inclusion for 4337 UserOps through PBH bundles. A PBH bundle is a standard 4337 bundle where the aggregated signature field is consists of an array of PBHPayload
. A valid PBH bundle should include a n
PBHPayload
s, with each item corresponding to a UserOp
in the bundle.
When creating a PBH UserOp
, users will append the PBHPayload
to the signature field and specify the PBHSignatureAggregator as the sigAuthorizer. The UserOp
can then be sent to a 4337 bundler that supports PBH and maintains an alt-mempool for PBH UserOps
.
The bundler will validate the PBHPayload, strip the payload from the userOp.signature
field and add it to the aggregated signature.
/**
* Aggregate multiple signatures into a single value.
* This method is called off-chain to calculate the signature to pass with handleOps()
* @param userOps - Array of UserOperations to collect the signatures from.
* @return aggregatedSignature - The aggregated signature.
*/
function aggregateSignatures(PackedUserOperation[] calldata userOps)
external
view
returns (bytes memory aggregatedSignature)
{
IPBHEntryPoint.PBHPayload[] memory pbhPayloads = new IPBHEntryPoint.PBHPayload[](userOps.length);
for (uint256 i = 0; i < userOps.length; ++i) {
(, bytes memory proofData) = SafeModuleSignatures.extractProof(
userOps[i].signature, ISafe(payable(userOps[i].sender)).getThreshold()
);
pbhPayloads[i] = abi.decode(proofData, (IPBHEntryPoint.PBHPayload));
}
aggregatedSignature = abi.encode(pbhPayloads);
}
Upon submitting a PBH bundle to the network, the World Chain builder will ensure that all PBH bundles have valid proofs and mark the bundle for priority inclusion.
Visit the validation section of the docs to see how to encode the signalHash
for a PBH UserOps
work, check out the handleAggregatedOps() function and PBH4337Module.
PBH Payload
A PBHPayload
consists of the following RLP encoded fields:
Field | Type | Description |
---|---|---|
external_nullifier | bytes | A unique identifier derived from the proof version, date marker, and nonce. |
nullifier_hash | Field | A cryptographic nullifier ensuring uniqueness and preventing double-signaling. |
root | Field | The Merkle root proving inclusion in the World ID set. |
proof | Proof | Semaphore proof verifying membership in the World ID set. |
External Nullifier
The external_nullifier
is a unique identifier used when verifying PBH transactions to prevent double-signaling and enforce PBH transaction rate limiting. For a given external_nullifier
, the resulting nullifier_hash will be the same, meaning it can be used a unique marker for a given World ID user while maintaining all privacy preserving guarantees provided by World ID. Note that since the external_nullifier
is different for every PBH transaction a user sends, no third party can know if two PBH transactions are from the same World ID.
The external_nullifier
is used to enforce that a World ID user can only submit n
PBH transactions per month, with the Builder ensuring that all proofs and external_nullifiers
are valid before a transaction is included.
The external_nullifier
is encoded as a 32-bit packed unsigned integer, with the following structure:
- Version (
uint8
): Defines the schema version. - Year (
uint16
): The current year expressed asyyyy
. - Month (
uint8
): The current month expressed as (1-12). - PBH Nonce (
uint8
): The PBH nonce, where0 <= n < pbhNonceLimit
.
Below is an example of how to encode and decode the external_nullifier
for a given PBH transaction.
uint8 public constant V1 = 1;
/// @notice Encodes a PBH external nullifier using the provided year, month, and nonce.
/// @param version An 8-bit version number (0-255) used to identify the encoding format.
/// @param pbhNonce An 8-bit nonce value (0-255) used to uniquely identify the nullifier within a month.
/// @param month An 8-bit 1-indexed value representing the month (1-12).
/// @param year A 16-bit value representing the year (e.g., 2024).
/// @return The encoded PBHExternalNullifier.
function encode(uint8 version, uint8 pbhNonce, uint8 month, uint16 year) internal pure returns (uint256) {
require(month > 0 && month < 13, InvalidExternalNullifierMonth());
return (uint256(year) << 24) | (uint256(month) << 16) | (uint256(pbhNonce) << 8) | uint256(version);
}
/// @notice Decodes an encoded PBHExternalNullifier into its constituent components.
/// @param externalNullifier The encoded external nullifier to decode.
/// @return version The 8-bit version extracted from the external nullifier.
/// @return pbhNonce The 8-bit nonce extracted from the external nullifier.
/// @return month The 8-bit month extracted from the external nullifier.
/// @return year The 16-bit year extracted from the external nullifier.
function decode(uint256 externalNullifier)
internal
pure
returns (uint8 version, uint8 pbhNonce, uint8 month, uint16 year)
{
year = uint16(externalNullifier >> 24);
month = uint8((externalNullifier >> 16) & 0xFF);
pbhNonce = uint8((externalNullifier >> 8) & 0xFF);
version = uint8(externalNullifier & 0xFF);
}
The World Chain Builder enforces:
- The
external_nullifier
is correctly formatted and specifies the current month, year and a valid nonce. - The
proof
is valid and specifies theexternal_nullifier
as a public input to the proof. - The
external_nullifier
has not been used before, ensuring that thepbh_nonce
is unique for the given month and year.
Signal Hash
One of the required inputs when generating a World ID proof is the signal_hash
.
As the name suggests, the signal_hash
is the hash of the signal which is an arbitrary message provided by the prover, allowing the verifier to hash the signal
when verifying the proof, ensuring that the proof was generated with the expected inputs.
Within the context of PBH, the signal_hash
is used to ensure the proof was created for the transaction being submitted. Depending on the type of the PBH transaction, this value could be a hash of the tx calldata, a 4337 UserOp hash or the tx hash itself.
PBH Validation
Upon receiving new transactions, the World Chain Builder will first ensure that the payload is a valid OP Stack tranasaction. In addition to the default checks, the builder will also evaluate transactions for PBH conditions.
Any transaction that calls the pbhMulticall()
or handleAggregatedOps()
function on the PBHEntyrPoint
will be considered a PBH transaction and must clear PBH Validation. PBH transactions must contain a valid PBHPayload
or PBHPayload[]
in the case of PBH 4337 bundles.
struct PBHPayload {
uint256 root;
uint256 pbhExternalNullifier;
uint256 nullifierHash;
uint256[8] proof;
}
Signal Hash
Transactions that target the pbhMulticall()
function must provide a valid PBHPayload
where included proof
is generated with a signalHash
specified as:
uint256 signalHash = abi.encode(msg.sender, calls).hashToField();
Transactions that target the handleAggregatedOps()
function (ie. PBH 4337 Bundles) must contain an aggregated signature consisting of an array of PBHPayload
where there is a PBHPayload
for each UserOp
in the bundle. The included proof
must be generated with a signalHash
specified as:
uint256 signalHash = abi.encodePacked(sender, userOp.nonce, userOp.callData).hashToField();
External Nullifier
PBH transactions must contain a valid external nullifier where:
- The
month
is the current month - The
year
is the current year (specified asyyyy
) - The
pbhNonce
is <pbhNonceLimit
. PBH nonces are0
indexed, meaning if thepbhNonce
limit is29
, a user is allotted30
PBH transactions per month.
Root
The root
provided must be a valid World ID Root with a timestamp less than 7 days old.
Proof
The proof
must be a valid semaphore proof, proving inclusion in the World ID set associated with the specified root
.