Troubleshooting: Building Arbitrum dApps
How does gas work on Arbitrum?
Fees on Arbitrum chains are collected on L2 in the chains' native currency (ETH on both Arbitrum One and Nova).
A transaction fee is comprised of both an L1 and an L2 component:
The L1 component is meant to compensate the Sequencer for the cost of posting transactions on L1 (but no more). (See L1 Pricing.)
The L2 component covers the cost of operating the L2 chain; it uses Geth for gas calculation and thus behaves nearly identically to L1 Ethereum. One difference is that unlike on Ethereum, Arbitrum chains enforce a gas price floor; currently 0.1 gwei on Arbitrum One and 0.01 gwei on Nova (See Gas).
L2 Gas price adjusts responsively to chain congestion, ala EIP 1559.
I tried to create a retryable ticket but the transaction reverted on L1. How can I debug the issue?
Creation of retryable tickets can revert with one of these custom errors:
- InsufficientValue: not enough gas included in your L1 transaction's callvalue to cover the total cost of your retryable ticket; i.e.,
msg.value < (maxSubmissionCost + l2CallValue + gasLimit * maxFeePerGas). Note that your L1 transaction's callvalue must cover this full cost. See Retryable Tickets Lifecycle for more information. - InsufficientSubmissionCost: provided submission cost isn't high enough to create your retryable ticket.
- GasLimitTooLarge: provided gas limit is greater than 2^64
- DataTooLarge: provided data is greater than 117.964 KB (90% of Geth's 128 KB transaction size limit).
To figure out which error caused your transaction to revert, we recommend using etherscan's Parity VM trace support (Tenderly is generally a very useful debugging tool; however, it can be buggy when it comes to custom Geth errors).
Use the following link to view the Parity VM trace of your failed transaction (replacing the tx-hash with your own, and using the appropriate etherscan root url):
https://etherscan.io/vmtrace?txhash=0x51a8088c9b319bbad649c36d9cf2b4e9b61a6099a158181676c8e79dbce2df58&type=parity#raw
To find out the reversion error signature, go to the "Raw Traces" tab, and scroll down to find the last "subtrace" in which your transaction is reverted. Then find "output" field of that subtrace.
(In the above example the desirable "output" is:
0xfadf238a0000000000000000000000000000000000000000000000000000c4df7e2903b00000000000000000000000000000000000000000000000000000a39a1d002808)
The first four bytes of the output is the custom error signature; in our example it's 0xfadf238a .
To let's find out which is custom error this signature represents, we can use this handy tool by Samzcsun: https://sig.eth.samczsun.com/
Checking 0xfadf238a gives us InsufficientSubmissionCost(uint256,uint256).
How is the L1 portion of an Arbitrum transaction's gas fee computed?
The L1 fee that a transaction is required to pay is determined by compressing its data with brotli and multiplying the size of the result (in bytes) by ArbOS's current calldata price; the latter value can be queried via the getPricesInWeimethod of the ArbGasInfoprecompile. You can find more information about gas calculations in Understanding Arbitrum: 2-Dimensional Fees and How to estimate gas in Arbitrum.
What is a retryable ticket's "submission fee"? How can I calculate it? What happens if I the fee I provide is insufficient?
A retryable's submission fee is a special fee a user must pay to create a retryable ticket. The fee is directly proportional to the size of the L1 calldata the retryable ticket uses. The fee can be queried using the Inbox.calculateRetryableSubmissionFeemethod. If insufficient fee is provided, the transaction will revert on L1, and the ticket won't get created.
Which method in the Inbox contract should I use to submit a retryable ticket (aka L1 to L2 message)?
The method you should (almost certainly) use is Inbox.createRetryableTicket. There is an alternative method, Inbox.unsafeCreateRetryableTicket, which, as the name suggests, should only be used by those who fully understand its implications.
There are two differences between createRetryableTicket and unsafeCreateRetryableTicket:
- Method
createRetryableTicketwill check that provided L1 callvalue is sufficient to cover the costs of creating and executing the retryable ticket (at the specified parameters) and otherwise revert directly at L1.unsafeCreateRetryableTicket, in contrast, will allow a retryable ticket to be created that is guaranteed to revert on L2. - Method
createRetryableTicketwill check if either the providedexcessFeeRefundAddressor thecallValueRefundAddressare contracts on L1; if they are, to prevent the situation where refunds are guaranteed to be irrecoverable on L2, it will convert them to their address alias, providing a potential path for fund recovery.unsafeCreateRetryableTicketwill allow the creation of a retryable ticket with refund addresses that are L1 contracts; since no L1 contract can alias to an address that is also itself an L1 contract, refunds to these addresses on L2 will be irrecoverable.
(Astute observers may note a third ticket creation method, createRetryableTicketNoRefundAliasRewrite; this is included only for backwards compatibility, but should be considered deprecated in favor of unsafeCreateRetryableTicket)