Arbitrum is designed to be as compatible and consistent with Ethereum as possible, from its high-level RPCs to its low-level bytecode and everything in between. Dapp developers with experience building on Ethereum will likely find that little-to-no new L2-specific knowledge is required to build on Arbitrum.
This document presents an overview of some of the minor differences, perks, and gotchas that devs are advised to be aware of.
Block and Transaction Properties
Blocks and Time
Time in L2 is tricky; the timing assumptions one is used to making about L1 blocks don't exactly carry over into the timing of Arbitrum blocks. For details, see Block Numbers and Time.
Block hashes and randomness
Arbitrum's L2 block hashes should not be relied on as a secure source of randomness (see 'blockhash(x);)
The L2 fees an Arbitrum transaction pays essentially work identically to gas fees on Ethereum. Arbitrum transactions must also, however, pay an L1-fee component to cover the cost of their calldata. (See L1 pricing.)
L1 to L2 Messages
Arbitrum chains support arbitrary L1 to L2 message passing; developers using this functionality should familiarize themselves with how they work (see L1 to L2 Messaging). Of particular note:
- The result of a successful initial/"auto"-execution of an L1 to L2 message will be an unsigned L2 transaction receipt.
msg.senderof the L2 side of an L1 to L2 message will be not the initiating L1 address, but rather its address alias.
- Using the special
ethDepositmethod will not result in an L2 contract's fallback function getting triggered.
Arbitrum chains include a number of special precompiles not present on Ethereum; see Precompiles.
You can deploy Solidity contracts onto Arbitrum just like you do Ethereum; there are only a few minor differences in behavior. See Solidity Support for details.
This section covers the differences in response body fields you'll find when using the Arbitrum JSON-RPC vs Ethereum mainnet.
Comprehensive documentation on all generally available JSON-RPC methods for Ethereum can be found at ethereum.org. As Arbitrum has
go-ethereum at its core, most of the documented methods there can be used with no modifications.
eth_syncing RPC Method
eth_syncing returns false if not synchronizing (just like on Ethereum). However, if the node is still syncing,
eth_syncing returns an object with data about the synchronization status. The returned object is different from what one would get on Ethereum, see here for more details.
eth_getBlockByNumber, Arbitrum includes a few additional fields and leverages some existing fields in different ways than mainnet Ethereum.
l1BlockNumber: An approximate L1 block number that occurred before this L2 block. See Block Numbers and Time for more info.
sendCount: The number of L2-to-L1 messages since Nitro genesis
sendRoot: The Merkle root of the Outbox tree state
Existing Fields With Different Behavior
extraData: This field is equivalent to
mixHash: First 8 bytes is equivalent to
sendCount, second 8 bytes is equivalent to
difficulty: Fixed at
gasLimit: Value is fixed at
0x4000000000000, but it's important to note that Arbitrum currently has a 32M gas limit per block
See full list of mainnet block fields for additional reference.
On RPC calls that return transactions, such as
type field will reflect the custom codes where applicable.
ArbitrumDepositTxType: A deposit of ETH from L1 to L2 via the Arbitrum bridge.
ArbitrumUnsignedTxType: An L1 user can use to call an L2 contract via the bridge.
ArbitrumContractTxType: Allows an L1 contract to call an L2 contract method via the bridge.
ArbitrumRetryTxType: Used to redeem a retryable ticket on L2, which finalizes a retryable that failed to execute automatically (usually due to low gas).
ArbitrumSubmitRetryableTxType: Retryable tickets are submitted via the L1 bridge and allow arbitrary L1 to L2 messages to be created and executed on L2.
ArbitrumInternalTxType: Internal transactions created by the ArbOS itself for certain state updates, like L1 base fee and block number.
requestId: Added to L1-to-L2 transactions to indicate position in the
Existing Fields With Different Behavior
from: For L1-to-L2 related transactions, will be the aliased version of the L1's
msg.sender. See L1 to L2 Messaging for more info.
See full list of mainnet transaction fields for additional reference.
l1BlockNumber: The L1 block number that would be used for block.number calls.
gasUsedForL1: Amount of gas spent on L1 calldata in units of L2 gas.
See full list of mainnet transaction receipt fields for additional reference.