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:
PROPOSER_ROLEcallsdiamondCut(facetCuts[], init, calldata)→ proposal queued with an auto-incrementedidand a 7-day TTL.MULTISIG_ROLEcallsfinalizeUpgrade(id)→ change applied viaLibDiamond.diamondCut.
Safety functions (MULTISIG_ROLE):
rejectUpgrade(id)— mark a single proposal as rejected.rejectAllUpgrades()— reject all pending proposals (unbounded loop; userejectRangeif 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:
| Function | Description |
|---|---|
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:
| Facet | Address |
|---|---|
| DiamondCutFacet | 0x2A37630aB3706B87b36e27fA1476f074AcB4f113 |
| DiamondLoupeFacet | 0x3Bcf4185443A339517aD4e580067f178d1B68E1D |
| AccessControlFacet | 0xd76d071128338d229d5687B7361D408947ad7260 |
| WithdrawalTimelockFacet | 0x5589719a0ad9112063972DBA099A1f87C4158d96 |
| ReceiverSelectorWhitelist | 0x2BBf54EBe0E0fC0714D2BB75B4F02a246c728545 |
Init-cut facet addresses vary per chain (CCIP constructor immutables + CREATE nonce). See Official Addresses for the full per-chain table.