diff --git a/docs/build-decentralized-apps/01-quickstart-solidity-remix.mdx b/docs/build-decentralized-apps/01-quickstart-solidity-remix.mdx
index 2ea5737f97..d6558844ad 100644
--- a/docs/build-decentralized-apps/01-quickstart-solidity-remix.mdx
+++ b/docs/build-decentralized-apps/01-quickstart-solidity-remix.mdx
@@ -5,7 +5,7 @@ author: symbolpunk
user_story: As a web2 developer, I want to onboard into Arbitrum by building and deploying my first smart contract, and knowing how to build a web widget interacting with it.
content_type: quickstart
slug: /build-decentralized-apps/quickstart-solidity-remix
-displayed_sidebar: buildAppsSidebar
+displayed_sidebar: buildSoliditySidebar
---
import { VanillaAdmonition } from '@site/src/components/VanillaAdmonition/';
diff --git a/docs/build-decentralized-apps/02-how-to-estimate-gas.mdx b/docs/build-decentralized-apps/02-how-to-estimate-gas.mdx
index c1f4115188..288c4513d8 100644
--- a/docs/build-decentralized-apps/02-how-to-estimate-gas.mdx
+++ b/docs/build-decentralized-apps/02-how-to-estimate-gas.mdx
@@ -3,7 +3,7 @@ title: 'How to estimate gas in Arbitrum'
description: Learn how to estimate gas before submitting transactions.
author: TucksonDev
content_type: how-to
-displayed_sidebar: buildAppsSidebar
+displayed_sidebar: buildSoliditySidebar
---
:::info Looking for Stylus guidance?
diff --git a/docs/build-decentralized-apps/03-public-chains.mdx b/docs/build-decentralized-apps/03-public-chains.mdx
index bbc0b796a6..66d941d7b9 100644
--- a/docs/build-decentralized-apps/03-public-chains.mdx
+++ b/docs/build-decentralized-apps/03-public-chains.mdx
@@ -3,7 +3,7 @@ title: 'Arbitrum chains overview'
description: A high level description of the Arbitrum chains available
user_story: As a developer, I want to understand the different Arbitrum chains and how they relate to each other.
content_type: concept
-displayed_sidebar: buildAppsSidebar
+displayed_sidebar: buildSoliditySidebar
---
import { AddressExplorerLink as AEL } from '@site/src/components/AddressExplorerLink';
diff --git a/docs/build-decentralized-apps/04-cross-chain-messaging.mdx b/docs/build-decentralized-apps/04-cross-chain-messaging.mdx
index f73d98e710..c581bb7732 100644
--- a/docs/build-decentralized-apps/04-cross-chain-messaging.mdx
+++ b/docs/build-decentralized-apps/04-cross-chain-messaging.mdx
@@ -3,7 +3,7 @@ title: 'Cross-chain messaging overview'
description: Learn about cross-chain messaging in Arbitrum
user_story: As a developer, I want to understand how cross-chain messaging works in Arbitrum.
content_type: concept
-displayed_sidebar: buildAppsSidebar
+displayed_sidebar: buildSoliditySidebar
---
The Arbitrum protocol and related tooling makes it easy for developers to build cross-chain applications; i.e., applications that involve sending messages from Ethereum to an Arbitrum chain, and/or from an Arbitrum chain to Ethereum.
diff --git a/docs/build-decentralized-apps/arbitrum-vs-ethereum/01-comparison-overview.mdx b/docs/build-decentralized-apps/arbitrum-vs-ethereum/01-comparison-overview.mdx
index 316fa8fdbc..595f405270 100644
--- a/docs/build-decentralized-apps/arbitrum-vs-ethereum/01-comparison-overview.mdx
+++ b/docs/build-decentralized-apps/arbitrum-vs-ethereum/01-comparison-overview.mdx
@@ -6,7 +6,7 @@ author: jose-franco
sme: jose-franco
target_audience: developers who want to build on Arbitrum
content_type: concept
-displayed_sidebar: buildAppsSidebar
+displayed_sidebar: buildSoliditySidebar
---
Arbitrum's design is to be as compatible and consistent with Ethereum as possible, from its high-level RPCs to its low-level bytecode and everything in between. Decentralized app developers with experience building on Ethereum will likely find that little to no new specific knowledge is required to build on Arbitrum.
diff --git a/docs/build-decentralized-apps/arbitrum-vs-ethereum/02-block-numbers-and-time.mdx b/docs/build-decentralized-apps/arbitrum-vs-ethereum/02-block-numbers-and-time.mdx
index 80532d5abd..fdab098ba9 100644
--- a/docs/build-decentralized-apps/arbitrum-vs-ethereum/02-block-numbers-and-time.mdx
+++ b/docs/build-decentralized-apps/arbitrum-vs-ethereum/02-block-numbers-and-time.mdx
@@ -6,7 +6,7 @@ author: dzgoldman, jose-franco
sme: jose-franco
target_audience: developers who want to build on Arbitrum
content_type: concept
-displayed_sidebar: buildAppsSidebar
+displayed_sidebar: buildSoliditySidebar
---
import { VanillaAdmonition } from '@site/src/components/VanillaAdmonition/';
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..91fdf6f8ed 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
@@ -5,7 +5,7 @@ description: This concept page provides information about the differences betwee
target_audience: developers who want to build on Arbitrum
author: dzgoldman
content_type: concept
-displayed_sidebar: buildAppsSidebar
+displayed_sidebar: buildSoliditySidebar
---
Although the majority of RPC methods follow the same behavior as in Ethereum, some methods may produce a different result or add more information when used on an Arbitrum chain. This page covers the differences in response body fields you'll find when calling RPC methods on an Arbitrum chain vs on Ethereum.
diff --git a/docs/build-decentralized-apps/arbitrum-vs-ethereum/04-solidity-support.mdx b/docs/build-decentralized-apps/arbitrum-vs-ethereum/04-solidity-support.mdx
index b5842e3010..9ed06b6a16 100644
--- a/docs/build-decentralized-apps/arbitrum-vs-ethereum/04-solidity-support.mdx
+++ b/docs/build-decentralized-apps/arbitrum-vs-ethereum/04-solidity-support.mdx
@@ -5,7 +5,7 @@ description: This concept page provides information about the differences betwee
target_audience: developers who want to build on Arbitrum
author: dzgoldman
content_type: concept
-displayed_sidebar: buildAppsSidebar
+displayed_sidebar: buildSoliditySidebar
---
Arbitrum chains are Ethereum-compatible and, therefore, allow you to trustlessly deploy Solidity smart contracts, as well as contracts written in Vyper or any other language that compiles to EVM bytecode. However, when calling certain properties and functions on a Solidity smart contract, there are some differences between the result you'd obtain if that contract were on Ethereum and the result on Arbitrum.
diff --git a/docs/build-decentralized-apps/custom-gas-token-sdk.mdx b/docs/build-decentralized-apps/custom-gas-token-sdk.mdx
index f100dac3cc..77c8f2c3f6 100644
--- a/docs/build-decentralized-apps/custom-gas-token-sdk.mdx
+++ b/docs/build-decentralized-apps/custom-gas-token-sdk.mdx
@@ -5,7 +5,7 @@ author: Mehdi Salehi
sme: Mehdi Salehi
target_audience: 'Developers deploying and maintaining Arbitrum chains.'
sidebar_position: 2
-displayed_sidebar: buildAppsSidebar
+displayed_sidebar: buildSoliditySidebar
---
Arbitrum SDK is a TypeScript library for client-side interactions with Arbitrum. It provides common helper functionality as well as access to the underlying smart contract interfaces.
diff --git a/docs/build-decentralized-apps/nodeinterface/01-overview.mdx b/docs/build-decentralized-apps/nodeinterface/01-overview.mdx
index 31c59cd571..a528be0092 100644
--- a/docs/build-decentralized-apps/nodeinterface/01-overview.mdx
+++ b/docs/build-decentralized-apps/nodeinterface/01-overview.mdx
@@ -3,7 +3,7 @@ title: 'NodeInterface overview'
description: A high level description of what the NodeInterface is and how it works
user_story: As a developer, I want to understand what the NodeInterface is and how it works.
content_type: concept
-displayed_sidebar: buildAppsSidebar
+displayed_sidebar: buildSoliditySidebar
---
diff --git a/docs/build-decentralized-apps/nodeinterface/02-reference.mdx b/docs/build-decentralized-apps/nodeinterface/02-reference.mdx
index 023bcc2db5..1be9333d80 100644
--- a/docs/build-decentralized-apps/nodeinterface/02-reference.mdx
+++ b/docs/build-decentralized-apps/nodeinterface/02-reference.mdx
@@ -3,7 +3,7 @@ title: 'NodeInterface reference'
description: A reference page of the NodeInterface available on Arbitrum chains
user_story: As a developer, I want to understand the specific methods available in the NodeInterface
content_type: reference
-displayed_sidebar: buildAppsSidebar
+displayed_sidebar: buildSoliditySidebar
---
The Arbitrum Nitro software includes a special `NodeInterface` contract available at address `0xc8` that is only accessible via RPCs (it's not actually deployed onchain, and thus can't be called by smart contracts). This reference page documents the specific calls available in the `NodeInterface`. For a more conceptual description of what it is and how it works, please refer to the [`NodeInterface` conceptual page](/build-decentralized-apps/nodeinterface/01-overview.mdx).
diff --git a/docs/build-decentralized-apps/oracles/01-overview.mdx b/docs/build-decentralized-apps/oracles/01-overview.mdx
index a4228fbe40..6f323431f6 100644
--- a/docs/build-decentralized-apps/oracles/01-overview.mdx
+++ b/docs/build-decentralized-apps/oracles/01-overview.mdx
@@ -5,7 +5,7 @@ description: A high level description of what oracles are
user_story: As a developer, I want to understand what oracles are and how they work.
content_type: concept
sidebar_label: Oracles
-displayed_sidebar: buildAppsSidebar
+displayed_sidebar: buildSoliditySidebar
---
import ImageZoom from '@site/src/components/ImageZoom';
diff --git a/docs/build-decentralized-apps/precompiles/01-overview.mdx b/docs/build-decentralized-apps/precompiles/01-overview.mdx
index 6b87a8c694..caad074ccd 100644
--- a/docs/build-decentralized-apps/precompiles/01-overview.mdx
+++ b/docs/build-decentralized-apps/precompiles/01-overview.mdx
@@ -3,7 +3,7 @@ title: 'Precompiles overview'
description: A high level description of what precompiles are and how they work
user_story: As a developer, I want to understand what precompiles are and how they work.
content_type: concept
-displayed_sidebar: buildAppsSidebar
+displayed_sidebar: buildSoliditySidebar
---
Precompiles are predefined smart contracts that have special addresses and provide specific functionality which is executed not at the EVM bytecode level, but natively by the Arbitrum client itself. Precompiles are primarily used to introduce specific functions that would be computationally expensive if executed in EVM bytecode, and functions that facilitate the interaction between the parent chain and the child chain. By having them natively in the Arbitrum client, they can be optimized for performance.
diff --git a/docs/build-decentralized-apps/precompiles/02-reference.mdx b/docs/build-decentralized-apps/precompiles/02-reference.mdx
index 9593ab9445..aaa7c6f914 100644
--- a/docs/build-decentralized-apps/precompiles/02-reference.mdx
+++ b/docs/build-decentralized-apps/precompiles/02-reference.mdx
@@ -3,7 +3,7 @@ title: 'Precompiles reference'
description: A reference page of all precompiles available on Arbitrum chains
user_story: As a developer, I want to understand the most useful precompiles available on Arbitrum chains and how to use them.
content_type: reference
-displayed_sidebar: buildAppsSidebar
+displayed_sidebar: buildSoliditySidebar
---
ArbOS provides child chain-specific precompiles with methods smart contracts can call the same way they can solidity functions. This reference page exhaustively documents the specific calls ArbOS makes available through precompiles. For a more conceptual description of what precompiles are and how they work, please refer to the [precompiles conceptual page](/build-decentralized-apps/precompiles/01-overview.mdx).
diff --git a/docs/build-decentralized-apps/reference/01-node-providers.mdx b/docs/build-decentralized-apps/reference/01-node-providers.mdx
index 1a6016b0cd..a3fd4af1d4 100644
--- a/docs/build-decentralized-apps/reference/01-node-providers.mdx
+++ b/docs/build-decentralized-apps/reference/01-node-providers.mdx
@@ -3,7 +3,7 @@ title: 'RPC endpoints and providers'
description: Find available RPC endpoints and providers in the ecosystem
reader_audience: developers who want to build on Arbitrum
content_type: overview
-displayed_sidebar: buildAppsSidebar
+displayed_sidebar: buildSoliditySidebar
---
import ArbitrumRpcEndpoints from '../../partials/_reference-arbitrum-rpc-endpoints-partial.mdx';
diff --git a/docs/build-decentralized-apps/reference/02-contract-addresses.mdx b/docs/build-decentralized-apps/reference/02-contract-addresses.mdx
index 01c4f451dd..5e91a09c55 100644
--- a/docs/build-decentralized-apps/reference/02-contract-addresses.mdx
+++ b/docs/build-decentralized-apps/reference/02-contract-addresses.mdx
@@ -6,7 +6,7 @@ author: anegg0
sme: anegg0
user_story: As a current or prospective Arbitrum user I need to know to what addresses Arbitrum contracts have been deployed.
content_type: reference
-displayed_sidebar: buildAppsSidebar
+displayed_sidebar: buildSoliditySidebar
---
import ArbitrumContractAddresses from '../../partials/_reference-arbitrum-contract-addresses-partial.mdx';
diff --git a/docs/build-decentralized-apps/reference/03-chain-params.mdx b/docs/build-decentralized-apps/reference/03-chain-params.mdx
index 221f35769a..fa5926041e 100644
--- a/docs/build-decentralized-apps/reference/03-chain-params.mdx
+++ b/docs/build-decentralized-apps/reference/03-chain-params.mdx
@@ -3,7 +3,7 @@ title: 'Chain parameters'
description: Information about important system parameters for public Arbitrum chains
user_story: As a developer, I want to understand the system parameters for the public Arbitrum chains.
content_type: overview
-displayed_sidebar: buildAppsSidebar
+displayed_sidebar: buildSoliditySidebar
---
| Param | Description | Arbitrum One | Arbitrum Nova | Arb Sepolia |
diff --git a/docs/build-decentralized-apps/reference/04-development-frameworks.mdx b/docs/build-decentralized-apps/reference/04-development-frameworks.mdx
index ce41891626..9d59cbec03 100644
--- a/docs/build-decentralized-apps/reference/04-development-frameworks.mdx
+++ b/docs/build-decentralized-apps/reference/04-development-frameworks.mdx
@@ -3,7 +3,7 @@ title: 'Development frameworks'
description: An overview of popular development frameworks that exist in the Arbitrum ecosystem
user_story: As a developer, I want to understand the popular development frameworks that exist in the Arbitrum ecosystem.
content_type: overview
-displayed_sidebar: buildAppsSidebar
+displayed_sidebar: buildSoliditySidebar
---
import KnowMoreToolsBox from '../../for-devs/partials/_know-more-tools-box-partial.mdx';
@@ -20,6 +20,10 @@ The following tools will help you develop and test your decentralized apps (dApp
[Foundry](https://github.com/foundry-rs/foundry) is a high-performance, portable, and modular toolkit designed for EVM application development, leveraging the Rust programming language. It offers a comprehensive suite of tools to streamline the process of creating, testing, and deploying smart contracts on the Ethereum, Arbitrum and, in general, any EVM network. Foundry facilitates seamless interaction with EVM smart contracts, transactions, and chain data, while also providing a local node and a user-friendly Solidity REPL environment for efficient development.
+## Truffle
+
+[Truffle](https://trufflesuite.com/) is a comprehensive suite of tools for smart contract development, providing an end-to-end solution for building, testing, debugging, and deploying on Ethereum, Arbitrum and other EVM-compatible chains. It features advanced debugging capabilities, fast EVM simulation with Ganache, a user-centric design with a VS Code extension, and robust parent and child chain support. Truffle prioritizes security and partners with ConsenSys Diligence to bring continuous security to projects, providing a seamless and secure developer experience.
+
## thirdweb
[thirdweb SDK](https://portal.thirdweb.com/sdk) covers all aspects of the Web3 development stack, including connecting to user’s wallets, interacting with the blockchain and smart contracts, decentralized storage, authentication, and more; enabling you to build scalable and performant Web3 applications on any EVM-compatible blockchain. Out of the box, infrastructure is provided for everything required to create decentralized applications, including connection to the blockchain (RPC), decentralized storage (IPFS + pinning services), and tools to create powerful user experiences; such as gasless transactions, wallet connection components, FIAT on-ramps, data APIs, and more.
diff --git a/docs/build-decentralized-apps/reference/05-web3-libraries-tools.mdx b/docs/build-decentralized-apps/reference/05-web3-libraries-tools.mdx
index 103f5582f1..3915f5f3db 100644
--- a/docs/build-decentralized-apps/reference/05-web3-libraries-tools.mdx
+++ b/docs/build-decentralized-apps/reference/05-web3-libraries-tools.mdx
@@ -3,7 +3,7 @@ title: 'Web3 libraries and tools'
description: An overview of some popular Web3 libraries that help developers interact with the Ethereum and Arbitrum blockchains.
user_story: As a developer, I want to understand what Web3 libraries and tools are available in the Ethereum and Arbitrum ecosystems.
content_type: overview
-displayed_sidebar: buildAppsSidebar
+displayed_sidebar: buildSoliditySidebar
---
import KnowMoreToolsBox from '../../for-devs/partials/_know-more-tools-box-partial.mdx';
diff --git a/docs/build-decentralized-apps/reference/06-monitoring-tools-block-explorers.mdx b/docs/build-decentralized-apps/reference/06-monitoring-tools-block-explorers.mdx
index 2b59378770..23c3a82c61 100644
--- a/docs/build-decentralized-apps/reference/06-monitoring-tools-block-explorers.mdx
+++ b/docs/build-decentralized-apps/reference/06-monitoring-tools-block-explorers.mdx
@@ -3,7 +3,7 @@ title: 'Monitoring tools and block explorers'
description: An overview of popular monitoring tools and block explorers that exist in the Arbitrum ecosystem
user_story: As a developer, I want to understand what monitoring tools and block explorers are available in the Arbitrum ecosystem.
content_type: overview
-displayed_sidebar: buildAppsSidebar
+displayed_sidebar: buildSoliditySidebar
---
import KnowMoreToolsBox from '../../for-devs/partials/_know-more-tools-box-partial.mdx';
diff --git a/docs/build-decentralized-apps/reference/07-debugging-tools.mdx b/docs/build-decentralized-apps/reference/07-debugging-tools.mdx
index 22fe44b703..17a64fb0f5 100644
--- a/docs/build-decentralized-apps/reference/07-debugging-tools.mdx
+++ b/docs/build-decentralized-apps/reference/07-debugging-tools.mdx
@@ -3,7 +3,7 @@ title: 'Debugging tools'
description: An overview of popular debugging tools that exist in the Arbitrum ecosystem
user_story: As a developer, I want to understand what debugging tools are available in the Arbitrum ecosystem.
content_type: overview
-displayed_sidebar: buildAppsSidebar
+displayed_sidebar: buildSoliditySidebar
---
import KnowMoreToolsBox from '../../for-devs/partials/_know-more-tools-box-partial.mdx';
diff --git a/docs/build-decentralized-apps/reference/08-mainnet-risks.mdx b/docs/build-decentralized-apps/reference/08-mainnet-risks.mdx
index 0431279295..219e3bae3f 100644
--- a/docs/build-decentralized-apps/reference/08-mainnet-risks.mdx
+++ b/docs/build-decentralized-apps/reference/08-mainnet-risks.mdx
@@ -2,7 +2,7 @@
title: 'Arbitrum: Understanding the risks'
description: 'Understand the risks associated with cutting-edge software development'
author: dzgoldman
-displayed_sidebar: buildAppsSidebar
+displayed_sidebar: buildSoliditySidebar
---
# Arbitrum: Understanding the risks
diff --git a/docs/build-decentralized-apps/token-bridging/01-overview.mdx b/docs/build-decentralized-apps/token-bridging/01-overview.mdx
index 139e5b1ccd..0992179ad7 100644
--- a/docs/build-decentralized-apps/token-bridging/01-overview.mdx
+++ b/docs/build-decentralized-apps/token-bridging/01-overview.mdx
@@ -5,7 +5,7 @@ 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
+displayed_sidebar: buildSoliditySidebar
---
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.
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
index d6f4803f34..7a99151a64 100644
--- a/docs/build-decentralized-apps/token-bridging/02-token-bridge-ether.mdx
+++ b/docs/build-decentralized-apps/token-bridging/02-token-bridge-ether.mdx
@@ -5,7 +5,7 @@ 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
+displayed_sidebar: buildSoliditySidebar
---
import ImageZoom from '@site/src/components/ImageZoom';
diff --git a/docs/build-decentralized-apps/token-bridging/03-token-bridge-erc20.mdx b/docs/build-decentralized-apps/token-bridging/03-token-bridge-erc20.mdx
index cbaa2cfe8a..99a1eaa60c 100644
--- a/docs/build-decentralized-apps/token-bridging/03-token-bridge-erc20.mdx
+++ b/docs/build-decentralized-apps/token-bridging/03-token-bridge-erc20.mdx
@@ -5,7 +5,7 @@ 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.
content_type: concept
sidebar_position: 3
-displayed_sidebar: buildAppsSidebar
+displayed_sidebar: buildSoliditySidebar
---
import ImageZoom from '@site/src/components/ImageZoom';
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
index 93ed153406..b35bd528c2 100644
--- 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
@@ -3,7 +3,7 @@ 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
+displayed_sidebar: buildSoliditySidebar
---
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.
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
index 3412398745..7679ab82d1 100644
--- 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
@@ -3,7 +3,7 @@ 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
+displayed_sidebar: buildSoliditySidebar
---
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).
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/bridge-tokens-programmatically/03-how-to-bridge-tokens-generic-custom.mdx
index 81c8acd8f9..c1bf69c54a 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/bridge-tokens-programmatically/03-how-to-bridge-tokens-generic-custom.mdx
@@ -3,7 +3,7 @@ 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
content_type: how-to
-displayed_sidebar: buildAppsSidebar
+displayed_sidebar: buildSoliditySidebar
---
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).
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/bridge-tokens-programmatically/04-how-to-bridge-tokens-custom-gateway.mdx
index 29b7fbb608..3ad4a6ea84 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/bridge-tokens-programmatically/04-how-to-bridge-tokens-custom-gateway.mdx
@@ -3,7 +3,7 @@ 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
content_type: how-to
-displayed_sidebar: buildAppsSidebar
+displayed_sidebar: buildSoliditySidebar
---
:::caution Do you really need a custom gateway?
diff --git a/docs/for-users/contribute.mdx b/docs/for-users/contribute.mdx
new file mode 100644
index 0000000000..4f7c3501cf
--- /dev/null
+++ b/docs/for-users/contribute.mdx
@@ -0,0 +1,8 @@
+---
+title: 'Contribute docs'
+description: "Learn how to contribute to Arbitrum's documentation"
+---
+
+import ContributeDocsPartial from '../partials/_contribute-docs-partial.mdx';
+
+
diff --git a/docs/launch-arbitrum-chain/02-configure-your-chain/advanced-configurations/04-deploy-timeboost.mdx b/docs/launch-arbitrum-chain/02-configure-your-chain/advanced-configurations/04-deploy-timeboost.mdx
new file mode 100644
index 0000000000..d871814667
--- /dev/null
+++ b/docs/launch-arbitrum-chain/02-configure-your-chain/advanced-configurations/04-deploy-timeboost.mdx
@@ -0,0 +1,195 @@
+---
+title: 'Deploy and configure Timeboost for your chain'
+sidebar_label: 'Deploy and configure Timeboost'
+description: 'Learn how to deploy and configure Timeboost'
+user_story: As a current or prospective Orbit chain owner, I need to understand how to deploy and configure Timeboost
+author: Jason Wan
+sme: Jason Wan
+content_type: how-to
+---
+
+# Enabling Timeboost for your Arbitrum (Orbit) chain
+
+This guide walks you through the process of enabling Timeboost for your Arbitrum Orbit chain. For a conceptual introduction to Timeboost, see the [Timeboost Introduction](/how-arbitrum-works/timeboost/gentle-introduction.mdx).
+
+## Prerequisites
+
+Before starting, ensure you have:
+
+1. An `ERC-20` token address to use as the bid token
+2. A Redis server for auctioneer coordination
+3. A server to run the auctioneer service
+4. A proxy admin contract address
+
+## Overview
+
+Enabling Timeboost requires completing these three steps:
+
+1. Deploy the `ExpressLaneAuction` contract
+2. Run Auctioneer Services (bid validator and auction server)
+3. Configure your sequencer node to support Timeboost
+
+## Step 1: Deploy the `ExpressLaneAuction` contract
+
+First, clone the `orbit-actions` repository:
+
+```shell
+git clone https://github.com/OffchainLabs/orbit-actions.git
+cd orbit-actions/scripts/foundry/timeboost
+```
+
+Create and edit the environment configuration file:
+
+```shell
+cp .env.sample .env
+```
+
+Configure the following parameters in your `.env` file:
+
+```shell
+## Configuration for DeployExpressLaneAuction.s.sol
+PROXY_ADMIN_ADDRESS= # Your proxy admin contract address
+AUCTIONEER_ADDRESS= # Address that will send resolve auction requests
+BIDDING_TOKEN_ADDRESS= # Your ERC20 bid token address
+BENEFICIARY_ADDRESS= # Address to receive bid proceeds
+AUCTIONEER_ADMIN_ADDRESS= # Admin address for the auctioneer
+MIN_RESERVE_PRICE_SETTER_ADDRESS= # Address allowed to set minimum reserve price
+RESERVE_PRICE_SETTER_ADDRESS= # Address allowed to set reserve price
+RESERVE_PRICE_SETTER_ADMIN_ADDRESS= # Admin for reserve price setter
+BENEFICIARY_SETTER_ADDRESS= # Address allowed to change beneficiary
+ROUND_TIMING_SETTER_ADDRESS= # Address allowed to adjust round timing
+MASTER_ADMIN_ADDRESS= # Master admin address
+
+MIN_RESERVE_PRICE=0 # Minimum price for bids (0 recommended for testing)
+
+# Round timing configuration (in seconds)
+ROUND_DURATION_SECONDS=60 # Total duration of each round
+AUCTION_CLOSING_SECONDS=15 # Time before round end when new bids are closed
+RESERVE_SUBMISSION_SECONDS=15 # Time allocated for reserve price submission
+```
+
+Deploy the contract:
+
+```shell
+forge script --sender $DEPLOYER --rpc-url $CHILD_CHAIN_RPC --slow ./DeployExpressLaneAuction.s.sol -vvv --verify --broadcast
+# Use --account XXX / --private-key XXX / --interactive / --ledger to specify the transaction signer
+```
+
+Verify successful deployment by checking that the contract returns your configured bid token:
+
+```shell
+cast call --rpc-url= "biddingToken()(address)"
+```
+
+Example output:
+
+```shell
+0xYourBidTokenAddress
+```
+
+## Step 2: Run auctioneer services
+
+There are two distinct services to run: the bid validator and the auction server. The bid validator verifies submitted bids, while the auction server sends the winning bid on-chain.
+
+### Prerequisites
+
+The services require the `autonomous-auctioneer` binary, which is included in the Nitro Docker image. Alternatively, you can build it locally by following the [Build Nitro Locally](https://docs.arbitrum.io/run-arbitrum-node/nitro/build-nitro-locally) guide.
+
+To build only the `autonomous-auctioneer` component during the local build process:
+
+```shell
+make target/bin/autonomous-auctioneer
+```
+
+### Running bid validator service
+
+Start the bid validator with:
+
+```shell
+./autonomous-auctioneer \
+--bid-validator.auction-contract-address= \
+--bid-validator.rpc-endpoint= \
+--auctioneer-server.enable=false \
+--bid-validator.redis-url= \
+--http.addr=0.0.0.0 \
+--http.port=
+```
+
+### Running auction server service
+
+Start the auction server with:
+
+```shell
+./autonomous-auctioneer \
+--auctioneer-server.auction-contract-address= \
+--auctioneer-server.db-directory= \
+--auctioneer-server.redis-url= \
+--auctioneer-server.use-redis-coordinator=false \
+--auctioneer-server.sequencer-endpoint= \
+--auctioneer-server.wallet.private-key= \
+--bid-validator.enable=false
+```
+
+## Step 3: Configure your sequencer node for Timeboost
+
+Update your sequencer node configuration to enable Timeboost functionality. Add the following new config to your sequencer's node configuration file:
+
+```json
+{
+ "http": {
+ "api": [
+ // existing APIs
+ "auctioneer",
+ "timeboost"
+ ]
+ },
+ "ws": {
+ "api": [
+ // existing APIs
+ "auctioneer",
+ "timeboost"
+ ]
+ },
+ "execution": {
+ "sequencer": {
+ "timeboost": {
+ "enable": true,
+ "auction-contract-address": "",
+ "auctioneer-address": "",
+ "redis-url": ""
+ }
+ }
+ }
+}
+```
+
+## Verifying your Timeboost setup
+
+There are multiple ways to confirm that Timeboost is correctly enabled on your chain:
+
+### Periodic startup logs
+
+When you start your sequencer with Timeboost enabled, you'll see periodic logs indicating the start of new express lane auction rounds:
+
+```shell
+New express lane auction round
+```
+
+This log indicates that the Timeboost mechanism is active and running normally.
+
+### Transaction processing confirmation
+
+After finishing a bid request, look for messages in your sequencer logs such as:
+
+```shell
+AuctionResolved: New express lane controller assigned round
+```
+
+This message confirms that your sequencer is processing express queue transactions from the express lane controller, and that Timeboost is functioning correctly.
+
+### User interaction verification
+
+Users can interact with Timeboost by submitting bids through the `auctioneer_submitBid` endpoint of your auctioneer service.
+For detailed instructions on how users can interact with Timeboost, see [How to Use Timeboost](https://docs.arbitrum.io/how-arbitrum-works/timeboost/how-to-use-timeboost).
+
+A successful bid submission will trigger the auction resolution process and generate the corresponding logs mentioned above.
diff --git a/docs/launch-arbitrum-chain/02-configure-your-chain/common-configurations/04-stake-and-validator-configurations.mdx b/docs/launch-arbitrum-chain/02-configure-your-chain/common-configurations/04-stake-and-validator-configurations.mdx
index db32663f22..b49d50fb0f 100644
--- a/docs/launch-arbitrum-chain/02-configure-your-chain/common-configurations/04-stake-and-validator-configurations.mdx
+++ b/docs/launch-arbitrum-chain/02-configure-your-chain/common-configurations/04-stake-and-validator-configurations.mdx
@@ -1,453 +1,13 @@
---
-title: 'Bond and validator configurations'
-description: 'Learn how to configure your Arbitrum chain with a custom bond and validator configurations'
-author: pete-vielhaber
-sme: Jason-W123
+title: 'Stake and validator configurations'
+description: 'Learn how to configure your Arbitrum chain with a custom stake and validator configurations'
+author:
+sme:
content_type: how-to
+tags: ['hide-from-search']
unlisted: true
---
-Arbitrum chains are customizable Layer 3 (L3) chains that settle
-to an Arbitrum Layer 2 (L2) chain, such as Arbitrum One. They support
- validator
- configurations to ensure chain security through bonding and
- assertion
- challenges. Validators post assertions about the chain's state on the parent L2 chain and can
- challenge
- incorrect assertions. Arbitrum chains can be permissioned, meaning validators must be allowlisted.
-For chains that use that elect to use the BoLD protocol, permissionless validation is an option (BoLD
-also supports permissioned validation).
-
-Bonding is required for active validation, where validators place bond funds to participate. If a validator loses a challenge (e.g., due to a faulty assertion), their bond is escrowed or burned. Configurations such as the `stakeToken`, `baseStake`, and `loserStakeEscrow` are configurable during chain deployment or post-deployment via contract calls.
-
-Arbitrum chains may utilize the BoLD (Bounded Liquidity Delay) protocol for efficient dispute resolution, which affects bonding tokens (e.g., `WETH` for BoLD-enabled chains like Arbitrum One/Nova, or `ETH`/native for other chains).
-
-## `stakeToken`
-
-The bonded token is the asset that validators must bond to participate in asserting the chain's state on the parent L2 chain. It serves as collateral for challenges. A bonded token can be `WETH` or the parent chain's native token.
-
-### Configuration details
-
-- Can be `ETH` (native gas token) or any `ERC-20` token.
-- **For BoLD-enabled chains** (e.g., settling to Arbitrum One or Nova), it defaults to `WETH`.
-- **For non-BoLD chains**, it defaults to the parent chain's native token (usually `ETH`).
-- Currently, its value is often hardcoded to `ETH` in basic deployments, but customizable to an `ERC-20` contract address in advanced setups. Future updates will expand `ERC-20` support.
-
-### How to configure
-
-1. **Prepare the chain configuration**:
- Generate the base chain config using `prepareChainConfig`. Set the `chainId` and initial owner.
-
- ```typescript
- import { prepareChainConfig } from '@arbitrum/chain-sdk';
-
- const chainConfig = prepareChainConfig({
- chainId: 123456, // Replace with your desired chain ID
- arbitrum: {
- InitialChainOwner: '0xYourOwnerAddressHere', // Wallet address that will own the chain
- DataAvailabilityCommittee: false, // Set to true for AnyTrust chains (DAC)
- },
- });
- ```
-
-2. **Set up the public client for the parent chain**:
- Create a public client to interact with the parent chain.
-
- ```typescript
- import { createPublicClient, http } from 'viem';
- import { sepolia } from 'viem/chains'; // Example: Use 'mainnet' or 'arbitrumOne' as needed
-
- const parentChainPublicClient = createPublicClient({
- chain: sepolia, // Replace with your parent chain (e.g., arbitrumOne)
- transport: http('https://your-parent-chain-rpc-url'), // Replace with actual RPC URL
- });
- ```
-
-3. **Prepare deployment parameters, including `stakeToken`**:
- Use `createRollupPrepareDeploymentParamsConfig` to define the rollout config. This is where you configure the `stakeToken` (the `ERC-20` address on the parent chain) and `baseStake` (minimum stake amount in wei).
-
- ```typescript
- import { createRollupPrepareDeploymentParamsConfig } from '@arbitrum/chain-sdk';
-
- const createRollupConfig = await createRollupPrepareDeploymentParamsConfig(
- parentChainPublicClient,
- {
- chainId: 123456, // Must match the chainId from Step 1
- owner: '0xYourOwnerAddressHere', // Must match InitialChainOwner from Step 1
- chainConfig,
- stakeToken: '0xYourStakeTokenERC20AddressHere', // ERC-20 token address on parent chain for validator staking
- baseStake: 1000000000000000000n, // Example: 1 token (adjust based on token decimals)
- // Optional: Other params like confirmPeriodBlocks, loserStakeEscrow, etc.
- },
- );
- ```
-
-4. **Deploy the chain**:
- Use `createRollup` to deploy. Provide validator and batch poster addresses. This step sends the transaction to the parent chain.
-
- ```typescript
- import { createWalletClient } from 'viem';
- import { privateKeyToAccount } from 'viem/accounts';
- import { createRollup } from '@arbitrum/chain-sdk';
-
- const deployerAccount = privateKeyToAccount('0xYourDeployerPrivateKeyHere'); // Securely manage this
-
- const walletClient = createWalletClient({
- account: deployerAccount,
- chain: sepolia, // Match parent chain
- transport: http('https://your-parent-chain-rpc-url'),
- });
-
- const createRollupResults = await createRollup({
- params: {
- config: createRollupConfig,
- batchPosters: ['0xYourBatchPosterAddressHere'], // Address for posting batches
- validators: ['0xYourValidatorAddressHere'], // Validator addresses
- // Optional: nativeToken: '0xCustomGasTokenAddress' for custom fee tokens
- },
- account: deployerAccount,
- publicClient: parentChainPublicClient,
- walletClient,
- });
-
- console.log('Deployment Transaction Hash:', createRollupResults.transactionHash);
- console.log('Core Contracts:', createRollupResults.coreContracts);
- ```
-
-5. **Post-deployment (if using AnyTrust)**:
- If `DataAvailabilityCommittee` is `true`, set the DAC keyset in the `SequencerInbox`.
-
- ```typescript
- import { setValidKeyset } from '@arbitrum/chain-sdk';
-
- // Generate or provide your keyset (BLS public keys for the committee)
- const keyset = '0xYourGeneratedKeysetHere';
-
- const setKeysetResults = await setValidKeyset({
- coreContracts: createRollupResults.coreContracts,
- keyset,
- publicClient: parentChainPublicClient,
- walletClient,
- });
- ```
-
-### Additional notes
-
-- Ensure the `stakeToken` is a compliant `ERC-20` on the parent chain; mismatches can cause deployment failures.
-- Test on a testnet (e.g., Sepolia) first, as deployments are irreversible and cost gas.
-- The owner address gains control over upgrades and settings—use a secure multisig in production.
-- For production, consider a Rollup-as-a-Service (RaaS) provider for monitoring and scaling.
-- **Verification**: This SDK-based method deploys directly via smart contract interactions and does not involve any UI or portal (the portal is for asset bridging, not chain deployment).
-
-## `baseStake`
-
-The `baseStake` is the minimum amount of the bond token that validators must deposit to bond and post assertions.
-
-### Configuration details
-
-- Specified as a float value (e.g., in `wei` for `ETH`).
-- Must be greater than 0.
-- **Balances security**: Low values ease entry but risk malicious challenges; high values deter attacks but exclude smaller validators.
-
-### How to configure
-
-1. **Prepare the chain configuration**:
- Generate the base chain config using `prepareChainConfig`. Set the `chainId` and initial owner.
-
- ```typescript
- import { prepareChainConfig } from '@arbitrum/chain-sdk';
-
- const chainConfig = prepareChainConfig({
- chainId: 123456, // Replace with your desired chain ID
- arbitrum: {
- InitialChainOwner: '0xYourOwnerAddressHere', // Wallet address that will own the chain
- DataAvailabilityCommittee: false, // Set to true for AnyTrust chains (DAC)
- },
- });
- ```
-
-2. **Set up the public client for the parent chain**:
- Create a public client to interact with the parent chain.
-
- ```typescript
- import { createPublicClient, http } from 'viem';
- import { sepolia } from 'viem/chains'; // Example: Use 'mainnet' or 'arbitrumOne' as needed
-
- const parentChainPublicClient = createPublicClient({
- chain: sepolia, // Replace with your parent chain (e.g., arbitrumOne)
- transport: http('https://your-parent-chain-rpc-url'), // Replace with actual RPC URL
- });
- ```
-
-3. **Prepare deployment parameters, including `stakeToken`**:
- Use `createRollupPrepareDeploymentParamsConfig` to define the rollout config. This is where you configure the `stakeToken` (the `ERC-20` address on the parent chain) and `baseStake` (minimum stake amount in wei).
-
- ```typescript
- import { createRollupPrepareDeploymentParamsConfig } from '@arbitrum/chain-sdk';
-
- const createRollupConfig = await createRollupPrepareDeploymentParamsConfig(
- parentChainPublicClient,
- {
- chainId: 123456, // Must match the chainId from Step 1
- owner: '0xYourOwnerAddressHere', // Must match InitialChainOwner from Step 1
- chainConfig,
- stakeToken: '0xYourStakeTokenERC20AddressHere', // ERC-20 token address on parent chain for validator staking
- baseStake: 1000000000000000000n, // Example: 1 token (adjust based on token decimals)
- // Optional: Other params like confirmPeriodBlocks, loserStakeEscrow, etc.
- },
- );
- ```
-
-4. **Deploy the chain**:
- Use `createRollup` to deploy. Provide validator and batch poster addresses. This step sends the transaction to the parent chain.
-
- ```typescript
- import { createWalletClient } from 'viem';
- import { privateKeyToAccount } from 'viem/accounts';
- import { createRollup } from '@arbitrum/chain-sdk';
-
- const deployerAccount = privateKeyToAccount('0xYourDeployerPrivateKeyHere'); // Securely manage this
-
- const walletClient = createWalletClient({
- account: deployerAccount,
- chain: sepolia, // Match parent chain
- transport: http('https://your-parent-chain-rpc-url'),
- });
-
- const createRollupResults = await createRollup({
- params: {
- config: createRollupConfig,
- batchPosters: ['0xYourBatchPosterAddressHere'], // Address for posting batches
- validators: ['0xYourValidatorAddressHere'], // Validator addresses
- // Optional: nativeToken: '0xCustomGasTokenAddress' for custom fee tokens
- },
- account: deployerAccount,
- publicClient: parentChainPublicClient,
- walletClient,
- });
-
- console.log('Deployment Transaction Hash:', createRollupResults.transactionHash);
- console.log('Core Contracts:', createRollupResults.coreContracts);
- ```
-
-5. **Post-deployment (if using AnyTrust)**:
- If `DataAvailabilityCommittee` is `true`, set the DAC keyset in the `SequencerInbox`.
-
- ```typescript
- import { setValidKeyset } from '@arbitrum/chain-sdk';
-
- // Generate or provide your keyset (BLS public keys for the committee)
- const keyset = '0xYourGeneratedKeysetHere';
-
- const setKeysetResults = await setValidKeyset({
- coreContracts: createRollupResults.coreContracts,
- keyset,
- publicClient: parentChainPublicClient,
- walletClient,
- });
- ```
-
-### Additional notes
-
-- **Validators and staking**: After deployment, validators bond by depositing at least `baseStake` of the `stakeToken` into the Rollup contract. This can be done via contract calls (e.g., using the `RollupUserLogic` interface).
-- **Warnings**:
- - Ensure the `stakeToken` is a compliant `ERC-20` on the parent chain; mismatches can cause deployment failures.
- - Test on a testnet (e.g., Sepolia) first, as deployments are irreversible and cost gas.
- - The owner address gains control over upgrades and settings—use a secure multisig in production.
- - For production, consider a Rollup-as-a-Service (RaaS) provider for monitoring and scaling.
-- **Verification**: This SDK-based method deploys directly via smart contract interactions and does not involve any UI or portal (the portal is for asset bridging, not chain deployment).
-
-## `loserStakeEscrow`
-
-The `loserStakeEscrow` is the address where a validator's bonded funds are sent if they lose a challenge (e.g., due to an incorrect assertion). This mechanism acts as a penalty. The default configuration has no default specified; must be configured.
-
-### Configuration details
-
-- Funds are escrowed rather than immediately burned, allowing potential recovery or governance decisions.
-- **Recommended**: Set to an address controlled by the chain owner(s) for management, or a burn address (e.g., `0x000000000000000000000000000000000000dEaD`) if funds should be permanently removed.
-
-### Configuring during deployment
-
-1. **Prepare chain config**:
-
- ```typescript
- import { prepareChainConfig } from '@arbitrum/chain-sdk';
-
- const chainConfig = prepareChainConfig({
- chainId: 123456, // Your unique chain ID
- arbitrum: {
- InitialChainOwner: '0xYourOwnerAddressHere',
- DataAvailabilityCommittee: false, // True for AnyTrust chains
- },
- });
- ```
-
-2. **Set up parent chain client**:
-
- ```typescript
- import { createPublicClient, http } from 'viem';
- import { sepolia } from 'viem/chains';
-
- const parentChainPublicClient = createPublicClient({
- chain: sepolia, // Replace with your parent chain
- transport: http('https://your-parent-rpc-url'),
- });
- ```
-
-3. **Prepare deployment params, including `loserStakeEscrow`**:
- Set `loserStakeEscrow` as an address (string). There is no explicit default documented, but if omitted, it may revert to a system default (e.g., zero address)—always specify for control.
-
- ```typescript
- import { createRollupPrepareDeploymentParamsConfig } from '@arbitrum/chain-sdk';
-
- const createRollupConfig = await createRollupPrepareDeploymentParamsConfig(
- parentChainPublicClient,
- {
- chainId: 123456,
- owner: '0xYourOwnerAddressHere',
- chainConfig,
- loserStakeEscrow: '0xYourEscrowAddressHere', // e.g., owner-controlled or burn address
- // Optional: baseStake: 100000000000000000n, stakeToken: '0xERC20Address', etc.
- },
- );
- ```
-
-4. **Deploy the chain**:
-
- ```typescript
- import { createWalletClient } from 'viem';
- import { privateKeyToAccount } from 'viem/accounts';
- import { createRollup } from '@arbitrum/chain-sdk';
-
- const deployerAccount = privateKeyToAccount('0xYourPrivateKey');
-
- const walletClient = createWalletClient({
- account: deployerAccount,
- chain: sepolia,
- transport: http('https://your-parent-rpc-url'),
- });
-
- const createRollupResults = await createRollup({
- params: {
- config: createRollupConfig,
- batchPosters: ['0xYourBatchPosterAddress'],
- validators: ['0xYourValidatorAddress'],
- },
- account: deployerAccount,
- publicClient: parentChainPublicClient,
- walletClient,
- });
-
- console.log('Rollup Address:', createRollupResults.coreContracts.rollup);
- ```
-
-5. **For AnyTrust chains**:
- If `DataAvailabilityCommittee` is `true`, configure the DAC keyset post-deployment using `setValidKeyset` from the SDK.
-
-### Updating `loserStakeEscrow` post-deployment
-
-The chain owner can update it by calling `setLoserStakeEscrow` on the deployed Rollup contract (address from `createRollupResults.coreContracts.rollup`):
-
-```typescript
-import { parseAbi } from 'viem';
-
-const rollupAbi = parseAbi(['function setLoserStakeEscrow(address newLoserStakedEscrow) external']);
-
-await walletClient.writeContract({
- address: '0xYourRollupAddress',
- abi: rollupAbi,
- functionName: 'setLoserStakeEscrow',
- args: ['0xNewEscrowAddressHere'],
-});
-```
-
-### Additional notes
-
-- This process requires the caller to be the owner. Changes affect how challenge losses are handled, so test thoroughly.
-- Setting `loserStakeEscrow` to a burn address increases the cost of failed challenges, enhancing security.
-- Deploy on testnets first; mainnet deployments are costly and permanent.
-
-## Validator configurations and how to setup them up
-
-Validators are configured during chain deployment and can be run post-launch. Arbitrum chains support permissioned validators, that can be added to an allowlist.
-
-### Step 1: Configure and run the validator node
-
-Start with the base configuration for a full node, then add validator-specific flags. Use a Docker command to run the Nitro node image (replace placeholders like version numbers, RPC URLs, and chain IDs with your specifics). For example, on Arbitrum One (chain ID 42161):
-
-```shell
-docker run --rm -it -v /path/to/local/dir/arbitrum:/home/user/.arbitrum offchainlabs/nitro-node:v3.8.0-62c0aa7 \
- --parent-chain.connection.url=https://your-l1-rpc-url:8545 \
- --chain.id=42161 \
- --node.staker.enable=true \
- --node.staker.strategy=Defensive \
- --node.staker.parent-chain-wallet.password="YOUR_SECURE_PASSWORD"
-```
-
-Key configuration parameters to include:
-
-- `--node.staker.enable=true`: This flag enables validation mode.
-- `--node.staker.strategy=[Strategy]`: Choose a strategy based on your intended behavior:
- - `Defensive`: Monitors the chain and challenges incorrect assertions by posting a bond (recommended for most users).
- - `StakeLatest`: Bonds on the latest correct assertion and challenges bad ones (available only on pre-BoLD chains).
- - `ResolveNodes`: Bonds on the latest assertion, resolves unconfirmed ones, and challenges bad assertions.
- - `MakeNodes`: Creates new assertions, resolves unconfirmed ones, and challenges bad ones (use cautiously, as it may lead to reverted transactions if multiple validators act at once).
- - `Watchtower`: Passively monitors and logs errors without active challenging (enabled by default; no wallet needed, but set `--node.staker.enable=false` to disable if not wanted).
-- `--node.staker.parent-chain-wallet.private-key=[0xYourPrivateKey]` or `--node.staker.parent-chain-wallet.password=[YourPassword]`: Provides access to the wallet for on-chain operations. Use a secure method; password-protected keystores are safer than direct private keys.
-- `--node.bold.enable=true`: Enable this if the chain has BoLD activated (required for versions before Nitro v3.6.0).
-
-For custom Arbitrum chains:
-
-- Add `--chain.info-json=[JSON string or file path with Arbitrum chain info]` to specify the chain's details.
-- BoLD parameters may not be needed if BoLD isn't activated on that chain.
-
-Run the command in a persistent setup (e.g., using Docker Compose or a systemd service) to keep the node online.
-
-### Step 2: Verify the validator is running correctly
-
-- Monitor the node logs for confirmation messages:
- - Look for `INFO [...] running as validator txSender=[your_wallet_address] actingAsWallet=[your_wallet_address] whitelisted=true strategy=[YourChosenStrategy]`. This indicates the node is in validator mode with a valid wallet.
- - `validation succeeded`: Shows the node is successfully validating blocks.
- - `found correct assertion`: Confirms the node is detecting and agreeing with on-chain assertions.
-- If issues arise, check for errors related to wallet funding, RPC connectivity, or chain syncing.
-
-### Advanced: Creating a dedicated validator wallet
-
-If you need a new wallet specifically for validation, use Nitro to generate one:
-
-```shell
-docker run --rm -it -v /path/to/local/dir/arbitrum:/home/user/.arbitrum offchainlabs/nitro-node:v3.8.0-62c0aa7 \
- --parent-chain.connection.url=https://your-l1-rpc-url:8545 \
- --chain.id=42161 \
- --node.staker.enable=true \
- --node.staker.parent-chain-wallet.only-create-key \
- --node.staker.parent-chain-wallet.password="YOUR_SECURE_PASSWORD"
-```
-
-This creates a wallet file in your mounted directory (e.g., under `arb1/wallet/` for Arbitrum One). Back it up securely, as it's needed to withdraw any staked funds later. Load it in your node run command using the password.
-
-### For permissioned chains
-
-If the Arbitrum chain is permissioned (not fully permissionless), add your validator wallet address to the allowlist:
-
-1. Identify the `upgradeExecutor` contract address for the chain.
-2. Call the `executeCall` method on it:
- - Set `target` to the Rollup contract address.
- - Set `targetCalldata` to `0xa3ffb772` followed by your validator address (this is the encoded signature for `setValidator(address[],bool[])`).
-3. Verify by calling `isValidator(your_address)` on the Rollup contract, which should return `true`.
-
-This step requires administrative access to the chain.
-
-Keep your node synced and monitor it regularly to ensure it contributes effectively to chain security. Always use the latest Nitro version for security and compatibility.
-
-:::info Additional information
-
-Refer to the [Run a full node](/run-arbitrum-node/02-run-full-node.mdx) article for instructions on how to get up and running.
-
-:::
-
-### Assertion interval
-
-Default to 15 minutes for new assertions (via `--node.bold.assertion-posting-interval` (BoLD) or `--node.staker.make-assertion-interval` (Legacy ~1 hour is default)); must exceed the Rollup's `minimumAssertionPeriod` (~15 minutes).
-
-For production, run multiple validators in a network. Test on devnets first. Always back up keys, as they're needed to withdraw bonds. If issues arise, refer to the official Arbitrum docs for updates, as configurations evolve.
+- **Base Stake**: Minimum bond required for validators.
+- **Stake Token**: Token used for staking.
+- **Loser Stake Escrow**: Address for escrowed funds from losing validators.
diff --git a/docs/launch-arbitrum-chain/02-configure-your-chain/common-configurations/09-sequencer-timing-adjustments.mdx b/docs/launch-arbitrum-chain/02-configure-your-chain/common-configurations/09-sequencer-timing-adjustments.mdx
new file mode 100644
index 0000000000..9d3ce1e017
--- /dev/null
+++ b/docs/launch-arbitrum-chain/02-configure-your-chain/common-configurations/09-sequencer-timing-adjustments.mdx
@@ -0,0 +1,9 @@
+---
+title: 'Sequencer Timing Adjustments'
+description: 'Learn how to configure sequencer timing adjustments for your Arbitrum chain'
+author:
+sme:
+content_type: how-to
+tags: ['hide-from-search']
+unlisted: true
+---
diff --git a/docs/launch-arbitrum-chain/02-configure-your-chain/common-configurations/10-per-batch-gas-cost.mdx b/docs/launch-arbitrum-chain/02-configure-your-chain/common-configurations/10-per-batch-gas-cost.mdx
new file mode 100644
index 0000000000..dfaa300451
--- /dev/null
+++ b/docs/launch-arbitrum-chain/02-configure-your-chain/common-configurations/10-per-batch-gas-cost.mdx
@@ -0,0 +1,9 @@
+---
+title: 'Per batch gas cost'
+description: 'Learn how to configure per batch gas cost for your Arbitrum chain'
+author:
+sme:
+content_type: how-to
+tags: ['hide-from-search']
+unlisted: true
+---
diff --git a/docs/launch-arbitrum-chain/02-configure-your-chain/common-configurations/11-smart-contract-size-limit.mdx b/docs/launch-arbitrum-chain/02-configure-your-chain/common-configurations/11-smart-contract-size-limit.mdx
new file mode 100644
index 0000000000..f6f3c94abe
--- /dev/null
+++ b/docs/launch-arbitrum-chain/02-configure-your-chain/common-configurations/11-smart-contract-size-limit.mdx
@@ -0,0 +1,9 @@
+---
+title: 'Smart contract size limit'
+description: 'Learn how to configure smart contract size limits on your Arbitrum chain'
+author:
+sme:
+content_type: how-to
+tags: ['hide-from-search']
+unlisted: true
+---
diff --git a/docs/launch-arbitrum-chain/02-configure-your-chain/common-configurations/12-customizing-anytrust.mdx b/docs/launch-arbitrum-chain/02-configure-your-chain/common-configurations/12-customizing-anytrust.mdx
new file mode 100644
index 0000000000..46a0ea59a0
--- /dev/null
+++ b/docs/launch-arbitrum-chain/02-configure-your-chain/common-configurations/12-customizing-anytrust.mdx
@@ -0,0 +1,9 @@
+---
+title: 'Customizing AnyTrust'
+description: 'Learn how to customize AnyTrust on your Arbitrum chain'
+author:
+sme:
+content_type: how-to
+tags: ['hide-from-search']
+unlisted: true
+---
diff --git a/docs/launch-arbitrum-chain/02-configure-your-chain/common-configurations/13-arbos-upgrade.mdx b/docs/launch-arbitrum-chain/02-configure-your-chain/common-configurations/13-arbos-upgrade.mdx
index 67eaeab929..f531d4b465 100644
--- a/docs/launch-arbitrum-chain/02-configure-your-chain/common-configurations/13-arbos-upgrade.mdx
+++ b/docs/launch-arbitrum-chain/02-configure-your-chain/common-configurations/13-arbos-upgrade.mdx
@@ -16,7 +16,7 @@ The specific upgrade requirements for each ArbOS release are located under each
#### Step 1: Update Nitro on nodes and validators
-Refer to the [requirements for the targeted ArbOS release](/run-arbitrum-node/arbos-releases/01-overview.mdx) to identify the specific [Nitro release](https://github.com/OffchainLabs/nitro/releases/) that supports the ArbOS version that you're upgrading to. For example, if your upgrade targets ArbOS 51, you'd use Nitro `v3.9.3` (Docker image: `offchainlabs/nitro-node:v3.9.3-8bc5554`) or higher. This is the version of the Nitro stack that needs to be running on each of your Arbitrum chain's nodes. A list of [all Nitro releases can be found on Github](https://github.com/OffchainLabs/nitro/releases).
+Refer to the [requirements for the targeted ArbOS release](/run-arbitrum-node/arbos-releases/01-overview.mdx) to identify the specific [Nitro release](https://github.com/OffchainLabs/nitro/releases/) that supports the ArbOS version that you're upgrading to. For example, if your upgrade targets ArbOS 50, you'd use Nitro `v3.9.0` (Docker image: `offchainlabs/nitro-node:v3.9.0-cca645a`) or higher. This is the version of the Nitro stack that needs to be running on each of your Arbitrum chain's nodes. A list of [all Nitro releases can be found on Github](https://github.com/OffchainLabs/nitro/releases).
Begin by upgrading your validator node(s) to the specified Nitro version, then update each remaining Arbitrum chain node to match this version.
@@ -26,7 +26,7 @@ Note that upgrading your node version _must occur_ before the deadline establish
While every ArbOS upgrade will require an update to the WASM module root, not every ArbOS upgrade will require an upgrade to the chain's `nitro-contracts` version.
-If necessary, as defined in the release notes for each ArbOS release ([example of ArbOS 51](/run-arbitrum-node/arbos-releases/arbos51.mdx)), you may need to deploy new versions of some (or all) of the Nitro contracts to the parent chain of your Arbitrum chain. These contracts include the rollup logic, bridging logic, fraud-proof contracts, and interfaces for interacting with Nitro precompiles. To verify the current version of your Nitro contracts, follow [these instructions](https://github.com/OffchainLabs/orbit-actions/blob/main/README.md#check-version-and-upgrade-path) while replacing the inbox contract address and network name with that of your Arbitrum chain. This information will allow you to find the correct upgrade path for your Nitro contracts.
+If necessary, as defined in the release notes for each ArbOS release ([example of ArbOS 50](/run-arbitrum-node/arbos-releases/arbos50.mdx)), you may need to deploy new versions of some (or all) of the Nitro contracts to the parent chain of your Arbitrum chain. These contracts include the rollup logic, bridging logic, fraud-proof contracts, and interfaces for interacting with Nitro precompiles. To verify the current version of your Nitro contracts, follow [these instructions](https://github.com/OffchainLabs/orbit-actions/blob/main/README.md#check-version-and-upgrade-path) while replacing the inbox contract address and network name with that of your Arbitrum chain. This information will allow you to find the correct upgrade path for your Nitro contracts.
To update the WASM module root and deploy your chain's Nitro contracts to the parent chain for the most recent ArbOS release, you will need the following inputs (obtained from the [requirements for the targeted ArbOS release](/run-arbitrum-node/arbos-releases/01-overview.mdx)):
@@ -35,7 +35,7 @@ To update the WASM module root and deploy your chain's Nitro contracts to the pa
Once you have the WASM module root and have identified the required `nitro-contracts` version for the target ArbOS release, if any, [please follow the instructions in this guide](https://github.com/OffchainLabs/orbit-actions?tab=readme-ov-file#nitro-contracts-upgrades) for specific actions based on the `nitro-contracts` version you are deploying. Note that each ArbOS release will require performing this step with a different WASM module root and may require a different version of `nitro-contracts`. The guide linked above will be kept updated with the instructions for each specific ArbOS release.
-The `WASM module root` is a 32-byte hash created from the Merkelized Go replay binary and its dependencies. When ArbOS is upgraded, a new WASM module root is generated due to modifications in the State Transition Function (STF). This new WASM module root must be set in the rollup contract on the parent chain. For example, the WASM module root for ArbOS 51 Dia is `0x8a7513bf7bb3e3db04b0d982d0e973bcf57bf8b88aef7c6d03dba3a81a56a499`.
+The `WASM module root` is a 32-byte hash created from the Merkelized Go replay binary and its dependencies. When ArbOS is upgraded, a new WASM module root is generated due to modifications in the State Transition Function (STF). This new WASM module root must be set in the rollup contract on the parent chain. For example, the WASM module root for ArbOS 50 Dia is `0x2c54f6e9e378ba320ed9c713a1d9f067a572b1437e4f1c40b1a915d3066c04f2`.
To set the WASM module root manually (i.e., not using the above guide), use the `Rollup proxy` contract's [`setWasmModuleRoot`](https://github.com/OffchainLabs/nitro-contracts/blob/38a70a5e14f8b52478eb5db08e7551a82ced14fe/src/rollup/RollupAdminLogic.sol#L321) method. Note that the `upgrade executor` contract on the parent chain is the designated owner of the Rollup contract, so the **chain owner account** needs to initiate a call to the `upgrade executor` contract in order to perform the upgrade. This call should include the correct calldata for setting the new WASM module root.
@@ -49,7 +49,7 @@ WASM module roots are backward compatible, so upgrading them before an ArbOS ver
To schedule an ArbOS version upgrade for your Arbitrum chain, [follow this guide](https://github.com/OffchainLabs/orbit-actions/tree/main/scripts/foundry/arbos-upgrades/at-timestamp). In addition to the upgrade action contract address and the account address for the chain owner account, you will need the following inputs:
-1. **`newVersion`**: Specify the ArbOS version you wish to upgrade to (e.g., `51`).
+1. **`newVersion`**: Specify the ArbOS version you wish to upgrade to (e.g., `50`).
2. **`timestamp`**: Set the exact UNIX timestamp at which you want your Arbitrum chain (Orbit) to transition to the new ArbOS version.
If you would prefer to do this manually, simply call the [`scheduleArbOSUpgrade`](https://github.com/OffchainLabs/nitro-precompile-interfaces/blob/fe4121240ca1ee2cbf07d67d0e6c38015d94e704/ArbOwner.sol#L116) function on the `ArbOwner` precompile of the Arbitrum chain(s) you're upgrading. Because this is an administrative action (similar to upgrading your Wasm module root), the **chain owner account** must call the target chain's `upgrade executor` contract with the appropriate calldata in order to invoke the `scheduleArbOSUpgrade` function of the ArbOwner precompile. This will schedule the ArbOS upgrade using the specified version and timestamp.
@@ -64,14 +64,14 @@ To upgrade immediately (without scheduling), set the timestamp to `0`.
You can obtain the current ArbOS version of your chain by calling `ArbSys.ArbOSVersion()`. Keep in mind that this function adds `55` to the current ArbOS version. For example, if your chain is running on ArbOS 10, calling this function will return `65`.
-When scheduling the ArbOS upgrade through `ArbOwner.scheduleArbOSUpgrade` you must use the actual ArbOS version you're upgrading to. For example, if you're upgrading to ArbOS 51, you will pass `51` when calling this function.
+When scheduling the ArbOS upgrade through `ArbOwner.scheduleArbOSUpgrade` you must use the actual ArbOS version you're upgrading to. For example, if you're upgrading to ArbOS 50, you will pass `50` when calling this function.
#### Step 4: Enable ArbOS specific configurations or feature flags (not always required)
-For some ArbOS upgrades, such as [ArbOS 51 Dia](/run-arbitrum-node/arbos-releases/arbos51.mdx), there may be additional requirements or steps that need to be satisfied to ensure your Arbitrum chain can use all of the new features and improvements made available in that particular ArbOS release.
+For some ArbOS upgrades, such as [ArbOS 50 Dia](/run-arbitrum-node/arbos-releases/arbos50.mdx), there may be additional requirements or steps that need to be satisfied to ensure your Arbitrum chain can use all of the new features and improvements made available in that particular ArbOS release.
-If there are additional requirements for the targeted ArbOS release you're attempting to upgrade to; the additional requirements will be listed on the reference pages for [the targeted ArbOS release](/run-arbitrum-node/arbos-releases/01-overview.mdx#list-of-available-arbos-releases). For example, the additional requirements for Arbitrum chains upgrading to ArbOS 51 can be found [here on the ArbOS 51 docs](/run-arbitrum-node/arbos-releases/arbos51.mdx).
+If there are additional requirements for the targeted ArbOS release you're attempting to upgrade to; the additional requirements will be listed on the reference pages for [the targeted ArbOS release](/run-arbitrum-node/arbos-releases/01-overview.mdx#list-of-available-arbos-releases). For example, the additional requirements for Arbitrum chains upgrading to ArbOS 50 can be found [here on the ArbOS 50 docs](/run-arbitrum-node/arbos-releases/arbos50.mdx).
Congratulations! You've upgraded your Arbitrum chain(s) to the specified ArbOS version.
diff --git a/docs/launch-arbitrum-chain/02-start-your-journey.mdx b/docs/launch-arbitrum-chain/02-start-your-journey.mdx
new file mode 100644
index 0000000000..7efc94581b
--- /dev/null
+++ b/docs/launch-arbitrum-chain/02-start-your-journey.mdx
@@ -0,0 +1,12 @@
+---
+title: 'Start your journey into Arbitrum chains'
+sidebar_label: 'Arbitrum chain: get started'
+description: 'Learn how to get started with Arbitrum chains'
+sidebar_position: '2'
+author: 'anegg0'
+sme: 'mahsamoosavi'
+editor: 'anegg0'
+target_audience: 'Prospective chain owners, and operators'
+tags: ['hide-from-search']
+unlisted: true
+---
diff --git a/docs/launch-arbitrum-chain/03-arbitrum-license.mdx b/docs/launch-arbitrum-chain/03-arbitrum-license.mdx
new file mode 100644
index 0000000000..b727357f1a
--- /dev/null
+++ b/docs/launch-arbitrum-chain/03-arbitrum-license.mdx
@@ -0,0 +1,12 @@
+---
+title: 'The Arbitrum chain license'
+sidebar_label: 'Arbitrum chain license'
+description: ''
+sidebar_position: 2
+author:
+sme:
+editor: anegg0
+target_audience:
+tags: ['hide-from-search']
+unlisted: true
+---
diff --git a/docs/launch-arbitrum-chain/04-maintain-your-chain/01-bridging.mdx b/docs/launch-arbitrum-chain/04-maintain-your-chain/01-bridging.mdx
new file mode 100644
index 0000000000..598237adc9
--- /dev/null
+++ b/docs/launch-arbitrum-chain/04-maintain-your-chain/01-bridging.mdx
@@ -0,0 +1,9 @@
+---
+title: 'Bridging'
+description: 'PLACEHOLDER'
+author:
+sme:
+content_type: how-to
+tags: ['hide-from-search']
+unlisted: true
+---
diff --git a/docs/launch-arbitrum-chain/04-maintain-your-chain/02-monitoring.mdx b/docs/launch-arbitrum-chain/04-maintain-your-chain/02-monitoring.mdx
new file mode 100644
index 0000000000..649e63f45f
--- /dev/null
+++ b/docs/launch-arbitrum-chain/04-maintain-your-chain/02-monitoring.mdx
@@ -0,0 +1,9 @@
+---
+title: 'Monitoring'
+description: 'Learn how to configure your Arbitrum chain with a custom bond and validator configurations'
+author:
+sme:
+content_type: how-to
+tags: ['hide-from-search']
+unlisted: true
+---
diff --git a/docs/launch-arbitrum-chain/04-maintain-your-chain/04-guidance/01-decentralization-security.mdx b/docs/launch-arbitrum-chain/04-maintain-your-chain/04-guidance/01-decentralization-security.mdx
new file mode 100644
index 0000000000..272aab0202
--- /dev/null
+++ b/docs/launch-arbitrum-chain/04-maintain-your-chain/04-guidance/01-decentralization-security.mdx
@@ -0,0 +1,9 @@
+---
+title: 'Decentralization and Security'
+description: 'PLACEHOLDER'
+author:
+sme:
+content_type: how-to
+tags: ['hide-from-search']
+unlisted: true
+---
diff --git a/docs/launch-arbitrum-chain/04-maintain-your-chain/04-guidance/02-guidance-on-altda.mdx b/docs/launch-arbitrum-chain/04-maintain-your-chain/04-guidance/02-guidance-on-altda.mdx
new file mode 100644
index 0000000000..eca5d9a361
--- /dev/null
+++ b/docs/launch-arbitrum-chain/04-maintain-your-chain/04-guidance/02-guidance-on-altda.mdx
@@ -0,0 +1,9 @@
+---
+title: 'Guidance on AltDA'
+description: 'PLACEHOLDER'
+author:
+sme:
+content_type: how-to
+tags: ['hide-from-search']
+unlisted: true
+---
diff --git a/docs/launch-arbitrum-chain/06-third-party-integrations/03-integrations.mdx b/docs/launch-arbitrum-chain/06-third-party-integrations/03-integrations.mdx
new file mode 100644
index 0000000000..34c95d1a99
--- /dev/null
+++ b/docs/launch-arbitrum-chain/06-third-party-integrations/03-integrations.mdx
@@ -0,0 +1,15 @@
+---
+title: 'Integrations'
+description: 'PLACEHOLDER'
+author:
+sme:
+content_type: how-to
+tags: ['hide-from-search']
+unlisted: true
+---
+
+## Compatible third-party integrations
+
+## LayerZero OFT
+
+## xERC-20 Gateway
diff --git a/docs/launch-arbitrum-chain/07-arbitrum-node-runners/arbitrum-chain-node-providers.mdx b/docs/launch-arbitrum-chain/07-arbitrum-node-runners/arbitrum-chain-node-providers.mdx
new file mode 100644
index 0000000000..142b208a4a
--- /dev/null
+++ b/docs/launch-arbitrum-chain/07-arbitrum-node-runners/arbitrum-chain-node-providers.mdx
@@ -0,0 +1,9 @@
+---
+title: 'Arbitrum chain Node Providers'
+description: 'PLACEHOLDER'
+author:
+sme:
+content_type: how-to
+tags: ['hide-from-search']
+robots: noindex
+---
diff --git a/docs/launch-arbitrum-chain/08-ecosystem-support/01-arbitrum-chain-portal.mdx b/docs/launch-arbitrum-chain/08-ecosystem-support/01-arbitrum-chain-portal.mdx
new file mode 100644
index 0000000000..7c7c6b0fe6
--- /dev/null
+++ b/docs/launch-arbitrum-chain/08-ecosystem-support/01-arbitrum-chain-portal.mdx
@@ -0,0 +1,9 @@
+---
+title: 'Arbitrum chains Portal'
+description: 'PLACEHOLDER'
+author:
+sme:
+content_type: how-to
+tags: ['hide-from-search']
+unlisted: true
+---
diff --git a/docs/launch-arbitrum-chain/08-ecosystem-support/03-get-listed-arbitrum-chain-platforms.mdx b/docs/launch-arbitrum-chain/08-ecosystem-support/03-get-listed-arbitrum-chain-platforms.mdx
new file mode 100644
index 0000000000..416349caf7
--- /dev/null
+++ b/docs/launch-arbitrum-chain/08-ecosystem-support/03-get-listed-arbitrum-chain-platforms.mdx
@@ -0,0 +1,9 @@
+---
+title: 'Get listed on Arbitrum platforms'
+description: 'PLACEHOLDER'
+author:
+sme:
+content_type: how-to
+tags: ['hide-from-search']
+unlisted: true
+---
diff --git a/docs/launch-arbitrum-chain/arbitrum-chain-supported-parent-chains.mdx b/docs/launch-arbitrum-chain/arbitrum-chain-supported-parent-chains.mdx
new file mode 100644
index 0000000000..01cd8daf02
--- /dev/null
+++ b/docs/launch-arbitrum-chain/arbitrum-chain-supported-parent-chains.mdx
@@ -0,0 +1,60 @@
+---
+title: 'Supported parent chains'
+sidebar_label: 'Supported parent chains'
+description: 'List of officially supported parent chains for Arbitrum chains'
+sidebar_position: '2'
+author: 'mahsamoosavi'
+sme: 'mahsamoosavi'
+editor: 'anegg0'
+target_audience: 'Arbitrum chains owners and operators'
+---
+
+This page lists the parent chains that are officially supported for Arbitrum chains, including mainnets, testnets, and options for local development. While the Arbitrum chain SDK provides functionality to enable custom parent chains, support is limited to the chains explicitly listed on this page. Developers are welcome to use the Arbitrum chain SDK to configure and deploy custom parent chains; however, such setups, including deploying the required creator and template contracts, are beyond the scope of our official support.
+
+:::caution Supported Chains Only
+
+Please note that we cannot guarantee compatibility or offer assistance for custom configurations outside of the supported chains detailed below.
+
+:::
+
+#### Supported chains
+
+
+
+### Adding custom parent chains
+
+Although Arbitrum chains primarily supports a predefined set of chains, it provides developers with the flexibility to add custom parent chains through [the `registerCustomParentChain` function of the Arbitrum (Orbit) chain SDK](https://github.com/OffchainLabs/arbitrum-orbit-sdk/blob/729facd4d50156d7a84cf1204552c900eb86655c/src/chains.ts#L102). This capability enables integration with chains beyond the officially supported list, offering opportunities for customization and expanding the Arbtrium ecosystem.
+
+However, adding a custom chain requires deploying essential contracts, such as the creator contract and template contract, on the target chain. These contracts are fundamental for ensuring the proper functioning of the Arbitrum chain framework on the custom chain.
diff --git a/docs/launch-arbitrum-chain/partials/_arbitrum-chain-public-preview-banner-partial.md b/docs/launch-arbitrum-chain/partials/_arbitrum-chain-public-preview-banner-partial.md
new file mode 100644
index 0000000000..3b33fd052a
--- /dev/null
+++ b/docs/launch-arbitrum-chain/partials/_arbitrum-chain-public-preview-banner-partial.md
@@ -0,0 +1,7 @@
+:::info PUBLIC PREVIEW, MAINNET READY
+
+Arbitrum chains are now [Mainnet ready](/launch-arbitrum-chain/concepts/public-preview-expectations.mdx#arbitrum-chains-are-mainnet-ready-but-deploy-to-testnet-first)! Note that Arbitrum is still a **[public preview](/launch-arbitrum-chain/concepts/public-preview-expectations.mdx)** capability - the Arbitrum product and its supporting documentation may change significantly as we capture feedback from readers like you.
+
+To provide feedback, click the _Request an update_ button at the top of this document, [join the Arbitrum Discord](https://discord.gg/arbitrum), or reach out to our team directly by completing [this form](http://bit.ly/3yy6EUK).
+
+:::
diff --git a/docs/node-running/how-tos/data-availability-committee/partials/parameters/_ipfs-parameters.mdx b/docs/node-running/how-tos/data-availability-committee/partials/parameters/_ipfs-parameters.mdx
new file mode 100644
index 0000000000..2ff2d05dfe
--- /dev/null
+++ b/docs/node-running/how-tos/data-availability-committee/partials/parameters/_ipfs-parameters.mdx
@@ -0,0 +1,5 @@
+| Parameter | Description |
+| --------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
+| --data-availability.ipfs-storage.enable | Enables storage/retrieval of sequencer batch data from IPFS |
+| --data-availability.ipfs-storage.profiles | Comma separated list of IPFS profiles to use |
+| --data-availability.ipfs-storage.read-timeout | Timeout for IPFS reads, since by default it will wait forever. Treat timeout as not found (default 1m0s) |
diff --git a/docs/node-running/sequencer-content-map.mdx b/docs/node-running/sequencer-content-map.mdx
deleted file mode 100644
index cd4465ec67..0000000000
--- a/docs/node-running/sequencer-content-map.mdx
+++ /dev/null
@@ -1,38 +0,0 @@
----
-id: sequencer-content-map
-title: Sequencer
-sidebar_label: Sequencer
----
-
-import Card from '@site/src/components/Cards/Card';
-
-# Sequencer
-
-Keep your node in sync with the sequencer.
-
-
-
-
-
-
diff --git a/docs/notices/arbos51-arbsepolia-upgrade-notice.mdx b/docs/notices/arbos51-arbsepolia-upgrade-notice.mdx
deleted file mode 100644
index bd51d461ee..0000000000
--- a/docs/notices/arbos51-arbsepolia-upgrade-notice.mdx
+++ /dev/null
@@ -1,23 +0,0 @@
----
-title: 'ArbOS 51 activation on ArbSepolia'
-description: Upgrade notices for ArbOS 51 activation on ArbSepolia
-user_story: As an Arbitrum chain owner or node operator, I want to configure my chain/nodes to run on ArbSepolia.
-content_type: notice
----
-
-On Monday, December 1, 2025 at 17:00 UTC, ArbOS 51 will activate on the Arbitrum Sepolia chain.
-
-:::warning Action required
-
-Arbitrum Sepolia node operators **must upgrade** to Nitro **`v3.9.3`** ahead of this activation to continue syncing the chain.
-
-- **Docker image:** `offchainlabs/nitro-node:v3.9.3-8bc5554`
-- **Release notes:** https://github.com/OffchainLabs/nitro/releases/tag/v3.9.3
-
-:::
-
-### **Context**
-
-ArbOS 51 is a follow-up to ArbOS 50 that fixes issues with the Pricing Framework. ArbOS 50 was previously activated on Sepolia, but the Gas Target & Pricing Framework changes remained disabled pending this fix. ArbOS 51 now completes that work.
-
-Upstream governance items (ArbOS 50 + Gas Target & Pricing Framework) are being bundled into a single on-chain vote. Both components have passed Snapshot temperature checks, with the Tally vote expected to open on or around **December 4, 2025**.
diff --git a/docs/partials/_macos-verify-tx-issue-banner-partial.mdx b/docs/partials/_macos-verify-tx-issue-banner-partial.mdx
new file mode 100644
index 0000000000..11df1da02c
--- /dev/null
+++ b/docs/partials/_macos-verify-tx-issue-banner-partial.mdx
@@ -0,0 +1,13 @@
+---
+partial_type: banner
+title: 'MacOS Verification Issue Banner'
+description: 'Warning banner for MacOS transaction verification limitations'
+author: anegg0
+last_reviewed: 2025-01-15
+---
+
+:::caution MacOS Users
+
+This feature is currently unavailable to MacOS users. We are working on a solution and will update this page when this limitation is resolved.
+
+:::
diff --git a/docs/partials/_reference-arbitrum-rpc-endpoints-partial.mdx b/docs/partials/_reference-arbitrum-rpc-endpoints-partial.mdx
index 4be212eb7c..c9ea9fd096 100644
--- a/docs/partials/_reference-arbitrum-rpc-endpoints-partial.mdx
+++ b/docs/partials/_reference-arbitrum-rpc-endpoints-partial.mdx
@@ -12,7 +12,7 @@ last_reviewed: 2025-01-15
- Unlike the RPC Urls, the Sequencer endpoints only support `eth_sendRawTransaction` and `eth_sendRawTransactionConditional` calls.
- Arbitrum public RPCs do not provide Websocket support.
-- View the [faucets](/stylus/reference/testnet-information.md#faucets) for testnet Sepolia tokens on L2.
+- View the [faucets](docs/for-devs/dev-tools-and-resources/chain-info.mdx) for testnet Sepolia tokens on L2.
:::
diff --git a/docs/partials/_troubleshooting-arbitrum-chain-partial.mdx b/docs/partials/_troubleshooting-arbitrum-chain-partial.mdx
index b67ef7407b..7b61754aa8 100644
--- a/docs/partials/_troubleshooting-arbitrum-chain-partial.mdx
+++ b/docs/partials/_troubleshooting-arbitrum-chain-partial.mdx
@@ -1,67 +1,59 @@
----
-partial_type: troubleshooting
-title: 'Arbitrum Chain Troubleshooting Guide'
-description: 'Common issues and solutions for Arbitrum chain operations'
-author: anegg0
-last_reviewed: 2025-11-06
----
+### Can I use the Chain SDK to deploy a mainnet chain?
-### Can I use Orbit to deploy a mainnet chain?
+Yes! The Arbitrum Chain SDK's core technology has undergone a comprehensive audit and is now capable of supporting deployments to mainnet. You can read more about it in our [preview expectations notice](https://docs.arbitrum.io/launch-orbit-chain/concepts/public-preview-expectations#arbitrum-orbit-is-mainnet-ready-but-deploy-to-testnet-first).
-Yes! Arbitrum Orbit's core technology has undergone a comprehensive audit and is now capable of supporting deployments to mainnet. You can read more about it [in our public preview annoucement](https://docs.arbitrum.io/launch-orbit-chain/concepts/public-preview-expectations#arbitrum-orbit-is-mainnet-ready-but-deploy-to-testnet-first).
+### Do I need permission/license to launch an Arbitrum chain?
-### Do I need permission/license to launch an Orbit chain?
-
-You can launch any Arbitrum Orbit chain permissionlessly.
+You can launch any Arbitrum chain permissionlessly.
Nitro's license is under a [Business Source license](https://github.com/OffchainLabs/nitro?tab=License-1-ov-file), similar to DeFi protocols like Uniswap and Aave, among others. This license contains an Additional Use Grant that permits the permissionless deployment of Nitro software on blockchains that settle to Arbitrum One or Nova.
-However, Arbitrum Orbit chains that settle to a parent chain other than Arbitrum One or Nova are subject to additional licensing guidelines under the [AEP](https://docs.arbitrum.foundation/aep/ArbitrumExpansionProgramTerms.pdf).
+However, Arbitrum chains that settle to a parent chain other than Arbitrum One or Nova are subject to additional licensing guidelines under the [AEP](https://docs.arbitrum.foundation/aep/ArbitrumExpansionProgramTerms.pdf).
### Does Arbitrum officially deploy and/or maintain L3s for external teams?
-No. Teams are required to deploy and maintain their Arbitrum Orbit chains. There are, however, several RaaS (Rollup as a Service) providers that can deploy and maintain your Arbitrum Orbit chain on your behalf.
+No. Teams are required to deploy and maintain their Arbitrum chains. There are, however, several RaaS (Rollup as a Service) providers that can deploy and maintain your Arbitrum chain on your behalf.
-### Can I modify Orbit's underlying technology to customize my chain?
+### Can I modify the underlying technology to customize my Arbitrum chain?
Yes, you can make any changes you require to the underlying Nitro code base.
-### What Data Availability (DA) solutions are currently available for Orbit chains?
+### What Data Availability (DA) solutions are currently available for Arbitrum chains?
-Arbitrum Orbit currently supports three different DA solutions:
+Arbitrum chains currently support three different DA solutions:
- Rollup, posting data to the parent chain, which ultimately posts the data to Ethereum.
- AnyTrust, posting data to a Data Availability Committee, selected by the chain owner.
- Celestia, posting data to the [Celestia network](https://blog.celestia.org/celestia-is-first-modular-data-availability-network-to-integrate-with-arbitrum-orbit/).
Note that using AnyTrust provides the chain owner with the most flexibility and the most cost-effective fees.
-### What token is used to pay gas fees on Orbit chains?
+### What token is used to pay gas fees on Arbitrum chains?
-By default, Arbitrum Orbit chains pay gas in `ETH`. However, Arbitrum Orbit chains that use AnyTrust are configurable to use any `ERC-20` token for the gas fee token of the chain.
+By default, Arbitrum chains pay gas in `ETH`. However, Arbitrum chains that use AnyTrust are configurable to use any `ERC-20` token for the gas fee token of the chain.
-### Can I use Ethereum toolkits to develop on my Orbit chain?
+### Can I use Ethereum toolkits to develop on my Arbitrum chain?
-Arbitrum Orbit chains are fully EVM-compatible. Most tools that support Ethereum should be able to support an Arbitrum Orbit chain. There are, however, specific differences that developers need to consider when building on an Orbit chain. You can find them [in our Arbitrum vs. Ethereum overview](https://docs.arbitrum.io/for-devs/concepts/differences-between-arbitrum-ethereum/overview).
+Arbitrum chains are fully EVM-compatible. Most tools that support Ethereum should be able to support an Arbitrum chain. There are, however, specific differences that developers need to consider when building on an Arbitrum chain. You can find them in our [overview of the differences between Arbitrum and Ethereum](https://docs.arbitrum.io/for-devs/concepts/differences-between-arbitrum-ethereum/overview).
-### Do Orbit chains have any built-in AA solution?
+### Do Arbitrum chains have any built-in AA solution?
Not by default, but they can be customized to have native AA.
-### Is there any cross-chain bridging solution between two Orbit chains?
+### Is there any cross-chain bridging solution between two Arbitrum chains?
-There is currently no native Orbit-to-Orbit chain bridging solution, except for going through the parent chain (even if they share the same parent chain). However, many third-party bridges have expressed interest in supporting Arbitrum Orbit chains.
+There is currently no native Arbitrum-to-Arbitrum chain bridging solution, except for going through the parent chain (even if they share the same parent chain). However, many third-party bridges have expressed interest in supporting Arbitrum chains.
-### Is there an official block explorer for Orbit chains?
+### Is there an official block explorer for Arbitrum chains?
-Arbitrum Orbit chains deployments usually come with an open-source Blockscout explorer by default, but there are many third-party solutions that have expressed interest in supporting Arbitrum Orbit chains.
+Arbitrum chains deployments usually come with an open-source Blockscout explorer by default, but there are many third-party solutions that have expressed interest in supporting Arbitrum chains.
-### Is there any indexing solution that supports Orbit chains?
+### Is there any indexing solution that supports Arbitrum chains?
-Similar to bridges and block explorers, there are many third-party indexing solutions that have expressed interest in supporting Arbitrum Orbit chains.
+Similar to bridges and block explorers, there are many third-party indexing solutions that have expressed interest in supporting Arbitrum chains.
-### Can I increase the maximum contract size for my Orbit chain?
+### Can I increase the maximum contract size for my Arbitrum chain?
-Yes, Arbitrum Orbit chains support an increased smart contract size limit of up to 96kB. You can use our [Orbit SDK](https://github.com/OffchainLabs/arbitrum-orbit-sdk) and configure the parameters `[MaxCodeSize](https://github.com/OffchainLabs/arbitrum-orbit-sdk/blob/main/src/prepareChainConfig.ts#L29)`[ and ](https://github.com/OffchainLabs/arbitrum-orbit-sdk/blob/main/src/prepareChainConfig.ts#L29)`[MaxInitCodeSize](https://github.com/OffchainLabs/arbitrum-orbit-sdk/blob/main/src/prepareChainConfig.ts#L29)` when calling `[prepareNodeConfig](https://github.com/OffchainLabs/arbitrum-orbit-sdk/blob/main/examples/prepare-node-config/index.ts#L43)`. Once deployed, you cannot change the parameters for the smart contract size limit through an upgrade.
+Yes, Arbitrum chains support an increased smart contract size limit of up to 96kB. You can use our [Chain SDK](https://github.com/OffchainLabs/arbitrum-orbit-sdk) and configure the parameters `[MaxCodeSize](https://github.com/OffchainLabs/arbitrum-orbit-sdk/blob/main/src/prepareChainConfig.ts#L29)`[ and ](https://github.com/OffchainLabs/arbitrum-orbit-sdk/blob/main/src/prepareChainConfig.ts#L29)`[MaxInitCodeSize](https://github.com/OffchainLabs/arbitrum-orbit-sdk/blob/main/src/prepareChainConfig.ts#L29)` when calling `[prepareNodeConfig](https://github.com/OffchainLabs/arbitrum-orbit-sdk/blob/main/examples/prepare-node-config/index.ts#L43)`. Once deployed, you cannot change the parameters for the smart contract size limit through an upgrade.
### How can I modify Nitro to force posting an invalid assertion and test the fraud proof mechanism?
@@ -74,9 +66,9 @@ go test ./system_tests/ -tags=challengetest -run=TestChallenge
### What fee collectors can be configured on my chain?
-Four fee types are configurable on an Orbit chain:
+Four fee types are configurable on an Arbitrum chain:
-- **L2 base fee**: L2 execution fees corresponding to the minimum base price of the chain. This fee is deposited into the infraFeeAccount, which can be set by calling `ArbOwner.setInfraFeeAccount().`
+- **L2 base fee**: L2 execution fees corresponding to the minimum base price of the chain. This fee is deposited into the `infraFeeAccount`, which can be set by calling `ArbOwner.setInfraFeeAccount().`
- **L2 surplus fee**: L2 execution fees above the minimum base price (in the case of congestion). This fee goes to the `networkFeeAccount`, which can be set by calling `ArbOwner.setNetworkFeeAccount().`
- **L1 base fee**: Relative fees for posting a transaction on the parent chain. This fee is paid ultimately to the fee collector of the active batch poster. A call to `SequencerInbox.setIsBatchPoster()` on the parent chain will set the batch poster. Delegating a different fee collector for that batch poster can be specified by calling `ArbAggregator.setFeeCollector()`.
- **L1 surplus fee**: Any extra fees rewarded to the batch poster. This is paid to a specific `L1RewardRecipient`, which can be set by calling `ArbOwner.setL1PricingRewardRecipient()`.
@@ -86,7 +78,7 @@ To learn more about precompiles, refer to the [Precompiles reference page](https
### What is the lowest you can set the base fee to?
-You can set the base fee to any amount to charge users less. You can even set it to `0` (however, this would open the chain to DOS attacks). If the Orbit base fee is `0`, users are then only paying for the cost of DA.
+You can set the base fee to any amount to charge users less. You can even set it to `0` (however, this would open the chain to DOS attacks). If the Arbitrum chain base fee is `0`, users are then only paying for the cost of DA.
### How does fee collection work in Nitro?
@@ -134,7 +126,7 @@ Test this thoroughly on a testnet first.
### What is the max theoretical TPS for an Arbitrum Chain?
-Max TPS is a challenging metric to measure, as it relies on network activity and the type of submitted transactions. We can, however, calculate the max throughput using default orbit chain parameters.
+Max TPS is a challenging metric to measure, as it relies on network activity and the type of submitted transactions. We can, however, calculate the max throughput using default Arbitrum chain parameters.
The actual maximum throughput depends on the configurable execution parameters:
@@ -152,7 +144,7 @@ The actual maximum throughput depends on the configurable execution parameters:
### Why is the WETH Gateway not necessary for custom gas token chains?
-The `WETH` gateway used in the token bridge is a special, custom gateway that unwraps the `WETH` deposits and sends them to the Arbitrum chain, then wraps them again on the Arbitrum chain. Since `ETH` is the gas token in the Arbitrum (Orbit) chain, there's no need to perform this operation, so you can use a standard `ERC-20` for `WETH` (this is the default case of the token bridge so that you wouldn't need a special `WETH` gateway).
+The `WETH` gateway used in the token bridge is a special, custom gateway that unwraps the `WETH` deposits and sends them to the Arbitrum chain, then wraps them again on the Arbitrum chain. Since `ETH` is the gas token in the Arbitrum chain, there's no need to perform this operation, so you can use a standard `ERC-20` for `WETH` (this is the default case of the token bridge so that you wouldn't need a special `WETH` gateway).
If you want to enable extra custom operations with `WETH`, you can create a custom token and a custom gateway to handle this case.
diff --git a/docs/partials/_troubleshooting-bridging-partial.mdx b/docs/partials/_troubleshooting-bridging-partial.mdx
index 43cc06fbfc..7172a50dd8 100644
--- a/docs/partials/_troubleshooting-bridging-partial.mdx
+++ b/docs/partials/_troubleshooting-bridging-partial.mdx
@@ -1,11 +1,3 @@
----
-partial_type: troubleshooting
-title: 'Bridging Troubleshooting Guide'
-description: 'Common issues and solutions for bridging assets to/from Arbitrum'
-author: anegg0
-last_reviewed: 2025-11-06
----
-
### How do I move assets between One and Nova?
Both Arbitrum One and Arbitrum Nova run as layers on top of Ethereum. Thus, you can always move assets between the two chains in two steps by going "through" Ethereum. In other words: withdraw your assets from Arbitrum One to Ethereum and then deposit them onto Nova, or conversely, withdraw your assets from Nova to Ethereum and then deposit them to Arbitrum One. You can complete these steps using the [Arbitrum Bridge](https://bridge.arbitrum.io/).
diff --git a/docs/partials/_troubleshooting-building-partial.mdx b/docs/partials/_troubleshooting-building-partial.mdx
index f1496f7d10..c6dc5e4687 100644
--- a/docs/partials/_troubleshooting-building-partial.mdx
+++ b/docs/partials/_troubleshooting-building-partial.mdx
@@ -1,11 +1,3 @@
----
-partial_type: troubleshooting
-title: 'Building Troubleshooting Guide'
-description: 'Common issues and solutions for building on Arbitrum'
-author: anegg0
-last_reviewed: 2025-11-06
----
-
### How does gas work on Arbitrum?
Fees on Arbitrum chains are collected on L2 in the chains' native currency (ETH on both Arbitrum One and Nova).
@@ -62,9 +54,9 @@ There are two differences between `createRetryableTicket` and `unsafeCreateRe
### Why do I get "custom tx type" errors when I use hardhat?
-In Arbitrum, we use a number of non-standard [EIP-2718](https://eips.ethereum.org/EIPS/eip-2718) typed transactions. See [here](https://developer.arbitrum.io/arbos/geth#transaction-types) for the full list and the rationale.
+In Arbitrum, we use a number of non-standard [EIP-2718](https://eips.ethereum.org/EIPS/eip-2718) typed transactions. Feel free to consult the [full list of transaction types](https://developer.arbitrum.io/arbos/geth#transaction-types) and the rationale.
-Note that if you're using Hardhat, [v2.12.2](https://github.com/NomicFoundation/hardhat/releases/tag/hardhat%402.12.2) added support for forking networks like Arbitrum with custom transaction types (find more information [here](https://github.com/NomicFoundation/hardhat/issues/2995)).
+Note that if you're using Hardhat, [v2.12.2](https://github.com/NomicFoundation/hardhat/releases/tag/hardhat%402.12.2) added support for forking networks like Arbitrum with custom transaction types (find more information [in this HardHat GitHub issue](https://github.com/NomicFoundation/hardhat/issues/2995)).
### Why does it look like two identical transactions consume a different amount of gas?
@@ -156,7 +148,7 @@ The WASM module root is a 32-byte hash, which is a Merkelization of the Go repla
The replay binary is much too large to post on-chain, so this hash is set in the L1 rollup contract to determine the correct replay binary during fraud proofs.
-You can find more information in [How to Customize your Orbit chain's behavior](https://docs.arbitrum.io/launch-orbit-chain/how-tos/customize-stf#step-4-enable-fraud-proofs).
+You can find more information in [How to Customize your Arbitrum chain's behavior](https://docs.arbitrum.io/launch-orbit-chain/how-tos/customize-stf#step-4-enable-fraud-proofs).
### Why do I get a "gas required exceeds allowance" when trying to estimate the gas costs of a request?
diff --git a/docs/partials/_troubleshooting-nodes-partial.mdx b/docs/partials/_troubleshooting-nodes-partial.mdx
index 767479be47..f9b857ea34 100644
--- a/docs/partials/_troubleshooting-nodes-partial.mdx
+++ b/docs/partials/_troubleshooting-nodes-partial.mdx
@@ -1,14 +1,6 @@
----
-partial_type: troubleshooting
-title: 'Node Running Troubleshooting Guide'
-description: 'Common issues and solutions for running Arbitrum nodes'
-author: anegg0
-last_reviewed: 2025-11-06
----
-
### How do I run a node?
-See instructions in the tutorial explaining how to [run a full node](https://developer.arbitrum.io/node-running/how-tos/running-a-full-node)!
+See instructions [in our guide about running a full node](https://developer.arbitrum.io/node-running/how-tos/running-a-full-node)!
### How to verify the integrity of the Nitro database I currently have?
@@ -36,7 +28,7 @@ Running an Arbitrum relay locally as a [Feed Relay](https://docs.arbitrum.io/nod
### How do I run a node locally for development?
-See instructions on [how to set up a local dev node](https://developer.arbitrum.io/node-running/how-tos/local-dev-node).
+See instructions in our [our guide about running a local devnet node](https://developer.arbitrum.io/node-running/how-tos/local-dev-node).
We recommend running Nitro nodes via Docker; to compile directly / run without Docker, you can follow the steps in [How to build Nitro locally](https://docs.arbitrum.io/node-running/how-tos/build-nitro-locally).
@@ -44,7 +36,7 @@ We recommend running Nitro nodes via Docker; to compile directly / run without D
The pre-Nitro stack is also called the "classic" stack. Full Nitro nodes start with a database that contains the information from the "classic" era.
-However, a Nitro node can't query archive information contained in "classic" blocks right away. To do that, you also need to run a classic node. You can find detailed instructions [in this detailed How To](https://developer.arbitrum.io/node-running/how-tos/running-a-classic-node) and set the parameter `—node.rpc.classic-redirect=your-classic-node-RPC`.
+However, a Nitro node can't query archive information contained in "classic" blocks right away. To do that, you also need to run a classic node ([instructions in our guide about running a classic node](https://developer.arbitrum.io/node-running/how-tos/running-a-classic-node)) and set the parameter `—node.rpc.classic-redirect=your-classic-node-RPC`.
Please note that this information only applies to Arbitrum One nodes. Arbitrum Nova and Sepolia nodes started with a Nitro stack, so they don't have "classic" data.
diff --git a/docs/partials/_troubleshooting-stylus-partial.mdx b/docs/partials/_troubleshooting-stylus-partial.mdx
index 28cdc3f141..8716e431a0 100644
--- a/docs/partials/_troubleshooting-stylus-partial.mdx
+++ b/docs/partials/_troubleshooting-stylus-partial.mdx
@@ -1,11 +1,3 @@
----
-partial_type: troubleshooting
-title: 'Stylus Troubleshooting Guide'
-description: 'Common issues and solutions for Stylus smart contract development'
-author: anegg0
-last_reviewed: 2025-11-06
----
-
### How does Stylus manage security issues in smart contracts when interacting with so many different languages?
All languages are compiled to WASM for them to be able to work with Stylus. So it just needs to verify that the produced WASM programs behave as they should inside the new virtual machine.
@@ -62,7 +54,9 @@ To solve this, it's usually enough to rename the `main.rs` file to a `lib.rs` fi
### How can I generate the ABI of my Stylus contract?
-The [cargo-stylus tool](https://github.com/OffchainLabs/cargo-stylus/tree/main#exporting-solidity-abis) has a command that allows you to export the ABI of your Stylus contract: `cargo stylus export-abi`.
+The
+
+has a command that allows you to export the ABI of your Stylus contract: `cargo stylus export-abi`.
If you're using the Stylus Rust SDK, you'll need to enable the `export-abi` feature in your `Cargo.toml` file like so:
@@ -83,3 +77,33 @@ fn main() {
stylus_hello_world::main();
}
```
+
+### How can I find out if the smart contract bytecode is from a Stylus contract or Solidity contract?
+
+You can check the first three bytes of the code at the contract address. If they read `0xEFF000`, it's a Stylus program. Otherwise, it will be from a Solidity contract.
+
+### I'm trying to work with cargo stylus on Windows and got error: failed to resolve: could not find unix in os
+
+Cargo Stylus is just compatible with Unix operating systems and not Windows. You should install `WSL` (Windows Subsystem for Linux) before using that and then use `WSL terminal` to install and use cargo stylus.
+
+### How can I return a struct from a function?
+
+Currently, the SDK doesn't support external structs directly, but support is in progress. For now, you can use tuples as output instead of structs.
+
+Keep in mind that structs are mapped to tuples by the Solidity ABI. You can read more about this mapping here: [Solidity ABI Specification](https://docs.soliditylang.org/en/v0.8.19/abi-spec.html#mapping-solidity-to-abi-types).
+
+Since the current SDK macro doesn't automatically handle struct-to-tuple conversions, you'll need to manually convert your struct into a tuple in the return type.
+
+### How can I get the WASM opcodes of the compiled Stylus contracts?
+
+To view the WASM opcodes, you can convert the WebAssembly binary (WASM) to WebAssembly Text (WAT). This conversion is possible using the WebAssembly Binary Toolkit (wabt). An easy way to do this is to use the online tool [Wasm-to-Wat converter](https://webassembly.github.io/wabt/demo/wasm2wat/), which is part of wabt.
+
+For more information or if you'd like to use the toolkit locally, you can find wabt on [GitHub](https://github.com/WebAssembly/wabt).
+
+### What is the difference between .set and .setter?
+
+---
+
+- **`.set`\*\***:\*\* This method is used directly to set a value in storage. It's a straightforward way to assign a value if you only need to perform a one-time set operation.
+- **`.setter`\*\***:\*\* This method provides a handle to a storage slot. It allows you to both `set` and `get` the value of the storage slot, making it more versatile if you need to access the current value and also update it.
+ If you plan to both retrieve and modify a value frequently, it's more efficient to use `.setter`, assign it to a variable, and then use the `.get` and `.set` methods on that variable. However, if you only need to set a value, you can use `.set` directly.
diff --git a/docs/partials/_troubleshooting-users-partial.mdx b/docs/partials/_troubleshooting-users-partial.mdx
index 6b23b50681..b571e8f493 100644
--- a/docs/partials/_troubleshooting-users-partial.mdx
+++ b/docs/partials/_troubleshooting-users-partial.mdx
@@ -1,11 +1,3 @@
----
-partial_type: troubleshooting
-title: 'User Troubleshooting Guide'
-description: 'Common issues and solutions for Arbitrum users'
-author: anegg0
-last_reviewed: 2025-11-06
----
-
### Why do I need ETH to use the Arbitrum network?
`ETH` is the currency used to pay gas fees on Arbitrum, and it powers all transactions on Arbitrum. You can bridge `ETH` (and other tokens) from Ethereum to Arbitrum through [Arbitrum's bridge](https://bridge.arbitrum.io/).
@@ -105,13 +97,9 @@ Although we currently don't maintain any stats dashboard for Arbitrum, you can f
There is no notion of a mempool on Arbitrum; transactions are processed on a first-come, first-served basis by the Sequencer. Thus, the gas price bid parameter does not affect the order in which transactions get processed.
-### Where can I find a list of the current validators of the Arbitrum chains?
-
-Validation on both Arbitrum One and Arbitrum Nova is currently allow-listed to a committee of public entities. You can see the list of validators **[here](https://docs.arbitrum.foundation/state-of-progressive-decentralization#allowlisted-validators)**. Governance currently has the power to change this status.
-
### Where can I find the current Data Availability Committee members?
-The Arbitrum Nova chain has a 7-party DAC, whose members can be seen **[here](https://docs.arbitrum.foundation/state-of-progressive-decentralization#data-availability-committee-members)**. Governance has the ability to remove or add members to the committee.
+The Arbitrum Nova chain has a 7-party DAC, whose members can be seen in the [Arbitrum Foundation's state of progressive decentralization status document](https://docs.arbitrum.foundation/state-of-progressive-decentralization#data-availability-committee-members). Governance has the ability to remove or add members to the committee.
### Can I withdraw my funds from Arbitrum back to Ethereum without going through the Sequencer? What about funds that are in a contract?
diff --git a/docs/partials/_under-construction-banner-partial.mdx b/docs/partials/_under-construction-banner-partial.mdx
new file mode 100644
index 0000000000..1a30c582f3
--- /dev/null
+++ b/docs/partials/_under-construction-banner-partial.mdx
@@ -0,0 +1,15 @@
+---
+partial_type: banner
+title: 'Under Construction Banner'
+description: 'Banner indicating document is under construction'
+author: anegg0
+last_reviewed: 2025-01-15
+---
+
+:::caution UNDER CONSTRUCTION
+
+This document is currently under construction.
+
+If you're waiting for this content to be completed, click the `Request an update` button at the top of this page to let us know. We incorporate these requests into our content prioritization discussions. **Thank you!**
+
+:::
diff --git a/docs/run-arbitrum-node/02-run-full-node.mdx b/docs/run-arbitrum-node/02-run-full-node.mdx
index 14571bbb55..6890c0ff22 100644
--- a/docs/run-arbitrum-node/02-run-full-node.mdx
+++ b/docs/run-arbitrum-node/02-run-full-node.mdx
@@ -42,7 +42,7 @@ Although there are beta and release candidate versions of the Arbitrum Nitro sof
:::
-Latest [Docker image](https://hub.docker.com/r/offchainlabs/nitro-node/tags): @@latestNitroNodeImage=offchainlabs/nitro-node:v3.9.3-8bc5554@@
+Latest [Docker image](https://hub.docker.com/r/offchainlabs/nitro-node/tags): @@latestNitroNodeImage=offchainlabs/nitro-node:v3.8.0-62c0aa7@@
### Database snapshots
diff --git a/docs/run-arbitrum-node/arbos-releases/arbos50.mdx b/docs/run-arbitrum-node/arbos-releases/arbos50.mdx
new file mode 100644
index 0000000000..deb96184bc
--- /dev/null
+++ b/docs/run-arbitrum-node/arbos-releases/arbos50.mdx
@@ -0,0 +1,152 @@
+---
+title: 'ArbOS 50 Dia'
+sidebar_label: 'ArbOS 50 Dia'
+sidebar_position: 2
+description: 'Learn about ArbOS 50 Dia release, including Fusaka upgrade support, new EIPs, bug fixes, and upgrade requirements for Arbitrum chain owners and node operators.'
+user_story: As an Arbitrum chain owner or node operator, I want to know what ArbOS 50 implements.
+author: zk-Lumi
+sme: zk-Lumi
+content_type: release note
+---
+
+import { VanillaAdmonition } from '@site/src/components/VanillaAdmonition';
+
+
+
+ArbOS 50 Dia is still pre-release. This document may change.
+
+
+
+This page is intended for all Arbitrum node operators and Arbitrum chain owners chains. This page summarizes the changes brought by ArbOS 50 Dia, and what you should do to ensure a seamless upgrade.
+
+
+
+Before Ethereum Parent chain Sepolia and Mainnet upgrade to Fusaka: ensure that your Nitro node version is upgraded and consensus layer client is configured as per the [Fusaka upgrade notice](../../notices/fusaka-upgrade-notice.mdx).
+
+
+The minimum Nitro version that supports ArbOS 50 "Dia" is [Nitro v3.9.0](https://github.com/OffchainLabs/nitro/releases/tag/v3.9.0), which is available on Docker Hub with the image tag `Noffchainlabs/nitro-node:v3.9.0-cca645a`. This release of Nitro is a mandatory upgrade for Arbitrum One and Nova node operators if adopted. For Arbitrum One and Nova, the ArbOS 50 "Dia" upgrade will require a governance vote to activate.
+
+As a refresher, ArbOS upgrades get treated as Arbitrum's equivalent of a hard fork. To learn more, refer to the [Arbitrum ArbOS upgrades forum post](https://forum.arbitrum.foundation/t/arbitrum-arbos-upgrades/19695). Note that ArbOS 50 Dia is an upgrade that builds upon [ArbOS 40 Callisto](./arbos40.mdx).
+
+### Requirements
+
+- Having read and understood the [ArbOS Software Releases Overview page](./01-overview.mdx).
+- Running [Nitro v3.9.0](https://github.com/OffchainLabs/nitro/releases/tag/v3.9.0) or higher, which is available on Docker Hub with the image tag `offchainlabs/nitro-node:v3.9.0-cca645a`.
+ - Note that it's important to run Nitro v3.9.0 only against trusted databases. If you want to use an untrusted database, you can first remove the `wasm` directory if it exists (potentially inside the `nitro` folder). Otherwise, the database may have malicious, unvalidated code that can result in remote code execution. Avoiding unvalidated code is also mitigated by ensuring you run the Arbitrum Nitro node inside Docker.
+- Running [nitro-contracts v3.1.0](https://github.com/OffchainLabs/nitro-contracts/releases/tag/v3.1.0) or higher
+ - If your chain isn't ready to activate BoLD, use [nitro-contracts v2.1.3](https://github.com/OffchainLabs/nitro-contracts/releases/tag/v2.1.3) instead; v3.0.0 or higher can't be used without activating BoLD.
+ - If you want to enable [Native Token Mint/Burn capabilities](#native-token-mint-and-burn) for your chain, you must use [nitro-contracts v3.1.1](https://github.com/OffchainLabs/nitro-contracts/releases/tag/v3.1.1) or higher.
+- WASM module root: `0x2c54f6e9e378ba320ed9c713a1d9f067a572b1437e4f1c40b1a915d3066c04f2`
+- If the Arbitrum chain posts blobs to a Fusaka-enabled parent chain, ensure that the consensus layer client is configured correctly as per the [Fusaka upgrade notice](../../notices/fusaka-upgrade-notice.mdx).
+
+### High-level description of ArbOS 50 changes
+
+ArbOS 50 Dia is an upgrade to support the relevant EVM changes that are a part of Ethereum's [Fusaka upgrade](https://ethereum.org/en/roadmap/fusaka/). Ethereum's mainnet upgrade to Fusaka is tentatively scheduled for [December 3, 2025 at epoch `411392`](https://notes.ethereum.org/@bbusa/fusaka-bpo-timeline). As a result, the majority of the ArbOS-specific changes revolve around implementing the relevant [Fusaka EIPs](https://eips.ethereum.org/EIPS/eip-7607) on Arbitrum Chains.
+
+Here's the list of all changes included in ArbOS 50 Dia:
+
+#### [EIP-7951: Precompile for secp256r1 curve support](https://eips.ethereum.org/EIPS/eip-7951)
+
+This EIP implements the same functionality and interface as [RIP-7212](https://github.com/ethereum/RIPs/blob/master/RIPS/rip-7212.md), which was activated as part of [ArbOS 31 Bianca](https://forum.arbitrum.foundation/t/aip-arbos-31-bianca-activation-of-arbitrum-stylus-rip-7212-support-nova-fee-router-proposal/25904). The main difference here is to add a point-at-infinity check and to update the comparison step in the signature verification algorithm. Developers should expect the same behavior as the EIP being proposed on Ethereum after Fusaka is activated.
+
+#### [EIP-7825: Transaction gas limit cap](https://eips.ethereum.org/EIPS/eip-7825)
+
+This EIP introduces a gas cap for individual transactions. The goal is to ensure fairer access to block space and improve network stability. For Arbitrum One and Arbitrum Nova we're proposing a 32 million gas limit (Child chain execution gas, not including parent chain gas) per transaction, which is the same as the current block gas limit. This 32 million gas limit diverges from the EIP's proposed limit of 16 million gas per transaction for Ethereum parent chain. Orbit chains can customize this value according to their chains' needs.
+
+#### [EIP-7642: eth/69 - history expiry and simpler receipts](https://eips.ethereum.org/EIPS/eip-7642)
+
+This networking upgrade removes deprecated fields used prior to Ethereum's Proof of Stake (PoS) transition. We're including this EIP as part of Geth upstream. This is a networking change that impacts mainly parent chain nodes. As Arbitrum nodes don't have a P2P layer, we don't expect this to have any impact on Arbitrum node operators.
+
+#### [EIP-7939: Count leading zeros (CLZ) opcode](https://eips.ethereum.org/EIPS/eip-7939)
+
+This EIP adds a new CLZ (Count Leading Zeros) opcode to efficiently count the number of zero bits at the start of a 256-bit number. This is a fundamental mathematical operation used in many algorithms, especially for mathematical computations, data compression, and cryptographic operations. Currently, implementing this operation in Solidity requires complex and expensive code - this opcode makes it much cheaper and faster.
+
+#### [EIP-7823: Set upper bounds for ModExp](https://eips.ethereum.org/EIPS/eip-7823)
+
+This EIP introduces an 8192-bit (1024 byte) limit on each input to the ModExp cryptographic precompile. ModExp has been a source of consensus bugs due to unbounded inputs. By setting practical limits that cover real-world use cases (like RSA verification), this reduces the testing surface area and paves the way for future replacement with more efficient EVM code.
+
+#### [EIP-7883: ModExp gas cost increase](https://eips.ethereum.org/EIPS/eip-7883)
+
+This EIP increases the gas cost of the ModExp cryptographic precompile to address underpriced operations. It raises the minimum cost from 200 to 500 gas and doubles the costs for large inputs over 32 bytes.
+
+#### [EIP-7910: eth_config JSON-RPC method](https://eips.ethereum.org/EIPS/eip-7910)
+
+This EIP provides a new RPC method that allows the Arbitrum Nitro node to respond with key configuration variables, offering node operators the ability to gain greater confidence that their Nitro nodes are correctly configured and prepared for upcoming forks. In future Nitro releases, we expect to include additional fields specific to Arbitrum chains. This update is at the RPC level and may be enabled later than the ArbOS 50 Dia upgrade.
+
+#### [EIP-2537: Precompile for BLS12-381 curve operations](https://eips.ethereum.org/EIPS/eip-2537)
+
+As [disclosed previously](https://forum.arbitrum.foundation/t/disclosure-of-support-for-eip-2537-on-arbitrum-one-and-nova/29720), the precompiled contracts for performing various operations over the BLS12-381 elliptic curve, including BLS Signature verification, were added but not properly enabled in [ArbOS 40 Callisto](./arbos40.mdx) as originally expected. ArbOS 50 Dia will now enable `EIP-2537`.
+
+#### ArbOS block limit change: Effective block gas limit
+
+Since ArbOS 50 introduces a `MaxTxGasLimit`, the State Transition Function (STF) will be relaxed in ArbOS 50 to allow the final transaction in a block to use up to the `MaxTxGasLimit` even if it would cause the block to exceed `MaxBlockGasLimit`. This means that the "Effective Block Gas Limit" is really `MaxBlockGasLimit + MaxTxGasLimit`. In previous versions of ArbOS, the Sequencer would skip transactions if the transaction request's `GasLimit` minus the parent chain data posting gas exceeded the gas remaining in the block.
+
+The new algorithm is more efficient because the sequencer doesn't need to keep searching through the queue of transactions to find one that fits in the remaining block gas, and can continue to add transactions until the unused block gas is 0.
+
+This change doesn't affect the `GasTarget`, and therefore doesn't affect how much overall gas per second the chain will use - only how transactions using that gas could be divided between different blocks.
+
+#### A few bug fixes
+
+- ArbOS didn't get updated for parent chain calldata price increase
+ - This change standardizes the calculation of gas units for compressed Batch calldata across the codebase by replacing hard-coded values with a method call (tokenGasUnits).
+- `EIP-7702` precompile delegation behavior divergence
+ - Previously, calls to precompiles could execute an INVALID opcode instead of succeeding with no execution. ArbOS 50 Dia will update code to align with `EIP-7702` spec to treat precompile code as empty during delegation.
+- ARM and x86 divergence
+ - This change adds a map to store transaction hash along with its gas used to bypass transaction execution for a problematic transaction execution which diverged between ARM and x86 architectures. This was added in to hardcode one transaction that caused the divergence on Arbitrum Sepolia, as disclosed in the [security council emergency Action report](https://forum.arbitrum.foundation/t/security-council-emergency-action-10-13-2025/30093).
+ - The default WASM Stack Depth value in ArbOS is now set to 22,000, preventing new chains from encountering the same divergence issue.
+
+#### Raising the gas target, increasing the min L2 base fee, & improving the pricing algorithm
+
+As part of our strategy for scaling Arbitrum technology, the ArbOS 50 release includes a slight change to improve the pricing algorithm for Arbitrum One and Nova. This improvement is aimed at reducing the severity, frequency, and duration of high L2 gas prices during periods of elevated demand on the network. Concretely, the changes are to:
+
+- Replace the current single gas target and single adjustment window, with a new model that employs multiple (higher) gas targets, measured over multiple adjustment windows.
+- Increase the default minimum L2 base fee from 0.01 gwei per gas to 0.02 gwei per gas.
+
+To read more about this change, see the [AIP for raising the gas target & improvements to the pricing algorithm](https://forum.arbitrum.foundation/t/aip-raise-the-gas-target-implement-improvements-to-the-pricing-algorithm/30182).
+
+#### A constraint-based pricing change: STF instrumentation to track multi-gas
+
+We've instrumented Arbitrum's State Transition Function (STF) to track gas usage across multiple resource types including computation, storage access, storage growth, and history growth, rather than only a single total based on opcodes. This work lays the foundation for dynamic, constraint-based pricing where gas fees can adjust based on the most constrained resource at the network level. The goal is to create more stable prices, improve responsiveness to spikes, and allow the network to safely increase throughput without overloading node hardware. In this release, none of the constraints are enabled, so there won't be any impact on current gas prices. This update simply adds the ability to measure and record per-resource usage, with actual pricing changes coming in a later version once constraints are configured, benchmarked and tested.
+
+To read more about this feature, see the [dynamic pricing explainer](https://blog.arbitrum.io/dynamic-pricing-explainer/).
+
+#### Native token mint and burn
+
+Native token mint and burn is a feature that allows Arbitrum chains to use interoperability-enabled token standards (e.g., LayerZero OFTs, xERC20s, native USDC) as native gas tokens on their chains. Currently, Arbitrum chains are designed to "lock and mint" native gas tokens on the chain's canonical Bridge. However, doing so means that these "locked and minted" native gas tokens can't interact with third-party cross-chain adapter contracts. This new feature lets an Arbitrum chain delegate minting and burning of its native gas token to a trusted bridge provider (e.g., LayerZero OFT).
+
+Native token mint and burn is included in ArbOS 50 Dia for the benefit of Arbitrum Orbit chains (reducing the need for forks) and to streamline development and testing into a single codebase. There are no plans to enable this feature on Arbitrum One or Arbitrum Nova, consequently this feature will be explicitly left disabled for Arbitrum One and Arbitrum Nova.
+
+To read more about this feature, see the [Native Token Mint/Burn enablement guide](https://arbitrum.notion.site/Enable-Native-Mint-Burn-for-your-chain-s-gas-token-22601a3f59f880be9b99d3d55f982a17).
+
+
+
+If you want to enable Native Token Mint/Burn capabilities for your chain, you must use [nitro-contracts v3.1.1](https://github.com/OffchainLabs/nitro-contracts/releases/tag/v3.1.1) or higher.
+
+
+
+### Fusaka EIPs that aren't proposed to be in ArbOS 50 Dia
+
+Support and implementation for the following EIPs aren't planned to be part of ArbOS 50 Dia:
+
+- [EIP-7594](https://eips.ethereum.org/EIPS/eip-7594), [EIP-7918](https://eips.ethereum.org/EIPS/eip-7918), and [EIP-7892](https://eips.ethereum.org/EIPS/eip-7892), since Arbitrum chains don't have blob data markets (though they do support posting blob data to a non-Arbitrum parent chain)
+- [EIP-7917](https://eips.ethereum.org/EIPS/eip-7917), since Arbitrum chains don't have a beacon chain and therefore don't have a peer-to-peer layer like Ethereum does
+- [EIP-7934](https://eips.ethereum.org/EIPS/eip-7934), this EIP is to help propagating blocks between nodes. Arbitrum doesn't do that - it sends messages (which are limited) and each node builds every block by itself
+- [EIP-7907](https://eips.ethereum.org/EIPS/eip-7907), this EIP is no longer Scheduled For Inclusion (SFI) for Fusaka, as agreed upon by Client teams during [ACDE 216](https://ethereum-magicians.org/t/allcoredevs-execution-acde-216-july-17-2025/24770/2) on July 17, 2025. We're currently exploring alternative ways to increase the Smart Contract size limit that don't interfere with the ability for Arbitrum chains to support `EIP-7907` in the future. See this [forum post reply](https://forum.arbitrum.foundation/t/non-constitutional-proposal-to-direct-the-arbitrum-foundation-to-implement-an-extended-version-of-eip-7907-and-for-the-dao-to-ratify-its-deployment-on-arbitrum-one/29375/3) for more details about this.
+- [EIP-7935](https://eips.ethereum.org/EIPS/eip-7935), since Arbitrum chains already have a default gas target of 28Mgas/s and we have separate, alternative plans for increasing the gas limit through other means, as mentioned in [Scaling Arbitrum everywhere](https://blog.arbitrum.io/scaling-arbitrum-everywhere/).
+
+### Special note about ArbOS 50 Dia for chains that haven't yet upgraded to use Arbitrum BoLD
+
+While ArbOS 50 Dia will be compatible with both `nitro-contracts 3.1.X` and `nitro-contracts 2.1.3`, only chains that have Arbitrum BoLD enabled can use `nitro-contracts 3.x`. This requirement means that if your chain hasn't yet upgraded to use BoLD, only use `nitro-contracts 2.1.3` for your ArbOS 50 Dia upgrade.
+
+### Reference links for ArbOS 50 Dia
+
+- [Guide for how to upgrade ArbOS on your Arbitrum chain](../../launch-arbitrum-chain/02-configure-your-chain/common-configurations/13-arbos-upgrade.mdx)
+- [README for how to upgrade your rollup contracts to support ArbOS 50 on your chain](https://github.com/OffchainLabs/orbit-actions?tab=readme-ov-file#nitro-contracts-upgrades)
+- [Nitro v3.9.0](https://github.com/OffchainLabs/nitro/releases/tag/v3.9.0)
+- [nitro-contracts v3.1.1](https://github.com/OffchainLabs/nitro-contracts/releases/tag/v3.1.1)
+- [nitro-contracts v2.1.3](https://github.com/OffchainLabs/nitro-contracts/releases/tag/v2.1.3) (only relevant for Arbitrum chains that don't have BoLD enabled yet)
+- [AIP: ArbOS Version 50 Dia Forum Post](https://forum.arbitrum.foundation/t/constitutional-aip-arbos-version-50-dia/)
+- [Temperature check vote on Snapshot for ArbOS 50 Dia](https://snapshot.box/#/s:arbitrumfoundation.eth/proposal/0x33754da4006d0ef38666ec5d5e85fd0966a891a594ab9dc21f23beedea2d330b)
+- [AIP: Raising the gas target & improvements to pricing algorithm Forum Post](https://forum.arbitrum.foundation/t/aip-raise-the-gas-target-implement-improvements-to-the-pricing-algorithm/30182)
+- [Temperature check vote on Snapshot for Raising the gas target & improvements to pricing algorithm](https://snapshot.box/#/s:arbitrumfoundation.eth/proposal/0x4a96a91d162975de0d402b83ca8b8a24e808ca357150120fc0d44ae0bf1cc4a5)
+- Coming Soon - ArbOS 50 Audit Report, from Trail of Bits
diff --git a/docs/stylus/advanced/hostio-exports.mdx b/docs/stylus/advanced/hostio-exports.mdx
new file mode 100644
index 0000000000..80210af745
--- /dev/null
+++ b/docs/stylus/advanced/hostio-exports.mdx
@@ -0,0 +1,878 @@
+---
+title: 'Hostio exports'
+description: 'Learn about low-level hostio functions for direct VM access in Stylus smart contracts'
+author: chrisco
+sme: chrisco
+sidebar_position: 1
+target_audience: Developers who need to understand how to use hostio exports with Stylus.
+displayed_sidebar: buildStylusSidebar
+---
+
+Hostio (Host I/O) exports are low-level functions that provide direct access to the Stylus VM runtime. These functions are WebAssembly imports that allow Stylus programs to interact with the blockchain environment, similar to how EVM opcodes work in Solidity.
+
+## Overview
+
+Hostio functions are the foundational layer that powers all Stylus smart contract operations. While most developers will use the higher-level SDK abstractions, understanding hostio functions is valuable for:
+
+- Performance optimization through direct VM access
+- Implementing custom low-level operations
+- Understanding gas costs and execution flow
+- Debugging and troubleshooting contract behavior
+
+:::info
+Most developers should use the high-level SDK wrappers instead of calling hostio functions directly. The SDK provides safe, ergonomic interfaces that handle memory management and error checking automatically.
+:::
+
+## How hostio works
+
+Hostio functions are WebAssembly imports defined in the `vm_hooks` module. When a Stylus program is compiled to WASM, these functions are linked at runtime by the Arbitrum VM:
+
+```rust
+#[link(wasm_import_module = "vm_hooks")]
+extern "C" {
+ pub fn msg_sender(sender: *mut u8);
+ pub fn block_number() -> u64;
+ // ... more functions
+}
+```
+
+During execution, calls to these functions are intercepted by the Stylus VM, which implements the actual functionality using the underlying ArbOS infrastructure.
+
+## Function categories
+
+Hostio functions are organized into several categories based on their purpose.
+
+### Account operations
+
+Query information about accounts on the blockchain.
+
+#### `account_balance`
+
+Gets the ETH balance of an account in wei. Equivalent to EVM's `BALANCE` opcode.
+
+```rust
+pub fn account_balance(address: *const u8, dest: *mut u8);
+```
+
+**Parameters:**
+
+- `address`: Pointer to 20-byte address
+- `dest`: Pointer to write 32-byte balance value
+
+**Usage:**
+
+```rust
+use stylus_sdk::alloy_primitives::{Address, U256};
+
+unsafe {
+ let addr = Address::from([0x11; 20]);
+ let mut balance_bytes = [0u8; 32];
+ hostio::account_balance(addr.as_ptr(), balance_bytes.as_mut_ptr());
+ let balance = U256::from_be_bytes(balance_bytes);
+}
+```
+
+#### `account_code`
+
+Gets a subset of code from an account. Equivalent to EVM's `EXTCODECOPY` opcode.
+
+```rust
+pub fn account_code(
+ address: *const u8,
+ offset: usize,
+ size: usize,
+ dest: *mut u8
+) -> usize;
+```
+
+**Returns:** Number of bytes actually written
+
+#### `account_code_size`
+
+Gets the size of code at an address. Equivalent to EVM's `EXTCODESIZE` opcode.
+
+```rust
+pub fn account_code_size(address: *const u8) -> usize;
+```
+
+#### `account_codehash`
+
+Gets the code hash of an account. Equivalent to EVM's `EXTCODEHASH` opcode.
+
+```rust
+pub fn account_codehash(address: *const u8, dest: *mut u8);
+```
+
+:::note
+Empty accounts return the keccak256 hash of empty bytes: `c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470`
+:::
+
+### Storage operations
+
+Interact with persistent contract storage.
+
+#### `storage_load_bytes32`
+
+Reads a 32-byte value from storage. Equivalent to EVM's `SLOAD` opcode.
+
+```rust
+pub fn storage_load_bytes32(key: *const u8, dest: *mut u8);
+```
+
+**Parameters:**
+
+- `key`: Pointer to 32-byte storage key
+- `dest`: Pointer to write 32-byte value
+
+#### `storage_cache_bytes32`
+
+Writes a 32-byte value to the storage cache. Equivalent to EVM's `SSTORE` opcode.
+
+```rust
+pub fn storage_cache_bytes32(key: *const u8, value: *const u8);
+```
+
+:::warning
+Values are cached and must be persisted using `storage_flush_cache` before they're permanently written to storage.
+:::
+
+#### `storage_flush_cache`
+
+Persists cached storage values to the EVM state trie. Equivalent to multiple `SSTORE` operations.
+
+```rust
+pub fn storage_flush_cache(clear: bool);
+```
+
+**Parameters:**
+
+- `clear`: Whether to drop the cache entirely after flushing
+
+**Storage caching benefits:**
+
+The Stylus VM implements storage caching for improved performance:
+
+- Repeated reads of the same key cost less gas
+- Writes are batched for efficiency
+- Cache is automatically managed by the SDK
+
+### Block information
+
+Access information about the current block.
+
+#### `block_basefee`
+
+Gets the basefee of the current block. Equivalent to EVM's `BASEFEE` opcode.
+
+```rust
+pub fn block_basefee(basefee: *mut u8);
+```
+
+#### `block_chainid`
+
+Gets the chain identifier. Equivalent to EVM's `CHAINID` opcode.
+
+```rust
+pub fn chainid() -> u64;
+```
+
+#### `block_coinbase`
+
+Gets the coinbase (block producer address). On Arbitrum, this is the L1 batch poster's address.
+
+```rust
+pub fn block_coinbase(coinbase: *mut u8);
+```
+
+#### `block_gas_limit`
+
+Gets the gas limit of the current block. Equivalent to EVM's `GASLIMIT` opcode.
+
+```rust
+pub fn block_gas_limit() -> u64;
+```
+
+#### `block_number`
+
+Gets a bounded estimate of the L1 block number when the transaction was sequenced.
+
+```rust
+pub fn block_number() -> u64;
+```
+
+:::info
+See [Arbitrum block numbers and time](https://developer.arbitrum.io/time) for more information on how block numbers work on Arbitrum chains.
+:::
+
+#### `block_timestamp`
+
+Gets a bounded estimate of the Unix timestamp when the transaction was sequenced.
+
+```rust
+pub fn block_timestamp() -> u64;
+```
+
+### Transaction and message context
+
+Access information about the current transaction and call context.
+
+#### `msg_sender`
+
+Gets the address of the caller. Equivalent to EVM's `CALLER` opcode.
+
+```rust
+pub fn msg_sender(sender: *mut u8);
+```
+
+**Parameters:**
+
+- `sender`: Pointer to write 20-byte address
+
+:::note
+For L1-to-L2 retryable ticket transactions, addresses are aliased. See [address aliasing documentation](https://developer.arbitrum.io/arbos/l1-to-l2-messaging#address-aliasing).
+:::
+
+#### `msg_value`
+
+Gets the ETH value sent with the call in wei. Equivalent to EVM's `CALLVALUE` opcode.
+
+```rust
+pub fn msg_value(value: *mut u8);
+```
+
+#### `msg_reentrant`
+
+Checks if the current call is reentrant.
+
+```rust
+pub fn msg_reentrant() -> bool;
+```
+
+#### `tx_gas_price`
+
+Gets the gas price in wei per gas. On Arbitrum, this equals the basefee. Equivalent to EVM's `GASPRICE` opcode.
+
+```rust
+pub fn tx_gas_price(gas_price: *mut u8);
+```
+
+#### `tx_origin`
+
+Gets the top-level sender of the transaction. Equivalent to EVM's `ORIGIN` opcode.
+
+```rust
+pub fn tx_origin(origin: *mut u8);
+```
+
+#### `tx_ink_price`
+
+Gets the price of ink in EVM gas basis points. See [Ink and Gas](https://docs.arbitrum.io/stylus/concepts/gas-metering) for more information.
+
+```rust
+pub fn tx_ink_price() -> u32;
+```
+
+### Contract calls
+
+Make calls to other contracts.
+
+#### `call_contract`
+
+Calls another contract with optional value and gas limit. Equivalent to EVM's `CALL` opcode.
+
+```rust
+pub fn call_contract(
+ contract: *const u8,
+ calldata: *const u8,
+ calldata_len: usize,
+ value: *const u8,
+ gas: u64,
+ return_data_len: *mut usize
+) -> u8;
+```
+
+**Parameters:**
+
+- `contract`: Pointer to 20-byte contract address
+- `calldata`: Pointer to calldata bytes
+- `calldata_len`: Length of calldata
+- `value`: Pointer to 32-byte value in wei (use 0 for no value)
+- `gas`: Gas to supply (use `u64::MAX` for all available gas)
+- `return_data_len`: Pointer to store length of return data
+
+**Returns:** `0` on success, non-zero on failure
+
+**Gas rules:**
+
+- Follows the 63/64 rule (at most 63/64 of available gas is forwarded)
+- Includes callvalue stipend when value is sent
+
+**Usage:**
+
+```rust
+use stylus_sdk::call::RawCall;
+
+unsafe {
+ let result = RawCall::new(self.vm())
+ .gas(100_000)
+ .value(U256::from(1_000_000))
+ .call(contract_address, &calldata)?;
+}
+```
+
+#### `delegate_call_contract`
+
+Delegate calls another contract. Equivalent to EVM's `DELEGATECALL` opcode.
+
+```rust
+pub fn delegate_call_contract(
+ contract: *const u8,
+ calldata: *const u8,
+ calldata_len: usize,
+ gas: u64,
+ return_data_len: *mut usize
+) -> u8;
+```
+
+:::warning
+Delegate calls execute code in the context of the current contract. Be extremely careful when delegate calling to untrusted contracts as they have full access to your storage.
+:::
+
+#### `static_call_contract`
+
+Static calls another contract (read-only). Equivalent to EVM's `STATICCALL` opcode.
+
+```rust
+pub fn static_call_contract(
+ contract: *const u8,
+ calldata: *const u8,
+ calldata_len: usize,
+ gas: u64,
+ return_data_len: *mut usize
+) -> u8;
+```
+
+### Contract deployment
+
+Deploy new contracts.
+
+#### `create1`
+
+Deploys a contract using CREATE. Equivalent to EVM's `CREATE` opcode.
+
+```rust
+pub fn create1(
+ code: *const u8,
+ code_len: usize,
+ endowment: *const u8,
+ contract: *mut u8,
+ revert_data_len: *mut usize
+);
+```
+
+**Parameters:**
+
+- `code`: Pointer to initialization code (EVM bytecode)
+- `code_len`: Length of initialization code
+- `endowment`: Pointer to 32-byte value to send
+- `contract`: Pointer to write deployed contract address (20 bytes)
+- `revert_data_len`: Pointer to store revert data length on failure
+
+**Deployment rules:**
+
+- Init code must be EVM bytecode
+- Deployed code can be Stylus (WASM) if it starts with `0xEFF000` header
+- Address is determined by sender and nonce
+- On failure, address will be zero
+
+#### `create2`
+
+Deploys a contract using CREATE2. Equivalent to EVM's `CREATE2` opcode.
+
+```rust
+pub fn create2(
+ code: *const u8,
+ code_len: usize,
+ endowment: *const u8,
+ salt: *const u8,
+ contract: *mut u8,
+ revert_data_len: *mut usize
+);
+```
+
+**Parameters:**
+
+- `salt`: Pointer to 32-byte salt value
+
+**Address calculation:**
+
+- Address is deterministic based on sender, salt, and init code hash
+- Allows for pre-computed addresses
+
+### Events and logging
+
+Emit events to the blockchain.
+
+#### `emit_log`
+
+Emits an EVM log with topics and data. Equivalent to EVM's `LOG0`-`LOG4` opcodes.
+
+```rust
+pub fn emit_log(data: *const u8, len: usize, topics: usize);
+```
+
+**Parameters:**
+
+- `data`: Pointer to event data (first bytes should be 32-byte aligned topics)
+- `len`: Total length of data including topics
+- `topics`: Number of topics (0-4)
+
+:::warning
+Requesting more than 4 topics will cause a revert.
+:::
+
+**Higher-level usage:**
+
+```rust
+sol! {
+ event Transfer(address indexed from, address indexed to, uint256 value);
+}
+
+// Emit using the SDK
+self.vm().log(Transfer {
+ from: sender,
+ to: recipient,
+ value: amount,
+});
+```
+
+### Gas and ink metering
+
+Monitor execution costs.
+
+#### `evm_gas_left`
+
+Gets the amount of gas remaining. Equivalent to EVM's `GAS` opcode.
+
+```rust
+pub fn evm_gas_left() -> u64;
+```
+
+#### `evm_ink_left`
+
+Gets the amount of ink remaining. Stylus-specific metering unit.
+
+```rust
+pub fn evm_ink_left() -> u64;
+```
+
+:::info
+Ink is Stylus's compute pricing unit. See [Ink and Gas](https://docs.arbitrum.io/stylus/concepts/gas-metering) for conversion between ink and gas.
+:::
+
+#### `pay_for_memory_grow`
+
+Pays for WASM memory growth. Automatically called when allocating new pages.
+
+```rust
+pub fn pay_for_memory_grow(pages: u16);
+```
+
+:::note
+The `entrypoint!` macro handles importing this hostio. Manual calls will unproductively consume gas.
+:::
+
+### Cryptography
+
+Cryptographic operations.
+
+#### `native_keccak256`
+
+Efficiently computes keccak256 hash. Equivalent to EVM's `SHA3` opcode.
+
+```rust
+pub fn native_keccak256(bytes: *const u8, len: usize, output: *mut u8);
+```
+
+**Parameters:**
+
+- `bytes`: Pointer to input data
+- `len`: Length of input data
+- `output`: Pointer to write 32-byte hash
+
+**Higher-level usage:**
+
+```rust
+use stylus_sdk::crypto::keccak;
+
+let hash = keccak(b"hello world");
+```
+
+### Calldata operations
+
+Read and write calldata and return data.
+
+#### `read_args`
+
+Reads the program calldata. Equivalent to EVM's `CALLDATACOPY` opcode.
+
+```rust
+pub fn read_args(dest: *mut u8);
+```
+
+:::note
+This reads the entirety of the call's calldata.
+:::
+
+#### `read_return_data`
+
+Copies bytes from the last call or deployment return result. Equivalent to EVM's `RETURNDATACOPY` opcode.
+
+```rust
+pub fn read_return_data(dest: *mut u8, offset: usize, size: usize) -> usize;
+```
+
+**Parameters:**
+
+- `dest`: Destination buffer
+- `offset`: Offset in return data to start copying from
+- `size`: Number of bytes to copy
+
+**Returns:** Number of bytes actually written
+
+**Behavior:**
+
+- Does not revert if out of bounds
+- Copies overlapping portion only
+
+#### `return_data_size`
+
+Gets the length of the last return result. Equivalent to EVM's `RETURNDATASIZE` opcode.
+
+```rust
+pub fn return_data_size() -> usize;
+```
+
+#### `write_result`
+
+Writes the final return data for the current call.
+
+```rust
+pub fn write_result(data: *const u8, len: usize);
+```
+
+**Behavior:**
+
+- Does not cause program to exit
+- If not called, return data will be empty
+- Program exits naturally when entrypoint returns
+
+#### `contract_address`
+
+Gets the address of the current program. Equivalent to EVM's `ADDRESS` opcode.
+
+```rust
+pub fn contract_address(address: *mut u8);
+```
+
+### Debug and console
+
+Debug-only functions for development.
+
+#### `log_txt`
+
+Prints UTF-8 text to console. Only available in debug mode.
+
+```rust
+pub fn log_txt(text: *const u8, len: usize);
+```
+
+#### `log_i32` / `log_i64`
+
+Prints integers to console. Only available in debug mode.
+
+```rust
+pub fn log_i32(value: i32);
+pub fn log_i64(value: i64);
+```
+
+#### `log_f32` / `log_f64`
+
+Prints floating-point numbers to console. Only available in debug mode with floating point enabled.
+
+```rust
+pub fn log_f32(value: f32);
+pub fn log_f64(value: f64);
+```
+
+**Higher-level usage:**
+
+```rust
+use stylus_sdk::console;
+
+console!("Value: {}", value); // Prints in debug mode, no-op in production
+```
+
+## Safety considerations
+
+All hostio functions are marked `unsafe` because they:
+
+1. **Operate on raw pointers**: Require correct memory management
+2. **Lack bounds checking**: Can cause undefined behavior if pointers are invalid
+3. **Have side effects**: Can modify contract state or make external calls
+4. **May revert**: Some operations can cause the transaction to revert
+
+### Safe usage patterns
+
+**Always validate inputs:**
+
+```rust
+// Bad: unchecked pointer usage
+unsafe {
+ hostio::msg_sender(ptr); // ptr might be invalid
+}
+
+// Good: use safe wrappers
+let sender = self.vm().msg_sender();
+```
+
+**Use SDK wrappers:**
+
+```rust
+// Bad: direct hostio call
+unsafe {
+ let mut balance = [0u8; 32];
+ hostio::account_balance(addr.as_ptr(), balance.as_mut_ptr());
+}
+
+// Good: use SDK wrapper
+use stylus_sdk::evm;
+let balance = evm::balance(addr);
+```
+
+**Handle return values:**
+
+```rust
+// Check return status from calls
+let status = unsafe {
+ hostio::call_contract(
+ contract.as_ptr(),
+ calldata.as_ptr(),
+ calldata.len(),
+ value.as_ptr(),
+ gas,
+ &mut return_len,
+ )
+};
+
+if status != 0 {
+ // Handle call failure
+}
+```
+
+## Higher-level wrappers
+
+The Stylus SDK provides safe, ergonomic wrappers around hostio functions:
+
+### Storage operations
+
+```rust
+// Instead of direct hostio:
+unsafe {
+ hostio::storage_load_bytes32(key.as_ptr(), dest.as_mut_ptr());
+}
+
+// Use storage types:
+use stylus_sdk::storage::StorageU256;
+
+#[storage]
+pub struct Contract {
+ value: StorageU256,
+}
+
+let value = self.value.get(); // Safe, ergonomic
+```
+
+### Contract calls
+
+```rust
+// Instead of direct hostio:
+unsafe {
+ hostio::call_contract(/* many parameters */);
+}
+
+// Use RawCall or typed interfaces:
+use stylus_sdk::call::RawCall;
+
+let result = unsafe {
+ RawCall::new(self.vm())
+ .gas(100_000)
+ .call(contract, &calldata)?
+};
+```
+
+### VM context
+
+```rust
+// Instead of direct hostio:
+unsafe {
+ let mut sender = [0u8; 20];
+ hostio::msg_sender(sender.as_mut_ptr());
+}
+
+// Use VM accessor:
+let sender = self.vm().msg_sender();
+let value = self.vm().msg_value();
+let timestamp = self.vm().block_timestamp();
+```
+
+## Feature flags
+
+Hostio behavior changes based on feature flags:
+
+### `export-abi`
+
+When enabled, hostio functions are stubbed and return `unimplemented!()`. Used for ABI generation.
+
+### `stylus-test`
+
+When enabled, hostio functions panic with an error message. Use `TestVM` for testing instead.
+
+### `debug`
+
+When enabled, console logging functions become available. In production, console functions are no-ops.
+
+## Performance considerations
+
+### Direct hostio vs SDK wrappers
+
+- **Direct hostio**: Slightly lower overhead, requires manual memory management
+- **SDK wrappers**: Minimal overhead (often zero-cost abstractions), much safer
+
+**Recommendation:** Use SDK wrappers unless profiling shows a specific performance bottleneck.
+
+### Storage caching
+
+The Stylus VM automatically caches storage operations:
+
+```rust
+// First read: full SLOAD cost
+let value1 = storage.value.get();
+
+// Subsequent reads: reduced cost from cache
+let value2 = storage.value.get();
+
+// Writes are cached until flush
+storage.value.set(new_value); // Cached
+
+// Cache is flushed automatically at call boundaries
+```
+
+### Gas vs ink
+
+Stylus uses "ink" for fine-grained gas metering:
+
+- **Ink**: WASM execution cost in Stylus-specific units
+- **Gas**: Standard EVM gas units
+- Conversion happens automatically
+
+Most developers don't need to think about ink vs gas distinction.
+
+## Common patterns
+
+### Check-effects-interactions pattern
+
+```rust
+#[public]
+impl MyContract {
+ pub fn transfer(&mut self, to: Address, amount: U256) -> Result<(), Vec> {
+ // Checks
+ let sender = self.vm().msg_sender();
+ let balance = self.balances.get(sender);
+ if balance < amount {
+ return Err(b"Insufficient balance".to_vec());
+ }
+
+ // Effects
+ self.balances.setter(sender).set(balance - amount);
+ self.balances.setter(to).set(self.balances.get(to) + amount);
+
+ // Interactions (if any)
+ Ok(())
+ }
+}
+```
+
+### Efficient event logging
+
+```rust
+sol! {
+ event DataUpdated(bytes32 indexed key, uint256 value);
+}
+
+// SDK handles hostio::emit_log internally
+self.vm().log(DataUpdated {
+ key: key_hash,
+ value: new_value,
+});
+```
+
+### Gas-limited external calls
+
+```rust
+use stylus_sdk::call::RawCall;
+
+// Limit gas to prevent griefing
+let result = unsafe {
+ RawCall::new(self.vm())
+ .gas(50_000) // Fixed gas limit
+ .call(untrusted_contract, &calldata)
+};
+
+match result {
+ Ok(data) => { /* process return data */ },
+ Err(_) => { /* handle failure gracefully */ },
+}
+```
+
+## Testing with hostio
+
+Hostio functions are not available in the test environment. Use `TestVM` instead:
+
+```rust
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use stylus_sdk::testing::*;
+
+ #[test]
+ fn test_function() {
+ let vm = TestVM::default();
+ let mut contract = MyContract::from(&vm);
+
+ // VM functions work in tests
+ let sender = vm.msg_sender(); // Works
+
+ // Direct hostio would panic
+ // unsafe { hostio::msg_sender(...) } // Would panic
+ }
+}
+```
+
+## Resources
+
+- [Stylus VM specification](https://github.com/OffchainLabs/stylus)
+- [EVM opcodes reference](https://www.evm.codes/)
+- [Arbitrum block numbers and time](https://developer.arbitrum.io/time)
+- [Ink and gas metering](https://docs.arbitrum.io/stylus/concepts/gas-metering)
+- [stylus-sdk-rs source](https://github.com/OffchainLabs/stylus-sdk-rs/blob/main/stylus-sdk/src/hostio.rs)
+
+## Best practices
+
+1. **Use SDK wrappers**: Prefer high-level abstractions over direct hostio calls
+2. **Validate inputs**: Always check pointers and sizes before unsafe operations
+3. **Handle errors**: Check return values from call operations
+4. **Test thoroughly**: Use `TestVM` for comprehensive testing
+5. **Profile first**: Only optimize to direct hostio if profiling shows it's necessary
+6. **Document unsafe code**: Always document why `unsafe` is necessary
+7. **Minimize unsafe blocks**: Keep `unsafe` blocks as small as possible
diff --git a/docs/stylus/advanced/minimal-entrypoint-contracts.mdx b/docs/stylus/advanced/minimal-entrypoint-contracts.mdx
new file mode 100644
index 0000000000..d3d530ba7f
--- /dev/null
+++ b/docs/stylus/advanced/minimal-entrypoint-contracts.mdx
@@ -0,0 +1,644 @@
+---
+title: 'Minimal entrypoint contracts'
+description: 'Understanding low-level Stylus contract entrypoints and the Router trait'
+author: chrisco
+sme: chrisco
+sidebar_position: 1
+target_audience: Developers who need to understand how to build a minimal entrypoint contract without high-level macros.
+displayed_sidebar: buildStylusSidebar
+---
+
+This guide explains the low-level mechanics of Stylus contract entrypoints, helping you understand what happens behind the `#[entrypoint]` and `#[public]` macros. This knowledge is useful for advanced use cases, debugging, and building custom contract frameworks.
+
+## Overview
+
+A Stylus contract at its core consists of:
+
+1. **`user_entrypoint` function**: The WASM export that Stylus calls
+2. **Router implementation**: Routes function selectors to method implementations
+3. **TopLevelStorage trait**: Marks the contract's root storage type
+4. **ArbResult type**: Represents success/failure with encoded return data
+
+## Understanding `ArbResult`
+
+`ArbResult` is the fundamental return type for Stylus contract methods:
+
+```rust
+pub type ArbResult = Result, Vec>;
+```
+
+- `Ok(Vec)` - Success with ABI-encoded return data
+- `Err(Vec)` - Revert with ABI-encoded error data
+
+**Example:**
+
+```rust
+use stylus_sdk::ArbResult;
+
+// Success with no return data
+fn no_return() -> ArbResult {
+ Ok(Vec::new())
+}
+
+// Success with encoded data
+fn return_value() -> ArbResult {
+ let value: u32 = 42;
+ Ok(value.to_le_bytes().to_vec())
+}
+
+// Revert with error data
+fn revert_with_error() -> ArbResult {
+ Err(b"InsufficientBalance".to_vec())
+}
+```
+
+## The `user_entrypoint` Function
+
+The `user_entrypoint` function is the WASM export that Stylus calls when a transaction invokes the contract. The `#[entrypoint]` macro generates this function automatically.
+
+### Generated Structure
+
+When you use `#[entrypoint]`, the macro generates:
+
+```rust
+#[no_mangle]
+pub extern "C" fn user_entrypoint(len: usize) -> usize {
+ let host = stylus_sdk::host::VM {
+ host: stylus_sdk::host::WasmVM{}
+ };
+
+ // Reentrancy check (unless reentrant feature enabled)
+ if host.msg_reentrant() {
+ return 1; // revert
+ }
+
+ // Ensure pay_for_memory_grow is referenced
+ // (costs 8700 ink, less than 1 gas)
+ host.pay_for_memory_grow(0);
+
+ // Read calldata
+ let input = host.read_args(len);
+
+ // Call the router
+ let (data, status) = match router_entrypoint::(input, host.clone()) {
+ Ok(data) => (data, 0), // Success
+ Err(data) => (data, 1), // Revert
+ };
+
+ // Persist storage changes
+ host.flush_cache(false);
+
+ // Write return data
+ host.write_result(&data);
+
+ status
+}
+```
+
+### Key Points
+
+- **Signature**: `extern "C" fn user_entrypoint(len: usize) -> usize`
+- **Input**: `len` is the length of calldata to read
+- **Output**: `0` for success, `1` for revert
+- **Side effects**: Reads calldata, writes return data, flushes storage cache
+
+## The `Router` Trait
+
+The `Router` trait defines how function calls are dispatched to method implementations.
+
+### Trait Definition
+
+```rust
+pub trait Router
+where
+ S: TopLevelStorage + BorrowMut + ValueDenier,
+ I: ?Sized,
+{
+ type Storage;
+
+ /// Route a function call by selector
+ fn route(storage: &mut S, selector: u32, input: &[u8]) -> Option;
+
+ /// Handle receive (plain ETH transfers, no calldata)
+ fn receive(storage: &mut S) -> Option>>;
+
+ /// Handle fallback (unknown selectors or no receive)
+ fn fallback(storage: &mut S, calldata: &[u8]) -> Option;
+
+ /// Handle constructor
+ fn constructor(storage: &mut S, calldata: &[u8]) -> Option;
+}
+```
+
+### Routing Logic
+
+The `router_entrypoint` function implements the routing logic:
+
+```rust
+pub fn router_entrypoint(input: Vec, host: VM) -> ArbResult
+where
+ R: Router,
+ S: StorageType + TopLevelStorage + BorrowMut + ValueDenier,
+{
+ let mut storage = unsafe { S::new(U256::ZERO, 0, host) };
+
+ // No calldata - try receive, then fallback
+ if input.is_empty() {
+ if let Some(res) = R::receive(&mut storage) {
+ return res.map(|_| Vec::new());
+ }
+ if let Some(res) = R::fallback(&mut storage, &[]) {
+ return res;
+ }
+ return Err(Vec::new()); // No receive or fallback
+ }
+
+ // Extract selector (first 4 bytes)
+ if input.len() >= 4 {
+ let selector = u32::from_be_bytes(input[..4].try_into().unwrap());
+
+ // Check for constructor
+ if selector == CONSTRUCTOR_SELECTOR {
+ if let Some(res) = R::constructor(&mut storage, &input[4..]) {
+ return res;
+ }
+ }
+ // Try to route to a method
+ else if let Some(res) = R::route(&mut storage, selector, &input[4..]) {
+ return res;
+ }
+ }
+
+ // Try fallback
+ if let Some(res) = R::fallback(&mut storage, &input) {
+ return res;
+ }
+
+ Err(Vec::new()) // Unknown selector and no fallback
+}
+```
+
+## The `TopLevelStorage` Trait
+
+The `TopLevelStorage` trait marks types that represent the contract's root storage.
+
+### Trait Definition
+
+```rust
+pub unsafe trait TopLevelStorage {}
+```
+
+### Purpose
+
+- Prevents storage aliasing during reentrancy
+- Lifetime tracks all EVM state changes during contract invocation
+- Must hold a reference when making external calls
+- Automatically implemented by `#[entrypoint]`
+
+### Safety
+
+The trait is `unsafe` because:
+
+- Type must truly be top-level to prevent storage aliasing
+- Incorrectly implementing this trait can lead to undefined behavior
+
+## Building a Minimal Contract
+
+Here's a minimal contract without using the high-level macros:
+
+### Step 1: Define Storage
+
+```rust
+#![cfg_attr(not(any(test, feature = "export-abi")), no_main)]
+extern crate alloc;
+
+use alloc::vec::Vec;
+use stylus_sdk::{
+ abi::{Router, ArbResult},
+ storage::StorageType,
+ host::VM,
+ alloy_primitives::U256,
+};
+
+// Mark as top-level storage (normally done by #[entrypoint])
+pub struct MyContract;
+
+unsafe impl stylus_core::storage::TopLevelStorage for MyContract {}
+
+impl StorageType for MyContract {
+ type Wraps<'a> = &'a Self where Self: 'a;
+ type WrapsMut<'a> = &'a mut Self where Self: 'a;
+
+ unsafe fn new(_slot: U256, _offset: u8, _host: VM) -> Self {
+ MyContract
+ }
+
+ fn load<'s>(self) -> Self::Wraps<'s> {
+ &self
+ }
+
+ fn load_mut<'s>(self) -> Self::WrapsMut<'s> {
+ &mut self
+ }
+}
+```
+
+### Step 2: Implement Router
+
+```rust
+impl Router for MyContract {
+ type Storage = MyContract;
+
+ fn route(_storage: &mut MyContract, selector: u32, _input: &[u8]) -> Option {
+ // Simple example: one method with selector 0x12345678
+ match selector {
+ 0x12345678 => Some(Ok(Vec::new())),
+ _ => None, // Unknown selector
+ }
+ }
+
+ fn receive(_storage: &mut MyContract) -> Option>> {
+ None // No receive function
+ }
+
+ fn fallback(_storage: &mut MyContract, _calldata: &[u8]) -> Option {
+ None // No fallback function
+ }
+
+ fn constructor(_storage: &mut MyContract, _calldata: &[u8]) -> Option {
+ None // No constructor
+ }
+}
+```
+
+### Step 3: Define Entrypoint
+
+```rust
+#[no_mangle]
+pub extern "C" fn user_entrypoint(len: usize) -> usize {
+ let host = VM { host: stylus_sdk::host::WasmVM{} };
+
+ // Reentrancy check
+ if host.msg_reentrant() {
+ return 1;
+ }
+
+ // Reference pay_for_memory_grow
+ host.pay_for_memory_grow(0);
+
+ // Read input
+ let input = host.read_args(len);
+
+ // Route the call
+ let (data, status) = match stylus_sdk::abi::router_entrypoint::(input, host.clone()) {
+ Ok(data) => (data, 0),
+ Err(data) => (data, 1),
+ };
+
+ // Flush storage
+ host.flush_cache(false);
+
+ // Write result
+ host.write_result(&data);
+
+ status
+}
+```
+
+## Function Selectors
+
+Function selectors are 4-byte identifiers computed from the function signature.
+
+### Computing Selectors
+
+```rust
+use stylus_sdk::function_selector;
+
+// Manual computation
+const MY_FUNCTION: [u8; 4] = function_selector!("myFunction");
+
+// With parameters
+const TRANSFER: [u8; 4] = function_selector!("transfer", Address, U256);
+
+// Constructor selector
+const CONSTRUCTOR_SELECTOR: u32 =
+ u32::from_be_bytes(function_selector!("constructor"));
+```
+
+### Using in Router
+
+```rust
+impl Router for MyContract {
+ type Storage = MyContract;
+
+ fn route(_storage: &mut MyContract, selector: u32, input: &[u8]) -> Option {
+ const GET_VALUE: u32 = u32::from_be_bytes(function_selector!("getValue"));
+ const SET_VALUE: u32 = u32::from_be_bytes(function_selector!("setValue", U256));
+
+ match selector {
+ GET_VALUE => {
+ // Return encoded U256 value
+ let value = U256::from(42);
+ Some(Ok(value.to_be_bytes::<32>().to_vec()))
+ }
+ SET_VALUE => {
+ // Decode input and set value
+ if input.len() >= 32 {
+ // Process set_value logic
+ Some(Ok(Vec::new()))
+ } else {
+ Some(Err(Vec::new()))
+ }
+ }
+ _ => None,
+ }
+ }
+
+ fn receive(_storage: &mut MyContract) -> Option>> {
+ None
+ }
+
+ fn fallback(_storage: &mut MyContract, _calldata: &[u8]) -> Option {
+ None
+ }
+
+ fn constructor(_storage: &mut MyContract, _calldata: &[u8]) -> Option {
+ None
+ }
+}
+```
+
+## Implementing Special Functions
+
+### Receive Function
+
+Handles plain ETH transfers (no calldata):
+
+```rust
+fn receive(storage: &mut MyContract) -> Option>> {
+ // Access msg_value via storage.vm().msg_value()
+ // Must return Ok(()) for success
+ Some(Ok(()))
+}
+```
+
+### Fallback Function
+
+Handles unknown selectors or when no receive is defined:
+
+```rust
+fn fallback(storage: &mut MyContract, calldata: &[u8]) -> Option {
+ // Can access full calldata
+ // Return Some to handle, None to revert
+ Some(Ok(Vec::new()))
+}
+```
+
+### Constructor
+
+Called once during deployment with `CONSTRUCTOR_SELECTOR`:
+
+```rust
+fn constructor(storage: &mut MyContract, calldata: &[u8]) -> Option {
+ // Initialize contract state
+ // calldata contains constructor parameters
+ Some(Ok(Vec::new()))
+}
+```
+
+## Complete Minimal Example
+
+Here's a complete working minimal contract:
+
+```rust
+#![cfg_attr(not(any(test, feature = "export-abi")), no_main)]
+extern crate alloc;
+
+use alloc::vec::Vec;
+use core::borrow::BorrowMut;
+use stylus_sdk::{
+ abi::Router,
+ alloy_primitives::U256,
+ host::VM,
+ storage::StorageType,
+ ArbResult,
+ function_selector,
+};
+use stylus_core::{storage::TopLevelStorage, ValueDenier};
+
+// Contract storage
+pub struct MinimalContract;
+
+// Mark as top-level storage
+unsafe impl TopLevelStorage for MinimalContract {}
+
+// Implement StorageType
+impl StorageType for MinimalContract {
+ type Wraps<'a> = &'a Self where Self: 'a;
+ type WrapsMut<'a> = &'a mut Self where Self: 'a;
+
+ unsafe fn new(_slot: U256, _offset: u8, _host: VM) -> Self {
+ MinimalContract
+ }
+
+ fn load<'s>(self) -> Self::Wraps<'s> {
+ &self
+ }
+
+ fn load_mut<'s>(self) -> Self::WrapsMut<'s> {
+ &mut self
+ }
+}
+
+// Implement ValueDenier (for non-payable check)
+impl ValueDenier for MinimalContract {
+ fn deny_value(&self, _method_name: &str) -> Result<(), Vec> {
+ Ok(()) // Allow all for simplicity
+ }
+}
+
+// Implement BorrowMut
+impl BorrowMut for MinimalContract {
+ fn borrow_mut(&mut self) -> &mut MinimalContract {
+ self
+ }
+}
+
+// Implement Router
+impl Router for MinimalContract {
+ type Storage = MinimalContract;
+
+ fn route(_storage: &mut MinimalContract, selector: u32, _input: &[u8]) -> Option {
+ const HELLO: u32 = u32::from_be_bytes(function_selector!("hello"));
+
+ match selector {
+ HELLO => Some(Ok(Vec::new())),
+ _ => None,
+ }
+ }
+
+ fn receive(_storage: &mut MinimalContract) -> Option>> {
+ None
+ }
+
+ fn fallback(_storage: &mut MinimalContract, _calldata: &[u8]) -> Option {
+ Some(Ok(Vec::new())) // Accept all unknown calls
+ }
+
+ fn constructor(_storage: &mut MinimalContract, _calldata: &[u8]) -> Option {
+ Some(Ok(Vec::new()))
+ }
+}
+
+// Define user_entrypoint
+#[no_mangle]
+pub extern "C" fn user_entrypoint(len: usize) -> usize {
+ let host = VM { host: stylus_sdk::host::WasmVM{} };
+
+ if host.msg_reentrant() {
+ return 1;
+ }
+
+ host.pay_for_memory_grow(0);
+
+ let input = host.read_args(len);
+ let (data, status) = match stylus_sdk::abi::router_entrypoint::(input, host.clone()) {
+ Ok(data) => (data, 0),
+ Err(data) => (data, 1),
+ };
+
+ host.flush_cache(false);
+ host.write_result(&data);
+ status
+}
+```
+
+## Why Use High-Level Macros?
+
+While minimal contracts are educational, the `#[entrypoint]` and `#[public]` macros provide:
+
+1. **Automatic selector generation** from method names
+2. **Type-safe parameter encoding/decoding** using Alloy types
+3. **Solidity ABI export** for interoperability
+4. **Storage trait implementations** with caching
+5. **Error handling** with `Result` types
+6. **Payable checks** for ETH-receiving functions
+7. **Reentrancy protection** by default
+
+**Recommended approach:**
+
+```rust
+// Use macros for production contracts
+#[storage]
+#[entrypoint]
+pub struct MyContract {
+ value: StorageU256,
+}
+
+#[public]
+impl MyContract {
+ pub fn get_value(&self) -> U256 {
+ self.value.get()
+ }
+
+ pub fn set_value(&mut self, value: U256) {
+ self.value.set(value);
+ }
+}
+```
+
+This generates all the low-level code automatically while providing a clean, type-safe interface.
+
+## Advanced Use Cases
+
+### Custom Routing Logic
+
+Implement custom routing for multi-contract systems:
+
+```rust
+impl Router for MultiContract {
+ type Storage = MultiContract;
+
+ fn route(storage: &mut MultiContract, selector: u32, input: &[u8]) -> Option {
+ // Route to different modules based on selector range
+ match selector {
+ 0x00000000..=0x0fffffff => ModuleA::route(storage, selector, input),
+ 0x10000000..=0x1fffffff => ModuleB::route(storage, selector, input),
+ _ => None,
+ }
+ }
+
+ // ... other methods
+}
+```
+
+### Custom Entrypoint Logic
+
+Add custom logic before/after routing:
+
+```rust
+#[no_mangle]
+pub extern "C" fn user_entrypoint(len: usize) -> usize {
+ let host = VM { host: stylus_sdk::host::WasmVM{} };
+
+ // Custom pre-processing
+ let start_gas = host.evm_gas_left();
+
+ // Standard entrypoint logic
+ if host.msg_reentrant() {
+ return 1;
+ }
+
+ host.pay_for_memory_grow(0);
+ let input = host.read_args(len);
+
+ let (data, status) = match stylus_sdk::abi::router_entrypoint::(input, host.clone()) {
+ Ok(data) => (data, 0),
+ Err(data) => (data, 1),
+ };
+
+ // Custom post-processing
+ let gas_used = start_gas - host.evm_gas_left();
+ // Log or handle gas usage
+
+ host.flush_cache(false);
+ host.write_result(&data);
+ status
+}
+```
+
+## Debugging Tips
+
+### Enable Debug Mode
+
+```rust
+#[cfg(feature = "debug")]
+use stylus_sdk::console;
+
+fn route(storage: &mut MyContract, selector: u32, input: &[u8]) -> Option {
+ #[cfg(feature = "debug")]
+ console!("Selector: {:08x}", selector);
+
+ // Routing logic...
+}
+```
+
+### Check Selector Computation
+
+```rust
+#[test]
+fn test_selectors() {
+ use stylus_sdk::function_selector;
+
+ let hello = u32::from_be_bytes(function_selector!("hello"));
+ assert_eq!(hello, 0x19ff1d21);
+
+ // Compare with Solidity: bytes4(keccak256("hello()"))
+}
+```
+
+## See Also
+
+- [Contracts](../reference/contracts.mdx) - High-level contract development
+- [Global Variables](../reference/global-variables-and-functions.mdx) - VM context methods
+- [Storage Types](../reference/data-types/storage.mdx) - Persistent storage
diff --git a/docs/stylus/recommended-libraries.md b/docs/stylus/advanced/recommended-libraries.mdx
similarity index 96%
rename from docs/stylus/recommended-libraries.md
rename to docs/stylus/advanced/recommended-libraries.mdx
index 11f5485db1..0d7886f509 100644
--- a/docs/stylus/recommended-libraries.md
+++ b/docs/stylus/advanced/recommended-libraries.mdx
@@ -1,11 +1,9 @@
---
id: recommended-libraries
-title: Recommended Libraries
-sidebar_label: Use Rust Crates
+title: Recommended libraries (Rust crates)
+sidebar_label: Recommended libraries
---
-# Recommended libraries
-
## Using public Rust crates
Rust provides a package registry at [crates.io](https://crates.io/), which lets developers conveniently access a plethora of open source libraries to utilize as dependencies in their code. Stylus Rust contracts can take advantage of these crates to simplify their development workflow.
diff --git a/docs/stylus/advanced/solidity-differences.mdx b/docs/stylus/advanced/solidity-differences.mdx
new file mode 100644
index 0000000000..984ab66bc4
--- /dev/null
+++ b/docs/stylus/advanced/solidity-differences.mdx
@@ -0,0 +1,742 @@
+---
+title: 'Differences between Solidity and Stylus'
+description: 'Learn the key differences between writing smart contracts in Solidity versus Stylus Rust'
+author: chrisco
+sme: chrisco
+sidebar_position: 1
+target_audience: Developers who need to compare Stylus with Solidity.
+displayed_sidebar: buildStylusSidebar
+---
+
+Stylus introduces a new paradigm for writing smart contracts on Arbitrum using Rust and other WebAssembly-compatible languages. While Stylus contracts maintain full interoperability with Solidity contracts, there are important differences in how you structure and write code. This guide helps Solidity developers understand these differences.
+
+## Language and syntax
+
+### Contract structure
+
+**Solidity:**
+
+```solidity
+contract MyContract {
+ uint256 private value;
+ address public owner;
+
+ constructor(uint256 initialValue) {
+ value = initialValue;
+ owner = msg.sender;
+ }
+
+ function setValue(uint256 newValue) public {
+ value = newValue;
+ }
+}
+```
+
+**Stylus (Rust):**
+
+```rust
+use stylus_sdk::prelude::*;
+use stylus_sdk::alloy_primitives::{Address, U256};
+
+#[storage]
+#[entrypoint]
+pub struct MyContract {
+ value: StorageU256,
+ owner: StorageAddress,
+}
+
+#[public]
+impl MyContract {
+ #[constructor]
+ pub fn constructor(&mut self, initial_value: U256) {
+ self.value.set(initial_value);
+ self.owner.set(self.vm().msg_sender());
+ }
+
+ pub fn set_value(&mut self, new_value: U256) {
+ self.value.set(new_value);
+ }
+}
+```
+
+### Key structural differences
+
+1. **Attributes over keywords**: Stylus uses Rust attributes (`#[storage]`, `#[entrypoint]`, `#[public]`) instead of Solidity keywords
+2. **Explicit storage types**: Storage variables use special types like `StorageU256`, `StorageAddress`
+3. **Getter/setter pattern**: Storage access requires explicit `.get()` and `.set()` calls
+4. **Module system**: Rust uses `mod` and `use` for imports instead of `import`
+
+## Function visibility and state mutability
+
+### Visibility
+
+**Solidity:**
+
+```solidity
+function publicFunc() public {}
+function externalFunc() external {}
+function internalFunc() internal {}
+function privateFunc() private {}
+```
+
+**Stylus:**
+
+```rust
+#[public]
+impl MyContract {
+ // Public external functions
+ pub fn public_func(&self) {}
+
+ // Internal functions (not in #[public] block)
+ fn internal_func(&self) {}
+
+ // Private functions
+ fn private_func(&self) {}
+}
+```
+
+In Stylus:
+
+- Functions in `#[public]` blocks are externally callable
+- Regular `pub fn` outside `#[public]` blocks are internal
+- Non-pub functions are private to the module
+
+### State mutability
+
+**Solidity:**
+
+```solidity
+function viewFunc() public view returns (uint256) {}
+function pureFunc() public pure returns (uint256) {}
+function payableFunc() public payable {}
+```
+
+**Stylus:**
+
+```rust
+#[public]
+impl MyContract {
+ // View function (immutable reference)
+ pub fn view_func(&self) -> U256 {
+ self.value.get()
+ }
+
+ // Pure function (no self reference)
+ pub fn pure_func(a: U256, b: U256) -> U256 {
+ a + b
+ }
+
+ // Payable function
+ #[payable]
+ pub fn payable_func(&mut self) {
+ // Can receive Ether
+ }
+
+ // Write function (mutable reference)
+ pub fn write_func(&mut self) {
+ self.value.set(U256::from(42));
+ }
+}
+```
+
+State mutability in Stylus is determined by:
+
+- `&self` → View (read-only)
+- `&mut self` → Write (can modify storage)
+- No `self` → Pure (no storage access)
+- `#[payable]` → Can receive Ether
+
+## Constructors
+
+**Solidity:**
+
+```solidity
+constructor(uint256 initialValue) {
+ value = initialValue;
+}
+```
+
+**Stylus:**
+
+```rust
+#[public]
+impl MyContract {
+ #[constructor]
+ pub fn constructor(&mut self, initial_value: U256) {
+ self.value.set(initial_value);
+ }
+}
+```
+
+Key differences:
+
+- Use `#[constructor]` attribute
+- Constructor name is always `constructor`
+- Can be marked `#[payable]` if needed
+- Called only once during deployment
+- Each contract struct can have only one constructor
+
+## Modifiers
+
+Solidity modifiers don't exist in Stylus. Instead, use regular Rust patterns.
+
+**Solidity:**
+
+```solidity
+modifier onlyOwner() {
+ require(msg.sender == owner, "Not owner");
+ _;
+}
+
+function sensitiveFunction() public onlyOwner {
+ // Function logic
+}
+```
+
+**Stylus:**
+
+```rust
+impl MyContract {
+ fn only_owner(&self) -> Result<(), Vec> {
+ if self.owner.get() != self.vm().msg_sender() {
+ return Err(b"Not owner".to_vec());
+ }
+ Ok(())
+ }
+}
+
+#[public]
+impl MyContract {
+ pub fn sensitive_function(&mut self) -> Result<(), Vec> {
+ self.only_owner()?;
+ // Function logic
+ Ok(())
+ }
+}
+```
+
+Or using custom errors:
+
+```rust
+sol! {
+ error Unauthorized();
+}
+
+#[derive(SolidityError)]
+pub enum MyErrors {
+ Unauthorized(Unauthorized),
+}
+
+impl MyContract {
+ fn only_owner(&self) -> Result<(), MyErrors> {
+ if self.owner.get() != self.vm().msg_sender() {
+ return Err(MyErrors::Unauthorized(Unauthorized {}));
+ }
+ Ok(())
+ }
+}
+```
+
+## Fallback and receive functions
+
+**Solidity:**
+
+```solidity
+receive() external payable {
+ // Handle plain Ether transfers
+}
+
+fallback() external payable {
+ // Handle unmatched calls
+}
+```
+
+**Stylus:**
+
+```rust
+#[public]
+impl MyContract {
+ #[receive]
+ #[payable]
+ pub fn receive(&mut self) -> Result<(), Vec> {
+ // Handle plain Ether transfers
+ Ok(())
+ }
+
+ #[fallback]
+ #[payable]
+ pub fn fallback(&mut self, calldata: &[u8]) -> ArbResult {
+ // Handle unmatched calls
+ Ok(Vec::new())
+ }
+}
+```
+
+Key differences:
+
+- Use `#[receive]` and `#[fallback]` attributes
+- Receive function takes no parameters
+- Fallback function receives calldata as a parameter
+- Both return `Result` types
+
+## Events
+
+**Solidity:**
+
+```solidity
+event Transfer(address indexed from, address indexed to, uint256 value);
+
+function transfer() public {
+ emit Transfer(msg.sender, recipient, amount);
+}
+```
+
+**Stylus:**
+
+```rust
+sol! {
+ event Transfer(address indexed from, address indexed to, uint256 value);
+}
+
+#[public]
+impl MyContract {
+ pub fn transfer(&mut self, recipient: Address, amount: U256) {
+ self.vm().log(Transfer {
+ from: self.vm().msg_sender(),
+ to: recipient,
+ value: amount,
+ });
+ }
+}
+```
+
+Key differences:
+
+- Define events in `sol!` macro
+- Emit using `self.vm().log()`
+- Up to 3 parameters can be indexed
+- Can also use `raw_log()` for custom logging
+
+## Error handling
+
+**Solidity:**
+
+```solidity
+error InsufficientBalance(uint256 requested, uint256 available);
+
+function withdraw(uint256 amount) public {
+ if (balance < amount) {
+ revert InsufficientBalance(amount, balance);
+ }
+}
+```
+
+**Stylus:**
+
+```rust
+sol! {
+ error InsufficientBalance(uint256 requested, uint256 available);
+}
+
+#[derive(SolidityError)]
+pub enum MyErrors {
+ InsufficientBalance(InsufficientBalance),
+}
+
+#[public]
+impl MyContract {
+ pub fn withdraw(&mut self, amount: U256) -> Result<(), MyErrors> {
+ let balance = self.balance.get();
+ if balance < amount {
+ return Err(MyErrors::InsufficientBalance(InsufficientBalance {
+ requested: amount,
+ available: balance,
+ }));
+ }
+ Ok(())
+ }
+}
+```
+
+Key differences:
+
+- Define errors in `sol!` macro
+- Create error enum with `#[derive(SolidityError)]`
+- Return `Result`
+- Use Rust's `?` operator for error propagation
+
+## Inheritance
+
+**Solidity:**
+
+```solidity
+contract Base {
+ function baseFoo() public virtual {}
+}
+
+contract Derived is Base {
+ function baseFoo() public override {}
+}
+```
+
+**Stylus:**
+
+```rust
+#[public]
+trait IBase {
+ fn base_foo(&self);
+}
+
+#[storage]
+struct Base {}
+
+#[public]
+impl IBase for Base {
+ fn base_foo(&self) {
+ // Implementation
+ }
+}
+
+#[storage]
+#[entrypoint]
+struct Derived {
+ base: Base,
+}
+
+#[public]
+#[implements(IBase)]
+impl Derived {}
+
+#[public]
+impl IBase for Derived {
+ fn base_foo(&self) {
+ // Override implementation
+ }
+}
+```
+
+Key differences:
+
+- Use Rust traits for interfaces
+- Composition through storage fields
+- Use `#[implements()]` to expose inherited interfaces
+- No `virtual` or `override` keywords
+
+## Storage
+
+### Storage slots
+
+**Solidity:**
+
+```solidity
+uint256 public value;
+mapping(address => uint256) public balances;
+uint256[] public items;
+```
+
+**Stylus:**
+
+```rust
+#[storage]
+pub struct MyContract {
+ value: StorageU256,
+ balances: StorageMap,
+ items: StorageVec,
+}
+```
+
+### Storage access
+
+**Solidity:**
+
+```solidity
+value = 42;
+uint256 x = value;
+balances[user] = 100;
+```
+
+**Stylus:**
+
+```rust
+self.value.set(U256::from(42));
+let x = self.value.get();
+self.balances.setter(user).set(U256::from(100));
+```
+
+Key differences:
+
+- Explicit storage types (`Storage*`)
+- Must use `.get()` and `.set()`
+- Maps use `.setter()` for write access
+- Storage layout is compatible with Solidity
+
+## Constants and immutables
+
+**Solidity:**
+
+```solidity
+uint256 public constant MAX_SUPPLY = 1000000;
+address public immutable OWNER;
+
+constructor() {
+ OWNER = msg.sender;
+}
+```
+
+**Stylus:**
+
+```rust
+const MAX_SUPPLY: u64 = 1000000;
+
+#[storage]
+#[entrypoint]
+pub struct MyContract {
+ owner: StorageAddress, // Set in constructor
+}
+
+#[public]
+impl MyContract {
+ #[constructor]
+ pub fn constructor(&mut self) {
+ self.owner.set(self.vm().msg_sender());
+ }
+}
+```
+
+Key differences:
+
+- Use Rust `const` for constants
+- No direct equivalent to `immutable` (use storage set once in constructor)
+- Constants can be defined outside structs
+
+## Type system
+
+### Integer types
+
+| Solidity | Stylus (Rust) | Notes |
+| -------------------- | ---------------------- | ------------------------------------- |
+| `uint8` to `uint256` | `u8` to `u128`, `U256` | Native Rust types or Alloy primitives |
+| `int8` to `int256` | `i8` to `i128`, `I256` | Signed integers |
+| `address` | `Address` | 20-byte addresses |
+| `bytes` | `Bytes` | Dynamic bytes |
+| `bytesN` | `FixedBytes` | Fixed-size bytes |
+| `string` | `String` | UTF-8 strings |
+
+### Arrays and mappings
+
+| Solidity | Stylus (Rust) | Notes |
+| ----------------------------- | ------------------------------------------------------------ | -------------- |
+| `uint256[]` | `Vec` (memory) `StorageVec` (storage) | Dynamic arrays |
+| `uint256[5]` | `[U256; 5]` | Fixed arrays |
+| `mapping(address => uint256)` | `StorageMap` | Key-value maps |
+
+## Global variables and functions
+
+### Block and transaction properties
+
+| Solidity | Stylus (Rust) |
+| ----------------- | ----------------------------- |
+| `msg.sender` | `self.vm().msg_sender()` |
+| `msg.value` | `self.vm().msg_value()` |
+| `msg.data` | Access through calldata |
+| `tx.origin` | `self.vm().tx_origin()` |
+| `tx.gasprice` | `self.vm().tx_gas_price()` |
+| `block.number` | `self.vm().block_number()` |
+| `block.timestamp` | `self.vm().block_timestamp()` |
+| `block.basefee` | `self.vm().block_basefee()` |
+| `block.coinbase` | `self.vm().block_coinbase()` |
+
+### Cryptographic functions
+
+| Solidity | Stylus (Rust) |
+| ----------------- | ---------------------------------- |
+| `keccak256(data)` | `self.vm().native_keccak256(data)` |
+| `sha256(data)` | Use external crate |
+| `ecrecover(...)` | Use `crypto::recover()` |
+
+## External calls
+
+**Solidity:**
+
+```solidity
+(bool success, bytes memory data) = address.call{value: amount}(data);
+```
+
+**Stylus:**
+
+```rust
+use stylus_sdk::call::RawCall;
+
+let result = unsafe {
+ RawCall::new(self.vm())
+ .value(amount)
+ .call(address, &data)
+};
+```
+
+Key differences:
+
+- Use `RawCall` for raw calls
+- Calls are `unsafe` in Rust
+- Use type-safe interfaces when possible via `sol_interface!`
+
+## Contract deployment
+
+**Solidity:**
+
+```solidity
+new MyContract{value: amount}(arg1, arg2);
+```
+
+**Stylus:**
+
+```rust
+use stylus_sdk::deploy::RawDeploy;
+
+let contract_address = unsafe {
+ RawDeploy::new(self.vm())
+ .value(amount)
+ .deploy(&bytecode, salt)?
+};
+```
+
+## Assembly
+
+**Solidity:**
+
+```solidity
+assembly {
+ let x := mload(0x40)
+ sstore(0, x)
+}
+```
+
+**Stylus:**
+
+Stylus does not support inline assembly. Instead:
+
+- Use hostio functions for low-level operations
+- Use Rust's `unsafe` blocks when necessary
+- Direct memory manipulation through safe Rust APIs
+
+## Features not in Stylus
+
+1. **No inline assembly**: Use hostio or safe Rust instead
+2. **No `selfdestruct`**: Deprecated in Ethereum, not available in Stylus
+3. **No `delegatecall` from storage**: Available but requires careful use
+4. **No modifier syntax**: Use regular functions
+5. **No multiple inheritance complexity**: Use trait-based composition
+
+## Features unique to Stylus
+
+1. **Rust's type system**: Strong compile-time guarantees
+2. **Zero-cost abstractions**: No overhead for safe code patterns
+3. **Cargo ecosystem**: Access to thousands of Rust crates
+4. **Memory safety**: Rust's borrow checker prevents common bugs
+5. **Better performance**: Wasm execution can be more efficient
+6. **Testing framework**: Use Rust's built-in testing with `TestVM`
+
+## Memory and gas costs
+
+### Memory management
+
+- **Solidity**: Automatic memory management with gas costs for allocation
+- **Stylus**: Manual control with Rust's ownership system, more efficient memory usage
+
+### Gas efficiency
+
+Stylus programs typically use less gas than equivalent Solidity:
+
+- More efficient Wasm execution
+- Better compiler optimizations
+- Fine-grained control over allocations
+
+## Development workflow
+
+### Compilation
+
+**Solidity:**
+
+```shell
+solc --bin --abi MyContract.sol
+```
+
+**Stylus:**
+
+```shell
+cargo stylus build
+```
+
+### Testing
+
+**Solidity:**
+
+```javascript
+// Hardhat or Foundry tests
+```
+
+**Stylus:**
+
+```rust
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use stylus_sdk::testing::*;
+
+ #[test]
+ fn test_function() {
+ let vm = TestVM::default();
+ let mut contract = MyContract::from(&vm);
+ // Test logic
+ }
+}
+```
+
+### Deployment
+
+Both use similar deployment processes but Stylus requires an activation step for new programs.
+
+## Interoperability
+
+Stylus and Solidity contracts can fully interact:
+
+- Stylus can call Solidity contracts
+- Solidity can call Stylus contracts
+- Same ABI encoding/decoding
+- Share storage layout compatibility
+
+Example calling Solidity from Stylus:
+
+```rust
+sol_interface! {
+ interface IToken {
+ function transfer(address to, uint256 amount) external returns (bool);
+ }
+}
+
+#[public]
+impl MyContract {
+ pub fn call_token(&self, token: Address, recipient: Address, amount: U256) -> Result> {
+ let token_contract = IToken::new(token);
+ let result = token_contract.transfer(self.vm(), recipient, amount)?;
+ Ok(result)
+ }
+}
+```
+
+## Best practices for transitioning
+
+1. **Think in Rust patterns**: Don't translate Solidity directly, use idiomatic Rust
+2. **Leverage the type system**: Use Rust's types to prevent bugs at compile time
+3. **Use composition over inheritance**: Prefer traits and composition
+4. **Handle errors explicitly**: Use `Result` types and the `?` operator
+5. **Write tests in Rust**: Take advantage of `TestVM` for unit testing
+6. **Read existing examples**: Study the stylus-sdk-rs examples directory
+7. **Start small**: Convert simple contracts first to learn the patterns
+
+## Resources
+
+- [Stylus SDK Documentation](https://docs.arbitrum.io/stylus/reference/stylus-sdk)
+- [stylus-sdk-rs Repository](https://github.com/OffchainLabs/stylus-sdk-rs)
+- [Example Contracts](https://github.com/OffchainLabs/stylus-sdk-rs/tree/main/examples)
+- [Rust Book](https://doc.rust-lang.org/book/) for learning Rust
diff --git a/docs/stylus/cli-tools-overview.md b/docs/stylus/cli-tools-overview.md
deleted file mode 100644
index ba9675b99d..0000000000
--- a/docs/stylus/cli-tools-overview.md
+++ /dev/null
@@ -1,43 +0,0 @@
----
-id: cli-tools-overview
-title: CLI Tools (cargo-stylus)
-sidebar_label: CLI tools overview
----
-
-The CLI tools provided for Stylus, specifically the `cargo-stylus` tool, are designed to help developers manage, compile, and optimize their Stylus contracts efficiently. This overview provides a summary of the tools available and how to use them effectively.
-
-## Available tools
-
-### 1. Optimize WASM binaries
-
-The `cargo-stylus` tool allows you to optimize WebAssembly (WASM) binaries, ensuring that your contracts are as efficient as possible.
-
-- **[Optimize WASM binaries](/stylus/how-tos/optimizing-binaries.mdx):** Learn how to optimize your WASM binaries for performance and size.
-
-### 2. Debug Stylus transactions
-
-Gain insights into your Stylus contracts by debugging transactions.
-
-- **[Debug Stylus transactions](/stylus/how-tos/debugging-tx.mdx):** A guide to debugging transactions, helping you identify and fix issues.
-
-### 3. Verify contracts
-
-Ensure that your Stylus contracts are correctly verified.
-
-- **[Verify contracts](/stylus/how-tos/verifying-contracts.mdx):** Step-by-step instructions on how to verify your contracts using `cargo-stylus`.
-
-## Source code repository
-
-The source code for `cargo-stylus` is available on GitHub. Explore the code, contribute, or use it as a reference.
-
-- **[cargo-stylus repository](https://github.com/OffchainLabs/stylus):** Visit the GitHub repository for more information.
-
-## Additional resources
-
-For more advanced usage and detailed guides, refer to the following resources:
-
-- **[Optimize WASM binaries](/stylus/how-tos/optimizing-binaries.mdx)**
-- **[Troubleshooting](/stylus/troubleshooting-building-stylus.md)**
-- **[Run a Stylus dev node](/run-arbitrum-node/03-run-local-full-chain-simulation.mdx)**
-
-This overview page serves as the starting point for mastering the CLI tools available for Stylus development. From optimizing your contracts to debugging and verifying them, the `cargo-stylus` toolset is integral to a smooth development experience.
diff --git a/docs/stylus/concepts/activation.mdx b/docs/stylus/concepts/activation.mdx
new file mode 100644
index 0000000000..24c8edf9ed
--- /dev/null
+++ b/docs/stylus/concepts/activation.mdx
@@ -0,0 +1,788 @@
+---
+title: 'Activation'
+description: 'Understanding Stylus contract deployment and activation'
+author: chrisco
+sme: chrisco
+sidebar_position: 1
+target_audience: Developers deploying Stylus contracts to Arbitrum chains.
+displayed_sidebar: buildStylusSidebar
+---
+
+Stylus contracts undergo a two-step process to become executable on Arbitrum chains: **deployment** and **activation**. This guide explains both steps, the distinction between them, and how to manage the activation process.
+
+## Overview
+
+Unlike traditional EVM contracts that become immediately executable after deployment, Stylus contracts require an additional activation step:
+
+1. **Deployment**: Stores the compressed WASM bytecode on-chain at a contract address
+2. **Activation**: Converts the bytecode into an executable Stylus program by registering it with the ArbWasm precompile
+
+**Why two steps?**
+
+- **Gas optimization**: Activation involves one-time processing and caching that would be expensive to repeat on every call
+- **Code reuse**: Multiple contracts can share the same activated codehash, reducing activation costs
+- **Version management**: Allows the chain to track which Stylus protocol version a contract targets
+
+## Deployment vs Activation
+
+| Aspect | Deployment | Activation |
+| --------------------- | ------------------------------ | ------------------------------ |
+| **Purpose** | Store compressed WASM on-chain | Register program with ArbWasm |
+| **Transaction count** | 1 transaction | 1 transaction (separate) |
+| **Cost type** | Standard EVM deployment gas | Data fee (WASM-specific cost) |
+| **When required** | Always - stores the code | Always - makes code executable |
+| **Reversible** | No | No (but can expire) |
+| **Who can call** | Anyone with funds | Anyone (after deployment) |
+| **Can be skipped** | No | No (unless already activated) |
+
+### Contract States
+
+A Stylus contract can be in one of these states:
+
+```rust
+pub enum ContractStatus {
+ /// Contract already exists on-chain and is activated
+ Active { code: Vec },
+
+ /// Contract is deployed but not yet activated
+ /// Ready to activate with the given data fee
+ Ready { code: Vec, fee: U256 },
+}
+```
+
+## The Activation Process
+
+### Step 1: Build and Process WASM
+
+Before deployment, your Rust contract is compiled and processed:
+
+```bash
+cargo stylus check
+```
+
+This performs:
+
+1. **Compile Rust to WASM**: Using `wasm32-unknown-unknown` target
+2. **Process WASM binary**:
+ - Remove dangling references
+ - Add project hash metadata
+ - Strip unnecessary custom sections
+3. **Brotli compression**: Maximum compression (level 11)
+4. **Add EOF prefix**: `EFF00000` (identifies Stylus programs)
+5. **Size validation**: Compressed code must be ≤ 24KB
+
+**WASM Processing Pipeline**:
+
+```
+Raw WASM Binary
+ ↓
+Remove dangling references
+ ↓
+Add project_hash metadata
+ ↓
+Strip user custom sections
+ ↓
+Brotli compress (level 11)
+ ↓
+Add EOF prefix (EFF00000)
+ ↓
+Final compressed code (≤ 24KB)
+```
+
+### Step 2: Deploy the Contract
+
+Deployment creates a transaction that stores your processed WASM on-chain:
+
+```bash
+cargo stylus deploy \
+ --private-key-path=key.txt \
+ --endpoint="https://sepolia-rollup.arbitrum.io/rpc"
+```
+
+**What happens during deployment:**
+
+1. **Generate deployment bytecode**: Create EVM initcode with embedded compressed WASM
+2. **Estimate gas**: Calculate deployment transaction gas cost
+3. **Send deployment transaction**: To Stylus deployer contract
+4. **Extract contract address**: From transaction receipt
+
+**Deployment Bytecode Structure**:
+
+```
+EVM Initcode Prelude (43 bytes):
+┌─────────────────────────────────────┐
+│ 0x7f PUSH32 │ Push code length
+│ 0x80 DUP1 │ Duplicate length
+│ 0x60 PUSH1 │ Push prelude length
+│ 0x60 PUSH1 0x00 │ Push 0
+│ 0x39 CODECOPY │ Copy code to memory
+│ 0x60 PUSH1 0x00 │ Push 0
+│ 0xf3 RETURN │ Return code
+│ 0x00 │ Stylus version
+└─────────────────────────────────────┘
+ ↓
+
+```
+
+### Step 3: Calculate Activation Fee
+
+Before activating, the data fee must be calculated:
+
+```rust
+// Simulated via state overrides (no transaction sent)
+let data_fee = calculate_activation_fee(contract_address);
+
+// Apply bump percentage for safety (default: 20%)
+let final_fee = data_fee * (1 + bump_percent / 100);
+```
+
+**Data fee calculation**:
+
+- Uses state override simulation to estimate fee
+- No actual transaction sent during estimation
+- Configurable bump percentage protects against variance (default: 20%)
+- Fee is paid in ETH when activating
+
+### Step 4: Activate the Contract
+
+Activation registers your contract with the ArbWasm precompile:
+
+```bash
+# Automatic activation (default)
+cargo stylus deploy --private-key-path=key.txt
+
+# Or manual activation
+cargo stylus activate \
+ --address=0x1234... \
+ --private-key-path=key.txt
+```
+
+**What happens during activation:**
+
+1. **Call ArbWasm precompile**: At address `0x0000000000000000000000000000000000000071`
+2. **Send activation transaction**:
+ ```solidity
+ ArbWasm.activateProgram{value: dataFee}(contractAddress)
+ ```
+3. **ArbWasm processes the code**:
+ - Validates WASM format
+ - Checks against protocol version
+ - Stores activation metadata
+ - Emits `ProgramActivated` event
+4. **Returns activation info**:
+ ```solidity
+ returns (uint16 version, uint256 actualDataFee)
+ ```
+
+## Using cargo-stylus
+
+The `cargo-stylus` CLI tool simplifies the deployment and activation workflow.
+
+### Basic Deployment (Automatic Activation)
+
+By default, `cargo stylus deploy` handles both steps:
+
+```bash
+cargo stylus deploy \
+ --private-key-path=wallet.txt \
+ --endpoint="https://sepolia-rollup.arbitrum.io/rpc"
+```
+
+**Output**:
+
+```
+Building contract...
+Compressing WASM...
+Deploying contract to 0x1234567890abcdef...
+Deployment transaction: 0xabcd...
+Contract deployed at: 0x1234567890abcdef
+Activating contract...
+Activation transaction: 0xef12...
+Contract activated successfully!
+```
+
+### Deploy Without Activation
+
+To deploy but skip activation:
+
+```bash
+cargo stylus deploy \
+ --private-key-path=wallet.txt \
+ --no-activate
+```
+
+This is useful when:
+
+- You want to inspect the contract before activating
+- Someone else will handle activation
+- You're testing deployment workflows
+
+### Manual Activation
+
+Activate a previously deployed contract:
+
+```bash
+cargo stylus activate \
+ --address=0x1234567890abcdef \
+ --private-key-path=wallet.txt \
+ --endpoint="https://sepolia-rollup.arbitrum.io/rpc"
+```
+
+### Check Contract Status
+
+Before deploying, check if a contract with the same code is already activated:
+
+```bash
+cargo stylus check \
+ --endpoint="https://sepolia-rollup.arbitrum.io/rpc"
+```
+
+**Possible outcomes**:
+
+1. **Code already activated**: You can reuse the existing deployment
+2. **Ready to activate**: Shows estimated data fee
+3. **Validation errors**: Displays issues that must be fixed
+
+## Deployment with Constructors
+
+If your contract has a constructor, provide arguments during deployment:
+
+```rust
+#[public]
+impl MyContract {
+ #[constructor]
+ pub fn constructor(&mut self, initial_value: U256, owner: Address) {
+ self.value.set(initial_value);
+ self.owner.set(owner);
+ }
+}
+```
+
+**Deploy with constructor arguments**:
+
+```bash
+cargo stylus deploy \
+ --private-key-path=wallet.txt \
+ --constructor-args 42 0x1234567890abcdef1234567890abcdef12345678
+```
+
+**With payable constructor**:
+
+```rust
+#[constructor]
+#[payable]
+pub fn constructor(&mut self) {
+ let value = self.vm().msg_value();
+ self.initial_balance.set(value);
+}
+```
+
+```bash
+cargo stylus deploy \
+ --private-key-path=wallet.txt \
+ --constructor-value=1000000000000000000 # 1 ETH in wei
+```
+
+## The ArbWasm Precompile
+
+Activation is handled by the ArbWasm precompile at address `0x0000000000000000000000000000000000000071`.
+
+### Key Functions
+
+#### activateProgram
+
+Activates a deployed Stylus contract:
+
+```solidity
+function activateProgram(
+ address program
+) external payable returns (uint16 version, uint256 dataFee);
+```
+
+**Parameters**:
+
+- `program`: Contract address containing WASM bytecode
+
+**Payment**:
+
+- Must send `value` equal to the calculated data fee (in wei)
+
+**Returns**:
+
+- `version`: Stylus protocol version the program was activated against
+- `dataFee`: Actual fee paid for activation
+
+**Example (via cast)**:
+
+```bash
+cast send 0x0000000000000000000000000000000000000071 \
+ "activateProgram(address)" \
+ 0x1234567890abcdef \
+ --value 100000000000000000 \
+ --private-key=$PRIVATE_KEY
+```
+
+#### codehashVersion
+
+Check if a codehash is activated and get its version:
+
+```solidity
+function codehashVersion(bytes32 codehash) external view returns (uint16 version);
+```
+
+**Reverts if**:
+
+- Code is not activated
+- Program needs upgrade
+- Program has expired
+
+#### programTimeLeft
+
+Get remaining time before a program expires:
+
+```solidity
+function programTimeLeft(address program) external view returns (uint64 timeLeft);
+```
+
+Returns seconds until expiration (default: ~1 year from activation).
+
+#### codehashKeepalive
+
+Extend a program's expiration time:
+
+```solidity
+function codehashKeepalive(bytes32 codehash) external payable returns (uint64 expirySeconds);
+```
+
+Resets the expiration timer, preventing program deactivation.
+
+### ArbWasm Errors
+
+Activation can fail with these errors:
+
+```solidity
+error ProgramNotWasm();
+// The deployed bytecode is not valid WASM
+
+error ProgramNotActivated();
+// Contract exists but hasn't been activated
+
+error ProgramNeedsUpgrade(uint16 version, uint16 stylusVersion);
+// Program version incompatible with current Stylus version
+
+error ProgramExpired(uint64 ageInSeconds);
+// Program has expired and must be reactivated
+
+error ProgramInsufficientValue(uint256 have, uint256 want);
+// Sent data fee is less than required
+```
+
+## Gas and Fee Optimization
+
+### Estimating Costs
+
+Get cost estimates before deploying:
+
+```bash
+# Estimate deployment gas
+cargo stylus deploy --estimate-gas
+
+# Check activation fee
+cargo stylus check # Shows estimated data fee
+```
+
+### Fee Bump Configuration
+
+Protect against fee variance with configurable bump percentage:
+
+```bash
+# Default: 20% bump
+cargo stylus deploy --private-key-path=wallet.txt
+
+# Custom bump percentage
+# (Note: Use programmatically via stylus-tools library)
+```
+
+**In code** (using stylus-tools):
+
+```rust
+use stylus_tools::core::activation::ActivationConfig;
+
+let config = ActivationConfig {
+ data_fee_bump_percent: 25, // 25% safety margin
+};
+```
+
+### Code Reuse Optimization
+
+If your contract's codehash matches an already-activated contract:
+
+```bash
+cargo stylus check
+```
+
+**Output if already activated**:
+
+```
+Checking contract...
+✓ Contract with this codehash is already activated!
+Version: 1
+No activation needed - you can deploy without activating.
+```
+
+You can deploy the contract normally, and it will automatically use the existing activation.
+
+### Contract Caching
+
+After activation, contracts can be cached for cheaper calls:
+
+```solidity
+// ArbWasmCache precompile (0x0000000000000000000000000000000000000072)
+function cacheProgram(address program) external payable returns (uint256);
+```
+
+**Benefits**:
+
+- Reduces gas costs for subsequent contract calls
+- One-time caching fee
+- Shared across all contracts with same codehash
+
+## Advanced Activation Patterns
+
+### Multi-Contract Deployment
+
+When deploying multiple instances of the same contract:
+
+```bash
+# First deployment: full deploy + activate
+cargo stylus deploy --private-key-path=wallet.txt
+# Contract 1: 0xaaaa... (activated)
+
+# Subsequent deployments: deploy only (reuses activation)
+cargo stylus deploy --private-key-path=wallet.txt --no-activate
+# Contract 2: 0xbbbb... (uses existing activation)
+
+cargo stylus deploy --private-key-path=wallet.txt --no-activate
+# Contract 3: 0xcccc... (uses existing activation)
+```
+
+All three contracts share the same codehash and activation, saving on data fees.
+
+### Programmatic Deployment
+
+Using the stylus-tools library directly:
+
+```rust
+use stylus_tools::core::{
+ deployment::{deploy, DeploymentConfig},
+ activation::{activate_contract, ActivationConfig, data_fee},
+ check::{check_contract, ContractStatus},
+};
+use alloy::providers::{Provider, WalletProvider};
+
+async fn deploy_and_activate(
+ provider: &impl Provider + WalletProvider,
+) -> Result> {
+ let contract = /* build contract */;
+
+ // Step 1: Check if already activated
+ let config = CheckConfig::default();
+ match check_contract(&contract, None, &config, provider).await? {
+ ContractStatus::Active { .. } => {
+ println!("Already activated!");
+ // Deploy without activation
+ }
+ ContractStatus::Ready { code, fee } => {
+ println!("Ready to activate. Data fee: {}", fee);
+ // Continue with deployment + activation
+ }
+ }
+
+ // Step 2: Deploy
+ let deploy_config = DeploymentConfig {
+ no_activate: false,
+ ..Default::default()
+ };
+
+ deploy(&contract, &deploy_config, provider).await?;
+
+ // Contract address returned from deployment
+ Ok(contract_address)
+}
+```
+
+### Custom Deployer Contracts
+
+Use a custom deployer contract instead of the default:
+
+```bash
+cargo stylus deploy \
+ --private-key-path=wallet.txt \
+ --deployer-address=0x... \
+ --deployer-salt=0x0000000000000000000000000000000000000000000000000000000000000001
+```
+
+This is useful for:
+
+- CREATE2 deterministic addresses
+- Custom deployment logic
+- Factory patterns
+
+## Contract Lifecycle
+
+### Activation Lifecycle
+
+```
+Deployed → Activated → [Active] → [Keepalive] → [Expired]
+ ↑ ↓
+ └──────────┘
+ (Periodic keepalive)
+```
+
+### Expiration and Keepalive
+
+Programs automatically expire after ~1 year (configurable by chain):
+
+```solidity
+// Check time remaining
+uint64 timeLeft = ArbWasm.programTimeLeft(contractAddress);
+
+// Extend expiration
+ArbWasm.codehashKeepalive{value: keepaliveFee}(codehash);
+```
+
+**Why expiration?**
+
+- Prevents abandoned contracts from consuming ArbOS resources
+- Encourages active maintenance
+- Allows protocol upgrades
+
+**Keepalive strategy**:
+
+- Monitor `programTimeLeft()` periodically
+- Call `codehashKeepalive()` before expiration
+- Automated scripts can handle this
+
+### Reactivation After Expiry
+
+If a program expires:
+
+```bash
+# Reactivate the existing deployment
+cargo stylus activate --address=0x...
+```
+
+The contract code remains on-chain; only the activation state was cleared.
+
+## Troubleshooting
+
+### Common Activation Errors
+
+#### "Program not activated"
+
+**Cause**: Trying to call a deployed but not activated contract
+
+**Solution**:
+
+```bash
+cargo stylus activate --address=0x...
+```
+
+#### "Insufficient value"
+
+**Cause**: Data fee sent is less than required
+
+**Solution**:
+
+- Check current data fee: `cargo stylus check`
+- Increase fee bump percentage
+- Ensure sufficient ETH balance
+
+#### "Program not WASM"
+
+**Cause**: Deployed bytecode is not valid Stylus WASM
+
+**Solution**:
+
+- Verify you deployed the correct contract
+- Rebuild and redeploy: `cargo stylus deploy`
+
+#### "Program needs upgrade"
+
+**Cause**: Contract was activated against an old Stylus version
+
+**Solution**:
+
+- Recompile with latest SDK
+- Redeploy and reactivate
+
+#### "Program expired"
+
+**Cause**: Contract hasn't been kept alive and expired
+
+**Solution**:
+
+```bash
+# Reactivate the contract
+cargo stylus activate --address=0x...
+```
+
+### Debugging Activation
+
+Enable verbose output:
+
+```bash
+# Check detailed status
+cargo stylus check --verbose
+
+# Deploy with verbose logging
+RUST_LOG=debug cargo stylus deploy --private-key-path=wallet.txt
+```
+
+### Verifying Activation Status
+
+Check if a contract is activated:
+
+```bash
+# Via cargo-stylus
+cargo stylus check --address=0x...
+
+# Via cast (calling ArbWasm)
+cast call 0x0000000000000000000000000000000000000071 \
+ "codehashVersion(bytes32)" \
+ $(cast keccak $(cast code 0x...))
+```
+
+## Best Practices
+
+### 1. Always Check Before Deploying
+
+```bash
+cargo stylus check
+```
+
+This prevents deploying duplicate code and wasting gas.
+
+### 2. Use Automatic Activation
+
+Unless you have specific reasons to split deployment and activation, use the default behavior:
+
+```bash
+cargo stylus deploy # Deploys AND activates
+```
+
+### 3. Test on Testnet First
+
+Deploy to Arbitrum Sepolia before mainnet:
+
+```bash
+cargo stylus deploy \
+ --endpoint="https://sepolia-rollup.arbitrum.io/rpc" \
+ --private-key-path=testnet-key.txt
+```
+
+### 4. Monitor Contract Expiration
+
+Set up monitoring for production contracts:
+
+```solidity
+uint64 timeLeft = ArbWasm.programTimeLeft(contractAddress);
+if (timeLeft < 30 days) {
+ // Send alert or trigger keepalive
+}
+```
+
+### 5. Document Activation Details
+
+Track activation information:
+
+- Contract address
+- Activation transaction hash
+- Stylus version
+- Data fee paid
+- Activation timestamp
+
+### 6. Keep SDK Updated
+
+Use the latest Stylus SDK version:
+
+```toml
+[dependencies]
+stylus-sdk = "0.10.0-beta.1"
+```
+
+Older versions may become incompatible with chain upgrades.
+
+### 7. Handle Constructor Arguments Carefully
+
+Type-check constructor arguments:
+
+```bash
+# Incorrect (will fail)
+cargo stylus deploy --constructor-args "hello" 123
+
+# Correct (matches constructor signature)
+cargo stylus deploy --constructor-args 0x1234... 42
+```
+
+## Complete Example
+
+Here's a full deployment workflow:
+
+```bash
+# 1. Create new Stylus project
+cargo stylus new my-token
+cd my-token
+
+# 2. Build and verify locally
+cargo build --release --target wasm32-unknown-unknown
+cargo stylus check
+
+# 3. Test on Arbitrum Sepolia
+export SEPOLIA_ENDPOINT="https://sepolia-rollup.arbitrum.io/rpc"
+export PRIVATE_KEY_PATH="./sepolia-key.txt"
+
+cargo stylus deploy \
+ --endpoint=$SEPOLIA_ENDPOINT \
+ --private-key-path=$PRIVATE_KEY_PATH \
+ --constructor-args "MyToken" "MTK" 18
+
+# 4. Verify deployment
+cargo stylus check \
+ --endpoint=$SEPOLIA_ENDPOINT \
+ --address=0x... # Address from step 3
+
+# 5. Deploy to mainnet
+export MAINNET_ENDPOINT="https://arb1.arbitrum.io/rpc"
+export MAINNET_KEY_PATH="./mainnet-key.txt"
+
+cargo stylus deploy \
+ --endpoint=$MAINNET_ENDPOINT \
+ --private-key-path=$MAINNET_KEY_PATH \
+ --constructor-args "MyToken" "MTK" 18
+
+# 6. Cache the contract (optional, for gas optimization)
+cast send 0x0000000000000000000000000000000000000072 \
+ "cacheProgram(address)" \
+ 0x... # Your contract address \
+ --value 10000000000000000 \
+ --rpc-url=$MAINNET_ENDPOINT \
+ --private-key=$MAINNET_PRIVATE_KEY
+```
+
+## Summary
+
+- **Two-step process**: Deployment stores code, activation makes it executable
+- **cargo-stylus handles both**: Use `deploy` for automatic activation
+- **Data fee required**: Activation costs ETH (separate from deployment gas)
+- **Code reuse**: Identical contracts share activation, saving costs
+- **Expiration**: Programs expire after ~1 year without keepalive
+- **ArbWasm precompile**: All activation goes through address `0x71`
+- **Check first**: Use `cargo stylus check` to avoid duplicate activations
+
+## See Also
+
+- [Contracts](../reference/contracts.mdx) - Writing Stylus contracts
+- [Global Variables and Functions](../reference/global-variables-and-functions.mdx) - VM interface methods
+
+
diff --git a/docs/stylus/concepts/evm-differences.mdx b/docs/stylus/concepts/evm-differences.mdx
new file mode 100644
index 0000000000..e844c7a6ee
--- /dev/null
+++ b/docs/stylus/concepts/evm-differences.mdx
@@ -0,0 +1,638 @@
+---
+title: 'EVM and WASM VM differences'
+description: 'Understand the key differences between the traditional EVM and Nitro WASM VM for Stylus contracts'
+author: chrisco
+sme: chrisco
+sidebar_position: 1
+target_audience: Developers who need to understand how Stylus works with EVM differences.
+displayed_sidebar: buildStylusSidebar
+---
+
+Arbitrum Nitro supports two execution environments: the traditional Ethereum Virtual Machine (EVM) for Solidity contracts and a WebAssembly (WASM) VM for Stylus contracts. While both environments are fully interoperable and share the same state, they differ significantly in their execution models, performance characteristics, and developer experience.
+
+## Execution model
+
+### EVM: Stack-based architecture
+
+The EVM uses a stack-based execution model:
+
+- **Operations**: Work with values on a stack (PUSH, POP, ADD, etc.)
+- **Opcodes**: 256 predefined opcodes with fixed gas costs
+- **Memory**: Linear, byte-addressable memory that grows dynamically
+- **Storage**: 256-bit word-based key-value store
+- **Call depth**: Limited to 1024 levels
+
+**Example EVM execution:**
+
+```
+PUSH1 0x02 // Push 2 onto stack
+PUSH1 0x03 // Push 3 onto stack
+ADD // Pop 2 values, push sum (5)
+```
+
+### WASM: Register-based architecture
+
+The Stylus WASM VM uses a register-based execution model:
+
+- **Operations**: Work with virtual registers and local variables
+- **Instructions**: Thousands of WASM instructions with fine-grained metering
+- **Memory**: Linear memory with explicit grow operations
+- **Storage**: Same 256-bit storage as EVM (shared state)
+- **Call depth**: Same 1024 limit for compatibility
+
+**Example WASM execution:**
+
+```wasm
+(local.get 0) ;; Read from local variable 0
+(local.get 1) ;; Read from local variable 1
+(i32.add) ;; Add and store result
+(local.set 2) ;; Store in local variable 2
+```
+
+## Memory model
+
+### EVM memory
+
+- **Dynamic expansion**: Memory grows in 32-byte chunks
+- **Gas cost**: Quadratic growth (memory expansion gets expensive)
+- **Access pattern**: Byte-level addressing
+- **Limit**: Practical limit around 15 MB due to gas costs
+
+### WASM memory
+
+- **Page-based**: Memory grows in 64 KB pages (WASM standard)
+- **Gas cost**: Linear cost per page through `pay_for_memory_grow`
+- **Access pattern**: Direct memory load/store instructions
+- **Limit**: Can grow much larger efficiently
+
+**Memory growth in Stylus:**
+
+```rust
+// The entrypoint macro automatically handles pay_for_memory_grow
+#[entrypoint]
+pub struct MyContract {
+ // Large data structures are more practical in WASM
+ data: StorageVec,
+}
+
+// Nitro automatically inserts pay_for_memory_grow calls
+// when allocating new pages
+let large_vector = vec![0u8; 100_000]; // Efficient in WASM
+```
+
+:::note
+The Stylus SDK's `entrypoint!` macro includes a no-op call to `pay_for_memory_grow` to ensure the function is referenced. Nitro then automatically inserts actual calls when memory allocation occurs.
+:::
+
+## Gas metering: Ink and gas
+
+### EVM gas metering
+
+- **Unit**: Gas (standard Ethereum unit)
+- **Granularity**: Per opcode (e.g., ADD = 3 gas, SSTORE = 20,000 gas)
+- **Measurement**: Coarse-grained
+- **Refunds**: Available for storage deletions
+
+### Stylus ink metering
+
+Stylus introduces "ink" as a fine-grained metering unit:
+
+- **Unit**: Ink (Stylus-specific, converted to gas)
+- **Granularity**: Per WASM instruction (more fine-grained)
+- **Measurement**: Precise tracking of WASM execution costs
+- **Conversion**: Ink → Gas conversion happens automatically
+
+**Ink to gas conversion:**
+
+```rust
+// Check remaining ink
+let ink_left = evm_ink_left();
+
+// Check remaining gas
+let gas_left = evm_gas_left();
+
+// Get ink price (in gas basis points)
+let ink_price = tx_ink_price();
+
+// Conversion formula:
+// gas = ink * ink_price / 10000
+```
+
+**Why ink?**
+
+1. **Precision**: WASM instructions have varying costs that don't map cleanly to EVM gas
+2. **Efficiency**: Fine-grained metering allows for more accurate pricing
+3. **Performance**: Enables cheaper execution for compute-heavy operations
+4. **Flexibility**: Ink prices can be adjusted without changing contract code
+
+**Gas cost comparison:**
+
+| Operation | EVM Gas | Stylus Gas | Improvement |
+| ------------------- | --------- | ---------- | --------------- |
+| Basic arithmetic | 3-5 | ~1-2 | 2-3x cheaper |
+| Memory operations | Variable | Efficient | 10-100x cheaper |
+| Complex computation | Expensive | Cheap | 10-100x cheaper |
+| Storage operations | Same | Same | Equal |
+| External calls | Same | Same | Equal |
+
+## Instruction sets
+
+### EVM opcodes
+
+- **Count**: ~140 opcodes
+- **Categories**: Arithmetic, logic, storage, flow control, system
+- **Size**: 1 byte per opcode
+- **Examples**:
+ - `ADD`, `MUL`, `SUB`, `DIV` (arithmetic)
+ - `SLOAD`, `SSTORE` (storage)
+ - `CALL`, `DELEGATECALL` (calls)
+ - `SHA3` (hashing)
+
+### WASM instructions
+
+- **Count**: Hundreds of instructions
+- **Categories**: Numeric, memory, control flow, function calls
+- **Size**: Variable encoding (1-5 bytes)
+- **Examples**:
+ - `i32.add`, `i64.mul`, `f64.div` (numeric)
+ - `memory.grow`, `memory.size` (memory)
+ - `call`, `call_indirect` (functions)
+ - Hostio imports (system operations)
+
+**WASM advantages:**
+
+- More expressive instruction set
+- Better compiler optimization targets
+- Efficient handling of complex data structures
+- Native support for 32-bit and 64-bit operations
+
+## Size limits
+
+### EVM contracts
+
+- **Maximum size**: 24,576 bytes (24 KB) of deployed bytecode
+- **Limit reason**: Block gas limit and deployment costs
+- **Workaround**: Contract splitting, proxies
+
+### Stylus contracts
+
+- **Initial limit**: 24 KB (same as EVM for compatibility)
+- **Compressed size**: Can be larger before compression
+- **Future**: Limit may be increased as WASM tooling improves
+- **Practical size**: Stylus programs are often smaller due to efficient compilation
+
+**Size optimization:**
+
+```rust
+// Stylus contracts benefit from:
+// 1. Rust's zero-cost abstractions
+// 2. Dead code elimination by wasm-opt
+// 3. Efficient WASM encoding
+
+#[no_std] // Opt out of standard library for smaller binaries
+extern crate alloc;
+
+// Only the code actually used is included
+use stylus_sdk::prelude::*;
+```
+
+## Storage model
+
+Both EVM and WASM contracts use the **same storage system**:
+
+- **Format**: 256-bit key-value store
+- **Compatibility**: EVM and WASM contracts can share storage
+- **Costs**: SLOAD and SSTORE costs are identical
+- **Caching**: Stylus VM implements storage caching for efficiency
+
+### Storage caching in Stylus
+
+```rust
+use stylus_sdk::prelude::*;
+
+#[storage]
+pub struct Counter {
+ count: StorageU256,
+}
+
+#[public]
+impl Counter {
+ pub fn increment(&mut self) {
+ // First read: full SLOAD cost
+ let current = self.count.get();
+
+ // Write is cached
+ self.count.set(current + U256::from(1));
+
+ // Additional reads in same call are cheaper (cached)
+ let new_value = self.count.get();
+
+ // Cache is automatically flushed at call boundary
+ }
+}
+```
+
+**Cache benefits:**
+
+1. **Reduced gas costs**: Repeated reads are cheaper
+2. **Better performance**: Fewer state trie accesses
+3. **Automatic management**: SDK handles cache flushing
+4. **Compatibility**: Refund logic matches EVM exactly
+
+## Performance characteristics
+
+### Compute operations
+
+| Category | EVM | Stylus WASM | Winner |
+| ------------------- | --------- | ----------- | ------------ |
+| Integer arithmetic | Moderate | Fast | WASM (10x+) |
+| Loops | Expensive | Cheap | WASM (100x+) |
+| Memory copying | Expensive | Cheap | WASM (10x+) |
+| Hashing (keccak256) | Native | Native | Equal |
+| Cryptography | Limited | Efficient | WASM |
+| String operations | Expensive | Cheap | WASM (100x+) |
+
+### Storage operations
+
+| Operation | EVM | Stylus WASM | Winner |
+| --------------- | ---------- | ----------- | ------ |
+| SLOAD | 2,100 gas | 2,100 gas | Equal |
+| SSTORE (new) | 20,000 gas | 20,000 gas | Equal |
+| SSTORE (update) | 5,000 gas | 5,000 gas | Equal |
+| Storage refunds | Standard | Standard | Equal |
+| Cached reads | No | Yes | WASM |
+
+### External interactions
+
+| Operation | EVM | Stylus WASM | Winner |
+| -------------------- | ------------- | ------------- | ------ |
+| Contract calls | ~700 gas base | ~700 gas base | Equal |
+| Cross-language calls | N/A | Efficient | WASM |
+| Event emission | Same cost | Same cost | Equal |
+| Value transfers | Same cost | Same cost | Equal |
+
+## Call semantics
+
+### Interoperability
+
+Both environments support seamless interoperability:
+
+```rust
+// Stylus calling Solidity
+sol_interface! {
+ interface IERC20 {
+ function transfer(address to, uint256 amount) external returns (bool);
+ }
+}
+
+#[public]
+impl MyContract {
+ pub fn call_evm_contract(&self, token: Address) -> Result> {
+ let erc20 = IERC20::new(token);
+ let result = erc20.transfer(self.vm(), recipient, amount)?;
+ Ok(result)
+ }
+}
+```
+
+```solidity
+// Solidity calling Stylus
+interface IStylusContract {
+ function computeHash(bytes calldata data) external view returns (bytes32);
+}
+
+contract EvmContract {
+ function useStylus(address stylusAddr, bytes calldata data) public view returns (bytes32) {
+ return IStylusContract(stylusAddr).computeHash(data);
+ }
+}
+```
+
+### Call costs
+
+- **Same call overhead**: Both directions have similar base costs
+- **ABI encoding**: Identical for both
+- **Gas forwarding**: Follows 63/64 rule in both cases
+- **Return data**: Handled consistently
+
+## Contract lifecycle
+
+### Deployment
+
+**EVM contracts:**
+
+1. Submit init code (constructor bytecode)
+2. EVM executes init code
+3. Returns runtime bytecode
+4. Bytecode stored onchain
+
+**Stylus contracts:**
+
+1. Compile Rust → WASM
+2. Submit WASM code
+3. **Activation step**: One-time compilation to native code
+4. Activated programs cached for efficiency
+5. WASM code stored onchain
+
+**Activation benefits:**
+
+```shell
+# Deploy and activate a Stylus program
+cargo stylus deploy --private-key $PRIVATE_KEY
+
+# Activation happens once
+# Subsequent calls use cached native code
+```
+
+- **One-time cost**: Pay activation gas once
+- **Future savings**: All executions use optimized native code
+- **Upgradeability**: Re-activation needed for upgrades
+
+### Execution flow
+
+**EVM contracts:**
+
+```
+Transaction → EVM → Opcode interpretation → State changes
+```
+
+**Stylus contracts:**
+
+```
+Transaction → WASM VM → Native code execution → State changes
+ ↓
+ Hostio calls for state access
+```
+
+## Developer experience
+
+### EVM development
+
+**Languages**: Solidity, Vyper, Huff
+
+**Tools**:
+
+- Hardhat, Foundry for testing
+- Remix for quick development
+- Ethers.js/Web3.js for interaction
+
+**Debugging**:
+
+- Revert messages
+- Events for tracing
+- Stack traces limited
+
+### Stylus development
+
+**Languages**: Rust, C, C++ (any WASM-compatible language)
+
+**Tools**:
+
+- `cargo stylus` for deployment
+- Standard Rust tooling (cargo, rustc)
+- `TestVM` for unit testing
+- Rust analyzer for IDE support
+
+**Debugging**:
+
+- Full Rust error messages
+- Compile-time safety checks
+- `console!` macro for debug builds
+- Stack traces in development
+
+**Development comparison:**
+
+| Aspect | EVM | Stylus | Notes |
+| --------------- | -------------- | ------------------- | ------------------------------------- |
+| Type safety | Runtime | Compile-time | Rust catches errors before deployment |
+| Memory safety | Manual | Automatic | Rust's borrow checker |
+| Testing | External tools | Built-in Rust tests | `#[test]` functions work natively |
+| Iteration speed | Slower | Faster | No need to redeploy for tests |
+| Learning curve | Moderate | Steeper | Rust has more concepts |
+| Maturity | Very mature | Growing | Solidity has more resources |
+
+## Feature compatibility
+
+### Supported features
+
+Both EVM and Stylus support:
+
+✅ Contract calls and delegate calls
+✅ Value transfers
+✅ Event emission
+✅ Storage operations
+✅ Block and transaction properties
+✅ Cryptographic functions (keccak256)
+✅ Contract creation (CREATE, CREATE2)
+✅ Revert and error handling
+✅ Reentrancy guards
+
+### EVM-specific features not in WASM
+
+❌ Inline assembly (use hostio or Rust instead)
+❌ `selfdestruct` (deprecated in Ethereum anyway)
+❌ Solidity modifiers (use Rust functions)
+❌ Multiple inheritance (use traits and composition)
+
+### Stylus-specific features not in EVM
+
+✅ Access to Rust ecosystem (crates)
+✅ Efficient memory management
+✅ Zero-cost abstractions
+✅ Compile-time guarantees
+✅ Native testing support
+✅ Better optimization opportunities
+
+## State sharing
+
+EVM and WASM contracts share the same blockchain state:
+
+```rust
+// Stylus contract can read EVM contract storage
+#[storage]
+pub struct Bridge {
+ evm_contract: StorageAddress,
+}
+
+#[public]
+impl Bridge {
+ pub fn read_evm_storage(&self, key: U256) -> U256 {
+ // Both VMs use the same storage layout
+ // Can read storage written by EVM contracts
+ storage_load_bytes32(key)
+ }
+}
+```
+
+**Shared state:**
+
+- Account balances
+- Contract storage
+- Contract code
+- Transaction history
+- Block data
+
+## Gas economics
+
+### Cost structure
+
+**EVM contract execution:**
+
+```
+Total cost = Base transaction cost (21,000 gas)
+ + Input data cost (~16 gas/byte)
+ + Execution cost (opcode gas)
+ + Storage cost (SLOAD/SSTORE)
+```
+
+**Stylus contract execution:**
+
+```
+Total cost = Base transaction cost (21,000 gas)
+ + Input data cost (~16 gas/byte)
+ + Execution cost (ink → gas conversion)
+ + Storage cost (same as EVM)
+```
+
+### When to use each
+
+**Use EVM (Solidity) when:**
+
+- Quick prototyping needed
+- Simple contracts with minimal computation
+- Team expertise in Solidity
+- Extensive storage operations (cost is equal)
+- Maximum ecosystem compatibility
+
+**Use Stylus (Rust) when:**
+
+- Compute-intensive operations
+- Complex algorithms or data structures
+- Need for memory safety guarantees
+- Existing Rust codebase to port
+- Optimizing for gas efficiency
+- Cryptographic operations
+- String/byte manipulation
+
+## Best practices
+
+### For EVM contracts
+
+1. **Minimize storage operations**: Use memory when possible
+2. **Optimize loops**: Keep iterations minimal
+3. **Pack storage**: Use smaller types when possible
+4. **Avoid complex math**: Basic operations only
+5. **Use libraries**: Leverage audited code
+
+### For Stylus contracts
+
+1. **Leverage Rust's safety**: Let the compiler catch bugs
+2. **Use iterators**: More efficient than manual loops
+3. **Profile before optimizing**: Use cargo-stylus tools
+4. **Test thoroughly**: Use Rust's built-in test framework
+5. **Consider binary size**: Use `#[no_std]` if needed
+6. **Batch operations**: Take advantage of cheap compute
+
+### Hybrid approach
+
+Many projects can benefit from both:
+
+```rust
+// Compute-heavy logic in Stylus
+#[public]
+impl ComputeEngine {
+ pub fn complex_calculation(&self, data: Vec) -> Vec {
+ // Efficient loops and data processing
+ data.iter()
+ .map(|x| expensive_computation(*x))
+ .collect()
+ }
+}
+```
+
+```solidity
+// Coordination and state management in Solidity
+contract Coordinator {
+ IComputeEngine public engine; // Stylus contract
+
+ function process(uint256[] calldata data) public {
+ uint256[] memory results = engine.complex_calculation(data);
+ // Store results, emit events, etc.
+ }
+}
+```
+
+## Migration considerations
+
+### From Solidity to Stylus
+
+**What stays the same:**
+
+- Contract addresses
+- Storage layout
+- ABIs and interfaces
+- Gas for storage operations
+- Event signatures
+
+**What changes:**
+
+- Programming language (Solidity → Rust)
+- Execution engine (EVM → WASM)
+- Gas costs for compute (usually cheaper)
+- Development workflow
+- Testing approach
+
+**Migration strategy:**
+
+1. Start with compute-heavy functions
+2. Maintain same ABI for compatibility
+3. Test extensively with existing contracts
+4. Monitor gas costs in production
+5. Gradually migrate more functionality
+
+## Future developments
+
+### EVM evolution
+
+- EIP improvements
+- New opcodes
+- Gas repricing
+- EOF (EVM Object Format)
+
+### Stylus evolution
+
+- Support for more languages
+- SIMD instructions
+- Floating point operations
+- Larger contract size limits
+- Further gas optimizations
+- Enhanced debugging tools
+
+## Resources
+
+- [Stylus documentation](https://docs.arbitrum.io/stylus)
+- [Ink and gas metering](https://docs.arbitrum.io/stylus/concepts/gas-metering)
+- [WASM specification](https://webassembly.github.io/spec/)
+- [EVM opcodes reference](https://www.evm.codes/)
+- [Stylus SDK repository](https://github.com/OffchainLabs/stylus-sdk-rs)
+
+## Summary
+
+The WASM VM in Arbitrum Nitro represents a significant evolution in smart contract execution:
+
+**Key advantages of WASM:**
+
+- 10-100x cheaper for compute operations
+- More expressive programming languages
+- Better memory management
+- Compile-time safety guarantees
+- Access to mature language ecosystems
+
+**Key advantages of EVM:**
+
+- Mature tooling and ecosystem
+- Familiar to existing developers
+- No activation cost
+- Decades of collective knowledge
+
+Both execution environments coexist harmoniously on Arbitrum, allowing developers to choose the best tool for each use case while maintaining full interoperability.
diff --git a/docs/stylus/concepts/gas-metering.mdx b/docs/stylus/concepts/gas-metering.mdx
index 9354556719..c10a902f0d 100644
--- a/docs/stylus/concepts/gas-metering.mdx
+++ b/docs/stylus/concepts/gas-metering.mdx
@@ -5,7 +5,7 @@ author: rachel-bousfield
sme: rachel-bousfield
target_audience: 'Developers deploying smart contracts using Stylus.'
sidebar_position: 3
-displayed_sidebar: buildAppsSidebar
+displayed_sidebar: buildStylusSidebar
---
**Gas and ink** are the pricing primitives that are used to determine the cost of handling specific opcodes and host I/Os on Stylus. For an overview of specific opcode and host I/O costs, see [Gas and ink costs](/stylus/reference/opcode-hostio-pricing).
diff --git a/docs/stylus/concepts/public-preview-expectations.mdx b/docs/stylus/concepts/public-preview-expectations.mdx
new file mode 100644
index 0000000000..3ff00bc060
--- /dev/null
+++ b/docs/stylus/concepts/public-preview-expectations.mdx
@@ -0,0 +1,42 @@
+---
+title: 'Public preview: what to expect'
+description: 'Stylus is currently tagged as a `release-candidate` supported by *public preview* documentation. This concept document explains what this means, and what to expect.'
+author: symbolpunk
+sidebar_position: 10
+---
+
+Stylus is currently tagged as a `release-candidate` supported by _public preview_ documentation. This concept document explains what "public preview" means, what to expect from public preview capabilities, and how to engage with our team as you tinker.
+
+### How products are developed at Offchain Labs
+
+Offchain Labs builds products in a way that aligns loosely with the spirit of "building in public". We like to release things **early and often** so that we can capture feedback and iterate in service of your needs, as empirically as possible.
+
+To do this, some of our product offerings are documented with **public preview** disclaimers that look like this:
+
+This banner's purpose is to set expectations while inviting readers like you to express your needs so that we can incorporate them into the way that we iterate on product.
+
+### What to expect when using public preview offerings
+
+As you tinker and provide feedback, we'll be listening. Sometimes, we'll learn something non-obvious that will result in a significant change. More commonly, you'll experience incremental improvements to the developer experience as the offering grows out of its **public preview** status, towards **stable** status.
+
+Public preview offerings are evolving rapidly, so don't expect the degree of release notes discipline that you'd expect from a stable offering. Keep your eyes open for notifications regarding patch, minor, and major changes, along with corresponding relnotes that highlight breaking changes and new capabilities.
+
+### How to provide feedback
+
+Our product team primarily uses three feedback channels while iterating on public preview capabilities:
+
+1. **Docs**: Click on the **Request an update** button located in the top-right corner of any document to provide feedback on the docs and/or developer experience. This will lead you to a prefilled Github issue that members of our product team periodically review.
+2. **Discord**: [Join the Arbitrum Discord](https://discord.gg/arbitrum) to engage with members of the Arbitrum community and product team.
+3. **Google form**: Complete [this form](http://bit.ly/3yy6EUK) to ask for support.
+
+### What to expect when providing feedback
+
+Our ability to respond to feedback is determined by our ever-evolving capacity and priorities. We can't guarantee responses to all feedback submissions, but our small-but-mighty team is listening, and we'll try our best to acknowledge and respond to your feedback. No guarantees though!
+
+:::info
+[Our small-but-mighty team is hiring](https://jobs.lever.co/offchainlabs).
+:::
+
+### Thank you!
+
+Thanks for helping us build things that meet your needs! We're excited to engage with OGs and newcomers alike; please don't hesitate to reach out.
diff --git a/docs/stylus/concepts/webassembly.mdx b/docs/stylus/concepts/webassembly.mdx
new file mode 100644
index 0000000000..456055c36e
--- /dev/null
+++ b/docs/stylus/concepts/webassembly.mdx
@@ -0,0 +1,400 @@
+---
+title: 'WebAssembly in Nitro'
+description: 'Understanding WebAssembly compilation, deployment, and execution in Arbitrum Nitro'
+author: chrisco
+sme: chrisco
+sidebar_position: 1
+target_audience: Developers who need to understand how Stylus works with WebAssembly.
+displayed_sidebar: buildStylusSidebar
+---
+
+WebAssembly (WASM) is a binary instruction format that enables high-performance execution of programs in the Nitro virtual machine. This guide explains how WASM works in the context of Arbitrum Nitro and Stylus smart contract development.
+
+## What is WebAssembly?
+
+WebAssembly is a portable, size-efficient binary format designed for safe execution at near-native speeds. Key characteristics include:
+
+- **Binary format**: Compact representation that's faster to parse than text-based formats
+- **Stack-based VM**: Simple execution model with operand stack
+- **Sandboxed execution**: Memory-safe by design with explicit bounds checking
+- **Language-agnostic**: Can be targeted by many programming languages (Rust, C, C++, etc.)
+
+## Why WebAssembly in Nitro?
+
+Nitro uses WebAssembly as its execution environment for several reasons:
+
+1. **Performance**: WASM compiles to native machine code for fast execution
+2. **Security**: Sandboxed environment prevents unauthorized access
+3. **Portability**: Same bytecode runs identically across all nodes
+4. **Language flexibility**: Developers can use Rust, C, C++, or any language that compiles to WASM
+5. **Determinism**: Guaranteed identical execution across all validators
+
+## WASM compilation target
+
+Stylus contracts are compiled to the `wasm32-unknown-unknown` target, which means:
+
+- **32-bit addressing**: Uses 32-bit pointers and memory addresses
+- **Unknown OS**: No operating system dependencies
+- **Unknown environment**: Minimal runtime assumptions (no std by default)
+
+The `.cargo/config.toml` file in Stylus projects configures the WASM target:
+
+```toml
+[target.wasm32-unknown-unknown]
+rustflags = [
+ "-C", "link-arg=-zstack-size=32768", # 32KB stack
+ "-C", "target-feature=-reference-types", # Disable reference types
+ "-C", "target-feature=+bulk-memory", # Enable bulk memory operations
+]
+```
+
+### Compilation flags
+
+- **Stack size**: Limited to 32KB to ensure bounded memory usage
+- **Bulk memory**: Enables efficient `memory.copy` and `memory.fill` operations
+- **No reference types**: Keeps the WASM simpler and more compatible
+
+## WASM binary structure
+
+A Stylus WASM module consists of several sections:
+
+### Exports
+
+Every Stylus contract exports a `user_entrypoint` function:
+
+```rust
+#[no_mangle]
+pub extern "C" fn user_entrypoint(len: usize) -> usize {
+ // Entry point for all contract calls
+ // len: size of calldata in bytes
+ // returns: size of output data in bytes
+}
+```
+
+This function is automatically generated by the `#[entrypoint]` macro and serves as the single entry point for all contract interactions.
+
+### Imports
+
+WASM modules import low-level functions from the `vm_hooks` module:
+
+```rust
+// Example hostio imports
+extern "C" {
+ fn storage_load_bytes32(key: *const u8, dest: *mut u8);
+ fn storage_store_bytes32(key: *const u8, value: *const u8);
+ fn msg_sender(sender: *mut u8);
+ fn block_timestamp() -> u64;
+ // ... and many more
+}
+```
+
+These imported functions (called "hostio" functions) provide access to blockchain state and functionality.
+
+### Memory
+
+WASM modules use linear memory, which is:
+
+- **Contiguous**: Single continuous address space starting at 0
+- **Growable**: Can expand at runtime (in 64KB pages)
+- **Isolated**: Each contract has its own memory space
+
+Memory growth is explicitly metered:
+
+```rust
+// Exported function that must exist
+#[no_mangle]
+pub extern "C" fn pay_for_memory_grow(pages: u16) {
+ // Called before memory.grow to charge for new pages
+ // Each page is 64KB
+}
+```
+
+### Custom sections
+
+WASM supports custom sections for metadata:
+
+```rust
+// Example: Add version information
+#[link_section = ".custom.stylus-version"]
+static VERSION: [u8; 5] = *b"0.1.0";
+```
+
+Custom sections can store:
+
+- Contract version
+- Source code hashes
+- Compiler metadata
+- ABI information
+
+## Compression and deployment
+
+Before deployment, Stylus contracts undergo compression:
+
+### Brotli compression
+
+```rust
+// From stylus-tools/src/utils/wasm.rs
+pub fn brotli_compress(wasm: impl Read, compression_level: u32) -> io::Result> {
+ let mut compressed = Vec::new();
+ let mut encoder = brotli::CompressorWriter::new(&mut compressed, 4096, compression_level, 22);
+ io::copy(&mut wasm, &mut encoder)?;
+ encoder.flush()?;
+ Ok(compressed)
+}
+```
+
+Brotli compression typically reduces WASM size by 50-70%.
+
+### 0xEFF000 prefix
+
+Compressed WASM is prefixed with `0xEFF000` to identify it as a Stylus program:
+
+```rust
+pub fn add_prefix(compressed_wasm: impl IntoIterator, prefix: &str) -> Vec {
+ let prefix_bytes = hex::decode(prefix.strip_prefix("0x").unwrap_or(prefix)).unwrap();
+ prefix_bytes.into_iter().chain(compressed_wasm).collect()
+}
+```
+
+This prefix allows the Nitro VM to distinguish Stylus contracts from EVM bytecode.
+
+## Contract activation
+
+After deployment, contracts must be **activated** before execution:
+
+### Activation process
+
+1. **Initial deployment**: Contract code is stored on-chain (compressed)
+2. **Activation call**: Special transaction invokes `activateProgram`
+3. **Decompression**: Brotli-compressed WASM is decompressed
+4. **Validation**: WASM is checked for:
+ - Valid structure
+ - Required exports (`user_entrypoint`)
+ - Allowed imports (only `vm_hooks`)
+ - Memory constraints
+5. **Compilation**: WASM is compiled to native machine code
+6. **Caching**: Compiled code is cached for future executions
+
+### One-time cost
+
+Activation incurs a one-time gas cost but provides benefits:
+
+- **Fast execution**: Native code runs 10-100x faster than interpreted
+- **Persistent cache**: Compilation happens once, benefits all future calls
+- **Optimizations**: Native compiler applies target-specific optimizations
+
+### Verification
+
+The activation process checks for the `pay_for_memory_grow` function to verify correct entrypoint setup:
+
+```rust
+// From activation.rs
+if !wasm::has_entrypoint(&wasm)? {
+ bail!("WASM is missing the entrypoint export");
+}
+```
+
+## Development workflow
+
+### 1. Write Rust code
+
+```rust
+use stylus_sdk::{alloy_primitives::U256, prelude::*};
+
+#[entrypoint]
+#[storage]
+pub struct Counter {
+ count: StorageU256,
+}
+
+#[public]
+impl Counter {
+ pub fn increment(&mut self) {
+ let count = self.count.get() + U256::from(1);
+ self.count.set(count);
+ }
+}
+```
+
+### 2. Compile to WASM
+
+```bash
+cargo stylus build
+```
+
+This runs:
+
+```bash
+cargo build \
+ --lib \
+ --locked \
+ --release \
+ --target wasm32-unknown-unknown \
+ --target-dir target/wasm32-unknown-unknown/release
+```
+
+### 3. Optimize (optional)
+
+```bash
+wasm-opt target/wasm32-unknown-unknown/release/my_contract.wasm \
+ -O3 \
+ --strip-debug \
+ -o optimized.wasm
+```
+
+Optimization can reduce size by an additional 10-30%.
+
+### 4. Deploy and activate
+
+```bash
+# Deploy compressed WASM
+cargo stylus deploy --private-key=$PRIVATE_KEY
+
+# Activation happens automatically
+```
+
+## Size limitations
+
+Nitro imposes limits on WASM contract size:
+
+| Limit | Value | Reason |
+| --------------------- | ---------------------- | --------------------------------- |
+| **Uncompressed size** | ~3-4 MB | Memory and processing constraints |
+| **Compressed size** | 24 KB (initial) | Ethereum transaction size limit |
+| **Compressed size** | 128 KB (with EIP-4844) | Larger blob transactions |
+
+To stay within limits:
+
+- Use `#[no_std]` to avoid standard library bloat
+- Strip debug symbols with `--strip-debug`
+- Enable aggressive optimization (`-O3`)
+- Minimize dependencies
+- Use compact data structures
+
+## Memory model
+
+### Linear memory layout
+
+```
+0x00000000 ┌─────────────────┐
+ │ Stack │ 32 KB fixed size
+0x00008000 ├─────────────────┤
+ │ Heap/Data │ Grows upward
+ │ │
+ │ (Available) │
+ │ │
+0xFFFFFFFF └─────────────────┘
+```
+
+### Memory operations
+
+```rust
+// Bulk memory operations (enabled by target config)
+unsafe {
+ // Fast memory copy
+ core::ptr::copy_nonoverlapping(src, dst, len);
+
+ // Fast memory fill
+ core::ptr::write_bytes(ptr, value, len);
+}
+```
+
+The `bulk-memory` feature flag enables efficient WASM instructions like `memory.copy` and `memory.fill`.
+
+## Advanced: WASM instructions
+
+Stylus uses WASM MVP (Minimum Viable Product) instructions plus bulk-memory operations:
+
+### Arithmetic
+
+- `i32.add`, `i32.sub`, `i32.mul`, `i32.div_s`, `i32.div_u`
+- `i64.add`, `i64.sub`, `i64.mul`, `i64.div_s`, `i64.div_u`
+
+### Memory access
+
+- `i32.load`, `i32.store` (32-bit load/store)
+- `i64.load`, `i64.store` (64-bit load/store)
+- `memory.grow` (expand memory)
+- `memory.copy` (bulk copy, requires flag)
+- `memory.fill` (bulk fill, requires flag)
+
+### Control flow
+
+- `call`, `call_indirect` (function calls)
+- `if`, `else`, `block`, `loop` (structured control flow)
+- `br`, `br_if` (branching)
+
+### Not supported
+
+- ❌ Floating point operations (f32, f64)
+- ❌ SIMD operations
+- ❌ Reference types
+- ❌ Multiple memories
+- ❌ Threads
+
+## Best practices
+
+### 1. Minimize binary size
+
+```rust
+// Use #[no_std] when possible
+#![no_std]
+extern crate alloc;
+
+// Avoid large dependencies
+// Prefer: alloy-primitives
+// Avoid: serde_json, regex (unless necessary)
+```
+
+### 2. Optimize memory usage
+
+```rust
+// Stack allocate when possible
+let small_buffer = [0u8; 32];
+
+// Heap allocate only when necessary
+let large_buffer = vec![0u8; 1024];
+```
+
+### 3. Profile before optimizing
+
+```bash
+# Check binary size
+ls -lh target/wasm32-unknown-unknown/release/*.wasm
+
+# Analyze with twiggy
+cargo install twiggy
+twiggy top target/wasm32-unknown-unknown/release/my_contract.wasm
+```
+
+### 4. Test locally
+
+```bash
+# Use cargo-stylus for local testing
+cargo stylus check
+cargo stylus export-abi
+```
+
+### 5. Validate before deployment
+
+```rust
+// Ensure entrypoint exists
+#[entrypoint]
+#[storage]
+pub struct MyContract { /* ... */ }
+
+// Verify required exports
+#[no_mangle]
+pub extern "C" fn pay_for_memory_grow(pages: u16) {
+ // Generated automatically by SDK
+}
+```
+
+## Resources
+
+- [WebAssembly specification](https://webassembly.github.io/spec/)
+- [Rust WASM target documentation](https://doc.rust-lang.org/rustc/platform-support/wasm32-unknown-unknown.html)
+- [Stylus SDK repository](https://github.com/OffchainLabs/stylus-sdk-rs)
+- [Cargo Stylus CLI tool](https://github.com/OffchainLabs/stylus-sdk-rs/tree/main/cargo-stylus)
+- [WASM binary toolkit (wabt)](https://github.com/WebAssembly/wabt)
+- [Binaryen optimization tools](https://github.com/WebAssembly/binaryen)
diff --git a/docs/stylus/gentle-introduction.mdx b/docs/stylus/gentle-introduction.mdx
index a4bb3621ad..18338666d4 100644
--- a/docs/stylus/gentle-introduction.mdx
+++ b/docs/stylus/gentle-introduction.mdx
@@ -6,7 +6,7 @@ author: amarrazza
sme: amarrazza
target_audience: 'Developers who want to build on Arbitrum using popular programming languages, like Rust'
sidebar_position: 1
-displayed_sidebar: buildAppsSidebar
+displayed_sidebar: buildStylusSidebar
---
import ImageZoom from '@site/src/components/ImageZoom';
@@ -72,7 +72,7 @@ Stylus can integrate into existing Solidity projects by calling a Stylus contrac
### Getting started
-1. Follow the [quickstart](/stylus/quickstart.mdx) to deploy your first Stylus contract, and explore the [Rust SDK](/stylus/reference/overview.md) documentation.
+1. Follow the [quickstart](/stylus/quickstart.mdx) to deploy your first Stylus contract, and explore the [Rust SDK](/stylus/reference/overview.mdx) documentation.
2. Join the Stylus Developer [Telegram](https://t.me/arbitrum_stylus) group and [Arbitrum Discord](https://discord.gg/arbitrum) for community support.
3. Browse the [Awesome Stylus](https://github.com/OffchainLabs/awesome-stylus) repository for community-contributed projects, examples, and tools.
4. Subscribe to the [Stylus Saturdays](https://stylus-saturdays.com/) newsletter for tutorials and technical content.
diff --git a/docs/stylus/how-tos/adding-support-for-new-languages.mdx b/docs/stylus/how-tos/adding-support-for-new-languages.mdx
index 03f167dc7b..346d42abfe 100644
--- a/docs/stylus/how-tos/adding-support-for-new-languages.mdx
+++ b/docs/stylus/how-tos/adding-support-for-new-languages.mdx
@@ -6,14 +6,14 @@ sme: rauljordan
target_audience: 'Developers deploying smart contracts using Stylus'
content_type: how-to
sidebar_position: 1
-displayed_sidebar: buildAppsSidebar
+displayed_sidebar: buildStylusSidebar
---
[Arbitrum Stylus](../gentle-introduction.mdx) is a new technology developed for Arbitrum chains which gives smart contract developers superpowers. With Stylus, developers can write EVM-compatible smart contracts in many different programming languages, and reap massive performance gains. Stylus slashes fees, with performance gains ranging from 10-70x, and memory efficiency gains as high as 100-500x.
This is possible thanks to [WebAssembly](https://www.infoworld.com/article/3291780/what-is-webassembly-the-next-generation-web-platform-explained.html) technology, which all Stylus contracts compile to. Stylus smart contracts live under the **same Ethereum state trie** in Arbitrum nodes, and can fully interoperate with Solidity or Vyper EVM smart contracts. With Stylus, developers can write smart contracts in Rust that talk to Solidity and vice versa without any limitations.
-Today, the Stylus testnet also comes with two officially supported [SDKs](/stylus/overview.mdx) for developers to write contracts in the [Rust](../reference/rust-sdk-guide.md) or [C](https://github.com/OffchainLabs/stylus-sdk-c) programming languages.
+Today, the Stylus testnet also comes with two officially supported SDKs for developers to write contracts in the [Rust](../reference/rust-sdk-guide.md) or [C](https://github.com/OffchainLabs/stylus-sdk-c) programming languages.
However, _anyone_ can add support for new languages in Stylus. **As long as a programming language can compile to WebAssembly**, Stylus will let you use it to write EVM-compatible smart contracts. Note that in order to be deployed onchain, your compiled program must fit under the 24Kb brotli-compressed limit, and should meet Stylus gas metering requirements.
@@ -30,7 +30,7 @@ Programs written in Zig and deployed to Stylus have a tiny footprint and will ha
## Requirements
- Download and install [Zig 0.11.0](https://ziglang.org/downloads)
-- Install [Rust](https://www.rust-lang.org/tools/install), which we'll need for the [Stylus CLI tool](https://github.com/OffchainLabs/cargo-stylus) to deploy our program to the Stylus testnet
+- Install [Rust](https://www.rust-lang.org/tools/install), which we'll need for the [Stylus CLI tool](https://github.com/OffchainLabs/stylus-sdk-rs/tree/main/cargo-stylus) to deploy our program to the Stylus testnet
We'll also be using Rust to run an example script that can call our Zig contract on the Stylus testnet using the popular [ethers-rs](https://github.com/gakonst/ethers-rs) library.
@@ -79,7 +79,7 @@ Next, we can build our Zig library to a freestanding WASM file for our onchain d
zig build-lib ./src/main.zig -target wasm32-freestanding -dynamic --export=user_entrypoint -OReleaseSmall --export=mark_unused
```
-This is enough for us to deploy on the Stylus testnet! We'll use the [Stylus CLI tool](https://github.com/OffchainLabs/cargo-stylus), which you installed earlier using `cargo install`:
+This is enough for us to deploy on the Stylus testnet! We'll use the [Stylus CLI tool](https://github.com/OffchainLabs/stylus-sdk-rs/tree/main/cargo-stylus), which you installed earlier using `cargo install`:
```
cargo stylus deploy --private-key= --wasm-file=main.wasm
diff --git a/docs/stylus/how-tos/caching-contracts.mdx b/docs/stylus/how-tos/caching-contracts.mdx
index 92c3a75094..cd55d7d5c7 100644
--- a/docs/stylus/how-tos/caching-contracts.mdx
+++ b/docs/stylus/how-tos/caching-contracts.mdx
@@ -5,7 +5,7 @@ description: 'A conceptual overview of the Stylus caching strategy and CacheMana
sme: mahsa-moosavi
target_audience: 'Developers deploying smart contracts using Stylus.'
sidebar_position: 3
-displayed_sidebar: buildAppsSidebar
+displayed_sidebar: buildStylusSidebar
---
Stylus is designed for fast computation and efficiency. However,
diff --git a/docs/stylus/how-tos/check-and-deploy.mdx b/docs/stylus/how-tos/check-and-deploy.mdx
new file mode 100644
index 0000000000..aa60343b78
--- /dev/null
+++ b/docs/stylus/how-tos/check-and-deploy.mdx
@@ -0,0 +1,779 @@
+---
+title: 'Check and deploy'
+description: 'Check and deploy Stylus contracts'
+author: chrisco
+sme: chrisco
+sidebar_position: 1
+target_audience: Developers who need to understand how to check and deploy Stylus contracts.
+displayed_sidebar: buildStylusSidebar
+---
+
+This guide explains how to validate and deploy Stylus smart contracts using the `cargo stylus` CLI tool. The process involves two main steps: checking that your contract is valid, and deploying it to an Arbitrum chain.
+
+## Prerequisites
+
+Before checking or deploying contracts, ensure you have:
+
+1. **Rust toolchain** installed (see [rustup.rs](https://rustup.rs))
+2. **WebAssembly target** added:
+ ```shell
+ rustup target add wasm32-unknown-unknown
+ ```
+3. **cargo-stylus CLI** installed:
+ ```shell
+ cargo install cargo-stylus
+ ```
+4. **RPC endpoint** for the target chain (see [testnet information](https://docs.arbitrum.io/stylus/reference/testnet-information))
+5. **Funded account** with ETH for gas and activation fees
+
+## Overview: The Two-Step Process
+
+Deploying a Stylus contract involves two distinct steps:
+
+1. **Deployment**: Upload the compressed WASM bytecode to the chain, assigning it an address
+2. **Activation**: Trigger onchain compilation to native code and cache it for fast execution
+
+The `cargo stylus check` command validates your contract before deployment, and `cargo stylus deploy` handles both steps automatically.
+
+## Checking Contracts
+
+The `cargo stylus check` command validates that your contract can be deployed and activated without actually sending a transaction.
+
+### What check does
+
+1. **Compiles** your Rust code to WASM with `wasm32-unknown-unknown` target
+2. **Compresses** the WASM using brotli compression
+3. **Validates** the WASM structure:
+ - Required exports (`user_entrypoint`)
+ - Allowed imports (only `vm_hooks`)
+ - Memory constraints
+ - Size limits (24KB compressed)
+4. **Simulates activation** using `eth_call` to verify onchain compatibility
+5. **Estimates data fee** required for activation
+
+### Basic usage
+
+```shell
+# Check the current project against Arbitrum Sepolia (default)
+cargo stylus check
+```
+
+### Common options
+
+```shell
+# Check against a specific network
+cargo stylus check \
+ --endpoint="https://arb1.arbitrum.io/rpc"
+
+# Check a specific WASM file
+cargo stylus check \
+ --wasm-file=./target/wasm32-unknown-unknown/release/my_contract.wasm
+
+# Check with a specific contract address
+cargo stylus check \
+ --contract-address=0x1234567890123456789012345678901234567890
+```
+
+### Success output
+
+When your contract passes validation:
+
+```shell
+Finished release [optimized] target(s) in 1.88s
+Reading WASM file at target/wasm32-unknown-unknown/release/my_contract.wasm
+Compressed WASM size: 3 KB
+Contract succeeded Stylus onchain activation checks with Stylus version: 1
+wasm data fee: 0.0001 ETH (originally 0.00008 ETH with 20% bump)
+```
+
+### Failure output
+
+If validation fails, you'll see detailed error information:
+
+```shell
+Reading WASM file at target/wasm32-unknown-unknown/release/bad_contract.wasm
+Compressed WASM size: 55 B
+Stylus checks failed: contract predeployment check failed
+
+Caused by:
+ binary exports reserved symbol stylus_ink_left
+
+Location:
+ prover/src/binary.rs:493:9
+```
+
+Common validation errors include:
+
+- **Missing entrypoint**: Contract lacks `#[entrypoint]` attribute
+- **Invalid exports**: Contract exports reserved symbols
+- **Size limit exceeded**: Compressed WASM exceeds 24KB
+- **Invalid imports**: Contract imports functions outside `vm_hooks`
+- **Memory violations**: Incorrect memory handling or growth
+
+## Deploying Contracts
+
+The `cargo stylus deploy` command compiles, deploys, and activates your contract in a single operation.
+
+### What deploy does
+
+1. **Compiles and checks** the contract (same as `cargo stylus check`)
+2. **Deploys bytecode**: Sends transaction to upload compressed WASM to the chain
+3. **Activates contract**: Calls `activateProgram` precompile to compile to native code
+4. **Verifies success**: Confirms both transactions completed successfully
+
+### Basic deployment
+
+```shell
+# Deploy to Arbitrum Sepolia (default testnet)
+cargo stylus deploy \
+ --private-key-path=./key.txt
+```
+
+### Deployment with gas estimation
+
+Before deploying, estimate the gas required:
+
+```shell
+cargo stylus deploy \
+ --private-key-path=./key.txt \
+ --estimate-gas
+```
+
+Output:
+
+```shell
+Compressed WASM size: 3 KB
+Deploying contract to address 0x457b1ba688e9854bdbed2f473f7510c476a3da09
+Estimated gas: 12756792
+wasm data fee: 0.0001 ETH
+```
+
+### Full deployment
+
+Once estimation looks correct, deploy for real:
+
+```shell
+cargo stylus deploy \
+ --private-key-path=./key.txt
+```
+
+Output shows both transactions:
+
+```shell
+Compressed WASM size: 3 KB
+Deploying contract to address 0x457b1ba688e9854bdbed2f473f7510c476a3da09
+Estimated gas: 12756792
+Submitting tx...
+Confirmed tx 0x42db...7311, gas used 11657164
+
+Activating contract at address 0x457b1ba688e9854bdbed2f473f7510c476a3da09
+Estimated gas: 14251759
+Submitting tx...
+Confirmed tx 0x0bdb...3307, gas used 14204908
+```
+
+### Deployment options
+
+#### Network selection
+
+```shell
+# Deploy to Arbitrum One (mainnet)
+cargo stylus deploy \
+ --endpoint="https://arb1.arbitrum.io/rpc" \
+ --private-key-path=./key.txt
+
+# Deploy to Arbitrum Sepolia (testnet)
+cargo stylus deploy \
+ --endpoint="https://sepolia-rollup.arbitrum.io/rpc" \
+ --private-key-path=./key.txt
+```
+
+#### Private key management
+
+```shell
+# From file (recommended)
+cargo stylus deploy \
+ --private-key-path=./key.txt
+
+# From environment (WARNING: exposes key to shell history)
+cargo stylus deploy \
+ --private-key=$PRIVATE_KEY
+
+# From keystore file
+cargo stylus deploy \
+ --keystore-path=./keystore.json \
+ --keystore-password-path=./password.txt
+```
+
+#### Gas price control
+
+```shell
+# Set custom gas price (in gwei)
+cargo stylus deploy \
+ --private-key-path=./key.txt \
+ --max-fee-per-gas-gwei=0.05
+```
+
+#### Deploy without activation
+
+For advanced use cases, deploy bytecode without immediate activation:
+
+```shell
+cargo stylus deploy \
+ --private-key-path=./key.txt \
+ --no-activate
+```
+
+Later, activate separately:
+
+```shell
+cargo stylus activate \
+ --address=0x457b1ba688e9854bdbed2f473f7510c476a3da09 \
+ --private-key-path=./key.txt
+```
+
+#### Constructor arguments
+
+Deploy contracts with constructor arguments:
+
+```shell
+cargo stylus deploy \
+ --private-key-path=./key.txt \
+ --constructor-args "Hello" "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb" 42
+```
+
+Send ETH to payable constructor:
+
+```shell
+cargo stylus deploy \
+ --private-key-path=./key.txt \
+ --constructor-value=0.1 \
+ --constructor-args "InitialValue"
+```
+
+#### Reproducible builds
+
+For contract verification, use Docker-based reproducible builds:
+
+```shell
+# Default: uses Docker for reproducibility
+cargo stylus deploy \
+ --private-key-path=./key.txt
+
+# Specify cargo-stylus version
+cargo stylus deploy \
+ --private-key-path=./key.txt \
+ --cargo-stylus-version=0.5.0
+```
+
+Skip Docker for local development (non-reproducible):
+
+```shell
+cargo stylus deploy \
+ --private-key-path=./key.txt \
+ --no-verify
+```
+
+## Understanding Activation
+
+Activation is the process of compiling WASM to native machine code onchain.
+
+### Why activation is required
+
+- **Performance**: Native code executes 10-100x faster than interpreted WASM
+- **Validation**: Ensures WASM is well-formed and follows all constraints
+- **Caching**: Compiled code is cached for all future contract calls
+
+### Activation process
+
+1. **Decompress**: Brotli-compressed WASM is decompressed
+2. **Validate**: WASM structure is checked for correctness
+3. **Compile**: WASM is compiled to native machine code
+4. **Cache**: Compiled code is stored in the activation cache
+5. **Charge fee**: Data fee based on WASM size is charged
+
+### Data fee calculation
+
+The data fee depends on the size of your WASM:
+
+```rust
+// From activation.rs
+pub async fn data_fee(
+ code: impl Into,
+ address: Address,
+ config: &ActivationConfig,
+ provider: &impl Provider,
+) -> Result {
+ let result = arbwasm
+ .activateProgram(address)
+ .call()
+ .await?;
+
+ let data_fee = result.dataFee;
+ let bump = config.data_fee_bump_percent; // Default 20%
+ let adjusted = bump_data_fee(data_fee, bump);
+
+ Ok(adjusted)
+}
+```
+
+By default, the fee is bumped by 20% to account for gas price fluctuations.
+
+### Activation errors
+
+Common activation failures:
+
+#### Missing entrypoint
+
+```
+Error: Contract could not be activated as it is missing an entrypoint.
+Please ensure that your contract has an #[entrypoint] defined on your main struct
+```
+
+**Solution**: Add `#[entrypoint]` to your main storage struct:
+
+```rust
+#[entrypoint]
+#[storage]
+pub struct MyContract {
+ // ...
+}
+```
+
+#### Insufficient funds
+
+```
+Error: not enough funds in account 0x... to pay for data fee
+balance 0.0001 ETH < 0.0005 ETH
+```
+
+**Solution**: Fund your account with more ETH. Get testnet ETH from faucets:
+
+- [Arbitrum Sepolia faucet](https://faucet.quicknode.com/arbitrum/sepolia)
+
+#### Invalid WASM
+
+```
+Error: contract activation failed: failed to parse contract
+Caused by: binary exports reserved symbol stylus_ink_left
+```
+
+**Solution**: Ensure you're using the latest `stylus-sdk` version and following SDK conventions.
+
+## Deployment Workflows
+
+### Development workflow
+
+For rapid iteration during development:
+
+```shell
+# 1. Check frequently during development
+cargo stylus check
+
+# 2. Deploy to testnet with no-verify for speed
+cargo stylus deploy \
+ --endpoint="https://sepolia-rollup.arbitrum.io/rpc" \
+ --private-key-path=./key.txt \
+ --no-verify
+
+# 3. Test the deployed contract
+# (use your testing framework)
+
+# 4. Iterate and redeploy as needed
+```
+
+### Production workflow
+
+For production deployments:
+
+```shell
+# 1. Final check against mainnet
+cargo stylus check \
+ --endpoint="https://arb1.arbitrum.io/rpc"
+
+# 2. Estimate costs
+cargo stylus deploy \
+ --endpoint="https://arb1.arbitrum.io/rpc" \
+ --private-key-path=./key.txt \
+ --estimate-gas
+
+# 3. Deploy with reproducible build (for verification)
+cargo stylus deploy \
+ --endpoint="https://arb1.arbitrum.io/rpc" \
+ --private-key-path=./key.txt \
+ --cargo-stylus-version=0.5.0
+
+# 4. Verify the deployed contract
+cargo stylus verify \
+ --endpoint="https://arb1.arbitrum.io/rpc" \
+ --deployment-tx=0x...
+```
+
+### Multi-contract deployment
+
+Deploy multiple contracts from a workspace:
+
+```shell
+# Check all contracts in workspace
+cargo stylus check
+
+# Deploy specific contract
+cargo stylus deploy \
+ --contract=my-token \
+ --private-key-path=./key.txt
+
+# Deploy all contracts (must have no-arg constructors)
+cargo stylus deploy \
+ --private-key-path=./key.txt
+```
+
+## Checking Existing Deployments
+
+### Check if contract is activated
+
+```rust
+// From check.rs
+let codehash = processed.codehash();
+if Contract::exists(codehash, &provider).await? {
+ return Ok(ContractStatus::Active {
+ code: processed.code,
+ });
+}
+```
+
+Use `cargo stylus check` with `--contract-address` to verify an existing deployment:
+
+```shell
+cargo stylus check \
+ --contract-address=0x457b1ba688e9854bdbed2f473f7510c476a3da09
+```
+
+### Re-activation
+
+If a contract is already deployed but not activated, activate it:
+
+```shell
+cargo stylus activate \
+ --address=0x457b1ba688e9854bdbed2f473f7510c476a3da09 \
+ --private-key-path=./key.txt
+```
+
+## Best Practices
+
+### 1. Always check before deploying
+
+```shell
+# ✅ Good: Check first
+cargo stylus check
+cargo stylus deploy --private-key-path=./key.txt
+
+# ❌ Bad: Deploy without checking
+cargo stylus deploy --private-key-path=./key.txt
+```
+
+### 2. Use gas estimation
+
+```shell
+# ✅ Good: Estimate first
+cargo stylus deploy --private-key-path=./key.txt --estimate-gas
+# Review the output, then deploy for real
+cargo stylus deploy --private-key-path=./key.txt
+
+# ❌ Bad: Deploy without estimation
+cargo stylus deploy --private-key-path=./key.txt
+```
+
+### 3. Secure private key handling
+
+```shell
+# ✅ Good: Use key file
+echo $PRIVATE_KEY > /tmp/key.txt
+chmod 600 /tmp/key.txt
+cargo stylus deploy --private-key-path=/tmp/key.txt
+rm /tmp/key.txt
+
+# ⚠️ Risky: Expose key in command line
+cargo stylus deploy --private-key=$PRIVATE_KEY
+```
+
+### 4. Test on testnet first
+
+```shell
+# ✅ Good: Test on Sepolia first
+cargo stylus deploy \
+ --endpoint="https://sepolia-rollup.arbitrum.io/rpc" \
+ --private-key-path=./key.txt
+
+# After testing succeeds, deploy to mainnet
+cargo stylus deploy \
+ --endpoint="https://arb1.arbitrum.io/rpc" \
+ --private-key-path=./key.txt
+
+# ❌ Bad: Deploy directly to mainnet
+cargo stylus deploy \
+ --endpoint="https://arb1.arbitrum.io/rpc" \
+ --private-key-path=./key.txt
+```
+
+### 5. Use reproducible builds for verification
+
+```shell
+# ✅ Good: Reproducible build for mainnet
+cargo stylus deploy \
+ --endpoint="https://arb1.arbitrum.io/rpc" \
+ --private-key-path=./key.txt \
+ --cargo-stylus-version=0.5.0
+
+# Then verify on Arbiscan
+cargo stylus verify \
+ --endpoint="https://arb1.arbitrum.io/rpc" \
+ --deployment-tx=0x...
+
+# ⚠️ OK for development: Skip Docker
+cargo stylus deploy \
+ --endpoint="https://sepolia-rollup.arbitrum.io/rpc" \
+ --private-key-path=./key.txt \
+ --no-verify
+```
+
+### 6. Monitor contract size
+
+```shell
+# Check compressed size
+cargo stylus check
+
+# If size is close to 24KB limit:
+# - Use #[no_std]
+# - Remove unused dependencies
+# - Enable aggressive optimizations
+# - Strip debug symbols
+```
+
+### 7. Verify data fee is reasonable
+
+```shell
+# Check data fee before deploying
+cargo stylus deploy --private-key-path=./key.txt --estimate-gas
+
+# Output shows:
+# wasm data fee: 0.0001 ETH (originally 0.00008 ETH with 20% bump)
+
+# If fee seems high:
+# - Optimize WASM size
+# - Check network congestion
+# - Verify contract correctness
+```
+
+## Troubleshooting
+
+### Size limit errors
+
+**Error**: Compressed WASM exceeds 24KB
+
+**Solutions**:
+
+1. Use `#[no_std]` to eliminate standard library:
+
+ ```rust
+ #![no_std]
+ extern crate alloc;
+ ```
+
+2. Remove unused dependencies from `Cargo.toml`:
+
+ ```toml
+ [dependencies]
+ stylus-sdk = "0.5"
+ # Remove unnecessary crates
+ ```
+
+3. Enable size optimizations in `Cargo.toml`:
+
+ ```toml
+ [profile.release]
+ opt-level = "z"
+ lto = true
+ strip = true
+ ```
+
+4. Use `wasm-opt` for additional optimization:
+ ```shell
+ wasm-opt -Oz -o optimized.wasm input.wasm
+ cargo stylus deploy --wasm-file=optimized.wasm --private-key-path=./key.txt
+ ```
+
+### Activation failures
+
+**Error**: Transaction reverted during activation
+
+**Solutions**:
+
+1. Verify entrypoint exists:
+
+ ```rust
+ #[entrypoint]
+ #[storage]
+ pub struct MyContract { /* ... */ }
+ ```
+
+2. Check WASM validity:
+
+ ```shell
+ cargo stylus check --wasm-file=./target/wasm32-unknown-unknown/release/my_contract.wasm
+ ```
+
+3. Ensure sufficient funds:
+
+ ```shell
+ # Check balance
+ cast balance $YOUR_ADDRESS --rpc-url $RPC_URL
+
+ # Get testnet ETH if needed
+ # Visit faucet.quicknode.com/arbitrum/sepolia
+ ```
+
+### RPC errors
+
+**Error**: Connection timeout or RPC error
+
+**Solutions**:
+
+1. Verify endpoint URL:
+
+ ```shell
+ curl -X POST $RPC_URL \
+ -H "Content-Type: application/json" \
+ -d '{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}'
+ ```
+
+2. Try alternative endpoints:
+
+ ```shell
+ # Arbitrum One
+ --endpoint="https://arb1.arbitrum.io/rpc"
+ --endpoint="https://arbitrum-one.publicnode.com"
+
+ # Arbitrum Sepolia
+ --endpoint="https://sepolia-rollup.arbitrum.io/rpc"
+ --endpoint="https://arbitrum-sepolia.blockpi.network/v1/rpc/public"
+ ```
+
+3. Check network status:
+ - [Arbitrum Status](https://arbiscan.io/)
+ - [Chainlist](https://chainlist.org/)
+
+### Build errors
+
+**Error**: Compilation fails
+
+**Solutions**:
+
+1. Update dependencies:
+
+ ```shell
+ cargo update
+ ```
+
+2. Clear build cache:
+
+ ```shell
+ cargo clean
+ ```
+
+3. Verify Rust toolchain:
+
+ ```shell
+ rustup update
+ rustup target add wasm32-unknown-unknown
+ ```
+
+4. Check SDK version compatibility:
+ ```toml
+ [dependencies]
+ stylus-sdk = "0.5" # Use latest stable version
+ ```
+
+## Non-Rust WASM Deployment
+
+Deploy WASM from any language (C, C++, etc.):
+
+```shell
+# Deploy raw WASM file
+cargo stylus deploy \
+ --wasm-file=./my_contract.wasm \
+ --private-key-path=./key.txt
+
+# Deploy WebAssembly Text (WAT) file
+cargo stylus deploy \
+ --wasm-file=./my_contract.wat \
+ --private-key-path=./key.txt
+```
+
+Example WAT file structure:
+
+```wasm
+(module
+ (memory 0 0)
+ (export "memory" (memory 0))
+ (func (export "user_entrypoint") (param $args_len i32) (result i32)
+ (i32.const 0)
+ ))
+```
+
+## Command Reference
+
+### cargo stylus check
+
+**Syntax**:
+
+```shell
+cargo stylus check [OPTIONS]
+```
+
+**Common options**:
+
+- `--endpoint=`: RPC endpoint (default: Arbitrum Sepolia)
+- `--wasm-file=`: Check specific WASM file
+- `--contract-address=`: Target contract address
+
+### cargo stylus deploy
+
+**Syntax**:
+
+```shell
+cargo stylus deploy [OPTIONS]
+```
+
+**Common options**:
+
+- `--endpoint=`: RPC endpoint
+- `--private-key-path=`: Private key file
+- `--estimate-gas`: Only estimate gas
+- `--no-activate`: Deploy without activation
+- `--no-verify`: Skip Docker reproducible build
+- `--constructor-args `: Constructor arguments
+- `--constructor-value=`: ETH sent to constructor
+- `--max-fee-per-gas-gwei=`: Custom gas price
+
+### cargo stylus activate
+
+**Syntax**:
+
+```shell
+cargo stylus activate --address= [OPTIONS]
+```
+
+**Options**:
+
+- `--address=`: Deployed contract address (required)
+- `--private-key-path=`: Private key file
+- `--estimate-gas`: Only estimate gas
+
+## Resources
+
+- [Stylus quickstart guide](https://docs.arbitrum.io/stylus/stylus-quickstart)
+- [Cargo Stylus repository](https://github.com/OffchainLabs/stylus-sdk-rs/tree/main/cargo-stylus)
+- [Testnet information](https://docs.arbitrum.io/stylus/reference/testnet-information)
+- [Contract verification guide](https://docs.arbitrum.io/stylus/how-tos/verifying-contracts)
+- [Optimizing WASM size](https://github.com/OffchainLabs/stylus-sdk-rs/tree/main/cargo-stylus/blob/main/OPTIMIZING_BINARIES.md)
+- [Valid WASM requirements](https://github.com/OffchainLabs/stylus-sdk-rs/tree/main/cargo-stylus/blob/main/VALID_WASM.md)
diff --git a/docs/stylus/how-tos/debugging-tx.mdx b/docs/stylus/how-tos/debugging-tx.mdx
index c47cb69116..aac7157564 100644
--- a/docs/stylus/how-tos/debugging-tx.mdx
+++ b/docs/stylus/how-tos/debugging-tx.mdx
@@ -7,14 +7,14 @@ sme: mahsamoosavi
target_audience: 'Developers deploying smart contracts using Stylus'
content_type: how-to
sidebar_position: 2
-displayed_sidebar: buildAppsSidebar
+displayed_sidebar: buildStylusSidebar
---
Debugging smart contracts can be challenging, especially when dealing with complex transactions. The `cargo-stylus` crate simplifies the debugging process by allowing developers to replay Stylus transactions. This tool leverages GDB to provide an interactive debugging experience, enabling developers to set breakpoints, inspect state changes, and trace the execution flow step-by-step. This capability is crucial for identifying and resolving issues, ensuring that smart contracts function correctly and efficiently.
### Overview
-Cargo Stylus is a tool designed to simplify the development and debugging process for smart contracts written in Rust for the Stylus execution environment. One of its powerful features is the `cargo stylus` subcommand, which provides essential functionalities for developers:
+Cargo Stylus simplifies the development and debugging of Rust smart contracts for the Stylus execution environment. One of its powerful features is the `cargo stylus` subcommand, which provides essential functionalities for developers:
1. **Trace transactions**: Perform trace calls against Stylus transactions using Ethereum nodes' `debug_traceTransaction` RPC. This feature enables developers to analyze the execution flow and state changes of their transactions in a detailed manner.
2. **Debugging with GDB or LLDB**: Replay and debug the execution of a Stylus transaction using a debugger. This allows developers to set breakpoints, inspect variables, and step through the transaction execution line by line, providing an in-depth understanding of the transaction's behavior.
diff --git a/docs/stylus/how-tos/deploying-non-rust-wasm-contracts.mdx b/docs/stylus/how-tos/deploying-non-rust-wasm-contracts.mdx
new file mode 100644
index 0000000000..a3e111bbba
--- /dev/null
+++ b/docs/stylus/how-tos/deploying-non-rust-wasm-contracts.mdx
@@ -0,0 +1,805 @@
+---
+id: deploying-non-rust-wasm-contracts
+title: 'Deploying non-Rust WASM contracts'
+description: 'Deploy WebAssembly contracts from C, C++, and other languages to Arbitrum Stylus'
+author: chrisco
+sme: chrisco
+target_audience: 'Developers who need to deploy non-Rust WASM contracts'
+content_type: how-to
+sidebar_position: 3
+displayed_sidebar: buildStylusSidebar
+---
+
+# Deploying Non-Rust WASM Contracts
+
+While Rust provides the best developer experience for Stylus, any language that compiles to WebAssembly can be deployed. This guide explains how to deploy WASM contracts written in C, C++, or even pure WebAssembly Text (WAT).
+
+## Overview
+
+Stylus accepts any valid WebAssembly module that meets its requirements. You can:
+
+- Write contracts in **C or C++** using the Stylus C SDK
+- Use **WebAssembly Text (WAT)** for direct bytecode control
+- Compile from **any language** that targets `wasm32-unknown-unknown`
+- Deploy **pre-compiled WASM** binaries directly
+
+The key is using the `--wasm-file` flag with `cargo stylus` commands to bypass Rust compilation.
+
+## Why use non-Rust languages?
+
+Different languages excel at different tasks:
+
+| Language | Best For | Use Cases |
+| ------------------ | -------------------------------------- | -------------------------------------------------- |
+| **C/C++** | Low-level control, cryptography | Hash functions, signature verification, algorithms |
+| **WAT** | Learning, debugging, minimal contracts | Simple logic, educational examples |
+| **AssemblyScript** | TypeScript developers | Web3 integration with familiar syntax |
+| **Other** | Specific requirements | Domain-specific computations |
+
+### When to choose non-Rust
+
+- **Existing codebase**: Port existing C/C++ cryptography libraries
+- **Performance-critical**: Hand-optimized assembly-like control
+- **Minimal size**: Ultra-compact contracts for specific operations
+- **Team expertise**: Leverage existing C/C++ knowledge
+
+### When to stick with Rust
+
+- **Full-featured contracts**: Complex DeFi, NFTs, governance
+- **Type safety**: Strong guarantees and tooling
+- **Ecosystem**: Rich library support and examples
+- **Productivity**: Higher-level abstractions and macros
+
+## WASM requirements
+
+All WASM modules deployed to Stylus must meet these requirements:
+
+### Required exports
+
+```wasm
+(export "user_entrypoint" (func $user_entrypoint))
+(export "memory" (memory 0))
+```
+
+The `user_entrypoint` function:
+
+- **Signature**: `(param i32) (result i32)`
+- **Parameter**: Length of input calldata in bytes
+- **Returns**: Length of output data in bytes
+
+### Allowed imports
+
+Only functions from the `vm_hooks` module are permitted:
+
+```wasm
+(import "vm_hooks" "msg_sender" (func $msg_sender (param i32)))
+(import "vm_hooks" "storage_load_bytes32" (func $storage_load (param i32 i32)))
+(import "vm_hooks" "storage_store_bytes32" (func $storage_store (param i32 i32)))
+```
+
+See the [hostio exports documentation](/stylus/advanced/hostio-exports) for the complete list of available VM hooks.
+
+### Memory requirements
+
+- Linear memory must be exported as `"memory"`
+- Memory growth must be explicitly paid for
+- Initial memory size should be minimal (often `0 0`)
+- Maximum memory is limited by gas costs
+
+### Compilation target
+
+- **Target triple**: `wasm32-unknown-unknown`
+- **No standard library**: WASM runs in a sandboxed environment
+- **No floating point**: Not yet supported by Stylus
+- **No SIMD**: Not yet supported by Stylus
+- **No reference types**: Disabled for compatibility
+
+## WebAssembly Text (WAT)
+
+WAT provides direct control over WASM bytecode using human-readable text format.
+
+### Minimal contract
+
+The simplest valid Stylus contract:
+
+```wasm
+(module
+ ;; Export linear memory
+ (memory 0 0)
+ (export "memory" (memory 0))
+
+ ;; Required entrypoint
+ ;; Takes calldata length, returns output length
+ (func (export "user_entrypoint") (param $args_len i32) (result i32)
+ (i32.const 0) ;; Return 0 bytes
+ ))
+```
+
+Save as `minimal.wat` and deploy:
+
+```shell
+cargo stylus deploy --wasm-file=minimal.wat --private-key-path=./key.txt
+```
+
+### Echo contract
+
+Returns input data unchanged:
+
+```wasm
+(module
+ (memory 1 1)
+ (export "memory" (memory 0))
+
+ ;; Import VM hook to read calldata
+ (import "vm_hooks" "read_args" (func $read_args (param i32)))
+
+ (func (export "user_entrypoint") (param $args_len i32) (result i32)
+ ;; Read calldata into memory at offset 0
+ (call $read_args (i32.const 0))
+
+ ;; Return the same length (echo)
+ (local.get $args_len)
+ ))
+```
+
+### Storage counter
+
+Increment a value in storage:
+
+```wasm
+(module
+ (memory 1 1)
+ (export "memory" (memory 0))
+
+ ;; Import storage operations
+ (import "vm_hooks" "storage_load_bytes32"
+ (func $storage_load (param i32 i32)))
+ (import "vm_hooks" "storage_store_bytes32"
+ (func $storage_store (param i32 i32)))
+
+ (func (export "user_entrypoint") (param $args_len i32) (result i32)
+ ;; Load current value from storage slot 0
+ (call $storage_load
+ (i32.const 0) ;; key pointer
+ (i32.const 32)) ;; value destination
+
+ ;; Increment the value at memory[32]
+ (i32.store (i32.const 32)
+ (i32.add
+ (i32.load (i32.const 32))
+ (i32.const 1)))
+
+ ;; Store back to storage
+ (call $storage_store
+ (i32.const 0) ;; key pointer
+ (i32.const 32)) ;; value pointer
+
+ ;; Return 0 bytes of output
+ (i32.const 0)
+ ))
+```
+
+### Checking WAT contracts
+
+Validate before deploying:
+
+```shell
+cargo stylus check --wasm-file=counter.wat
+```
+
+Output shows validation results:
+
+```
+Reading WASM file at counter.wat
+Compressed WASM size: 142 B
+Contract succeeded Stylus onchain activation checks with Stylus version: 1
+```
+
+## C/C++ development
+
+The [Stylus C SDK](https://github.com/OffchainLabs/stylus-sdk-c) enables C/C++ smart contract development.
+
+### Installation
+
+Install the C SDK:
+
+```shell
+git clone https://github.com/OffchainLabs/stylus-sdk-c.git
+cd stylus-sdk-c
+```
+
+Install dependencies:
+
+```shell
+# macOS
+brew install llvm binaryen wabt
+
+# Ubuntu/Debian
+sudo apt-get install clang lld wasm-ld binaryen wabt
+```
+
+### Project structure
+
+Basic C project layout:
+
+```
+my-contract/
+├── Makefile
+├── src/
+│ └── main.c
+└── include/
+ └── stylus_sdk.h
+```
+
+### Simple C contract
+
+```c
+// main.c
+#include "stylus_sdk.h"
+
+// Storage slot for counter
+static uint8_t counter_slot[32] = {0};
+
+// Main entrypoint
+int main(int argc, char *argv[]) {
+ // Load counter from storage
+ uint8_t value[32];
+ storage_load_bytes32(counter_slot, value);
+
+ // Increment
+ value[31]++;
+
+ // Store back
+ storage_store_bytes32(counter_slot, value);
+
+ return 0;
+}
+```
+
+### C SDK features
+
+The C SDK provides:
+
+```c
+// Account operations
+void msg_sender(uint8_t *sender);
+void tx_origin(uint8_t *origin);
+void contract_address(uint8_t *addr);
+
+// Storage operations
+void storage_load_bytes32(uint8_t *key, uint8_t *dest);
+void storage_store_bytes32(uint8_t *key, uint8_t *value);
+
+// Block information
+uint64_t block_timestamp(void);
+uint64_t block_number(void);
+void block_basefee(uint8_t *basefee);
+
+// Call operations
+void call_contract(
+ uint8_t *contract,
+ uint8_t *calldata,
+ uint32_t calldata_len,
+ uint8_t *value,
+ uint32_t gas,
+ uint8_t *return_data_len
+);
+
+// And many more...
+```
+
+### Building C contracts
+
+Create a Makefile:
+
+```makefile
+CLANG = clang
+WASM_LD = wasm-ld
+WASM_OPT = wasm-opt
+
+CFLAGS = -target wasm32 -nostdlib -O3
+LDFLAGS = -no-entry --export=user_entrypoint --export=memory
+
+SRC = src/main.c
+OUT = build/contract.wasm
+OUT_OPT = build/contract-opt.wasm
+
+all: $(OUT_OPT)
+
+$(OUT): $(SRC)
+ mkdir -p build
+ $(CLANG) $(CFLAGS) -c $(SRC) -o build/main.o
+ $(WASM_LD) $(LDFLAGS) build/main.o -o $(OUT)
+
+$(OUT_OPT): $(OUT)
+ $(WASM_OPT) -Oz $(OUT) -o $(OUT_OPT)
+
+clean:
+ rm -rf build
+
+deploy: $(OUT_OPT)
+ cargo stylus deploy --wasm-file=$(OUT_OPT) \
+ --private-key-path=$$PRIVATE_KEY_PATH
+
+check: $(OUT_OPT)
+ cargo stylus check --wasm-file=$(OUT_OPT)
+```
+
+Build and deploy:
+
+```shell
+make
+make check
+make deploy
+```
+
+### C cryptography example
+
+Verifying a signature:
+
+```c
+#include "stylus_sdk.h"
+#include
+
+// Verify ECDSA signature
+int verify_signature(
+ uint8_t *message_hash,
+ uint8_t *signature,
+ uint8_t *public_key
+) {
+ uint8_t recovered[65];
+
+ // Recover signer from signature
+ if (ecrecover(message_hash, signature, recovered) != 0) {
+ return -1; // Recovery failed
+ }
+
+ // Compare with expected public key
+ if (memcmp(recovered + 1, public_key, 64) == 0) {
+ return 0; // Valid signature
+ }
+
+ return -1; // Invalid signature
+}
+
+int main(int argc, char *argv[]) {
+ uint8_t msg_hash[32];
+ uint8_t sig[65];
+ uint8_t pubkey[64];
+
+ // Read inputs from calldata
+ read_args(0);
+ memcpy(msg_hash, memory, 32);
+ memcpy(sig, memory + 32, 65);
+ memcpy(pubkey, memory + 97, 64);
+
+ // Verify
+ int result = verify_signature(msg_hash, sig, pubkey);
+
+ // Write result
+ memory[0] = (result == 0) ? 1 : 0;
+ write_result(memory, 1);
+
+ return 0;
+}
+```
+
+## AssemblyScript contracts
+
+AssemblyScript is TypeScript-like language that compiles to WebAssembly.
+
+### Installation
+
+```shell
+npm install -g assemblyscript
+npm install @assemblyscript/loader
+```
+
+### Simple AssemblyScript contract
+
+```typescript
+// contract.ts
+
+// Import Stylus VM hooks
+@external("vm_hooks", "msg_sender")
+declare function msg_sender(ptr: usize): void;
+
+@external("vm_hooks", "storage_load_bytes32")
+declare function storage_load(key: usize, dest: usize): void;
+
+@external("vm_hooks", "storage_store_bytes32")
+declare function storage_store(key: usize, value: usize): void;
+
+// Storage key
+const COUNTER_KEY: StaticArray = [0, 0, 0, 0, /* ... 32 zeros ... */];
+
+// Entrypoint
+export function user_entrypoint(args_len: i32): i32 {
+ // Load counter
+ let value = new StaticArray(32);
+ storage_load(
+ changetype(COUNTER_KEY),
+ changetype(value)
+ );
+
+ // Increment
+ value[31]++;
+
+ // Store
+ storage_store(
+ changetype(COUNTER_KEY),
+ changetype(value)
+ );
+
+ return 0; // No output
+}
+```
+
+### Compile AssemblyScript
+
+```shell
+asc contract.ts \
+ --target release \
+ --exportRuntime \
+ --exportTable \
+ -o contract.wasm
+```
+
+### Deploy AssemblyScript contract
+
+```shell
+cargo stylus deploy \
+ --wasm-file=contract.wasm \
+ --private-key-path=./key.txt
+```
+
+## Deployment workflow
+
+### 1. Prepare your WASM
+
+Ensure your WASM module meets requirements:
+
+```shell
+# Check WASM structure with wasm-objdump
+wasm-objdump -x contract.wasm | grep -A 5 "Export\|Import"
+
+# Should show:
+# Export[0]:
+# - func[0]
+# - memory[0]
+# Import[0]:
+# - module="vm_hooks" func=...
+```
+
+### 2. Optimize the WASM
+
+Reduce size with wasm-opt:
+
+```shell
+wasm-opt -Oz contract.wasm -o contract-opt.wasm
+```
+
+### 3. Check before deploying
+
+Validate the contract:
+
+```shell
+cargo stylus check --wasm-file=contract-opt.wasm
+```
+
+### 4. Deploy
+
+Deploy to testnet:
+
+```shell
+cargo stylus deploy \
+ --wasm-file=contract-opt.wasm \
+ --private-key-path=./key.txt \
+ --endpoint="https://sepolia-rollup.arbitrum.io/rpc"
+```
+
+### 5. Verify deployment
+
+Check deployment succeeded:
+
+```shell
+# Output shows:
+Compressed WASM size: 245 B
+Deploying contract to address 0x...
+Confirmed tx 0x...
+Activating contract at address 0x...
+Confirmed tx 0x...
+```
+
+## Best practices
+
+### 1. Minimize binary size
+
+```shell
+# ✅ Good: Optimize aggressively
+wasm-opt -Oz input.wasm -o output.wasm
+
+# Use wasm-strip to remove symbols
+wasm-strip output.wasm
+
+# Check final size
+ls -lh output.wasm
+```
+
+### 2. Test with cargo stylus check
+
+```shell
+# ✅ Good: Always check before deploying
+cargo stylus check --wasm-file=contract.wasm
+
+# Test on testnet first
+cargo stylus deploy \
+ --wasm-file=contract.wasm \
+ --private-key-path=./key.txt \
+ --endpoint="https://sepolia-rollup.arbitrum.io/rpc"
+```
+
+### 3. Use standard memory layout
+
+```c
+// ✅ Good: Predictable memory layout
+uint8_t calldata[1024]; // 0-1023: Input data
+uint8_t storage[32]; // 1024-1055: Storage scratch
+uint8_t output[256]; // 1056-1311: Output buffer
+
+// ❌ Bad: Unpredictable allocations
+uint8_t *data = malloc(size); // No malloc in WASM!
+```
+
+### 4. Handle calldata properly
+
+```wasm
+;; ✅ Good: Read calldata into memory
+(call $read_args (i32.const 0))
+
+;; Process the data
+(call $process_calldata (local.get $args_len))
+
+;; ❌ Bad: Assume calldata location
+(i32.load (i32.const 0)) ;; Calldata not automatically loaded
+```
+
+### 5. Export all required functions
+
+```wasm
+;; ✅ Good: Export entrypoint and memory
+(export "user_entrypoint" (func $main))
+(export "memory" (memory 0))
+
+;; ❌ Bad: Missing exports
+(export "main" (func $main)) ;; Wrong name!
+```
+
+### 6. Use VM hooks correctly
+
+```c
+// ✅ Good: Proper VM hook usage
+uint8_t sender[20];
+msg_sender(sender);
+
+// ✅ Good: Check return values
+uint8_t success;
+call_contract(addr, data, len, value, gas, &success);
+if (!success) {
+ revert("Call failed");
+}
+
+// ❌ Bad: Ignoring errors
+call_contract(addr, data, len, value, gas, NULL);
+```
+
+### 7. Mind the size limit
+
+```shell
+# Check compressed size
+cargo stylus check --wasm-file=contract.wasm
+
+# Should show:
+# Compressed WASM size: < 24 KB
+
+# If too large:
+# - Remove debug symbols
+# - Enable aggressive optimization
+# - Minimize code and data sections
+```
+
+## Troubleshooting
+
+### Missing entrypoint
+
+**Error**: `WASM is missing the entrypoint export`
+
+**Solution**: Ensure `user_entrypoint` is exported:
+
+```wasm
+;; WAT
+(func (export "user_entrypoint") (param i32) (result i32)
+ ;; Implementation
+)
+
+// C
+int user_entrypoint(int argc) __attribute__((export_name("user_entrypoint")));
+```
+
+### Invalid imports
+
+**Error**: `contract imports unauthorized function`
+
+**Solution**: Only import from `vm_hooks`:
+
+```wasm
+;; ✅ Allowed
+(import "vm_hooks" "msg_sender" (func $msg_sender (param i32)))
+
+;; ❌ Not allowed
+(import "env" "print" (func $print (param i32)))
+```
+
+### Memory not exported
+
+**Error**: `WASM must export memory`
+
+**Solution**: Export linear memory:
+
+```wasm
+;; WAT
+(memory 1 1)
+(export "memory" (memory 0))
+
+// C Makefile
+LDFLAGS = -no-entry --export=user_entrypoint --export=memory
+```
+
+### Size too large
+
+**Error**: `Compressed WASM exceeds 24KB`
+
+**Solutions**:
+
+1. Optimize with wasm-opt:
+
+ ```shell
+ wasm-opt -Oz input.wasm -o output.wasm
+ ```
+
+2. Strip symbols:
+
+ ```shell
+ wasm-strip output.wasm
+ ```
+
+3. Remove unused code:
+
+ ```c
+ // Use static/inline for internal functions
+ static inline void helper(void) { }
+ ```
+
+4. Minimize data section:
+
+ ```c
+ // ✅ Good: Minimal data
+ const uint8_t PREFIX[4] = {0xEF, 0xF0, 0x00, 0x00};
+
+ // ❌ Bad: Large data
+ const char *STRINGS[1000] = { /* ... */ };
+ ```
+
+### Compilation errors
+
+**Error**: Clang fails to compile
+
+**Solutions**:
+
+1. Target wasm32:
+
+ ```shell
+ clang -target wasm32 -nostdlib -c main.c
+ ```
+
+2. Disable standard library:
+
+ ```c
+ // Don't use stdio, stdlib, etc.
+ // Use SDK-provided functions
+ ```
+
+3. Check imports/exports:
+ ```shell
+ wasm-objdump -x contract.wasm
+ ```
+
+### Runtime errors
+
+**Error**: Contract reverts unexpectedly
+
+**Solutions**:
+
+1. Check gas usage:
+
+ ```shell
+ cargo stylus deploy --estimate-gas --wasm-file=contract.wasm
+ ```
+
+2. Add debug output (testnet only):
+
+ ```c
+ emit_log(error_msg, sizeof(error_msg));
+ ```
+
+3. Test with minimal input:
+ ```shell
+ # Call with empty calldata
+ cast call $CONTRACT "0x"
+ ```
+
+## Examples repository
+
+Official examples for different languages:
+
+- [**Stylus C SDK**](https://github.com/OffchainLabs/stylus-sdk-c) - C/C++ examples
+- [**Stylus Bf SDK**](https://github.com/OffchainLabs/stylus-sdk-bf) - Brainfuck educational examples
+- [**Awesome Stylus**](https://github.com/OffchainLabs/awesome-stylus) - Community examples
+
+## Language support matrix
+
+| Language | Status | SDK | Best Use Case |
+| ------------------ | --------------- | -------------------------------------------------------------- | --------------------------- |
+| **Rust** | ✅ Production | [stylus-sdk-rs](https://github.com/OffchainLabs/stylus-sdk-rs) | Full-featured contracts |
+| **C/C++** | ✅ Production | [stylus-sdk-c](https://github.com/OffchainLabs/stylus-sdk-c) | Cryptography, algorithms |
+| **WAT** | ✅ Supported | Manual | Minimal contracts, learning |
+| **AssemblyScript** | 🔶 Community | Custom | TypeScript developers |
+| **Go** | 🔶 Experimental | TinyGo | Custom applications |
+| **Zig** | 🔶 Experimental | Custom | Systems programming |
+
+## Advanced: custom languages
+
+To support a new language:
+
+1. **Compile to wasm32-unknown-unknown**
+
+ ```shell
+ your-compiler --target=wasm32-unknown-unknown input.src -o output.wasm
+ ```
+
+2. **Export required functions**
+
+ ```
+ - user_entrypoint(i32) -> i32
+ - memory
+ ```
+
+3. **Import only vm_hooks**
+
+ ```
+ - vm_hooks:msg_sender
+ - vm_hooks:storage_*
+ - etc.
+ ```
+
+4. **Test and deploy**
+ ```shell
+ cargo stylus check --wasm-file=output.wasm
+ cargo stylus deploy --wasm-file=output.wasm --private-key-path=./key.txt
+ ```
+
+## Resources
+
+- [Stylus C SDK](https://github.com/OffchainLabs/stylus-sdk-c)
+- [WebAssembly specification](https://webassembly.github.io/spec/)
+- [WAT format reference](https://webassembly.github.io/spec/core/text/)
+- [Cargo Stylus CLI](https://github.com/OffchainLabs/stylus-sdk-rs/tree/main/cargo-stylus)
+- [Awesome Stylus](https://github.com/OffchainLabs/awesome-stylus)
+- [WASM binary toolkit (wabt)](https://github.com/WebAssembly/wabt)
+- [Binaryen optimization tools](https://github.com/WebAssembly/binaryen)
+
+## Sources
+
+- [GitHub - OffchainLabs/stylus-sdk-c: C/C++ Smart Contracts on Arbitrum](https://github.com/OffchainLabs/stylus-sdk-c)
+- [GitHub - OffchainLabs/awesome-stylus](https://github.com/OffchainLabs/awesome-stylus)
+- [GitHub - OffchainLabs/stylus-sdk-rs: Rust Smart Contracts on Arbitrum](https://github.com/OffchainLabs/stylus-sdk-rs)
diff --git a/docs/stylus/how-tos/exporting-abi.mdx b/docs/stylus/how-tos/exporting-abi.mdx
new file mode 100644
index 0000000000..f75a1e2945
--- /dev/null
+++ b/docs/stylus/how-tos/exporting-abi.mdx
@@ -0,0 +1,996 @@
+---
+title: 'Exporting ABIs'
+description: 'Exporting Solidity ABIs from Stylus contracts'
+author: chrisco
+sme: chrisco
+sidebar_position: 2
+target_audience: Developers who need to understand how to export ABIs from Stylus contracts for integration with front-end applications and other tools.
+displayed_sidebar: buildStylusSidebar
+---
+
+Stylus contracts written in Rust can automatically generate Solidity Application Binary Interfaces (ABIs) that enable seamless interoperability with existing Ethereum tools, front-end libraries, and other smart contracts.
+
+## What is an ABI?
+
+An Application Binary Interface (ABI) defines how to interact with a smart contract:
+
+- **Function signatures**: Names, parameters, and return types
+- **Events**: Event definitions and indexed parameters
+- **Errors**: Custom error types and parameters
+- **Constructor**: Initialization parameters
+
+ABIs enable:
+
+- Front-end libraries (ethers.js, web3.js, viem) to interact with contracts
+- Solidity contracts to call Rust contracts
+- Block explorers to decode transactions
+- Development tools to provide type-safe interfaces
+
+## Overview: ABI Generation
+
+Stylus contracts generate ABIs through:
+
+1. **`#[public]` macro**: Annotates public functions
+2. **`export-abi` feature**: Enables ABI generation code
+3. **`cargo stylus export-abi`**: CLI command to generate output
+4. **Solidity interface**: Generated Solidity interface file
+5. **JSON format**: Optional JSON ABI for tool integration
+
+The process is automatic—just annotate your functions with `#[public]` and run the export command.
+
+## Basic Usage
+
+### Export Solidity interface
+
+Generate a Solidity interface for your contract:
+
+```shell
+cargo stylus export-abi
+```
+
+Output:
+
+```solidity
+/**
+ * This file was automatically generated by Stylus and represents a Rust program.
+ * For more information, please see [The Stylus SDK](https://github.com/OffchainLabs/stylus-sdk-rs).
+ */
+
+// SPDX-License-Identifier: MIT-OR-APACHE-2.0
+pragma solidity ^0.8.23;
+
+interface IMyContract {
+ function getValue() external view returns (uint256);
+
+ function setValue(uint256 new_value) external;
+
+ error Unauthorized(address caller);
+}
+```
+
+### Export to file
+
+Save the interface to a file:
+
+```shell
+cargo stylus export-abi > IMyContract.sol
+```
+
+Or specify output path:
+
+```shell
+cargo stylus export-abi --output=./interfaces/IMyContract.sol
+```
+
+### Export JSON ABI
+
+Generate JSON format ABI (requires `solc` installed):
+
+```shell
+cargo stylus export-abi --json > abi.json
+```
+
+Output:
+
+```json
+[
+ {
+ "type": "function",
+ "name": "getValue",
+ "inputs": [],
+ "outputs": [
+ {
+ "name": "",
+ "type": "uint256",
+ "internalType": "uint256"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "setValue",
+ "inputs": [
+ {
+ "name": "new_value",
+ "type": "uint256",
+ "internalType": "uint256"
+ }
+ ],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ }
+]
+```
+
+## Writing ABI-Compatible Contracts
+
+### Basic contract structure
+
+Contracts must use the `#[public]` macro to generate ABIs:
+
+```rust
+use stylus_sdk::{alloy_primitives::U256, prelude::*};
+
+sol_storage! {
+ #[entrypoint]
+ pub struct Counter {
+ uint256 count;
+ }
+}
+
+#[public]
+impl Counter {
+ // This function will be included in the ABI
+ pub fn get_count(&self) -> U256 {
+ self.count.get()
+ }
+
+ // This function will also be included
+ pub fn increment(&mut self) {
+ let count = self.count.get() + U256::from(1);
+ self.count.set(count);
+ }
+}
+```
+
+Generated interface:
+
+```solidity
+interface ICounter {
+ function getCount() external view returns (uint256);
+
+ function increment() external;
+}
+```
+
+### Function visibility mapping
+
+Rust function signatures map to Solidity visibility:
+
+```rust
+#[public]
+impl MyContract {
+ // Immutable reference → view function
+ pub fn read_value(&self) -> U256 {
+ self.value.get()
+ }
+
+ // Mutable reference → non-view function
+ pub fn write_value(&mut self, new_value: U256) {
+ self.value.set(new_value);
+ }
+
+ // Pure computation (no self) → pure function
+ pub fn compute(a: U256, b: U256) -> U256 {
+ a + b
+ }
+}
+```
+
+Generated Solidity:
+
+```solidity
+interface IMyContract {
+ function readValue() external view returns (uint256);
+
+ function writeValue(uint256 new_value) external;
+
+ function compute(uint256 a, uint256 b) external pure returns (uint256);
+}
+```
+
+### Type mapping
+
+Rust types map to Solidity types automatically:
+
+| Rust Type | Solidity Type | Example |
+| ------------------------- | ------------------------------------- | ---------------------- |
+| `U256` | `uint256` | Token amounts |
+| `U128`, `u128` | `uint128` | Medium integers |
+| `u64`, `u32`, `u16`, `u8` | `uint64`, `uint32`, `uint16`, `uint8` | Small integers |
+| `I256` | `int256` | Signed integers |
+| `Address` | `address` | Account addresses |
+| `bool` | `bool` | Boolean values |
+| `FixedBytes` | `bytesN` | Fixed-size byte arrays |
+| `Bytes` | `bytes` | Dynamic byte arrays |
+| `String` | `string` | UTF-8 strings |
+| `Vec` | `T[]` | Dynamic arrays |
+| `[T; N]` | `T[N]` | Fixed-size arrays |
+
+Example:
+
+```rust
+#[public]
+impl MyContract {
+ pub fn process(
+ owner: Address,
+ amount: U256,
+ data: Bytes,
+ flags: Vec,
+ ) -> Result {
+ // Implementation
+ }
+}
+```
+
+Generates:
+
+```solidity
+interface IMyContract {
+ function process(
+ address owner,
+ uint256 amount,
+ bytes calldata data,
+ bool[] calldata flags
+ ) external returns (string memory);
+}
+```
+
+### Custom errors
+
+Define custom errors with parameters:
+
+```rust
+use stylus_sdk::prelude::*;
+
+sol! {
+ error InsufficientBalance(address account, uint256 requested, uint256 available);
+ error Unauthorized(address caller);
+ error InvalidAmount();
+}
+
+#[public]
+impl Token {
+ pub fn transfer(&mut self, to: Address, amount: U256) -> Result<(), InsufficientBalance> {
+ let balance = self.balances.get(msg::sender());
+ if balance < amount {
+ return Err(InsufficientBalance {
+ account: msg::sender(),
+ requested: amount,
+ available: balance,
+ });
+ }
+ // Transfer logic
+ Ok(())
+ }
+}
+```
+
+Generated interface includes errors:
+
+```solidity
+interface IToken {
+ function transfer(address to, uint256 amount) external;
+
+ error InsufficientBalance(address account, uint256 requested, uint256 available);
+ error Unauthorized(address caller);
+ error InvalidAmount();
+}
+```
+
+### Events
+
+Events are automatically included in the ABI:
+
+```rust
+use stylus_sdk::prelude::*;
+
+sol! {
+ event Transfer(address indexed from, address indexed to, uint256 value);
+ event Approval(address indexed owner, address indexed spender, uint256 value);
+}
+
+#[public]
+impl Token {
+ pub fn transfer(&mut self, to: Address, value: U256) -> bool {
+ // Transfer logic
+ evm::log(Transfer {
+ from: msg::sender(),
+ to,
+ value,
+ });
+ true
+ }
+}
+```
+
+Generated interface:
+
+```solidity
+interface IToken {
+ function transfer(address to, uint256 value) external returns (bool);
+
+ event Transfer(address indexed from, address indexed to, uint256 value);
+ event Approval(address indexed owner, address indexed spender, uint256 value);
+}
+```
+
+## Trait Implementation
+
+Export ABIs for trait implementations:
+
+### Define a trait
+
+```rust
+// ierc20.rs
+use stylus_sdk::prelude::*;
+
+#[public]
+pub trait IErc20 {
+ fn name(&self) -> String;
+ fn symbol(&self) -> String;
+ fn decimals(&self) -> u8;
+ fn total_supply(&self) -> U256;
+ fn balance_of(&self, owner: Address) -> U256;
+ fn transfer(&mut self, to: Address, value: U256) -> Result;
+}
+```
+
+### Implement the trait
+
+```rust
+// lib.rs
+use stylus_sdk::prelude::*;
+
+sol_storage! {
+ #[entrypoint]
+ struct MyToken {
+ // Storage fields
+ }
+}
+
+#[public]
+#[implements(IErc20)]
+impl MyToken {
+ // Additional functions beyond the trait
+ pub fn mint(&mut self, to: Address, value: U256) {
+ // Mint logic
+ }
+}
+
+#[public]
+impl IErc20 for MyToken {
+ fn name(&self) -> String {
+ "My Token".to_string()
+ }
+
+ fn symbol(&self) -> String {
+ "MTK".to_string()
+ }
+
+ fn decimals(&self) -> u8 {
+ 18
+ }
+
+ fn total_supply(&self) -> U256 {
+ self.total_supply.get()
+ }
+
+ fn balance_of(&self, owner: Address) -> U256 {
+ self.balances.get(owner)
+ }
+
+ fn transfer(&mut self, to: Address, value: U256) -> Result {
+ // Transfer logic
+ Ok(true)
+ }
+}
+```
+
+Generated interface with inheritance:
+
+```solidity
+interface IMyToken is IIErc20 {
+ function mint(address to, uint256 value) external;
+}
+
+interface IIErc20 {
+ function name() external view returns (string memory);
+ function symbol() external view returns (string memory);
+ function decimals() external view returns (uint8);
+ function totalSupply() external view returns (uint256);
+ function balanceOf(address owner) external view returns (uint256);
+ function transfer(address to, uint256 value) external returns (bool);
+}
+```
+
+## Constructor Signatures
+
+Export constructor signatures for deployment:
+
+```rust
+sol_storage! {
+ #[entrypoint]
+ struct MyContract {
+ address owner;
+ uint256 initial_value;
+ }
+}
+
+#[public]
+impl MyContract {
+ #[constructor]
+ pub fn new(owner: Address, initial_value: U256) {
+ self.owner.set(owner);
+ self.initial_value.set(initial_value);
+ }
+
+ // Other methods...
+}
+```
+
+Export constructor signature:
+
+```shell
+cargo stylus export-abi constructor
+```
+
+Output:
+
+```
+constructor(address owner, uint256 initial_value)
+```
+
+For payable constructors:
+
+```rust
+#[public]
+impl MyContract {
+ #[constructor]
+ #[payable]
+ pub fn new(owner: Address) {
+ self.owner.set(owner);
+ // msg::value() is available
+ }
+}
+```
+
+Output:
+
+```
+constructor(address owner) payable
+```
+
+## Export Configuration
+
+### Custom license
+
+Specify a custom SPDX license identifier:
+
+```shell
+cargo stylus export-abi --license=GPL-3.0
+```
+
+Output includes:
+
+```solidity
+// SPDX-License-Identifier: GPL-3.0
+pragma solidity ^0.8.23;
+```
+
+### Custom pragma
+
+Specify a custom Solidity version pragma:
+
+```shell
+cargo stylus export-abi --pragma="pragma solidity ^0.8.20;"
+```
+
+### Rust features
+
+Export ABI with specific Rust features enabled:
+
+```shell
+cargo stylus export-abi --rust-features=feature1,feature2
+```
+
+This is useful when your contract has conditional compilation:
+
+```rust
+#[cfg(feature = "advanced")]
+#[public]
+impl MyContract {
+ pub fn advanced_function(&self) -> U256 {
+ // Advanced logic
+ }
+}
+```
+
+## Integration with Front-End
+
+### Using ethers.js
+
+```typescript
+import { ethers } from 'ethers';
+import MyContractABI from './abi.json';
+
+const provider = new ethers.JsonRpcProvider('https://arb1.arbitrum.io/rpc');
+const contract = new ethers.Contract(
+ '0x1234567890123456789012345678901234567890',
+ MyContractABI,
+ provider,
+);
+
+// Call view function
+const value = await contract.getValue();
+console.log('Value:', value.toString());
+
+// Call state-changing function (requires signer)
+const signer = provider.getSigner();
+const contractWithSigner = contract.connect(signer);
+const tx = await contractWithSigner.setValue(42);
+await tx.wait();
+```
+
+### Using viem
+
+```typescript
+import { createPublicClient, http } from 'viem';
+import { arbitrum } from 'viem/chains';
+import MyContractABI from './abi.json';
+
+const client = createPublicClient({
+ chain: arbitrum,
+ transport: http(),
+});
+
+// Read contract
+const value = await client.readContract({
+ address: '0x1234567890123456789012345678901234567890',
+ abi: MyContractABI,
+ functionName: 'getValue',
+});
+
+// Write contract
+const hash = await client.writeContract({
+ address: '0x1234567890123456789012345678901234567890',
+ abi: MyContractABI,
+ functionName: 'setValue',
+ args: [42n],
+});
+```
+
+### Using wagmi/RainbowKit
+
+```typescript
+import { useContractRead, useContractWrite } from 'wagmi';
+import MyContractABI from './abi.json';
+
+function MyComponent() {
+ // Read contract
+ const { data: value } = useContractRead({
+ address: '0x1234567890123456789012345678901234567890',
+ abi: MyContractABI,
+ functionName: 'getValue',
+ });
+
+ // Write contract
+ const { write } = useContractWrite({
+ address: '0x1234567890123456789012345678901234567890',
+ abi: MyContractABI,
+ functionName: 'setValue',
+ });
+
+ return (
+
+
Current value: {value?.toString()}
+
+
+ );
+}
+```
+
+## Solidity Integration
+
+Use exported interfaces in Solidity contracts:
+
+### Import the interface
+
+```solidity
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.23;
+
+import "./IMyContract.sol";
+
+contract SolidityContract {
+ IMyContract public stylusContract;
+
+ constructor(address _stylusContract) {
+ stylusContract = IMyContract(_stylusContract);
+ }
+
+ function interactWithStylus() external {
+ // Read from Stylus contract
+ uint256 value = stylusContract.getValue();
+
+ // Write to Stylus contract
+ stylusContract.setValue(value + 1);
+ }
+}
+```
+
+### Cross-language composition
+
+Combine Solidity and Rust contracts:
+
+```solidity
+contract Router {
+ IToken public token;
+ IStaking public staking;
+
+ constructor(address _token, address _staking) {
+ token = IToken(_token); // Rust contract
+ staking = IStaking(_staking); // Rust contract
+ }
+
+ function stakeTokens(uint256 amount) external {
+ // Transfer tokens (Rust contract)
+ require(
+ token.transferFrom(msg.sender, address(this), amount),
+ "Transfer failed"
+ );
+
+ // Stake tokens (Rust contract)
+ token.approve(address(staking), amount);
+ staking.stake(msg.sender, amount);
+ }
+}
+```
+
+## How It Works
+
+### The export-abi feature
+
+The `export-abi` feature enables ABI generation:
+
+```toml
+# Cargo.toml
+[features]
+export-abi = ["stylus-sdk/export-abi"]
+
+[lib]
+crate-type = ["lib", "cdylib"]
+```
+
+When enabled, the SDK generates:
+
+1. A `GenerateAbi` trait implementation
+2. A CLI entry point for running ABI export
+3. Formatting logic for Solidity interface generation
+
+### Main function
+
+Your contract needs a main function for ABI export:
+
+```rust
+// main.rs
+#![cfg_attr(not(any(test, feature = "export-abi")), no_main)]
+
+#[cfg(not(any(test, feature = "export-abi")))]
+#[no_mangle]
+pub extern "C" fn main() {}
+
+#[cfg(feature = "export-abi")]
+fn main() {
+ my_contract::print_from_args();
+}
+```
+
+This main function:
+
+- Runs only when `export-abi` feature is enabled
+- Executes the ABI generation logic
+- Outputs the Solidity interface to stdout
+
+### The #[public] macro
+
+The `#[public]` macro generates ABI code:
+
+```rust
+// From stylus-proc/src/macros/public/export_abi.rs
+impl GenerateAbi for MyContract {
+ const NAME: &'static str = "MyContract";
+
+ fn fmt_abi(f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ write!(f, "interface I{} {{", Self::NAME)?;
+ // Generate function signatures
+ write!(f, "\n function getValue() external view returns (uint256);")?;
+ writeln!(f, "}}")?;
+ Ok(())
+ }
+}
+```
+
+Key transformations:
+
+- `snake_case` → `camelCase` function names
+- Rust types → Solidity types
+- `&self` → `view`, `&mut self` → non-view
+- `Result` → return type `T`, error `E`
+
+## Best Practices
+
+### 1. Always export ABIs for integration
+
+```shell
+# ✅ Good: Generate and version control ABIs
+cargo stylus export-abi > interfaces/IMyContract.sol
+git add interfaces/IMyContract.sol
+git commit -m "Update contract ABI"
+
+# ❌ Bad: Rely on manual interface definitions
+```
+
+### 2. Use semantic function names
+
+```rust
+// ✅ Good: Clear, descriptive names
+#[public]
+impl Token {
+ pub fn get_balance(&self, account: Address) -> U256 { }
+ pub fn transfer_from(&mut self, from: Address, to: Address, amount: U256) { }
+}
+
+// ❌ Bad: Unclear abbreviations
+#[public]
+impl Token {
+ pub fn bal(&self, acc: Address) -> U256 { }
+ pub fn xfer(&mut self, f: Address, t: Address, amt: U256) { }
+}
+```
+
+### 3. Document complex functions
+
+```rust
+#[public]
+impl Staking {
+ /// Stakes tokens for a specified duration
+ ///
+ /// # Arguments
+ /// * `amount` - Amount of tokens to stake
+ /// * `duration` - Lock duration in seconds
+ ///
+ /// # Returns
+ /// The unique stake ID
+ pub fn stake(&mut self, amount: U256, duration: u64) -> U256 {
+ // Implementation
+ }
+}
+```
+
+### 4. Export JSON for tooling
+
+```shell
+# ✅ Good: Generate both formats
+cargo stylus export-abi > IMyContract.sol
+cargo stylus export-abi --json > abi.json
+
+# Share with front-end team
+cp abi.json ../frontend/src/abis/
+```
+
+### 5. Version control constructor changes
+
+When adding or modifying constructors, regenerate and commit:
+
+```shell
+cargo stylus export-abi constructor > CONSTRUCTOR.txt
+git add CONSTRUCTOR.txt
+git commit -m "Update constructor signature"
+```
+
+### 6. Test ABI compatibility
+
+```typescript
+// test/abi.test.ts
+import { expect } from 'chai';
+import { ethers } from 'hardhat';
+import MyContractABI from '../abi.json';
+
+describe('ABI Compatibility', () => {
+ it('should match deployed contract', async () => {
+ const contract = await ethers.getContractAt(MyContractABI, deployedAddress);
+
+ // Verify functions exist
+ expect(contract.getValue).to.exist;
+ expect(contract.setValue).to.exist;
+
+ // Call and verify
+ const value = await contract.getValue();
+ expect(value).to.be.a('bigint');
+ });
+});
+```
+
+### 7. Keep interfaces synchronized
+
+Use CI/CD to verify ABI is up to date:
+
+```yaml
+# .github/workflows/check-abi.yml
+name: Check ABI
+
+on: [pull_request]
+
+jobs:
+ check-abi:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - name: Install Rust
+ uses: actions-rs/toolchain@v1
+ - name: Generate ABI
+ run: cargo stylus export-abi > /tmp/abi.sol
+ - name: Check for changes
+ run: diff /tmp/abi.sol interfaces/IMyContract.sol
+```
+
+## Troubleshooting
+
+### solc not found
+
+**Error**: `failed to run solc: No such file or directory`
+
+**Solution**: Install Solidity compiler:
+
+```shell
+# macOS
+brew install solidity
+
+# Ubuntu/Debian
+sudo add-apt-repository ppa:ethereum/ethereum
+sudo apt-get update
+sudo apt-get install solc
+
+# Or use solc-select
+pip install solc-select
+solc-select install 0.8.23
+solc-select use 0.8.23
+```
+
+### Feature not enabled
+
+**Error**: `no main function`
+
+**Solution**: Ensure `export-abi` feature is defined and main.rs exists:
+
+```toml
+# Cargo.toml
+[features]
+export-abi = ["stylus-sdk/export-abi"]
+```
+
+```rust
+// main.rs
+#![cfg_attr(not(any(test, feature = "export-abi")), no_main)]
+
+#[cfg(feature = "export-abi")]
+fn main() {
+ my_contract::print_from_args();
+}
+```
+
+### Type not supported
+
+**Error**: `the trait AbiType is not implemented for MyType`
+
+**Solution**: Use supported types or implement `AbiType`:
+
+```rust
+// ✅ Use supported types
+pub fn process(&self, amount: U256) -> U256 { }
+
+// ❌ Custom types need AbiType implementation
+pub fn process(&self, amount: MyCustomType) -> MyCustomType { }
+```
+
+For custom types, implement `AbiType`:
+
+```rust
+use stylus_sdk::abi::AbiType;
+
+#[derive(Clone)]
+struct MyType(U256);
+
+impl AbiType for MyType {
+ type SolType = alloy_sol_types::sol_data::Uint<256>;
+
+ fn encode(&self) -> Vec {
+ self.0.encode()
+ }
+
+ fn decode(data: &[u8]) -> Result {
+ U256::decode(data).map(MyType)
+ }
+}
+```
+
+### Missing function in ABI
+
+**Error**: Function doesn't appear in exported ABI
+
+**Solutions**:
+
+1. Ensure function is in `#[public]` impl block:
+
+ ```rust
+ #[public]
+ impl MyContract {
+ pub fn my_function(&self) -> U256 { } // ✅ Exported
+ }
+
+ impl MyContract {
+ pub fn helper(&self) -> U256 { } // ❌ Not exported
+ }
+ ```
+
+2. Check function visibility is `pub`:
+ ```rust
+ #[public]
+ impl MyContract {
+ pub fn exported(&self) -> U256 { } // ✅ Exported
+ fn not_exported(&self) -> U256 { } // ❌ Not exported
+ }
+ ```
+
+## Advanced: Multiple Contracts
+
+Export ABIs for all contracts in a workspace:
+
+```shell
+# Export specific contract
+cargo stylus export-abi --contract=my-token
+
+# Export all contracts
+for contract in token staking governance; do
+ cargo stylus export-abi --contract=$contract > interfaces/I${contract^}.sol
+done
+```
+
+Or create a script:
+
+```shell
+#!/bin/bash
+# export-all-abis.sh
+
+contracts=("token" "staking" "governance")
+
+for contract in "${contracts[@]}"; do
+ echo "Exporting ABI for $contract..."
+ cargo stylus export-abi --contract=$contract > "interfaces/I${contract^}.sol"
+ cargo stylus export-abi --contract=$contract --json > "abis/${contract}.json"
+done
+
+echo "✅ All ABIs exported"
+```
+
+## Resources
+
+- [Stylus SDK repository](https://github.com/OffchainLabs/stylus-sdk-rs)
+- [Cargo Stylus CLI](https://github.com/OffchainLabs/stylus-sdk-rs/tree/main/cargo-stylus)
+- [ERC-20 example](https://github.com/OffchainLabs/stylus-sdk-rs/tree/main/examples/erc20)
+- [ABI specification](https://docs.soliditylang.org/en/latest/abi-spec.html)
+- [ethers.js documentation](https://docs.ethers.org/)
+- [viem documentation](https://viem.sh/)
+- [wagmi documentation](https://wagmi.sh/)
diff --git a/docs/stylus/how-tos/importing-interfaces.mdx b/docs/stylus/how-tos/importing-interfaces.mdx
new file mode 100644
index 0000000000..d9cb3dcdd8
--- /dev/null
+++ b/docs/stylus/how-tos/importing-interfaces.mdx
@@ -0,0 +1,630 @@
+---
+id: 'importing-interfaces'
+title: 'Import and call external contract interfaces'
+sidebar_label: 'Importing interfaces'
+description: 'Learn how to import Solidity interfaces and call external contracts from your Stylus smart contracts'
+author: 'anegg0'
+sme: 'anegg0'
+user_story: 'As a Rust developer building Stylus smart contracts, I want to interact with other contracts on the blockchain'
+content_type: 'how-to'
+target_audience: 'Developers building Stylus smart contracts that need to interact with other contracts'
+sidebar_position: 8
+displayed_sidebar: buildStylusSidebar
+---
+
+import CustomDetails from '@site/src/components/CustomDetails';
+import { VanillaAdmonition } from '@site/src/components/VanillaAdmonition/';
+
+Interfaces enable your Stylus contract to interact with other contracts on the blockchain, regardless of whether they're written in Solidity, Rust, or another language. This guide shows you how to import and use external contract interfaces in your Stylus smart contracts.
+
+## Why use interfaces
+
+Contract interfaces provide a type-safe way to communicate with other contracts on the blockchain. Common use cases include:
+
+- **Interacting with existing protocols**: Call methods on deployed Solidity contracts like ERC-20 tokens, oracles, or DeFi protocols
+- **Composing functionality**: Build contracts that leverage other contracts' capabilities
+- **Cross-language interoperability**: Stylus contracts can call Solidity contracts and vice versa
+- **Upgradeability patterns**: Use interfaces to interact with proxy contracts
+
+
+ Since interfaces operate at the ABI level, they work identically whether the target contract is
+ written in Solidity, Rust, or any other language that compiles to EVM bytecode.
+
+
+## Prerequisites
+
+Before implementing interfaces, ensure you have:
+
+
+
+Follow the instructions on [Rust Lang's installation page](https://www.rust-lang.org/tools/install) to install a complete Rust toolchain (v1.88 or newer) on your system. After installation, ensure you can access the programs `rustup`, `rustc`, and `cargo` from your preferred terminal application.
+
+
+
+
+
+In your terminal, run:
+
+```shell
+cargo install --force cargo-stylus
+```
+
+Add WASM ([WebAssembly](https://webassembly.org/)) as a build target for the specific Rust toolchain you are using. The below example sets your default Rust toolchain to 1.88 as well as adding the WASM build target:
+
+```shell
+rustup default 1.88
+rustup target add wasm32-unknown-unknown --toolchain 1.88
+```
+
+You can verify that cargo stylus is installed by running `cargo stylus --help` in your terminal, which will return a list of helpful commands.
+
+
+
+## Declaring interfaces with `sol_interface!`
+
+The [`sol_interface!`](https://docs.rs/stylus-sdk/latest/stylus_sdk/prelude/macro.sol_interface.html) macro allows you to declare interfaces using Solidity syntax. It generates Rust structs that represent external contracts and provides type-safe methods for calling them.
+
+### Basic interface declaration
+
+```rust
+use stylus_sdk::prelude::*;
+
+sol_interface! {
+ interface IToken {
+ function balanceOf(address account) external view returns (uint256);
+ function transfer(address to, uint256 amount) external returns (bool);
+ function approve(address spender, uint256 amount) external returns (bool);
+ }
+}
+```
+
+This macro generates an `IToken` struct that you can use to call methods on any deployed contract that implements this interface.
+
+### Declaring multiple interfaces
+
+You can declare multiple interfaces in a single `sol_interface!` block:
+
+```rust
+sol_interface! {
+ interface IPaymentService {
+ function makePayment(address user) payable returns (string);
+ function getBalance(address user) view returns (uint256);
+ }
+
+ interface IOracle {
+ function getPrice(bytes32 feedId) external view returns (uint256);
+ function getLastUpdate() external view returns (uint256);
+ }
+
+ interface IVault {
+ function deposit() external payable;
+ function withdraw(uint256 amount) external;
+ }
+}
+```
+
+
+ Interface declarations use standard Solidity syntax. The SDK computes the correct 4-byte function
+ selectors based on the exact names and parameter types you provide.
+
+
+## Calling external contract methods
+
+Once you've declared an interface, you can call methods on external contracts using instances of the generated struct.
+
+### Creating interface instances
+
+Use the `::new(address)` constructor to create an interface instance pointing to a deployed contract:
+
+```rust
+use alloy_primitives::Address;
+
+// Create an instance pointing to a deployed token contract
+let token_address = Address::from([0x12; 20]); // Replace with actual address
+let token = IToken::new(token_address);
+```
+
+### CamelCase to snake_case conversion
+
+The `sol_interface!` macro converts Solidity's CamelCase method names to Rust's snake_case convention:
+
+| Solidity method | Rust method |
+| --------------- | --------------- |
+| `balanceOf` | `balance_of` |
+| `makePayment` | `make_payment` |
+| `getPrice` | `get_price` |
+| `transferFrom` | `transfer_from` |
+
+The macro preserves the original CamelCase name for computing the correct function selector, so your calls reach the right method on the target contract.
+
+### Basic method calls
+
+Here's how to call methods on an external contract:
+
+```rust
+use stylus_sdk::{call::Call, prelude::*};
+use alloy_primitives::{Address, U256};
+
+sol_interface! {
+ interface IToken {
+ function balanceOf(address account) external view returns (uint256);
+ function transfer(address to, uint256 amount) external returns (bool);
+ }
+}
+
+#[public]
+impl MyContract {
+ pub fn check_balance(&self, token_address: Address, account: Address) -> U256 {
+ let token = IToken::new(token_address);
+ let config = Call::new();
+
+ token.balance_of(self.vm(), config, account).unwrap()
+ }
+}
+```
+
+## Configuring your calls
+
+The Stylus SDK provides three `Call` constructors for different types of external calls. Choosing the correct one is essential for your contract to work properly.
+
+### View calls with `Call::new()`
+
+Use `Call::new()` for read-only calls that don't modify state:
+
+```rust
+use stylus_sdk::call::Call;
+
+#[public]
+impl MyContract {
+ pub fn get_token_balance(&self, token: Address, account: Address) -> U256 {
+ let token_contract = IToken::new(token);
+ let config = Call::new();
+
+ token_contract.balance_of(self.vm(), config, account).unwrap()
+ }
+
+ pub fn get_oracle_price(&self, oracle: Address, feed_id: [u8; 32]) -> U256 {
+ let oracle_contract = IOracle::new(oracle);
+ let config = Call::new();
+
+ oracle_contract.get_price(self.vm(), config, feed_id.into()).unwrap()
+ }
+}
+```
+
+### State-changing calls with `Call::new_mutating(self)`
+
+Use `Call::new_mutating(self)` for calls that modify state on the target contract:
+
+```rust
+#[public]
+impl MyContract {
+ pub fn transfer_tokens(
+ &mut self,
+ token: Address,
+ to: Address,
+ amount: U256,
+ ) -> bool {
+ let token_contract = IToken::new(token);
+ let config = Call::new_mutating(self);
+
+ token_contract.transfer(self.vm(), config, to, amount).unwrap()
+ }
+
+ pub fn approve_spender(
+ &mut self,
+ token: Address,
+ spender: Address,
+ amount: U256,
+ ) -> bool {
+ let token_contract = IToken::new(token);
+ let config = Call::new_mutating(self);
+
+ token_contract.approve(self.vm(), config, spender, amount).unwrap()
+ }
+}
+```
+
+
+ When using `Call::new_mutating(self)`, your method must take `&mut self` as its first parameter.
+ This ensures the Stylus runtime properly handles state changes and reentrancy protection.
+
+
+### Payable calls with `Call::new_payable(self, value)`
+
+Use `Call::new_payable(self, value)` to send ETH along with your call:
+
+```rust
+use alloy_primitives::U256;
+
+sol_interface! {
+ interface IVault {
+ function deposit() external payable;
+ }
+}
+
+#[public]
+impl MyContract {
+ #[payable]
+ pub fn deposit_to_vault(&mut self, vault: Address) -> Result<(), Vec> {
+ let vault_contract = IVault::new(vault);
+ let value = self.vm().msg_value();
+ let config = Call::new_payable(self, value);
+
+ vault_contract.deposit(self.vm(), config)?;
+ Ok(())
+ }
+
+ pub fn deposit_specific_amount(
+ &mut self,
+ vault: Address,
+ amount: U256,
+ ) -> Result<(), Vec> {
+ let vault_contract = IVault::new(vault);
+ let config = Call::new_payable(self, amount);
+
+ vault_contract.deposit(self.vm(), config)?;
+ Ok(())
+ }
+}
+```
+
+### Configuring gas limits
+
+You can limit the gas forwarded to external calls using the `.gas()` method:
+
+```rust
+#[public]
+impl MyContract {
+ pub fn safe_transfer(
+ &mut self,
+ token: Address,
+ to: Address,
+ amount: U256,
+ ) -> bool {
+ let token_contract = IToken::new(token);
+
+ // Use half of remaining gas
+ let gas_limit = self.vm().evm_gas_left() / 2;
+ let config = Call::new_mutating(self).gas(gas_limit);
+
+ token_contract.transfer(self.vm(), config, to, amount).unwrap()
+ }
+}
+```
+
+### Call configuration summary
+
+| Constructor | Use case | State access | ETH transfer |
+| -------------------------------- | --------------- | ------------ | ------------ |
+| `Call::new()` | View/pure calls | Read-only | No |
+| `Call::new_mutating(self)` | Write calls | Read/write | No |
+| `Call::new_payable(self, value)` | Payable calls | Read/write | Yes |
+
+## Complete example
+
+Here's a complete contract that demonstrates all aspects of interface usage:
+
+```rust
+#![cfg_attr(not(any(test, feature = "export-abi")), no_main)]
+extern crate alloc;
+
+use alloy_primitives::{Address, U256};
+use alloy_sol_types::sol;
+use stylus_sdk::{call::Call, prelude::*};
+
+// Declare interfaces for external contracts
+sol_interface! {
+ interface IToken {
+ function balanceOf(address account) external view returns (uint256);
+ function transfer(address to, uint256 amount) external returns (bool);
+ function approve(address spender, uint256 amount) external returns (bool);
+ function transferFrom(address from, address to, uint256 amount) external returns (bool);
+ }
+
+ interface IOracle {
+ function getPrice(bytes32 feedId) external view returns (uint256);
+ }
+
+ interface IVault {
+ function deposit() external payable;
+ function withdraw(uint256 amount) external;
+ }
+}
+
+// Define events
+sol! {
+ event TokensTransferred(address indexed token, address indexed to, uint256 amount);
+ event DepositMade(address indexed vault, uint256 amount);
+}
+
+// Define errors
+sol! {
+ error TransferFailed(address token, address to, uint256 amount);
+ error InsufficientBalance(uint256 have, uint256 want);
+}
+
+#[derive(SolidityError)]
+pub enum InterfaceError {
+ TransferFailed(TransferFailed),
+ InsufficientBalance(InsufficientBalance),
+}
+
+// Contract storage
+sol_storage! {
+ #[entrypoint]
+ pub struct InterfaceExample {
+ address owner;
+ address default_token;
+ address default_vault;
+ }
+}
+
+#[public]
+impl InterfaceExample {
+ #[constructor]
+ pub fn constructor(&mut self, token: Address, vault: Address) {
+ self.owner.set(self.vm().tx_origin());
+ self.default_token.set(token);
+ self.default_vault.set(vault);
+ }
+
+ // View call example
+ pub fn get_token_balance(&self, token: Address, account: Address) -> U256 {
+ let token_contract = IToken::new(token);
+ let config = Call::new();
+
+ token_contract.balance_of(self.vm(), config, account).unwrap()
+ }
+
+ // View call with oracle
+ pub fn get_price(&self, oracle: Address, feed_id: [u8; 32]) -> U256 {
+ let oracle_contract = IOracle::new(oracle);
+ let config = Call::new();
+
+ oracle_contract.get_price(self.vm(), config, feed_id.into()).unwrap()
+ }
+
+ // Mutating call example
+ pub fn transfer_tokens(
+ &mut self,
+ token: Address,
+ to: Address,
+ amount: U256,
+ ) -> Result {
+ let token_contract = IToken::new(token);
+ let config = Call::new_mutating(self);
+
+ let success = token_contract
+ .transfer(self.vm(), config, to, amount)
+ .map_err(|_| InterfaceError::TransferFailed(TransferFailed {
+ token,
+ to,
+ amount,
+ }))?;
+
+ if success {
+ self.vm().log(TokensTransferred { token, to, amount });
+ }
+
+ Ok(success)
+ }
+
+ // Payable call example
+ #[payable]
+ pub fn deposit_to_vault(&mut self, vault: Address) -> Result<(), Vec> {
+ let vault_contract = IVault::new(vault);
+ let value = self.vm().msg_value();
+ let config = Call::new_payable(self, value);
+
+ vault_contract.deposit(self.vm(), config)?;
+
+ self.vm().log(DepositMade { vault, amount: value });
+ Ok(())
+ }
+
+ // Using gas limits
+ pub fn safe_withdraw(&mut self, vault: Address, amount: U256) -> Result<(), Vec> {
+ let vault_contract = IVault::new(vault);
+
+ // Limit gas to prevent reentrancy issues
+ let gas_limit = self.vm().evm_gas_left() / 2;
+ let config = Call::new_mutating(self).gas(gas_limit);
+
+ vault_contract.withdraw(self.vm(), config, amount)?;
+ Ok(())
+ }
+
+ // Complex multi-call example
+ pub fn swap_and_deposit(
+ &mut self,
+ token: Address,
+ vault: Address,
+ amount: U256,
+ ) -> Result<(), InterfaceError> {
+ let token_contract = IToken::new(token);
+
+ // First, check balance
+ let balance = token_contract
+ .balance_of(self.vm(), Call::new(), self.vm().contract_address())
+ .unwrap();
+
+ if balance < amount {
+ return Err(InterfaceError::InsufficientBalance(InsufficientBalance {
+ have: balance,
+ want: amount,
+ }));
+ }
+
+ // Approve vault to spend tokens
+ let config = Call::new_mutating(self);
+ token_contract
+ .approve(self.vm(), config, vault, amount)
+ .map_err(|_| InterfaceError::TransferFailed(TransferFailed {
+ token,
+ to: vault,
+ amount,
+ }))?;
+
+ Ok(())
+ }
+}
+```
+
+## Best practices
+
+### Validate addresses before calls
+
+Always verify that contract addresses are valid before making external calls:
+
+```rust
+pub fn safe_transfer(
+ &mut self,
+ token: Address,
+ to: Address,
+ amount: U256,
+) -> Result {
+ // Validate addresses
+ if token == Address::ZERO || to == Address::ZERO {
+ return Err(InterfaceError::InvalidAddress);
+ }
+
+ let token_contract = IToken::new(token);
+ let config = Call::new_mutating(self);
+
+ Ok(token_contract.transfer(self.vm(), config, to, amount).unwrap())
+}
+```
+
+### Handle call failures gracefully
+
+External calls can fail for various reasons. Always handle errors appropriately:
+
+```rust
+pub fn try_transfer(
+ &mut self,
+ token: Address,
+ to: Address,
+ amount: U256,
+) -> Result {
+ let token_contract = IToken::new(token);
+ let config = Call::new_mutating(self);
+
+ match token_contract.transfer(self.vm(), config, to, amount) {
+ Ok(success) => Ok(success),
+ Err(_) => Err(InterfaceError::TransferFailed(TransferFailed {
+ token,
+ to,
+ amount,
+ })),
+ }
+}
+```
+
+### Follow the checks-effects-interactions pattern
+
+When making external calls, update your contract's state before calling external contracts to prevent reentrancy attacks:
+
+```rust
+pub fn withdraw_tokens(
+ &mut self,
+ token: Address,
+ amount: U256,
+) -> Result<(), InterfaceError> {
+ let caller = self.vm().msg_sender();
+
+ // Checks
+ let balance = self.balances.get(caller);
+ if balance < amount {
+ return Err(InterfaceError::InsufficientBalance(InsufficientBalance {
+ have: balance,
+ want: amount,
+ }));
+ }
+
+ // Effects - update state BEFORE external call
+ self.balances.setter(caller).set(balance - amount);
+
+ // Interactions - external call last
+ let token_contract = IToken::new(token);
+ let config = Call::new_mutating(self);
+ token_contract.transfer(self.vm(), config, caller, amount)
+ .map_err(|_| InterfaceError::TransferFailed(TransferFailed {
+ token,
+ to: caller,
+ amount,
+ }))?;
+
+ Ok(())
+}
+```
+
+### Use gas limits for untrusted contracts
+
+When calling untrusted contracts, limit the gas to prevent malicious behavior:
+
+```rust
+pub fn call_untrusted(
+ &mut self,
+ target: Address,
+) -> Result> {
+ let contract = IToken::new(target);
+
+ // Limit gas to prevent griefing attacks
+ let config = Call::new().gas(100_000);
+
+ Ok(contract.balance_of(self.vm(), config, self.vm().msg_sender()).unwrap())
+}
+```
+
+## Common pitfalls
+
+### Using the wrong call constructor
+
+Using `Call::new()` for state-changing calls will cause the transaction to fail:
+
+```rust
+// Wrong - using Call::new() for a write operation
+pub fn bad_transfer(&mut self, token: Address, to: Address, amount: U256) -> bool {
+ let token_contract = IToken::new(token);
+ let config = Call::new(); // This will fail!
+ token_contract.transfer(self.vm(), config, to, amount).unwrap()
+}
+
+// Correct - using Call::new_mutating(self)
+pub fn good_transfer(&mut self, token: Address, to: Address, amount: U256) -> bool {
+ let token_contract = IToken::new(token);
+ let config = Call::new_mutating(self);
+ token_contract.transfer(self.vm(), config, to, amount).unwrap()
+}
+```
+
+### Forgetting to pass the VM context
+
+All interface method calls require `self.vm()` as the first argument:
+
+```rust
+// Wrong - missing self.vm()
+let balance = token_contract.balance_of(config, account).unwrap();
+
+// Correct
+let balance = token_contract.balance_of(self.vm(), config, account).unwrap();
+```
+
+### Incorrect method naming
+
+Remember that Solidity method names are converted to snake_case in Rust:
+
+```rust
+// Wrong - using Solidity naming
+let balance = token.balanceOf(self.vm(), config, account);
+
+// Correct - using Rust snake_case
+let balance = token.balance_of(self.vm(), config, account);
+```
+
+## See also
+
+- [Stylus contracts reference](../reference/contracts.mdx#external-contract-calls): Detailed reference for external contract calls
+- [Stylus by Example: Import interfaces](https://stylus-by-example.org/basic_examples/import_interfaces): Interactive examples
+- [Stylus SDK documentation](https://docs.rs/stylus-sdk/latest/stylus_sdk/): Complete API reference
diff --git a/docs/stylus/how-tos/optimizing-binaries.mdx b/docs/stylus/how-tos/optimizing-binaries.mdx
index d88274f8ad..7524a28812 100644
--- a/docs/stylus/how-tos/optimizing-binaries.mdx
+++ b/docs/stylus/how-tos/optimizing-binaries.mdx
@@ -6,12 +6,12 @@ sme: rauljordan
target_audience: 'Developers deploying smart contracts using Stylus'
content_type: how-to
sidebar_position: 1
-displayed_sidebar: buildAppsSidebar
+displayed_sidebar: buildStylusSidebar
---
To be deployed onchain, the size of your **uncompressed WebAssembly (WASM) file** must not exceed 128Kb, while the **compressed binary** must not exceed 24KB. Stylus conforms with the same contract size limit as the EVM to remain fully interoperable with all smart contracts on Arbitrum chains.
-[cargo-stylus](https://github.com/OffchainLabs/cargo-stylus), the Stylus CLI tool, automatically compresses your WASM programs, but there are additional steps that you can take to further reduce the size of your binaries.
+[cargo-stylus](https://github.com/OffchainLabs/stylus-sdk-rs/tree/main/cargo-stylus), the Stylus CLI tool, automatically compresses your WASM programs, but there are additional steps that you can take to further reduce the size of your binaries.
Your options fall into two categories: Rust compiler flags, and third-party optimization tools.
@@ -46,7 +46,7 @@ Additional WASM-specific tooling exists to shrink binaries. Due to being third p
[twiggy](https://github.com/rustwasm/twiggy) is a code size profiler for WASM, it can help you estimate the impact of each added component on your binaries' size.
-Our team has also curated a [list of recommended libraries](/stylus/recommended-libraries) that are helpful to Stylus development and optimally sized.
+Our team has also curated a [list of recommended libraries](/stylus/advanced/recommended-libraries) that are helpful to Stylus development and optimally sized.
### Frequently asked questions
diff --git a/docs/stylus/how-tos/testing-contracts.mdx b/docs/stylus/how-tos/testing-contracts.mdx
index dd61568bb5..17ddda113a 100644
--- a/docs/stylus/how-tos/testing-contracts.mdx
+++ b/docs/stylus/how-tos/testing-contracts.mdx
@@ -1,11 +1,11 @@
---
id: 'testing-contracts'
-title: 'Testing Smart Contracts with Stylus'
+title: 'Testing smart contracts with Stylus'
description: 'A comprehensive guide to writing and running tests for Stylus smart contracts.'
sme: anegg0
target_audience: 'Developers writing smart contracts using Stylus.'
sidebar_position: 3
-displayed_sidebar: buildAppsSidebar
+displayed_sidebar: buildStylusSidebar
---
import CustomDetails from '@site/src/components/CustomDetails';
diff --git a/docs/stylus/how-tos/using-constructors.mdx b/docs/stylus/how-tos/using-constructors.mdx
new file mode 100644
index 0000000000..8f58324d2c
--- /dev/null
+++ b/docs/stylus/how-tos/using-constructors.mdx
@@ -0,0 +1,543 @@
+---
+title: 'Using constructors with Stylus'
+description: 'A comprehensive guide to implementing and deploying smart contracts with constructors in Stylus'
+sidebar_label: 'Using constructors'
+author: 'anegg0'
+sme: 'anegg0'
+user_story: 'As a Rust developer, I want to understand how to implement and use constructors in Stylus smart contracts'
+content_type: 'how-to'
+sidebar_position: 3
+displayed_sidebar: buildStylusSidebar
+---
+
+import CustomDetails from '@site/src/components/CustomDetails';
+import { VanillaAdmonition } from '@site/src/components/VanillaAdmonition/';
+
+Constructors allow you to initialize your Stylus smart contracts with specific parameters when deploying them. This guide will show you how to implement constructors in Rust, understand their behavior, and deploy contracts using them.
+
+## What you'll accomplish
+
+By the end of this guide, you'll be able to:
+
+- Implement constructor functions in Stylus contracts
+- Understand the constructor rules and limitations
+- Deploy contracts with constructor parameters
+- Test the constructor functionality
+- Handle constructor errors and validation
+
+## Prerequisites
+
+Before implementing constructors, ensure you have:
+
+
+
+Follow the instructions on [Rust Lang's installation page](https://www.rust-lang.org/tools/install) to install a complete Rust toolchain (v1.88 or newer) on your system. After installation, ensure you can access the programs `rustup`, `rustc`, and `cargo` from your preferred terminal application.
+
+
+
+
+
+In your terminal, run:
+
+```shell
+cargo install --force cargo-stylus
+```
+
+Add WASM ([WebAssembly](https://webassembly.org/)) as a build target for the specific Rust toolchain you are using. The below example sets your default Rust toolchain to 1.88 as well as adding the WASM build target:
+
+```shell
+rustup default 1.88
+rustup target add wasm32-unknown-unknown --toolchain 1.88
+```
+
+You can verify that cargo stylus is installed by running `cargo stylus --help` in your terminal, which will return a list of helpful commands.
+
+
+
+
+
+Instructions on how to set up a local Arbitrum test node can be found [in the Nitro-devnode repository](https://github.com/OffchainLabs/nitro-devnode).
+
+
+
+## Understanding Stylus constructors
+
+Stylus constructors provide an atomic way to deploy, activate, and initialize a contract in a single transaction. If your contract lacks a constructor, it may allow access to the contract's storage before the initialization logic runs, leading to unexpected behavior.
+
+
+ Stylus uses trait-based composition instead of traditional inheritance. When building contracts
+ that compose multiple traits, constructors help initialize all components properly. See the
+ [Constructor with trait-based composition](#constructor-with-trait-based-composition) section for
+ examples.
+
+
+### Constructor rules and guarantees
+
+Stylus constructors follow these important rules:
+
+| Rule | Why it exists |
+| ------------------------------------------- | --------------------------------------------------------------------------------------- |
+| **Exactly 0 or 1 constructor** per contract | Mimics Solidity behavior and avoids ambiguity |
+| **Must be annotated with `#[constructor]`** | Guarantees the deployer calls the correct initialization method |
+| **Must take `&mut self`** | Allows writing to contract storage during deployment |
+| **Returns `()` or `Result<(), Error>`** | Enables error handling; reverting aborts deployment |
+| **Use `tx_origin()` for deployer address** | Factory contracts are used in deployment, so `msg_sender()` returns the factory address |
+| **Constructor runs exactly once** | The SDK uses a sentinel system to prevent re-execution |
+
+
+ Stylus uses a factory pattern for deployment, which means `msg_sender()` in a constructor returns
+ the factory contract address, not the deployer. Always use `tx_origin()` to get the actual
+ deployer address.
+
+
+## Basic constructor implementation
+
+Here's a simple example of a constructor in a Stylus contract:
+
+```rust
+#![cfg_attr(not(any(test, feature = "export-abi")), no_main)]
+
+extern crate alloc;
+
+use alloy_primitives::{Address, U256};
+use alloy_sol_types::sol;
+use stylus_sdk::prelude::*;
+
+sol! {
+ #[derive(Debug)]
+ error InvalidAmount();
+}
+
+sol_storage! {
+ #[entrypoint]
+ pub struct SimpleToken {
+ address owner;
+ uint256 total_supply;
+ string name;
+ string symbol;
+ mapping(address => uint256) balances;
+ }
+}
+
+#[derive(SolidityError, Debug)]
+pub enum SimpleTokenError {
+ InvalidAmount(InvalidAmount),
+}
+
+#[public]
+impl SimpleToken {
+ /// Constructor initializes the token with a name, symbol, and initial supply
+ #[constructor]
+ #[payable]
+ pub fn constructor(
+ &mut self,
+ name: String,
+ symbol: String,
+ initial_supply: U256,
+ ) -> Result<(), SimpleTokenError> {
+ // Validate input parameters
+ if initial_supply == U256::ZERO {
+ return Err(SimpleTokenError::InvalidAmount(InvalidAmount {}));
+ }
+
+ // Get the deployer address using tx_origin()
+ let deployer = self.vm().tx_origin();
+
+ // Initialize contract state
+ self.owner.set(deployer);
+ self.name.set_str(&name);
+ self.symbol.set_str(&symbol);
+ self.total_supply.set(initial_supply);
+
+ // Mint initial supply to deployer
+ self.balances.setter(deployer).set(initial_supply);
+
+ Ok(())
+ }
+
+ // Additional contract methods...
+ pub fn balance_of(&self, account: Address) -> U256 {
+ self.balances.get(account)
+ }
+
+ pub fn total_supply(&self) -> U256 {
+ self.total_supply.get()
+ }
+}
+```
+
+### Key implementation details
+
+1. **Parameter validation**: Always validate constructor parameters before proceeding with initialization
+2. **Error handling**: Use `Result<(), Error>` to handle initialization failures gracefully
+3. **Payable constructors**: Add `#[payable]` to receive ETH during deployment
+4. **State initialization**: Set all necessary contract state in the constructor
+
+## Advanced constructor patterns
+
+### Constructor with complex validation
+
+```rust
+#[constructor]
+#[payable]
+pub fn constructor(
+ &mut self,
+ name: String,
+ symbol: String,
+ initial_supply: U256,
+ max_supply: U256,
+) -> Result<(), TokenContractError> {
+ // Multiple validation checks
+ if initial_supply == U256::ZERO {
+ return Err(TokenContractError::InvalidAmount(InvalidAmount {}));
+ }
+
+ if initial_supply > max_supply {
+ return Err(TokenContractError::TooManyTokens(TooManyTokens {}));
+ }
+
+ if name.is_empty() || symbol.is_empty() {
+ return Err(TokenContractError::InvalidAmount(InvalidAmount {}));
+ }
+
+ let deployer = self.vm().tx_origin();
+
+ // Initialize with timestamp tracking
+ self.owner.set(deployer);
+ self.name.set_str(&name);
+ self.symbol.set_str(&symbol);
+ self.total_supply.set(initial_supply);
+ self.max_supply.set(max_supply);
+ self.created_at.set(U256::from(self.vm().block_timestamp()));
+
+ // Mint tokens to deployer
+ self.balances.setter(deployer).set(initial_supply);
+
+ // Emit initialization event
+ log(self.vm(), TokenCreated {
+ creator: deployer,
+ name: name.clone(),
+ symbol: symbol.clone(),
+ initial_supply,
+ });
+
+ Ok(())
+}
+```
+
+### Constructor with trait-based composition
+
+Stylus uses trait-based composition instead of traditional inheritance. When implementing constructors with traits, each component typically has its own initialization logic:
+
+```rust
+// Define traits for different functionality
+trait IErc20 {
+ fn balance_of(&self, account: Address) -> U256;
+ fn transfer(&mut self, to: Address, value: U256) -> bool;
+}
+
+trait IOwnable {
+ fn owner(&self) -> Address;
+ fn transfer_ownership(&mut self, new_owner: Address) -> bool;
+}
+
+// Define storage for each component
+#[storage]
+struct Erc20Component {
+ balances: StorageMap,
+ total_supply: StorageU256,
+}
+
+#[storage]
+struct OwnableComponent {
+ owner: StorageAddress,
+}
+
+// Main contract that composes functionality
+#[storage]
+#[entrypoint]
+struct MyToken {
+ erc20: Erc20Component,
+ ownable: OwnableComponent,
+ name: StorageString,
+ symbol: StorageString,
+}
+
+#[public]
+#[implements(IErc20, IOwnable)]
+impl MyToken {
+ #[constructor]
+ pub fn constructor(
+ &mut self,
+ name: String,
+ symbol: String,
+ initial_supply: U256,
+ ) -> Result<(), TokenError> {
+ // Initialize each component
+ self.initialize_ownable()?;
+ self.initialize_erc20(initial_supply)?;
+
+ // Initialize contract-specific state
+ self.name.set_str(&name);
+ self.symbol.set_str(&symbol);
+
+ Ok(())
+ }
+
+ fn initialize_ownable(&mut self) -> Result<(), TokenError> {
+ let deployer = self.vm().tx_origin();
+ self.ownable.owner.set(deployer);
+ Ok(())
+ }
+
+ fn initialize_erc20(&mut self, initial_supply: U256) -> Result<(), TokenError> {
+ if initial_supply == U256::ZERO {
+ return Err(TokenError::InvalidSupply);
+ }
+
+ let deployer = self.vm().tx_origin();
+ self.erc20.total_supply.set(initial_supply);
+ self.erc20.balances.setter(deployer).set(initial_supply);
+ Ok(())
+ }
+}
+```
+
+
+ Unlike Solidity's inheritance, Stylus uses Rust's trait system for composition. Each component is
+ initialized explicitly in the constructor.
+
+
+## Testing constructors
+
+The Stylus SDK provides comprehensive testing tools for constructor functionality:
+
+```rust
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use stylus_sdk::testing::*;
+
+ #[test]
+ fn test_constructor_success() {
+ let vm = TestVMBuilder::new()
+ .sender(Address::from([0x01; 20]))
+ .build();
+
+ let mut contract = SimpleToken::from(&vm);
+
+ let result = contract.constructor(
+ "Test Token".to_string(),
+ "TEST".to_string(),
+ U256::from(1000000),
+ );
+
+ assert!(result.is_ok());
+ assert_eq!(contract.name.get_string(), "Test Token");
+ assert_eq!(contract.symbol.get_string(), "TEST");
+ assert_eq!(contract.total_supply.get(), U256::from(1000000));
+ assert_eq!(
+ contract.balance_of(Address::from([0x01; 20])),
+ U256::from(1000000)
+ );
+ }
+
+ #[test]
+ fn test_constructor_validation() {
+ let vm = TestVMBuilder::new()
+ .sender(Address::from([0x01; 20]))
+ .build();
+
+ let mut contract = SimpleToken::from(&vm);
+
+ // Test zero supply rejection
+ let result = contract.constructor(
+ "Test Token".to_string(),
+ "TEST".to_string(),
+ U256::ZERO,
+ );
+
+ assert!(result.is_err());
+ assert!(matches!(
+ result.unwrap_err(),
+ SimpleTokenError::InvalidAmount(_)
+ ));
+ }
+}
+```
+
+## Deploying contracts with constructors
+
+### Using cargo stylus
+
+Deploy your contract with constructor arguments using `cargo stylus deploy`:
+
+```bash
+# Deploy with constructor parameters
+cargo stylus deploy \
+ --private-key-path ~/.arbitrum/key \
+ --endpoint https://sepolia-rollup.arbitrum.io/rpc \
+ --constructor-args "MyToken" "MTK" 1000000
+```
+
+### Constructor argument encoding
+
+`cargo stylus` automatically encodes the constructor arguments. The arguments should be provided in the same order as defined in your constructor function.
+
+For complex types:
+
+- **Strings**: Provide as quoted strings
+- **Numbers**: Provide as decimal or hex (0x prefix)
+- **Addresses**: Provide as hex strings with 0x prefix
+- **Arrays**: Use JSON array syntax
+
+```bash
+# Example with multiple argument types
+cargo stylus deploy \
+ --constructor-args "TokenName" "TKN" 1000000 "0x742d35Cc6635C0532925a3b8D95B5C1b0ea3C28F"
+```
+
+## Best practices
+
+### Constructor parameter validation
+
+Always validate constructor parameters to prevent deployment of misconfigured contracts:
+
+```rust
+#[constructor]
+pub fn constructor(&mut self, params: ConstructorParams) -> Result<(), Error> {
+ // Validate all parameters before any state changes
+ self.validate_parameters(¶ms)?;
+
+ // Initialize state only after validation passes
+ self.initialize_state(params)?;
+
+ Ok(())
+}
+
+fn validate_parameters(&self, params: &ConstructorParams) -> Result<(), Error> {
+ if params.name.is_empty() {
+ return Err(Error::InvalidName);
+ }
+ // Additional validation...
+ Ok(())
+}
+```
+
+### Error handling patterns
+
+Use descriptive error types and provide meaningful error messages:
+
+```rust
+sol! {
+ #[derive(Debug)]
+ error InvalidName(string reason);
+ #[derive(Debug)]
+ error InvalidSupply(uint256 provided, uint256 max_allowed);
+ #[derive(Debug)]
+ error Unauthorized(address caller);
+}
+
+#[derive(SolidityError, Debug)]
+pub enum ConstructorError {
+ InvalidName(InvalidName),
+ InvalidSupply(InvalidSupply),
+ Unauthorized(Unauthorized),
+}
+```
+
+### State initialization order
+
+Initialize contract state in a logical order to avoid dependency issues:
+
+```rust
+#[constructor]
+pub fn constructor(&mut self, params: ConstructorParams) -> Result<(), Error> {
+ // 1. Validate parameters first
+ self.validate_parameters(¶ms)?;
+
+ // 2. Set basic contract metadata
+ self.name.set_str(¶ms.name);
+ self.symbol.set_str(¶ms.symbol);
+
+ // 3. Set ownership and permissions
+ let deployer = self.vm().tx_origin();
+ self.owner.set(deployer);
+
+ // 4. Initialize token economics
+ self.total_supply.set(params.initial_supply);
+ self.max_supply.set(params.max_supply);
+
+ // 5. Set up initial balances
+ self.balances.setter(deployer).set(params.initial_supply);
+
+ // 6. Emit events last
+ log(self.vm(), ContractInitialized { /* ... */ });
+
+ Ok(())
+}
+```
+
+## Common pitfalls and solutions
+
+### Using msg_sender() instead of tx_origin()
+
+**Problem**: Using `msg_sender()` in constructors returns the factory contract address, not the deployer.
+
+```rust
+// ❌ Wrong - returns factory address
+let deployer = self.vm().msg_sender();
+
+// ✅ Correct - returns actual deployer
+let deployer = self.vm().tx_origin();
+```
+
+### Missing parameter validation
+
+**Problem**: Not validating constructor parameters can lead to unusable contracts.
+
+```rust
+// ❌ Wrong - no validation
+#[constructor]
+pub fn constructor(&mut self, supply: U256) {
+ self.total_supply.set(supply); // Could be zero!
+}
+
+// ✅ Correct - validate first
+#[constructor]
+pub fn constructor(&mut self, supply: U256) -> Result<(), Error> {
+ if supply == U256::ZERO {
+ return Err(Error::InvalidSupply);
+ }
+ self.total_supply.set(supply);
+ Ok(())
+}
+```
+
+### Forgetting the #[constructor] annotation
+
+**Problem**: Functions named "constructor" without the annotation won't be recognized.
+
+```rust
+// ❌ Wrong - missing annotation
+pub fn constructor(&mut self, value: U256) {
+ // This won't be called during deployment
+}
+
+// ✅ Correct - properly annotated
+#[constructor]
+pub fn constructor(&mut self, value: U256) {
+ // This will be called during deployment
+}
+```
+
+## Summary
+
+Constructors in Stylus provide a powerful way to initialize your smart contracts during deployment. Key takeaways:
+
+- Use `#[constructor]` annotation and `&mut self` parameter
+- Always use `tx_origin()` to get the deployer address
+- Validate all parameters before initializing state
+- Handle errors gracefully with `Result<(), Error>` return type
+- Test the constructor behavior thoroughly
+- Deploy with `cargo stylus deploy --constructor-args`
diff --git a/docs/stylus/how-tos/using-inheritance.mdx b/docs/stylus/how-tos/using-inheritance.mdx
index 155ff8813f..a413e427d1 100644
--- a/docs/stylus/how-tos/using-inheritance.mdx
+++ b/docs/stylus/how-tos/using-inheritance.mdx
@@ -5,7 +5,7 @@ author: anegg0
sme: mahsamoosavi
content_type: how-to
sidebar_position: 1
-displayed_sidebar: buildAppsSidebar
+displayed_sidebar: buildStylusSidebar
---
import CustomDetails from '@site/src/components/CustomDetails';
diff --git a/docs/stylus/how-tos/verifying-contracts-arbiscan.mdx b/docs/stylus/how-tos/verifying-contracts-arbiscan.mdx
index 358f20ba2c..54a6420c9b 100644
--- a/docs/stylus/how-tos/verifying-contracts-arbiscan.mdx
+++ b/docs/stylus/how-tos/verifying-contracts-arbiscan.mdx
@@ -6,7 +6,7 @@ sme: mahsamoosavi
target_audience: 'Developers deploying smart contracts using Stylus'
content_type: how-to
sidebar_position: 1
-displayed_sidebar: buildAppsSidebar
+displayed_sidebar: buildStylusSidebar
---
import ImageZoom from '@site/src/components/ImageZoom';
diff --git a/docs/stylus/how-tos/verifying-contracts.mdx b/docs/stylus/how-tos/verifying-contracts.mdx
index 819ec19f4b..e19d19e036 100644
--- a/docs/stylus/how-tos/verifying-contracts.mdx
+++ b/docs/stylus/how-tos/verifying-contracts.mdx
@@ -8,7 +8,7 @@ target_audience: 'Developers learning how to use Stylus'
sidebar_position: 2
user_story: 'As a current or prospective Stylus user, I want to learn how to make sure my Stylus contracts deployment are reproducible by anyone running the same environment.'
content_type: how-to
-displayed_sidebar: buildAppsSidebar
+displayed_sidebar: buildStylusSidebar
---
:::caution
diff --git a/docs/stylus/overview.mdx b/docs/stylus/overview.mdx
deleted file mode 100644
index 10b1700705..0000000000
--- a/docs/stylus/overview.mdx
+++ /dev/null
@@ -1,81 +0,0 @@
----
-id: stylus-overview
-title: Write Stylus Contracts
-sidebar_label: Write Stylus contracts
-displayed_sidebar: buildAppsSidebar
----
-
-import Card from '@site/src/components/Cards/Card';
-
-# Write Stylus Contracts
-
-Let's learn how to write contracts with Stylus!
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/docs/stylus/partials/_stylus-public-preview-banner-partial.md b/docs/stylus/partials/_stylus-public-preview-banner-partial.md
new file mode 100644
index 0000000000..729f55610d
--- /dev/null
+++ b/docs/stylus/partials/_stylus-public-preview-banner-partial.md
@@ -0,0 +1,7 @@
+:::info ALPHA RELEASE, PUBLIC PREVIEW DOCS
+
+Stylus is currently tagged as a `release-candidate`. The code has been audited, and testing in production environments is underway. Caution should be taken when used in production scenarios. This documentation is currently in [public preview](/stylus/concepts/public-preview-expectations).
+
+To provide feedback, click the **Request an update** button at the top of this document, [join the Arbitrum Discord](https://discord.gg/arbitrum), or reach out to our team directly by completing [this form](http://bit.ly/3yy6EUK).
+
+:::
diff --git a/docs/stylus/quickstart.mdx b/docs/stylus/quickstart.mdx
index a3b28c3042..e74b73644a 100644
--- a/docs/stylus/quickstart.mdx
+++ b/docs/stylus/quickstart.mdx
@@ -6,7 +6,7 @@ author: chrisco512, anegg0
sme: chrisco512, anegg0
sidebar_position: 2
target_audience: Developers writing Stylus contracts in Rust using Stylus
-displayed_sidebar: buildAppsSidebar
+displayed_sidebar: buildStylusSidebar
---
import ImageZoom from '@site/src/components/ImageZoom';
@@ -82,7 +82,7 @@ cd nitro-devnode
## Creating a Stylus project with cargo stylus
-[cargo stylus](https://github.com/OffchainLabs/cargo-stylus/blob/main/main/VALID_WASM.md) is a CLI toolkit built to facilitate the development of Stylus contracts.
+[cargo stylus](https://github.com/OffchainLabs/stylus-sdk-rs/tree/main/cargo-stylus/blob/main/main/VALID_WASM.md) is a CLI toolkit built to facilitate the development of Stylus contracts.
It is available as a plugin to the standard cargo tool used for developing Rust programs.
@@ -186,7 +186,7 @@ Location:
prover/src/binary.rs:493:9, data: None
```
-The contract can fail the check for various reasons (on compile, deployment, etc...). Reading the [Invalid Stylus WASM Contracts explainer](https://github.com/OffchainLabs/cargo-stylus/blob/main/main/VALID_WASM.md) can help you understand what makes a WASM contract valid or not.
+The contract can fail the check for various reasons (on compile, deployment, etc...). Reading the [Invalid Stylus WASM Contracts explainer](https://github.com/OffchainLabs/stylus-sdk-rs/tree/main/cargo-stylus/blob/main/main/VALID_WASM.md) can help you understand what makes a WASM contract valid or not.
If your contract succeeds, you'll see something like this:
diff --git a/docs/stylus/reference/contracts.mdx b/docs/stylus/reference/contracts.mdx
new file mode 100644
index 0000000000..f644c2f3fb
--- /dev/null
+++ b/docs/stylus/reference/contracts.mdx
@@ -0,0 +1,1834 @@
+---
+title: 'Stylus contracts'
+description: 'Stylus Rust SDK contracts, methods, and lifecycle'
+author: chrisco
+sme: chrisco
+sidebar_position: 2
+target_audience: Developers using the Stylus Rust SDK to write and deploy smart contracts.
+displayed_sidebar: buildStylusSidebar
+---
+
+Stylus smart contracts are fully compatible with Solidity contracts on Arbitrum chains. They compile to WebAssembly and share the same EVM state trie as Solidity contracts, enabling seamless interoperability.
+
+## Contract basics
+
+A Stylus contract consists of three main components:
+
+1. **Storage Definition**: Defines the contract's persistent state
+2. **Entrypoint**: Marks the main contract struct that handles incoming calls
+3. **Public Methods**: Functions exposed to external callers via the `#[public]` macro
+
+### Minimal Contract
+
+Here's the simplest possible Stylus contract:
+
+```rust
+#![cfg_attr(not(any(test, feature = "export-abi")), no_main)]
+extern crate alloc;
+
+use stylus_sdk::prelude::*;
+
+#[storage]
+#[entrypoint]
+pub struct HelloWorld;
+
+#[public]
+impl HelloWorld {
+ fn user_main(_input: Vec) -> ArbResult {
+ Ok(Vec::new())
+ }
+}
+```
+
+This contract:
+
+- Uses `#[storage]` to define the contract struct (empty in this case)
+- Uses `#[entrypoint]` to mark it as the contract's entry point
+- Uses `#[public]` to expose the `user_main` function
+- Returns `ArbResult`, which is `Result, Vec>`
+
+## Storage Definition
+
+Stylus contracts use the `sol_storage!` macro or `#[storage]` attribute to define persistent storage that maps directly to Solidity storage slots.
+
+### Using `sol_storage!` (Solidity-style)
+
+The `sol_storage!` macro lets you define storage using Solidity syntax:
+
+```rust
+use stylus_sdk::prelude::*;
+use alloy_primitives::{Address, U256};
+
+sol_storage! {
+ #[entrypoint]
+ pub struct Counter {
+ uint256 count;
+ address owner;
+ mapping(address => uint256) balances;
+ }
+}
+```
+
+This creates a contract with:
+
+- A `count` field of type `StorageU256`
+- An `owner` field of type `StorageAddress`
+- A `balances` mapping from `Address` to `StorageU256`
+
+### Using `#[storage]` (Rust-style)
+
+Alternatively, use the `#[storage]` attribute with explicit storage types:
+
+```rust
+use stylus_sdk::prelude::*;
+use stylus_sdk::storage::{StorageU256, StorageAddress, StorageMap};
+use alloy_primitives::{Address, U256};
+
+#[storage]
+#[entrypoint]
+pub struct Counter {
+ count: StorageU256,
+ owner: StorageAddress,
+ balances: StorageMap,
+}
+```
+
+Both approaches produce identical storage layouts and are fully interoperable with Solidity contracts using the same storage structure.
+
+## The `#[entrypoint]` Macro
+
+The `#[entrypoint]` macro marks a struct as the contract's main entry point. It automatically implements the `TopLevelStorage` trait, which enables:
+
+- Routing incoming calls to public methods
+- Managing contract storage
+- Handling reentrancy protection (unless the `reentrant` feature is enabled)
+
+**Key requirements:**
+
+- Exactly one struct per contract must have `#[entrypoint]`
+- The struct must also have `#[storage]` or be defined in `sol_storage!`
+- The entrypoint struct represents the contract's root storage
+
+**Example:**
+
+```rust
+sol_storage! {
+ #[entrypoint]
+ pub struct MyContract {
+ uint256 value;
+ }
+}
+```
+
+The `#[entrypoint]` macro generates:
+
+1. An implementation of `TopLevelStorage` for the struct
+2. A `user_entrypoint` function that Stylus calls when the contract receives a transaction
+3. Method routing logic to dispatch calls to `#[public]` methods
+
+## Public Methods with `#[public]`
+
+The `#[public]` macro exposes Rust methods as external contract functions callable from Solidity, other Stylus contracts, or external callers.
+
+### Basic Public Methods
+
+```rust
+use stylus_sdk::prelude::*;
+use alloy_primitives::U256;
+
+sol_storage! {
+ #[entrypoint]
+ pub struct Calculator {
+ uint256 result;
+ }
+}
+
+#[public]
+impl Calculator {
+ // View function (read-only)
+ pub fn get_result(&self) -> U256 {
+ self.result.get()
+ }
+
+ // Write function (mutates state)
+ pub fn set_result(&mut self, value: U256) {
+ self.result.set(value);
+ }
+
+ // Pure function (no state access)
+ pub fn add(a: U256, b: U256) -> U256 {
+ a + b
+ }
+}
+```
+
+### State Mutability
+
+The SDK automatically infers state mutability from the method signature:
+
+| Signature | Mutability | Solidity Equivalent | Description |
+| ----------- | ---------- | ------------------- | --------------------- |
+| `&self` | `view` | `view` | Read contract state |
+| `&mut self` | Write | (default) | Modify contract state |
+| Neither | `pure` | `pure` | No state access |
+
+**Examples:**
+
+```rust
+#[public]
+impl MyContract {
+ // View: can read state, cannot modify
+ pub fn balance_of(&self, account: Address) -> U256 {
+ self.balances.get(account)
+ }
+
+ // Write: can read and modify state
+ pub fn transfer(&mut self, to: Address, amount: U256) {
+ let sender = self.vm().msg_sender();
+ let balance = self.balances.get(sender);
+ self.balances.setter(sender).set(balance - amount);
+ self.balances.setter(to).set(self.balances.get(to) + amount);
+ }
+
+ // Pure: no state access at all
+ pub fn calculate_fee(amount: U256) -> U256 {
+ amount * U256::from(3) / U256::from(100)
+ }
+}
+```
+
+## Constructor
+
+The `#[constructor]` attribute marks a function that runs once during contract deployment.
+
+### Basic Constructor
+
+```rust
+use stylus_sdk::prelude::*;
+use alloy_primitives::{Address, U256};
+
+sol_storage! {
+ #[entrypoint]
+ pub struct Token {
+ address owner;
+ uint256 total_supply;
+ }
+}
+
+#[public]
+impl Token {
+ #[constructor]
+ pub fn constructor(&mut self, initial_supply: U256) {
+ let deployer = self.vm().msg_sender();
+ self.owner.set(deployer);
+ self.total_supply.set(initial_supply);
+ }
+
+ pub fn owner(&self) -> Address {
+ self.owner.get()
+ }
+}
+```
+
+### Constructor Features
+
+**Payable Constructor:**
+
+```rust
+#[public]
+impl Token {
+ #[constructor]
+ #[payable]
+ pub fn constructor(&mut self, initial_supply: U256) {
+ // Contract can receive ETH during deployment
+ let received = self.vm().msg_value();
+ self.owner.set(self.vm().msg_sender());
+ self.total_supply.set(initial_supply);
+ }
+}
+```
+
+**Important Notes:**
+
+- The constructor name can be anything (doesn't have to be `constructor`)
+- Only one constructor per contract
+- Constructor runs exactly once when the contract is deployed
+- Use `tx_origin()` instead of `msg_sender()` when deploying via a factory contract
+
+## Method Attributes
+
+### `#[payable]`
+
+Marks a function as able to receive ETH:
+
+```rust
+#[public]
+impl PaymentProcessor {
+ #[payable]
+ pub fn deposit(&mut self) -> U256 {
+ let sender = self.vm().msg_sender();
+ let amount = self.vm().msg_value();
+
+ let current = self.balances.get(sender);
+ self.balances.setter(sender).set(current + amount);
+
+ amount
+ }
+
+ // Non-payable function will revert if ETH is sent
+ pub fn withdraw(&mut self, amount: U256) {
+ // Will revert if msg.value > 0
+ let sender = self.vm().msg_sender();
+ let balance = self.balances.get(sender);
+ self.balances.setter(sender).set(balance - amount);
+ }
+}
+```
+
+**Important:** Without `#[payable]`, sending ETH to a function causes a revert.
+
+### `#[receive]`
+
+Handles plain ETH transfers without calldata (equivalent to Solidity's `receive()` function):
+
+```rust
+use alloy_sol_types::sol;
+
+sol! {
+ event EtherReceived(address indexed sender, uint256 amount);
+}
+
+#[public]
+impl Wallet {
+ #[receive]
+ #[payable]
+ pub fn receive(&mut self) -> Result<(), Vec> {
+ let sender = self.vm().msg_sender();
+ let amount = self.vm().msg_value();
+
+ let balance = self.balances.get(sender);
+ self.balances.setter(sender).set(balance + amount);
+
+ self.vm().log(EtherReceived { sender, amount });
+ Ok(())
+ }
+}
+```
+
+**Notes:**
+
+- Must be combined with `#[payable]`
+- Called when the contract receives ETH without calldata
+- Only one `#[receive]` function per contract
+- Must have signature: `fn name(&mut self) -> Result<(), Vec>`
+
+### `#[fallback]`
+
+Handles calls to non-existent functions or as a fallback for ETH transfers:
+
+```rust
+use alloy_sol_types::sol;
+
+sol! {
+ event FallbackCalled(address indexed sender, bytes4 selector, uint256 value);
+}
+
+#[public]
+impl Contract {
+ #[fallback]
+ #[payable]
+ pub fn fallback(&mut self, calldata: &[u8]) -> ArbResult {
+ let sender = self.vm().msg_sender();
+ let value = self.vm().msg_value();
+
+ // Extract function selector if present
+ let selector = if calldata.len() >= 4 {
+ [calldata[0], calldata[1], calldata[2], calldata[3]]
+ } else {
+ [0; 4]
+ };
+
+ self.vm().log(FallbackCalled {
+ sender,
+ selector: selector.into(),
+ value,
+ });
+
+ Ok(vec![])
+ }
+}
+```
+
+**Fallback is called when:**
+
+1. A function call doesn't match any existing function signature
+2. Plain ETH transfer when no `#[receive]` function exists
+3. The contract receives calldata but no function matches
+
+**Notes:**
+
+- Must have signature: `fn name(&mut self, calldata: &[u8]) -> ArbResult`
+- Can optionally include `#[payable]` to accept ETH
+- Only one `#[fallback]` function per contract
+
+### `#[selector]`
+
+Customizes the Solidity function selector:
+
+```rust
+#[public]
+impl Token {
+ // Use a custom name in the ABI
+ #[selector(name = "balanceOf")]
+ pub fn get_balance(&self, account: Address) -> U256 {
+ self.balances.get(account)
+ }
+
+ // Explicitly set the 4-byte selector
+ #[selector(bytes = "0x70a08231")]
+ pub fn balance_of_custom(&self, account: Address) -> U256 {
+ self.balances.get(account)
+ }
+}
+```
+
+This is useful for:
+
+- Matching existing Solidity interfaces exactly
+- Avoiding naming conflicts
+- Implementing multiple methods with the same name but different selectors
+
+## Contract Composition and Inheritance
+
+Stylus supports two patterns for code reuse: trait-based composition (new, preferred) and struct inheritance (legacy).
+
+### Trait-Based Composition (Preferred)
+
+Define reusable functionality as traits and implement them on your contract:
+
+```rust
+use stylus_sdk::prelude::*;
+use alloy_primitives::{Address, U256};
+
+// Define interface traits
+#[public]
+trait IOwnable {
+ fn owner(&self) -> Address;
+ fn transfer_ownership(&mut self, new_owner: Address) -> bool;
+}
+
+#[public]
+trait IErc20 {
+ fn name(&self) -> String;
+ fn symbol(&self) -> String;
+ fn balance_of(&self, account: Address) -> U256;
+ fn transfer(&mut self, to: Address, value: U256) -> bool;
+}
+
+// Define storage components
+#[storage]
+struct Ownable {
+ owner: StorageAddress,
+}
+
+#[storage]
+struct Erc20 {
+ balances: StorageMap,
+}
+
+// Compose into main contract
+#[storage]
+#[entrypoint]
+struct MyToken {
+ ownable: Ownable,
+ erc20: Erc20,
+}
+
+// Declare which interfaces this contract implements
+#[public]
+#[implements(IOwnable, IErc20)]
+impl MyToken {}
+
+// Implement each trait
+#[public]
+impl IOwnable for MyToken {
+ fn owner(&self) -> Address {
+ self.ownable.owner.get()
+ }
+
+ fn transfer_ownership(&mut self, new_owner: Address) -> bool {
+ let caller = self.vm().msg_sender();
+ if caller != self.ownable.owner.get() {
+ return false;
+ }
+ self.ownable.owner.set(new_owner);
+ true
+ }
+}
+
+#[public]
+impl IErc20 for MyToken {
+ fn name(&self) -> String {
+ "MyToken".into()
+ }
+
+ fn symbol(&self) -> String {
+ "MTK".into()
+ }
+
+ fn balance_of(&self, account: Address) -> U256 {
+ self.erc20.balances.get(account)
+ }
+
+ fn transfer(&mut self, to: Address, value: U256) -> bool {
+ let from = self.vm().msg_sender();
+ let from_balance = self.erc20.balances.get(from);
+ if from_balance < value {
+ return false;
+ }
+ self.erc20.balances.setter(from).set(from_balance - value);
+ let to_balance = self.erc20.balances.get(to);
+ self.erc20.balances.setter(to).set(to_balance + value);
+ true
+ }
+}
+```
+
+**Benefits:**
+
+- Clear separation of concerns
+- Explicit interface declarations
+- Type-safe composition
+- Easy to test components independently
+- Compatible with Solidity interface standards
+
+### Accessing VM Context
+
+All public methods can access blockchain context via `self.vm()`:
+
+```rust
+#[public]
+impl MyContract {
+ pub fn get_caller_info(&self) -> (Address, U256, U256) {
+ let vm = self.vm();
+ (
+ vm.msg_sender(), // Caller's address
+ vm.msg_value(), // ETH sent with call
+ vm.block_number(), // Current block number
+ )
+ }
+}
+```
+
+See the [Global Variables and Functions](./global-variables-and-functions.mdx) documentation for a complete list of available VM methods.
+
+## Events
+
+Events allow contracts to log data to the blockchain, enabling off-chain monitoring and indexing.
+
+### Defining Events
+
+Use the `sol!` macro to define events with Solidity-compatible signatures:
+
+```rust
+use alloy_sol_types::sol;
+use alloy_primitives::{Address, U256};
+
+sol! {
+ // Up to 3 parameters can be indexed
+ event Transfer(address indexed from, address indexed to, uint256 value);
+ event Approval(address indexed owner, address indexed spender, uint256 value);
+ event DataUpdated(string indexed key, bytes data);
+}
+```
+
+**Indexed parameters:**
+
+- Allow filtering events by that parameter
+- Limited to 3 indexed parameters per event
+- Indexed parameters are stored in log topics, not data
+
+### Emitting Events
+
+Use `self.vm().log()` to emit events:
+
+```rust
+#[public]
+impl Token {
+ pub fn transfer(&mut self, to: Address, value: U256) -> bool {
+ let from = self.vm().msg_sender();
+
+ // Transfer logic...
+ let from_balance = self.balances.get(from);
+ if from_balance < value {
+ return false;
+ }
+ self.balances.setter(from).set(from_balance - value);
+ self.balances.setter(to).set(self.balances.get(to) + value);
+
+ // Emit event
+ self.vm().log(Transfer { from, to, value });
+
+ true
+ }
+}
+```
+
+### Raw Log Emission
+
+For advanced use cases, emit raw logs directly:
+
+```rust
+use alloy_primitives::FixedBytes;
+
+#[public]
+impl Contract {
+ pub fn emit_raw_log(&self) {
+ let user = Address::from([0x22; 20]);
+ let balance = U256::from(1000);
+
+ // Topics (up to 4, must be FixedBytes<32>)
+ let topics = &[user.into_word()];
+
+ // Data (arbitrary bytes)
+ let mut data: Vec = vec![];
+ data.extend_from_slice(&balance.to_be_bytes::<32>());
+
+ self.vm().raw_log(topics, &data).unwrap();
+ }
+}
+```
+
+## External Contract Calls
+
+Stylus contracts can call other contracts (Solidity or Stylus) using typed interfaces or raw calls.
+
+### Defining Contract Interfaces
+
+Use `sol_interface!` to define interfaces for external contracts:
+
+```rust
+use stylus_sdk::prelude::*;
+
+sol_interface! {
+ interface IToken {
+ function balanceOf(address account) external view returns (uint256);
+ function transfer(address to, uint256 amount) external returns (bool);
+ }
+
+ interface IOracle {
+ function getPrice() external view returns (uint256);
+ }
+}
+```
+
+### Calling External Contracts
+
+#### View Calls (Read-Only)
+
+```rust
+use stylus_sdk::call::Call;
+
+#[public]
+impl MyContract {
+ pub fn get_token_balance(&self, token: IToken, account: Address) -> U256 {
+ // Call::new() for view calls (no state mutation)
+ let config = Call::new();
+ token.balance_of(self.vm(), config, account).unwrap()
+ }
+}
+```
+
+#### Mutating Calls
+
+```rust
+#[public]
+impl MyContract {
+ pub fn transfer_tokens(&mut self, token: IToken, to: Address, amount: U256) -> bool {
+ // Call::new_mutating(self) for state-changing calls
+ let config = Call::new_mutating(self);
+ token.transfer(self.vm(), config, to, amount).unwrap()
+ }
+}
+```
+
+#### Payable Calls
+
+```rust
+#[public]
+impl MyContract {
+ #[payable]
+ pub fn forward_payment(&mut self, recipient: IPaymentProcessor) -> Result<(), Vec> {
+ // Forward received ETH to another contract
+ let value = self.vm().msg_value();
+ let config = Call::new_payable(self, value);
+
+ recipient.process_payment(self.vm(), config)?;
+ Ok(())
+ }
+}
+```
+
+#### Configuring Gas
+
+```rust
+#[public]
+impl MyContract {
+ pub fn call_with_limited_gas(&mut self, token: IToken, to: Address) -> bool {
+ let config = Call::new_mutating(self)
+ .gas(self.vm().evm_gas_left() / 2); // Use half remaining gas
+
+ token.transfer(self.vm(), config, to, U256::from(100)).unwrap()
+ }
+}
+```
+
+### Low-Level Calls
+
+For maximum flexibility, use raw calls:
+
+```rust
+use stylus_sdk::call::{call, static_call, RawCall};
+
+#[public]
+impl MyContract {
+ // Low-level call (state-changing)
+ pub fn execute_call(&mut self, target: Address, calldata: Vec) -> Result, Vec> {
+ let config = Call::new_mutating(self)
+ .gas(self.vm().evm_gas_left());
+
+ call(self.vm(), config, target, &calldata)
+ }
+
+ // Static call (read-only)
+ pub fn execute_static_call(&self, target: Address, calldata: Vec) -> Result, Vec> {
+ static_call(self.vm(), Call::new(), target, &calldata)
+ }
+
+ // Unsafe raw call with advanced options
+ pub fn execute_raw_call(&mut self, target: Address, calldata: Vec) -> Result, Vec> {
+ unsafe {
+ RawCall::new_delegate(self.vm())
+ .gas(2100)
+ .limit_return_data(0, 32)
+ .flush_storage_cache()
+ .call(target, &calldata)
+ }
+ }
+}
+```
+
+**Call Types:**
+
+- `call()`: State-changing call to another contract
+- `static_call()`: Read-only call (equivalent to Solidity `staticcall`)
+- `RawCall`: Low-level unsafe calls with fine-grained control
+
+## Error Handling
+
+Stylus contracts can define and return custom errors using Solidity-compatible error types.
+
+### Defining Errors
+
+```rust
+use alloy_sol_types::sol;
+
+sol! {
+ error Unauthorized();
+ error InsufficientBalance(address from, uint256 have, uint256 want);
+ error InvalidAddress(address addr);
+}
+
+#[derive(SolidityError)]
+pub enum TokenError {
+ Unauthorized(Unauthorized),
+ InsufficientBalance(InsufficientBalance),
+ InvalidAddress(InvalidAddress),
+}
+```
+
+### Using Errors in Methods
+
+```rust
+#[public]
+impl Token {
+ pub fn transfer(&mut self, to: Address, amount: U256) -> Result {
+ let from = self.vm().msg_sender();
+
+ if to == Address::ZERO {
+ return Err(TokenError::InvalidAddress(InvalidAddress { addr: to }));
+ }
+
+ let balance = self.balances.get(from);
+ if balance < amount {
+ return Err(TokenError::InsufficientBalance(InsufficientBalance {
+ from,
+ have: balance,
+ want: amount,
+ }));
+ }
+
+ self.balances.setter(from).set(balance - amount);
+ self.balances.setter(to).set(self.balances.get(to) + amount);
+
+ Ok(true)
+ }
+}
+```
+
+**Error handling notes:**
+
+- Errors automatically encode as Solidity-compatible error data
+- Use `Result` where `E` implements `SolidityError`
+- Error data includes the error signature and parameters
+- Compatible with Solidity `try/catch` blocks
+
+## Complete Example
+
+Here's a complete ERC-20-style token contract demonstrating all major features:
+
+```rust
+#![cfg_attr(not(any(test, feature = "export-abi")), no_main)]
+extern crate alloc;
+
+use alloy_primitives::{Address, U256};
+use alloy_sol_types::sol;
+use stylus_sdk::prelude::*;
+
+// Define events
+sol! {
+ event Transfer(address indexed from, address indexed to, uint256 value);
+ event Approval(address indexed owner, address indexed spender, uint256 value);
+}
+
+// Define errors
+sol! {
+ error InsufficientBalance(address from, uint256 have, uint256 want);
+ error InsufficientAllowance(address owner, address spender, uint256 have, uint256 want);
+ error Unauthorized();
+}
+
+#[derive(SolidityError)]
+pub enum TokenError {
+ InsufficientBalance(InsufficientBalance),
+ InsufficientAllowance(InsufficientAllowance),
+ Unauthorized(Unauthorized),
+}
+
+// Define storage
+sol_storage! {
+ #[entrypoint]
+ pub struct SimpleToken {
+ mapping(address => uint256) balances;
+ mapping(address => mapping(address => uint256)) allowances;
+ uint256 total_supply;
+ address owner;
+ }
+}
+
+#[public]
+impl SimpleToken {
+ // Constructor
+ #[constructor]
+ pub fn constructor(&mut self, initial_supply: U256) {
+ let deployer = self.vm().msg_sender();
+ self.owner.set(deployer);
+ self.balances.setter(deployer).set(initial_supply);
+ self.total_supply.set(initial_supply);
+
+ self.vm().log(Transfer {
+ from: Address::ZERO,
+ to: deployer,
+ value: initial_supply,
+ });
+ }
+
+ // View functions
+ pub fn balance_of(&self, account: Address) -> U256 {
+ self.balances.get(account)
+ }
+
+ pub fn allowance(&self, owner: Address, spender: Address) -> U256 {
+ self.allowances.getter(owner).get(spender)
+ }
+
+ pub fn total_supply(&self) -> U256 {
+ self.total_supply.get()
+ }
+
+ pub fn owner(&self) -> Address {
+ self.owner.get()
+ }
+
+ // Write functions
+ pub fn transfer(&mut self, to: Address, value: U256) -> Result {
+ let from = self.vm().msg_sender();
+ self._transfer(from, to, value)?;
+ Ok(true)
+ }
+
+ pub fn approve(&mut self, spender: Address, value: U256) -> bool {
+ let owner = self.vm().msg_sender();
+ self.allowances.setter(owner).setter(spender).set(value);
+
+ self.vm().log(Approval { owner, spender, value });
+ true
+ }
+
+ pub fn transfer_from(
+ &mut self,
+ from: Address,
+ to: Address,
+ value: U256
+ ) -> Result {
+ let spender = self.vm().msg_sender();
+
+ // Check allowance
+ let current_allowance = self.allowances.getter(from).get(spender);
+ if current_allowance < value {
+ return Err(TokenError::InsufficientAllowance(InsufficientAllowance {
+ owner: from,
+ spender,
+ have: current_allowance,
+ want: value,
+ }));
+ }
+
+ // Update allowance
+ self.allowances.setter(from).setter(spender).set(current_allowance - value);
+
+ // Transfer
+ self._transfer(from, to, value)?;
+ Ok(true)
+ }
+
+ // Owner-only functions
+ pub fn mint(&mut self, to: Address, value: U256) -> Result<(), TokenError> {
+ if self.vm().msg_sender() != self.owner.get() {
+ return Err(TokenError::Unauthorized(Unauthorized {}));
+ }
+
+ self.balances.setter(to).set(self.balances.get(to) + value);
+ self.total_supply.set(self.total_supply.get() + value);
+
+ self.vm().log(Transfer {
+ from: Address::ZERO,
+ to,
+ value,
+ });
+
+ Ok(())
+ }
+
+ // Internal helper function
+ fn _transfer(&mut self, from: Address, to: Address, value: U256) -> Result<(), TokenError> {
+ let from_balance = self.balances.get(from);
+ if from_balance < value {
+ return Err(TokenError::InsufficientBalance(InsufficientBalance {
+ from,
+ have: from_balance,
+ want: value,
+ }));
+ }
+
+ self.balances.setter(from).set(from_balance - value);
+ self.balances.setter(to).set(self.balances.get(to) + value);
+
+ self.vm().log(Transfer { from, to, value });
+ Ok(())
+ }
+}
+```
+
+## Best Practices
+
+### 1. Use Appropriate State Mutability
+
+```rust
+// Good: Read-only functions use &self
+pub fn get_balance(&self, account: Address) -> U256 {
+ self.balances.get(account)
+}
+
+// Good: State-changing functions use &mut self
+pub fn set_balance(&mut self, account: Address, balance: U256) {
+ self.balances.setter(account).set(balance);
+}
+```
+
+### 2. Validate Inputs Early
+
+```rust
+pub fn transfer(&mut self, to: Address, amount: U256) -> Result {
+ // Validate inputs first
+ if to == Address::ZERO {
+ return Err(TokenError::InvalidAddress(InvalidAddress { addr: to }));
+ }
+
+ if amount == U256::ZERO {
+ return Ok(true); // Nothing to transfer
+ }
+
+ // Then proceed with logic
+ let from = self.vm().msg_sender();
+ // ...
+}
+```
+
+### 3. Use Custom Errors
+
+```rust
+// Good: Descriptive custom errors
+pub fn withdraw(&mut self, amount: U256) -> Result<(), VaultError> {
+ let balance = self.balances.get(self.vm().msg_sender());
+ if balance < amount {
+ return Err(VaultError::InsufficientBalance(InsufficientBalance {
+ have: balance,
+ want: amount,
+ }));
+ }
+ // ...
+}
+
+// Avoid: Generic Vec errors
+pub fn withdraw(&mut self, amount: U256) -> Result<(), Vec> {
+ // Less informative
+}
+```
+
+### 4. Emit Events for State Changes
+
+```rust
+pub fn update_value(&mut self, new_value: U256) {
+ let old_value = self.value.get();
+ self.value.set(new_value);
+
+ // Always emit events for important state changes
+ self.vm().log(ValueUpdated {
+ old_value,
+ new_value,
+ });
+}
+```
+
+### 5. Access Control Patterns
+
+```rust
+// Good: Clear access control checks
+pub fn admin_function(&mut self) -> Result<(), TokenError> {
+ if self.vm().msg_sender() != self.owner.get() {
+ return Err(TokenError::Unauthorized(Unauthorized {}));
+ }
+ // Admin logic...
+ Ok(())
+}
+
+// Consider: Reusable modifier-like helper
+impl Token {
+ fn only_owner(&self) -> Result<(), TokenError> {
+ if self.vm().msg_sender() != self.owner.get() {
+ return Err(TokenError::Unauthorized(Unauthorized {}));
+ }
+ Ok(())
+ }
+
+ pub fn admin_function(&mut self) -> Result<(), TokenError> {
+ self.only_owner()?;
+ // Admin logic...
+ Ok(())
+ }
+}
+```
+
+### 6. Gas-Efficient Storage Access
+
+```rust
+// Good: Read once, use multiple times
+pub fn complex_calculation(&self, account: Address) -> U256 {
+ let balance = self.balances.get(account); // Read once
+ let result = balance * U256::from(2) + balance / U256::from(10);
+ result
+}
+
+// Avoid: Multiple reads of same storage slot
+pub fn inefficient_calculation(&self, account: Address) -> U256 {
+ self.balances.get(account) * U256::from(2) + self.balances.get(account) / U256::from(10)
+}
+```
+
+### 7. Check Effects Interactions Pattern
+
+```rust
+// Good: Check-Effects-Interactions pattern
+pub fn withdraw(&mut self, amount: U256) -> Result<(), VaultError> {
+ let caller = self.vm().msg_sender();
+
+ // Checks
+ let balance = self.balances.get(caller);
+ if balance < amount {
+ return Err(VaultError::InsufficientBalance(InsufficientBalance {
+ have: balance,
+ want: amount,
+ }));
+ }
+
+ // Effects (update state BEFORE external calls)
+ self.balances.setter(caller).set(balance - amount);
+
+ // Interactions (external calls last)
+ // self.transfer_eth(caller, amount)?;
+
+ Ok(())
+}
+```
+
+### 8. Use Type-Safe Interfaces for External Calls
+
+```rust
+// Good: Use sol_interface! for type safety
+sol_interface! {
+ interface IToken {
+ function transfer(address to, uint256 amount) external returns (bool);
+ }
+}
+
+pub fn call_token(&mut self, token: IToken, to: Address, amount: U256) -> bool {
+ let config = Call::new_mutating(self);
+ token.transfer(self.vm(), config, to, amount).unwrap()
+}
+
+// Avoid: Raw calls unless necessary
+pub fn raw_call(&mut self, token: Address, to: Address, amount: U256) -> Vec {
+ // Less type-safe, more error-prone
+ let config = Call::new_mutating(self);
+ let calldata = /* manually construct */;
+ call(self.vm(), config, token, &calldata).unwrap()
+}
+```
+
+## Delegate calls
+
+Delegate calls allow a contract to execute code from another contract while maintaining its own context. When Contract A executes a delegate call to Contract B, B's code runs using Contract A's storage, `msg.sender`, and `msg.value`. This means any state changes affect Contract A, and the original sender and value of the transaction are preserved.
+
+This pattern is essential for building upgradeable contracts, proxy patterns, and modular smart contract systems.
+
+### Using the low-level `delegate_call` function
+
+The `delegate_call` function is a low-level operation similar to `call` and `static_call`. It is considered unsafe because it requires trusting the external contract to maintain safety.
+
+```rust
+pub unsafe fn delegate_call(
+ context: impl MutatingCallContext,
+ to: Address,
+ data: &[u8],
+) -> Result, Error>
+```
+
+**Example usage:**
+
+```rust
+use stylus_sdk::call::delegate_call;
+
+pub fn low_level_delegate_call(
+ &mut self,
+ calldata: Vec,
+ target: Address,
+) -> Result, DelegateCallErrors> {
+ unsafe {
+ let result = delegate_call(self, target, &calldata)
+ .map_err(|_| DelegateCallErrors::DelegateCallFailed(DelegateCallFailed {}))?;
+ Ok(result)
+ }
+}
+```
+
+### Using `RawCall` with `new_delegate()`
+
+For scenarios requiring untyped calls with more configuration options, `RawCall` offers a fluent interface. You can set up a delegate call by chaining optional configuration methods.
+
+```rust
+use stylus_sdk::call::RawCall;
+
+pub fn raw_delegate_call(
+ &mut self,
+ calldata: Vec,
+ target: Address,
+) -> Result, Vec> {
+ let data = RawCall::new_delegate() // Configure a delegate call
+ .gas(2100) // Supply 2100 gas
+ .limit_return_data(0, 32) // Only read the first 32 bytes back
+ .call(target, &calldata)?;
+
+ Ok(data)
+}
+```
+
+### Safety considerations
+
+:::caution
+Delegate calls are inherently unsafe and require careful consideration before use.
+:::
+
+- **Trust requirement**: The calling contract must trust the external contract to uphold safety requirements
+- **Storage modification**: The external contract can arbitrarily change the calling contract's storage
+- **Ether spending**: The external contract may spend ether or perform other critical operations on behalf of the caller
+- **Cache clearing**: While the `delegate_call` function clears any cached values, it cannot prevent unsafe actions by the external contract
+
+### Complete delegate call example
+
+```rust
+#![cfg_attr(not(feature = "export-abi"), no_main)]
+extern crate alloc;
+
+use alloy_sol_types::sol;
+use stylus_sdk::{
+ alloy_primitives::Address,
+ call::{delegate_call, RawCall},
+ prelude::*,
+};
+
+#[storage]
+#[entrypoint]
+pub struct DelegateExample;
+
+sol! {
+ error DelegateCallFailed();
+}
+
+#[derive(SolidityError)]
+pub enum DelegateCallErrors {
+ DelegateCallFailed(DelegateCallFailed),
+}
+
+#[public]
+impl DelegateExample {
+ // Low-level delegate call
+ pub fn low_level_delegate_call(
+ &mut self,
+ calldata: Vec,
+ target: Address,
+ ) -> Result, DelegateCallErrors> {
+ unsafe {
+ let result = delegate_call(self, target, &calldata)
+ .map_err(|_| DelegateCallErrors::DelegateCallFailed(DelegateCallFailed {}))?;
+
+ Ok(result)
+ }
+ }
+
+ // RawCall delegate call with configuration
+ pub fn raw_delegate_call(
+ &mut self,
+ calldata: Vec,
+ target: Address,
+ ) -> Result, Vec> {
+ let data = RawCall::new_delegate()
+ .gas(2100)
+ .limit_return_data(0, 32)
+ .call(target, &calldata)?;
+
+ Ok(data)
+ }
+}
+```
+
+## Sending ether
+
+Stylus provides multiple ways to send ether from a contract. Unlike Solidity's `transfer` method which is capped at 2300 gas, Stylus's `transfer_eth` forwards all gas to the recipient. You can cap gas using the low-level `call` method if needed.
+
+### Methods for sending ether
+
+| Method | Description | Gas behavior |
+| ------------------------ | --------------------------------------- | ------------------------------- |
+| `transfer_eth()` | Simple ether transfer | Forwards all gas |
+| `call()` with `.value()` | Low-level call with value | Forwards all gas (configurable) |
+| Payable external calls | Call payable methods on other contracts | Forwards all gas (configurable) |
+
+### Using `transfer_eth()`
+
+The simplest way to send ether:
+
+```rust
+use stylus_sdk::call::transfer_eth;
+
+#[public]
+impl SendEther {
+ #[payable]
+ pub fn send_via_transfer(to: Address) -> Result<(), Vec> {
+ transfer_eth(to, msg::value())?;
+ Ok(())
+ }
+}
+```
+
+### Using low-level `call()` with value
+
+For more control over the transfer:
+
+```rust
+use stylus_sdk::call::{call, Call};
+
+#[public]
+impl SendEther {
+ #[payable]
+ pub fn send_via_call(&mut self, to: Address) -> Result<(), Vec> {
+ call(Call::new_in(self).value(msg::value()), to, &[])?;
+ Ok(())
+ }
+}
+```
+
+These two approaches are equivalent under the hood:
+
+```rust
+// These are equivalent:
+transfer_eth(recipient, value)?;
+call(Call::new_in(self).value(value), recipient, &[])?;
+```
+
+### Sending with a gas limit
+
+To cap the gas forwarded to the recipient (similar to Solidity's `transfer`):
+
+```rust
+#[payable]
+pub fn send_via_call_gas_limit(&mut self, to: Address, gas_amount: u64) -> Result<(), Vec> {
+ call(
+ Call::new_in(self).value(msg::value()).gas(gas_amount),
+ to,
+ &[],
+ )?;
+ Ok(())
+}
+```
+
+### Sending with calldata
+
+To trigger a fallback function on the receiving contract:
+
+```rust
+use stylus_sdk::abi::Bytes;
+
+#[payable]
+pub fn send_via_call_with_calldata(
+ &mut self,
+ to: Address,
+ data: Bytes,
+) -> Result<(), Vec> {
+ call(Call::new_in(self).value(msg::value()), to, data.as_slice())?;
+ Ok(())
+}
+```
+
+### Sending to payable contract methods
+
+Use typed interfaces to call payable methods on other contracts:
+
+```rust
+sol_interface! {
+ interface ITarget {
+ function receiveEther() external payable;
+ }
+}
+
+#[public]
+impl SendEther {
+ #[payable]
+ pub fn send_to_contract(&mut self, to: Address) -> Result<(), Vec> {
+ let target = ITarget::new(to);
+ let config = Call::new_in(self).value(msg::value());
+ target.receive_ether(config)?;
+ Ok(())
+ }
+}
+```
+
+### Where you can send ether
+
+1. **Externally owned account (EOA) addresses**: Directly send ether to any EOA address
+2. **Solidity contracts with `receive()` function**: Send ether without calldata to contracts implementing `receive()`
+3. **Solidity contracts with `fallback()` function**: Send ether with calldata to contracts implementing `fallback()`
+4. **Contracts with payable methods**: Call any payable method on Solidity or Stylus contracts
+
+### Complete sending ether example
+
+```rust
+#![cfg_attr(not(any(feature = "export-abi", test)), no_main)]
+extern crate alloc;
+
+use alloy_primitives::Address;
+use stylus_sdk::{
+ abi::Bytes,
+ call::{call, transfer_eth, Call},
+ msg,
+ prelude::*,
+};
+
+sol_interface! {
+ interface ITarget {
+ function receiveEther() external payable;
+ }
+}
+
+#[storage]
+#[entrypoint]
+pub struct SendEther;
+
+#[public]
+impl SendEther {
+ // Simple transfer
+ #[payable]
+ pub fn send_via_transfer(to: Address) -> Result<(), Vec> {
+ transfer_eth(to, msg::value())?;
+ Ok(())
+ }
+
+ // Low-level call
+ #[payable]
+ pub fn send_via_call(&mut self, to: Address) -> Result<(), Vec> {
+ call(Call::new_in(self).value(msg::value()), to, &[])?;
+ Ok(())
+ }
+
+ // With gas limit
+ #[payable]
+ pub fn send_via_call_gas_limit(&mut self, to: Address, gas_amount: u64) -> Result<(), Vec> {
+ call(
+ Call::new_in(self).value(msg::value()).gas(gas_amount),
+ to,
+ &[],
+ )?;
+ Ok(())
+ }
+
+ // With calldata (triggers fallback)
+ #[payable]
+ pub fn send_via_call_with_calldata(
+ &mut self,
+ to: Address,
+ data: Bytes,
+ ) -> Result<(), Vec> {
+ call(Call::new_in(self).value(msg::value()), to, data.as_slice())?;
+ Ok(())
+ }
+
+ // To payable contract method
+ #[payable]
+ pub fn send_to_contract(&mut self, to: Address) -> Result<(), Vec> {
+ let target = ITarget::new(to);
+ let config = Call::new_in(self).value(msg::value());
+ target.receive_ether(config)?;
+ Ok(())
+ }
+}
+```
+
+## Factory contract deployment (coming soon)
+
+The factory pattern allows a contract to deploy other contracts programmatically. This is useful for creating contract instances on-demand, such as deploying new token contracts or creating user-specific vaults.
+
+:::note
+Advanced deployment patterns documentation is in development. This section will cover:
+
+- Deploying contracts from within a contract
+- Passing constructor arguments
+- Deterministic deployment with CREATE2
+- Handling deployment failures
+ :::
+
+**Constructor considerations for factory-deployed contracts:**
+
+When a contract is deployed via a factory contract (rather than directly by an EOA), the `msg_sender()` in the constructor will be the factory contract's address, not the original deployer. If you need the original deployer's address, use `tx_origin()` instead:
+
+```rust
+#[public]
+impl FactoryDeployedContract {
+ #[constructor]
+ pub fn constructor(&mut self) {
+ // msg_sender() = factory contract address
+ // tx_origin() = original transaction sender (EOA)
+ let original_deployer = self.vm().tx_origin();
+ self.owner.set(original_deployer);
+ }
+}
+```
+
+## Function modifiers and access control patterns
+
+Unlike Solidity, Rust does not have built-in modifier syntax. However, you can achieve similar functionality using helper functions that return `Result<(), Error>` combined with the `?` operator.
+
+### Basic modifier pattern
+
+Create helper functions that perform checks and return early on failure:
+
+```rust
+sol! {
+ error Unauthorized();
+ error Paused();
+}
+
+#[derive(SolidityError)]
+pub enum ContractError {
+ Unauthorized(Unauthorized),
+ Paused(Paused),
+}
+
+#[public]
+impl MyContract {
+ // Modifier-like helper function
+ fn only_owner(&self) -> Result<(), ContractError> {
+ if self.vm().msg_sender() != self.owner.get() {
+ return Err(ContractError::Unauthorized(Unauthorized {}));
+ }
+ Ok(())
+ }
+
+ // Using the "modifier" with the ? operator
+ pub fn admin_function(&mut self) -> Result<(), ContractError> {
+ self.only_owner()?; // Returns early if check fails
+
+ // Admin logic here...
+ Ok(())
+ }
+}
+```
+
+### Multiple guard functions
+
+You can combine multiple checks by chaining helper functions:
+
+```rust
+#[public]
+impl MyContract {
+ fn only_owner(&self) -> Result<(), ContractError> {
+ if self.vm().msg_sender() != self.owner.get() {
+ return Err(ContractError::Unauthorized(Unauthorized {}));
+ }
+ Ok(())
+ }
+
+ fn when_not_paused(&self) -> Result<(), ContractError> {
+ if self.paused.get() {
+ return Err(ContractError::Paused(Paused {}));
+ }
+ Ok(())
+ }
+
+ fn only_after(&self, timestamp: u64) -> Result<(), ContractError> {
+ if self.vm().block_timestamp() < timestamp {
+ return Err(ContractError::TooEarly(TooEarly {}));
+ }
+ Ok(())
+ }
+
+ // Combining multiple "modifiers"
+ pub fn protected_action(&mut self) -> Result<(), ContractError> {
+ self.only_owner()?;
+ self.when_not_paused()?;
+ self.only_after(self.unlock_time.get())?;
+
+ // Protected logic here...
+ Ok(())
+ }
+}
+```
+
+### Reusable access control module
+
+For larger projects, encapsulate access control in a reusable module:
+
+```rust
+// Access control helpers
+impl MyContract {
+ fn require_role(&self, role: FixedBytes<32>, account: Address) -> Result<(), ContractError> {
+ if !self.has_role(role, account) {
+ return Err(ContractError::MissingRole(MissingRole { role, account }));
+ }
+ Ok(())
+ }
+
+ fn has_role(&self, role: FixedBytes<32>, account: Address) -> bool {
+ self.roles.getter(role).get(account)
+ }
+
+ // Grant role (admin only)
+ pub fn grant_role(
+ &mut self,
+ role: FixedBytes<32>,
+ account: Address,
+ ) -> Result<(), ContractError> {
+ self.require_role(self.admin_role(), self.vm().msg_sender())?;
+ self.roles.setter(role).setter(account).set(true);
+ Ok(())
+ }
+}
+```
+
+### Complete access control example
+
+```rust
+#![cfg_attr(not(any(test, feature = "export-abi")), no_main)]
+extern crate alloc;
+
+use alloy_primitives::{Address, U256};
+use alloy_sol_types::sol;
+use stylus_sdk::prelude::*;
+
+sol! {
+ error Unauthorized();
+ error Paused();
+ error InvalidAmount();
+}
+
+#[derive(SolidityError)]
+pub enum VaultError {
+ Unauthorized(Unauthorized),
+ Paused(Paused),
+ InvalidAmount(InvalidAmount),
+}
+
+sol_storage! {
+ #[entrypoint]
+ pub struct Vault {
+ address owner;
+ bool paused;
+ mapping(address => uint256) balances;
+ }
+}
+
+#[public]
+impl Vault {
+ // Modifier-like helpers
+ fn only_owner(&self) -> Result<(), VaultError> {
+ if self.vm().msg_sender() != self.owner.get() {
+ return Err(VaultError::Unauthorized(Unauthorized {}));
+ }
+ Ok(())
+ }
+
+ fn when_not_paused(&self) -> Result<(), VaultError> {
+ if self.paused.get() {
+ return Err(VaultError::Paused(Paused {}));
+ }
+ Ok(())
+ }
+
+ fn valid_amount(&self, amount: U256) -> Result<(), VaultError> {
+ if amount == U256::ZERO {
+ return Err(VaultError::InvalidAmount(InvalidAmount {}));
+ }
+ Ok(())
+ }
+
+ // Public functions using modifiers
+ #[payable]
+ pub fn deposit(&mut self) -> Result<(), VaultError> {
+ self.when_not_paused()?;
+
+ let sender = self.vm().msg_sender();
+ let amount = self.vm().msg_value();
+ self.valid_amount(amount)?;
+
+ let current = self.balances.get(sender);
+ self.balances.setter(sender).set(current + amount);
+ Ok(())
+ }
+
+ pub fn pause(&mut self) -> Result<(), VaultError> {
+ self.only_owner()?;
+ self.paused.set(true);
+ Ok(())
+ }
+
+ pub fn unpause(&mut self) -> Result<(), VaultError> {
+ self.only_owner()?;
+ self.paused.set(false);
+ Ok(())
+ }
+}
+```
+
+## Struct inheritance with `#[inherit]` (legacy pattern - subject to change)
+
+:::note
+This section covers the legacy `#[inherit]` pattern. The trait-based composition pattern described in the [Contract composition and inheritance](#contract-composition-and-inheritance) section is the preferred approach for new contracts.
+:::
+
+The Stylus Rust SDK provides an `#[inherit]` macro that replicates Solidity's composition pattern. The `#[public]` macro provides the `Router` trait, which can be used to connect types via inheritance.
+
+### Basic inheritance
+
+Use `#[inherit]` to include methods from another type:
+
+```rust
+#[public]
+#[inherit(Erc20)]
+impl Token {
+ pub fn mint(&mut self, amount: U256) -> Result<(), Vec> {
+ // Token-specific logic
+ Ok(())
+ }
+}
+
+#[public]
+impl Erc20 {
+ pub fn balance_of(&self, account: Address) -> U256 {
+ self.balances.get(account)
+ }
+}
+```
+
+In this example, `Token` inherits the public methods from `Erc20`. If someone calls the `Token` contract with the `balanceOf` selector, the function `Erc20::balance_of()` executes.
+
+### Using `#[borrow]` for storage access
+
+The inheriting type must implement the `Borrow` trait for borrowing data from the inherited type. The `#[borrow]` annotation simplifies this:
+
+```rust
+sol_storage! {
+ #[entrypoint]
+ pub struct Token {
+ #[borrow]
+ Erc20 erc20;
+ uint256 cap;
+ }
+
+ pub struct Erc20 {
+ mapping(address => uint256) balances;
+ uint256 total_supply;
+ }
+}
+```
+
+### Method resolution order
+
+When a method is called, Stylus searches for it in this order:
+
+1. The entrypoint struct itself
+2. Inherited types, in order of declaration
+3. Types inherited by those types (depth-first search)
+
+```rust
+#[public]
+#[inherit(B, C)]
+impl A {
+ pub fn foo() -> Result<(), Vec> { /* ... */ }
+}
+
+#[public]
+impl B {
+ pub fn bar() -> Result<(), Vec> { /* ... */ }
+}
+
+#[public]
+impl C {
+ pub fn bar() -> Result<(), Vec> { /* ... */ }
+ pub fn baz() -> Result<(), Vec> { /* ... */ }
+}
+```
+
+In this example:
+
+- Calling `foo()` executes `A::foo()` (found in A)
+- Calling `bar()` executes `B::bar()` (found in B first, C's version is never reached)
+- Calling `baz()` executes `C::baz()` (not found in A or B, found in C)
+
+### Method overriding
+
+Because methods are checked in inheritance order, a method in the higher-level type overrides methods with the same name in lower levels:
+
+```rust
+#[public]
+#[inherit(B)]
+impl A {
+ pub fn foo() -> Result<(), Vec> {
+ // This version will be called
+ Ok(())
+ }
+}
+
+#[public]
+impl B {
+ pub fn foo() -> Result<(), Vec> {
+ // This version is never reached
+ Ok(())
+ }
+}
+```
+
+:::caution
+The Stylus Rust SDK does not currently contain explicit `override` or `virtual` keywords. Carefully ensure your contracts only override the intended functions. Unintentional method shadowing can lead to unexpected behavior.
+:::
+
+### Limitations
+
+- **No multi-inheritance**: A single contract cannot inherit from multiple unrelated types that both need storage access
+- **No explicit override/virtual**: Method overriding is implicit based on declaration order
+- **Subject to change**: This pattern may evolve in future SDK versions
+
+### Complete inheritance example
+
+```rust
+#![cfg_attr(not(any(test, feature = "export-abi")), no_main)]
+extern crate alloc;
+
+use alloy_primitives::{Address, U256};
+use stylus_sdk::prelude::*;
+
+sol_storage! {
+ #[entrypoint]
+ pub struct Token {
+ #[borrow]
+ Erc20 erc20;
+ address owner;
+ }
+
+ pub struct Erc20 {
+ mapping(address => uint256) balances;
+ uint256 total_supply;
+ }
+}
+
+#[public]
+#[inherit(Erc20)]
+impl Token {
+ pub fn mint(&mut self, to: Address, amount: U256) -> Result<(), Vec> {
+ // Token adds minting capability
+ let current = self.erc20.balances.get(to);
+ self.erc20.balances.setter(to).set(current + amount);
+ let supply = self.erc20.total_supply.get();
+ self.erc20.total_supply.set(supply + amount);
+ Ok(())
+ }
+}
+
+#[public]
+impl Erc20 {
+ pub fn balance_of(&self, account: Address) -> U256 {
+ self.balances.get(account)
+ }
+
+ pub fn total_supply(&self) -> U256 {
+ self.total_supply.get()
+ }
+
+ pub fn transfer(&mut self, to: Address, amount: U256) -> Result> {
+ let from = self.vm().msg_sender();
+ let from_balance = self.balances.get(from);
+ if from_balance < amount {
+ return Ok(false);
+ }
+ self.balances.setter(from).set(from_balance - amount);
+ let to_balance = self.balances.get(to);
+ self.balances.setter(to).set(to_balance + amount);
+ Ok(true)
+ }
+}
+```
+
+## See Also
+
+- [Primitives](./data-types/primitives.mdx) - Basic data types
+- [Compound Types](./data-types/compound-types.mdx) - Arrays, structs, tuples
+- [Storage Types](./data-types/storage.mdx) - Persistent storage
+- [Global Variables and Functions](./global-variables-and-functions.mdx) - VM context methods
diff --git a/docs/stylus/reference/data-types/compound-types.mdx b/docs/stylus/reference/data-types/compound-types.mdx
new file mode 100644
index 0000000000..6cef7b8c79
--- /dev/null
+++ b/docs/stylus/reference/data-types/compound-types.mdx
@@ -0,0 +1,845 @@
+---
+title: 'Stylus compound types'
+description: 'Stylus Rust SDK compound types'
+author: chrisco
+sme: chrisco
+sidebar_position: 2
+target_audience: Developers using the Stylus Rust SDK to write and deploy smart contracts.
+displayed_sidebar: buildStylusSidebar
+---
+
+Compound types allow you to group multiple values together in Stylus contracts. The SDK provides full support for tuples, structs, arrays, and vectors with automatic ABI encoding/decoding and Solidity type mappings.
+
+## Tuples
+
+Tuples group multiple values of different types together. They map directly to Solidity tuples.
+
+### Basic tuples
+
+```rust
+use alloy_primitives::{Address, U256, Bytes};
+use stylus_sdk::prelude::*;
+
+#[public]
+impl MyContract {
+ // Return multiple values as a tuple
+ pub fn get_data(&self) -> (U256, Address, bool) {
+ (U256::from(100), Address::ZERO, true)
+ }
+
+ // Accept tuple as parameter
+ pub fn process_tuple(&mut self, data: (U256, U256, U256)) -> U256 {
+ let (a, b, c) = data;
+ a + b + c
+ }
+
+ // Nested tuples
+ pub fn nested(&self) -> ((U256, U256), bool) {
+ ((U256::from(1), U256::from(2)), true)
+ }
+}
+```
+
+### Tuple destructuring
+
+```rust
+use alloy_primitives::U256;
+use stylus_sdk::prelude::*;
+
+#[public]
+impl MyContract {
+ pub fn calculate(&self) -> (U256, U256) {
+ let values = (U256::from(100), U256::from(200));
+
+ // Destructure the tuple
+ let (first, second) = values;
+
+ // Return new tuple
+ (first * U256::from(2), second * U256::from(2))
+ }
+
+ // Pattern matching with tuples
+ pub fn match_tuple(&self, data: (bool, U256)) -> U256 {
+ match data {
+ (true, value) => value * U256::from(2),
+ (false, value) => value,
+ }
+ }
+}
+```
+
+### Tuple type mappings
+
+| Rust Type | Solidity Type | ABI Signature |
+| ---------------------- | ---------------------------- | ---------------------------- |
+| `(U256,)` | `(uint256)` | `"(uint256)"` |
+| `(U256, Address)` | `(uint256, address)` | `"(uint256,address)"` |
+| `(bool, U256, Bytes)` | `(bool, uint256, bytes)` | `"(bool,uint256,bytes)"` |
+| `((U256, U256), bool)` | `((uint256, uint256), bool)` | `"((uint256,uint256),bool)"` |
+
+**Tuple Limitations**:
+
+- Tuples support up to 24 elements
+- Tuples are always returned as `memory` in Solidity
+- Empty tuple `()` represents no return value
+
+## Structs
+
+Structs define custom data types with named fields. Use the `sol!` macro to define Solidity-compatible structs.
+
+### Defining structs with `sol!`
+
+```rust
+use alloy_primitives::{Address, U256};
+use alloy_sol_types::sol;
+use stylus_sdk::prelude::*;
+
+sol! {
+ #[derive(Debug, AbiType)]
+ struct User {
+ address account;
+ uint256 balance;
+ string name;
+ }
+
+ #[derive(Debug, AbiType)]
+ struct Token {
+ string name;
+ string symbol;
+ uint8 decimals;
+ }
+}
+
+#[public]
+impl MyContract {
+ pub fn get_user(&self) -> User {
+ User {
+ account: Address::ZERO,
+ balance: U256::from(1000),
+ name: "Alice".to_string(),
+ }
+ }
+
+ pub fn process_user(&mut self, user: User) -> U256 {
+ // Access struct fields
+ user.balance
+ }
+
+ pub fn get_token_info(&self) -> Token {
+ Token {
+ name: "MyToken".to_string(),
+ symbol: "MTK".to_string(),
+ decimals: 18,
+ }
+ }
+}
+```
+
+### Nested structs
+
+Structs can contain other structs, enabling complex data structures:
+
+```rust
+use alloy_primitives::Address;
+use alloy_sol_types::sol;
+use stylus_sdk::prelude::*;
+
+sol! {
+ #[derive(Debug, AbiType)]
+ struct Dog {
+ string name;
+ string breed;
+ }
+
+ #[derive(Debug, AbiType)]
+ struct User {
+ address account;
+ string name;
+ Dog[] dogs;
+ }
+}
+
+#[public]
+impl MyContract {
+ pub fn create_user(&self) -> User {
+ let dogs = vec![
+ Dog {
+ name: "Rex".to_string(),
+ breed: "Labrador".to_string(),
+ },
+ Dog {
+ name: "Max".to_string(),
+ breed: "Beagle".to_string(),
+ },
+ ];
+
+ User {
+ account: Address::ZERO,
+ name: "Alice".to_string(),
+ dogs,
+ }
+ }
+
+ pub fn get_dog_count(&self, user: User) -> u256 {
+ user.dogs.len() as u256
+ }
+}
+```
+
+### Struct best practices
+
+1. **Always use `#[derive(AbiType)]`** for structs that will be used in contract interfaces:
+
+ ```rust
+ sol! {
+ #[derive(Debug, AbiType)]
+ struct MyData {
+ uint256 value;
+ address owner;
+ }
+ }
+ ```
+
+2. **Add `Debug` derive** for easier debugging:
+
+ ```rust
+ sol! {
+ #[derive(Debug, AbiType)]
+ struct Config {
+ bool enabled;
+ uint256 timeout;
+ }
+ }
+ ```
+
+3. **Use descriptive field names** that match Solidity conventions:
+ ```rust
+ sol! {
+ #[derive(Debug, AbiType)]
+ struct VestingSchedule {
+ address beneficiary;
+ uint256 startTime;
+ uint256 cliffDuration;
+ uint256 totalAmount;
+ }
+ }
+ ```
+
+## Arrays
+
+Arrays are fixed-size collections of elements. Stylus supports both Rust arrays and Solidity-style arrays.
+
+### Fixed-size arrays
+
+```rust
+use alloy_primitives::U256;
+use stylus_sdk::prelude::*;
+
+#[public]
+impl MyContract {
+ // Return a fixed-size array
+ pub fn get_numbers(&self) -> [U256; 5] {
+ [
+ U256::from(1),
+ U256::from(2),
+ U256::from(3),
+ U256::from(4),
+ U256::from(5),
+ ]
+ }
+
+ // Accept fixed-size array as parameter
+ pub fn sum_array(&self, numbers: [U256; 5]) -> U256 {
+ numbers.iter().fold(U256::ZERO, |acc, &x| acc + x)
+ }
+
+ // Nested arrays
+ pub fn matrix(&self) -> [[u32; 2]; 3] {
+ [[1, 2], [3, 4], [5, 6]]
+ }
+}
+```
+
+### Array operations
+
+```rust
+use alloy_primitives::{Address, U256};
+use stylus_sdk::prelude::*;
+
+#[public]
+impl MyContract {
+ // Iterate over array
+ pub fn process_addresses(&self, addresses: [Address; 10]) -> U256 {
+ let mut count = U256::ZERO;
+ for addr in addresses.iter() {
+ if *addr != Address::ZERO {
+ count += U256::from(1);
+ }
+ }
+ count
+ }
+
+ // Array of booleans
+ pub fn check_flags(&self, flags: [bool; 8]) -> bool {
+ flags.iter().all(|&f| f)
+ }
+}
+```
+
+### Array type mappings
+
+| Rust Type | Solidity Type | Description |
+| --------------------- | -------------- | ------------------------- |
+| `[U256; 5]` | `uint256[5]` | 5-element uint256 array |
+| `[bool; 10]` | `bool[10]` | 10-element bool array |
+| `[Address; 3]` | `address[3]` | 3-element address array |
+| `[[u32; 2]; 4]` | `uint32[2][4]` | Nested array (4x2 matrix) |
+| `[FixedBytes<32>; 2]` | `bytes32[2]` | 2-element bytes32 array |
+
+## Vectors
+
+Vectors are dynamic arrays that can grow or shrink at runtime. They map to Solidity dynamic arrays.
+
+### Basic vector usage
+
+```rust
+use alloy_primitives::{Address, U256, Bytes};
+use stylus_sdk::prelude::*;
+
+#[public]
+impl MyContract {
+ // Return a vector
+ pub fn get_numbers(&self) -> Vec {
+ vec![U256::from(1), U256::from(2), U256::from(3)]
+ }
+
+ // Accept vector as parameter
+ pub fn sum_vec(&self, numbers: Vec) -> U256 {
+ numbers.iter().fold(U256::ZERO, |acc, x| acc + *x)
+ }
+
+ // Vector of addresses
+ pub fn get_addresses(&self) -> Vec {
+ vec![Address::ZERO, Address::ZERO]
+ }
+
+ // Vector of bytes
+ pub fn get_data_list(&self) -> Vec {
+ vec![
+ Bytes::from(vec![1, 2, 3]),
+ Bytes::from(vec![4, 5, 6]),
+ ]
+ }
+}
+```
+
+### Vector operations
+
+```rust
+use alloy_primitives::U256;
+use stylus_sdk::prelude::*;
+
+#[public]
+impl MyContract {
+ // Filter vector
+ pub fn filter_even(&self, numbers: Vec) -> Vec {
+ numbers
+ .into_iter()
+ .filter(|n| n.byte(0) % 2 == 0)
+ .collect()
+ }
+
+ // Map over vector
+ pub fn double_values(&self, numbers: Vec) -> Vec {
+ numbers
+ .into_iter()
+ .map(|n| n * U256::from(2))
+ .collect()
+ }
+
+ // Find in vector
+ pub fn contains_value(&self, numbers: Vec, target: U256) -> bool {
+ numbers.contains(&target)
+ }
+
+ // Get vector length
+ pub fn get_length(&self, items: Vec) -> U256 {
+ U256::from(items.len())
+ }
+}
+```
+
+### Vectors of structs
+
+```rust
+use alloy_primitives::Address;
+use alloy_sol_types::sol;
+use stylus_sdk::prelude::*;
+
+sol! {
+ #[derive(Debug, AbiType)]
+ struct Transaction {
+ address from;
+ address to;
+ uint256 amount;
+ }
+}
+
+#[public]
+impl MyContract {
+ pub fn get_transactions(&self) -> Vec {
+ vec![
+ Transaction {
+ from: Address::ZERO,
+ to: Address::ZERO,
+ amount: U256::from(100),
+ },
+ Transaction {
+ from: Address::ZERO,
+ to: Address::ZERO,
+ amount: U256::from(200),
+ },
+ ]
+ }
+
+ pub fn total_amount(&self, txs: Vec) -> U256 {
+ txs.iter()
+ .fold(U256::ZERO, |acc, tx| acc + tx.amount)
+ }
+}
+```
+
+### Vector type mappings
+
+| Rust Type | Solidity Type | ABI Signature | Storage |
+| --------------- | ------------- | --------------------- | ------- |
+| `Vec` | `uint256[]` | `"uint256[] memory"` | Dynamic |
+| `Vec` | `address[]` | `"address[] memory"` | Dynamic |
+| `Vec` | `bool[]` | `"bool[] memory"` | Dynamic |
+| `Vec` | `bytes[]` | `"bytes[] memory"` | Dynamic |
+| `Vec` | `MyStruct[]` | `"MyStruct[] memory"` | Dynamic |
+
+**Important Notes**:
+
+- Vectors are **always returned as `memory`** in Solidity, never as `calldata`
+- `Vec` maps to `uint8[]`, not `bytes` (use `Bytes` for Solidity `bytes`)
+- Vectors have dynamic size and consume more gas than fixed arrays
+
+## Bytes Types
+
+The SDK provides `Bytes` for dynamic byte arrays and `FixedBytes` for fixed-size byte arrays.
+
+### Dynamic bytes (`Bytes`)
+
+```rust
+use alloy_primitives::Bytes;
+use stylus_sdk::prelude::*;
+
+#[public]
+impl MyContract {
+ // Return dynamic bytes
+ pub fn get_data(&self) -> Bytes {
+ Bytes::from(vec![1, 2, 3, 4, 5])
+ }
+
+ // Process bytes
+ pub fn get_length(&self, data: Bytes) -> usize {
+ data.len()
+ }
+
+ // Concatenate bytes
+ pub fn concat(&self, a: Bytes, b: Bytes) -> Bytes {
+ let mut result = a.to_vec();
+ result.extend_from_slice(&b);
+ Bytes::from(result)
+ }
+}
+```
+
+### Fixed bytes (`FixedBytes`)
+
+```rust
+use alloy_primitives::FixedBytes;
+use stylus_sdk::prelude::*;
+
+#[public]
+impl MyContract {
+ // bytes32 (common for hashes)
+ pub fn get_hash(&self) -> FixedBytes<32> {
+ FixedBytes::<32>::ZERO
+ }
+
+ // bytes4 (common for selectors)
+ pub fn get_selector(&self) -> FixedBytes<4> {
+ FixedBytes::from([0x12, 0x34, 0x56, 0x78])
+ }
+
+ // bytes16
+ pub fn get_uuid(&self) -> FixedBytes<16> {
+ FixedBytes::<16>::from([
+ 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0,
+ 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0,
+ ])
+ }
+}
+```
+
+## Complete examples
+
+### Example 1: Complex data structures
+
+```rust
+#![cfg_attr(not(any(test, feature = "export-abi")), no_main)]
+extern crate alloc;
+
+use alloc::string::String;
+use alloy_primitives::{Address, U256};
+use alloy_sol_types::sol;
+use stylus_sdk::prelude::*;
+
+sol! {
+ #[derive(Debug, AbiType)]
+ struct Token {
+ string name;
+ string symbol;
+ uint8 decimals;
+ uint256 totalSupply;
+ }
+
+ #[derive(Debug, AbiType)]
+ struct Balance {
+ address owner;
+ uint256 amount;
+ }
+}
+
+sol_storage! {
+ #[entrypoint]
+ pub struct CompoundExample {
+ uint256 counter;
+ }
+}
+
+#[public]
+impl CompoundExample {
+ // Return tuple
+ pub fn get_info(&self) -> (String, U256, bool) {
+ ("Example".to_string(), U256::from(42), true)
+ }
+
+ // Return struct
+ pub fn get_token(&self) -> Token {
+ Token {
+ name: "MyToken".to_string(),
+ symbol: "MTK".to_string(),
+ decimals: 18,
+ totalSupply: U256::from(1000000),
+ }
+ }
+
+ // Return vector of structs
+ pub fn get_balances(&self) -> Vec {
+ vec![
+ Balance {
+ owner: Address::ZERO,
+ amount: U256::from(100),
+ },
+ Balance {
+ owner: Address::ZERO,
+ amount: U256::from(200),
+ },
+ ]
+ }
+
+ // Accept array
+ pub fn process_array(&self, data: [U256; 5]) -> U256 {
+ data.iter().sum()
+ }
+
+ // Accept vector and struct
+ pub fn batch_transfer(&mut self, recipients: Vec) -> U256 {
+ recipients.iter().map(|b| b.amount).sum()
+ }
+}
+```
+
+### Example 2: Nested data structures
+
+```rust
+#![cfg_attr(not(any(test, feature = "export-abi")), no_main)]
+extern crate alloc;
+
+use alloc::string::String;
+use alloy_primitives::{Address, U256};
+use alloy_sol_types::sol;
+use stylus_sdk::prelude::*;
+
+sol! {
+ #[derive(Debug, AbiType)]
+ struct Dog {
+ string name;
+ string breed;
+ }
+
+ #[derive(Debug, AbiType)]
+ struct User {
+ address account;
+ string name;
+ Dog[] dogs;
+ }
+}
+
+sol_storage! {
+ #[entrypoint]
+ pub struct NestedExample {}
+}
+
+#[public]
+impl NestedExample {
+ pub fn create_user(&self) -> User {
+ User {
+ account: Address::ZERO,
+ name: "Alice".to_string(),
+ dogs: vec![
+ Dog {
+ name: "Rex".to_string(),
+ breed: "Labrador".to_string(),
+ },
+ Dog {
+ name: "Max".to_string(),
+ breed: "Beagle".to_string(),
+ },
+ ],
+ }
+ }
+
+ pub fn get_dog_names(&self, user: User) -> Vec {
+ user.dogs.into_iter().map(|dog| dog.name).collect()
+ }
+
+ pub fn count_dogs(&self, users: Vec) -> U256 {
+ let total: usize = users.iter().map(|u| u.dogs.len()).sum();
+ U256::from(total)
+ }
+}
+```
+
+## Best practices
+
+### 1. Choose the right type
+
+```rust
+// Use tuples for simple groupings
+pub fn get_basics(&self) -> (U256, Address, bool) { /* ... */ }
+
+// Use structs for complex data with named fields
+sol! {
+ #[derive(Debug, AbiType)]
+ struct UserProfile {
+ address account;
+ string name;
+ uint256 balance;
+ bool active;
+ }
+}
+
+// Use arrays for fixed-size collections
+pub fn get_top_five(&self) -> [U256; 5] { /* ... */ }
+
+// Use vectors for dynamic collections
+pub fn get_all_users(&self) -> Vec { /* ... */ }
+```
+
+### 2. Memory efficiency
+
+```rust
+use alloy_primitives::U256;
+
+// Prefer fixed arrays when size is known
+pub fn fixed_data(&self) -> [U256; 10] {
+ // More gas-efficient
+ [U256::ZERO; 10]
+}
+
+// Use vectors only when size varies
+pub fn dynamic_data(&self, count: usize) -> Vec {
+ vec![U256::ZERO; count]
+}
+```
+
+### 3. Struct naming
+
+```rust
+use alloy_sol_types::sol;
+
+sol! {
+ // Good: Clear, descriptive names
+ #[derive(Debug, AbiType)]
+ struct TokenMetadata {
+ string name;
+ string symbol;
+ uint8 decimals;
+ }
+
+ // Avoid: Ambiguous names
+ #[derive(Debug, AbiType)]
+ struct Data {
+ uint256 x;
+ uint256 y;
+ }
+}
+```
+
+### 4. Vector vs array
+
+```rust
+use alloy_primitives::{Address, U256};
+
+// Use fixed arrays for known sizes
+pub fn get_admins(&self) -> [Address; 3] {
+ // Three admin addresses
+ [Address::ZERO; 3]
+}
+
+// Use vectors for variable sizes
+pub fn get_users(&self) -> Vec {
+ // Unknown number of users
+ vec![]
+}
+```
+
+### 5. Nested structures
+
+```rust
+use alloy_sol_types::sol;
+
+sol! {
+ // Good: Reasonable nesting depth
+ #[derive(Debug, AbiType)]
+ struct User {
+ address account;
+ Profile profile;
+ }
+
+ #[derive(Debug, AbiType)]
+ struct Profile {
+ string name;
+ uint256 age;
+ }
+
+ // Avoid: Excessive nesting (gas inefficient)
+ #[derive(Debug, AbiType)]
+ struct DeepNesting {
+ Level1 l1;
+ }
+
+ #[derive(Debug, AbiType)]
+ struct Level1 {
+ Level2 l2;
+ }
+
+ #[derive(Debug, AbiType)]
+ struct Level2 {
+ Level3 l3;
+ }
+
+ #[derive(Debug, AbiType)]
+ struct Level3 {
+ uint256 value;
+ }
+}
+```
+
+## Type conversion and helpers
+
+### Converting between types
+
+```rust
+use alloy_primitives::{U256, Bytes};
+
+// Vec to Bytes
+let vec: Vec = vec![1, 2, 3];
+let bytes = Bytes::from(vec);
+
+// Bytes to Vec
+let bytes = Bytes::from(vec![1, 2, 3]);
+let vec: Vec = bytes.to_vec();
+
+// Array to Vec
+let arr: [U256; 3] = [U256::from(1), U256::from(2), U256::from(3)];
+let vec: Vec = arr.to_vec();
+
+// Vec to array (if size matches)
+let vec = vec![U256::from(1), U256::from(2), U256::from(3)];
+let arr: [U256; 3] = vec.try_into().unwrap();
+```
+
+### Working with iterators
+
+```rust
+use alloy_primitives::U256;
+
+// Map over vector
+let numbers = vec![U256::from(1), U256::from(2), U256::from(3)];
+let doubled: Vec = numbers.iter().map(|n| n * U256::from(2)).collect();
+
+// Filter vector
+let evens: Vec = numbers.into_iter().filter(|n| n.byte(0) % 2 == 0).collect();
+
+// Fold/reduce
+let sum = numbers.iter().fold(U256::ZERO, |acc, n| acc + n);
+```
+
+## Common patterns
+
+### Batch operations
+
+```rust
+use alloy_primitives::{Address, U256};
+use alloy_sol_types::sol;
+use stylus_sdk::prelude::*;
+
+sol! {
+ #[derive(Debug, AbiType)]
+ struct Transfer {
+ address to;
+ uint256 amount;
+ }
+}
+
+#[public]
+impl MyContract {
+ pub fn batch_transfer(&mut self, transfers: Vec) -> U256 {
+ let mut total = U256::ZERO;
+ for transfer in transfers {
+ // Process each transfer
+ total += transfer.amount;
+ }
+ total
+ }
+}
+```
+
+### Pagination
+
+```rust
+use alloy_primitives::U256;
+use stylus_sdk::prelude::*;
+
+#[public]
+impl MyContract {
+ pub fn get_page(&self, items: Vec, page: usize, size: usize) -> Vec {
+ let start = page * size;
+ let end = start + size;
+ items.get(start..end.min(items.len()))
+ .unwrap_or(&[])
+ .to_vec()
+ }
+}
+```
+
+## See also
+
+- [Primitives](./primitives.mdx) - Basic types (bool, integers, address, strings)
+- [Storage Types](./storage.mdx) - Persistent storage for compound types
+- [Type Conversions](./conversions-between-types.mdx) - Converting between types
diff --git a/docs/stylus/reference/data-types/conversions-between-types.mdx b/docs/stylus/reference/data-types/conversions-between-types.mdx
new file mode 100644
index 0000000000..d86fd64494
--- /dev/null
+++ b/docs/stylus/reference/data-types/conversions-between-types.mdx
@@ -0,0 +1,377 @@
+---
+title: 'Conversions between types'
+description: 'Learn how to convert between Rust types, Alloy primitives, and Solidity types in Stylus smart contracts'
+author: chrisco
+sme: chrisco
+sidebar_position: 1
+target_audience: Developers using the Stylus Rust SDK to write and deploy smart contracts.
+displayed_sidebar: buildStylusSidebar
+---
+
+Stylus smart contracts often need to convert between different type representations: Rust native types, Alloy primitives, and Solidity types. The Stylus SDK provides comprehensive conversion mechanisms through the `AbiType` trait and various standard Rust conversion traits.
+
+## Understanding type relationships
+
+The Stylus SDK establishes a bidirectional relationship between Rust and Solidity types:
+
+- **Alloy provides**: Solidity types → Rust types mapping via `SolType`
+- **Stylus SDK provides**: Rust types → Solidity types mapping via `AbiType`
+
+Together, these create a complete two-way type system for interoperability.
+
+### Key type mappings
+
+| Rust/Alloy type | Solidity type | Notes |
+| --------------------------------- | ------------------------- | ---------------------------------------------- |
+| `bool` | `bool` | Native boolean |
+| `u8`, `u16`, `u32`, `u64`, `u128` | `uint8` through `uint128` | Native unsigned integers |
+| `i8`, `i16`, `i32`, `i64`, `i128` | `int8` through `int128` | Native signed integers |
+| `Uint` | `uintBITS` | Arbitrary-sized unsigned integers (8-256 bits) |
+| `Signed` | `intBITS` | Arbitrary-sized signed integers (8-256 bits) |
+| `U256` | `uint256` | 256-bit unsigned integer (most common) |
+| `Address` | `address` | 20-byte Ethereum address |
+| `FixedBytes` | `bytesN` | Fixed-size byte array |
+| `Bytes` | `bytes` | Dynamic byte array |
+| `String` | `string` | Dynamic UTF-8 string |
+| `Vec` | `T[]` | Dynamic array |
+| `[T; N]` | `T[N]` | Fixed-size array |
+| `(T1, T2, ...)` | `(T1, T2, ...)` | Tuple types |
+
+**Important**: The SDK treats `Vec` as Solidity `uint8[]`. For Solidity `bytes`, use `alloy_primitives::Bytes`.
+
+## Converting numeric types
+
+### Creating integers from literals
+
+```rust
+use stylus_sdk::alloy_primitives::{U256, I256, U8, I8};
+
+// From integer literals
+let small: U8 = U8::from(1);
+let large: U256 = U256::from(255);
+
+// For signed integers
+let positive: I8 = I8::unchecked_from(127);
+let negative: I8 = I8::unchecked_from(-1);
+let signed_large: I256 = I256::unchecked_from(0xff_u64);
+```
+
+### Parsing from strings
+
+```rust
+use stylus_sdk::alloy_primitives::I256;
+
+// Parse decimal strings
+let a = I256::try_from(20003000).unwrap();
+let b = "100".parse::().unwrap();
+
+// Parse hexadecimal strings
+let c = "-0x138f".parse::().unwrap();
+
+// Underscores are ignored for readability
+let d = "1_000_000".parse::().unwrap();
+
+// Arithmetic works as expected
+let result = a * b + c - d;
+```
+
+### Integer constants
+
+```rust
+use stylus_sdk::alloy_primitives::I256;
+
+let max = I256::MAX; // Maximum value
+let min = I256::MIN; // Minimum value
+let zero = I256::ZERO; // Zero
+let minus_one = I256::MINUS_ONE; // -1
+```
+
+### Converting between integer sizes
+
+```rust
+use stylus_sdk::alloy_primitives::{Uint, Signed, U256};
+
+// Between Alloy integer types (same bit-width)
+let uint_value = Uint::<128, 2>::from(999);
+let u128_value: u128 = uint_value.try_into()
+ .map_err(|_| "conversion error")
+ .unwrap();
+
+// Between different bit-widths
+let small = Uint::<8, 1>::from(100);
+let large = U256::from(small);
+```
+
+The SDK uses the `ConvertInt` trait internally to enable conversions between Alloy's `Uint` types and Rust native integer types like `u8`, `u16`, `u32`, `u64`, and `u128`.
+
+## Converting addresses
+
+### Creating addresses
+
+```rust
+use stylus_sdk::alloy_primitives::{Address, address};
+
+// From a 20-byte array
+let addr1 = Address::from([0x11; 20]);
+
+// Using the address! macro with checksummed string
+let addr2 = address!("d8da6bf26964af9d7eed9e03e53415d37aa96045");
+
+// From a byte slice
+let bytes: [u8; 20] = [0xd8, 0xda, 0x6b, 0xf2, /* ... */];
+let addr3 = Address::from(bytes);
+```
+
+### Converting addresses to bytes
+
+```rust
+use stylus_sdk::alloy_primitives::Address;
+
+let addr = address!("d8da6bf26964af9d7eed9e03e53415d37aa96045");
+
+// Get reference to underlying bytes
+let bytes_ref: &[u8] = addr.as_ref();
+
+// Use in byte concatenation
+let data = [addr.as_ref(), other_data].concat();
+```
+
+## Converting byte types
+
+### Fixed-size bytes
+
+```rust
+use stylus_sdk::alloy_primitives::FixedBytes;
+
+// Create from array
+let fixed = FixedBytes::<32>::new([0u8; 32]);
+
+// Create from slice
+let slice: &[u8] = &[1, 2, 3, 4];
+let fixed = FixedBytes::<4>::from_slice(slice);
+
+// Convert to slice
+let bytes_ref: &[u8] = fixed.as_ref();
+```
+
+### Dynamic bytes
+
+```rust
+use stylus_sdk::abi::Bytes;
+
+// Create from Vec
+let bytes = Bytes::from(vec![1, 2, 3, 4]);
+
+// Create empty
+let empty = Bytes::new();
+
+// Get reference to underlying data
+let data: &[u8] = bytes.as_ref();
+
+// Convert to Vec
+let vec: Vec = bytes.to_vec();
+```
+
+### Byte array conversions
+
+```rust
+use stylus_sdk::alloy_primitives::U256;
+
+// Convert U256 to big-endian bytes
+let value = U256::from(12345);
+let bytes_vec: Vec = value.to_be_bytes_vec();
+let bytes_array: [u8; 32] = value.to_be_bytes();
+
+// Convert from big-endian bytes
+let from_slice = U256::try_from_be_slice(&bytes_vec).unwrap();
+```
+
+## Converting strings
+
+```rust
+use alloc::string::{String, ToString};
+
+// String conversions
+let rust_string = "hello".to_string();
+let bytes = rust_string.as_bytes();
+
+// For Solidity string parameters in functions
+// the String type is automatically handled by AbiType
+pub fn process_string(&self, text: String) -> String {
+ text
+}
+```
+
+## Converting collections
+
+### Dynamic arrays (Vec)
+
+```rust
+use stylus_sdk::alloy_primitives::U256;
+use alloc::vec::Vec;
+
+// Vec is used directly as Solidity dynamic arrays
+let numbers: Vec = vec![
+ U256::from(1),
+ U256::from(2),
+ U256::from(3),
+];
+
+// For Vec, note this maps to uint8[], not bytes
+let uint8_array: Vec = vec![1, 2, 3];
+```
+
+### Fixed-size arrays
+
+```rust
+use stylus_sdk::alloy_primitives::U256;
+
+// Fixed arrays map directly to Solidity fixed arrays
+let fixed: [U256; 3] = [
+ U256::from(1),
+ U256::from(2),
+ U256::from(3),
+];
+
+// Nested arrays
+let nested: [[u32; 2]; 4] = [[1, 2], [3, 4], [5, 6], [7, 8]];
+```
+
+## ABI encoding and decoding
+
+### Encoding types
+
+```rust
+use stylus_sdk::abi::{encode, encode_params};
+use stylus_sdk::alloy_primitives::{Address, U256};
+use alloy_sol_types::{sol_data::*, SolType};
+
+// Encode a single value
+let value = U256::from(100);
+let encoded = encode(&value);
+
+// Encode tuple of parameters
+type TransferParams = (Address, Uint<256>);
+let params = (address, amount);
+let encoded = TransferParams::abi_encode_params(¶ms);
+```
+
+### Decoding types
+
+```rust
+use stylus_sdk::abi::decode_params;
+use stylus_sdk::alloy_primitives::{Address, U256};
+use alloy_sol_types::{sol_data::*, SolType};
+
+// Define the expected type structure
+type TransferParams = (Address, Uint<256>);
+
+// Decode from bytes
+let decoded: (Address, U256) = TransferParams::abi_decode_params(&encoded_data)
+ .map_err(|_| "decode error")?;
+```
+
+### Packed encoding
+
+Packed encoding is useful for hashing and signature verification:
+
+```rust
+use stylus_sdk::alloy_primitives::{Address, U256};
+use alloy_sol_types::{sol_data::*, SolType};
+
+// Method 1: Using SolType::abi_encode_packed
+type DataTypes = (Address, Uint<256>, String, Bytes, Uint<256>);
+let data = (target, value, func, bytes, timestamp);
+let packed = DataTypes::abi_encode_packed(&data);
+
+// Method 2: Manual concatenation
+let packed_manual = [
+ target.as_ref(),
+ &value.to_be_bytes_vec(),
+ func.as_bytes(),
+ bytes.as_ref(),
+ ×tamp.to_be_bytes_vec(),
+].concat();
+```
+
+## Error type conversions
+
+Stylus error types can be converted using the `Into` trait:
+
+```rust
+use stylus_sdk::prelude::*;
+
+sol! {
+ error InvalidParam();
+ error NotFound();
+}
+
+#[derive(SolidityError)]
+pub enum MyError {
+ InvalidParam(InvalidParam),
+ NotFound(NotFound),
+}
+
+pub fn check_value(&self, value: U256) -> Result<(), MyError> {
+ if value == U256::ZERO {
+ return Err(InvalidParam {}.into());
+ }
+ Ok(())
+}
+```
+
+## Storage type conversions
+
+Storage types require special handling for persistence:
+
+```rust
+use stylus_sdk::prelude::*;
+use stylus_sdk::alloy_primitives::U256;
+
+#[storage]
+pub struct Counter {
+ count: StorageU256,
+}
+
+#[public]
+impl Counter {
+ // Get value from storage
+ pub fn get_count(&self) -> U256 {
+ self.count.get()
+ }
+
+ // Set value in storage
+ pub fn set_count(&mut self, value: U256) {
+ self.count.set(value);
+ }
+
+ // Increment using arithmetic
+ pub fn increment(&mut self) {
+ let current = self.count.get();
+ self.count.set(current + U256::from(1));
+ }
+}
+```
+
+## Best practices
+
+1. **Use try_from for fallible conversions**: When converting between types where overflow is possible, use `try_from` instead of panicking conversions.
+
+2. **Prefer native types when appropriate**: Use Rust's native `bool`, `u8`-`u128`, and `i8`-`i128` types when they match your needs exactly. They're more efficient and ergonomic.
+
+3. **Be explicit about byte types**: Remember that `Vec` maps to `uint8[]`, not `bytes`. Use `Bytes` from `stylus_sdk::abi` for Solidity `bytes` type.
+
+4. **Use the address! macro**: For hardcoded addresses, use the `address!` macro which performs compile-time validation and checksumming.
+
+5. **Handle conversion errors**: Always handle potential errors from `try_from`, `try_into`, and `parse` operations rather than using unwrap in production code.
+
+6. **Consider packed encoding for hashing**: When preparing data for hashing or signature verification, packed encoding produces more compact representations.
+
+## Reference
+
+For complete implementation details, see:
+
+- `[/stylus-sdk/src/abi/mod.rs](https://github.com/OffchainLabs/stylus-sdk-rs/blob/main/stylus-sdk/src/abi/mod.rs)` - AbiType trait and encoding functions
+- `[/stylus-sdk/src/abi/ints.rs](https://github.com/OffchainLabs/stylus-sdk-rs/blob/main/stylus-sdk/src/abi/ints.rs)` - Integer type conversions
+- `[/stylus-sdk/src/abi/impls.rs](https://github.com/OffchainLabs/stylus-sdk-rs/blob/main/stylus-sdk/src/abi/impls.rs)` - Implementations for standard types
+- `[/stylus-sdk/src/storage/traits.rs](https://github.com/OffchainLabs/stylus-sdk-rs/blob/main/stylus-sdk/src/abi/traits.rs)` - Storage type conversion traits
diff --git a/docs/stylus/reference/data-types/primitives.mdx b/docs/stylus/reference/data-types/primitives.mdx
new file mode 100644
index 0000000000..0ec5f86ddc
--- /dev/null
+++ b/docs/stylus/reference/data-types/primitives.mdx
@@ -0,0 +1,564 @@
+---
+title: 'Stylus primitives'
+description: 'Stylus Rust SDK primitives'
+author: chrisco
+sme: chrisco
+sidebar_position: 1
+target_audience: Developers using the Stylus Rust SDK to write and deploy smart contracts.
+displayed_sidebar: buildStylusSidebar
+---
+
+The Stylus SDK provides full support for Rust primitive types with automatic ABI encoding/decoding and Solidity type mappings. These primitives can be used in contract method signatures, storage, and as function parameters.
+
+## Boolean (`bool`)
+
+Booleans in Rust map directly to Solidity's `bool` type.
+
+### Usage in contract methods
+
+```rust
+use stylus_sdk::prelude::*;
+
+#[public]
+impl MyContract {
+ pub fn is_valid(&self) -> bool {
+ true
+ }
+
+ pub fn toggle(&mut self, flag: bool) {
+ // Use the boolean value
+ if flag {
+ // Do something
+ }
+ }
+}
+```
+
+### Solidity mapping
+
+- **Rust type**: `bool`
+- **Solidity type**: `bool`
+- **Storage size**: 1 byte
+- **ABI signature**: `"bool"`
+
+## Integers
+
+The Stylus SDK supports both signed and unsigned integers with various bit sizes. All integer types from Rust's standard library and alloy-primitives are supported.
+
+### Unsigned integers
+
+#### Standard Rust unsigned integers
+
+```rust
+use stylus_sdk::prelude::*;
+
+#[public]
+impl MyContract {
+ // u8: 8-bit unsigned integer
+ pub fn get_byte(&self) -> u8 {
+ 255
+ }
+
+ // u16: 16-bit unsigned integer
+ pub fn get_short(&self) -> u16 {
+ 65535
+ }
+
+ // u32: 32-bit unsigned integer
+ pub fn get_int(&self) -> u32 {
+ 4294967295
+ }
+
+ // u64: 64-bit unsigned integer
+ pub fn get_long(&self) -> u64 {
+ 18446744073709551615
+ }
+
+ // u128: 128-bit unsigned integer
+ pub fn get_u128(&self) -> u128 {
+ 340282366920938463463374607431768211455
+ }
+}
+```
+
+#### Alloy unsigned integers
+
+For larger integers and full compatibility with Solidity's uint types, use `alloy_primitives::Uint`:
+
+```rust
+use alloy_primitives::{U256, Uint};
+use stylus_sdk::prelude::*;
+
+#[public]
+impl MyContract {
+ // U256: 256-bit unsigned integer (most common in Solidity)
+ pub fn get_balance(&self) -> U256 {
+ U256::from(1000000)
+ }
+
+ // Any bit size from 8 to 256 (in multiples of 8)
+ pub fn get_u160(&self) -> Uint<160, 3> {
+ Uint::<160, 3>::from(999)
+ }
+
+ pub fn get_u96(&self) -> Uint<96, 2> {
+ Uint::<96, 2>::from(123456)
+ }
+}
+```
+
+### Signed integers
+
+#### Standard Rust signed integers
+
+```rust
+use stylus_sdk::prelude::*;
+
+#[public]
+impl MyContract {
+ // i8: 8-bit signed integer
+ pub fn get_signed_byte(&self) -> i8 {
+ -128
+ }
+
+ // i16: 16-bit signed integer
+ pub fn get_signed_short(&self) -> i16 {
+ -32768
+ }
+
+ // i32: 32-bit signed integer
+ pub fn get_signed_int(&self) -> i32 {
+ -2147483648
+ }
+
+ // i64: 64-bit signed integer
+ pub fn get_signed_long(&self) -> i64 {
+ -9223372036854775808
+ }
+
+ // i128: 128-bit signed integer
+ pub fn get_signed_i128(&self) -> i128 {
+ -170141183460469231731687303715884105728
+ }
+}
+```
+
+#### Alloy signed integers
+
+```rust
+use alloy_primitives::{Signed, I256};
+use stylus_sdk::prelude::*;
+
+#[public]
+impl MyContract {
+ // I256: 256-bit signed integer
+ pub fn get_signed_balance(&self) -> I256 {
+ I256::try_from(-1000).unwrap()
+ }
+
+ // Any bit size from 8 to 256 (in multiples of 8)
+ pub fn get_i160(&self) -> Signed<160, 3> {
+ Signed::<160, 3>::try_from(-999).unwrap()
+ }
+}
+```
+
+### Integer type mappings
+
+| Rust Type | Solidity Type | Bit Size | ABI Signature |
+| ------------------------- | ------------- | -------- | ------------- |
+| `u8` | `uint8` | 8 bits | `"uint8"` |
+| `u16` | `uint16` | 16 bits | `"uint16"` |
+| `u32` | `uint32` | 32 bits | `"uint32"` |
+| `u64` | `uint64` | 64 bits | `"uint64"` |
+| `u128` | `uint128` | 128 bits | `"uint128"` |
+| `Uint<160, 3>` | `uint160` | 160 bits | `"uint160"` |
+| `U256` / `Uint<256, 4>` | `uint256` | 256 bits | `"uint256"` |
+| `i8` | `int8` | 8 bits | `"int8"` |
+| `i16` | `int16` | 16 bits | `"int16"` |
+| `i32` | `int32` | 32 bits | `"int32"` |
+| `i64` | `int64` | 64 bits | `"int64"` |
+| `i128` | `int128` | 128 bits | `"int128"` |
+| `Signed<160, 3>` | `int160` | 160 bits | `"int160"` |
+| `I256` / `Signed<256, 4>` | `int256` | 256 bits | `"int256"` |
+
+**Note**: All Solidity uint/int types from `uint8`/`int8` to `uint256`/`int256` (in 8-bit increments) are supported through `Uint` and `Signed`.
+
+## Address
+
+Ethereum addresses are represented by the `Address` type from `alloy_primitives`.
+
+### Basic usage
+
+```rust
+use alloy_primitives::Address;
+use stylus_sdk::prelude::*;
+
+#[public]
+impl MyContract {
+ pub fn get_owner(&self) -> Address {
+ Address::ZERO
+ }
+
+ pub fn is_owner(&self, account: Address) -> bool {
+ account == self.vm().msg_sender()
+ }
+
+ pub fn transfer_ownership(&mut self, new_owner: Address) {
+ // Address validation and logic
+ if new_owner == Address::ZERO {
+ // Handle error
+ }
+ }
+}
+```
+
+### Address constants
+
+```rust
+use alloy_primitives::Address;
+
+// Zero address (0x0000000000000000000000000000000000000000)
+let zero = Address::ZERO;
+
+// Parse from string
+let addr = Address::parse_checksummed("0x1234567890123456789012345678901234567890", None).unwrap();
+
+// Create from bytes
+let bytes: [u8; 20] = [0; 20];
+let addr = Address::from(bytes);
+```
+
+### Solidity mapping
+
+- **Rust type**: `Address` (from `alloy_primitives`)
+- **Solidity type**: `address`
+- **Storage size**: 20 bytes (160 bits)
+- **ABI signature**: `"address"`
+
+## String
+
+Rust `String` types map to Solidity `string` type.
+
+### String literals
+
+```rust
+use stylus_sdk::prelude::*;
+use alloc::string::String;
+
+#[public]
+impl MyContract {
+ pub fn get_name(&self) -> String {
+ String::from("MyToken")
+ }
+
+ pub fn greet(&self, name: String) -> String {
+ format!("Hello, {}!", name)
+ }
+}
+```
+
+### Solidity mapping
+
+- **Rust type**: `String` (from `alloc::string`)
+- **Solidity type**: `string`
+- **Storage**: Dynamic (heap-allocated)
+- **ABI signature**: `"string"`
+- **ABI export**:
+ - As argument: `"string calldata"`
+ - As return: `"string memory"`
+
+**Note**: Strings in Solidity are UTF-8 encoded byte arrays. When using strings in Stylus:
+
+- Use `alloc::string::String` for owned strings
+- Strings are dynamically sized and stored in memory/calldata
+- For storage, use `StorageString` (see [Storage Types](./storage.mdx))
+
+## Bytes
+
+The SDK provides two types for working with byte data:
+
+### 1. Dynamic bytes (`Bytes`)
+
+For variable-length byte arrays (Solidity `bytes`):
+
+```rust
+use alloy_primitives::Bytes;
+use stylus_sdk::prelude::*;
+
+#[public]
+impl MyContract {
+ pub fn get_data(&self) -> Bytes {
+ Bytes::from(vec![1, 2, 3, 4])
+ }
+
+ pub fn process_data(&mut self, data: Bytes) -> usize {
+ data.len()
+ }
+}
+```
+
+### 2. Fixed bytes (`FixedBytes`)
+
+For fixed-length byte arrays (Solidity `bytesN`):
+
+```rust
+use alloy_primitives::FixedBytes;
+use stylus_sdk::prelude::*;
+
+#[public]
+impl MyContract {
+ // bytes32 (common for hashes)
+ pub fn get_hash(&self) -> FixedBytes<32> {
+ FixedBytes::<32>::ZERO
+ }
+
+ // bytes2
+ pub fn get_signature(&self) -> FixedBytes<2> {
+ FixedBytes::new([0x12, 0x34])
+ }
+
+ // Any size from 1 to 32
+ pub fn get_bytes8(&self) -> FixedBytes<8> {
+ FixedBytes::<8>::from([1, 2, 3, 4, 5, 6, 7, 8])
+ }
+}
+```
+
+### Common FixedBytes aliases
+
+```rust
+use alloy_primitives::{B256, B160, B128};
+
+// B256 is FixedBytes<32> (bytes32)
+let hash: B256 = B256::ZERO;
+
+// B160 is FixedBytes<20> (bytes20)
+let data: B160 = B160::ZERO;
+
+// B128 is FixedBytes<16> (bytes16)
+let value: B128 = B128::ZERO;
+```
+
+### Bytes type mappings
+
+| Rust Type | Solidity Type | Description |
+| ------------------------- | ------------- | -------------------------------- |
+| `Bytes` | `bytes` | Dynamic byte array |
+| `Vec` | `uint8[]` | Array of bytes (NOT `bytes`!) |
+| `FixedBytes` | `bytesN` | Fixed-size byte array (N = 1-32) |
+| `B256` / `FixedBytes<32>` | `bytes32` | 32-byte array (hashes) |
+| `B160` / `FixedBytes<20>` | `bytes20` | 20-byte array |
+| `B128` / `FixedBytes<16>` | `bytes16` | 16-byte array |
+
+**Important Distinction**:
+
+- `Vec` maps to Solidity `uint8[]` (array of unsigned integers)
+- `Bytes` maps to Solidity `bytes` (dynamic byte array)
+- For Solidity `bytes`, always use `alloy_primitives::Bytes`
+
+### Bytes ABI encoding
+
+```rust
+// Bytes type
+// ABI signature: "bytes"
+// As argument: "bytes calldata"
+// As return: "bytes memory"
+
+// FixedBytes type
+// ABI signature: "bytesN" where N is 1-32
+// Example: FixedBytes<32> -> "bytes32"
+```
+
+## Hex string literals
+
+When working with hex data, you can use hex literals:
+
+```rust
+use alloy_primitives::{hex, Address, FixedBytes, Bytes};
+
+// Hex bytes
+let data = hex!("deadbeef");
+
+// Address from hex
+let addr = Address::from(hex!("1234567890123456789012345678901234567890"));
+
+// FixedBytes from hex
+let hash = FixedBytes::<32>::from(hex!(
+ "0000000000000000000000000000000000000000000000000000000000000000"
+));
+
+// Dynamic Bytes from hex
+let bytes = Bytes::from(hex!("aabbccdd"));
+```
+
+## Complete example
+
+Here's a comprehensive example showing all primitive types:
+
+```rust
+#![cfg_attr(not(any(test, feature = "export-abi")), no_main)]
+extern crate alloc;
+
+use alloc::string::String;
+use alloy_primitives::{Address, Bytes, FixedBytes, U256};
+use stylus_sdk::prelude::*;
+
+sol_storage! {
+ #[entrypoint]
+ pub struct PrimitiveExample {
+ bool initialized;
+ uint256 count;
+ address owner;
+ }
+}
+
+#[public]
+impl PrimitiveExample {
+ // Boolean
+ pub fn is_initialized(&self) -> bool {
+ self.initialized.get()
+ }
+
+ // Unsigned integers (native Rust)
+ pub fn get_u8(&self) -> u8 {
+ 255
+ }
+
+ pub fn get_u256(&self) -> U256 {
+ self.count.get()
+ }
+
+ // Signed integers
+ pub fn get_signed(&self) -> i32 {
+ -42
+ }
+
+ // Address
+ pub fn get_owner(&self) -> Address {
+ self.owner.get()
+ }
+
+ pub fn set_owner(&mut self, new_owner: Address) {
+ self.owner.set(new_owner);
+ }
+
+ // String
+ pub fn get_name(&self) -> String {
+ String::from("PrimitiveExample")
+ }
+
+ // Dynamic bytes
+ pub fn get_data(&self) -> Bytes {
+ Bytes::from(vec![1, 2, 3, 4])
+ }
+
+ // Fixed bytes
+ pub fn get_hash(&self) -> FixedBytes<32> {
+ FixedBytes::<32>::ZERO
+ }
+
+ // Multiple parameters
+ pub fn complex_function(
+ &mut self,
+ flag: bool,
+ amount: U256,
+ recipient: Address,
+ data: Bytes
+ ) -> bool {
+ // Function logic
+ true
+ }
+}
+```
+
+## Best practices
+
+1. **Use U256 for token amounts**: Solidity commonly uses `uint256` for token balances and amounts.
+
+ ```rust
+ use alloy_primitives::U256;
+
+ pub fn transfer(&mut self, amount: U256) {
+ // amount is uint256 in Solidity
+ }
+ ```
+
+2. **Use Address for account addresses**: Always use `alloy_primitives::Address` for Ethereum addresses.
+
+ ```rust
+ use alloy_primitives::Address;
+
+ pub fn get_balance(&self, account: Address) -> U256 {
+ // Query balance
+ }
+ ```
+
+3. **Use Bytes for dynamic byte data**: For Solidity `bytes`, use `alloy_primitives::Bytes`, not `Vec`.
+
+ ```rust
+ use alloy_primitives::Bytes;
+
+ pub fn process(&self, data: Bytes) {
+ // data maps to Solidity bytes
+ }
+ ```
+
+4. **Use FixedBytes for hashes and signatures**: For fixed-size byte data like hashes.
+
+ ```rust
+ use alloy_primitives::FixedBytes;
+
+ pub fn verify(&self, hash: FixedBytes<32>) -> bool {
+ // hash maps to Solidity bytes32
+ true
+ }
+ ```
+
+5. **Check for zero addresses**: Always validate addresses before use.
+
+ ```rust
+ use alloy_primitives::Address;
+
+ pub fn set_admin(&mut self, admin: Address) {
+ if admin == Address::ZERO {
+ // Handle error
+ }
+ }
+ ```
+
+## Type conversion
+
+### Between integer types
+
+```rust
+use alloy_primitives::U256;
+
+// Native to U256
+let amount: u64 = 1000;
+let big_amount = U256::from(amount);
+
+// U256 to native (with bounds checking)
+let big_value = U256::from(1000);
+let small_value: u64 = big_value.to::();
+```
+
+### Address conversions
+
+```rust
+use alloy_primitives::Address;
+
+// From bytes
+let bytes: [u8; 20] = [0; 20];
+let addr = Address::from(bytes);
+
+// To bytes
+let addr = Address::ZERO;
+let bytes: [u8; 20] = addr.into();
+```
+
+## See also
+
+- [Compound Types](./compound-types.mdx) - Arrays, tuples, structs
+- [Storage Types](./storage.mdx) - Persistent storage for primitives
+- [Type Conversions](./conversions-between-types.mdx) - Converting between types
diff --git a/docs/stylus/reference/data-types/storage.mdx b/docs/stylus/reference/data-types/storage.mdx
new file mode 100644
index 0000000000..1de74c4d0b
--- /dev/null
+++ b/docs/stylus/reference/data-types/storage.mdx
@@ -0,0 +1,1097 @@
+---
+title: 'Stylus Rust SDK storage'
+description: 'Stylus Rust SDK storage types'
+author: anegg0
+sme: anegg0
+sidebar_position: 3
+target_audience: Developers using the Stylus Rust SDK to write and deploy smart contracts.
+displayed_sidebar: buildStylusSidebar
+---
+
+Persistent storage in Stylus contracts provides access to the EVM State Trie, the same key-value storage used by Solidity contracts. The SDK provides type-safe storage access through dedicated storage types that prevent aliasing errors at compile time using Rust's borrow checker.
+
+## Overview
+
+Stylus contracts share the same persistent storage as Solidity contracts:
+
+- Both Stylus and Solidity access the same EVM State Trie
+- Storage is fully interoperable between Stylus and Solidity contracts
+- Stylus provides compile-time safety through Rust's type system
+- Storage operations are cached for gas efficiency
+
+### Storage declaration
+
+Use the `sol_storage!` macro to define contract storage with Solidity-compatible layout:
+
+```rust
+use stylus_sdk::prelude::*;
+
+sol_storage! {
+ #[entrypoint]
+ pub struct MyContract {
+ uint256 count;
+ address owner;
+ bool initialized;
+ }
+}
+```
+
+Alternatively, use the `#[storage]` attribute for Rust-style declarations:
+
+```rust
+use stylus_sdk::prelude::*;
+use stylus_sdk::storage::*;
+
+#[storage]
+#[entrypoint]
+pub struct MyContract {
+ count: StorageU256,
+ owner: StorageAddress,
+ initialized: StorageBool,
+}
+```
+
+## Storage primitives
+
+Storage primitives are persistent versions of basic types.
+
+### Boolean storage (`StorageBool`)
+
+Store boolean values in persistent storage:
+
+```rust
+use stylus_sdk::prelude::*;
+
+sol_storage! {
+ #[entrypoint]
+ pub struct Contract {
+ bool is_initialized;
+ bool is_paused;
+ }
+}
+
+#[public]
+impl Contract {
+ pub fn initialize(&mut self) {
+ self.is_initialized.set(true);
+ }
+
+ pub fn is_initialized(&self) -> bool {
+ self.is_initialized.get()
+ }
+
+ pub fn toggle_pause(&mut self) {
+ let current = self.is_paused.get();
+ self.is_paused.set(!current);
+ }
+}
+```
+
+### Integer storage
+
+Store unsigned and signed integers with various bit sizes:
+
+```rust
+use stylus_sdk::prelude::*;
+use alloy_primitives::U256;
+
+sol_storage! {
+ #[entrypoint]
+ pub struct Counter {
+ uint256 count;
+ uint64 timestamp;
+ int256 balance;
+ }
+}
+
+#[public]
+impl Counter {
+ // Unsigned integer operations
+ pub fn increment(&mut self) {
+ let current = self.count.get();
+ self.count.set(current + U256::from(1));
+ }
+
+ pub fn add(&mut self, value: U256) {
+ let current = self.count.get();
+ self.count.set(current + value);
+ }
+
+ pub fn get_count(&self) -> U256 {
+ self.count.get()
+ }
+
+ // Timestamp storage
+ pub fn set_timestamp(&mut self, ts: u64) {
+ self.timestamp.set(ts);
+ }
+}
+```
+
+#### Available storage integer types
+
+| Storage Type | Primitive Type | Bit Size | Solidity Type |
+| ------------- | -------------- | -------- | ------------- |
+| `StorageU8` | `U8` | 8 bits | `uint8` |
+| `StorageU16` | `U16` | 16 bits | `uint16` |
+| `StorageU32` | `U32` | 32 bits | `uint32` |
+| `StorageU64` | `U64` | 64 bits | `uint64` |
+| `StorageU128` | `U128` | 128 bits | `uint128` |
+| `StorageU256` | `U256` | 256 bits | `uint256` |
+| `StorageI8` | `I8` | 8 bits | `int8` |
+| `StorageI16` | `I16` | 16 bits | `int16` |
+| `StorageI32` | `I32` | 32 bits | `int32` |
+| `StorageI64` | `I64` | 64 bits | `int64` |
+| `StorageI128` | `I128` | 128 bits | `int128` |
+| `StorageI256` | `I256` | 256 bits | `int256` |
+
+#### Integer update operations
+
+`StorageUint` types provide convenient update methods:
+
+```rust
+use stylus_sdk::prelude::*;
+use alloy_primitives::U256;
+
+sol_storage! {
+ #[entrypoint]
+ pub struct Contract {
+ uint256 balance;
+ }
+}
+
+#[public]
+impl Contract {
+ // Wrapping operations (overflow wraps around)
+ pub fn add_wrapping(&mut self, value: U256) -> U256 {
+ self.balance.update_wrap_add(value)
+ }
+
+ pub fn sub_wrapping(&mut self, value: U256) -> U256 {
+ self.balance.update_wrap_sub(value)
+ }
+
+ pub fn mul_wrapping(&mut self, value: U256) -> U256 {
+ self.balance.update_wrap_mul(value)
+ }
+
+ // Checked operations (return None on overflow)
+ pub fn add_checked(&mut self, value: U256) -> Option {
+ self.balance.update_check_add(value)
+ }
+
+ pub fn sub_checked(&mut self, value: U256) -> Option {
+ self.balance.update_check_sub(value)
+ }
+}
+```
+
+### Address storage (`StorageAddress`)
+
+Store Ethereum addresses:
+
+```rust
+use stylus_sdk::prelude::*;
+use alloy_primitives::Address;
+
+sol_storage! {
+ #[entrypoint]
+ pub struct Ownership {
+ address owner;
+ address pending_owner;
+ }
+}
+
+#[public]
+impl Ownership {
+ pub fn get_owner(&self) -> Address {
+ self.owner.get()
+ }
+
+ pub fn transfer_ownership(&mut self, new_owner: Address) {
+ // Validate address
+ if new_owner == Address::ZERO {
+ // Handle error
+ return;
+ }
+
+ let current_owner = self.owner.get();
+ if self.vm().msg_sender() != current_owner {
+ // Not authorized
+ return;
+ }
+
+ self.pending_owner.set(new_owner);
+ }
+
+ pub fn accept_ownership(&mut self) {
+ let caller = self.vm().msg_sender();
+ if caller != self.pending_owner.get() {
+ return;
+ }
+
+ self.owner.set(caller);
+ self.pending_owner.set(Address::ZERO);
+ }
+}
+```
+
+### Fixed bytes storage
+
+Store fixed-size byte arrays:
+
+```rust
+use stylus_sdk::prelude::*;
+use alloy_primitives::FixedBytes;
+
+sol_storage! {
+ #[entrypoint]
+ pub struct Hashes {
+ bytes32 merkle_root;
+ bytes32 commitment;
+ bytes4 selector;
+ }
+}
+
+#[public]
+impl Hashes {
+ pub fn set_merkle_root(&mut self, root: FixedBytes<32>) {
+ self.merkle_root.set(root);
+ }
+
+ pub fn get_merkle_root(&self) -> FixedBytes<32> {
+ self.merkle_root.get()
+ }
+
+ pub fn verify_hash(&self, proof: FixedBytes<32>) -> bool {
+ self.merkle_root.get() == proof
+ }
+}
+```
+
+#### Available fixed bytes storage types
+
+| Storage Type | Bytes | Bits | Solidity Type |
+| ------------- | ----- | -------- | ------------- |
+| `StorageB8` | 1 | 8 bits | `bytes1` |
+| `StorageB16` | 2 | 16 bits | `bytes2` |
+| `StorageB32` | 4 | 32 bits | `bytes4` |
+| `StorageB64` | 8 | 64 bits | `bytes8` |
+| `StorageB128` | 16 | 128 bits | `bytes16` |
+| `StorageB160` | 20 | 160 bits | `bytes20` |
+| `StorageB224` | 28 | 224 bits | `bytes28` |
+| `StorageB256` | 32 | 256 bits | `bytes32` |
+
+## Storage collections
+
+Storage collections provide persistent arrays, vectors, and maps.
+
+### StorageVec (dynamic array)
+
+Dynamic arrays that can grow and shrink:
+
+```rust
+use stylus_sdk::prelude::*;
+use alloy_primitives::{Address, U256};
+
+sol_storage! {
+ #[entrypoint]
+ pub struct TokenList {
+ address[] holders;
+ uint256[] balances;
+ }
+}
+
+#[public]
+impl TokenList {
+ // Add element
+ pub fn add_holder(&mut self, holder: Address) {
+ self.holders.push(holder);
+ }
+
+ // Get element
+ pub fn get_holder(&self, index: U256) -> Address {
+ self.holders.get(index).unwrap()
+ }
+
+ // Get length
+ pub fn holder_count(&self) -> U256 {
+ U256::from(self.holders.len())
+ }
+
+ // Set element
+ pub fn set_balance(&mut self, index: U256, balance: U256) {
+ self.balances.setter(index).unwrap().set(balance);
+ }
+
+ // Iterate over elements
+ pub fn total_balance(&self) -> U256 {
+ let mut total = U256::ZERO;
+ for i in 0..self.balances.len() {
+ total += self.balances.get(U256::from(i)).unwrap();
+ }
+ total
+ }
+
+ // Remove element (erase to zero)
+ pub fn remove_holder(&mut self, index: U256) {
+ self.holders.setter(index).unwrap().erase();
+ }
+
+ // Clear all elements
+ pub fn clear_holders(&mut self) {
+ self.holders.erase();
+ }
+}
+```
+
+#### StorageVec methods
+
+```rust
+// Length operations
+fn len(&self) -> usize
+fn is_empty(&self) -> bool
+
+// Access operations
+fn get(&self, index: impl TryInto) -> Option
+fn getter(&self, index: impl TryInto) -> Option>
+fn setter(&mut self, index: impl TryInto) -> Option>
+
+// Mutation operations
+fn push(&mut self, value: T)
+fn grow(&mut self) -> StorageGuardMut<'_, T> // Add new element and return mutable reference
+fn erase(&mut self) // Clear all elements
+```
+
+### StorageArray (fixed array)
+
+Fixed-size arrays with compile-time known length:
+
+```rust
+use stylus_sdk::prelude::*;
+use alloy_primitives::U256;
+
+sol_storage! {
+ #[entrypoint]
+ pub struct FixedData {
+ uint256[10] values;
+ address[5] admins;
+ }
+}
+
+#[public]
+impl FixedData {
+ // Get element
+ pub fn get_value(&self, index: U256) -> U256 {
+ self.values.get(index).unwrap()
+ }
+
+ // Set element
+ pub fn set_value(&mut self, index: U256, value: U256) {
+ self.values.setter(index).unwrap().set(value);
+ }
+
+ // Get array length (compile-time constant)
+ pub fn array_size(&self) -> U256 {
+ U256::from(self.values.len())
+ }
+
+ // Iterate over array
+ pub fn sum_values(&self) -> U256 {
+ let mut sum = U256::ZERO;
+ for i in 0..self.values.len() {
+ sum += self.values.get(U256::from(i)).unwrap();
+ }
+ sum
+ }
+}
+```
+
+### StorageMap (mapping)
+
+Key-value storage, equivalent to Solidity `mapping`:
+
+```rust
+use stylus_sdk::prelude::*;
+use alloy_primitives::{Address, U256};
+
+sol_storage! {
+ #[entrypoint]
+ pub struct Token {
+ mapping(address => uint256) balances;
+ mapping(address => mapping(address => uint256)) allowances;
+ }
+}
+
+#[public]
+impl Token {
+ // Get value (returns zero if not set)
+ pub fn balance_of(&self, account: Address) -> U256 {
+ self.balances.get(account)
+ }
+
+ // Set value
+ pub fn set_balance(&mut self, account: Address, amount: U256) {
+ self.balances.setter(account).set(amount);
+ }
+
+ // Insert value (same as set)
+ pub fn mint(&mut self, account: Address, amount: U256) {
+ let current = self.balances.get(account);
+ self.balances.insert(account, current + amount);
+ }
+
+ // Delete value (reset to zero)
+ pub fn burn(&mut self, account: Address, amount: U256) {
+ let current = self.balances.get(account);
+ if current >= amount {
+ self.balances.setter(account).set(current - amount);
+ }
+ }
+
+ // Nested mapping
+ pub fn allowance(&self, owner: Address, spender: Address) -> U256 {
+ self.allowances.get(owner).get(spender)
+ }
+
+ pub fn approve(&mut self, spender: Address, amount: U256) {
+ let owner = self.vm().msg_sender();
+ self.allowances
+ .setter(owner)
+ .setter(spender)
+ .set(amount);
+ }
+}
+```
+
+#### StorageMap methods
+
+```rust
+// Read operations
+fn get(&self, key: K) -> V // Returns zero-value if not present
+fn getter(&self, key: K) -> StorageGuard<'_, V>
+
+// Write operations
+fn setter(&mut self, key: K) -> StorageGuardMut<'_, V>
+fn insert(&mut self, key: K, value: V)
+fn replace(&mut self, key: K, value: V) -> V // Returns old value
+fn take(&mut self, key: K) -> V // Returns value and deletes
+fn delete(&mut self, key: K) // Erases entry
+```
+
+#### Supported map key types
+
+Any type implementing `StorageKey` can be used as a map key:
+
+- `Address`
+- `U256`, `U160`, and other `Uint` types
+- `FixedBytes`
+- `Signed` types
+- `bool`
+
+```rust
+use stylus_sdk::prelude::*;
+use alloy_primitives::{Address, U256, FixedBytes};
+
+sol_storage! {
+ #[entrypoint]
+ pub struct MultiMap {
+ mapping(address => uint256) by_address;
+ mapping(uint256 => address) by_id;
+ mapping(bytes32 => bool) by_hash;
+ mapping(bool => uint256) by_flag;
+ }
+}
+```
+
+### StorageString and StorageBytes
+
+Dynamic string and bytes storage:
+
+```rust
+use stylus_sdk::prelude::*;
+use alloc::string::String;
+
+sol_storage! {
+ #[entrypoint]
+ pub struct Metadata {
+ string name;
+ string symbol;
+ bytes data;
+ }
+}
+
+#[public]
+impl Metadata {
+ // String operations
+ pub fn get_name(&self) -> String {
+ self.name.get_string()
+ }
+
+ pub fn set_name(&mut self, name: String) {
+ self.name.set_str(name);
+ }
+
+ pub fn name_length(&self) -> usize {
+ self.name.len()
+ }
+
+ pub fn clear_name(&mut self) {
+ self.name.erase();
+ }
+
+ // Bytes operations
+ pub fn get_data(&self) -> Vec {
+ self.data.get_bytes()
+ }
+
+ pub fn set_data(&mut self, data: Vec) {
+ self.data.set_bytes(data);
+ }
+
+ pub fn data_length(&self) -> usize {
+ self.data.len()
+ }
+}
+```
+
+## Storage structs
+
+Define custom storage types with nested structures:
+
+```rust
+use stylus_sdk::prelude::*;
+use alloy_primitives::{Address, U256};
+
+// Storage struct definition
+#[storage]
+pub struct UserInfo {
+ balance: StorageU256,
+ is_active: StorageBool,
+ timestamp: StorageU64,
+}
+
+sol_storage! {
+ #[entrypoint]
+ pub struct UserRegistry {
+ mapping(address => UserInfo) users;
+ uint256 total_users;
+ }
+}
+
+#[public]
+impl UserRegistry {
+ pub fn register_user(&mut self, user: Address) {
+ let mut user_info = self.users.setter(user);
+ user_info.balance.set(U256::ZERO);
+ user_info.is_active.set(true);
+ user_info.timestamp.set(self.vm().block_timestamp());
+
+ let count = self.total_users.get();
+ self.total_users.set(count + U256::from(1));
+ }
+
+ pub fn get_balance(&self, user: Address) -> U256 {
+ self.users.get(user).balance.get()
+ }
+
+ pub fn update_balance(&mut self, user: Address, amount: U256) {
+ self.users.setter(user).balance.set(amount);
+ }
+
+ pub fn is_active(&self, user: Address) -> bool {
+ self.users.get(user).is_active.get()
+ }
+}
+```
+
+### Nested storage structs
+
+```rust
+use stylus_sdk::prelude::*;
+use alloy_primitives::Address;
+
+#[storage]
+pub struct Dog {
+ name: StorageString,
+ breed: StorageString,
+}
+
+#[storage]
+pub struct User {
+ name: StorageString,
+ dogs: StorageVec,
+}
+
+sol_storage! {
+ #[entrypoint]
+ pub struct Registry {
+ mapping(address => User) users;
+ }
+}
+
+#[public]
+impl Registry {
+ pub fn add_dog(&mut self, owner: Address, name: String, breed: String) {
+ let mut user = self.users.setter(owner);
+ let mut dog = user.dogs.grow();
+ dog.name.set_str(name);
+ dog.breed.set_str(breed);
+ }
+
+ pub fn get_dog_count(&self, owner: Address) -> usize {
+ self.users.get(owner).dogs.len()
+ }
+
+ pub fn get_dog_name(&self, owner: Address, index: usize) -> String {
+ self.users
+ .get(owner)
+ .dogs
+ .get(index)
+ .unwrap()
+ .name
+ .get_string()
+ }
+}
+```
+
+## Storage patterns
+
+### Initialization pattern
+
+```rust
+use stylus_sdk::prelude::*;
+use alloy_primitives::{Address, U256};
+
+sol_storage! {
+ #[entrypoint]
+ pub struct Contract {
+ bool initialized;
+ address owner;
+ uint256 value;
+ }
+}
+
+#[public]
+impl Contract {
+ #[constructor]
+ pub fn constructor(&mut self, initial_value: U256) {
+ self.owner.set(self.vm().msg_sender());
+ self.value.set(initial_value);
+ self.initialized.set(true);
+ }
+
+ fn only_initialized(&self) {
+ if !self.initialized.get() {
+ // Revert: not initialized
+ }
+ }
+
+ pub fn get_value(&self) -> U256 {
+ self.only_initialized();
+ self.value.get()
+ }
+}
+```
+
+### Counter pattern
+
+```rust
+use stylus_sdk::prelude::*;
+use alloy_primitives::U256;
+
+sol_storage! {
+ #[entrypoint]
+ pub struct Counter {
+ uint256 count;
+ mapping(address => uint256) user_counts;
+ }
+}
+
+#[public]
+impl Counter {
+ pub fn increment(&mut self) {
+ let current = self.count.get();
+ self.count.set(current + U256::from(1));
+ }
+
+ pub fn increment_by(&mut self, amount: U256) {
+ let current = self.count.get();
+ self.count.set(current + amount);
+ }
+
+ pub fn increment_user(&mut self) {
+ let user = self.vm().msg_sender();
+ let current = self.user_counts.get(user);
+ self.user_counts.insert(user, current + U256::from(1));
+ }
+
+ pub fn get_count(&self) -> U256 {
+ self.count.get()
+ }
+
+ pub fn get_user_count(&self, user: Address) -> U256 {
+ self.user_counts.get(user)
+ }
+}
+```
+
+### Access control pattern
+
+```rust
+use stylus_sdk::prelude::*;
+use alloy_primitives::Address;
+
+sol_storage! {
+ #[entrypoint]
+ pub struct AccessControl {
+ address owner;
+ mapping(address => bool) admins;
+ mapping(address => bool) users;
+ }
+}
+
+#[public]
+impl AccessControl {
+ #[constructor]
+ pub fn constructor(&mut self) {
+ let sender = self.vm().msg_sender();
+ self.owner.set(sender);
+ self.admins.insert(sender, true);
+ }
+
+ fn only_owner(&self) {
+ if self.vm().msg_sender() != self.owner.get() {
+ // Revert: not owner
+ }
+ }
+
+ fn only_admin(&self) {
+ let sender = self.vm().msg_sender();
+ if !self.admins.get(sender) {
+ // Revert: not admin
+ }
+ }
+
+ pub fn add_admin(&mut self, admin: Address) {
+ self.only_owner();
+ self.admins.insert(admin, true);
+ }
+
+ pub fn remove_admin(&mut self, admin: Address) {
+ self.only_owner();
+ self.admins.delete(admin);
+ }
+
+ pub fn add_user(&mut self, user: Address) {
+ self.only_admin();
+ self.users.insert(user, true);
+ }
+
+ pub fn is_admin(&self, account: Address) -> bool {
+ self.admins.get(account)
+ }
+
+ pub fn is_user(&self, account: Address) -> bool {
+ self.users.get(account)
+ }
+}
+```
+
+### Registry pattern
+
+```rust
+use stylus_sdk::prelude::*;
+use alloy_primitives::{Address, U256};
+
+#[storage]
+pub struct Record {
+ owner: StorageAddress,
+ created_at: StorageU64,
+ updated_at: StorageU64,
+ active: StorageBool,
+}
+
+sol_storage! {
+ #[entrypoint]
+ pub struct Registry {
+ mapping(bytes32 => Record) records;
+ mapping(address => bytes32[]) user_records;
+ uint256 total_records;
+ }
+}
+
+#[public]
+impl Registry {
+ pub fn create_record(&mut self, id: FixedBytes<32>) {
+ let now = self.vm().block_timestamp();
+ let owner = self.vm().msg_sender();
+
+ let mut record = self.records.setter(id);
+ record.owner.set(owner);
+ record.created_at.set(now);
+ record.updated_at.set(now);
+ record.active.set(true);
+
+ // Add to user's record list
+ self.user_records.setter(owner).push(id);
+
+ // Increment total
+ let total = self.total_records.get();
+ self.total_records.set(total + U256::from(1));
+ }
+
+ pub fn get_record_owner(&self, id: FixedBytes<32>) -> Address {
+ self.records.get(id).owner.get()
+ }
+
+ pub fn is_active(&self, id: FixedBytes<32>) -> bool {
+ self.records.get(id).active.get()
+ }
+
+ pub fn deactivate(&mut self, id: FixedBytes<32>) {
+ let owner = self.records.get(id).owner.get();
+ if owner != self.vm().msg_sender() {
+ // Not authorized
+ return;
+ }
+
+ self.records.setter(id).active.set(false);
+ }
+}
+```
+
+## Best practices
+
+### 1. Use appropriate storage types
+
+```rust
+// Good: Use StorageU256 for counters
+sol_storage! {
+ pub struct Counter {
+ uint256 count;
+ }
+}
+
+// Good: Use StorageMap for lookups
+sol_storage! {
+ pub struct Balances {
+ mapping(address => uint256) balances;
+ }
+}
+
+// Good: Use StorageVec for dynamic lists
+sol_storage! {
+ pub struct Users {
+ address[] user_list;
+ }
+}
+```
+
+### 2. Minimize storage operations
+
+```rust
+// Bad: Multiple storage reads
+pub fn bad_example(&self) -> U256 {
+ let a = self.value.get();
+ let b = self.value.get(); // Unnecessary read
+ a + b
+}
+
+// Good: Single storage read
+pub fn good_example(&self) -> U256 {
+ let value = self.value.get();
+ value + value
+}
+```
+
+### 3. Use batch operations
+
+```rust
+// Good: Batch updates in a single transaction
+pub fn update_multiple(&mut self, values: Vec) {
+ for (i, value) in values.iter().enumerate() {
+ self.data.setter(U256::from(i)).unwrap().set(*value);
+ }
+}
+```
+
+### 4. Check before delete
+
+```rust
+// Good: Verify before deletion
+pub fn remove_user(&mut self, user: Address) {
+ if self.users.get(user) {
+ self.users.delete(user);
+ // Update related storage
+ }
+}
+```
+
+### 5. Use erase for gas refunds
+
+```rust
+// Good: Clear storage for gas refunds
+pub fn clear_data(&mut self) {
+ self.data.erase(); // Refunds gas
+}
+```
+
+## Storage slots and layout
+
+Stylus uses the same storage layout as Solidity:
+
+- Each storage slot is **32 bytes** (256 bits)
+- Variables are **packed** when possible to save space
+- Arrays and mappings use **computed slots** via hashing
+
+```rust
+sol_storage! {
+ pub struct Packed {
+ uint128 a; // Slot 0 (first 16 bytes)
+ uint128 b; // Slot 0 (last 16 bytes)
+ uint256 c; // Slot 1 (full 32 bytes)
+ bool d; // Slot 2 (1 byte)
+ address e; // Slot 2 (20 bytes, packed with d)
+ }
+}
+```
+
+### Custom storage slots
+
+You can specify custom storage slots for specific use cases:
+
+```rust
+use stylus_sdk::prelude::*;
+use alloy_primitives::U256;
+
+#[storage]
+#[entrypoint]
+pub struct CustomSlots {
+ // Default slot allocation
+ value: StorageU256,
+
+ // Custom slot (advanced usage)
+ // Note: Requires manual slot management
+}
+```
+
+## Complete example
+
+Here's a comprehensive example demonstrating various storage types:
+
+```rust
+#![cfg_attr(not(any(test, feature = "export-abi")), no_main)]
+extern crate alloc;
+
+use alloc::string::String;
+use alloy_primitives::{Address, FixedBytes, U256};
+use stylus_sdk::prelude::*;
+
+#[storage]
+pub struct TokenMetadata {
+ name: StorageString,
+ symbol: StorageString,
+ decimals: StorageU8,
+}
+
+sol_storage! {
+ #[entrypoint]
+ pub struct Token {
+ // Primitives
+ uint256 total_supply;
+ bool paused;
+ address owner;
+
+ // Collections
+ mapping(address => uint256) balances;
+ mapping(address => mapping(address => uint256)) allowances;
+ address[] holders;
+
+ // Nested struct
+ TokenMetadata metadata;
+ }
+}
+
+#[public]
+impl Token {
+ #[constructor]
+ pub fn constructor(&mut self, name: String, symbol: String) {
+ self.owner.set(self.vm().msg_sender());
+ self.metadata.name.set_str(name);
+ self.metadata.symbol.set_str(symbol);
+ self.metadata.decimals.set(18);
+ self.paused.set(false);
+ }
+
+ pub fn total_supply(&self) -> U256 {
+ self.total_supply.get()
+ }
+
+ pub fn balance_of(&self, account: Address) -> U256 {
+ self.balances.get(account)
+ }
+
+ pub fn transfer(&mut self, to: Address, amount: U256) -> bool {
+ if self.paused.get() {
+ return false;
+ }
+
+ let from = self.vm().msg_sender();
+ let from_balance = self.balances.get(from);
+
+ if from_balance < amount {
+ return false;
+ }
+
+ self.balances.insert(from, from_balance - amount);
+
+ let to_balance = self.balances.get(to);
+ self.balances.insert(to, to_balance + amount);
+
+ true
+ }
+
+ pub fn approve(&mut self, spender: Address, amount: U256) -> bool {
+ let owner = self.vm().msg_sender();
+ self.allowances.setter(owner).insert(spender, amount);
+ true
+ }
+
+ pub fn allowance(&self, owner: Address, spender: Address) -> U256 {
+ self.allowances.get(owner).get(spender)
+ }
+
+ pub fn pause(&mut self) {
+ if self.vm().msg_sender() != self.owner.get() {
+ return;
+ }
+ self.paused.set(true);
+ }
+
+ pub fn unpause(&mut self) {
+ if self.vm().msg_sender() != self.owner.get() {
+ return;
+ }
+ self.paused.set(false);
+ }
+
+ pub fn name(&self) -> String {
+ self.metadata.name.get_string()
+ }
+
+ pub fn symbol(&self) -> String {
+ self.metadata.symbol.get_string()
+ }
+
+ pub fn decimals(&self) -> u8 {
+ self.metadata.decimals.get()
+ }
+}
+```
+
+## See also
+
+- [Primitives](./primitives.mdx) - Basic types used in storage
+- [Compound Types](./compound-types.mdx) - Complex types in storage
+- [Type Conversions](./conversions-between-types.mdx) - Converting between types
diff --git a/docs/stylus/reference/global-variables-and-functions.mdx b/docs/stylus/reference/global-variables-and-functions.mdx
new file mode 100644
index 0000000000..8d0bafbb19
--- /dev/null
+++ b/docs/stylus/reference/global-variables-and-functions.mdx
@@ -0,0 +1,894 @@
+---
+title: 'Global variables and functions'
+description: 'Stylus Rust SDK global variables and functions for blockchain context access'
+author: chrisco
+sme: chrisco
+sidebar_position: 3
+target_audience: Developers using the Stylus Rust SDK to write and deploy smart contracts.
+displayed_sidebar: buildStylusSidebar
+---
+
+Stylus contracts access blockchain context and utilities through the VM (Virtual Machine) interface via `self.vm()`. This provides access to message context, block information, transaction details, account data, cryptographic functions, and gas metering.
+
+## Accessing the VM
+
+All public contract methods have access to the VM context through `self.vm()`:
+
+```rust
+use stylus_sdk::prelude::*;
+use alloy_primitives::{Address, U256};
+
+#[public]
+impl MyContract {
+ pub fn get_context_info(&self) -> (Address, U256, u64) {
+ let vm = self.vm();
+ (
+ vm.msg_sender(), // Caller's address
+ vm.msg_value(), // ETH sent with call
+ vm.block_number(), // Current block number
+ )
+ }
+}
+```
+
+The VM provides methods organized into several categories:
+
+## Message Context (`msg`)
+
+Methods for accessing information about the current call.
+
+### `msg_sender()`
+
+Gets the address of the account that called the program.
+
+```rust
+pub fn msg_sender(&self) -> Address
+```
+
+**Equivalent to**: Solidity's `msg.sender`
+
+**Example:**
+
+```rust
+#[public]
+impl Token {
+ pub fn transfer(&mut self, to: Address, amount: U256) -> bool {
+ let from = self.vm().msg_sender();
+ // Transfer from the caller to recipient
+ self._transfer(from, to, amount)
+ }
+}
+```
+
+**Important Notes:**
+
+- For normal L2-to-L2 transactions, behaves like EVM's `CALLER` opcode
+- For L1-to-L2 retryable ticket transactions, the top-level sender's address will be [aliased](https://developer.arbitrum.io/arbos/l1-to-l2-messaging#address-aliasing)
+- In delegate calls, returns the original caller (not the delegating contract)
+
+### `msg_value()`
+
+Gets the ETH value in wei sent to the program.
+
+```rust
+pub fn msg_value(&self) -> U256
+```
+
+**Equivalent to**: Solidity's `msg.value`
+
+**Example:**
+
+```rust
+#[public]
+impl PaymentContract {
+ #[payable]
+ pub fn deposit(&mut self) -> U256 {
+ let amount = self.vm().msg_value();
+ let sender = self.vm().msg_sender();
+
+ let balance = self.balances.get(sender);
+ self.balances.setter(sender).set(balance + amount);
+
+ amount
+ }
+}
+```
+
+**Note:** Only functions marked with `#[payable]` can receive ETH. Non-payable functions will revert if `msg_value() > 0`.
+
+### `msg_reentrant()`
+
+Checks whether the current call is reentrant.
+
+```rust
+pub fn msg_reentrant(&self) -> bool
+```
+
+**Example:**
+
+```rust
+#[public]
+impl Vault {
+ pub fn withdraw(&mut self, amount: U256) {
+ if self.vm().msg_reentrant() {
+ // Handle reentrancy
+ panic!("Reentrant call detected");
+ }
+ // Withdrawal logic...
+ }
+}
+```
+
+**Note:** By default, Stylus contracts prevent reentrancy unless the `reentrant` feature is enabled.
+
+## Transaction Context (`tx`)
+
+Methods for accessing information about the current transaction.
+
+### `tx_origin()`
+
+Gets the top-level sender of the transaction.
+
+```rust
+pub fn tx_origin(&self) -> Address
+```
+
+**Equivalent to**: Solidity's `tx.origin`
+
+**Example:**
+
+```rust
+#[public]
+impl Factory {
+ #[constructor]
+ pub fn constructor(&mut self) {
+ // Use tx_origin when deploying via a factory
+ let deployer = self.vm().tx_origin();
+ self.owner.set(deployer);
+ }
+}
+```
+
+**Important:** Returns the original EOA (Externally Owned Account) that initiated the transaction, even through multiple contract calls.
+
+### `tx_gas_price()`
+
+Gets the gas price in wei per gas, which on Arbitrum chains equals the basefee.
+
+```rust
+pub fn tx_gas_price(&self) -> U256
+```
+
+**Equivalent to**: Solidity's `tx.gasprice`
+
+**Example:**
+
+```rust
+#[public]
+impl Analytics {
+ pub fn record_gas_price(&mut self) {
+ let price = self.vm().tx_gas_price();
+ self.gas_prices.push(price);
+ }
+}
+```
+
+### `tx_ink_price()`
+
+Gets the price of ink in EVM gas basis points.
+
+```rust
+pub fn tx_ink_price(&self) -> u32
+```
+
+**Description:** Stylus uses "ink" as its unit of computation. This method returns the conversion rate from ink to gas. See [Ink and Gas](https://docs.arbitrum.io/stylus/concepts/gas-metering) for more information.
+
+**Example:**
+
+```rust
+#[public]
+impl Contract {
+ pub fn get_ink_price(&self) -> u32 {
+ self.vm().tx_ink_price()
+ }
+}
+```
+
+## Block Context (`block`)
+
+Methods for accessing information about the current block.
+
+### `block_number()`
+
+Gets a bounded estimate of the L1 block number at which the Sequencer sequenced the transaction.
+
+```rust
+pub fn block_number(&self) -> u64
+```
+
+**Equivalent to**: Solidity's `block.number`
+
+**Example:**
+
+```rust
+#[public]
+impl TimeLock {
+ pub fn lock_until(&mut self, blocks: u64) {
+ let unlock_block = self.vm().block_number() + blocks;
+ self.unlock_block.set(U256::from(unlock_block));
+ }
+
+ pub fn can_unlock(&self) -> bool {
+ let current = self.vm().block_number();
+ let unlock = self.unlock_block.get().try_into().unwrap_or(u64::MAX);
+ current >= unlock
+ }
+}
+```
+
+**Note:** See [Block Numbers and Time](https://developer.arbitrum.io/time) for more information on how this value is determined on Arbitrum.
+
+### `block_timestamp()`
+
+Gets a bounded estimate of the Unix timestamp at which the Sequencer sequenced the transaction.
+
+```rust
+pub fn block_timestamp(&self) -> u64
+```
+
+**Equivalent to**: Solidity's `block.timestamp`
+
+**Example:**
+
+```rust
+#[public]
+impl Auction {
+ pub fn place_bid(&mut self, amount: U256) {
+ let now = self.vm().block_timestamp();
+ let deadline = self.deadline.get().try_into().unwrap_or(0);
+
+ if now > deadline {
+ panic!("Auction ended");
+ }
+
+ // Process bid...
+ }
+}
+```
+
+**Note:** See [Block Numbers and Time](https://developer.arbitrum.io/time) for more information on how this value is determined on Arbitrum.
+
+### `block_basefee()`
+
+Gets the basefee of the current block.
+
+```rust
+pub fn block_basefee(&self) -> U256
+```
+
+**Equivalent to**: Solidity's `block.basefee`
+
+**Example:**
+
+```rust
+#[public]
+impl FeeTracker {
+ pub fn current_basefee(&self) -> U256 {
+ self.vm().block_basefee()
+ }
+}
+```
+
+### `block_coinbase()`
+
+Gets the coinbase of the current block.
+
+```rust
+pub fn block_coinbase(&self) -> Address
+```
+
+**Equivalent to**: Solidity's `block.coinbase`
+
+**Important:** On Arbitrum chains, this is the L1 batch poster's address, which differs from Ethereum where the validator determines the coinbase.
+
+**Example:**
+
+```rust
+#[public]
+impl Contract {
+ pub fn get_batch_poster(&self) -> Address {
+ self.vm().block_coinbase()
+ }
+}
+```
+
+### `block_gas_limit()`
+
+Gets the gas limit of the current block.
+
+```rust
+pub fn block_gas_limit(&self) -> u64
+```
+
+**Equivalent to**: Solidity's `block.gaslimit`
+
+**Example:**
+
+```rust
+#[public]
+impl Contract {
+ pub fn check_gas_limit(&self) -> bool {
+ let limit = self.vm().block_gas_limit();
+ limit > 30_000_000
+ }
+}
+```
+
+## Chain Context
+
+Methods for accessing chain-specific information.
+
+### `chain_id()`
+
+Gets the unique chain identifier of the Arbitrum chain.
+
+```rust
+pub fn chain_id(&self) -> u64
+```
+
+**Equivalent to**: Solidity's `block.chainid`
+
+**Example:**
+
+```rust
+#[public]
+impl MultiChain {
+ pub fn verify_chain(&self, expected_chain: u64) -> bool {
+ self.vm().chain_id() == expected_chain
+ }
+}
+```
+
+**Common Arbitrum Chain IDs:**
+
+- Arbitrum One: 42161
+- Arbitrum Nova: 42170
+- Arbitrum Sepolia (testnet): 421614
+
+## Account Information
+
+Methods for querying account details.
+
+### `contract_address()`
+
+Gets the address of the current program.
+
+```rust
+pub fn contract_address(&self) -> Address
+```
+
+**Equivalent to**: Solidity's `address(this)`
+
+**Example:**
+
+```rust
+#[public]
+impl Contract {
+ pub fn this_address(&self) -> Address {
+ self.vm().contract_address()
+ }
+
+ pub fn this_balance(&self) -> U256 {
+ let addr = self.vm().contract_address();
+ self.vm().balance(addr)
+ }
+}
+```
+
+### `balance(address)`
+
+Gets the ETH balance in wei of the account at the given address.
+
+```rust
+pub fn balance(&self, account: Address) -> U256
+```
+
+**Equivalent to**: Solidity's `address.balance`
+
+**Example:**
+
+```rust
+#[public]
+impl BalanceChecker {
+ pub fn get_balance(&self, account: Address) -> U256 {
+ self.vm().balance(account)
+ }
+
+ pub fn has_sufficient_balance(&self, account: Address, required: U256) -> bool {
+ self.vm().balance(account) >= required
+ }
+}
+```
+
+### `code(address)`
+
+Gets the code from the account at the given address.
+
+```rust
+pub fn code(&self, account: Address) -> Vec