diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000000..2bd37267d8 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,147 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project overview + +Arbitrum Docs — a Docusaurus 3.6.3 site serving technical documentation at docs.arbitrum.io. The repo contains 450+ MDX docs organized by audience (developers, node operators, chain builders, users) plus React components, build scripts, and two git submodules (arbitrum-sdk, stylus-by-example). + +## Commands + +```bash +# Install dependencies +yarn + +# Initialize submodules (required for first setup) +git submodule update --init --recursive + +# Development server +yarn start + +# Production build (installs SDK deps, syncs stylus content, then builds) +yarn build + +# Serve production build locally +yarn serve + +# Format all files (docs + app code) +yarn format + +# Check formatting only +yarn format:check + +# Lint markdown (excludes docs/sdk/) +yarn lint:markdown +yarn lint:markdown:fix + +# TypeScript type checking +yarn typecheck + +# Generate SDK docs from submodule source +yarn generate-sdk-docs + +# Build glossary from partial files +npx tsx scripts/build-glossary.ts + +# Generate doc manifest (scans all docs into .audit/doc-manifest.json) +yarn generate-doc-manifest + +# Run doc audit (generates manifest then audits) +yarn audit-docs + +# Update variable references in docs after changing globalVars.js +yarn update-variable-refs + +# Generate precompile reference tables +yarn generate-precompiles-ref-tables +``` + +Node >= 22.0.0 is required. Yarn is the package manager. + +## Validation workflow + +**IMPORTANT**: `yarn build` is slow (~30-40s). Use faster commands for iteration: + +```bash +# Quick validation (1-2 seconds) - use this for most checks +yarn lint:markdown && yarn format:check + +# Dev server with hot reload (10-15s initial, then instant updates) +yarn start + +# Full production build (slow, only use before committing) +yarn build +``` + +**Recommended workflow when making doc changes:** + +1. Make changes to MDX files +2. Run quick validation: `yarn lint:markdown && yarn format:check` +3. If you need to verify links/sidebar structure, use `yarn start` (has hot reload) +4. Only run `yarn build` as final verification before committing + +Common issues caught by each: + +- `lint:markdown` - Markdown syntax errors, formatting issues +- `format:check` - Code/doc formatting problems +- `start` - Broken links, missing sidebar docs, MDX compilation errors +- `build` - Everything above + production-specific issues + +## Architecture + +### Content organization + +- `docs/` — MDX documentation files organized by section (build-decentralized-apps, stylus, how-arbitrum-works, run-arbitrum-node, launch-arbitrum-chain, etc.) +- `docs/partials/` — Reusable content fragments; filenames start with `_` (Docusaurus strips their frontmatter automatically via `docusaurus.config.js` `parseFrontMatter`) +- `docs/sdk/` — **Auto-generated** from TypeDoc; do not edit manually +- `docs/stylus-by-example/` — **Auto-generated** from submodule; do not edit manually +- `docs/partials/glossary/` — Glossary term files with specific frontmatter format (`title`, `key`, `titleforSort`) + +### Doc frontmatter + +Documents use frontmatter with fields: `title`, `description`, `sidebar_label`, `sidebar_position`, `content_type` (quickstart | how-to | concept | reference | faq | gentle-introduction | troubleshooting), `author`, `sme`. + +### Application code + +- `src/components/` — React components used within docs (ImageZoom, InteractiveDiagrams, Quicklooks, etc.) +- `src/resources/globalVars.js` — Chain-specific parameters (docker images, RPC endpoints, contract addresses, block times). After modifying, run `yarn update-variable-refs` to propagate changes into docs. +- `src/resources/precompilesInformation.js` — Precompile metadata +- `src/theme/` — Docusaurus theme overrides +- `src/css/custom.scss` — Global styles + +### Navigation + +`sidebars.js` (1600+ lines) defines all sidebar navigation. There are 8+ sidebars: getStartedSidebar, buildAppsSidebar, stylusSidebar, runArbitrumChainSidebar, runNodeSidebar, bridgeSidebar, howItWorksSidebar, noticeSidebar. + +### Build pipeline + +- `docusaurus.config.js` — Main config; uses a custom markdown preprocessor (`scripts/markdown-preprocessor.js`), remark-math/rehype-katex for LaTeX, Mermaid diagrams, and TypeDoc plugin for SDK generation +- Docusaurus routes docs at `/` (not `/docs/`) +- Broken links and broken markdown links both configured to `throw` (build fails on broken links) +- Vercel deployment with extensive URL redirects in `vercel.json` + +### Submodules + +- `submodules/arbitrum-sdk/` — TypeScript SDK source; TypeDoc generates `docs/sdk/` from this +- `submodules/stylus-by-example/` — Stylus examples; synced via `yarn sync-stylus-content` + +### Scripts + +`scripts/` contains tooling for content generation and maintenance: + +- `generate-doc-manifest.ts` — Scans all docs, outputs compact JSON manifest to `.audit/doc-manifest.json` +- `audit-docs.ts` — Documentation quality auditing +- `sync-stylus-content.js` — Copies content from stylus-by-example submodule +- `build-glossary.ts` — Generates glossary page from `docs/partials/glossary/_*.mdx` files +- `precompile-reference-generator.ts` — Generates precompile reference tables +- `update-variable-references.ts` — Propagates globalVars.js values into docs +- `markdown-preprocessor.js` — Custom MDX preprocessing for Docusaurus + +## Style conventions + +- Sentence-case for titles, headers, sidebar labels +- Address reader as "you"; informal professional tone +- Descriptive link text (avoid "click here") +- Prettier formatting via `@offchainlabs/prettier-config`; MDX files use mdx parser +- Markdown linting via markdownlint with many rules relaxed (see `.markdownlint.json`) +- Prism syntax highlighting supports: solidity, rust, bash, toml (in addition to defaults) diff --git a/docs/build-decentralized-apps/03-public-chains.mdx b/docs/build-decentralized-apps/03-public-chains.mdx index bbc0b796a6..c0679983e3 100644 --- a/docs/build-decentralized-apps/03-public-chains.mdx +++ b/docs/build-decentralized-apps/03-public-chains.mdx @@ -22,7 +22,7 @@ chains, their differences, and the technology stacks that these chains use. ### Arbitrum One -**Arbitrum One** is a child chain Optimistic Rollup chain that implements the Arbitrum Rollup Protocol and settles to Ethereum's Parent chain. It lets you build high-performance Ethereum dApps with low transaction costs and Ethereum-grade security guarantees, introducing no additional trust assumptions. This is made possible by the [Nitro](/how-arbitrum-works/deep-dives/geth.mdx) technology stack, a "Geth-at-the-core" architecture that gives Arbitrum One (and Nova) advanced calldata compression, separate contexts for common execution and fault proving, Ethereum parent chain gas compatibility, and more. +**Arbitrum One** is a child chain Optimistic Rollup chain that implements the Arbitrum Rollup Protocol and settles to Ethereum's Parent chain. It lets you build high-performance Ethereum dApps with low transaction costs and Ethereum-grade security guarantees, introducing no additional trust assumptions. This is made possible by the [Nitro](/how-arbitrum-works/reference/geth.mdx) technology stack, a "Geth-at-the-core" architecture that gives Arbitrum One (and Nova) advanced calldata compression, separate contexts for common execution and fault proving, Ethereum parent chain gas compatibility, and more. ### Arbitrum Nova diff --git a/docs/build-decentralized-apps/04-cross-chain-messaging.mdx b/docs/build-decentralized-apps/04-cross-chain-messaging.mdx index f73d98e710..947ca1b8f2 100644 --- a/docs/build-decentralized-apps/04-cross-chain-messaging.mdx +++ b/docs/build-decentralized-apps/04-cross-chain-messaging.mdx @@ -10,16 +10,16 @@ The Arbitrum protocol and related tooling makes it easy for developers to build ## Ethereum-to-Arbitrum messaging -Arbitrary parent to child chain contract calls can be created via the Inbox's `createRetryableTicket` method; upon publishing the parent chain transaction, the child chain side will typically get included within minutes. Commonly, the child chain execution will automatically succeed, but if reverts, and it can be rexecuted via a call to the `redeem` method of the [`ArbRetryableTx`](/build-decentralized-apps/precompiles/02-reference.mdx#arbretryabletx) precompile. +Arbitrary parent to child chain contract calls can be created via the Inbox's `createRetryableTicket` method; upon publishing the parent chain transaction, the child chain side will typically get included within minutes. Commonly, the child chain execution will automatically succeed, but if it reverts, it can be rexecuted via a call to the `redeem` method of the [`ArbRetryableTx`](/build-decentralized-apps/precompiles/02-reference.mdx#arbretryabletx) precompile. -For details and protocol specification, see [Parent to child chain messages](/how-arbitrum-works/deep-dives/l1-to-l2-messaging.mdx). - -For an example of retryable tickets in action, see the [`Greeter`](https://github.com/OffchainLabs/arbitrum-tutorials/tree/master/packages/greeter) tutorial, which uses the [Arbitrum SDK](../../sdk). +- **How-to guide**: [How to bridge from parent chain to child chain](/build-decentralized-apps/how-to-bridge-from-parent-chain.mdx) +- **Protocol details**: [Parent to child chain messaging](/how-arbitrum-works/deep-dives/l1-to-l2-messaging.mdx) +- **Example**: [`Greeter` tutorial](https://github.com/OffchainLabs/arbitrum-tutorials/tree/master/packages/greeter) using the [Arbitrum SDK](../../sdk) ## Arbitrum-to-Ethereum messaging -Similarly, child chain contracts can send Arbitrary messages for execution on the parent chain. These are initiated via calls to the [`ArbSys`](/build-decentralized-apps/precompiles/02-reference.mdx#arbsys) precompile contract's `sendTxToL1` method. Upon confirmation (about one week later), they can execute by retrieving the relevant data via a call to `NodeInterface` contract's `constructOutboxProof` method, and then executing them via the `Outbox`'s `executeTransaction` method. - -For details and protocol specification, see [Child to parent chain messages](/how-arbitrum-works/deep-dives/l2-to-l1-messaging.mdx). +Similarly, child chain contracts can send arbitrary messages for execution on the parent chain. These are initiated via calls to the [`ArbSys`](/build-decentralized-apps/precompiles/02-reference.mdx#arbsys) precompile contract's `sendTxToL1` method. Upon confirmation (about one week later), they can be executed by retrieving the relevant data via a call to the `NodeInterface` contract's `constructOutboxProof` method, and then executing them via the `Outbox`'s `executeTransaction` method. -For a demo, see the [Outbox Tutorial](https://github.com/OffchainLabs/arbitrum-tutorials/tree/master/packages/outbox-execute). +- **How-to guide**: [How to bridge to parent chain from child chain](/build-decentralized-apps/how-to-bridge-to-parent-chain.mdx) +- **Protocol details**: [Child to parent chain messaging](/how-arbitrum-works/deep-dives/l2-to-l1-messaging.mdx) +- **Example**: [Outbox Tutorial](https://github.com/OffchainLabs/arbitrum-tutorials/tree/master/packages/outbox-execute) diff --git a/docs/build-decentralized-apps/arbitrum-vs-ethereum/03-rpc-methods.mdx b/docs/build-decentralized-apps/arbitrum-vs-ethereum/03-rpc-methods.mdx index 3c6314fc56..fd1edf7ac0 100644 --- a/docs/build-decentralized-apps/arbitrum-vs-ethereum/03-rpc-methods.mdx +++ b/docs/build-decentralized-apps/arbitrum-vs-ethereum/03-rpc-methods.mdx @@ -22,7 +22,7 @@ When calling [`eth_getTransactionByHash`](https://ethereum.org/en/developers/doc ### Transaction types -In addition to the [three transaction types](https://ethereum.org/en/developers/docs/transactions/#types-of-transactions) currently supported on Ethereum, Arbitrum adds additional types listed below and [documented in full detail here](/how-arbitrum-works/deep-dives/geth.mdx#transaction-types). +In addition to the [three transaction types](https://ethereum.org/en/developers/docs/transactions/#types-of-transactions) currently supported on Ethereum, Arbitrum adds additional types listed below and [documented in full detail here](/how-arbitrum-works/reference/geth.mdx#transaction-types). On RPC calls that return transactions, the `type` field will reflect the custom codes where applicable. diff --git a/docs/build-decentralized-apps/custom-gas-token-sdk.mdx b/docs/build-decentralized-apps/custom-gas-token-sdk.mdx index f100dac3cc..fec11f5da0 100644 --- a/docs/build-decentralized-apps/custom-gas-token-sdk.mdx +++ b/docs/build-decentralized-apps/custom-gas-token-sdk.mdx @@ -40,7 +40,7 @@ You should use `EthBridger` when bridging the native token between the parent ch ### Registering a custom token in the Token Bridge -When [registering a custom token in the Token Bridge](/build-decentralized-apps/token-bridging/03-token-bridge-erc20.mdx#setting-up-your-token-with-the-generic-custom-gateway) of a custom-gas-token Arbitrum chain, there's an additional step to perform before calling `registerTokenToL2`. +When [registering a custom token in the Token Bridge](/how-arbitrum-works/deep-dives/token-bridging.mdx#setting-up-your-token-with-the-generic-custom-gateway) of a custom-gas-token Arbitrum chain, there's an additional step to perform before calling `registerTokenToL2`. Since the Token Bridge [router](https://github.com/OffchainLabs/token-bridge-contracts/blob/main/contracts/tokenbridge/ethereum/gateway/L1OrbitGatewayRouter.sol#L142-L144) and the [generic-custom gateway](https://github.com/OffchainLabs/token-bridge-contracts/blob/main/contracts/tokenbridge/ethereum/gateway/L1OrbitCustomGateway.sol#L203-L210) expect to have allowance to transfer the native token from the `msg.sender()` to the inbox contract, it's usually the token in the parent chain who handles those approvals. In the [TestCustomTokenL1](https://github.com/OffchainLabs/token-bridge-contracts/blob/main/contracts/tokenbridge/test/TestCustomTokenL1.sol#L158-L168), we offer as an example of implementation. We see that the contract transfers the native tokens to itself and then approves the router and gateway contracts. If we follow that implementation, we only need to send an approval transaction to the native token to allow the `TestCustomTokenL1` to transfer the native token from the caller of the `registerTokenToL2` function to itself. diff --git a/docs/build-decentralized-apps/how-to-bridge-from-parent-chain.mdx b/docs/build-decentralized-apps/how-to-bridge-from-parent-chain.mdx new file mode 100644 index 0000000000..75ef314ba6 --- /dev/null +++ b/docs/build-decentralized-apps/how-to-bridge-from-parent-chain.mdx @@ -0,0 +1,257 @@ +--- +title: How to bridge from parent chain to child chain +description: Learn how to programmatically bridge ETH and send messages from a parent chain to an Arbitrum child chain +content_type: how-to +--- + +import ImageZoom from '@site/src/components/ImageZoom'; + +This guide explains how to programmatically send messages and bridge assets from a parent chain (like Ethereum) to an Arbitrum child chain. For conceptual information about the messaging protocol, see [Parent to child chain messaging](/how-arbitrum-works/deep-dives/l1-to-l2-messaging.mdx). + +## Prerequisites + +- A parent chain wallet with funds (ETH or the chain's native token) +- Access to the parent chain's Inbox contract address +- Familiarity with smart contract interactions + +## Bridging ETH to a child chain + +To bridge ETH from a parent chain to a child chain, use the `depositEth` method on the Inbox contract: + +```javascript +function depositEth(address destAddr) external payable override returns (uint256) +``` + +### Example: Depositing ETH + +```javascript +const inbox = new ethers.Contract(inboxAddress, inboxABI, parentSigner); +const tx = await inbox.depositEth(destinationAddress, { + value: ethers.utils.parseEther('0.1'), // Amount to deposit +}); +await tx.wait(); +``` + +:::warning + +Depositing `ETH` directly via `depositEth` to a contract on a child chain **will not** invoke that contract's fallback function. If you need to trigger a fallback function, use retryable tickets instead. + +::: + +### How ETH deposits work + +When you deposit ETH, the funds are held in the Arbitrum Bridge contract on the parent chain. The bridge then credits the deposited amount to your address on the child chain: + + + +### Address aliasing for contract depositors + +When you deposit ETH from the parent chain, the destination address on the child chain depends on the caller type: + +- **EOA caller**: The deposited `ETH` appears at the same address on the child chain +- **Contract caller**: The `ETH` goes to the contract's aliased address on the child chain +- **7702-enabled account**: Similar to contracts, uses the aliased address + +The alias is calculated as: + +```solidity +Child_Alias = Parent_Contract_Address + 0x1111000000000000000000000000000000001111 +``` + +To recover the original parent chain address in your child chain contract, use the `AddressAliasHelper` library: + +```solidity +modifier onlyFromMyL1Contract() { + require( + AddressAliasHelper.undoL1ToL2Alias(msg.sender) == myL1ContractAddress, + "ONLY_COUNTERPART_CONTRACT" + ); + _; +} +``` + +## Sending transactions via the Delayed Inbox + +The Delayed Inbox allows you to send arbitrary messages from the parent chain to the child chain, bypassing the Sequencer if needed. + +### Sending signed messages + +Signed messages prove EOA ownership and execute with the signer's address on the child chain (no aliasing). + +#### Method 1: sendL2Message + +More flexible, can be called by EOAs or contracts: + +```solidity +function sendL2Message( + bytes calldata messageData +) external returns (uint256) +``` + +#### Method 2: sendL2MessageFromOrigin + +Cheaper gas costs, only callable by EOAs: + +```solidity +function sendL2MessageFromOrigin( + bytes calldata messageData +) external returns (uint256) +``` + +**Example use case**: [Withdraw Ether tutorial](https://github.com/OffchainLabs/arbitrum-tutorials/blob/a1c3f64a5abdd0f0e728cb94d4ecc2700eab7579/packages/delayedInbox-l2msg/scripts/withdrawFunds.js#L61-L65) + +### Sending unsigned messages + +Unsigned messages are automatically aliased for security. The Delayed Inbox provides four methods for unsigned messages, divided by sender type (EOA vs contract) and funding source (parent chain vs child chain balance): + +#### From EOAs (with nonce for replay protection) + +**sendL1FundedUnsignedTransaction** - Transfers value from parent to child chain: + +```solidity +function sendL1FundedUnsignedTransaction( + uint256 gasLimit, + uint256 maxFeePerGas, + uint256 nonce, + address to, + bytes calldata data +) external payable returns (uint256) +``` + +**sendUnsignedTransaction** - Uses child chain balance (no L1 funds transferred): + +```solidity +function sendUnsignedTransaction( + uint256 gasLimit, + uint256 maxFeePerGas, + uint256 nonce, + address to, + uint256 value, + bytes calldata data +) external returns (uint256) +``` + +#### From Contracts (standard Ethereum replay protection) + +**sendContractTransaction** - Uses contract's existing child chain balance: + +```solidity +function sendContractTransaction( + uint256 gasLimit, + uint256 maxFeePerGas, + address to, + uint256 value, + bytes calldata data +) external returns (uint256) +``` + +**sendL1FundedContractTransaction** - Transfers additional funds from parent to child: + +```solidity +function sendL1FundedContractTransaction( + uint256 gasLimit, + uint256 maxFeePerGas, + address to, + bytes calldata data +) external payable returns (uint256) +``` + +## Creating retryable tickets + +Retryable tickets are Arbitrum's canonical mechanism for reliable cross-chain message delivery. They automatically retry failed executions. + +### Key retryable ticket parameters + +Understanding these parameters is crucial for successful retryable ticket creation: + +- **`l1CallValue`** (msg.value): Total ETH sent with the transaction from parent chain. This funds the ticket submission, gas, and call value. + +- **`to`**: The destination child chain address that will receive the retryable ticket execution. + +- **`l2CallValue`**: The amount of ETH to be sent as `callvalue` when executing the retryable on the child chain. This is supplied within the `l1CallValue` deposit. + +- **`maxSubmissionCost`**: Maximum ETH to pay for submitting the ticket. This amount is: + + - Supplied within the deposit (`l1CallValue`) + - Later deducted from sender's child chain balance + - Directly proportional to retryable data size and parent chain basefee + +- **`excessFeeRefundAddress`**: Where to refund unused gas and submission costs: + + - Formula: `(gasLimit × maxFeePerGas - execution cost) + (maxSubmissionCost - submission cost)` + - **Important**: If auto-redeem fails, excess deposit goes to the alias of the L1 sender, not this address + +- **`callValueRefundAddress`**: The child chain address to credit the `l2CallValue` if the ticket times out or gets canceled. This address is also the "beneficiary" with permission to cancel the ticket. + +- **`gasLimit`**: Maximum gas for child chain execution of the ticket. Used for the automatic redemption attempt. + +- **`maxFeePerGas`**: Gas price bid for child chain execution, supplied within the deposit (`l1CallValue`). + +- **`data`**: Calldata to send to the destination address on the child chain. + +### Creating a retryable ticket + +```solidity +function createRetryableTicket( + address to, + uint256 l2CallValue, + uint256 maxSubmissionCost, + address excessFeeRefundAddress, + address callValueRefundAddress, + uint256 gasLimit, + uint256 maxFeePerGas, + bytes calldata data +) external payable returns (uint256) +``` + +### Example using the Arbitrum SDK + +```javascript +import { L1ToL2MessageGasEstimator } from '@arbitrum/sdk'; + +// Estimate gas for the retryable ticket +const l1ToL2MessageGasEstimator = new L1ToL2MessageGasEstimator(l2Provider); +const retryableGasParams = await l1ToL2MessageGasEstimator.estimateAll( + { + from: senderAddress, + to: destinationAddress, + l2CallValue: ethers.utils.parseEther('0.01'), + excessFeeRefundAddress: refundAddress, + callValueRefundAddress: refundAddress, + data: calldata, + }, + await l1Provider.getBaseFeePerGas(), + l1Provider, +); + +// Create the retryable ticket +const inbox = new ethers.Contract(inboxAddress, inboxABI, l1Signer); +const tx = await inbox.createRetryableTicket( + destinationAddress, + ethers.utils.parseEther('0.01'), // l2CallValue + retryableGasParams.maxSubmissionCost, + refundAddress, + refundAddress, + retryableGasParams.gasLimit, + retryableGasParams.maxFeePerGas, + calldata, + { + value: retryableGasParams.deposit, + }, +); +await tx.wait(); +``` + +### Redeeming retryable tickets + +Retryable tickets can auto-redeem if sufficient gas is provided. If the initial redemption fails, you can manually redeem using the `ArbRetryableTx` precompile: + +```solidity +ArbRetryableTx(address(110)).redeem(ticketId); +``` + +## Next steps + +- For protocol-level details, see [Parent to child chain messaging](/how-arbitrum-works/deep-dives/l1-to-l2-messaging.mdx) +- For token bridging, see [Token bridging overview](/how-arbitrum-works/deep-dives/token-bridging.mdx) +- For bridging tokens programmatically, see [Bridge tokens programmatically](/build-decentralized-apps/token-bridging/get-started.mdx) diff --git a/docs/build-decentralized-apps/how-to-bridge-to-parent-chain.mdx b/docs/build-decentralized-apps/how-to-bridge-to-parent-chain.mdx new file mode 100644 index 0000000000..9342ef20f9 --- /dev/null +++ b/docs/build-decentralized-apps/how-to-bridge-to-parent-chain.mdx @@ -0,0 +1,244 @@ +--- +title: How to bridge from child chain to parent chain +description: Learn how to programmatically withdraw ETH and send messages from an Arbitrum child chain to a parent chain +content_type: how-to +--- + +import ImageZoom from '@site/src/components/ImageZoom'; + +This guide explains how to programmatically send messages and withdraw assets from an Arbitrum child chain back to a parent chain (like Ethereum). For conceptual information about the messaging protocol, see [Child to parent chain messaging](/how-arbitrum-works/deep-dives/l2-to-l1-messaging.mdx). + +## Prerequisites + +- A child chain wallet with funds +- Access to parent chain infrastructure (for executing the final step) +- Awareness that child-to-parent messages require ~7 days to finalize + +## Overview of the process + +Child-to-parent chain messaging follows these steps: + +1. **Send message on child chain**: Call `ArbSys.sendTxToL1` to initiate the message +2. **Wait for finalization**: The message enters a ~7 day challenge period +3. **Execute on parent chain**: After finalization, call `Outbox.executeTransaction` to complete the transfer + +## Sending a message from child to parent chain + +To send a message from the child chain to the parent chain, use the `ArbSys` precompile's `sendTxToL1` method: + +```solidity +function sendTxToL1( + address destination, + bytes calldata data +) external payable returns (uint256) +``` + +### Parameters + +- **`destination`**: The parent chain address that will receive the message +- **`data`**: Calldata to send to the destination address on the parent chain + +### Return value + +Returns a unique identifier for the message, which can be used to track the message status and construct the outbox proof for execution. + +The `ArbSys` precompile is located at address `0x0000000000000000000000000000000000000064`. + +**Example**: Sending a simple message + +```javascript +const arbSys = new ethers.Contract( + '0x0000000000000000000000000000000000000064', + arbSysABI, + childChainSigner, +); + +const tx = await arbSys.sendTxToL1( + parentChainDestination, + ethers.utils.toUtf8Bytes('Hello from L2!'), +); +const receipt = await tx.wait(); +``` + +## Executing the message on the parent chain + +After the ~7 day challenge period, you can execute the message on the parent chain. + +### Step 1: Retrieve proof data + +Use the `NodeInterface` contract to get the Merkle proof: + +```solidity +function constructOutboxProof( + uint64 size, + uint64 leaf +) external view returns ( + bytes32[] memory proof, + uint256 path, + address l2Sender, + address l1Dest, + uint256 l2Block, + uint256 l1Block, + uint256 timestamp, + uint256 amount, + bytes memory calldataForL1 +) +``` + +**Parameters:** + +- **`size`**: The batch number containing your message +- **`leaf`**: The index of your message within the batch (0-indexed) + +**Example usage:** + +```javascript +const nodeInterface = new ethers.Contract( + '0x00000000000000000000000000000000000000C8', + nodeInterfaceABI, + childChainProvider, +); + +const proofData = await nodeInterface.constructOutboxProof(batchNumber, indexInBatch); +``` + +:::note + +`NodeInterface` is a "virtual" contract accessible at `0x00000000000000000000000000000000000000C8`. It's not a true precompile but provides Arbitrum-specific data without requiring a custom RPC. + +::: + +### Step 2: Execute on the parent chain + +Call `Outbox.executeTransaction` with the proof data from step 1: + +```solidity +function executeTransaction( + bytes32[] calldata proof, + uint256 path, + address l2Sender, + address l1Dest, + uint256 l2Block, + uint256 l1Block, + uint256 timestamp, + uint256 amount, + bytes calldata calldataForL1 +) external +``` + +All parameters come from the `constructOutboxProof` call. The method will execute the message at the `l1Dest` address with the provided calldata and amount. + +**Example usage:** + +```javascript +const outbox = new ethers.Contract(outboxAddress, outboxABI, parentChainSigner); + +const tx = await outbox.executeTransaction( + proofData.proof, + proofData.path, + proofData.l2Sender, + proofData.l1Dest, + proofData.l2Block, + proofData.l1Block, + proofData.timestamp, + proofData.amount, + proofData.calldataForL1, +); +await tx.wait(); +``` + +## Withdrawing ETH + +To withdraw ETH from the child chain, use the `ArbSys` precompile's `withdrawEth` method: + +```solidity +function withdrawEth( + address destination +) external payable returns (uint256) +``` + +### Parameters + +- **`destination`**: The parent chain address that will receive the ETH +- **Value** (msg.value): The amount of ETH to withdraw from the child chain + +### Return value + +Returns a unique identifier for the withdrawal message. + +### How ETH withdrawal works + +1. **On the child chain**: The ETH balance is burned and a message is created +2. **Challenge period**: Wait ~7 days for the assertion to finalize +3. **On the parent chain**: Execute via `Outbox.executeTransaction` to claim your ETH + +`ArbSys.withdrawEth` is equivalent to calling `ArbSys.sendTxToL1` with empty calldata. Like any child-to-parent message, it requires executing on the parent chain after the dispute period. + +### Example: Withdrawing ETH + +```javascript +const arbSys = new ethers.Contract( + '0x0000000000000000000000000000000000000064', + arbSysABI, + childChainSigner, +); + +// Withdraw 0.1 ETH +const tx = await arbSys.withdrawEth(parentChainAddress, { + value: ethers.utils.parseEther('0.1'), +}); +const receipt = await tx.wait(); + +// After 7 days, execute on parent chain using the steps above +``` + +The withdrawal process: + + + +## Withdrawing ERC-20 tokens + +For ERC-20 token withdrawals, see the dedicated [Withdraw tokens guide](/build-decentralized-apps/token-bridging/withdraw-tokens.mdx), which provides detailed instructions for using Arbitrum's canonical token bridge. + +## Using the Arbitrum SDK + +The Arbitrum SDK simplifies child-to-parent messaging: + +```javascript +import { L2ToL1MessageStatus, L2TransactionReceipt } from '@arbitrum/sdk'; + +// Get the L2 transaction receipt +const l2Receipt = await childChainProvider.getTransactionReceipt(l2TxHash); +const l2TxReceipt = new L2TransactionReceipt(l2Receipt); + +// Get L2ToL1 messages from the transaction +const messages = await l2TxReceipt.getL2ToL1Messages(parentChainSigner); + +// Wait for the message to be executable +const message = messages[0]; +await message.waitUntilReadyToExecute(childChainProvider); + +// Execute the message on the parent chain +const executeResult = await message.execute(childChainProvider); +await executeResult.wait(); +``` + +## Message lifecycle + +Child-to-parent messages go through these stages: + +| Stage | Description | +| ---------------------------------------- | --------------------------------------------------------------------------- | +| Posted on child chain | The message is sent via `ArbSys.sendTxToL1` | +| Waiting for finalization | The assertion containing the message is in the challenge period (~6.4 days) | +| Confirmed and executable on parent chain | The assertion is confirmed, and the message can be executed in the outbox | + +## Next steps + +- For protocol-level details, see [Child to parent chain messaging](/how-arbitrum-works/deep-dives/l2-to-l1-messaging.mdx) +- For token bridging concepts, see [Token bridging overview](/how-arbitrum-works/deep-dives/token-bridging.mdx) +- For the Arbitrum SDK documentation, see [SDK reference](/sdk) diff --git a/docs/build-decentralized-apps/token-bridging/01-overview.mdx b/docs/build-decentralized-apps/token-bridging/01-overview.mdx deleted file mode 100644 index 139e5b1ccd..0000000000 --- a/docs/build-decentralized-apps/token-bridging/01-overview.mdx +++ /dev/null @@ -1,19 +0,0 @@ ---- -title: 'Token bridging overview' -description: An overview of token bridging resources -author: dzgoldman -user_story: As a developer, I want to understand how the token bridge works and what options exist to bridge assets between layers. -content_type: overview -sidebar_position: 1 -displayed_sidebar: buildAppsSidebar ---- - -Token bridging is a fundamental aspect of any Layer 2 (child chain) protocol. Arbitrum uses its ability to pass messages between parent and child chains (see [Cross-chain messaging](/build-decentralized-apps/04-cross-chain-messaging.mdx)) to enable projects to move assets between Ethereum and an Arbitrum chain trustlessly, and vice versa. Any asset and asset type in principle can be bridged, including `ETH`, `ERC-20` tokens, and `ERC-721` tokens, among others. - -The following sections provide a series of conceptual documents that explain how asset bridging works and the options available to bridge `ETH` and other types of assets between layers. Additionally, a series of how-to guides showcases the different methods for making your token bridgeable. - -This section has three parts: - -- [`ETH` bridging](/build-decentralized-apps/token-bridging/02-token-bridge-ether.mdx): explains how Arbitrum handles bridging `ETH`, the native token of Ethereum and the Arbitrum chains, between the parent and child chain. -- [`ERC-20` token bridging](/build-decentralized-apps/token-bridging/03-token-bridge-erc20.mdx): explains the architecture of the token bridge for this type of asset, describing the different options available to make a token bridgeable. -- [Bridge tokens programmatically](/build-decentralized-apps/token-bridging/bridge-tokens-programmatically/01-get-started.mdx): goes over the process of making an `ERC-20` token bridgeable using the different types of gateways available in the token bridge. diff --git a/docs/build-decentralized-apps/token-bridging/02-token-bridge-ether.mdx b/docs/build-decentralized-apps/token-bridging/02-token-bridge-ether.mdx deleted file mode 100644 index d6f4803f34..0000000000 --- a/docs/build-decentralized-apps/token-bridging/02-token-bridge-ether.mdx +++ /dev/null @@ -1,49 +0,0 @@ ---- -title: 'ETH bridging' -description: Describes how bridging ether works on Arbitrum -author: dzgoldman -user_story: As a developer, I want to understand how bridging ether works on Arbitrum -content_type: concept -sidebar_position: 2 -displayed_sidebar: buildAppsSidebar ---- - -import ImageZoom from '@site/src/components/ImageZoom'; - -Ether (`ETH`) is the native currency of Ethereum and all Arbitrum chains. `ETH` is used to pay the necessary fees to execute transactions on those chains. Bridging `ETH` from Ethereum (the parent chain) to an Arbitrum chain (the child chain) follows a different process than the one followed when bridging other types of assets. - -## Depositing ether - -To move `ETH` from the parent chain to the child chain, you execute a deposit transaction via `Inbox.depositEth`. This transaction transfers funds to the Bridge contract on the parent chain and credits the same funds to you inside the Arbitrum chain at the specified address. - -```sol -function depositEth(address destAddr) external payable override returns (uint256) -``` - -:::warning - -If the transaction comes from a `7702-enabled account`, the destination address on L2 is subject to address aliasing. The `ETH` will be credited to the aliased address of the sender, not the raw `msg.sender`. See [address aliasing](https://docs.arbitrum.io/how-arbitrum-works/l1-to-l2-messaging#address-aliasing) for details. - -::: - -The following diagram depicts the process that funds follow during a deposit operation. - - - -Regarding the parent chain, all deposited funds are held in the Arbitrum Bridge contract. Once finalized, the `ETH` becomes available on the L2 at the aliased or specified address, depending on the sender type. - -## Withdrawing ether - -Withdrawing ether can be done using the [`ArbSys` precompile](/build-decentralized-apps/precompiles/02-reference.mdx#arbsys)'s `withdrawEth` method: - -```sol -ArbSys(100).withdrawEth{ value: 2300000 }(destAddress) -``` - -Upon withdrawal, the Ether balance is burned on the Arbitrum side and will later be made available on the Ethereum side. - -`ArbSys.withdrawEth` is a convenience function equivalent to calling `ArbSys.sendTxToL1` with empty `calldataForL1`. Like any other `sendTxToL1` call, it will require an additional call to `Outbox.executeTransaction` on the parent chain after the dispute period elapses for the user to finalize claiming their funds on the parent chain (see [Child to parent chain messaging](/how-arbitrum-works/deep-dives/l2-to-l1-messaging.mdx)). Once the withdrawal is executed from the Outbox, the user's `ETH` balance will be credited on the parent chain. - -The following diagram depicts the process that funds follow during a withdrawal operation. - - diff --git a/docs/build-decentralized-apps/token-bridging/bridge-tokens-programmatically/01-get-started.mdx b/docs/build-decentralized-apps/token-bridging/bridge-tokens-programmatically/01-get-started.mdx deleted file mode 100644 index 93ed153406..0000000000 --- a/docs/build-decentralized-apps/token-bridging/bridge-tokens-programmatically/01-get-started.mdx +++ /dev/null @@ -1,23 +0,0 @@ ---- -title: 'Get started with token bridging' -description: Learn the different options available to bridge tokens programmatically -user_story: As a developer, I want to understand how to bridge tokens between Ethereum and Arbitrum. -content_type: overview -displayed_sidebar: buildAppsSidebar ---- - -Token bridging is a fundamental aspect of any child chain protocol. It allows projects to quickly integrate with the Arbitrum ecosystem by leveraging their existing parent chain tokens. - -This section offers a series of how-tos showcasing the different methods available for making your token bridgeable. - -You have three options to consider when deciding on how to bridge your token: - -1. [Standard gateway](/build-decentralized-apps/token-bridging/bridge-tokens-programmatically/02-how-to-bridge-tokens-standard.mdx): opt for this method if you want to have a standard `ERC-20` token automatically deployed on Arbitrum, which will act as the child chain counterpart to your parent chain token. For additional information, please refer to the [standard `ERC-20` gateway](/build-decentralized-apps/token-bridging/03-token-bridge-erc20.mdx#default-standard-bridging) in the conceptual page. -2. [Generic-custom gateway](/build-decentralized-apps/token-bridging/bridge-tokens-programmatically/03-how-to-bridge-tokens-generic-custom.mdx): choose this method if you require custom functionality for your `ERC-20` token on Arbitrum. You will deploy your counterpart token on Arbitrum equipped with the unique features you wish to implement. For additional information, please refer to the [Arbitrum generic custom gateway](/build-decentralized-apps/token-bridging/03-token-bridge-erc20.mdx#the-arbitrum-generic-custom-gateway) in the conceptual page. -3. [Custom gateway](/build-decentralized-apps/token-bridging/bridge-tokens-programmatically/04-how-to-bridge-tokens-custom-gateway.mdx): This method is intended for edge cases where a custom `ERC-20` token is insufficient. You will need an additional layer of flexibility with the gateway (for example, your token has the capacity to increase its supply on the child chain, and you want those child chain-minted tokens to be withdrawable back to the parent chain and recognized by the parent chain contract). For additional information, please refer to the [other types of gateways](/build-decentralized-apps/token-bridging/03-token-bridge-erc20.mdx#other-flavors-of-gateways) in the conceptual page. - -## What if I want to bridge a token programmatically? - -Refer to the [How to bridge tokens via Arbitrum’s standard gateway](/build-decentralized-apps/token-bridging/bridge-tokens-programmatically/02-how-to-bridge-tokens-standard.mdx) page that provides an example of how to deposit your tokens (from the parent to child chain) programmatically, specifically in Steps 2 to 5. - -You can also find scripts demonstrating [parent-to-child chain bridging (deposits)](https://github.com/OffchainLabs/arbitrum-tutorials/tree/master/packages/token-deposit) and [child-to-parent chain bridging (withdrawals)](https://github.com/OffchainLabs/arbitrum-tutorials/tree/master/packages/token-withdraw) using the Arbitrum SDK. diff --git a/docs/build-decentralized-apps/token-bridging/bridge-tokens-programmatically/02-how-to-bridge-tokens-standard.mdx b/docs/build-decentralized-apps/token-bridging/bridge-tokens-programmatically/02-how-to-bridge-tokens-standard.mdx deleted file mode 100644 index 3412398745..0000000000 --- a/docs/build-decentralized-apps/token-bridging/bridge-tokens-programmatically/02-how-to-bridge-tokens-standard.mdx +++ /dev/null @@ -1,224 +0,0 @@ ---- -title: "Bridge tokens via Arbitrum's standard `ERC-20` gateway" -description: Learn how to programmatically bridge tokens between Ethereum and Arbitrum using Arbitrum’s standard ER-C20 gateway -user_story: As a developer, I want to understand how to bridge tokens between Ethereum and Arbitrum using the standard ER-C20 gateway. -content_type: how-to -displayed_sidebar: buildAppsSidebar ---- - -In this how-to, you’ll learn how to bridge your own token between Ethereum (parent chain) and Arbitrum (child chain), using [Arbitrum’s standard `ERC20` gateway](/build-decentralized-apps/token-bridging/03-token-bridge-erc20.mdx#default-standard-bridging). For alternative ways of bridging tokens, don’t forget to check out this [overview](/build-decentralized-apps/token-bridging/bridge-tokens-programmatically/01-get-started.mdx). - -Familiarity with [Arbitrum’s token bridge system](/build-decentralized-apps/token-bridging/01-overview.mdx), smart contracts, and blockchain development is expected. If you’re new to blockchain development, consider reviewing our [Quickstart: Build a dApp with Arbitrum (Solidity, Remix)](/build-decentralized-apps/01-quickstart-solidity-remix.mdx) before proceeding. We will use [Arbitrum’s SDK](https://github.com/OffchainLabs/arbitrum-sdk) throughout this how-to, although no prior knowledge is required. - -We will walk you through all the steps involved in the process. However, if you want to jump straight to the code, we have created [this script in our tutorials repository](https://github.com/OffchainLabs/arbitrum-tutorials/tree/master/packages/token-deposit) that encapsulates the entire process. - -## Step 1: Create a token and deploy it on the parent chain - -We‘ll begin the process by creating and deploying a sample token to the parent chain. If you already have a token contract on the parent chain, you don’t need to perform this step. - -We first create a standard `ERC-20` contract using OpenZeppelin’s implementation. We make only one adjustment to that implementation, for simplicity, although it is not required: we specify an `initialSupply` to be pre-minted and sent to the deployer address upon creation. - -```solidity -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; - -contract DappToken is ERC20 { - /** - * @dev See {ERC20-constructor}. - * - * An initial supply amount is passed, which is preminted to the deployer. - */ - constructor(uint256 _initialSupply) ERC20("Dapp Token", "DAPP") { - _mint(msg.sender, _initialSupply * 10 ** decimals()); - } -} -``` - -We now deploy that token to the parent chain. - -```tsx -const { ethers } = require('hardhat'); -const { providers, Wallet } = require('ethers'); -require('dotenv').config(); -const walletPrivateKey = process.env.DEVNET_PRIVKEY; -const l1Provider = new providers.JsonRpcProvider(process.env.L1RPC); -const l1Wallet = new Wallet(walletPrivateKey, l1Provider); - -/** - * For our tests, here we deploy an standard ERC20 token (DappToken) to L1 - * It sends its deployer (us) the initial supply of 1000 - */ -const main = async () => { - console.log('Deploying the test DappToken to L1:'); - const L1DappToken = await (await ethers.getContractFactory('DappToken')).connect(l1Wallet); - const l1DappToken = await L1DappToken.deploy(1000); - - await l1DappToken.deployed(); - console.log(`DappToken is deployed to L1 at ${l1DappToken.address}`); - - /** - * Get the deployer token balance - */ - const tokenBalance = await l1DappToken.balanceOf(l1Wallet.address); - console.log(`Initial token balance of deployer: ${tokenBalance}`); -}; - -main() - .then(() => process.exit(0)) - .catch((error) => { - console.error(error); - process.exit(1); - }); -``` - -## Step 2: Identify the bridge contracts to call (concepts summary) - -As stated in the [token bridge conceptual page](/build-decentralized-apps/token-bridging/03-token-bridge-erc20.mdx#default-standard-bridging), when using Arbitrum’s standard `ERC-20` gateway, you don’t need to do any pre-configuration process. Your token will be “bridgeable” out of the box. - -As explained in the conceptual page, there are two contracts that we need to be aware of when bridging tokens: - -- **Router contract**: this is the contract that we’ll interact with. It keeps a mapping of the gateway contracts assigned to each token, fallbacking to a default gateway for standard `ERC-20` tokens. -- **Gateway contract**: This is the contract that escrows or burns the tokens in the layer of origin and sends the message over to the counterpart layer to mint or release the tokens there. - -For simplicity, this how-to will focus on the first case: bridging from the parent chain (Ethereum) to the child chain (Arbitrum). - -We’ll explain below what specific contracts and methods need to be called to bridge your token, but you can abstract this whole process of finding the right addresses by using Arbitrum’s SDK. You can use the [deposit](../../../sdk/assetBridger/erc20Bridger#deposit-1) function of the [`Erc20Bridger`](../../../sdk/assetBridger/erc20Bridger) class to bridge your tokens, which will use the appropriate router contract based on the network you’re connected to, and will relay the request to the appropriate gateway contract. You can also use the function [`getParentGatewayAddress`](../../../sdk/assetBridger/erc20Bridger#getparentgatewayaddress-1) to get the address of the gateway contract that’s going to be used. But don’t worry about any of this yet; we’ll use those functions in the next steps. - -Now, here’s an explanation of the contracts and methods that need called to bridge your token manually: - -- When bridging from the parent chain (Ethereum) to the child chain (Arbitrum), you’ll need to interact with the `L1GatewayRouter` contract, by calling the `outboundTransferCustomRefund` method. This router contract will relay your request to the appropriate gateway contract, specifically, the `L1ERC20Gateway` contract. To get the address of the gateway contract that’s going to be used, you can call the `getGateway` function in the `L1GatewayRouter` contract. -- When bridging from the child chain (Arbitrum) to the parent chain (Ethereum), you’ll need to interact with the `L2GatewayRouter` contract by calling the `outBoundTransfer` method. This router contract will relay your request to the appropriate gateway contract, specifically, the `L2ERC20Gateway` contract. To get the address of the gateway contract that’s going to be used, you can call the `getGateway` function in the `L2GatewayRouter` contract. - -You can find the addresses of the contracts involved in the process on [this page](/build-decentralized-apps/reference/contract-addresses#token-bridge-smart-contracts). - -## Step 3: Approve token allowance for the gateway contract - -The gateway contract will be the one that will transfer the tokens to be bridged over. The next step is to allow the gateway contract to proceed accordingly. - -We typically do that by using the `approve` method of the token. Still, you can use Arbitrum’s SDK to abstract this process by calling the method [`approveToken`](../../../sdk/assetBridger/erc20Bridger#approvetoken-1) of the [`Erc20Bridger`](../../../sdk/assetBridger/erc20Bridger) class, which will call the approve method of the token passed by parameter, and set the allowance to the appropriate gateway contract. - -```tsx -/** - * Use l2Network to create an Arbitrum SDK Erc20Bridger instance - * We'll use Erc20Bridger for its convenience methods around transferring tokens to L2 - */ -const l2Network = await getArbitrumNetwork(l2Provider); -const erc20Bridge = new Erc20Bridger(l2Network); - -/** - * The Standard Gateway contract will ultimately be making the token transfer call; thus, that's the contract we need to approve. - * erc20Bridger.approveToken handles this approval - * Arguments required are: - * (1) l1Signer: The L1 address transferring token to L2 - * (2) erc20L1Address: L1 address of the ERC20 token to be deposited to L2 - */ -console.log('Approving:'); -const l1Erc20Address = l1DappToken.address; -const approveTx = await erc20Bridger.approveToken({ - parentSigner: l1Wallet, - erc20ParentAddress: l1Erc20Address, -}); - -const approveRec = await approveTx.wait(); -console.log( - `You successfully allowed the Arbitrum Bridge to spend DappToken ${approveRec.transactionHash}`, -); -``` - -As mentioned before, you can also call the `approve` method of the token and send as a parameter the address of the gateway contract, which you can find by calling the method `getGateway` function in the router contract. - -## Step 4: Start the bridging process through the router contract - -After allowing the gateway contract to transfer the tokens, we can now start the bridging process. - -You can use Arbitrum’s SDK to abstract this process by calling the method [`deposit`](../../../sdk/assetBridger/erc20Bridger#deposit-1) of the [`Erc20Bridger`](../../../sdk/assetBridger/erc20Bridger) class, which will estimate the gas parameters (`maxGas`, `gasPriceBid`, and `maxSubmissionCost`, explained below) and call the `outboundTransferCustomRefund` method of the router contract. You will only need to specify the following parameters: - -- `amount`: Amount of tokens to bridge -- `erc20L1Address`: Parent chain address of the `ERC-20` token to bridge -- `l1Signer`: Signer object of the account transferring the tokens, connected to the parent chain network -- `l2Provider`: Provider connected to the child chain network - -```tsx -/** - * Deposit DappToken to L2 using erc20Bridger. This will escrow funds in the Gateway contract on L1, and send a message to mint tokens on L2. - * The erc20Bridge.deposit method handles computing the necessary fees for automatic-execution of retryable tickets — maxSubmission cost & l2 gas price * gas — and will automatically forward the fees to L2 as callvalue - * Also note that since this is the first DappToken deposit onto L2, a standard Arb ERC20 contract will automatically be deployed. - * Arguments required are: - * (1) amount: The amount of tokens to be transferred to L2 - * (2) erc20L1Address: L1 address of the ERC20 token to be depositted to L2 - * (2) l1Signer: The L1 address transferring token to L2 - * (3) l2Provider: An l2 provider - */ -const depositTx = await erc20Bridger.deposit({ - amount: tokenDepositAmount, - erc20ParentAddress: l1Erc20Address, - parentSigner: l1Wallet, - childProvider: l2Provider, -}); -``` - -As mentioned before, you can also call the method `outboundTransferCustomRefund` manually in the router contract and specify the following parameters: - -- address `_token`: Parent chain address of the `ERC-20` token to bridge -- address `_refundTo`: Account to credit with the excess gas refund on the child chain -- address `_to`: Account to credit with the tokens on the child chain -- uint256 `_amount`: Amount of tokens to bridge -- uint256 `_maxGas`: Max gas deducted from the user’s child chain balance to cover the execution on the child chain -- uint256 `_gasPriceBid`: Gas price for the execution on the child chain -- bytes `_data`: two pieces of data encoded: - - uint256 `maxSubmissionCost`: Max gas deducted from user's child chain balance to cover base submission fee - - bytes `extraData`: “0x” - -## Step 5: Wait for execution on the child chain - -After calling the deposit method (or the `outboundTransferCustomRefund` if you’re choosing the manual way), you’ll have to wait a bit until the message executes on the child chain. We will verify the status of the underlying retryable ticket created to bridge the tokens. Check this page to learn more about [parent-to-child chain messages, also known as retryables](/how-arbitrum-works/deep-dives/l1-to-l2-messaging.mdx). - -You can programmatically wait for the execution of the transaction on the child chain using Arbitrum’s SDK. You should first wait for the execution of the submission transaction (the one sent to the router contract) and then the execution of the child chain transaction. - -```tsx -/** - * Now we wait for L1 and L2 side of transactions to be confirmed - */ -const depositRec = await depositTx.wait(); -const l2Result = await depositRec.waitForChildTransactionReceipt(l2Provider); - -/** - * The `complete` boolean tells us if the l1 to l2 message was successful - */ -l2Result.complete - ? console.log(`L2 message successful: status: ${L1ToL2MessageStatus[l2Result.status]}`) - : console.log(`L2 message failed: status ${L1ToL2MessageStatus[l2Result.status]}`); -``` - -If you’re going the manual way, you can verify if the message executed on the child chain through the [Retryables Dashboard](https://retryable-dashboard.arbitrum.io/). Paste the hash of the transaction submitted to the router contract, and the tool will tell you whether it’s been redeemed or not. - -## Step 6: Check the new token contract created on the child chain - -Finally, let’s find the token contract that has been created on the child chain. - -Using Arbitrum’s SDK, you can call method [`getChildErc20Address`](../../../sdk/assetBridger/erc20Bridger#getchildgatewayaddress-1) of the [`Erc20Bridger`](../../../sdk/assetBridger/erc20Bridger) class, which will return the address of the token contract on the child chain that corresponds to the parent chain token contract sent as parameter. - -```tsx -/** - * Check if our l2Wallet DappToken balance has been updated correctly - * To do so, we use erc20Bridge to get the l2Token address and contract - */ -const l2TokenAddress = await erc20Bridger.getChildErc20Address(l1Erc20Address, l1Provider); -const l2Token = erc20Bridger.getChildTokenContract(l2Provider, l2TokenAddress); -``` - -To do this operation manually, you can call the method `calculateL2TokenAddress` of the router contract. - -If you visit that address on [Arbiscan](https://arbiscan.io/), you’ll notice that it is a copy of the contract [`StandardArbERC20`](https://github.com/OffchainLabs/token-bridge-contracts/blob/main/contracts/tokenbridge/arbitrum/StandardArbERC20.sol). This is the standard contract that is automatically created the first time a token that doesn’t exist in Arbitrum is bridged. [The token bridge conceptual page](/build-decentralized-apps/token-bridging/03-token-bridge-erc20.mdx#default-standard-bridging) has more information about this contract. - -## Conclusion - -After finishing this process, you’ll now have a counterpart token contract automatically created on the child chain. You can bridge tokens between parent and child chains using the original token contract on the parent chain and the standard created contract on the child chain, along with the router and gateway contracts from each layer. - -## Resources - -1. [Concept page: Token Bridge](/build-decentralized-apps/token-bridging/01-overview.mdx) -2. [Arbitrum SDK](https://github.com/OffchainLabs/arbitrum-sdk) -3. [Token bridge contract addresses](/build-decentralized-apps/reference/02-contract-addresses.mdx) diff --git a/docs/build-decentralized-apps/token-bridging/bridge-tokens-programmatically/04-how-to-bridge-tokens-custom-gateway.mdx b/docs/build-decentralized-apps/token-bridging/configure-token-bridging/setup-custom-gateway.mdx similarity index 91% rename from docs/build-decentralized-apps/token-bridging/bridge-tokens-programmatically/04-how-to-bridge-tokens-custom-gateway.mdx rename to docs/build-decentralized-apps/token-bridging/configure-token-bridging/setup-custom-gateway.mdx index 29b7fbb608..e0af985ca9 100644 --- a/docs/build-decentralized-apps/token-bridging/bridge-tokens-programmatically/04-how-to-bridge-tokens-custom-gateway.mdx +++ b/docs/build-decentralized-apps/token-bridging/configure-token-bridging/setup-custom-gateway.mdx @@ -1,26 +1,24 @@ --- -title: 'How to bridge tokens via a custom gateway' -description: Learn how to set up a custom gateway using Arbitrum's Token Bridge to bridge tokens programmatically -reader_audience: developers who want to build on Ethereum/Arbitrum and bridge tokens between layers +title: 'Configure custom gateway bridging' +description: Learn how to create and deploy your own custom gateway for specialized token bridging needs content_type: how-to -displayed_sidebar: buildAppsSidebar --- :::caution Do you really need a custom gateway? -Before starting to implement and deploy a custom gateway, it is strongly encouraged to analyze the current solutions that Arbitrum’s token bridge provides: the [standard gateway](/build-decentralized-apps/token-bridging/bridge-tokens-programmatically/02-how-to-bridge-tokens-standard.mdx) and the [generic-custom gateway](/build-decentralized-apps/token-bridging/bridge-tokens-programmatically/03-how-to-bridge-tokens-generic-custom.mdx). These solutions provide enough functionality to solve the majority of bridging needs from projects. And if you are in doubt about your current approach, you can always ask for assistance on our [Discord server](https://discord.gg/arbitrum). +Before starting to implement and deploy a custom gateway, it is strongly encouraged to analyze the current solutions that Arbitrum's token bridge provides: the [standard gateway](/build-decentralized-apps/token-bridging/configure-token-bridging/setup-standard-gateway.mdx) and the [generic-custom gateway](/build-decentralized-apps/token-bridging/configure-token-bridging/setup-generic-custom-gateway.mdx). These solutions provide enough functionality to solve the majority of bridging needs from projects. And if you are in doubt about your current approach, you can always ask for assistance on our [Discord server](https://discord.gg/arbitrum). ::: -In this how-to, you’ll learn how to bridge your own token between Ethereum (the parent chain) and Arbitrum (the child chain), using a custom gateway. For alternative ways of bridging tokens, don’t forget to check out this [overview](/build-decentralized-apps/token-bridging/bridge-tokens-programmatically/01-get-started.mdx). +In this how-to, you’ll learn how to bridge your own token between Ethereum (the parent chain) and Arbitrum (the child chain), using a custom gateway. For alternative ways of bridging tokens, don’t forget to check out this [overview](/build-decentralized-apps/token-bridging/get-started.mdx). -Familiarity with [Arbitrum’s token bridge system](/build-decentralized-apps/token-bridging/01-overview.mdx), smart contracts, and blockchain development is expected. If you’re new to blockchain development, consider reviewing our [Quickstart: Build a dApp with Arbitrum (Solidity, Remix)](/build-decentralized-apps/01-quickstart-solidity-remix.mdx) before proceeding. We will use [Arbitrum’s SDK](https://github.com/OffchainLabs/arbitrum-sdk) throughout this how-to, although no prior knowledge is required. +Familiarity with [Arbitrum’s token bridge system](/how-arbitrum-works/deep-dives/token-bridging.mdx), smart contracts, and blockchain development is expected. If you’re new to blockchain development, consider reviewing our [Quickstart: Build a dApp with Arbitrum (Solidity, Remix)](/build-decentralized-apps/01-quickstart-solidity-remix.mdx) before proceeding. We will use [Arbitrum’s SDK](https://github.com/OffchainLabs/arbitrum-sdk) throughout this how-to, although no prior knowledge is required. We will go through all the steps involved in the process. However, if you want to jump straight to the code, we have created [this script in our tutorials repository](https://github.com/OffchainLabs/arbitrum-tutorials/tree/master/packages/custom-gateway-bridging) that encapsulates the entire process. ## Step 0: Review the prerequisites (a.k.a. do I really need a custom gateway?) -Before starting to implement and deploy a custom gateway, it is strongly encouraged to analyze the current solutions that Arbitrum’s token bridge provides: the [standard gateway](/build-decentralized-apps/token-bridging/bridge-tokens-programmatically/02-how-to-bridge-tokens-standard.mdx) and the [generic-custom gateway](/build-decentralized-apps/token-bridging/bridge-tokens-programmatically/03-how-to-bridge-tokens-generic-custom.mdx). These solutions provide enough functionality to solve the majority of bridging needs from projects. And if you are in doubt about your current approach, you can always ask for assistance on our [Discord server](https://discord.gg/arbitrum). +Before starting to implement and deploy a custom gateway, it is strongly encouraged to analyze the current solutions that Arbitrum’s token bridge provides: the [standard gateway](/build-decentralized-apps/token-bridging/deposit-tokens.mdx) and the [generic-custom gateway](/build-decentralized-apps/token-bridging/configure-token-bridging/setup-generic-custom-gateway.mdx). These solutions provide enough functionality to solve the majority of bridging needs from projects. And if you are in doubt about your current approach, you can always ask for assistance on our [Discord server](https://discord.gg/arbitrum). There are several prerequisites to consider when deploying your own custom gateway. @@ -43,7 +41,7 @@ On the other hand, the **child chain counterpart of the gateway**, must conform ### What about my custom tokens? -If you are deploying custom gateways, you will likely want to support your custom tokens on both the parent chain and the child chain. They also have several requirements they must comply with. You can find more information about it in [How to bridge tokens via Arbitrum’s generic-custom gateway](/build-decentralized-apps/token-bridging/bridge-tokens-programmatically/03-how-to-bridge-tokens-generic-custom.mdx). +If you are deploying custom gateways, you will likely want to support your custom tokens on both the parent chain and the child chain. They also have several requirements they must comply with. You can find more information about it in [How to bridge tokens via Arbitrum’s generic-custom gateway](/build-decentralized-apps/token-bridging/configure-token-bridging/setup-generic-custom-gateway.mdx). ## Step 1: Create a gateway and deploy it on the parent chain @@ -739,7 +737,7 @@ main() This step will depend on your setup. In this case, as our simplified gateway supports only one token, we will deploy it on both the parent chain and the child chain to enable calling the `setTokenBridgeInformation` method on both gateways afterwards. -We won’t go through the process of deploying custom tokens in this How-to, but you can see a detailed explanation of the steps to take in the page [How to bridge tokens via Arbitrum’s generic-custom gateway](/build-decentralized-apps/token-bridging/bridge-tokens-programmatically/03-how-to-bridge-tokens-generic-custom.mdx) +We won’t go through the process of deploying custom tokens in this How-to, but you can see a detailed explanation of the steps to take in the page [How to bridge tokens via Arbitrum’s generic-custom gateway](/build-decentralized-apps/token-bridging/configure-token-bridging/setup-generic-custom-gateway.mdx) ## Step 4: Configure your custom tokens on your gateways @@ -781,7 +779,7 @@ console.log( Once all contracts are deployed successfully on their respective chains, and they all have the information of the gateways and tokens, it’s time to register the token in your custom gateway. -As mentioned in [How to bridge tokens via Arbitrum’s generic-custom gateway](/build-decentralized-apps/token-bridging/bridge-tokens-programmatically/03-how-to-bridge-tokens-generic-custom.mdx), this action needs to be done by the parent chain token, and we’ve implemented the function `registerTokenOnL2` to do it. So now we only need to call that function. +As mentioned in [How to bridge tokens via Arbitrum’s generic-custom gateway](/build-decentralized-apps/token-bridging/configure-token-bridging/setup-generic-custom-gateway.mdx), this action needs to be done by the parent chain token, and we’ve implemented the function `registerTokenOnL2` to do it. So now we only need to call that function. In this case, when using this function, a single action occurs: @@ -832,14 +830,17 @@ console.log('Your custom token and gateways are now registered on the token brid Upon completion of all the steps, registration of your parent chain and child chain gateways in the token bridge will be complete, and both tokens will have connections through your custom gateway. -You can bridge tokens between the parent chain and child chain using the custom tokens, along with the router and gateway contracts from each layer. +The full code of this how-to and more extensive deployment and testing scripts can be found [in this package](https://github.com/OffchainLabs/arbitrum-tutorials/tree/master/packages/custom-gateway-bridging) of our tutorials repository. -Suppose you want to see an example of bridging a token from the parent to the child chain using Arbitrum’s SDK. In that case, you can check out [How to bridge tokens via Arbitrum’s standard `ERC-20` gateway](/build-decentralized-apps/token-bridging/bridge-tokens-programmatically/02-how-to-bridge-tokens-standard.mdx), specifically in Steps 2-5. +## Next steps -The full code of this how-to and a more extensive deployment and testing scripts can be found [in this package](https://github.com/OffchainLabs/arbitrum-tutorials/tree/master/packages/custom-gateway-bridging) of our tutorials repository. +Your custom gateway is now configured! Users can: + +- [Deposit tokens to the child chain](/build-decentralized-apps/token-bridging/deposit-tokens.mdx) +- [Withdraw tokens back to parent chain](/build-decentralized-apps/token-bridging/withdraw-tokens.mdx) ## Resources -1. [Concept page: Token Bridge](/build-decentralized-apps/token-bridging/01-overview.mdx) +1. [Concept page: Token Bridge](/how-arbitrum-works/deep-dives/token-bridging.mdx) 2. [Arbitrum SDK](https://github.com/OffchainLabs/arbitrum-sdk) 3. [Token bridge contract addresses](/build-decentralized-apps/reference/02-contract-addresses.mdx) diff --git a/docs/build-decentralized-apps/token-bridging/bridge-tokens-programmatically/03-how-to-bridge-tokens-generic-custom.mdx b/docs/build-decentralized-apps/token-bridging/configure-token-bridging/setup-generic-custom-gateway.mdx similarity index 89% rename from docs/build-decentralized-apps/token-bridging/bridge-tokens-programmatically/03-how-to-bridge-tokens-generic-custom.mdx rename to docs/build-decentralized-apps/token-bridging/configure-token-bridging/setup-generic-custom-gateway.mdx index 81c8acd8f9..26a6601d16 100644 --- a/docs/build-decentralized-apps/token-bridging/bridge-tokens-programmatically/03-how-to-bridge-tokens-generic-custom.mdx +++ b/docs/build-decentralized-apps/token-bridging/configure-token-bridging/setup-generic-custom-gateway.mdx @@ -1,20 +1,18 @@ --- -title: 'Bridge tokens via Arbitrum’s generic-custom gateway' -description: Learn how to use the generic-custom gateway to bridge tokens programmatically -user_story: As a developer, I want to understand how to bridge tokens between Ethereum and Arbitrum using the generic-custom gateway +title: 'Configure generic-custom gateway bridging' +description: Learn how to set up your token to use Arbitrum's generic-custom gateway for custom child chain functionality content_type: how-to -displayed_sidebar: buildAppsSidebar --- -In this how-to, you’ll learn how to bridge your own token between Ethereum (parent chain) and Arbitrum (child chain), using [Arbitrum’s generic-custom gateway](/build-decentralized-apps/token-bridging/03-token-bridge-erc20.mdx#the-arbitrum-generic-custom-gateway). For alternative ways of bridging tokens, don’t forget to check out this [overview](/build-decentralized-apps/token-bridging/bridge-tokens-programmatically/01-get-started.mdx). +This guide explains how to configure your token to work with Arbitrum's generic-custom gateway. Use this when you need custom functionality in your child chain token. For alternative bridging options, see the [overview](/build-decentralized-apps/token-bridging/get-started.mdx). -Familiarity with [Arbitrum’s token bridge system](/build-decentralized-apps/token-bridging/01-overview.mdx), smart contracts, and blockchain development is expected. If you’re new to blockchain development, consider reviewing our [Quickstart: Build a dApp with Arbitrum (Solidity, Hardhat)](/build-decentralized-apps/01-quickstart-solidity-remix.mdx) before proceeding. We will use [Arbitrum’s SDK](https://github.com/OffchainLabs/arbitrum-sdk) throughout this how-to, although no prior knowledge is required. +Familiarity with [Arbitrum’s token bridge system](/how-arbitrum-works/deep-dives/token-bridging.mdx), smart contracts, and blockchain development is expected. If you’re new to blockchain development, consider reviewing our [Quickstart: Build a dApp with Arbitrum (Solidity, Hardhat)](/build-decentralized-apps/01-quickstart-solidity-remix.mdx) before proceeding. We will use [Arbitrum’s SDK](https://github.com/OffchainLabs/arbitrum-sdk) throughout this how-to, although no prior knowledge is required. We will go through all the steps involved in the process. However, if you want to jump straight to the code, we have created [this script in our tutorials repository](https://github.com/OffchainLabs/arbitrum-tutorials/tree/master/packages/custom-token-bridging) that encapsulates the entire process. ## Step 1: Review the prerequisites -As stated in the [token bridge conceptual page](/build-decentralized-apps/token-bridging/03-token-bridge-erc20.mdx#the-arbitrum-generic-custom-gateway), there are a few prerequisites to keep in mind while using this method to make a token bridgeable. +As stated in the [token bridge conceptual page](/how-arbitrum-works/deep-dives/token-bridging.mdx#the-arbitrum-generic-custom-gateway), there are a few prerequisites to keep in mind while using this method to make a token bridgeable. First of all, the **parent chain counterpart of the token**, must conform to the [`ICustomToken`](https://github.com/OffchainLabs/token-bridge-contracts/blob/main/contracts/tokenbridge/ethereum/ICustomToken.sol) interface, meaning that: @@ -358,7 +356,7 @@ Upon completion, the parent and child chain tokens will have an established conn You can bridge tokens between the parent and child chain using the origin parent chain token and the custom token deployed on the child chain, along with the router and gateway contracts from each layer. -Suppose you want to see an example of bridging a token from the parent to the child chain using Arbitrum’s SDK. In that case, you can check out [How to bridge tokens via Arbitrum’s standard `ERC-20` gateway](/build-decentralized-apps/token-bridging/bridge-tokens-programmatically/02-how-to-bridge-tokens-standard.mdx), specifically in Steps 2-5. +Suppose you want to see an example of bridging a token from the parent to the child chain using Arbitrum’s SDK. In that case, you can check out [How to bridge tokens via Arbitrum’s standard `ERC-20` gateway](/build-decentralized-apps/token-bridging/deposit-tokens.mdx), specifically in Steps 2-5. ## Frequently asked questions @@ -374,8 +372,15 @@ As mentioned on the concept page, completing token registration can alternativel Yes, if your token has a standard `ERC-20` counterpart on the child chain, you can follow the process of registering your custom child chain token as outlined on this page. At that moment, your parent chain token will have two counterpart tokens on the child chain, but only your new custom child chain token will be minted when depositing tokens from the parent chain (parent-to-child chain bridging). Both child chain tokens will be withdrawable (child-to-parent chain bridging), so users holding the old standard `ERC-20` token will be able to withdraw back to the parent chain (using the `L2CustomGateway` contract instead of the bridge UI) and then deposit to the child chain to get the new custom child chain tokens. +## Next steps + +Your token is now configured for bridging! Users can: + +- [Deposit tokens to the child chain](/build-decentralized-apps/token-bridging/deposit-tokens.mdx) +- [Withdraw tokens back to parent chain](/build-decentralized-apps/token-bridging/withdraw-tokens.mdx) + ## Resources -1. [Concept page: Token Bridge](/build-decentralized-apps/token-bridging/01-overview.mdx) +1. [Concept page: Token Bridge](/how-arbitrum-works/deep-dives/token-bridging.mdx) 2. [Arbitrum SDK](https://github.com/OffchainLabs/arbitrum-sdk) 3. [Token bridge contract addresses](/build-decentralized-apps/reference/02-contract-addresses.mdx) diff --git a/docs/build-decentralized-apps/token-bridging/configure-token-bridging/setup-standard-gateway.mdx b/docs/build-decentralized-apps/token-bridging/configure-token-bridging/setup-standard-gateway.mdx new file mode 100644 index 0000000000..d71e841e2a --- /dev/null +++ b/docs/build-decentralized-apps/token-bridging/configure-token-bridging/setup-standard-gateway.mdx @@ -0,0 +1,207 @@ +--- +title: Configure standard gateway bridging +description: Learn how to set up token bridging using Arbitrum's standard ERC-20 gateway +content_type: how-to +--- + +This guide explains how to configure your ERC-20 token to work with Arbitrum's standard gateway. The standard gateway is the simplest option - it automatically creates a standard ERC-20 token on the child chain with no configuration required. + +## When to use the standard gateway + +Use the standard gateway when: + +- You have a standard ERC-20 token on the parent chain +- You don't need custom functionality on the child chain token +- You want automatic setup with no pre-configuration +- Your token doesn't have special behaviors (rebasing, fee-on-transfer, etc.) + +For custom token behavior, see: + +- [Generic-custom gateway](/build-decentralized-apps/token-bridging/configure-token-bridging/setup-generic-custom-gateway.mdx) - for custom child chain token logic +- [Custom gateway](/build-decentralized-apps/token-bridging/configure-token-bridging/setup-custom-gateway.mdx) - for advanced use cases + +## Prerequisites + +- A standard ERC-20 token deployed on the parent chain (or deploy one following this guide) +- Familiarity with [Arbitrum's token bridge system](/how-arbitrum-works/deep-dives/token-bridging.mdx) +- Basic understanding of smart contracts and blockchain development + +## How the standard gateway works + +When using the standard gateway: + +1. **No pre-configuration needed**: Your token is automatically bridgeable +2. **Automatic child chain deployment**: On the first deposit, a standard [`StandardArbERC20`](https://github.com/OffchainLabs/token-bridge-contracts/blob/main/contracts/tokenbridge/arbitrum/StandardArbERC20.sol) contract is deployed on the child chain +3. **Escrow model**: Parent chain tokens are escrowed in the gateway; child chain tokens are minted/burned +4. **Router handles routing**: The router automatically directs your token to the standard gateway + +For architectural details, see [Standard ERC-20 bridging](/how-arbitrum-works/deep-dives/token-bridging.mdx#default-standard-bridging). + +## Step 1: Deploy your token (or use existing) + +If you already have a token on the parent chain, skip to Step 2. Otherwise, create a standard ERC-20 token: + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract DappToken is ERC20 { + constructor(uint256 _initialSupply) ERC20("Dapp Token", "DAPP") { + _mint(msg.sender, _initialSupply * 10 ** decimals()); + } +} +``` + +Deploy it to the parent chain: + +```javascript +const { ethers } = require('hardhat'); +const { providers, Wallet } = require('ethers'); + +const parentProvider = new providers.JsonRpcProvider(process.env.PARENT_RPC); +const wallet = new Wallet(process.env.PRIVATE_KEY, parentProvider); + +async function deployToken() { + const TokenFactory = await ethers.getContractFactory('DappToken'); + const token = await TokenFactory.connect(wallet).deploy(1000000); + await token.deployed(); + + console.log(`Token deployed at: ${token.address}`); + return token.address; +} +``` + +## Step 2: Understand the bridge contracts + +Two contracts handle token bridging: + +### Router contracts + +- **L1GatewayRouter**: Entry point on parent chain +- **L2GatewayRouter**: Entry point on child chain + +The router maintains a mapping of which gateway handles which token, falling back to the standard gateway for unmapped tokens. + +### Gateway contracts + +- **L1ERC20Gateway**: Escrows parent chain tokens +- **L2ERC20Gateway**: Mints/burns child chain tokens + +You can find contract addresses on the [contract addresses page](/build-decentralized-apps/reference/02-contract-addresses.mdx#token-bridge-smart-contracts). + +## Step 3: Trigger child chain token deployment + +The child chain token is created automatically on the first deposit. You can trigger deployment by making a small deposit, or wait until users make their first deposits. + +### Using the Arbitrum SDK + +```javascript +import { getArbitrumNetwork, Erc20Bridger } from '@arbitrum/sdk'; +import { providers, Wallet } from 'ethers'; + +const parentProvider = new providers.JsonRpcProvider(process.env.PARENT_RPC); +const childProvider = new providers.JsonRpcProvider(process.env.CHILD_RPC); +const wallet = new Wallet(process.env.PRIVATE_KEY, parentProvider); + +const childNetwork = await getArbitrumNetwork(childProvider); +const erc20Bridge = new Erc20Bridger(childNetwork); + +// Approve the gateway +await erc20Bridge.approveToken({ + parentSigner: wallet, + erc20ParentAddress: tokenAddress, +}); + +// Make initial deposit to trigger L2 token creation +const depositTx = await erc20Bridge.deposit({ + amount: ethers.utils.parseUnits('1', 18), + erc20ParentAddress: tokenAddress, + parentSigner: wallet, + childProvider: childProvider, +}); + +const receipt = await depositTx.wait(); +console.log(`Deposit complete: ${receipt.transactionHash}`); +``` + +For complete deposit instructions, see [Deposit tokens](/build-decentralized-apps/token-bridging/deposit-tokens.mdx). + +## Step 4: Find your child chain token address + +After the first deposit, find your token's child chain address: + +### Using the SDK + +```javascript +const childTokenAddress = await erc20Bridge.getChildErc20Address( + parentTokenAddress, + parentProvider, +); + +console.log(`L2 token address: ${childTokenAddress}`); +``` + +### Manually + +Call `calculateL2TokenAddress` on the L1GatewayRouter contract: + +```solidity +address l2TokenAddress = l1GatewayRouter.calculateL2TokenAddress(l1TokenAddress); +``` + +Or look up the token on [Arbiscan](https://arbiscan.io/) by searching for the deployment transaction. + +## Step 5: Verify the child chain token + +The automatically deployed token is an instance of [`StandardArbERC20`](https://github.com/OffchainLabs/token-bridge-contracts/blob/main/contracts/tokenbridge/arbitrum/StandardArbERC20.sol) with: + +- Same name and symbol as parent chain token +- Same decimals as parent chain token +- `l1Address()` function returning the parent chain token address +- Minting/burning controlled by the L2ERC20Gateway + +You can verify this on [Arbiscan](https://arbiscan.io/) by viewing the token contract. + +## Configuration complete + +Your token is now bridgeable! Users can: + +- [Deposit tokens to the child chain](/build-decentralized-apps/token-bridging/deposit-tokens.mdx) +- [Withdraw tokens back to parent chain](/build-decentralized-apps/token-bridging/withdraw-tokens.mdx) + +## Important considerations + +### Token compatibility + +Standard gateway works for most ERC-20 tokens, but **not** for: + +- Rebasing tokens (supply changes) +- Fee-on-transfer tokens +- Tokens with transfer hooks +- Tokens with unique minting/burning logic + +For these cases, use [generic-custom gateway](/build-decentralized-apps/token-bridging/configure-token-bridging/setup-generic-custom-gateway.mdx) or [custom gateway](/build-decentralized-apps/token-bridging/configure-token-bridging/setup-custom-gateway.mdx). + +### Gateway assignment + +Once the first deposit occurs, the token is permanently assigned to the standard gateway. You cannot change gateway types after this point. + +### Child chain token ownership + +The automatically deployed child chain token is controlled by the gateway - you cannot modify or upgrade it. For control over the child chain token, use the generic-custom gateway. + +## Next steps + +- [Deposit tokens](/build-decentralized-apps/token-bridging/deposit-tokens.mdx) +- [Withdraw tokens](/build-decentralized-apps/token-bridging/withdraw-tokens.mdx) +- [Understand token bridge architecture](/how-arbitrum-works/deep-dives/token-bridging.mdx) +- [View example code](https://github.com/OffchainLabs/arbitrum-tutorials/tree/master/packages/token-deposit) + +## Resources + +- [Token bridge conceptual overview](/how-arbitrum-works/deep-dives/token-bridging.mdx) +- [Standard ERC-20 bridging details](/how-arbitrum-works/deep-dives/token-bridging.mdx#default-standard-bridging) +- [Arbitrum SDK documentation](/sdk) +- [Contract addresses](/build-decentralized-apps/reference/02-contract-addresses.mdx) diff --git a/docs/build-decentralized-apps/token-bridging/deposit-tokens.mdx b/docs/build-decentralized-apps/token-bridging/deposit-tokens.mdx new file mode 100644 index 0000000000..bf47e81e8c --- /dev/null +++ b/docs/build-decentralized-apps/token-bridging/deposit-tokens.mdx @@ -0,0 +1,184 @@ +--- +title: Deposit tokens to Arbitrum +description: Learn how to deposit ERC-20 tokens from parent chain to child chain using Arbitrum's token bridge +content_type: how-to +--- + +This guide shows you how to deposit ERC-20 tokens from a parent chain (like Ethereum) to an Arbitrum child chain. This assumes your token is already configured for bridging. If you need to set up bridging for a new token, see [Configure token bridging](/build-decentralized-apps/token-bridging/configure-token-bridging/setup-standard-gateway.mdx). + +## Prerequisites + +- Your token must already be bridgeable (registered with a gateway) +- A wallet with the tokens you want to deposit on the parent chain +- ETH on the parent chain to pay for gas fees +- Familiarity with [Arbitrum's token bridge architecture](/how-arbitrum-works/deep-dives/token-bridging.mdx) + +## Depositing tokens using the Arbitrum SDK + +The simplest way to deposit tokens is using the [Arbitrum SDK](https://github.com/OffchainLabs/arbitrum-sdk): + +### Step 1: Set up the SDK + +```javascript +import { getArbitrumNetwork, Erc20Bridger } from '@arbitrum/sdk'; +import { providers, Wallet } from 'ethers'; + +// Set up providers and wallet +const parentProvider = new providers.JsonRpcProvider(process.env.PARENT_RPC); +const childProvider = new providers.JsonRpcProvider(process.env.CHILD_RPC); +const wallet = new Wallet(process.env.PRIVATE_KEY, parentProvider); + +// Initialize the bridge +const childNetwork = await getArbitrumNetwork(childProvider); +const erc20Bridge = new Erc20Bridger(childNetwork); +``` + +### Step 2: Approve the gateway + +The gateway contract needs permission to transfer your tokens: + +```javascript +const approveTx = await erc20Bridge.approveToken({ + parentSigner: wallet, + erc20ParentAddress: tokenAddress, +}); + +const approveReceipt = await approveTx.wait(); +console.log(`Approved gateway: ${approveReceipt.transactionHash}`); +``` + +### Step 3: Deposit tokens + +```javascript +const depositTx = await erc20Bridge.deposit({ + amount: ethers.utils.parseUnits('100', 18), // Amount to deposit + erc20ParentAddress: tokenAddress, + parentSigner: wallet, + childProvider: childProvider, +}); + +const depositReceipt = await depositTx.wait(); +console.log(`Deposit initiated: ${depositReceipt.transactionHash}`); +``` + +### Step 4: Wait for child chain execution + +```javascript +// Wait for the deposit to be processed on the child chain +const childResult = await depositReceipt.waitForChildTransactionReceipt(childProvider); + +if (childResult.complete) { + console.log('Deposit successful!'); + console.log(`Child chain transaction: ${childResult.message.childTxReceipt.transactionHash}`); +} else { + console.log('Deposit failed or pending'); +} +``` + +## Depositing tokens manually + +If you prefer to interact with the contracts directly: + +### Step 1: Get contract addresses + +Find the router and gateway addresses for your network on the [contract addresses page](/build-decentralized-apps/reference/02-contract-addresses.mdx#token-bridge-smart-contracts). + +### Step 2: Approve the gateway + +First, find which gateway will handle your token: + +```solidity +// Call on L1GatewayRouter +address gateway = l1GatewayRouter.getGateway(tokenAddress); +``` + +Then approve it: + +```solidity +// Call on your ERC-20 token contract +token.approve(gateway, amountToDeposit); +``` + +### Step 3: Initiate deposit + +Call the router contract's `outboundTransferCustomRefund` method: + +```solidity +function outboundTransferCustomRefund( + address _token, + address _refundTo, + address _to, + uint256 _amount, + uint256 _maxGas, + uint256 _gasPriceBid, + bytes calldata _data +) external payable returns (bytes memory) +``` + +**Parameters:** + +- **`_token`**: Parent chain address of the token to deposit +- **`_refundTo`**: Address to receive excess gas refund on the child chain +- **`_to`**: Address to receive tokens on the child chain +- **`_amount`**: Amount of tokens to deposit +- **`_maxGas`**: Max gas for child chain execution +- **`_gasPriceBid`**: Gas price for child chain execution +- **`_data`**: Encoded data containing `maxSubmissionCost` and extra data + +**Example:** + +```javascript +const router = new ethers.Contract(routerAddress, routerABI, wallet); + +// Encode the data parameter +const data = ethers.utils.defaultAbiCoder.encode(['uint256', 'bytes'], [maxSubmissionCost, '0x']); + +const tx = await router.outboundTransferCustomRefund( + tokenAddress, + refundAddress, + destinationAddress, + amount, + maxGas, + gasPriceBid, + data, + { value: maxSubmissionCost + maxGas * gasPriceBid }, +); + +await tx.wait(); +``` + +## How deposits work + +When you deposit tokens: + +1. **Parent chain**: Your tokens are escrowed in the gateway contract (for standard tokens) or burned (for custom tokens) +2. **Message sent**: A retryable ticket is created to mint/release tokens on the child chain +3. **Child chain**: After a few minutes, tokens are minted/released to your address + +For more details on the underlying protocol, see [Token bridging overview](/how-arbitrum-works/deep-dives/token-bridging.mdx). + +## Troubleshooting + +### Deposit not appearing on child chain + +If your deposit doesn't appear after 10-15 minutes: + +1. Check the transaction status on the [Retryables Dashboard](https://retryable-dashboard.arbitrum.io/) +2. If the retryable ticket failed, you may need to manually redeem it +3. Check that you provided sufficient gas fees for child chain execution + +### "Insufficient allowance" error + +Make sure you approved the correct gateway contract (not the router). Use `getGateway()` to find the right gateway address. + +## Next steps + +- [Withdraw tokens back to parent chain](/build-decentralized-apps/token-bridging/withdraw-tokens.mdx) +- [Understand token bridge architecture](/how-arbitrum-works/deep-dives/token-bridging.mdx) +- [Configure bridging for a new token](/build-decentralized-apps/token-bridging/configure-token-bridging/setup-standard-gateway.mdx) + +## Resources + +- [Arbitrum SDK documentation](/sdk) +- [Token deposit tutorial](https://github.com/OffchainLabs/arbitrum-tutorials/tree/master/packages/token-deposit) +- [Contract addresses](/build-decentralized-apps/reference/02-contract-addresses.mdx) diff --git a/docs/build-decentralized-apps/token-bridging/get-started.mdx b/docs/build-decentralized-apps/token-bridging/get-started.mdx new file mode 100644 index 0000000000..afa2690166 --- /dev/null +++ b/docs/build-decentralized-apps/token-bridging/get-started.mdx @@ -0,0 +1,65 @@ +--- +title: 'Get started with token bridging' +description: Learn the different options available to bridge tokens programmatically +user_story: As a developer, I want to understand how to bridge tokens between Ethereum and Arbitrum. +content_type: quickstart +displayed_sidebar: buildAppsSidebar +--- + +Token bridging is a fundamental aspect of any child chain protocol. It allows projects to quickly integrate with the Arbitrum ecosystem by leveraging their existing parent chain tokens. + +## I want to deposit or withdraw tokens + +If your token is already configured for bridging, use these guides: + +- **[Deposit tokens](/build-decentralized-apps/token-bridging/deposit-tokens.mdx)**: Move tokens from parent chain to child chain +- **[Withdraw tokens](/build-decentralized-apps/token-bridging/withdraw-tokens.mdx)**: Move tokens from child chain back to parent chain + +These guides work for any token that's already bridgeable, regardless of which gateway type was configured. + +## I want to make my token bridgeable + +If you're setting up bridging for a new token, you have three gateway options to choose from: + +### 1. Standard gateway (recommended for most tokens) + +Use the [standard gateway](/build-decentralized-apps/token-bridging/configure-token-bridging/setup-standard-gateway.mdx) if you want: + +- Automatic deployment of a standard ERC-20 token on Arbitrum +- No pre-configuration required +- The simplest setup process + +For additional information, see [standard ERC-20 gateway](/how-arbitrum-works/deep-dives/token-bridging.mdx#default-standard-bridging) in the conceptual docs. + +### 2. Generic-custom gateway (for custom token logic) + +Use the [generic-custom gateway](/build-decentralized-apps/token-bridging/configure-token-bridging/setup-generic-custom-gateway.mdx) if you need: + +- Custom functionality in your child chain token +- Control over the child chain token implementation +- Additional features beyond standard ERC-20 + +For additional information, see [Arbitrum generic custom gateway](/how-arbitrum-works/deep-dives/token-bridging.mdx#the-arbitrum-generic-custom-gateway) in the conceptual docs. + +### 3. Custom gateway (for advanced use cases) + +Use a [custom gateway](/build-decentralized-apps/token-bridging/configure-token-bridging/setup-custom-gateway.mdx) if you need: + +- Specialized gateway logic beyond what standard gateways provide +- Custom handling of deposits and withdrawals +- Advanced features like child chain-minted tokens being withdrawable to parent chain + +For additional information, see [other types of gateways](/how-arbitrum-works/deep-dives/token-bridging.mdx#other-flavors-of-gateways) in the conceptual docs. + +## Example code + +You can find complete examples demonstrating: + +- [Token deposits (parent → child)](https://github.com/OffchainLabs/arbitrum-tutorials/tree/master/packages/token-deposit) +- [Token withdrawals (child → parent)](https://github.com/OffchainLabs/arbitrum-tutorials/tree/master/packages/token-withdraw) +- [Custom token bridging setup](https://github.com/OffchainLabs/arbitrum-tutorials/tree/master/packages/custom-token-bridging) + +## Learn more + +- [Token bridge architecture](/how-arbitrum-works/deep-dives/token-bridging.mdx) +- [Arbitrum SDK documentation](/sdk) diff --git a/docs/build-decentralized-apps/token-bridging/withdraw-tokens.mdx b/docs/build-decentralized-apps/token-bridging/withdraw-tokens.mdx new file mode 100644 index 0000000000..baf83bcd4c --- /dev/null +++ b/docs/build-decentralized-apps/token-bridging/withdraw-tokens.mdx @@ -0,0 +1,198 @@ +--- +title: Withdraw tokens to parent chain +description: Learn how to withdraw ERC-20 tokens from an Arbitrum child chain back to the parent chain +content_type: how-to +--- + +import ImageZoom from '@site/src/components/ImageZoom'; + +This guide shows you how to withdraw ERC-20 tokens from an Arbitrum child chain back to a parent chain (like Ethereum). This assumes your token is already configured for bridging. If you need to set up bridging for a new token, see [Configure token bridging](/build-decentralized-apps/token-bridging/configure-token-bridging/setup-standard-gateway.mdx). + +## Prerequisites + +- Your token must already be bridgeable (registered with a gateway) +- A wallet with tokens on the child chain +- ETH on both chains (child chain for initiation, parent chain for final execution) +- **Important**: Withdrawals require ~7 days to finalize due to the challenge period +- Familiarity with [child-to-parent messaging](/how-arbitrum-works/deep-dives/l2-to-l1-messaging.mdx) + +## Withdrawing tokens using the Arbitrum SDK + +The simplest way to withdraw tokens is using the [Arbitrum SDK](https://github.com/OffchainLabs/arbitrum-sdk): + +### Step 1: Initiate withdrawal on child chain + +```javascript +import { getArbitrumNetwork, Erc20Bridger } from '@arbitrum/sdk'; +import { providers, Wallet } from 'ethers'; + +// Set up providers and wallet +const parentProvider = new providers.JsonRpcProvider(process.env.PARENT_RPC); +const childProvider = new providers.JsonRpcProvider(process.env.CHILD_RPC); +const childWallet = new Wallet(process.env.PRIVATE_KEY, childProvider); + +// Initialize the bridge +const childNetwork = await getArbitrumNetwork(childProvider); +const erc20Bridge = new Erc20Bridger(childNetwork); + +// Initiate withdrawal +const withdrawTx = await erc20Bridge.withdraw({ + amount: ethers.utils.parseUnits('100', 18), + erc20ParentAddress: parentTokenAddress, + childSigner: childWallet, +}); + +const withdrawReceipt = await withdrawTx.wait(); +console.log(`Withdrawal initiated: ${withdrawReceipt.transactionHash}`); +``` + +### Step 2: Wait for the challenge period (~7 days) + +```javascript +import { L2ToL1MessageStatus } from '@arbitrum/sdk'; + +// Get the withdrawal message +const childReceipt = new L2TransactionReceipt(withdrawReceipt); +const messages = await childReceipt.getL2ToL1Messages(childWallet); +const message = messages[0]; + +// Wait for the message to be confirmed (takes ~7 days) +await message.waitUntilReadyToExecute(childProvider); +console.log('Message confirmed! Ready to execute on parent chain.'); +``` + +### Step 3: Execute on parent chain + +```javascript +// Execute the withdrawal on the parent chain +const parentWallet = new Wallet(process.env.PRIVATE_KEY, parentProvider); +const executeResult = await message.execute(childProvider); +await executeResult.wait(); + +console.log('Withdrawal complete!'); +console.log(`Parent chain transaction: ${executeResult.transactionHash}`); +``` + +## Withdrawing tokens manually + +If you prefer to interact with the contracts directly: + +### Step 1: Initiate withdrawal on child chain + +Approve and call the L2GatewayRouter: + +```javascript +// Approve the L2 gateway +const childToken = new ethers.Contract(childTokenAddress, erc20ABI, childWallet); +const childRouter = new ethers.Contract(childRouterAddress, routerABI, childWallet); + +// Get the gateway address +const gateway = await childRouter.getGateway(parentTokenAddress); + +// Approve +await childToken.approve(gateway, amountToWithdraw); + +// Initiate withdrawal +const withdrawTx = await childRouter.outboundTransfer( + parentTokenAddress, // Parent chain token address + parentDestination, // Where to send tokens on parent chain + amountToWithdraw, + '0x', // Extra data (usually empty) +); + +await withdrawTx.wait(); +``` + +### Step 2: Wait for challenge period + +After initiating the withdrawal, you must wait approximately 7 days for the assertion containing your withdrawal to be confirmed. + +You can check the status on the [Arbitrum Bridge UI](https://bridge.arbitrum.io/) by connecting your wallet, or programmatically track the message status. + +### Step 3: Construct proof and execute on parent chain + +After the challenge period, get the proof data using `NodeInterface`: + +```javascript +const nodeInterface = new ethers.Contract( + '0x00000000000000000000000000000000000000C8', + nodeInterfaceABI, + childProvider, +); + +// Get proof data (you'll need the batch number and index from the withdrawal receipt) +const proofData = await nodeInterface.constructOutboxProof(batchNumber, indexInBatch); +``` + +Then execute on the parent chain using the Outbox contract: + +```javascript +const outbox = new ethers.Contract(outboxAddress, outboxABI, parentWallet); + +const executeTx = await outbox.executeTransaction( + proofData.proof, + proofData.path, + proofData.l2Sender, + proofData.l1Dest, + proofData.l2Block, + proofData.l1Block, + proofData.timestamp, + proofData.amount, + proofData.calldataForL1, +); + +await executeTx.wait(); +console.log('Withdrawal executed on parent chain!'); +``` + +## How withdrawals work + +The token withdrawal process: + + + +1. **Child chain**: Tokens are burned (or escrowed for custom tokens) +2. **Message created**: A child-to-parent message is encoded and included in an assertion +3. **Challenge period**: Wait ~7 days for the assertion to be confirmed +4. **Parent chain**: After confirmation, execute the message to release tokens from the parent gateway + +For more details on the underlying protocol, see: + +- [Child to parent chain messaging](/how-arbitrum-works/deep-dives/l2-to-l1-messaging.mdx) +- [Token bridging overview](/how-arbitrum-works/deep-dives/token-bridging.mdx) + +## Troubleshooting + +### "Withdrawal not ready to execute" + +The challenge period hasn't passed yet. Withdrawals take approximately 7 days (6.4 days on Arbitrum One) to finalize. Check the status using: + +```javascript +const status = await message.status(childProvider); +console.log(L2ToL1MessageStatus[status]); // Should show "CONFIRMED" when ready +``` + +### "Message already executed" + +This withdrawal has already been executed. Check the parent chain token balance - the tokens should already be there. + +### Finding batch number and index + +If you need to manually construct the proof, you can find the batch number and index from the withdrawal transaction receipt events. Look for the `L2ToL1Tx` event emitted by `ArbSys`. + +## Next steps + +- [Deposit tokens to child chain](/build-decentralized-apps/token-bridging/deposit-tokens.mdx) +- [Understand token bridge architecture](/how-arbitrum-works/deep-dives/token-bridging.mdx) +- [General child-to-parent messaging](/build-decentralized-apps/how-to-bridge-to-parent-chain.mdx) + +## Resources + +- [Arbitrum SDK documentation](/sdk) +- [Token withdrawal tutorial](https://github.com/OffchainLabs/arbitrum-tutorials/tree/master/packages/token-withdraw) +- [Contract addresses](/build-decentralized-apps/reference/02-contract-addresses.mdx) +- [Arbitrum Bridge UI](https://bridge.arbitrum.io/) diff --git a/docs/how-arbitrum-works/deep-dives/transaction-lifecycle.mdx b/docs/build-decentralized-apps/transaction-lifecycle.mdx similarity index 99% rename from docs/how-arbitrum-works/deep-dives/transaction-lifecycle.mdx rename to docs/build-decentralized-apps/transaction-lifecycle.mdx index fe8b24e25f..cbff865b16 100644 --- a/docs/how-arbitrum-works/deep-dives/transaction-lifecycle.mdx +++ b/docs/build-decentralized-apps/transaction-lifecycle.mdx @@ -4,7 +4,7 @@ description: 'Learn how transactions are submitted and processed on Arbitrum, in author: pete-vielhaber sme: Mehdi user_story: As a current or prospective Arbitrum user, I need to learn more about the transaction lifecycle. -content_type: concept +content_type: how-to --- import ImageZoom from '@site/src/components/ImageZoom'; diff --git a/docs/how-arbitrum-works/01-inside-arbitrum-nitro.mdx b/docs/how-arbitrum-works/01-inside-arbitrum-nitro.mdx index 0b3f8e3e59..0664473221 100644 --- a/docs/how-arbitrum-works/01-inside-arbitrum-nitro.mdx +++ b/docs/how-arbitrum-works/01-inside-arbitrum-nitro.mdx @@ -43,7 +43,7 @@ This deterministic model means that identical inputs always produce the same out The submission process for a transaction on Arbitrum starts with the user sending the request, with options tailored to balance factors like speed, control, and reliability according to specific needs. Most transactions route through the Sequencer, a specialized node that orders them and issues quick confirmations. This path accommodates various submission methods, including public RPC for development and light usage, third-party RPC for improved throughput, direct Sequencer endpoints for minimal latency in critical operations, and self-hosted Arbitrum nodes for ultimate privacy and customization. -Alternatively, to counter risks of exclusion or delay by the Sequencer, users can submit directly to the Delayed Inbox contract on Ethereum, bolstering system resilience. In this mechanism, non-Sequencer transactions enter a dedicated queue, where a well-functioning Sequencer typically integrates them within about ten minutes. If delayed beyond 24 hours, any network participant can force inclusion into the main inbox, limiting the Sequencer's influence to temporary delays rather than permanent blocks. While the Sequencer route offers faster soft finality and streamlined workflows, the Delayed Inbox path doubles processing time but emphasizes censorship resistance. Overall, the Sequencer's commitment to ordered inclusion provides "soft finality" for a responsive experience, complemented by these alternatives for robust, flexible operations across applications. For more technical detail about the transaction lifecycle, refer to the [Transaction Lifecycle deep dive](/how-arbitrum-works/deep-dives/transaction-lifecycle.mdx). +Alternatively, to counter risks of exclusion or delay by the Sequencer, users can submit directly to the Delayed Inbox contract on Ethereum, bolstering system resilience. In this mechanism, non-Sequencer transactions enter a dedicated queue, where a well-functioning Sequencer typically integrates them within about ten minutes. If delayed beyond 24 hours, any network participant can force inclusion into the main inbox, limiting the Sequencer's influence to temporary delays rather than permanent blocks. While the Sequencer route offers faster soft finality and streamlined workflows, the Delayed Inbox path doubles processing time but emphasizes censorship resistance. Overall, the Sequencer's commitment to ordered inclusion provides "soft finality" for a responsive experience, complemented by these alternatives for robust, flexible operations across applications. For more technical detail about the transaction lifecycle, refer to the [Transaction Lifecycle deep dive](/build-decentralized-apps/transaction-lifecycle.mdx). ### Step 2: Ordering and broadcasting: The Sequencer @@ -55,11 +55,11 @@ After batching and compression, the data posts to Ethereum through the Sequencer 1. The default, blob transactions under [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844), provide cost-effective and scalable data inclusion when supported by Ethereum. -2. As a fallback, calldata transactions embed data directly, ensuring compatibility even if blob fees rise or [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844) is unavailable. This process yields 10-100x cost savings over individual postings and adapts to network conditions for consistent efficiency. For details on how parent chain costs are calculated and priced, including the adaptive pricing algorithm, see the [Parent chain pricing deep dive](/how-arbitrum-works/deep-dives/parent-chain-pricing.mdx). +2. As a fallback, calldata transactions embed data directly, ensuring compatibility even if blob fees rise or [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844) is unavailable. This process yields 10-100x cost savings over individual postings and adapts to network conditions for consistent efficiency. For details on how parent chain costs are calculated and priced, including the adaptive pricing algorithm, see the [Parent chain pricing deep dive](/how-arbitrum-works/reference/parent-chain-pricing.mdx). ### Step 3: Execution phase: State Transition Function -With ordering and batching complete, execution shifts to the State Transition Function (STF), the core of Arbitrum's processing engine. For foundational concepts about how the STF works, see the [State Transition Function gentle introduction](/how-arbitrum-works/deep-dives/01-stf-gentle-intro.mdx). Arbitrum ensures full Ethereum Virtual Machine (EVM) compatibility via a three-layer architecture. At the base, the Geth core handles EVM execution, aligning behaviors with Ethereum and drawing on its extensively tested code for security. For more technical details about Geth, refer to the [Geth deep dive documentation](/how-arbitrum-works/deep-dives/geth.mdx). +With ordering and batching complete, execution shifts to the State Transition Function (STF), the core of Arbitrum's processing engine. For foundational concepts about how the STF works, see the [State Transition Function gentle introduction](/how-arbitrum-works/deep-dives/01-stf-gentle-intro.mdx). Arbitrum ensures full Ethereum Virtual Machine (EVM) compatibility via a three-layer architecture. At the base, the Geth core handles EVM execution, aligning behaviors with Ethereum and drawing on its extensively tested code for security. For more technical details about Geth, refer to the [Geth deep dive documentation](/how-arbitrum-works/reference/geth.mdx). Above this, ArbOS—the Arbitrum Operating System—adds Layer 2 features like cross-chain messaging, fee management, gas pricing, handling of deposits and withdrawals, and advanced tools such as Stylus. The top layer, the node interface, manages RPC connections and APIs, providing Ethereum-like functionality for clients. @@ -67,7 +67,7 @@ In the STF, transactions follow a structured workflow: ArbOS first validates for -For Stylus contracts using WebAssembly (WASM), execution diverts to a WASM runtime, where contracts access state via specialized host I/O calls. This diversion yields 10-70x faster performance than EVM equivalents and full interoperability for mixed calls. The Geth base lets Ethereum apps run unchanged, while ArbOS and Stylus enable Layer 2 optimizations and high-performance innovations. Consult the [State Transition Function deep dive](/how-arbitrum-works/deep-dives/stf-inputs.mdx) for further mechanics. +For Stylus contracts using WebAssembly (WASM), execution diverts to a WASM runtime, where contracts access state via specialized host I/O calls. This diversion yields 10-70x faster performance than EVM equivalents and full interoperability for mixed calls. The Geth base lets Ethereum apps run unchanged, while ArbOS and Stylus enable Layer 2 optimizations and high-performance innovations. Consult the [State Transition Function deep dive](/how-arbitrum-works/reference/stf-inputs.mdx) for further mechanics. ### Step 4: Finality @@ -95,7 +95,7 @@ The canonical bridge architecture comprises asset contracts on both chains, gate ### Step 7: The Economics of execution: Gas and fees -Fees accumulate across the transaction lifecycle to fund processing and security. Arbitrum's dual-fee model separates child chain gas fees, which cover EVM computation and storage with [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559)-style dynamic pricing, adjusting for congestion, from Parent chain data fees (blobs or calldata). Parent chain data fees account for Ethereum data posting based on compressed batch contributions and apply only to Sequencer submissions. For comprehensive details on how parent chain pricing works, including the adaptive pricing algorithm and cost apportionment, see the [Parent chain pricing deep dive](/how-arbitrum-works/deep-dives/parent-chain-pricing.mdx). For a complete breakdown of how fees are calculated and collected, including both parent and child chain components, refer to the [Gas and fees deep dive](/how-arbitrum-works/deep-dives/gas-and-fees.mdx). +Fees accumulate across the transaction lifecycle to fund processing and security. Arbitrum's dual-fee model separates child chain gas fees, which cover EVM computation and storage with [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559)-style dynamic pricing, adjusting for congestion, from Parent chain data fees (blobs or calldata). Parent chain data fees account for Ethereum data posting based on compressed batch contributions and apply only to Sequencer submissions. For comprehensive details on how parent chain pricing works, including the adaptive pricing algorithm and cost apportionment, see the [Parent chain pricing deep dive](/how-arbitrum-works/reference/parent-chain-pricing.mdx). For a complete breakdown of how fees are calculated and collected, including both parent and child chain components, refer to the [Gas and fees deep dive](/how-arbitrum-works/deep-dives/gas-and-fees.mdx). A gas target is an optimal gas consumption rate/second. Exceeding this rate triggers [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559) fee escalations to prevent node overloads and maintain liveness. This rate prioritizes high-value transactions during peaks while deterring spam, ensuring that validators and the Sequencer stay operational, even though parent contracts handle ultimate security. [You can read more about how the child chain gas fees are calculated in this explainer ](/how-arbitrum-works/deep-dives/gas-and-fees#child-chain-gas-fees) @@ -115,13 +115,13 @@ This overview covers the complete transaction journey through Arbitrum Nitro. Fo #### Deep dives -- [Transaction Lifecycle](/how-arbitrum-works/deep-dives/transaction-lifecycle.mdx) - Detailed transaction lifecycle from submission to finality +- [Transaction Lifecycle](/build-decentralized-apps/transaction-lifecycle.mdx) - Detailed transaction lifecycle from submission to finality - [Sequencer](/how-arbitrum-works/deep-dives/sequencer.mdx) - How the Sequencer orders, batches, and posts transactions -- [Parent Chain Pricing](/how-arbitrum-works/deep-dives/parent-chain-pricing.mdx) - Adaptive pricing algorithm for parent chain costs +- [Parent Chain Pricing](/how-arbitrum-works/reference/parent-chain-pricing.mdx) - Adaptive pricing algorithm for parent chain costs - [State Transition Function: Gentle Introduction](/how-arbitrum-works/deep-dives/01-stf-gentle-intro.mdx) - Foundational concepts of the STF -- [Geth](/how-arbitrum-works/deep-dives/geth.mdx) - Geth core and EVM compatibility +- [Geth](/how-arbitrum-works/reference/geth.mdx) - Geth core and EVM compatibility - [ArbOS](/how-arbitrum-works/deep-dives/arbos.mdx) - Arbitrum Operating System features and capabilities -- [State Transition Function Inputs](/how-arbitrum-works/deep-dives/stf-inputs.mdx) - Message types and STF processing mechanics +- [State Transition Function Inputs](/how-arbitrum-works/reference/stf-inputs.mdx) - Message types and STF processing mechanics - [Assertions](/how-arbitrum-works/deep-dives/assertions.mdx) - Rollup assertions and validation structure - [Parent-to-Child Messaging](/how-arbitrum-works/deep-dives/l1-to-l2-messaging.mdx) - L1 to L2 messaging and bridging - [Child-to-Parent Messaging](/how-arbitrum-works/deep-dives/l2-to-l1-messaging.mdx) - L2 to L1 messaging and withdrawals diff --git a/docs/how-arbitrum-works/deep-dives/arbos.mdx b/docs/how-arbitrum-works/deep-dives/arbos.mdx index 34b1039620..1000ad83ea 100644 --- a/docs/how-arbitrum-works/deep-dives/arbos.mdx +++ b/docs/how-arbitrum-works/deep-dives/arbos.mdx @@ -1,6 +1,6 @@ --- -title: 'A gentle introduction' -description: 'Learn the fundamentals of Nitro, Arbitrum stack.' +title: 'ArbOS' +description: 'How ArbOS works as the child chain hypervisor, managing resources, block production, cross-chain messaging, and EVM execution.' author: petevielhaber sme: mehdi salehi user_story: As a current or prospective Arbitrum user, I need learn more about Nitros design. @@ -39,15 +39,7 @@ In the following section, we'll dive deep into these modifications, exploring ho ## How precompiles work -A precompile consists of a Solidity interface in `contracts/src/precompiles/` and a corresponding Golang implementation in `precompiles/`. Using Geth's ABI generator, `solgen/gen.go` generates `solgen/go/precompilesgen/precompilesgen.go`, which collects the ABI data of the precompiles. The [runtime installer](https://github.com/OffchainLabs/nitro/blob/bc6b52daf7232af2ca2fec3f54a5b546f1196c45/precompiles/precompile.go#L379) uses this generated file to check the type safety of each precompile's implementer. - -[The installer](https://github.com/OffchainLabs/nitro/blob/bc6b52daf7232af2ca2fec3f54a5b546f1196c45/precompiles/precompile.go#L379) uses runtime reflection to ensure each implementer has all the right methods and signatures. This reflection includes restricting access to stateful objects, such as the EVM and `statedb`, based on their declared purity. Additionally, the installer verifies and populates event function pointers, enabling each precompile to emit logs and determine its gas cost. Additional configurations, such as restricting a precompile's methods to be callable only by the chain owner, are possible by adding precompile wrappers like `ownerOnly` and `debugOnly` to their [installation entry](https://github.com/OffchainLabs/nitro/blob/bc6b52daf7232af2ca2fec3f54a5b546f1196c45/precompiles/precompile.go#L403). - -Completion of calling, dispatching, and recording of precompile methods occurs via runtime reflection, which avoids any human error that manually parsing and writing bytes could introduce, and uses Geth's stable APIs for [packing and unpacking](https://github.com/OffchainLabs/nitro/blob/bc6b52daf7232af2ca2fec3f54a5b546f1196c45/precompiles/precompile.go#L438) values. - -Each time a transaction calls a method of a child chain-specific precompile, a [`call context`](https://github.com/OffchainLabs/nitro/blob/f11ba39cf91ee1fe1b5f6b67e8386e5efd147667/precompiles/context.go#L26) gets created to track and record the gas burnt. For convenience, it also provides access to the public fields of the underlying [`TxProcessor`](https://github.com/OffchainLabs/nitro/blob/8e786ec6d1ac3862be85e0c9b5ac79cbd883791c/arbos/tx_processor.go#L38). Because sub-transactions could revert without updates to this struct, the `TxProcessor` only makes public what is safe, such as the amount of parent chain calldata paid by the top-level transaction. - -For a complete list of precompiles, refer to the [precompile references](/build-decentralized-apps/precompiles/02-reference.mdx). +ArbOS implements precompiles using a Solidity interface paired with a Golang backend, connected through runtime reflection for type safety and ABI conformance. For the full technical details of the precompile architecture, see [ArbOS technical reference](/how-arbitrum-works/reference/arbos-reference.mdx#how-precompiles-work). For a complete list of precompiles, refer to the [precompile references](/build-decentralized-apps/precompiles/02-reference.mdx). ## Messages @@ -59,145 +51,28 @@ A retryable is a special message type for creating an atomic parent-to-child cha ## ArbOS state -ArbOS's state is viewed and modified via [`ArbosState`](https://github.com/OffchainLabs/nitro/blob/fa36a0f138b8a7e684194f9840315d80c390f324/arbos/arbosState/arbosstate.go#L36) objects, which provide convenient abstractions for working with the underlying data of its [`backingStorage`](https://github.com/OffchainLabs/nitro/blob/fa36a0f138b8a7e684194f9840315d80c390f324/arbos/storage/storage.go#L51). The backing storage's [keyed subspace strategy](https://github.com/OffchainLabs/nitro/blob/fa36a0f138b8a7e684194f9840315d80c390f324/arbos/storage/storage.go#L21) makes [`ArbosState`](https://github.com/OffchainLabs/nitro/blob/fa36a0f138b8a7e684194f9840315d80c390f324/arbos/arbosState/arbosstate.go#L36)'s convenient getters and setters possible, minimizing the need to work directly with the specific keys and values of the underlying storage's [`stateDB`](https://github.com/OffchainLabs/go-ethereum/blob/0ba62aab54fd7d6f1570a235f4e3a877db9b2bd0/core/state/statedb.go#L66). - -Because two [`ArbosState`](https://github.com/OffchainLabs/nitro/blob/fa36a0f138b8a7e684194f9840315d80c390f324/arbos/arbosState/arbosstate.go#L36) objects with the same [`backingStorage`](https://github.com/OffchainLabs/nitro/blob/fa36a0f138b8a7e684194f9840315d80c390f324/arbos/storage/storage.go#L51) contain and mutate the same underlying state, different `ArbosState` objects can provide different views of ArbOS's contents. [`Burner`](https://github.com/OffchainLabs/nitro/blob/fa36a0f138b8a7e684194f9840315d80c390f324/arbos/burn/burn.go#L11) objects, which track gas usage while working with the `ArbosState`, provide the internal mechanism. Some are read-only, causing transactions to revert with `vm.ErrWriteProtection` when a mutating request is issued. Others demand that the caller have elevated privileges. Meanwhile, others dynamically charge users when doing stateful work. This view is chosen for safety when [`OpenArbosState()`](https://github.com/OffchainLabs/nitro/blob/fa36a0f138b8a7e684194f9840315d80c390f324/arbos/arbosState/arbosstate.go#L57) creates the object and may never change. - -[`arbosVersion`](https://github.com/OffchainLabs/nitro/blob/fa36a0f138b8a7e684194f9840315d80c390f324/arbos/arbosState/arbosstate.go#L37), [`updgradeVersion`](https://github.com/OffchainLabs/nitro/blob/fa36a0f138b8a7e684194f9840315d80c390f324/arbos/arbosState/arbosstate.go#L38) and [`upgradeTimestamp`](https://github.com/OffchainLabs/nitro/blob/fa36a0f138b8a7e684194f9840315d80c390f324/arbos/arbosState/arbosstate.go#L39) -ArbOS upgrades are scheduled to happen [when finalizing the first block](https://github.com/OffchainLabs/nitro/blob/fa36a0f138b8a7e684194f9840315d80c390f324/arbos/block_processor.go#L350) after the `upgradeTimestamp`. - -Most of ArbOS's state exists to facilitate its [precompiles](/build-decentralized-apps/precompiles/02-reference.mdx). The parts that aren't are detailed below. - -### [`blockhashes`](https://github.com/OffchainLabs/nitro/blob/fa36a0f138b8a7e684194f9840315d80c390f324/arbos/blockhash/blockhash.go#L15) - -This component maintains the last 256 parent chain block hashes in a circular buffer. This component allows the [`TxProcessor`](https://github.com/OffchainLabs/nitro/blob/8e786ec6d1ac3862be85e0c9b5ac79cbd883791c/arbos/tx_processor.go#L38) to implement the `BLOCKHASH` and `NUMBER` opcodes and supports the precompile methods that involve the Outbox. To avoid changing the ArbOS state outside of a transaction, blocks made from messages with a new parent chain–block number update this info during an [`InternalTxUpdateL1BlockNumber`](https://github.com/OffchainLabs/nitro/blob/fa36a0f138b8a7e684194f9840315d80c390f324/arbos/internal_tx.go#L24) [`ArbitrumInternalTx`](https://github.com/OffchainLabs/nitro/blob/8e786ec6d1ac3862be85e0c9b5ac79cbd883791c/arbos/internal_tx.go) included as the first transaction of the block. - -### [`l1PricingState`](https://github.com/OffchainLabs/nitro/blob/fa36a0f138b8a7e684194f9840315d80c390f324/arbos/l1pricing/l1pricing.go#L16) - -In addition to supporting the [`ArbAggregator precompile`](/build-decentralized-apps/precompiles/02-reference.mdx#arbaggregator), the parent chain pricing state provides tools for determining the parent chain component of a transaction's gas costs. This part of the state tracks the total amount of funds collected from transactions in parent chain gas fees and the funds spent by batch posters to post data breaches on the parent chain. - -Based on this information, ArbOS maintains a parent chain data fee, which is also tracked in this state and determines how much the parent chain fee will cost. ArbOS dynamically adjusts this value so that fees collected are approximately equal to batch posting costs. -For more details about darent chain pricing, see [Parent chain pricing](/how-arbitrum-works/deep-dives/parent-chain-pricing.mdx). - -### [`l2PricingState`](https://github.com/OffchainLabs/nitro/blob/fa36a0f138b8a7e684194f9840315d80c390f324/arbos/l2pricing/l2pricing.go#L14) - -The child chain pricing state tracks child chain resource usage to determine a reasonable child chain gas price. This process considers various factors, including user demand, the state of Geth, and the computational gas target. The primary mechanism for doing so consists of a pair of pools, one larger than the other, that drain as child chain–specific resources are consumed and filled as time passes. Parent chain-specific resources, such as parent chain `calldata`, are not accounted for in the pools since they do not directly impact the computational workload of network actors. Instead, the design of the gas target mechanism regulates execution resources to ensure consistent system performance and synchronization. - -While much of this state is accessible through the [`ArbGasInfo`](/build-decentralized-apps/precompiles/02-reference.mdx#arbgasinfo) and [`ArbOwner`](/build-decentralized-apps/precompiles/02-reference.mdx#arbowner) precompiles, most changes are automatic and happen during [block production](https://github.com/OffchainLabs/nitro/blob/fa36a0f138b8a7e684194f9840315d80c390f324/arbos/block_processor.go#L77) and the [transaction hooks](/how-arbitrum-works/deep-dives/geth.mdx#hooks). Each transaction in an incoming message removes the parent chain component of the gas it consumes from the pool. Afterward, the message's timestamp [informs the pricing mechanism](https://github.com/OffchainLabs/nitro/blob/fa36a0f138b8a7e684194f9840315d80c390f324/arbos/block_processor.go#L336) of the time passed as ArbOS [finalizes the block](https://github.com/OffchainLabs/nitro/blob/fa36a0f138b8a7e684194f9840315d80c390f324/arbos/block_processor.go#L350). - -ArbOS's larger gas pool [determines](https://github.com/OffchainLabs/nitro/blob/2ba6d1aa45abcc46c28f3d4f560691ce5a396af8/arbos/l2pricing/pools.go#L98) the per-block gas limit, setting a dynamic [upper limit](https://github.com/OffchainLabs/nitro/blob/2ba6d1aa45abcc46c28f3d4f560691ce5a396af8/arbos/block_processor.go#L146) on the amount of compute gas a child chain block may have. This limit is always enforced, though it's done in the [`GasChargingHook`](/how-arbitrum-works/deep-dives/geth.mdx#gascharginghook) for the first transaction to avoid sharp decreases in the parent chain gas price from over-inflating the compute component purchased to above the gas limit. Enforcing this improves UX by allowing the first transaction to succeed rather than requiring a resubmission. Because the first transaction reduces the space left in the block, subsequent transactions do not use this strategy and may fail due to compute-component inflation. This space is acceptable because such transactions occur only when the system is under heavy load. The result is that the user's transaction is dropped without charges since the state transition fails early. Those trusting the Sequencer can rely on the automatic resubmission of a transaction in such a scenario. - -We need a per-block gas limit because arbitrator WAVM execution is much slower than native transaction execution. This limit means there can only be so much gas, roughly translating to wall-block time, in a child chain block. It also allows ArbOS to limit the size of blocks should demand continue to surge even as prices rise. +ArbOS's state is managed through `ArbosState` objects that abstract over the underlying key-value storage. Most of this state supports [precompiles](/build-decentralized-apps/precompiles/02-reference.mdx). Key components include `blockhashes` (recent parent chain block hashes), `l1PricingState` (parent chain fee tracking and batch poster reimbursement), and `l2PricingState` (child chain gas pricing via dual gas pools). -ArbOS's per-block gas limit is distinct from Geth's block limit, which ArbOS [sets sufficiently high](https://github.com/OffchainLabs/nitro/blob/2ba6d1aa45abcc46c28f3d4f560691ce5a396af8/arbos/block_processor.go#L166) never to run out. This approach is safe since Geth's block limit exists to constrain the work done per block, which ArbOS already does via its own per-block gas limit. Though it'll never run out, a block's transactions use the [same Geth gas pool](https://github.com/OffchainLabs/nitro/blob/2ba6d1aa45abcc46c28f3d4f560691ce5a396af8/arbos/block_processor.go#L199) to maintain the invariant that the pool decreases monotonically after each transaction. Block headers [use the Geth block limit](https://github.com/OffchainLabs/nitro/blob/2ba6d1aa45abcc46c28f3d4f560691ce5a396af8/arbos/block_processor.go#L67) for internal consistency and to ensure gas estimation works. They are distinct from the [`gasLeft`](https://github.com/OffchainLabs/nitro/blob/2ba6d1aa45abcc46c28f3d4f560691ce5a396af8/arbos/block_processor.go#L146) variable, which ephemerally exists outside of the global state to keep child chain blocks from exceeding ArbOS's per-block gas limit and to deduct space where the state transition failed or [used negligible amounts](https://github.com/OffchainLabs/nitro/blob/faf55a1da8afcabb1f3c406b291e721bfde71a05/arbos/block_processor.go#L328) of compute gas. ArbOS does not need to persist `gasLeft` because its pool induces a revert, and transactions use the Geth block limit during EVM execution. +For the full technical details of ArbOS state, pricing internals, and the per-block gas limit mechanism, see the [ArbOS technical reference](/how-arbitrum-works/reference/arbos-reference.mdx#arbos-state). For parent chain pricing specifics, see [Parent chain pricing](/how-arbitrum-works/reference/parent-chain-pricing.mdx). -## Child chain gas pricing +## Gas and fees -The child chain gas price on a given Arbitrum chain has a set floor, which is queriable via [`ArbGasInfo`](/build-decentralized-apps/precompiles/02-reference.mdx#arbgasinfo)'s `getMinimumGasPrice` method. +ArbOS manages child chain gas pricing and fee collection for both child chain execution costs and parent chain data posting costs. The child chain uses a dynamic basefee mechanism with multiple gas targets over different time windows to handle demand spikes while maintaining long-term chain capacity. Transactions pay fees to both the batch poster (for parent chain data costs) and the network (for child chain execution). -## Estimating child chain gas +For a comprehensive explanation of how gas pricing works, including: -Calling an Arbitrum Node's `eth_estimateGas` RPC gives a value sufficient to cover the full transaction fee at the given child chain gas price, i.e., the value returned from `eth_estimateGas` multiplied by the child chain gas price tells you how much total Ether is required for the transaction to succeed. Note that this means the value returned by `eth_estimateGas` for a given operation will change over time (as the parent chain's calldata price fluctuates). See [2-D fees](https://medium.com/offchainlabs/understanding-arbitrum-2-dimensional-fees-fd1d582596c9) and [How to estimate gas in Arbitrum](/build-decentralized-apps/02-how-to-estimate-gas.mdx) for more information. +- Child chain basefee calculation and gas targets +- Parent chain calldata pricing and batch poster reimbursement +- Fee estimation and collection mechanisms +- The gas target's role in validator security -## Child chain gas fees - -Child chain gas fees work very similarly to gas on Ethereum. The amount of gas is multiplied by the current basefee to get the child chain gas fee charged to the transaction. - -The child chain basefee is set by a version of the "exponential mechanism" widely discussed in the Ethereum community and shown to be equivalent to Ethereum's EIP-1559 gas pricing mechanism. - -The algorithm compares gas usage against the [gas target](/how-arbitrum-works/deep-dives/gas-and-fees.mdx#the-gas-target) parameter — the target amount of gas per second that the chain can sustainably handle over time. ([You can read more about how the gas fees are calculated in this explainer](/how-arbitrum-works/deep-dives/gas-and-fees.mdx)) The algorithm tracks a gas backlog. Whenever a transaction consumes gas, the gas consumed gets added to the backlog. Whenever the clock ticks a second, the gas target is subtracted from the backlog, but the backlog can never go below zero. - -Intuitively, if the backlog grows, the algorithm should increase the gas price to slow gas usage because usage is above the sustainable level. If the backlog shrinks, the price should decrease again because usage has been below the sustainable limit, so more gas usage can be welcomed. - -More precisely, the basefee is an exponential function of the backlog, `F = exp(-a(B-b))`, where `a` and `b` are suitably chosen constants: `a` controls how rapidly the price escalates with the backlog, and `b` allows a small backlog before the basefee escalation begins. - -## Child chain tips - -The Sequencer prioritizes transactions on a first-come, first-served basis. Because tips do not make sense in this model, they are ignored. Arbitrum users always pay the basefee regardless of the tip they choose. - -## Gas estimating retryables - -When a transaction schedules another, the subsequent transaction's execution [will be included](https://github.com/OffchainLabs/go-ethereum/blob/d52739e6d54f2ea06146fdc44947af3488b89082/internal/ethapi/api.go#L999) when estimating gas via the node's RPC. Finding a gas estimate for a transaction is only possible if all the transactions succeed at a given gas limit. This estimation is especially important when working with retryables and scheduling `redeem` attempts. - -Because a call to `redeem` consumes all the call's gas, doing multiple calls requires limiting the gas provided to each sub-call. Otherwise, the first will take all of the gas and force the second to fail, irrespective of the estimation's gas limit. - -Gas estimation for retryable submissions is possible via the [`NodeInterface`](/build-decentralized-apps/nodeinterface/02-reference.mdx) and similarly requires the auto-redeem attempt to succeed. - -## The gas target - -The security of Nitro chains depends on the assumption that when one validator creates an assertion, other validators will check it and respond with a correct assertion and a challenge if it is wrong. This assumption requires that the other validators have the time and resources to check each assertion and issue a timely challenge quickly. The Arbitrum protocol accounts for this when setting deadlines for assertions. - -This approach sets an effective gas target on execution of a Nitro chain: in the long run, the chain cannot make progress faster than a validator can emulate its execution. If assertions are published faster than the gas target, their deadlines will get farther and farther into the future. Due to the Rollup protocol's limit on how far a deadline can be in the future, this will eventually slow new assertions, thereby enforcing the effective gas target. - -Being able to set the gas target accurately depends on estimating the time required to validate an assertion with some accuracy. Any uncertainty in estimating validation time will force us to lower the gas target to be safe. We do not want to lower the gas target, so we try to enable accurate estimation. +See [Gas and Fees](/how-arbitrum-works/deep-dives/gas-and-fees.mdx). For detailed mathematical formulas and parent chain pricing internals, see [Parent chain pricing](/how-arbitrum-works/reference/parent-chain-pricing.mdx). ## Stylus-specific differences -This section details how Stylus integrates into the State Transition Function (STF), covering execution flow, messaging handling, caching, and interactions with ArbOS and Geth. - -### 1. Execution flow of a Stylus transaction - -When a Transaction interacts with a Stylus contract, its execution follows a distinct path compared to EVM transactions: - -- **Transaction submission and routing** - - The transaction is included in a child chain block by the Sequencer. - - Geth processes the transaction and determines its target contract. - - If the target is a Stylus contract, ArbOS routes execution to the WASM runtime instead of the EVM. -- **Stylus execution within ArbOS** - - ArbOS retrieves the Stylus program from its cache (`stylus/src/cache.rs`) or loads it from storage if not cached. - - The WebAssembly System Interface (Go-WASI) initializes a secure execution environment. - - The WASM module executes within ArbOS, processing instructions efficiently and calling host I/O functions. -- **Host I/O operations for blockchain state access** - - Stylus contracts do not use EVM opcodes. Instead, they interact with the blockchain through host I/O calls handled by ArbOS. - - These include storage access (`TLOAD` `TSTORE`), arithmetic operations (`MULMOD`, `ADDMOD`), and context retrieval (`GETCALLER`, `GETCALLVALUE`). - - ArbOS ensures these operations are efficient and compatible with Ethereum's state model. - - **State commitment and finalization** - - Once execution is complete, ArbOS finalizes storage changes and updates logs and receipts. - - Geth processes the final transaction result and commits it to the state tree. - -This process bypasses the EVM interpreter entirely, allowing Stylus contracts to execute significantly faster than their Solidity counterparts. - -### 2. Stylus caching and gas pricing - -- **Stylus gas pricing model** - Unlike standard EVM gas pricing, Stylus pricing follows a multi-dimensional cost model, incorporating: - - **Ink cost (memory and execution cost)** - - Measure in `Ink` units (Stylus's equivalent of computational gas). - - `Ink` pricing varies based on execution complexity, memory usage, and computation steps. - - Complex WASM operations consume more `Ink`, directly impacting execution costs. - - **Opcode pricing** - - WASM instructions are assigned individual execution costs similar to EVM opcodes. - - Heavy computation opcodes are priced higher. - - Cheap opcodes (e.g., simple arithmetic, bitwise operations) have minimal costs. - - **Host I/O pricing** - - Stylus introduces fine-grained pricing for different I/O calls: - - **Storage read/writes**: Priced based on access pattern and data size. - - **Precompile calls**: Stylus-specific precompiles have fixed execution costs. - - **External calls to EVM contracts**: Encapsulated within ArbOS transaction handling, with additional gas considerations. - -#### Stylus caching - -Stylus contracts leverage an advanced caching system to minimize execution overhead within ArbOS: - -- **LRU (Least Recently Used) caching**: Keeps the most recently accessed Stylus contracts in memory for fast execution. -- **Persistent long-term caching**: Caching for selected contracts may occur across blocks based on an economic auction model. -- **Init costs and execution pricing**: Instead of a flat gas cost, Stylus contracts have dynamic execution costs based on WASM complexity. ArbOS maintains pricing parameters (`initCost`, `cachedCost`) that are adjusted based on future WASM execution optimizations. - -### 3. Interaction with ArbOS and Geth - -| Execution Stage | Handled By | -| ---------------------- | --------------------------------------------------------- | -| Transaction submission | **Geth** (identifies target contract) | -| Stylus execution | **ArbOS** (switches to WASM runtime) | -| Host I/O calls | **ArbOS** (handles storage, call data, context retrieval) | -| State commitment | **Geth and ArbOS** (finalizes updates, commits to state) | - -### 4. Go-WASI and co-threads in Stylus execution - -ArbOS executes Stylus contracts using Go-WASI, a WASM-compatible runtime with custom optimizations for Arbitrum. Key features include: - -- **Memory management**: WASM modules execute in a sandboxed environment with strict memory allocation policies. -- **Co-threads for efficient execution**: Instead of traditional synchronous execution, Stylus employs co-threading, enabling lightweight task switching and parallelism where possible. -- **Deterministic execution**: Ensures that Stylus contracts remain fully deterministic and compatible with Ethereum's consensus model. - -These optimizations make Stylus an extremely efficient execution environment, capable of outperforming the EVM while maintaining security and compatibility with Ethereum's state model. +Stylus extends ArbOS to support WASM-based smart contracts alongside +the EVM. When a transaction targets a Stylus contract, ArbOS routes execution to the WASM runtime, which +uses host I/O calls for blockchain state access instead of EVM opcodes. Stylus contracts use a multi-dimensional +gas model based on Ink units, with LRU caching to minimize execution overhead. + +For the full technical details of Stylus execution flow, caching, gas pricing, and Go-WASI integration, see the [ArbOS technical reference](/how-arbitrum-works/reference/arbos-reference.mdx#stylus-specific-differences). diff --git a/docs/how-arbitrum-works/deep-dives/gas-and-fees.mdx b/docs/how-arbitrum-works/deep-dives/gas-and-fees.mdx index d680acb885..251ac61092 100644 --- a/docs/how-arbitrum-works/deep-dives/gas-and-fees.mdx +++ b/docs/how-arbitrum-works/deep-dives/gas-and-fees.mdx @@ -24,6 +24,12 @@ The following sections will detail how to calculate parent and child chain fees. ArbOS dynamically prices the parent chain gas, with the price adjusting to ensure that the amount collected in the parent chain gas fees is as close as possible to the costs that must be covered, over time. +:::tip For detailed mathematical treatment + +For comprehensive mathematical formulas and technical details of parent chain pricing algorithms, including the dynamic pricing mechanism and cost allocation formulas, see [Parent chain pricing](/how-arbitrum-works/reference/parent-chain-pricing.mdx). + +::: + ### Parent chain costs There are two types of parent chain costs: batch posting costs, and rewards. diff --git a/docs/how-arbitrum-works/deep-dives/l1-to-l2-messaging.mdx b/docs/how-arbitrum-works/deep-dives/l1-to-l2-messaging.mdx index b093f9c794..2b0b118c0a 100644 --- a/docs/how-arbitrum-works/deep-dives/l1-to-l2-messaging.mdx +++ b/docs/how-arbitrum-works/deep-dives/l1-to-l2-messaging.mdx @@ -9,7 +9,13 @@ content_type: get-started import ImageZoom from '@site/src/components/ImageZoom'; -In the [Bypassing the Sequencer](/how-arbitrum-works/deep-dives/transaction-lifecycle.mdx#bypassing-the-sequencer) section, we introduced an alternative way for users to submit transactions to a child chain by going through the parent chain's Delayed Inbox contract instead of sending them directly to the Sequencer. This approach is one example of a parent-to-child messaging path. More broadly, parent-to-child chain messaging covers all ways to: +:::tip Looking for implementation guides? + +This document explains the protocol-level concepts of parent-to-child messaging. For practical, step-by-step instructions on implementing bridging and messaging, see [How to bridge from parent chain to child chain](/build-decentralized-apps/how-to-bridge-from-parent-chain.mdx). + +::: + +In the [Bypassing the Sequencer](/build-decentralized-apps/transaction-lifecycle.mdx#bypassing-the-sequencer) section, we introduced an alternative way for users to submit transactions to a child chain by going through the parent chain's Delayed Inbox contract instead of sending them directly to the Sequencer. This approach is one example of a parent-to-child messaging path. More broadly, parent-to-child chain messaging covers all ways to: - Submit child chain bound transaction from a parent chain - Deposit `ETH` or native tokens from a parent chain to a child chain @@ -22,7 +28,7 @@ We generally categorize these parent-to-child chain messaging methods as follows - **`ETH` Bridging**: For Arbitrum chains that use `ETH` as their gas token, users can deposit `ETH` onto a child chain via the Delayed Inbox. - **Custom gas token bridging**: For Arbitrum chains that use a custom gas token, users can deposit that chain's native token to a child chain using the same mechanism. -2. **Transaction via the Delayed Inbox**: As described in the [Bypassing the Sequencer](/how-arbitrum-works/deep-dives/transaction-lifecycle.mdx#bypassing-the-sequencer) section, this method allows users to send transactions through the parent chain. It includes two sub-types of messages: +2. **Transaction via the Delayed Inbox**: As described in the [Bypassing the Sequencer](/build-decentralized-apps/transaction-lifecycle.mdx#bypassing-the-sequencer) section, this method allows users to send transactions through the parent chain. It includes two sub-types of messages: - **Unsigned messages**: General arbitrary data or function calls - **Signed messages**: Messages that include a signature, enabling certain authenticated actions @@ -38,27 +44,17 @@ This section will explore these categories in detail and explain how they work. ## Native token bridging +Native token bridging refers to depositing a chain's native currency (the token used to pay gas fees) from the parent chain to the child chain. This follows a different, simpler process than ERC-20 token bridging through the canonical token bridge. + Arbitrum chains can use `ETH` or any other `ERC-20` tokens as their gas fee currency. Arbitrum One and Nova use `ETH` as their native token, while some Arbitrum chains opt for a custom gas token. For more details about chains that use custom gas tokens, refer to the [Custom gas token SDK](/build-decentralized-apps/custom-gas-token-sdk.mdx). Whether a chain uses `ETH` or a custom gas token, users can deposit the token from a parent chain (for Arbitrum One, it is Ethereum) to a child chain. Below, we describe how to deposit `ETH` on chains that use `ETH` as the native gas token. The process for depositing custom gas tokens follows the same steps, except it uses the chain's Delayed Inbox contract. -### Depositing ETH +### Depositing native tokens -A special message type exists for simple `ETH` deposits from parent-to-child chains. You can deposit `ETH` by calling the `Inbox` contract's `depositEth` method, for example: +A special message type exists for simple native token deposits from parent-to-child chains. The `Inbox` contract's `depositEth` method provides this functionality for chains using `ETH`, while custom gas token chains use similar mechanisms through their Delayed Inbox contract. -```javascript -function depositEth(address destAddr) external payable override returns (uint256) -``` - -:::warning - -Depositing `ETH` directly via `depositEth` to a contract on a child chain **will not** invoke that contract's fallback function. - -::: - -#### Using retryable tickets instead - -While `depositEth` is often the simplest path, you can also use _retryable tickets_ to deposit `ETH`. This method may be preferable if you need additional flexibility–for example, specifying an alternative destination address or triggering a fallback function on a child chain. +For step-by-step instructions on depositing native tokens, see [How to bridge from parent chain](/build-decentralized-apps/how-to-bridge-from-parent-chain.mdx#bridging-eth-to-a-child-chain). #### How deposits work @@ -105,7 +101,7 @@ modifier onlyFromMyL1Contract() override { Arbitrum provides a _Delayed Inbox_ contract on the parent chain that can deliver arbitrary messages to the child chain. This functionality is important for two reasons: 1. **General cross-chain messaging**: Allows parent chain EOAs or parent chain contracts to send messages or transactions to a child chain. This functionality is critical for bridging assets (other than the chain's native token) and performing cross-chain operations. -2. **Censorship resistance**: It ensures the Arbitrum chain remains censorship-resistant, even if the Sequencer misbehaves or excludes certain transactions; refer to [Bypassing the Sequencer](/how-arbitrum-works/deep-dives/transaction-lifecycle.mdx#bypassing-the-sequencer) for more details. +2. **Censorship resistance**: It ensures the Arbitrum chain remains censorship-resistant, even if the Sequencer misbehaves or excludes certain transactions; refer to [Bypassing the Sequencer](/build-decentralized-apps/transaction-lifecycle.mdx#bypassing-the-sequencer) for more details. Users can send child chain transactions through the Delayed Inbox in two primary ways: @@ -128,104 +124,15 @@ Signed messages let a parent chain EOA prove ownership of an address, ensuring t - You want to force-include a transaction on a child chain in case of Sequencer downtime or censorship. - You need an operation on a child chain that explicitly requires EOA authorization (e.g., a withdrawal). -#### How signed messages work - When submitting through the Delayed Inbox, a child chain transaction signature gets included in the message's calldata. Because it matches the EOA's signature, the child chain can safely treat the signer's address as the sender. -Example use case: -[Withdraw Ether tutorial](https://github.com/OffchainLabs/arbitrum-tutorials/blob/a1c3f64a5abdd0f0e728cb94d4ecc2700eab7579/packages/delayedInbox-l2msg/scripts/withdrawFunds.js#L61-L65) - -#### Delayed Inbox methods for signed messages - -There are two primary methods for sending signed messages: - -1. `sendL2Message` - -- It can be called by either an EOA or a contract -- The complete signed transaction data is emitted in an event log so that nodes can reconstruct the transaction without replaying it -- More flexible - -```solidity -function sendL2Message( - bytes calldata messageData -) external whenNotPaused onlyAllowed returns (uint256) -``` - -2. `sendL2MessageFromOrigin` - -- Only an EOA with no deployed code can call this ("codeless origin") -- The signed transaction is retrieved directly from calldata, so emitting a large event log is unnecessary -- Offers lower gas costs (cheaper) - -```solidity -function sendL2MessageFromOrigin( - bytes calldata messageData -) external whenNotPaused onlyAllowed returns (uint256); -``` +The Delayed Inbox provides two methods for signed messages: `sendL2Message` (more flexible, can be called by EOAs or contracts) and `sendL2MessageFromOrigin` (cheaper gas costs, EOA-only). For implementation details, see [Sending signed messages](/build-decentralized-apps/how-to-bridge-from-parent-chain.mdx#sending-signed-messages). ### Unsigned messages -Unsigned messages allow a parent chain sender to specify transaction parameters without an EOA signature. Because there is no signature, **the sender's address must be aliased on the child chain** (see the [Address aliasing](#address-aliasing) section for the rationale). The Delayed Inbox provides four main methods for unsigned messages, divided based on whether the sender is an EOA or a contract and whether it includes parent chain funds: - -1. **Unsigned from EOA's**: These methods incorporate a nonce for replay protection, similar to standard EOA-based transactions on Ethereum. - -- `sendL1FundedUnsignedTransaction` - - Transfers value from a parent chain to a child chain along with the transaction - - Parameters: gas limit, fee, nonce, destination address, and calldata +Unsigned messages allow a parent chain sender to specify transaction parameters without an EOA signature. Because there is no signature, **the sender's address must be aliased on the child chain** (see the [Address aliasing](#address-aliasing) section for the rationale). -```solidity -function sendL1FundedUnsignedTransaction( - uint256 gasLimit, - uint256 maxFeePerGas, - uint256 nonce, - address to, - bytes calldata data -) external payable returns (uint256); -``` - -- `sendUnsignedTransaction` - - No value transfers from the parent chain - - Transaction fees and value on a child chain come from the child chain balance - -```solidity -function sendUnsignedTransaction( - uint256 gasLimit, - uint256 maxFeePerGas, - uint256 nonce, - address to, - uint256 value, - bytes calldata data -) external whenNotPaused onlyAllowed returns (uint256); -``` - -2. **Unsigned from contracts**: Contracts typically rely on standard Ethereum replay protection using their contract address. - -- `sendContractTransaction` - - Sends a transaction from a parent chain with no new funds; uses the contract's existing child chain balance. - -```solidity -function sendContractTransaction( - uint256 gasLimit, - uint256 maxFeePerGas, - address to, - uint256 value, - bytes calldata data -) external whenNotPaused onlyAllowed returns (uint256); -``` - -- `sendL1FundedContractTransaction` - - Sends the transaction _and_ transfers additional funds from a parent to child chain - -```solidity -function sendL1FundedContractTransaction( - uint256 gasLimit, - uint256 maxFeePerGas, - address to, - bytes calldata data -) external payable returns (uint256); -``` - -In these methods, a "delayed message" is created and passed to the parent chain bridge contract, which then arranges its inclusion on a child chain. +The Delayed Inbox provides methods for unsigned messages from both EOAs and contracts, with variants for whether funds are transferred from the parent chain or drawn from the child chain balance. For implementation details and method signatures, see [Sending unsigned messages](/build-decentralized-apps/how-to-bridge-from-parent-chain.mdx#sending-unsigned-messages). ### Messages types @@ -243,268 +150,41 @@ Please refer to the [Address aliasing](#address-aliasing) discussion for more ba ## Retryable tickets -Retryable tickets are Arbitrum's canonical method for creating parent-to-child chain messages, i.e., parent-chain transactions that initiate a message to get executed on a child chain. A retryable is submittable for a fixed-cost (dependent only on its calldata size) paid at the parent chain; its _submission_ on the parent chain is separable/asynchronous with its _execution_ on the child chain. Retryables provide atomicity between the cross-chain operations; if the parent chain transaction to request submission succeeds (i.e., does not revert), then the execution of the retryable on the child chain has a strong guarantee to succeed. +Retryable tickets are Arbitrum's canonical method for creating parent-to-child chain messages—parent-chain transactions that initiate a message to be executed on a child chain. A retryable is submittable for a fixed cost (dependent only on its calldata size) paid at the parent chain. Critically, the ticket's _submission_ on the parent chain is separable and asynchronous from its _execution_ on the child chain. This design provides atomicity between the cross-chain operations: if the parent chain transaction to request submission succeeds (does not revert), then the execution of the retryable on the child chain has a strong guarantee to eventually succeed. + +For step-by-step instructions on creating retryable tickets, including parameter estimation and SDK usage, see [Creating retryable tickets](/build-decentralized-apps/how-to-bridge-from-parent-chain.mdx#creating-retryable-tickets). ### Retryable ticket lifecycle -Here, we walk through the different stages of the lifecycle of a retryable ticket: (1) submission, (2) auto-redemption, and (3) manual redemption. +The lifecycle of a retryable ticket involves three stages: submission, automatic redemption, and manual redemption. #### Submission -1. Creating a retryable ticket is initiated with a call (direct or internal) to the `createRetryableTicket` function of the [`inbox` contract](https://github.com/OffchainLabs/nitro-contracts/blob/67127e2c2fd0943d9d87a05915d77b1f220906aa/src/bridge/Inbox.sol). A ticket is guaranteed to get created if this call succeeds. Here, we describe parameters that need adjusting with care. Note that this function forces the sender to provide _a reasonable_ amount of funds (at least enough for submitting and _attempting_ to execute the ticket), but that doesn't guarantee a successful auto-redemption. - -| Parameter | Description | -| ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `l1CallValue` (also referred to as deposit) | Not a real function parameter; it is rather the `callValue` that gets sent along with the transaction | -| `address to` | The destination child chain address | -| `uint256 l2CallValue` | The `callvalue` for the retryable child chain message that is supplied within the deposit (`l1CallValue`) | -| `uint256 maxSubmissionCost` | The maximum amount of `ETH` payable for submitting the ticket. This amount is (1) supplied within the deposit (`l1CallValue`) to be later deducted from the sender's child chain balance and is (2) directly proportional to the size of the retryable's data and parent chain basefee. | -| `address excessFeeRefundAddress` | The unused gas cost and submission cost will deposit to this address, formula is: `(gasLimit x maxFeePerGas - execution cost) + (maxSubmission - (autoredeem ? 0 : submission cost))`. (**Note**: The excess deposit will transfer to the alias address of the parent chain transaction's `msg.sender` rather than this address) | -| `address callValueRefundAddress` | The child chain address to which the `l2CallValue` is credited if the ticket times out or gets canceled (also called the `beneficiary`, who's got a critical permission to cancel the ticket). | -| uint256 gasLimit | Maximum amount of gas used to cover the child chain execution of the ticket | -| uint256 maxFeePerGas | The gas price bid for child chain execution of the ticket supplied within the deposit (`l1CallValue`) | -| bytes calldata data | The calldata to the destination child chain address | - -2. The sender's deposit must be enough to make the parent chain submission succeed and for child chain execution to be _attempted_. If provided correctly, a new ticket with a unique `TicketID` is created and added to the retryable buffer. Also, funds (`submissionCost` + `l2CallValue`) are deducted from the sender and placed into escrow for later use in redeeming the ticket. - -3. Ticket creation causes the [`ArbRetryableTx`](/build-decentralized-apps/precompiles/02-reference.mdx#arbretryabletx) precompile to emit a `TicketCreated` event containing the `TicketID` on the child chain. - -**Ticket Submission** - -1. User initiates a parent-to-child message -2. Initiating a parent-child message - -- A call to `inbox.createRetryableTicket` function that puts the message in the child chain inbox that can be re-executed for some fixed amount of time if it reverts. - -3. Check the user's deposit - -- Logic that checks if the user has enough funds to create a ticket. A process that checks if the `msg.value` provided by the user is greater than or equal to `maxSubmissionCost + l2CallValue + gasLimit * maxFeePerGas`. - -4. Ticket creation fails, and no funds get deducted from the user -5. Ticket creation - -- A ticket is created and added to the retryable buffer on the child chain. Funds (`l2CallValue + submissionCost`) get deducted to cover the `callValue` from the user and are placed into escrow (on the child chain) for later use in redeeming the ticket. +Creating a retryable ticket is initiated with a call to the `createRetryableTicket` function of the inbox contract. Key parameters include the destination address, call value, gas limits, refund addresses, and calldata. The ticket requires sufficient funds to cover both submission costs and gas for child chain execution. Upon successful submission, a unique `TicketID` is created and the `ArbRetryableTx` precompile emits a `TicketCreated` event. #### Automatic redemption -1. It is very important to note that submitting a ticket on the parent chain is separable/asynchronous from its execution on the child chain, i.e., a successful parent chain ticket creation does not guarantee successful redemption. Upon successful ticket creation, checks validate the two following conditions: - -- if the user's child chain balance is greater than (or equal to) `maxFeePerGas * gasLimit` **and** -- if the `maxFeePerGas` (provided by the user in the ticket submission process) is greater than (or equal to) the `l2BaseFee`. If these conditions are both met, an attempt to execute the ticket on the child chain triggers (i.e., **auto-redeem** using the supplied gas, as if the `redeem` method of the `[ArbyRetryableTx]` precompile had been called). Depending on how much gas the sender has provided in Step 1, the ticket's redemption can either (1) immediately succeed or (2) fail. We explain both situations below: - -**Immediate success** - -If the ticket is successfully auto-redeemed, it will execute with the original submission's sender, destination, callvalue, and calldata. The submission fee is refunded to the user on the child chain (`excessFeeRefundAddress`). Note that to ensure successful auto-`redeem` of the ticket, one could use the Arbitrum SDK, which provides a [convenience function](https://github.com/OffchainLabs/arbitrum-sdk/blob/4cedb1fcf1c7302a4c3d0f8e75fb33d82bc8338d/src/lib/message/L1ToL2MessageGasEstimator.ts#L215) that returns the desired gas parameters when sending parent-to-child messages. +Upon successful ticket creation, the system checks if conditions are met for automatic redemption: the user's child chain balance must cover the gas costs, and the provided `maxFeePerGas` must meet or exceed the child chain base fee. If these conditions are met, the system automatically attempts to execute the ticket (auto-redeem). -**Fail** - -If a `redeem` is not done at submission or the submission's initial `redeem` fails (for example, because the child chain's gas price has increased unexpectedly), the submission fee is collected on the child chain to cover the resources required to temporarily keep the ticket in memory for a fixed period (one week), and only in this case, a manual redemption of the ticket is required (see the next section). - -**Automatic redemption of the `TicketManual` redemption of the ticket** - -1. Does the auto-`redeem` succeed? - -- Logic that determines if the user's child chain balance is greater than (or equal to) `maxFeePerGas * gasLimit && maxFeePerGas` is greater than (or equal to) the `l2BaseFee`. - -2. Ticket is executed - -- The actual `submissionFee` is refunded to the `excessFeeRefundAddress` because the ticket cleared from the buffer of the child chain. - -3. The ticket is deleted from the child chain retryable buffer. -4. Refund `callValueRefundAddress` - -- Refunded with (`maxGas - gasUsed) * gasPrice`. Note that the cap amount is the `l2CallValue` in the auto-`redeem`. +If auto-redemption succeeds, the ticket executes immediately with the original parameters, and excess fees are refunded. If auto-redemption fails (for example, due to insufficient gas or an increase in gas prices), the ticket remains in the retryable buffer for up to one week, allowing for manual redemption. #### Manual redemption -1. At this point, _anyone_ can attempt to manually redeem the ticket again by calling [`ArbRetryableTx`](/build-decentralized-apps/precompiles/02-reference.mdx#arbretryabletx) `redeem` precompile method, which donates the call's gas to the next attempt. Note that the amount of gas is **not** limited by the original `gasLimit` set during the ticket creation. ArbOS will [enqueue the `redeem`](https://github.com/OffchainLabs/nitro/blob/fa36a0f138b8a7e684194f9840315d80c390f324/arbos/block_processor.go#L245) before moving on to the next non-`redeem` transaction in the block it's forming. In this manner, `redeem`'s are scheduled to happen as soon as possible and will always be in the same block as the transaction that scheduled it. Note that the `redeem` attempt's gas comes from the call to `redeem`, so there's no chance to reach the block's gas limit before execution. - -2. If the fixed period (one week) elapses without a successful `redeem`, the ticket **expires** and will be [automatically discarded](https://github.com/OffchainLabs/nitro/blob/fa36a0f138b8a7e684194f9840315d80c390f324/arbos/retryables/retryable.go#L262), unless some party has paid a fee to [keep the ticket alive](https://github.com/OffchainLabs/nitro-precompile-interfaces/blob/fe4121240ca1ee2cbf07d67d0e6c38015d94e704/ArbRetryableTx.sol#L45) for another full period. A ticket can live indefinitely as long as it continues to renew each time before it expires. - -**Process flow** - -1. Is the ticket manually created or not redeemed within seven days? - -- Logic that determines if the ticket is manually canceled or not redeemed within seven days (i.e., it is expired). - -2. Refunding `callValueRefundAddress` - -- The `l2CallValue` is refunded to the `callValueRefundAddress` - -3. The ticket is deleted from the child chain retryable buffer. -4. Is the ticket manually redeemed? - -- Logic that determines if the ticket is manually redeemed. - -#### Avoid losing funds! - -Any associated messages and values, excluding the escrowed `callValue`, may be permanently lost if a ticket is not redeemed or rescheduled within seven days. +If automatic redemption fails, anyone can manually redeem the ticket by calling the `ArbRetryableTx` precompile's `redeem` method. The manual redemption attempt donates its call gas to the execution, and the gas limit is not constrained by the original ticket parameters. This allows tickets to be retried with different gas conditions. -On success, the `To` address receives the escrowed `callValue`, and any unused gas returns to ArbOS's gas pools. On failure, the `callValue` is returned to the escrow for the future `redeem` attempt. In either case, the network fee gets paid during the scheduling transaction, so no fee charges or refunds get made. +Tickets remain in the retryable buffer for one week. If not successfully redeemed within this period, the ticket expires and is automatically discarded. However, tickets can be kept alive indefinitely by paying a fee to extend the lifetime for another full period before expiration. -During ticket redemption, attempts to cancel the same ticket or schedule another `redeem` of the same ticket will revert. In this manner, retryable tickets are not self-modifying. +If a ticket expires without successful redemption, the escrowed `callValue` is refunded to the `callValueRefundAddress` specified during submission. This protects user funds even if execution never succeeds. -If a ticket with a `callValue` is eventually discarded (canceled or expired) having never successfully run, the escrowed `callValue` will be paid out to a `callValueRefundAddress` account that was specified in the initial submission (Step 1). +#### Receipt types -:::info Important notes +Retryable tickets produce two types of receipts: -If a `redeem` is not done at submission or the submission's initial `redeem` fails, anyone can attempt to redeem the retryable again by calling [`ArbRetryableTx`](/build-decentralized-apps/precompiles/02-reference.mdx#arbretryabletx) `redeem` precompile method, which donates the call's gas to the next attempt. +- **Ticket creation receipt**: Confirms successful ticket creation and includes a `TicketCreated` event with the `ticketId` +- **Redeem attempt receipt**: Records each redemption attempt and includes a `RedeemScheduled` event -- One can `redeem` live tickets using the [Arbitrum Retryables Transaction Panel](https://retryable-dashboard.arbitrum.io/tx) -- The calldata of a ticket is saved on the child chain until it is redeemed or expired -- Redeeming the cost of a ticket will not increase over time; it only depends on the current gas price and gas required for execution. - -::: - -#### Receipts - -In the lifecycle of a retryable ticket, two types of child chain transaction receipts will emit: - -- **Ticket creation receipt**: This receipt indicates successful ticket creation; any successful parent chain call to the `Inbox`'s `createRetryableTicket` method is guaranteed to create a ticket. The ticket creation receipt includes a `TicketCreated` event (from `ArbRetryableTx`), which includes a `ticketId` field. This `ticketId` is computable via RLP encoding and hashing the transaction; see `[calculateSubmitRetryableId]`(https://github.com/OffchainLabs/arbitrum-sdk/blob/6cc143a3bb019dc4c39c8bcc4aeac9f1a48acb01/src/lib/message/L1ToL2Message.ts#L109). -- **`redeem` attempt**: A `redeem` attempt receipt represents the results of an attempted child chain execution of a ticket, i.e., success/failure of that specified `redeem` attempt. It includes a `RedeemScheduled` event from `ArbRetryableTx`, with a `ticketId` field. At most, one successful `redeem` attempt can ever exist for a given ticket; if, e.g., the auto-`redeem` upon initial creation succeeds, only the receipt from the auto-`redeem` will ever get emitted for that ticket. If the auto-`redeem` fails (or was never attempted–i.e., the provided `child chain gas limit * child chain gas price = 0`), each initial attempt will emit a `redeem` attempt receipt until one succeeds. - -#### Alternative "unsafe" retryable ticket creation - -The `Inbox.createRetryableTicket` convenience method includes sanity checks to help minimize the risk of user error: the method will ensure enough funds are provided directly from a parent chain to cover the current cost of ticket creation. It also will convert the provided `callValueRefundAddress` and `excessFeeRefundAddress` to their [address alias](#address-aliasing) if either is a contract (determined by if the address has code during the call), providing a path for the parent chain contract to recover funds. A power-user may bypass these sanity-check measures via the `Inbox`'s `unsafeCreateRetryableTicket` method; as the method's name desperately attempts to warn you, only a user who knows what they are doing should access it. +Only one successful redemption can occur per ticket. Multiple failed redemption attempts will each produce a receipt until one succeeds. ## Token bridging -We can build **customized feature messaging** (for example, token bridging) using the messaging systems described in previous sections. In particular, **retryable tickets** power Arbitrum's **canonical token bridge**, which Offchain Labs developed. By leveraging retryable tickets under the hood, this token bridge provides a seamless user experience for transferring assets from a parent-to-child chain. An overview of how the system works is below: - -### `ERC-20` token bridging - -The Arbitrum protocol technically has no native notion of any token standards and gives no built-in advantage or special recognition to any particular token bridge. In this page, we describe the "canonical bridge", which was implemented by Offchain Labs and should be the primary bridge most users and applications use; it is (effectively) a decentralized app with contracts on both parent and child chains that leverages Arbitrum's [cross-chain message passing system](/build-decentralized-apps/04-cross-chain-messaging.mdx) to achieve basic desired token-bridging functionality. We recommend that you use it! - -### Design rationale - -In our token bridge design, we use the term "gateway" as per [this proposal](https://ethereum-magicians.org/t/outlining-a-standard-interface-for-cross-domain-erc20-transfers/6151); i.e., one of a pair of contracts on two different domains (i.e., Ethereum and an Arbitrum One chain), used to facilitate cross-domain asset transfers. - -We will now outline some core goals that motivated the design of our bridging system. - -#### Custom gateway functionality - -For many `ERC-20` tokens, "standard" bridging functionality is sufficient, which entails the following: a token contract on the parent chain (i.e., Ethereum) is associated with a "paired" token contract on the child chain (i.e., Arbitrum). - -Depositing a token entails escrowing some of the tokens in a parent chain bridge contract and minting the same amount of the paired token contract on a child chain. On the child chain, the paired contract behaves much like a standard `ERC-20` token contract. Withdrawing entails burning some amount of the token in the child chain contract, which is claimable from the parent chain bridge contract at a later time. - -Many tokens, however, require custom gateway systems, the possibilities which are hard to generalize: - -- Tokens that accrue interest to their holders must ensure that the interest is dispersed properly across layers, and doesn't simply accrue to the bridge contracts. -- Our cross-domain `WETH` implementations require wrapping and unwrapping tokens as they move across layers. - -Thus, our bridge architecture must allow the standard deposit and withdrawal functionalities, and new, custom gateways can be added dynamically over time. - -#### Canonical child chain representation per parent chain token contract - -Having multiple custom gateways is well and good. Still, we also want to avoid a situation in which a single parent chain token that uses our bridging system can be represented at multiple addresses/contracts on the child chain, as this adds significant friction and confusion for users and developers. Thus, we need a way to track which parent chain token uses which gateway and, in turn, to have a canonical address oracle that maps the token addresses across the parent and child chain domains. - -### Canonical token bridge implementation - -With this in mind, we provide an overview of our token-bridging architecture. - -Our architecture consists of three types of contracts: - -1. **Asset contracts**: These are the token contracts, i.e., an `ERC-20` on a parent chain and its counterpart on the child chain. -2. **Gateways**: Pairs of contracts (one on the parent and one on the child chain) implementing a particular type of cross-chain asset bridging. -3. **Routers**: Exactly two contracts (one on parent and one on child chain) route each asset to its designated gateway. - - - -All parent-to-child token transfers initiate via the router contract on the parent chain, the `L1GatewayRouter` contract. `L1GatewayRouter` forwards the token's deposit call to the appropriate gateway contract on the parent chain, the `L1ArbitrumGateway` contract. `L1GatewayRouter` is responsible for mapping the parent chain token addresses to L1Gateway contracts, thus acting as a parent/child address oracle and ensuring each token corresponds to only one gateway. The `L1ArbitrumGateway` then communicates to its counterpart gateway contract on the child chain, the `L2ArbitrumGateway` contract (typically/expectedly via [retryable tickets](#retryable-tickets)). - - - -For any given gateway pairing, we require that calls initiate through the corresponding router (`L1GatewayRouter`) and that the gateways conform to the [TokenGateway](https://github.com/OffchainLabs/token-bridge-contracts/blob/main/contracts/tokenbridge/libraries/gateway/TokenGateway.sol) interfaces; the `TokenGateway` interfaces should be flexible and extensible enough to support any bridging functionality a particular token may require. - -### The standard `ERC-20` gateway - -Any `ERC-20` token on a parent chain not registered to a gateway can be permissionlessly bridged through the `StandardERC20Gateway` by default. - -You can use the [bridge UI](https://bridge.arbitrum.io/) or follow the instructions in [How to bridge tokens via Arbitrum's standard `ERC-20` gateway](/build-decentralized-apps/token-bridging/bridge-tokens-programmatically/02-how-to-bridge-tokens-standard.mdx) to bridge a token to a child chain via this gateway. - -#### Example: Standard `Arb-ERC-20` deposit and withdraw - -To help illustrate what this all looks like in practice, let's go through the steps of what depositing and withdrawing `SomeERC20Token` via our standard `ERC-20` gateway looks like. We assume that `SomeERC20Token` has already been registered in the `L1GatewayRouter` to use the standard `ERC-20` gateway. - -#### Deposits - -1. A user calls `L1GatewayRouter.outboundTransferCustomRefund` (with `SomeERC20Token`'s parent chain address as an argument). - -:::warning - -Please keep in mind that some older custom gateways might not have `outboundTransferCustomRefund` implemented, and `L1GatewayRouter.outboundTransferCustomRefund` does not fall to `outboundTransfer`. In those cases, please use the function `L1GatewayRouter.outboundTransfer`. - -::: - -2. `L1GatewayRouter` looks up `SomeERC20Token`'s gateway and finds its standard `ERC-20` gateway (the `L1ERC20Gateway` contract). - -3. `L1GatewayRouter` calls `L1ERC20Gateway.outboundTransferCustomRefund`, forwarding the appropriate parameters. - -4. `L1ERC20Gateway` escrows the tokens sent and creates a retryable ticket to trigger `L2ERC20Gateway`'s `finalizeInboundTransfer` method on the child chain. - -5. `L2ERC20Gateway.finalizeInboundTransfer` mints the appropriate token amount at the `arbSomeERC20Token` contract on the child chain. - -Note that `arbSomeERC20Token` is an instance of [`StandardArbERC20`](https://github.com/OffchainLabs/token-bridge-contracts/blob/main/contracts/tokenbridge/arbitrum/StandardArbERC20.sol), which includes `bridgeMint` and `bridgeBurn` methods only callable by the `L2ERC20Gateway`. - -### The Arbitrum generic-custom gateway - -Just because a token has requirements beyond what the standard `ERC-20` gateway offers doesn't necessarily mean that a unique gateway needs to be tailor-made for the token in question. Our generic-custom gateway design is flexible enough to be suitable for most (but not necessarily all) custom fungible token needs. - -:::info As a general rule - -Suppose your custom token can increase its supply (i.e., mint) directly on the child chain, and you want the child-chain-minted tokens to be withdrawable back to the parent chain and recognized by the parent chain contract. In that case, a special gateway is likely required. Otherwise, the generic-custom gateway is likely the solution for you! - -::: - -Some examples of token features suitable for the generic-custom gateway: - -- A child chain token contract upgradable via a proxy -- A child chain token contract that includes address allowlisting/denylisting -- The deployer determines the address of the child chain token contract - -#### Setting up your token with the generic-custom gateway - -The following steps can set up your token for the generic-custom gateway. You can also find more detailed instructions on the page [How to bridge tokens via Arbitrum's generic-custom gateway](/build-decentralized-apps/token-bridging/bridge-tokens-programmatically/03-how-to-bridge-tokens-generic-custom.mdx). - -**Pre-requisites** - -Your token on the parent chain should conform to the [ICustomToken](https://github.com/OffchainLabs/token-bridge-contracts/blob/main/contracts/tokenbridge/ethereum/ICustomToken.sol) interface (see [`TestCustomTokenL1`](https://github.com/OffchainLabs/token-bridge-contracts/blob/main/contracts/tokenbridge/test/TestCustomTokenL1.sol) for an example implementation). Crucially, it must have an `isArbitrumEnabled` method in its interface. - -1. **Deploy your token on the child chain** - -- Your token should conform to the minimum [IArbToken](https://github.com/OffchainLabs/token-bridge-contracts/blob/main/contracts/tokenbridge/arbitrum/IArbToken.sol) interface, i.e., it should have `bridgeMint` and `bridgeBurn` methods only callable by the `L2CustomGateway` contract, and the address of its corresponding Ethereum token accessible via `l1Address`. For an example implementation, see [`L2GatewayToken`](https://github.com/OffchainLabs/token-bridge-contracts/blob/main/contracts/tokenbridge/libraries/L2GatewayToken.sol). -- Token compatibility with available tooling - - If you want your token to be compatible out of the box with all the tooling available (e.g., the [Arbitrum bridge]), we recommend that you keep the implementation of the IArbToken interface as close as possible to the [`L2GatewayToken`](https://github.com/OffchainLabs/token-bridge-contracts/blob/main/contracts/tokenbridge/libraries/L2GatewayToken.sol) implementation example. - - For example, if an allowance check is added to the `bridgeBurn()` function, the token will not be easily withdrawable through the Arbitrum Bridge UI, as the UI does not prompt an approval transaction of tokens by default (it expects the tokens to follow the recommended `L2GatewayToken` implementation). - -2. **Register your token on the parent chain to your token on the child chain via the `L1CustomGateway` contract** - -- Have your parent chain token's contract make an external call to `L1CustomGateway.registerTokenToL2`. Alternatively, this registration is submittable as a chain-owner registration via an [Arbitrum DAO](https://forum.arbitrum.foundation/) proposal. - -3. **Register your token on the parent chain to the `L1GatewayRouter`** - -- After your token's registration to the generic-custom gateway is complete, have your parent chain token's contract make an external call to `L1GatewayRouter.setGateway`; Alternatively, this registration is submittable as a chain-owner registration via an [Arbitrum DAO](https://forum.arbitrum.foundation/) proposal. - -:::info We are here to help - -If you have questions about your custom token needs, please reach out on our [Discord](https://discord.gg/arbitrum) server. - -::: - -### Other flavors of gateways - -Note that in the system described above, one pair of gateway contracts handles the bridging of many `ERC-20`'s; i.e., many `ERC-20`'s on parent chain's are each paired with their own `ERC-20`'s on child chain's via a single gateway contract pairing. Other gateways may bear different relations with the contracts they bridge. - -Take our wrapped Ether implementation; for example, a single `WETH` contract on a parent chain connects to a single `WETH` contract on a child chain. When transferring `WETH` from one domain to another, the parent/child gateway architecture unwraps the `WETH` on domain A, transfers the now-unwrapped Ether, and re-wraps it on domain B. This process ensures that `WETH` can behave on the child chain the way users are used to it behaving on the parent chain while ensuring that all `WETH` tokens are always fully collateralized on the layer in which they reside. - -No matter the complexity of a particular token's bridging needs, a gateway can, in principle, be created to accommodate it within our canonical bridging system. - -You can find an example of the implementation of a custom gateway on the page [How to bridge tokens via a custom gateway](/build-decentralized-apps/token-bridging/bridge-tokens-programmatically/04-how-to-bridge-tokens-custom-gateway.mdx). - -### Demos - -Our [How to bridge tokens](/build-decentralized-apps/token-bridging/bridge-tokens-programmatically/01-get-started.mdx) section provides examples of interacting with Arbitrum's token bridge via the [Arbitrum SDK](https://github.com/OffchainLabs/arbitrum-sdk). - -### "I've got a bridge to sell you," a word of caution - -Cross-chain bridging is an exciting design space. Alternative bridge designs can potentially offer faster withdrawals, interoperability with other chains, different trust assumptions with potentially valuable UX tradeoffs, and more. However, they can also potentially be completely insecure and/or outright scams. Users should treat other non-canonical bridge applications the same way they treat any application running on Arbitrum, exercise caution, and do their due diligence before entrusting them with their value. +Retryable tickets power Arbitrum's canonical token bridge. For the full token bridge architecture, including how `ETH` and `ERC-20` tokens are bridged between layers, see the [Token bridging overview](/how-arbitrum-works/deep-dives/token-bridging.mdx). diff --git a/docs/how-arbitrum-works/deep-dives/l2-to-l1-messaging.mdx b/docs/how-arbitrum-works/deep-dives/l2-to-l1-messaging.mdx index d2d17c466c..442d3284eb 100644 --- a/docs/how-arbitrum-works/deep-dives/l2-to-l1-messaging.mdx +++ b/docs/how-arbitrum-works/deep-dives/l2-to-l1-messaging.mdx @@ -9,6 +9,12 @@ content_type: concept import ImageZoom from '@site/src/components/ImageZoom'; +:::tip Looking for implementation guides? + +This document explains the protocol-level concepts of child-to-parent messaging. For practical, step-by-step instructions on implementing withdrawals and messaging, see [How to bridge to parent chain from child chain](/build-decentralized-apps/how-to-bridge-to-parent-chain.mdx). + +::: + Arbitrum's outbox system allows for arbitrary child chain to parent chain @@ -34,37 +40,11 @@ Messages from child to parent are included in Arbitrum's Rollup state and finali - Once the assertion is confirmed, anyone can execute the message on the parent chain by proving its inclusion. - Execution is possible using `Outbox.executeTransaction`, which takes a Merkle proof proving the message exists in the finalized assertion. -## Client flow (How to send and execute messages) - -### Sending a message from a child to parent chain - -A contract or user sends a message from the child chain by calling: - -```solidity -ArbSys(100).sendTxToL1(destAddress, calldata); -``` - -:::note - -`ArbSys(100)` references the ArbSys precompile at `0x0000000000000000000000000000000000000064`. - -::: - -### Executing the message on the parent chain +## Sending and executing messages -After the seven-day Challenge period, if the assertion is confirmed, anyone can execute the message on the parent chain. +Child-to-parent messages are sent via the `ArbSys` precompile's `sendTxToL1` method. After the ~7 day challenge period, messages can be executed on the parent chain using the `Outbox.executeTransaction` method with a Merkle proof obtained from the `NodeInterface` contract. -1. **Retrieve proof data**: - - Call the `constructOutboxProof` method from the `NodeInterface` contract to get proof data. - -:::note - -We refer to `NodeInterface` as a "virtual" contract; its methods are accessible via calls `0x00000000000000000000000000000000000000C8`, but it doesn't live onchain. It isn't a precompile but behaves like a precompile that can't receive calls from other contracts. This approach is a cute trick that lets us provide Arbitrum-specific data without implementing a custom RPC. - -::: - -2. **Execute on the parent chain**; - - Call `Outbox.executeTransaction` with the proof data to execute the message on the parent chain. +For step-by-step implementation instructions, see [How to bridge to parent chain](/build-decentralized-apps/how-to-bridge-to-parent-chain.mdx). ## Protocol design details @@ -92,44 +72,16 @@ Each message progresses through three primary states: | Waiting for finalization | The assertion containing the message is in the challenge period (~6.4 days) | | Confirmed and executable on the parent chain | If no fraud proof is submitted, the assertion is confirmed, and the message is available for execution in the outbox. | -## Withdrawing Ether - -Withdrawing Ether can be done using the [ArbSys precompile](/build-decentralized-apps/precompiles/02-reference.mdx#arbsys)'s `withdrawEth` method: - -```solidity -ArbSys(100).withdrawEth{ value: 2300000 }(destAddress) -``` - -Upon withdrawing, the Ether balance is burnt on the child chain side and will later be made available on the parent chain side. - -`ArbSys.withdrawEth` is a convenience function equivalent to calling `ArbSys.sendTxToL1` with empty `calldataForL1`. Like any other `sendTxToL1` call, it will require an additional call to `Outbox.executeTransaction` on the parent chain after the dispute period elapses for the user to finalize claiming their funds on the parent chain. Once the withdraw executes (removed from the outbox), the user's Ether balance will receive credit on the parent chain. - -The following diagram depicts the process that funds follow during a withdrawal operation. - - - -## `ERC-20` token withdrawal +## Asset withdrawals -Arbitrum has a canonical bridge design and architecture, which we explain in detail in the [Token bridging](/how-arbitrum-works/deep-dives/l1-to-l2-messaging.mdx#token-bridging) section of Bridging from a parent chain to a child chain article. This section will explain how the Arbitrum canonical bridge works on the child-to-parent chain token bridging. +### ETH withdrawals -1. Initiation of a child-to-parent chain transfer occurs via the `L2GatewayRouter` contract on the child chain. -2. The token's gateway contract (`L2ArbitrumGateway`) on the child chain gets called. -3. `L2ArbitrumGateway` finally communicates to its corresponding gateway contract on the parent chain using the `L1ArbitrumGateway` contract. +The `ArbSys` precompile provides a `withdrawEth` convenience method for withdrawing ETH from the child chain. This method burns the ETH on the child chain and creates a child-to-parent message. Like all child-to-parent messages, it requires execution on the parent chain after the challenge period via `Outbox.executeTransaction`. - +For implementation details, see [Withdrawing ETH](/build-decentralized-apps/how-to-bridge-to-parent-chain.mdx#withdrawing-eth). -For any given gateway pairing, calls have to be initiated through the corresponding router (`L2GatewayRouter`), and the gateways must conform to the `TokenGateway` interfaces; the `TokenGateway` interfaces should be flexible and extensible enough to support any bridging functionality a particular token may require. +### ERC-20 token withdrawals -Token withdrawals are the most common usage of child-to-parent chain messaging. The standard withdrawal happens as follows (for standard gateway): +Token withdrawals use Arbitrum's canonical bridge architecture, detailed in the [Token bridging overview](/how-arbitrum-works/deep-dives/token-bridging.mdx). The process flows through the `L2GatewayRouter` to the appropriate gateway contract (`L2ArbitrumGateway`), which burns the child chain tokens and creates a message to the parent chain gateway. After the challenge period, the message can be executed on the parent chain to release tokens from escrow. -1. On the child chain, a user calls `L2GatewayRouter.outBoundTransfer`, which calls `outBoundTransfer` on `arbSomeERC20Token`'s gateway (i.e., `L2ERC20Gateway`). -2. This call burns `arbSomeERC20Token` tokens and calls `ArbSys` with an encoded message to `L1ERC20Gateway.finalizeInboundTransfer`, which will eventually execute on the parent chain. -3. After the dispute window expires and the assertion with the user's transaction receives confirmation, a user can call `Outbox.executeTransaction`, which calls the encoded `L1ERC20Gateway.finalizeInboundTransfer` message, releasing the user's tokens from the `L1ERC20Gateway` contract's escrow. +For implementation details, see [Withdrawing ERC-20 tokens](/build-decentralized-apps/how-to-bridge-to-parent-chain.mdx#withdrawing-erc-20-tokens). diff --git a/docs/build-decentralized-apps/token-bridging/03-token-bridge-erc20.mdx b/docs/how-arbitrum-works/deep-dives/token-bridging.mdx similarity index 93% rename from docs/build-decentralized-apps/token-bridging/03-token-bridge-erc20.mdx rename to docs/how-arbitrum-works/deep-dives/token-bridging.mdx index cbaa2cfe8a..77af77ea80 100644 --- a/docs/build-decentralized-apps/token-bridging/03-token-bridge-erc20.mdx +++ b/docs/how-arbitrum-works/deep-dives/token-bridging.mdx @@ -1,11 +1,9 @@ --- -title: 'ERC-20 token bridging' -description: Describes how bridging ERC-20 tokens works on Arbitrum and the architecture of the token bridge +title: 'Token bridging' +description: Describes how token bridging works on Arbitrum and the architecture of the token bridge author: dzgoldman -user_story: As a developer, I want to understand how ERC-20 token bridging works on Arbitrum, and the architecture of the token bridge. +user_story: As a developer, I want to understand how token bridging works on Arbitrum, and the architecture of the token bridge. content_type: concept -sidebar_position: 3 -displayed_sidebar: buildAppsSidebar --- import ImageZoom from '@site/src/components/ImageZoom'; @@ -61,7 +59,7 @@ For any given gateway pairing, we require that calls initiate through the corres By default, any `ERC-20` token on a parent chain that isn't registered to a gateway can be bridged permissionlessly through the `StandardERC20Gateway`. -You can use the [bridge UI](https://bridge.arbitrum.io/) or follow the instructions in [How to bridge tokens via Arbitrum’s standard `ERC-20` gateway](/build-decentralized-apps/token-bridging/bridge-tokens-programmatically/02-how-to-bridge-tokens-standard.mdx) to bridge a token to a child chain via this gateway. +You can use the [bridge UI](https://bridge.arbitrum.io/) or follow the instructions in [How to bridge tokens via Arbitrum’s standard `ERC-20` gateway](/build-decentralized-apps/token-bridging/configure-token-bridging/setup-standard-gateway.mdx) to bridge a token to a child chain via this gateway. ### Example: Standard `Arb-ERC-20` deposit and withdraw @@ -103,7 +101,7 @@ Some examples of token features suitable for the generic-custom gateway: ### Setting up your token with the generic-custom gateway -Follow the steps below to set up your token for use with the generic-custom gateway. You can also find more detailed instructions on the page [How to bridge tokens via Arbitrum’s generic-custom gateway](/build-decentralized-apps/token-bridging/bridge-tokens-programmatically/03-how-to-bridge-tokens-generic-custom.mdx). +Follow the steps below to set up your token for use with the generic-custom gateway. You can also find more detailed instructions on the page [How to bridge tokens via Arbitrum’s generic-custom gateway](/build-decentralized-apps/token-bridging/configure-token-bridging/setup-generic-custom-gateway.mdx). **0. Have a parent chain token** @@ -113,7 +111,7 @@ Your token on the parent chain should conform to the [ICustomToken](https://gith Your token should conform to the minimum [`IArbToken`](https://github.com/OffchainLabs/token-bridge-contracts/blob/main/contracts/tokenbridge/arbitrum/IArbToken.sol) interface; i.e., it should have `bridgeMint` and `bridgeBurn` methods only callable by the `L2CustomGateway` contract, and the address of its corresponding Ethereum token accessible via `l1Address`. For an example implementation, see [`L2GatewayToken`](https://github.com/OffchainLabs/token-bridge-contracts/blob/main/contracts/tokenbridge/libraries/L2GatewayToken.sol). -import TokenCompatibilityPartial from './partials/_token-compatibility.mdx'; +import TokenCompatibilityPartial from '../../build-decentralized-apps/token-bridging/partials/_token-compatibility.mdx'; @@ -139,11 +137,11 @@ Take our wrapped Ether implementation for example: here, a single `WETH` contrac Regardless of a token's complexity in bridging needs, it is possible to create a gateway to accommodate it within our canonical bridging system. -You can find an example of implementation of a custom gateway in the page [How to bridge tokens via a custom gateway](/build-decentralized-apps/token-bridging/bridge-tokens-programmatically/04-how-to-bridge-tokens-custom-gateway.mdx). +You can find an example of implementation of a custom gateway in the page [How to bridge tokens via a custom gateway](/build-decentralized-apps/token-bridging/configure-token-bridging/setup-custom-gateway.mdx). ## Demos -Our [How to bridge tokens](/build-decentralized-apps/token-bridging/bridge-tokens-programmatically/01-get-started.mdx) section provides an example of interacting with Arbitrum's token bridge via the [Arbitrum SDK](https://github.com/OffchainLabs/arbitrum-sdk). +Our [How to bridge tokens](/build-decentralized-apps/token-bridging/get-started.mdx) section provides an example of interacting with Arbitrum's token bridge via the [Arbitrum SDK](https://github.com/OffchainLabs/arbitrum-sdk). ## A word of caution on bridges (aka, "I've got a bridge to sell you") diff --git a/docs/how-arbitrum-works/reference/arbos-reference.mdx b/docs/how-arbitrum-works/reference/arbos-reference.mdx new file mode 100644 index 0000000000..a14ba30fdc --- /dev/null +++ b/docs/how-arbitrum-works/reference/arbos-reference.mdx @@ -0,0 +1,127 @@ +--- +title: 'ArbOS technical reference' +description: 'Technical reference for ArbOS internals: precompile architecture, state storage, child chain pricing state, and Stylus execution details.' +author: petevielhaber +sme: mehdi salehi +user_story: As a current or prospective Arbitrum user, I need learn more about Nitros design. +content_type: reference +--- + +This page provides a technical reference for ArbOS internals. For a conceptual overview of ArbOS and its role in the Arbitrum stack, see [ArbOS](/how-arbitrum-works/deep-dives/arbos.mdx). + +## How precompiles work + +A precompile consists of a Solidity interface in `contracts/src/precompiles/` and a corresponding Golang implementation in `precompiles/`. Using Geth's ABI generator, `solgen/gen.go` generates `solgen/go/precompilesgen/precompilesgen.go`, which collects the ABI data of the precompiles. The [runtime installer](https://github.com/OffchainLabs/nitro/blob/bc6b52daf7232af2ca2fec3f54a5b546f1196c45/precompiles/precompile.go#L379) uses this generated file to check the type safety of each precompile's implementer. + +[The installer](https://github.com/OffchainLabs/nitro/blob/bc6b52daf7232af2ca2fec3f54a5b546f1196c45/precompiles/precompile.go#L379) uses runtime reflection to ensure each implementer has all the right methods and signatures. This reflection includes restricting access to stateful objects, such as the EVM and `statedb`, based on their declared purity. Additionally, the installer verifies and populates event function pointers, enabling each precompile to emit logs and determine its gas cost. Additional configurations, such as restricting a precompile's methods to be callable only by the chain owner, are possible by adding precompile wrappers like `ownerOnly` and `debugOnly` to their [installation entry](https://github.com/OffchainLabs/nitro/blob/bc6b52daf7232af2ca2fec3f54a5b546f1196c45/precompiles/precompile.go#L403). + +Completion of calling, dispatching, and recording of precompile methods occurs via runtime reflection, which avoids any human error that manually parsing and writing bytes could introduce, and uses Geth's stable APIs for [packing and unpacking](https://github.com/OffchainLabs/nitro/blob/bc6b52daf7232af2ca2fec3f54a5b546f1196c45/precompiles/precompile.go#L438) values. + +Each time a transaction calls a method of a child chain-specific precompile, a [`call context`](https://github.com/OffchainLabs/nitro/blob/f11ba39cf91ee2cbf07d67d0e6c38015d94e704/precompiles/context.go#L26) gets created to track and record the gas burnt. For convenience, it also provides access to the public fields of the underlying [`TxProcessor`](https://github.com/OffchainLabs/nitro/blob/8e786ec6d1ac3862be85e0c9b5ac79cbd883791c/arbos/tx_processor.go#L38). Because sub-transactions could revert without updates to this struct, the `TxProcessor` only makes public what is safe, such as the amount of parent chain calldata paid by the top-level transaction. + +For a complete list of precompiles, refer to the [precompile references](/build-decentralized-apps/precompiles/02-reference.mdx). + +## ArbOS state + +ArbOS's state is viewed and modified via [`ArbosState`](https://github.com/OffchainLabs/nitro/blob/fa36a0f138b8a7e684194f9840315d80c390f324/arbos/arbosState/arbosstate.go#L36) objects, which provide convenient abstractions for working with the underlying data of its [`backingStorage`](https://github.com/OffchainLabs/nitro/blob/fa36a0f138b8a7e684194f9840315d80c390f324/arbos/storage/storage.go#L51). The backing storage's [keyed subspace strategy](https://github.com/OffchainLabs/nitro/blob/fa36a0f138b8a7e684194f9840315d80c390f324/arbos/storage/storage.go#L21) makes [`ArbosState`](https://github.com/OffchainLabs/nitro/blob/fa36a0f138b8a7e684194f9840315d80c390f324/arbos/arbosState/arbosstate.go#L36)'s convenient getters and setters possible, minimizing the need to work directly with the specific keys and values of the underlying storage's [`stateDB`](https://github.com/OffchainLabs/go-ethereum/blob/0ba62aab54fd7d6f1570a235f4e3a877db9b2bd0/core/state/statedb.go#L66). + +Because two [`ArbosState`](https://github.com/OffchainLabs/nitro/blob/fa36a0f138b8a7e684194f9840315d80c390f324/arbos/arbosState/arbosstate.go#L36) objects with the same [`backingStorage`](https://github.com/OffchainLabs/nitro/blob/fa36a0f138b8a7e684194f9840315d80c390f324/arbos/storage/storage.go#L51) contain and mutate the same underlying state, different `ArbosState` objects can provide different views of ArbOS's contents. [`Burner`](https://github.com/OffchainLabs/nitro/blob/fa36a0f138b8a7e684194f9840315d80c390f324/arbos/burn/burn.go#L11) objects, which track gas usage while working with the `ArbosState`, provide the internal mechanism. Some are read-only, causing transactions to revert with `vm.ErrWriteProtection` when a mutating request is issued. Others demand that the caller have elevated privileges. Meanwhile, others dynamically charge users when doing stateful work. This view is chosen for safety when [`OpenArbosState()`](https://github.com/OffchainLabs/nitro/blob/fa36a0f138b8a7e684194f9840315d80c390f324/arbos/arbosState/arbosstate.go#L57) creates the object and may never change. + +[`arbosVersion`](https://github.com/OffchainLabs/nitro/blob/fa36a0f138b8a7e684194f9840315d80c390f324/arbos/arbosState/arbosstate.go#L37), [`updgradeVersion`](https://github.com/OffchainLabs/nitro/blob/fa36a0f138b8a7e684194f9840315d80c390f324/arbos/arbosState/arbosstate.go#L38) and [`upgradeTimestamp`](https://github.com/OffchainLabs/nitro/blob/fa36a0f138b8a7e684194f9840315d80c390f324/arbos/arbosState/arbosstate.go#L39) +ArbOS upgrades are scheduled to happen [when finalizing the first block](https://github.com/OffchainLabs/nitro/blob/fa36a0f138b8a7e684194f9840315d80c390f324/arbos/block_processor.go#L350) after the `upgradeTimestamp`. + +Most of ArbOS's state exists to facilitate its [precompiles](/build-decentralized-apps/precompiles/02-reference.mdx). The parts that aren't are detailed below. + +### [`blockhashes`](https://github.com/OffchainLabs/nitro/blob/fa36a0f138b8a7e684194f9840315d80c390f324/arbos/blockhash/blockhash.go#L15) + +This component maintains the last 256 parent chain block hashes in a circular buffer. This component allows the [`TxProcessor`](https://github.com/OffchainLabs/nitro/blob/8e786ec6d1ac3862be85e0c9b5ac79cbd883791c/arbos/tx_processor.go#L38) to implement the `BLOCKHASH` and `NUMBER` opcodes and supports the precompile methods that involve the Outbox. To avoid changing the ArbOS state outside of a transaction, blocks made from messages with a new parent chain–block number update this info during an [`InternalTxUpdateL1BlockNumber`](https://github.com/OffchainLabs/nitro/blob/fa36a0f138b8a7e684194f9840315d80c390f324/arbos/internal_tx.go#L24) [`ArbitrumInternalTx`](https://github.com/OffchainLabs/nitro/blob/8e786ec6d1ac3862be85e0c9b5ac79cbd883791c/arbos/internal_tx.go) included as the first transaction of the block. + +### [`l1PricingState`](https://github.com/OffchainLabs/nitro/blob/fa36a0f138b8a7e684194f9840315d80c390f324/arbos/l1pricing/l1pricing.go#L16) + +In addition to supporting the [`ArbAggregator precompile`](/build-decentralized-apps/precompiles/02-reference.mdx#arbaggregator), the parent chain pricing state provides tools for determining the parent chain component of a transaction's gas costs. This part of the state tracks the total amount of funds collected from transactions in parent chain gas fees and the funds spent by batch posters to post data breaches on the parent chain. + +Based on this information, ArbOS maintains a parent chain data fee, which is also tracked in this state and determines how much the parent chain fee will cost. ArbOS dynamically adjusts this value so that fees collected are approximately equal to batch posting costs. +For more details about parent chain pricing, see [Parent chain pricing](/how-arbitrum-works/reference/parent-chain-pricing.mdx). + +### [`l2PricingState`](https://github.com/OffchainLabs/nitro/blob/fa36a0f138b8a7e684194f9840315d80c390f324/arbos/l2pricing/l2pricing.go#L14) + +The child chain pricing state tracks child chain resource usage to determine a reasonable child chain gas price. This process considers various factors, including user demand, the state of Geth, and the computational gas target. The primary mechanism for doing so consists of a pair of pools, one larger than the other, that drain as child chain–specific resources are consumed and filled as time passes. Parent chain-specific resources, such as parent chain `calldata`, are not accounted for in the pools since they do not directly impact the computational workload of network actors. Instead, the design of the gas target mechanism regulates execution resources to ensure consistent system performance and synchronization. + +While much of this state is accessible through the [`ArbGasInfo`](/build-decentralized-apps/precompiles/02-reference.mdx#arbgasinfo) and [`ArbOwner`](/build-decentralized-apps/precompiles/02-reference.mdx#arbowner) precompiles, most changes are automatic and happen during [block production](https://github.com/OffchainLabs/nitro/blob/fa36a0f138b8a7e684194f9840315d80c390f324/arbos/block_processor.go#L77) and the [transaction hooks](/how-arbitrum-works/reference/geth.mdx#hooks). Each transaction in an incoming message removes the parent chain component of the gas it consumes from the pool. Afterward, the message's timestamp [informs the pricing mechanism](https://github.com/OffchainLabs/nitro/blob/fa36a0f138b8a7e684194f9840315d80c390f324/arbos/block_processor.go#L336) of the time passed as ArbOS [finalizes the block](https://github.com/OffchainLabs/nitro/blob/fa36a0f138b8a7e684194f9840315d80c390f324/arbos/block_processor.go#L350). + +ArbOS's larger gas pool [determines](https://github.com/OffchainLabs/nitro/blob/2ba6d1aa45abcc46c28f3d4f560691ce5a396af8/arbos/l2pricing/pools.go#L98) the per-block gas limit, setting a dynamic [upper limit](https://github.com/OffchainLabs/nitro/blob/2ba6d1aa45abcc46c28f3d4f560691ce5a396af8/arbos/block_processor.go#L146) on the amount of compute gas a child chain block may have. This limit is always enforced, though it's done in the [`GasChargingHook`](/how-arbitrum-works/reference/geth.mdx#gascharginghook) for the first transaction to avoid sharp decreases in the parent chain gas price from over-inflating the compute component purchased to above the gas limit. Enforcing this improves UX by allowing the first transaction to succeed rather than requiring a resubmission. Because the first transaction reduces the space left in the block, subsequent transactions do not use this strategy and may fail due to compute-component inflation. This space is acceptable because such transactions occur only when the system is under heavy load. The result is that the user's transaction is dropped without charges since the state transition fails early. Those trusting the Sequencer can rely on the automatic resubmission of a transaction in such a scenario. + +We need a per-block gas limit because arbitrator WAVM execution is much slower than native transaction execution. This limit means there can only be so much gas, roughly translating to wall-block time, in a child chain block. It also allows ArbOS to limit the size of blocks should demand continue to surge even as prices rise. + +ArbOS's per-block gas limit is distinct from Geth's block limit, which ArbOS [sets sufficiently high](https://github.com/OffchainLabs/nitro/blob/2ba6d1aa45abcc46c28f3d4f560691ce5a396af8/arbos/block_processor.go#L166) never to run out. This approach is safe since Geth's block limit exists to constrain the work done per block, which ArbOS already does via its own per-block gas limit. Though it'll never run out, a block's transactions use the [same Geth gas pool](https://github.com/OffchainLabs/nitro/blob/2ba6d1aa45abcc46c28f3d4f560691ce5a396af8/arbos/block_processor.go#L199) to maintain the invariant that the pool decreases monotonically after each transaction. Block headers [use the Geth block limit](https://github.com/OffchainLabs/nitro/blob/2ba6d1aa45abcc46c28f3d4f560691ce5a396af8/arbos/block_processor.go#L67) for internal consistency and to ensure gas estimation works. They are distinct from the [`gasLeft`](https://github.com/OffchainLabs/nitro/blob/2ba6d1aa45abcc46c28f3d4f560691ce5a396af8/arbos/block_processor.go#L146) variable, which ephemerally exists outside of the global state to keep child chain blocks from exceeding ArbOS's per-block gas limit and to deduct space where the state transition failed or [used negligible amounts](https://github.com/OffchainLabs/nitro/blob/faf55a1da8afcabb1f3c406b291e721bfde71a05/arbos/block_processor.go#L328) of compute gas. ArbOS does not need to persist `gasLeft` because its pool induces a revert, and transactions use the Geth block limit during EVM execution. + +## Stylus-specific differences + +This section details how Stylus integrates into the State Transition Function (STF), covering execution flow, messaging handling, caching, and interactions with ArbOS and Geth. + +### 1. Execution flow of a Stylus transaction + +When a Transaction interacts with a Stylus contract, its execution follows a distinct path compared to EVM transactions: + +- **Transaction submission and routing** + - The transaction is included in a child chain block by the Sequencer. + - Geth processes the transaction and determines its target contract. + - If the target is a Stylus contract, ArbOS routes execution to the WASM runtime instead of the EVM. +- **Stylus execution within ArbOS** + - ArbOS retrieves the Stylus program from its cache (`stylus/src/cache.rs`) or loads it from storage if not cached. + - The WebAssembly System Interface (Go-WASI) initializes a secure execution environment. + - The WASM module executes within ArbOS, processing instructions efficiently and calling host I/O functions. +- **Host I/O operations for blockchain state access** + - Stylus contracts do not use EVM opcodes. Instead, they interact with the blockchain through host I/O calls handled by ArbOS. + - These include storage access (`TLOAD` `TSTORE`), arithmetic operations (`MULMOD`, `ADDMOD`), and context retrieval (`GETCALLER`, `GETCALLVALUE`). + - ArbOS ensures these operations are efficient and compatible with Ethereum's state model. + - **State commitment and finalization** + - Once execution is complete, ArbOS finalizes storage changes and updates logs and receipts. + - Geth processes the final transaction result and commits it to the state tree. + +This process bypasses the EVM interpreter entirely, allowing Stylus contracts to execute significantly faster than their Solidity counterparts. + +### 2. Stylus caching and gas pricing + +- **Stylus gas pricing model** + Unlike standard EVM gas pricing, Stylus pricing follows a multi-dimensional cost model, incorporating: + - **Ink cost (memory and execution cost)** + - Measure in `Ink` units (Stylus's equivalent of computational gas). + - `Ink` pricing varies based on execution complexity, memory usage, and computation steps. + - Complex WASM operations consume more `Ink`, directly impacting execution costs. + - **Opcode pricing** + - WASM instructions are assigned individual execution costs similar to EVM opcodes. + - Heavy computation opcodes are priced higher. + - Cheap opcodes (e.g., simple arithmetic, bitwise operations) have minimal costs. + - **Host I/O pricing** + - Stylus introduces fine-grained pricing for different I/O calls: + - **Storage read/writes**: Priced based on access pattern and data size. + - **Precompile calls**: Stylus-specific precompiles have fixed execution costs. + - **External calls to EVM contracts**: Encapsulated within ArbOS transaction handling, with additional gas considerations. + +#### Stylus caching + +Stylus contracts leverage an advanced caching system to minimize execution overhead within ArbOS: + +- **LRU (Least Recently Used) caching**: Keeps the most recently accessed Stylus contracts in memory for fast execution. +- **Persistent long-term caching**: Caching for selected contracts may occur across blocks based on an economic auction model. +- **Init costs and execution pricing**: Instead of a flat gas cost, Stylus contracts have dynamic execution costs based on WASM complexity. ArbOS maintains pricing parameters (`initCost`, `cachedCost`) that are adjusted based on future WASM execution optimizations. + +### 3. Interaction with ArbOS and Geth + +| Execution Stage | Handled By | +| ---------------------- | --------------------------------------------------------- | +| Transaction submission | **Geth** (identifies target contract) | +| Stylus execution | **ArbOS** (switches to WASM runtime) | +| Host I/O calls | **ArbOS** (handles storage, call data, context retrieval) | +| State commitment | **Geth and ArbOS** (finalizes updates, commits to state) | + +### 4. Go-WASI and co-threads in Stylus execution + +ArbOS executes Stylus contracts using Go-WASI, a WASM-compatible runtime with custom optimizations for Arbitrum. Key features include: + +- **Memory management**: WASM modules execute in a sandboxed environment with strict memory allocation policies. +- **Co-threads for efficient execution**: Instead of traditional synchronous execution, Stylus employs co-threading, enabling lightweight task switching and parallelism where possible. +- **Deterministic execution**: Ensures that Stylus contracts remain fully deterministic and compatible with Ethereum's consensus model. + +These optimizations make Stylus an extremely efficient execution environment, capable of outperforming the EVM while maintaining security and compatibility with Ethereum's state model. diff --git a/docs/how-arbitrum-works/deep-dives/geth.mdx b/docs/how-arbitrum-works/reference/geth.mdx similarity index 99% rename from docs/how-arbitrum-works/deep-dives/geth.mdx rename to docs/how-arbitrum-works/reference/geth.mdx index 3e0258d9ef..a25bb4d415 100644 --- a/docs/how-arbitrum-works/deep-dives/geth.mdx +++ b/docs/how-arbitrum-works/reference/geth.mdx @@ -1,10 +1,10 @@ --- title: 'Geth at the core: modified Geth on Arbitrum Nitro' -description: 'Learn the fundamentals of Nitro, Arbitrum stack.' +description: 'Technical reference for Nitro Geth hooks, interfaces, transaction types, and chain parameters.' author: petevielhaber sme: Mehdi user_story: As a current or prospective Arbitrum user, I need learn more about Nitros design. -content_type: get-started +content_type: reference --- Nitro makes minimal modifications to Geth in hopes of not violating its assumptions. This section will explore the relationship between Geth and ArbOS, which consists of a series of hooks, interface implementations, and strategic re-appropriations of Geth's basic types. diff --git a/docs/how-arbitrum-works/deep-dives/parent-chain-pricing.mdx b/docs/how-arbitrum-works/reference/parent-chain-pricing.mdx similarity index 97% rename from docs/how-arbitrum-works/deep-dives/parent-chain-pricing.mdx rename to docs/how-arbitrum-works/reference/parent-chain-pricing.mdx index cae227e469..28d174cfc9 100644 --- a/docs/how-arbitrum-works/deep-dives/parent-chain-pricing.mdx +++ b/docs/how-arbitrum-works/reference/parent-chain-pricing.mdx @@ -1,10 +1,10 @@ --- -title: 'A gentle introduction' -description: 'Learn the fundamentals of Nitro, Arbitrum stack.' +title: 'Parent chain pricing' +description: 'Technical reference for the parent chain pricing algorithm, including cost apportionment, adaptive fee adjustments, and batch posting economics.' author: petevielhaber sme: Mehdi user_story: As a current or prospective Arbitrum user, I need learn more about Nitros design. -content_type: get-started +content_type: reference --- ## Parent chain pricing model diff --git a/docs/how-arbitrum-works/deep-dives/stf-inputs.mdx b/docs/how-arbitrum-works/reference/stf-inputs.mdx similarity index 97% rename from docs/how-arbitrum-works/deep-dives/stf-inputs.mdx rename to docs/how-arbitrum-works/reference/stf-inputs.mdx index 96160c03ae..b84a9fd32d 100644 --- a/docs/how-arbitrum-works/deep-dives/stf-inputs.mdx +++ b/docs/how-arbitrum-works/reference/stf-inputs.mdx @@ -1,6 +1,6 @@ --- title: 'Inputs to the State Transition Function' -description: 'Learn the fundamentals of Nitro, Arbitrum stack.' +description: 'Reference for the message types and input channels that feed the Arbitrum State Transition Function.' author: petevielhaber sme: Mehdi user_story: As a current or prospective Arbitrum user, I need learn more about Nitros design. diff --git a/docs/launch-arbitrum-chain/02-configure-your-chain/common/validation-and-security/arbitrum-chain-finality.mdx b/docs/launch-arbitrum-chain/02-configure-your-chain/common/validation-and-security/arbitrum-chain-finality.mdx index b269acb393..9a752b4cab 100644 --- a/docs/launch-arbitrum-chain/02-configure-your-chain/common/validation-and-security/arbitrum-chain-finality.mdx +++ b/docs/launch-arbitrum-chain/02-configure-your-chain/common/validation-and-security/arbitrum-chain-finality.mdx @@ -9,7 +9,7 @@ content_type: how-to ## Child chain transactions -Generally, transactions executed through the sequencer on Arbitrum chains [achieve finality](/how-arbitrum-works/deep-dives/transaction-lifecycle.mdx) equivalent to their parent chain once the relevant transaction data has been [posted in a batch](/how-arbitrum-works/01-inside-arbitrum-nitro.mdx). This means that transactions on Arbitrum chains are considered final in minutes. +Generally, transactions executed through the sequencer on Arbitrum chains [achieve finality](/build-decentralized-apps/transaction-lifecycle.mdx) equivalent to their parent chain once the relevant transaction data has been [posted in a batch](/how-arbitrum-works/01-inside-arbitrum-nitro.mdx). This means that transactions on Arbitrum chains are considered final in minutes. ## Parent chain → child chain transactions diff --git a/docs/launch-arbitrum-chain/03-deploy-an-arbitrum-chain/05-deploying-token-bridge.mdx b/docs/launch-arbitrum-chain/03-deploy-an-arbitrum-chain/05-deploying-token-bridge.mdx index 152749ced5..1dffb9ac02 100644 --- a/docs/launch-arbitrum-chain/03-deploy-an-arbitrum-chain/05-deploying-token-bridge.mdx +++ b/docs/launch-arbitrum-chain/03-deploy-an-arbitrum-chain/05-deploying-token-bridge.mdx @@ -21,7 +21,7 @@ Once you have deployed your Arbitrum chain and have a node running, you can depl Before reading this guide, we recommend: - Becoming familiar with the general process of creating new chains explained in [How to deploy an Arbitrum chain](/launch-arbitrum-chain/03-deploy-an-arbitrum-chain/02-deploying-an-arbitrum-chain.mdx) -- Learning about the canonical token bridge in the [Token bridging](/build-decentralized-apps/token-bridging/01-overview.mdx) section +- Learning about the canonical token bridge in the [Token bridging](/how-arbitrum-works/deep-dives/token-bridging.mdx) section ## Parameters used when deploying a token bridge @@ -219,7 +219,7 @@ const tokenBridgeContracts = await txReceipt.getTokenBridgeContracts({ That step only applies to ETH-based Arbitrum chains (i.e., not custom gas token chains). The canonical bridge design has a separate custom gateway for `WETH` to bridge it in and out of the Arbitrum chain. -You can find more info about `WETH` gateways in our ["other gateways flavors" documentation](/build-decentralized-apps/token-bridging/03-token-bridge-erc20.mdx#other-flavors-of-gateways). +You can find more info about `WETH` gateways in our ["other gateways flavors" documentation](/how-arbitrum-works/deep-dives/token-bridging.mdx#other-flavors-of-gateways). ::: diff --git a/docs/launch-arbitrum-chain/06-third-party-integrations/01-bridged-usdc-standard.md b/docs/launch-arbitrum-chain/06-third-party-integrations/01-bridged-usdc-standard.md index 8da7f9ae59..e3e851c96c 100644 --- a/docs/launch-arbitrum-chain/06-third-party-integrations/01-bridged-usdc-standard.md +++ b/docs/launch-arbitrum-chain/06-third-party-integrations/01-bridged-usdc-standard.md @@ -11,7 +11,7 @@ Circle’s [Bridged `USDC` Standard](https://www.circle.com/blog/bridged-usdc-st ## Why adopt the bridged `USDC` standard? -When `USDC` is bridged into an Arbitrum chain, the default path is to use the chain’s [canonical gateway contracts for `ERC-20`'s](/build-decentralized-apps/token-bridging/03-token-bridge-erc20.mdx). By way of example, when a user bridges `USDC` from Arbitrum One to an Arbitrum chain, their Arbitrum One `USDC` tokens are locked into the Arbitrum chain’s parent side bridge, and a representative `USDC` token is minted to the user’s address on the Arbitrum chain, via the child side bridge. +When `USDC` is bridged into an Arbitrum chain, the default path is to use the chain’s [canonical gateway contracts for `ERC-20`'s](/how-arbitrum-works/deep-dives/token-bridging.mdx). By way of example, when a user bridges `USDC` from Arbitrum One to an Arbitrum chain, their Arbitrum One `USDC` tokens are locked into the Arbitrum chain’s parent side bridge, and a representative `USDC` token is minted to the user’s address on the Arbitrum chain, via the child side bridge. The challenge with this user flow is two-fold: @@ -41,7 +41,7 @@ Other requirements: - It is assumed there is already a `USDC` token deployed and used on the parent chain. - Also, it is assumed that the standard Arbitrum chain ownership system is used, i.e., `UpgradeExecutor` is the owner of the `ownable` contracts, and there is an EOA or multi-sig that has the executor role on the `UpgradeExecutor`. -- Refer to the [token bridge overview page](/launch-arbitrum-chain/03-deploy-an-arbitrum-chain/05-deploying-token-bridge.mdx) for more information about the token bridge design and operational dynamics. You can learn more in our [overview of gateways operating models](/build-decentralized-apps/token-bridging/03-token-bridge-erc20.mdx#other-flavors-of-gateways). +- Refer to the [token bridge overview page](/launch-arbitrum-chain/03-deploy-an-arbitrum-chain/05-deploying-token-bridge.mdx) for more information about the token bridge design and operational dynamics. You can learn more in our [overview of gateways operating models](/how-arbitrum-works/deep-dives/token-bridging.mdx#other-flavors-of-gateways). ## Deployment steps diff --git a/docs/partials/_gentle-intro-partial.mdx b/docs/partials/_gentle-intro-partial.mdx index 11c5d88d33..b0bdff7d4e 100644 --- a/docs/partials/_gentle-intro-partial.mdx +++ b/docs/partials/_gentle-intro-partial.mdx @@ -63,7 +63,7 @@ For one, Arbitrum transactions are submitted on the L1 in batches; typically, a We really meant it, yes. Different layer 2 protocols emphasize and optimize for different things; Arbitrum was created with Ethereum compatibility as a top priority. This means users can use Arbitrum with all their favorite Ethereum wallets; developers can build and deploy contracts with all their favorite Ethereum libraries and tooling; in fact, most of the time, the experience of using Arbitrum will feel identical to that of using Ethereum (with the important exception of it being much cheaper and faster). -Much development went into achieving this level of Ethereum compatibility. But at its core: the Arbitrum itself uses a fork of [Geth](/how-arbitrum-works/deep-dives/geth.mdx) — the most widely used Ethereum implementation — with modifications to transform it into a trustless layer 2. This means most of the code running in Arbitrum is identical to the code running in Ethereum. We call this cutting-edge approach Nitro (developers can see the codebase [here](https://github.com/OffchainLabs/nitro)). +Much development went into achieving this level of Ethereum compatibility. But at its core: the Arbitrum itself uses a fork of [Geth](/how-arbitrum-works/reference/geth.mdx) — the most widely used Ethereum implementation — with modifications to transform it into a trustless layer 2. This means most of the code running in Arbitrum is identical to the code running in Ethereum. We call this cutting-edge approach Nitro (developers can see the codebase [here](https://github.com/OffchainLabs/nitro)). #### Q: So builders can do all the stuff they do on Ethereum on Arbitrum, nice! But can they do _more_? diff --git a/sidebars.js b/sidebars.js index 28c8dc0e57..cdfd4c37f7 100644 --- a/sidebars.js +++ b/sidebars.js @@ -1066,40 +1066,77 @@ const sidebars = { label: 'Child to Parent chain Messaging', id: 'how-arbitrum-works/deep-dives/l2-to-l1-messaging', }, + { + type: 'doc', + label: 'Sequencer', + id: 'how-arbitrum-works/deep-dives/sequencer', + }, + { + type: 'doc', + label: 'STF', + id: 'how-arbitrum-works/deep-dives/stf-gentle-intro', + }, + { + type: 'doc', + label: 'Gas and fees', + id: 'how-arbitrum-works/deep-dives/gas-and-fees', + }, + { + type: 'doc', + label: 'Token bridging', + id: 'how-arbitrum-works/deep-dives/token-bridging', + }, + ], + }, + { + type: 'category', + label: 'Reference', + items: [ + { + type: 'doc', + label: 'ArbOS technical reference', + id: 'how-arbitrum-works/reference/arbos-reference', + }, { type: 'doc', label: 'Geth', - id: 'how-arbitrum-works/deep-dives/geth', + id: 'how-arbitrum-works/reference/geth', }, { type: 'doc', label: 'Parent chain pricing', - id: 'how-arbitrum-works/deep-dives/parent-chain-pricing', + id: 'how-arbitrum-works/reference/parent-chain-pricing', }, { type: 'doc', - label: 'Sequencer', - id: 'how-arbitrum-works/deep-dives/sequencer', + label: 'STF inputs', + id: 'how-arbitrum-works/reference/stf-inputs', }, + ], + }, + { + type: 'category', + label: 'Reference', + items: [ { type: 'doc', - label: 'STF', - id: 'how-arbitrum-works/deep-dives/stf-gentle-intro', + label: 'ArbOS technical reference', + id: 'how-arbitrum-works/reference/arbos-reference', }, { type: 'doc', - label: 'STF inputs', - id: 'how-arbitrum-works/deep-dives/stf-inputs', + label: 'Geth', + id: 'how-arbitrum-works/reference/geth', }, { type: 'doc', - label: 'Transaction lifecycle', - id: 'how-arbitrum-works/deep-dives/transaction-lifecycle', + label: 'Parent chain pricing', + id: 'how-arbitrum-works/reference/parent-chain-pricing', }, { type: 'doc', - label: 'Gas and fees', - id: 'how-arbitrum-works/deep-dives/gas-and-fees', + label: 'STF inputs', + id: 'how-arbitrum-works/reference/stf-inputs', }, ], }, @@ -1226,6 +1263,11 @@ const sidebars = { label: 'Estimate gas', id: 'build-decentralized-apps/how-to-estimate-gas', }, + { + type: 'doc', + label: 'Transaction lifecycle', + id: 'build-decentralized-apps/transaction-lifecycle', + }, { type: 'doc', label: 'Chains and testnets', @@ -1236,6 +1278,16 @@ const sidebars = { label: 'Cross-chain messaging', id: 'build-decentralized-apps/cross-chain-messaging', }, + { + type: 'doc', + label: 'Bridge from parent chain', + id: 'build-decentralized-apps/how-to-bridge-from-parent-chain', + }, + { + type: 'doc', + label: 'Bridge to parent chain', + id: 'build-decentralized-apps/how-to-bridge-to-parent-chain', + }, { type: 'doc', id: 'build-decentralized-apps/custom-gas-token-sdk', @@ -1313,42 +1365,38 @@ const sidebars = { items: [ { type: 'doc', - label: 'Overview', - id: 'build-decentralized-apps/token-bridging/overview', + label: 'Get started', + id: 'build-decentralized-apps/token-bridging/get-started', }, { type: 'doc', - label: 'ETH bridging', - id: 'build-decentralized-apps/token-bridging/token-bridge-ether', + label: 'Deposit tokens', + id: 'build-decentralized-apps/token-bridging/deposit-tokens', }, { type: 'doc', - label: 'ERC-20 token bridging', - id: 'build-decentralized-apps/token-bridging/token-bridge-erc20', + label: 'Withdraw tokens', + id: 'build-decentralized-apps/token-bridging/withdraw-tokens', }, { type: 'category', - label: 'Bridge tokens programmatically', + label: 'Configure token bridging', + collapsed: true, items: [ { type: 'doc', - label: 'Get started', - id: 'build-decentralized-apps/token-bridging/bridge-tokens-programmatically/get-started', - }, - { - type: 'doc', - label: 'Use the standard gateway', - id: 'build-decentralized-apps/token-bridging/bridge-tokens-programmatically/how-to-bridge-tokens-standard', + label: 'Standard gateway', + id: 'build-decentralized-apps/token-bridging/configure-token-bridging/setup-standard-gateway', }, { type: 'doc', - label: 'Use the generic-custom gateway', - id: 'build-decentralized-apps/token-bridging/bridge-tokens-programmatically/how-to-bridge-tokens-generic-custom', + label: 'Generic-custom gateway', + id: 'build-decentralized-apps/token-bridging/configure-token-bridging/setup-generic-custom-gateway', }, { type: 'doc', - label: 'Use the custom gateway', - id: 'build-decentralized-apps/token-bridging/bridge-tokens-programmatically/how-to-bridge-tokens-custom-gateway', + label: 'Custom gateway', + id: 'build-decentralized-apps/token-bridging/configure-token-bridging/setup-custom-gateway', }, ], }, diff --git a/vercel.json b/vercel.json index a73c247d55..584ad2a6b1 100644 --- a/vercel.json +++ b/vercel.json @@ -10,6 +10,21 @@ "destination": "/get-started/overview", "permanent": false }, + { + "source": "/(how-arbitrum-works/deep-dives/geth/?)", + "destination": "/how-arbitrum-works/reference/geth", + "permanent": false + }, + { + "source": "/(how-arbitrum-works/deep-dives/parent-chain-pricing/?)", + "destination": "/how-arbitrum-works/reference/parent-chain-pricing", + "permanent": false + }, + { + "source": "/(how-arbitrum-works/deep-dives/stf-inputs/?)", + "destination": "/how-arbitrum-works/reference/stf-inputs", + "permanent": false + }, { "source": "/(anytrust/?)", "destination": "/how-arbitrum-works/deep-dives/anytrust-protocol", @@ -37,7 +52,7 @@ }, { "source": "/(arbos/?)", - "destination": "/how-arbitrum-works/deep-dives/geth", + "destination": "/how-arbitrum-works/reference/geth", "permanent": false }, { @@ -57,12 +72,12 @@ }, { "source": "/(arbos/geth/?)", - "destination": "/how-arbitrum-works/deep-dives/geth", + "destination": "/how-arbitrum-works/reference/geth", "permanent": false }, { "source": "/(arbos/introduction/?)", - "destination": "/how-arbitrum-works/deep-dives/geth", + "destination": "/how-arbitrum-works/reference/geth", "permanent": false }, { @@ -97,7 +112,7 @@ }, { "source": "/(arbos_formats/?)", - "destination": "/how-arbitrum-works/deep-dives/geth", + "destination": "/how-arbitrum-works/reference/geth", "permanent": false }, { @@ -112,7 +127,7 @@ }, { "source": "/(asset-bridging/?)", - "destination": "/build-decentralized-apps/token-bridging/token-bridge-erc20", + "destination": "/how-arbitrum-works/token-bridging/erc20-bridging", "permanent": false }, { @@ -147,7 +162,7 @@ }, { "source": "/(bridging_assets/?)", - "destination": "/build-decentralized-apps/token-bridging/token-bridge-erc20", + "destination": "/how-arbitrum-works/token-bridging/erc20-bridging", "permanent": false }, { @@ -202,32 +217,147 @@ }, { "source": "/(devs-how-tos/bridge-tokens/gentle-introduction-bridge/?)", - "destination": "/build-decentralized-apps/token-bridging/overview", + "destination": "/how-arbitrum-works/token-bridging/overview", "permanent": false }, { "source": "/(devs-how-tos/bridge-tokens/how-to-bridge-tokens-custom-gateway/?)", - "destination": "/build-decentralized-apps/token-bridging/bridge-tokens-programmatically/how-to-bridge-tokens-custom-gateway", + "destination": "/build-decentralized-apps/token-bridging/how-to-bridge-tokens-custom-gateway", "permanent": false }, { "source": "/(devs-how-tos/bridge-tokens/how-to-bridge-tokens-custom-generic/?)", - "destination": "/build-decentralized-apps/token-bridging/bridge-tokens-programmatically/how-to-bridge-tokens-generic-custom", + "destination": "/build-decentralized-apps/token-bridging/how-to-bridge-tokens-generic-custom", "permanent": false }, { "source": "/(devs-how-tos/bridge-tokens/how-to-bridge-tokens-generic-custom/?)", - "destination": "/build-decentralized-apps/token-bridging/bridge-tokens-programmatically/how-to-bridge-tokens-generic-custom", + "destination": "/build-decentralized-apps/token-bridging/how-to-bridge-tokens-generic-custom", "permanent": false }, { "source": "/(devs-how-tos/bridge-tokens/how-to-bridge-tokens-overview/?)", - "destination": "/build-decentralized-apps/token-bridging/bridge-tokens-programmatically/get-started", + "destination": "/build-decentralized-apps/token-bridging/get-started", "permanent": false }, { "source": "/(devs-how-tos/bridge-tokens/how-to-bridge-tokens-standard/?)", - "destination": "/build-decentralized-apps/token-bridging/bridge-tokens-programmatically/how-to-bridge-tokens-standard", + "destination": "/build-decentralized-apps/token-bridging/how-to-bridge-tokens-standard", + "permanent": false + }, + { + "source": "/(build-decentralized-apps/token-bridging/bridge-tokens-programmatically/01-get-started/?)", + "destination": "/build-decentralized-apps/token-bridging/get-started", + "permanent": false + }, + { + "source": "/(build-decentralized-apps/token-bridging/bridge-tokens-programmatically/get-started/?)", + "destination": "/build-decentralized-apps/token-bridging/get-started", + "permanent": false + }, + { + "source": "/(build-decentralized-apps/token-bridging/bridge-tokens-programmatically/02-how-to-bridge-tokens-standard/?)", + "destination": "/build-decentralized-apps/token-bridging/how-to-bridge-tokens-standard", + "permanent": false + }, + { + "source": "/(build-decentralized-apps/token-bridging/bridge-tokens-programmatically/how-to-bridge-tokens-standard/?)", + "destination": "/build-decentralized-apps/token-bridging/how-to-bridge-tokens-standard", + "permanent": false + }, + { + "source": "/(build-decentralized-apps/token-bridging/bridge-tokens-programmatically/03-how-to-bridge-tokens-generic-custom/?)", + "destination": "/build-decentralized-apps/token-bridging/how-to-bridge-tokens-generic-custom", + "permanent": false + }, + { + "source": "/(build-decentralized-apps/token-bridging/bridge-tokens-programmatically/how-to-bridge-tokens-generic-custom/?)", + "destination": "/build-decentralized-apps/token-bridging/how-to-bridge-tokens-generic-custom", + "permanent": false + }, + { + "source": "/(build-decentralized-apps/token-bridging/bridge-tokens-programmatically/04-how-to-bridge-tokens-custom-gateway/?)", + "destination": "/build-decentralized-apps/token-bridging/how-to-bridge-tokens-custom-gateway", + "permanent": false + }, + { + "source": "/(build-decentralized-apps/token-bridging/bridge-tokens-programmatically/how-to-bridge-tokens-custom-gateway/?)", + "destination": "/build-decentralized-apps/token-bridging/how-to-bridge-tokens-custom-gateway", + "permanent": false + }, + { + "source": "/(build-decentralized-apps/token-bridging/01-get-started/?)", + "destination": "/build-decentralized-apps/token-bridging/get-started", + "permanent": false + }, + { + "source": "/(build-decentralized-apps/token-bridging/how-to-bridge-tokens-standard/?)", + "destination": "/build-decentralized-apps/token-bridging/configure-token-bridging/setup-standard-gateway", + "permanent": false + }, + { + "source": "/(build-decentralized-apps/token-bridging/02-how-to-bridge-tokens-standard/?)", + "destination": "/build-decentralized-apps/token-bridging/configure-token-bridging/setup-standard-gateway", + "permanent": false + }, + { + "source": "/(build-decentralized-apps/token-bridging/how-to-bridge-tokens-generic-custom/?)", + "destination": "/build-decentralized-apps/token-bridging/configure-token-bridging/setup-generic-custom-gateway", + "permanent": false + }, + { + "source": "/(build-decentralized-apps/token-bridging/03-how-to-bridge-tokens-generic-custom/?)", + "destination": "/build-decentralized-apps/token-bridging/configure-token-bridging/setup-generic-custom-gateway", + "permanent": false + }, + { + "source": "/(build-decentralized-apps/token-bridging/how-to-bridge-tokens-custom-gateway/?)", + "destination": "/build-decentralized-apps/token-bridging/configure-token-bridging/setup-custom-gateway", + "permanent": false + }, + { + "source": "/(build-decentralized-apps/token-bridging/04-how-to-bridge-tokens-custom-gateway/?)", + "destination": "/build-decentralized-apps/token-bridging/configure-token-bridging/setup-custom-gateway", + "permanent": false + }, + { + "source": "/(build-decentralized-apps/token-bridging/01-overview/?)", + "destination": "/how-arbitrum-works/token-bridging/overview", + "permanent": false + }, + { + "source": "/(build-decentralized-apps/token-bridging/overview/?)", + "destination": "/how-arbitrum-works/token-bridging/overview", + "permanent": false + }, + { + "source": "/(build-decentralized-apps/token-bridging/02-token-bridge-ether/?)", + "destination": "/build-decentralized-apps/how-to-bridge-from-parent-chain", + "permanent": false + }, + { + "source": "/(build-decentralized-apps/token-bridging/token-bridge-ether/?)", + "destination": "/build-decentralized-apps/how-to-bridge-from-parent-chain", + "permanent": false + }, + { + "source": "/(how-arbitrum-works/token-bridging/eth-bridging/?)", + "destination": "/build-decentralized-apps/how-to-bridge-from-parent-chain", + "permanent": false + }, + { + "source": "/(build-decentralized-apps/token-bridging/03-token-bridge-erc20/?)", + "destination": "/how-arbitrum-works/token-bridging/erc20-bridging", + "permanent": false + }, + { + "source": "/(build-decentralized-apps/token-bridging/token-bridge-erc20/?)", + "destination": "/how-arbitrum-works/token-bridging/erc20-bridging", + "permanent": false + }, + { + "source": "/(how-arbitrum-works/deep-dives/transaction-lifecycle/?)", + "destination": "/build-decentralized-apps/transaction-lifecycle", "permanent": false }, { @@ -372,17 +502,17 @@ }, { "source": "/(for-devs/concepts/token-bridge/token-bridge-erc20/?)", - "destination": "/build-decentralized-apps/token-bridging/token-bridge-erc20", + "destination": "/how-arbitrum-works/token-bridging/erc20-bridging", "permanent": false }, { "source": "/(for-devs/concepts/token-bridge/token-bridge-ether/?)", - "destination": "/build-decentralized-apps/token-bridging/token-bridge-ether", + "destination": "/build-decentralized-apps/how-to-bridge-from-parent-chain", "permanent": false }, { "source": "/(for-devs/concepts/token-bridge/token-bridge-overview/?)", - "destination": "/build-decentralized-apps/token-bridging/overview", + "destination": "/how-arbitrum-works/token-bridging/overview", "permanent": false }, { @@ -527,7 +657,7 @@ }, { "source": "/(how-arbitrum-works/arbos/geth/?)", - "destination": "/how-arbitrum-works/deep-dives/geth", + "destination": "/how-arbitrum-works/reference/geth", "permanent": false }, { @@ -657,7 +787,7 @@ }, { "source": "/(how-arbitrum-works/state-transition-function/modified-geth-on-arbitrum/?)", - "destination": "/how-arbitrum-works/deep-dives/geth", + "destination": "/how-arbitrum-works/reference/geth", "permanent": false }, { @@ -667,7 +797,7 @@ }, { "source": "/(how-arbitrum-works/state-transition-function/stf-inputs/?)", - "destination": "/how-arbitrum-works/deep-dives/stf-inputs", + "destination": "/how-arbitrum-works/reference/stf-inputs", "permanent": false }, {