// SPDX-License-Identifier: MIT pragma solidity 0.8.26; interface IInbox { function createRetryableTicket( address to, uint256 l2CallValue, uint256 maxSubmissionCost, address excessFeeRefundAddress, address callValueRefundAddress, uint256 gasLimit, uint256 maxFeePerGas, bytes calldata data ) external payable returns (uint256); function calculateRetryableSubmissionFee(uint256 dataLength, uint256 baseFee) external view returns (uint256); } /// @title Ambrosia bridge, HyperEVM -> Elysium /// @notice Sends HYPE through Elysium's own rollup inbox to the same (or any) address on Elysium. /// Keeps 0.5% for the team. Uses a retryable ticket, not depositEth(), because a contract calling /// depositEth() would credit its own aliased address instead of the user. Unused L2 gas comes back /// to the recipient on Elysium. No owner, no funds held. contract AmbrosiaBridgeIn { uint256 public constant FEE_BPS = 50; uint256 public constant L2_GAS = 100_000; uint256 public constant MIN_FEE_PER_GAS = 0.1 gwei; uint256 public constant MAX_FEE_PER_GAS = 50 gwei; IInbox public immutable inbox; address public immutable team; uint256 public count; uint256 public totalBridged; uint256 public totalFees; event Bridged( uint256 indexed id, address indexed from, address indexed to, uint256 amount, uint256 fee, uint256 ticket, uint32 origin ); error TooSmall(); error BadFeePerGas(); error SendFailed(); constructor(IInbox inbox_, address team_) { inbox = inbox_; team = team_; } /// @notice Gas and submission HYPE taken on top of the fee, for a given L2 fee per gas. function overhead(uint256 maxFeePerGas) public view returns (uint256 submission, uint256 total) { submission = inbox.calculateRetryableSubmissionFee(0, block.basefee) * 2; total = submission + L2_GAS * maxFeePerGas; } /// @notice What `to` receives on Elysium for `value` sent, before the gas refund. function quote(uint256 value, uint256 maxFeePerGas) external view returns (uint256 amount, uint256 fee) { fee = value * FEE_BPS / 10_000; (, uint256 o) = overhead(maxFeePerGas); amount = value > fee + o ? value - fee - o : 0; } /// @param origin Chain the user started from, for the public history (999 for HyperEVM, 56 for BNB, ...). function bridge(address to, uint256 maxFeePerGas, uint32 origin) external payable returns (uint256 ticket) { if (maxFeePerGas < MIN_FEE_PER_GAS || maxFeePerGas > MAX_FEE_PER_GAS) revert BadFeePerGas(); uint256 fee = msg.value * FEE_BPS / 10_000; (uint256 submission, uint256 o) = overhead(maxFeePerGas); if (msg.value <= fee + o) revert TooSmall(); uint256 amount = msg.value - fee - o; ticket = inbox.createRetryableTicket{value: msg.value - fee}(to, amount, submission, to, to, L2_GAS, maxFeePerGas, ""); if (fee > 0) { (bool ok,) = team.call{value: fee}(""); if (!ok) revert SendFailed(); } uint256 id = ++count; totalBridged += amount; totalFees += fee; emit Bridged(id, msg.sender, to, amount, fee, ticket, origin); } }