StarknetGatewayAdapterLib
Title: StarknetGatewayAdapterLib
Authors: David Dada <daveproxy80@gmail.com> (https://github.com/dadadave80), Modified from Starknet (https://github.com/starkware-libs/cairo-lang)
Logic + ERC-7201 storage for the L1-side Starknet L1 <-> L2 connector. Outbound sendMessage
parses the ERC-7930 felt252 recipient, felt-chunk encodes the payload, escrows msg.value as the
non-refundable message fee via sendMessageToL2, and records the caller as the message's initiator;
startCancellation/cancel re-derive the message hash from the original inputs and are gated to
that initiator (the DIAMOND is the L1 sender on the core, so cancellation authority must be
re-derived at the facet level — an ungated cancel would let anyone grief in-flight messages);
inbound consumeMessage is a permissionless keeper-driven PULL against the core's per-message
counter, allow-listed by trusted L2 sender.
TRUST MODEL: the Starknet core is the authenticator on BOTH directions — outbound it escrows the fee
and mints the nonce, inbound consumeMessageFromL2 only consumes messages addressed to msg.sender
(the diamond) and reverts when the counter is zero. The fee is ESCROWED AND NEVER REFUNDED, cancellation
included. DELIBERATELY BESPOKE (not IERC7786GatewaySource): the inbound path is a pull-based consume
against a counter (no message id to dedup), attributes cannot express the escrowed fee + cancellation
lifecycle, and the wire payload is a felt array — see IStarknetGatewayAdapter. Emit-only inbound in
v1; never routed into CrosschainLink / OpenBridge.
Constants
CHUNK_BOUND
One byte more than a felt chunk's 31-byte capacity — every valid chunk is < 2**248.
uint256 private constant CHUNK_BOUND = 1 << 248;Functions
starknetGatewayAdapterStorage
function starknetGatewayAdapterStorage() internal pure returns (StarknetGatewayAdapterStorage storage $);Returns
| Name | Type | Description |
|---|---|---|
| $ | StarknetGatewayAdapterStorage |
__StarknetGatewayAdapter_init
Configures the Starknet core + expected chain reference and registers the IStarknetGatewayAdapter ERC-165 id.
Reverts StarknetZeroAddress on a zero core, StarknetEmptyChainReference on an empty chain
reference (an unconfigured adapter must not exist). Called inside the diamond initializing window.
function __StarknetGatewayAdapter_init(address starknetCore_, bytes memory expectedChainReference_) internal;Parameters
| Name | Type | Description |
|---|---|---|
| starknetCore_ | address | |
| expectedChainReference_ | bytes |
registerInterface
Writes true to the ERC-165 map slot for IStarknetGatewayAdapter.
function registerInterface() internal;starknetCore
function starknetCore() internal view returns (address);Returns
| Name | Type | Description |
|---|---|---|
| <none> | address |
expectedChainReference
function expectedChainReference() internal view returns (bytes memory);Returns
| Name | Type | Description |
|---|---|---|
| <none> | bytes |
l1HandlerSelector
function l1HandlerSelector(uint256 l2Target) internal view returns (uint256);Parameters
| Name | Type | Description |
|---|---|---|
| l2Target | uint256 |
Returns
| Name | Type | Description |
|---|---|---|
| <none> | uint256 |
isTrustedL2Sender
function isTrustedL2Sender(uint256 fromAddress) internal view returns (bool);Parameters
| Name | Type | Description |
|---|---|---|
| fromAddress | uint256 |
Returns
| Name | Type | Description |
|---|---|---|
| <none> | bool |
initiatorOf
function initiatorOf(bytes32 msgHash) internal view returns (address);Parameters
| Name | Type | Description |
|---|---|---|
| msgHash | bytes32 |
Returns
| Name | Type | Description |
|---|---|---|
| <none> | address |
starknetSelector
The Starknet selector (starknet_keccak) of an entry-point name: keccak256 masked to its low
250 bits. l1_handler selectors are starknet_keccak values of the handler's Cairo name.
function starknetSelector(string calldata name) internal pure returns (uint256);Parameters
| Name | Type | Description |
|---|---|---|
| name | string |
Returns
| Name | Type | Description |
|---|---|---|
| <none> | uint256 |
registerL2Handler
Registers the l1_handler selector to invoke when sending to l2Target. Both must be non-zero
felt252s (StarknetNotAFelt) — selectors are starknet_keccak values that CANNOT be derived from
the ERC-7930 address, so each L2 target is registered with its handler by the admin. Admin only.
function registerL2Handler(uint256 l2Target, uint256 selector) internal;Parameters
| Name | Type | Description |
|---|---|---|
| l2Target | uint256 | |
| selector | uint256 |
setTrustedL2Sender
Sets the trusted flag of the L2 sender fromAddress (a non-zero felt252) for the inbound
consume path. Admin only.
function setTrustedL2Sender(uint256 fromAddress, bool trusted) internal;Parameters
| Name | Type | Description |
|---|---|---|
| fromAddress | uint256 | |
| trusted | bool |
sendMessage
Sends payload (felt-chunk encoded) to the ERC-7930 Starknet recipient's registered
l1_handler, forwarding msg.value as the message fee, and records msg.sender as the
message's initiator (the only party able to cancel it).
FEE WARNING: the fee is escrowed by the Starknet core and NEVER refunded — cancellation included.
The adapter only requires it to be non-zero (StarknetZeroFee); the core bounds the max
(getMaxL1MsgFee, bubbling MAX_L1_MSG_FEE_EXCEEDED). Fail-closed destination checks (mirroring
the Across/ZetaChain cross-check precedent): the chainType must be the starknet key
(StarknetWrongChainType) and the chain reference must equal the configured one
(StarknetChainReferenceMismatch). nonReentrant with CEI — the only effect (the initiator
record) depends on the core-returned msgHash, so it lands right after the call, inside the guard.
function sendMessage(bytes calldata recipient, bytes calldata payload)
internal
returns (bytes32 msgHash, uint256 nonce);Parameters
| Name | Type | Description |
|---|---|---|
| recipient | bytes | |
| payload | bytes |
Returns
| Name | Type | Description |
|---|---|---|
| msgHash | bytes32 | |
| nonce | uint256 |
startCancellation
Starts the two-step cancellation of a previously sent message. Initiator only.
The DIAMOND is the L1 sender on the core, so only the diamond can call the core's cancellation
entrypoints — authority is re-derived here: toFelt/felts are recomputed deterministically from
the ORIGINAL recipient/payload, the message hash is recomputed via
core.l1ToL2MsgHash(address(this), ...), and the caller must be the recorded initiator
(StarknetNotInitiator) BEFORE anything is forwarded to the core. The selector is the SEND-TIME
selector, passed EXPLICITLY (it is emitted in StarknetMessageSent) and deliberately NOT re-read
from the mutable _l1HandlerSelector registry — re-registering a target's handler while a message
is in flight would otherwise change the re-derived hash and permanently lock the initiator out of
cancelling (review finding). A wrong selector is harmless: it re-derives a different hash whose
initiator record is empty. The core enforces the messageCancellationDelay() wait before cancel.
function startCancellation(bytes calldata recipient, uint256 selector, bytes calldata payload, uint256 nonce)
internal
returns (bytes32 msgHash);Parameters
| Name | Type | Description |
|---|---|---|
| recipient | bytes | |
| selector | uint256 | |
| payload | bytes | |
| nonce | uint256 |
Returns
| Name | Type | Description |
|---|---|---|
| msgHash | bytes32 |
cancel
Completes the cancellation started by startCancellation (same arguments), at least
messageCancellationDelay() seconds later. Initiator only; clears the initiator record on
success. The escrowed fee is NOT refunded. selector is the send-time selector (see
startCancellation for why it is explicit rather than registry-derived).
function cancel(bytes calldata recipient, uint256 selector, bytes calldata payload, uint256 nonce)
internal
returns (bytes32 msgHash);Parameters
| Name | Type | Description |
|---|---|---|
| recipient | bytes | |
| selector | uint256 | |
| payload | bytes | |
| nonce | uint256 |
Returns
| Name | Type | Description |
|---|---|---|
| msgHash | bytes32 |
consumeMessage
Permissionless keeper-driven PULL: consumes an L2 -> L1 message from the trusted L2 sender
fromAddress addressed to this diamond (StarknetUntrustedSender otherwise). Emit-only in v1.
The core is the real authenticator — consumeMessageFromL2 only consumes messages addressed to
msg.sender (the diamond) and reverts when the per-message counter is zero. COUNTER SEMANTICS:
duplicates are N DISTINCT consumes, NOT replays — each consume decrements a core-side counter, so
consuming the same payload twice requires the L2 to have SENT it twice. NOT routed into
CrosschainLink / OpenBridge (there is no message id to dedup or M-of-N confirm on).
function consumeMessage(uint256 fromAddress, bytes calldata payload) internal returns (bytes32 msgHash);Parameters
| Name | Type | Description |
|---|---|---|
| fromAddress | uint256 | |
| payload | bytes |
Returns
| Name | Type | Description |
|---|---|---|
| msgHash | bytes32 |
encodeFelts
Encodes payload bytes into felts per the LATTICE FELT-CHUNK CONVENTION v1 — the wire format
the Cairo l1_handler must mirror: felts[0] = payload.length (byte count); felts[1..] =
consecutive 31-byte big-endian chunks of the payload, the last chunk right-padded with zeros.
Each chunk is < 2**248 < FIELD_PRIME by construction. An empty payload encodes to [0].
Exact inverse of decodeFelts.
function encodeFelts(bytes memory payload) internal pure returns (uint256[] memory felts);Parameters
| Name | Type | Description |
|---|---|---|
| payload | bytes |
Returns
| Name | Type | Description |
|---|---|---|
| felts | uint256[] |
decodeFelts
Decodes a LATTICE FELT-CHUNK CONVENTION v1 felt array back into the original bytes payload.
STRICT exact inverse of encodeFelts: reverts StarknetMalformedFeltPayload on a missing/wrong
length prefix, a chunk >= 2**248, or non-zero padding bits in the final chunk — so
decodeFelts(encodeFelts(x)) == x and every accepted felt array is canonical.
function decodeFelts(uint256[] memory felts) internal pure returns (bytes memory payload);Parameters
| Name | Type | Description |
|---|---|---|
| felts | uint256[] |
Returns
| Name | Type | Description |
|---|---|---|
| payload | bytes |
_parseAndCheckRecipient
Parses the ERC-7930 recipient into a validated felt252 and fail-closed checks it targets THIS
adapter's Starknet chain, then resolves the registered l1_handler selector (send path only —
cancellation takes the send-time selector explicitly, see startCancellation).
Reverts StarknetTargetNotRegistered when no selector is registered for the target.
function _parseAndCheckRecipient(StarknetGatewayAdapterStorage storage $, bytes calldata recipient)
private
view
returns (uint256 toFelt, uint256 selector);Parameters
| Name | Type | Description |
|---|---|---|
| $ | StarknetGatewayAdapterStorage | |
| recipient | bytes |
Returns
| Name | Type | Description |
|---|---|---|
| toFelt | uint256 | |
| selector | uint256 |
_parseRecipientFelt
Parses the ERC-7930 recipient into a validated felt252 target on THIS adapter's Starknet chain.
Reverts StarknetWrongChainType on a non-starknet chainType, StarknetChainReferenceMismatch on a
foreign chain reference. Felt range/zero checks live in NonEvmAddress.parseV1ToFelt252.
function _parseRecipientFelt(StarknetGatewayAdapterStorage storage $, bytes calldata recipient)
private
view
returns (uint256 toFelt);Parameters
| Name | Type | Description |
|---|---|---|
| $ | StarknetGatewayAdapterStorage | |
| recipient | bytes |
Returns
| Name | Type | Description |
|---|---|---|
| toFelt | uint256 |
_checkFelt
Requires value to be a valid non-zero felt252 (0 < value < FIELD_PRIME).
function _checkFelt(uint256 value) private pure;Parameters
| Name | Type | Description |
|---|---|---|
| value | uint256 |