Skip to main content

Diamond & Facets

The Diamond (0x14fbb1eD5BC098B4Ea236dcE0941EDB02e967b44, identical on all 11 chains) is an EIP-2535 proxy that routes calls to 12 facets by function selector — 65 selectors total.

Facets are divided into two groups based on when they were added:

  • Genesis facets (5): deployed and cut in during DeployDiamond. Their implementation addresses are uniform across all chains (same deployer + same bytecode = same CREATE address).
  • Init-cut facets (7): added during InitializeDiamond. Their addresses vary by chain because they embed CCIP-immutable constructor arguments.

Genesis facets

DiamondCutFacet

Purpose: Two-step upgrade mechanism for the Diamond.

Flow:

  1. PROPOSER_ROLE calls diamondCut(facetCuts[], init, calldata) → proposal queued with an auto-incremented id and a 7-day TTL.
  2. MULTISIG_ROLE calls finalizeUpgrade(id) → change applied via LibDiamond.diamondCut.

Safety functions (MULTISIG_ROLE):

  • rejectUpgrade(id) — mark a single proposal as rejected.
  • rejectAllUpgrades() — reject all pending proposals (unbounded loop; use rejectRange if proposer key is compromised).
  • rejectRange(start, end) — paginated rejection for spam scenarios.
  • upgradeStatus(id) — view a proposal's state (exists, rejected, finalized, createdAt, expiresAt).

Finalized proposals cannot be re-executed. Proposals expire after 7 days (PROPOSAL_TTL).

DiamondLoupeFacet

Purpose: EIP-2535 introspection — enumerate facets, function selectors, and supported interfaces.

Key functions: facets(), facetFunctionSelectors(facet), facetAddresses(), facetAddress(selector), supportsInterface(interfaceId).

AccessControlFacet

Purpose: Role management for the Diamond. Exposes grantRole, revokeRole, renounceRole, hasRole, getRoleAdmin. Also implements the ERC-721 receiver hook so the Diamond can hold NFTs during bridge operations.

WithdrawalTimelockFacet

Purpose: 48-hour timelocked emergency withdrawals for ETH, ERC-20, and ERC-721 tokens stuck in the Diamond.

Flow: MULTISIG_ROLE submits a withdrawal request → after a 48-hour delay (WITHDRAWAL_DELAY), executes it. Any pending request can be cancelled before execution.

ReceiverSelectorWhitelist

Purpose: Allowlist of function selectors that may be invoked through the CCIP ccipReceive self-call dispatch path. The CCIP entry point checks this map before delegating, so any non-whitelisted selector is rejected before reaching the onlySelf-gated implementation. (HIGH #5 security mitigation.)


Init-cut facets

BridgeFacet

Purpose: Cross-chain ERC-20 bridging via Chainlink CCIP. Source-chain entry: lock canonical tokens or burn bridge-deployed SkyToken wrappers.

Access: All bridge functions require onlyEntryPoint. Pause/unpause via PAUSER_ROLE.

Key functions:

FunctionDescription
bridgeERC20(token, amount, chainSelector, receiver)Bridge an ERC-20 (or native ETH via address(0) — Diamond wraps to WETH)
getBridgeMessageDetails(token, amount, chainSelector, receiver)Off-chain quote: CCIP fee + deployment flag
setNeverWrapToken(token, status)Block a canonical token (e.g., WETH, USDC) from getting a SkyToken wrapper. OPERATOR_ROLE.
setNeverWrapTokens(tokens[], status)Batch version
isNeverWrapToken(token)Query status
getLockedTokenAmount(token)Amount of a token currently locked in the Diamond
pause() / unPause()Pause ERC-20 bridging (PAUSER_ROLE)

Fee-on-transfer tokens are auto-rejected. Token deployment state is written before calling the CCIP router (CEI pattern).

ERC721BridgeFacet

Purpose: Cross-chain ERC-721 bridging via Chainlink CCIP. Source-chain entry: lock or burn NFTs. Only callable by EntryPoint.

Key functions: bridgeERC721(token, tokenId, chainSelector, receiver), bridgeERC721Batch(token, tokenIds[], chainSelector, receiver), getERC721BridgeMessageDetails(...).

BridgeReceiverFacet

Purpose: Destination-chain handler for incoming CCIP ERC-20 messages (ccipReceive path). Mints SkyToken wrappers on first arrival or unlocks canonical tokens on return. Auto-deploys tokens via CREATE3 if the token does not exist on the destination.

WETH handling: On destination, if the incoming token is WETH and the chain supports native ETH, the Diamond unwraps WETH and sends native ETH to the receiver. If the unwrap/send fails, WETH is forwarded directly (fallback event WETHUnwrapFallback).

ccipReceive uses try-catch — the CCIP router expects no reverts; failures are stored in s_failedMessages for later retry.

ERC721BridgeReceiverFacet

Purpose: Destination-chain handler for incoming CCIP ERC-721 messages. Mints SkyNFT wrappers on first arrival or unlocks canonical NFTs on return.

CCIPFacet

Purpose: CCIP router integration, chain and sender allowlisting, and failed-message handling.

Key functions: allowlistDestinationChain(chainSelector, allowed), allowlistSourceChain(chainSelector, allowed), allowlistSender(sender, allowed), retryFailedMessage(messageId, token, amount, beneficiary), setWeth(weth), getCCIPFees(...).

Gating: allowlist management requires MULTISIG_ROLE. Retry requires DEFAULT_ADMIN_ROLE.

SkyTokenDeployerFacet

Purpose: Deterministic ERC-20 token deployment via CREATE3. Called internally by BridgeReceiverFacet (restricted to onlySelf).

Key functions: deployChild(salt, tokenData) → deploys a new SkyToken; predictTokenAddress(salt) → compute address without deploying.

SkyNFTDeployerFacet

Purpose: Deterministic ERC-721 wrapper deployment via CREATE3. Called internally by ERC721BridgeReceiverFacet (restricted to onlySelf).

Key functions: deployNFTChild(salt, data) → deploys a new SkyNFT; predictNFTAddress(salt) → compute address without deploying.


Selector count

All 11 chains: 12 facets / 65 selectors (verified via live on-chain facets() loupe at deploy time).


Implementation addresses

Genesis facet implementations are identical across all chains:

FacetAddress
DiamondCutFacet0x2A37630aB3706B87b36e27fA1476f074AcB4f113
DiamondLoupeFacet0x3Bcf4185443A339517aD4e580067f178d1B68E1D
AccessControlFacet0xd76d071128338d229d5687B7361D408947ad7260
WithdrawalTimelockFacet0x5589719a0ad9112063972DBA099A1f87C4158d96
ReceiverSelectorWhitelist0x2BBf54EBe0E0fC0714D2BB75B4F02a246c728545

Init-cut facet addresses vary per chain (CCIP constructor immutables + CREATE nonce). See Official Addresses for the full per-chain table.