Skip to main content

Troubleshooting: BUIDling on Arbitrum

Why am I getting error "429 Too Many Requests" when using one of Offchain Labs' Public RPCs?

Offchain Labs offers public RPCs for free, but limits requests to prevent DOSing. Hitting the rate limit could come from your request frequency and/or the resources required to process the requests. If you hitting our rate limit, we recommend running your own node or using a third party node provider.

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:

  1. 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 as of the Nitro upgrade, your L1 transaction's callvalue must cover this full cost (previously an L2 message's execution could be paid for with funds on L2: see dapp migration and "retryable ticket creation".
  2. InsufficientSubmissionCost: provided submission cost isn't high enough to create your retryable ticket.
  3. GasLimitTooLarge: provided gas limit is greater than 2^64
  4. 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 tx (replacing the tx-hash with your own, and using the appropriate etherscan root url):

To find out the reversion error signature, go to the "Raw Traces" tab, and scroll down to find the last "subtrace" in which your tx is reverted. Then find "output" field of that subtrace.

(In the above example the desirable "output" is:
0x7040b58c0000000000000000000000000000000000000000000000000001476b081e80000000000000000000000000000000000000000000000000000000000000000000 )

The first four bytes of the output is the custom error signature; in our example it's 0x7040b58c .

To let's find out which is custom error this signature represents, we can use this handy tool by Samzcsun:

Checking 0x7040b58c gives us InsufficientValue(uint256,uint256).

Do I need to pay a tip / Priority fee for my Arbitrum transactions?

Since transactions are processed in the order that the Sequencer receives them, no priority fee is necessary for Arbitrum transactions; if a transaction does include a priority fee, it will be refunded to the transaction's origin address at the end of execution.

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.

What is a retryable ticket's "submission fee"? How can I calculate it? What happens if I the fee I provide is insufficient?

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:

  1. createRetryableTicket will 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 [TODO: link to "retryable reverts at L1" faq]. unsafeCreateRetryableTicket, in contrast, will allow a retryable ticket to be created that is guaranteed to revert on L2.
  2. createRetryableTicket will check if either the provided excessFeeRefundAddress or the callValueRefundAddress are 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. unsafeCreateRetryableTicket will 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)

Why do I get "custom tx type" errors when I use hardhat?

In Arbitrum, we use a number of non-standard EIP-2718 typed transactions. See here for the full list and the rationale.

Hardhat v2.12.2 added supports for forking networks like Arbitrum with custom transaction types, so if you're using hardhat, upgrade to 2.12.2!


Why does it look like two identical transaction consume a different amount of gas?

Calling an Arbitrum node's eth_estimateGas RPC returns a value sufficient to cover both the L1 and L2 components of the fee for the current gas price; this is the value that, e.g., will appear in users' wallets in the "gas limit" field.

Thus, if the L1 calldata price changes over time, it will appear (in e.g., a wallet) that a transaction's gas limit is changing. In fact, the L2 gas limit isn't changing, merely the total gas required to cover the transaction's L1 + L2 fees.

See 2-D fees for more.