diff --git a/docs/tutorials/factory/0-introduction.md b/docs/tutorials/factory/0-introduction.md
index 9ec7f46fd9e..8eea06af213 100644
--- a/docs/tutorials/factory/0-introduction.md
+++ b/docs/tutorials/factory/0-introduction.md
@@ -56,4 +56,4 @@ Before we begin, it's important to understand NEAR's account creation rules:
This means your factory at `factory.testnet` can create `dao1.factory.testnet` but cannot create `dao1.alice.testnet`.
-Ready to get started? Let's [build your first factory contract](1-factory-contract.md)!
\ No newline at end of file
+Ready to get started? Let's [build your first factory contract](1-factory-contract.md)!
diff --git a/docs/tutorials/factory/1-setup.md b/docs/tutorials/factory/1-setup.md
new file mode 100644
index 00000000000..e5a2d4e9ae8
--- /dev/null
+++ b/docs/tutorials/factory/1-setup.md
@@ -0,0 +1,133 @@
+---
+id: setup
+title: Project Setup and Structure
+sidebar_label: Project Setup
+---
+
+import {Github} from "@site/src/components/codetabs"
+
+Let's set up a factory contract project that can deploy contracts using NEAR's global contracts feature.
+
+## Create the Project
+
+Initialize a new Rust project:
+
+```bash
+cargo new factory-contract --lib
+cd factory-contract
+```
+
+## Configure Cargo.toml
+
+Set up the dependencies and build configuration:
+
+
+
+Key dependencies:
+- `near-sdk` with `global-contracts` feature enables global contract functionality
+- `bs58` for decoding base58-encoded code hashes
+- `near-workspaces` for integration testing
+
+## Rust Toolchain
+
+Configure the Rust toolchain for WASM compilation:
+
+
+
+## Project Structure
+
+Create the following file structure:
+
+```
+factory-contract/
+├── Cargo.toml
+├── rust-toolchain.toml
+├── src/
+│ ├── lib.rs # Main contract logic
+│ └── manager.rs # Configuration management
+└── tests/
+ └── workspaces.rs # Integration tests
+```
+
+## Contract State
+
+Define the contract state that stores the global contract reference:
+
+
+
+The `GlobalContractId` enum allows referencing a global contract in two ways:
+- **AccountId**: Reference the account that deployed the global contract
+- **CodeHash**: Reference the global contract by its code hash directly
+
+## Initialize the Factory
+
+Set up the default state:
+
+
+
+By default:
+- References the global Fungible Token contract on testnet
+- Requires minimum 0.2 NEAR deposit for sub-account creation
+
+## Build the Contract
+
+Compile the contract to WASM:
+
+```bash
+cargo near build
+```
+
+This creates an optimized WASM file in `target/near/`.
+
+## Deploy to Testnet
+
+Create a testnet account and deploy:
+
+```bash
+# Create a dev account
+cargo near create-dev-account
+
+# Deploy the contract
+cargo near deploy
+```
+
+Your factory is now deployed and ready to create sub-accounts!
+
+## Understanding Global Contract References
+
+### By Account ID
+
+When you reference a global contract by account ID:
+
+```rust
+GlobalContractId::AccountId("ft.globals.primitives.testnet".parse().unwrap())
+```
+
+The factory will look up the most recent global contract deployed by that account.
+
+### By Code Hash
+
+When you reference a global contract by hash:
+
+```rust
+GlobalContractId::CodeHash("3vaopJ7aRoivvzZLngPQRBEd8VJr2zPLTxQfnRCoFgNX".to_string())
+```
+
+The factory directly uses that specific contract code, regardless of who deployed it.
+
+:::tip Which to Use?
+
+- **AccountId**: Use when you trust the deployer and want automatic updates
+- **CodeHash**: Use when you need a specific, immutable version of the contract
+
+:::
+
+Next, we'll implement the core deployment functionality that creates sub-accounts and deploys contracts.
\ No newline at end of file
diff --git a/docs/tutorials/factory/2-deploy.md b/docs/tutorials/factory/2-deploy.md
new file mode 100644
index 00000000000..c6dcffca4ab
--- /dev/null
+++ b/docs/tutorials/factory/2-deploy.md
@@ -0,0 +1,171 @@
+---
+id: deploy
+title: Implementing the Deploy Method
+sidebar_label: Deploy Contracts
+---
+
+import {Github} from "@site/src/components/codetabs"
+
+The `deploy` method is the core of the factory pattern - it creates a sub-account and deploys a contract onto it using global contracts.
+
+## The Complete Deploy Method
+
+
+
+Let's break down each part of this method.
+
+## Validating the Deposit
+
+
+
+The attached deposit must cover:
+- Account creation and storage
+- Initial balance for the new sub-account to initialize the contract
+
+:::info Why Require Deposit?
+
+While deploying via global contracts doesn't cost storage, the new contract needs tokens to:
+- Cover storage when initialized with state
+- Pay for future transactions
+- Maintain minimum account balance
+
+:::
+
+## Creating the Sub-Account
+
+
+
+Key points:
+- Sub-accounts must follow the pattern `name.factory-account.testnet`
+- The factory can **only** create its own sub-accounts
+- Account ID validation ensures the name is valid
+
+### Account Creation Limitations
+
+The factory:
+- ✅ **Can** create `sub.factory.testnet` if it is `factory.testnet`
+- ❌ **Cannot** create sub-accounts for other accounts
+- ❌ **Cannot** create top-level accounts like `newaccount.testnet`
+
+## Building the Promise Chain
+
+
+
+The promise chain:
+1. **create_account**: Creates the new sub-account
+2. **transfer**: Moves attached deposit to the new account
+3. **add_full_access_key**: Adds the signer's public key so they control the account
+
+:::tip Full Access Key
+
+Adding the signer's public key as a full access key ensures the caller can manage their new sub-account immediately after creation.
+
+:::
+
+## Deploying the Global Contract
+
+The final step differs based on how the global contract is referenced:
+
+### Deploy by Account ID
+
+
+
+Uses `use_global_contract_by_account_id()` to deploy the most recent global contract from that account.
+
+### Deploy by Code Hash
+
+
+
+Uses `use_global_contract()` with the decoded base58 hash to deploy that specific contract version.
+
+## Using the Deploy Method
+
+Deploy a new contract instance:
+
+```bash
+near contract call-function as-transaction deploy \
+ json-args '{"name": "my-token"}' \
+ prepaid-gas '100.0 Tgas' \
+ attached-deposit '0.2 NEAR' \
+ sign-as \
+ network-config testnet \
+ sign-with-keychain send
+```
+
+This creates `my-token.` with the global contract deployed on it.
+
+## Initialize the Deployed Contract
+
+After deployment, initialize the contract. For the default FT contract:
+
+```bash
+near contract call-function as-transaction my-token. \
+ new_default_meta \
+ json-args '{
+ "owner_id": "",
+ "total_supply": "100000000000000000000000000"
+ }' \
+ prepaid-gas '100.0 Tgas' \
+ attached-deposit '0 NEAR' \
+ sign-as \
+ network-config testnet \
+ sign-with-keychain send
+```
+
+## Verify the Deployment
+
+Check that the contract is working:
+
+```bash
+near contract call-function as-read-only \
+ my-token. ft_metadata \
+ json-args {} \
+ network-config testnet now
+```
+
+Expected response:
+
+```json
+{
+ "decimals": 24,
+ "name": "Example NEAR fungible token",
+ "symbol": "EXAMPLE",
+ "spec": "ft-1.0.0"
+}
+```
+
+## Promise Chain Execution
+
+The entire operation executes atomically:
+
+1. If account creation fails → entire operation reverts
+2. If transfer fails → entire operation reverts
+3. If key addition fails → entire operation reverts
+4. If global contract deployment fails → entire operation reverts
+
+This ensures you never end up with a partially created account.
+
+## Cost Savings
+
+Using global contracts dramatically reduces costs:
+
+| Deployment Method | Storage Cost per Instance |
+|------------------|---------------------------|
+| Traditional | ~3 NEAR (full bytecode) |
+| Global Contracts | ~0.2 NEAR (just state) |
+
+For 100 instances, this saves **~280 NEAR**!
+
+Next, we'll implement management functions to update the global contract reference and configuration.
\ No newline at end of file
diff --git a/docs/tutorials/factory/3-manager.md b/docs/tutorials/factory/3-manager.md
new file mode 100644
index 00000000000..cb8ef83a092
--- /dev/null
+++ b/docs/tutorials/factory/3-manager.md
@@ -0,0 +1,205 @@
+---
+id: manager
+title: Managing Factory Configuration
+sidebar_label: Configuration Management
+---
+
+import {Github} from "@site/src/components/codetabs"
+
+The factory needs management functions to update which global contract it deploys and configure deployment parameters.
+
+## Manager Module
+
+Create a separate module for management functions:
+
+
+
+## Update Global Contract Reference
+
+The `update_global_contract_id` method allows changing which contract the factory deploys:
+
+
+
+The `#[private]` macro restricts this method - only the contract account itself can call it.
+
+### Switch to Account-Based Reference
+
+Update the factory to deploy from a specific account:
+
+```bash
+near contract call-function as-transaction \
+ update_global_contract_id \
+ json-args '{
+ "contract_id": {
+ "AccountId": "ft.globals.primitives.testnet"
+ },
+ "min_deposit": "200000000000000000000000"
+ }' \
+ prepaid-gas '100.0 Tgas' \
+ attached-deposit '0 NEAR' \
+ sign-as \
+ network-config testnet \
+ sign-with-keychain send
+```
+
+### Switch to Hash-Based Reference
+
+Update the factory to deploy a specific contract version:
+
+```bash
+near contract call-function as-transaction \
+ update_global_contract_id \
+ json-args '{
+ "contract_id": {
+ "CodeHash": "3vaopJ7aRoivvzZLngPQRBEd8VJr2zPLTxQfnRCoFgNX"
+ },
+ "min_deposit": "200000000000000000000000"
+ }' \
+ prepaid-gas '100.0 Tgas' \
+ attached-deposit '0 NEAR' \
+ sign-as \
+ network-config testnet \
+ sign-with-keychain send
+```
+
+:::caution Private Method
+
+Only the factory contract account can update its configuration. If you try to call this from another account, the transaction will fail with "Method is private".
+
+:::
+
+## Query Current Configuration
+
+### Get Global Contract Reference
+
+
+
+Check which global contract the factory is currently using:
+
+```bash
+near contract call-function as-read-only \
+ get_global_contract_id \
+ json-args {} \
+ network-config testnet now
+```
+
+Response examples:
+
+```json
+// Account-based reference
+{
+ "AccountId": "ft.globals.primitives.testnet"
+}
+
+// Hash-based reference
+{
+ "CodeHash": "3vaopJ7aRoivvzZLngPQRBEd8VJr2zPLTxQfnRCoFgNX"
+}
+```
+
+### Get Minimum Deposit
+
+
+
+Check the minimum required deposit:
+
+```bash
+near contract call-function as-read-only \
+ get_min_deposit \
+ json-args {} \
+ network-config testnet now
+```
+
+Response:
+```json
+"200000000000000000000000" // 0.2 NEAR in yoctoNEAR
+```
+
+## Complete Manager Implementation
+
+Include the manager module in your main contract file:
+
+```rust
+// In lib.rs
+mod manager;
+```
+
+This makes the management methods available on your contract instance.
+
+## Configuration Strategy
+
+Choose your configuration based on your needs:
+
+### Account-Based Reference
+**Best for:**
+- Trusting the deployer to maintain the global contract
+- Wanting automatic updates to the latest version
+- Flexibility to deploy new global contract versions
+
+**Example use case:** A protocol team manages a global contract and regularly updates it with new features.
+
+### Hash-Based Reference
+**Best for:**
+- Requiring a specific, immutable contract version
+- Security-critical applications
+- Audit requirements
+
+**Example use case:** A DAO deploys multiple instances and wants to ensure all use the exact same audited code.
+
+## Access Control Patterns
+
+The `#[private]` macro is simple but limited. For more complex access control:
+
+```rust
+#[near]
+impl GlobalFactoryContract {
+ // Only owner can update
+ pub fn update_global_contract_id(
+ &mut self,
+ contract_id: GlobalContractId,
+ min_deposit: NearToken
+ ) {
+ assert_eq!(
+ env::predecessor_account_id(),
+ self.owner_id,
+ "Only owner can update"
+ );
+ self.global_contract_id = contract_id;
+ self.min_deposit_amount = min_deposit;
+ }
+}
+```
+
+This pattern allows designating an owner separate from the contract account.
+
+## Updating Multiple Parameters
+
+You can extend the update method to handle more configuration:
+
+```rust
+pub struct FactoryConfig {
+ pub global_contract_id: GlobalContractId,
+ pub min_deposit: NearToken,
+ pub max_instances: u64,
+ pub enabled: bool,
+}
+
+#[private]
+pub fn update_config(&mut self, config: FactoryConfig) {
+ self.global_contract_id = config.global_contract_id;
+ self.min_deposit_amount = config.min_deposit;
+ // Update other fields...
+}
+```
+
+This keeps the management API clean while allowing comprehensive configuration updates.
+
+Next, we'll implement comprehensive tests to ensure the factory works correctly.
\ No newline at end of file
diff --git a/docs/tutorials/factory/4-testing.md b/docs/tutorials/factory/4-testing.md
new file mode 100644
index 00000000000..fe6c4c4332e
--- /dev/null
+++ b/docs/tutorials/factory/4-testing.md
@@ -0,0 +1,265 @@
+---
+id: testing
+title: Testing the Factory Contract
+sidebar_label: Testing
+---
+
+import {Github} from "@site/src/components/codetabs"
+
+Testing factory contracts requires verifying both management functions and the deployment process. We'll use `near-workspaces` for integration testing.
+
+## Test Setup
+
+Create `tests/workspaces.rs`:
+
+
+
+:::info Runtime Version
+
+Global contracts require nearcore 2.7.0 or later. The `sandbox_with_version("2.7.0")` ensures we're testing with the correct runtime.
+
+:::
+
+## Testing Management Functions
+
+Test that the factory correctly stores and updates configuration:
+
+
+
+### Verify Default Configuration
+
+After deployment, check the default global contract reference:
+
+
+
+### Update to Hash-Based Reference
+
+Test switching to a code hash reference:
+
+
+
+### Verify the Update
+
+Confirm the new configuration is stored:
+
+
+
+### Update to Account-Based Reference
+
+Test switching to an account reference:
+
+
+
+### Verify Minimum Deposit
+
+Check the minimum deposit configuration:
+
+
+
+## Testing Edge Cases
+
+Test error conditions and invalid inputs:
+
+
+
+This test verifies that using a non-existent global contract hash fails appropriately.
+
+## Running Tests
+
+Execute all tests:
+
+```bash
+# Run all tests
+cargo test
+
+# Run only integration tests
+cargo test --test workspaces
+
+# Run with output
+cargo test -- --nocapture
+```
+
+## Unit Tests
+
+You can also add unit tests directly in your contract modules:
+
+```rust
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use near_sdk::test_utils::{accounts, VMContextBuilder};
+ use near_sdk::testing_env;
+
+ #[test]
+ fn test_default_config() {
+ let context = VMContextBuilder::new()
+ .current_account_id(accounts(0))
+ .build();
+ testing_env!(context);
+
+ let contract = GlobalFactoryContract::default();
+
+ let expected = GlobalContractId::AccountId(
+ "ft.globals.primitives.testnet".parse().unwrap()
+ );
+ assert_eq!(contract.global_contract_id, expected);
+ assert_eq!(
+ contract.min_deposit_amount,
+ NearToken::from_millinear(200)
+ );
+ }
+
+ #[test]
+ #[should_panic(expected = "Attach at least")]
+ fn test_insufficient_deposit() {
+ let mut context = VMContextBuilder::new()
+ .current_account_id(accounts(0))
+ .attached_deposit(NearToken::from_millinear(100))
+ .build();
+ testing_env!(context);
+
+ let mut contract = GlobalFactoryContract::default();
+ contract.deploy("sub".to_string());
+ }
+}
+```
+
+## Testing Deployment (Manual)
+
+While `near-workspaces` doesn't yet support deploying global contracts in tests, you can manually test deployment:
+
+### 1. Deploy Factory
+
+```bash
+cargo near deploy
+```
+
+### 2. Deploy a Sub-Account
+
+```bash
+near contract call-function as-transaction deploy \
+ json-args '{"name": "test"}' \
+ prepaid-gas '100.0 Tgas' \
+ attached-deposit '0.2 NEAR' \
+ sign-as \
+ network-config testnet \
+ sign-with-keychain send
+```
+
+### 3. Verify Sub-Account Creation
+
+```bash
+near account view-account-summary test. \
+ network-config testnet
+```
+
+### 4. Initialize the Contract
+
+```bash
+near contract call-function as-transaction test. \
+ new_default_meta \
+ json-args '{
+ "owner_id": "",
+ "total_supply": "1000000000000000000000000"
+ }' \
+ prepaid-gas '100.0 Tgas' \
+ attached-deposit '0 NEAR' \
+ sign-as \
+ network-config testnet \
+ sign-with-keychain send
+```
+
+### 5. Test the Deployed Contract
+
+```bash
+near contract call-function as-read-only test. \
+ ft_metadata \
+ json-args {} \
+ network-config testnet now
+```
+
+## Continuous Integration
+
+Add GitHub Actions for automated testing:
+
+```yaml
+name: Test Factory Contract
+on: push
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Install Rust
+ uses: actions-rs/toolchain@v1
+ with:
+ toolchain: stable
+
+ - name: Install cargo-near
+ run: cargo install --locked cargo-near
+
+ - name: Run tests
+ run: cargo test
+```
+
+## Test Coverage
+
+Ensure comprehensive coverage:
+
+- ✅ Default configuration on deployment
+- ✅ Updating global contract reference (both types)
+- ✅ Updating minimum deposit
+- ✅ Querying configuration
+- ✅ Invalid global contract hash handling
+- ✅ Insufficient deposit handling
+- ✅ Invalid sub-account names
+- ✅ Access control (private methods)
+
+## Debugging Tips
+
+When tests fail:
+
+1. **Check runtime version**: Global contracts need nearcore 2.7.0+
+2. **Verify deposits**: Ensure attached deposits meet minimum requirements
+3. **Validate account names**: Sub-account names must be valid NEAR account IDs
+4. **Review logs**: Use `env::log_str` in your contract for debugging
+5. **Test incrementally**: Start with simple cases and add complexity
+
+## Summary
+
+You've learned how to:
+- Set up a factory contract project with global contracts support
+- Implement the deploy method to create sub-accounts with deployed contracts
+- Manage factory configuration and global contract references
+- Test the factory with both unit and integration tests
+
+This factory pattern enables efficient, scalable contract deployment across the NEAR ecosystem!
+
+:::tip Production Considerations
+
+Before deploying to mainnet:
+- Thoroughly test with realistic scenarios
+- Implement proper access control
+- Consider upgrade paths for the factory itself
+- Monitor gas costs and optimize where possible
+- Document the deployment and initialization process for users
+
+:::
\ No newline at end of file
diff --git a/docs/tutorials/frontend-multiple-contracts/0-introduction.md b/docs/tutorials/frontend-multiple-contracts/0-introduction.md
new file mode 100644
index 00000000000..e4c94e3ecdb
--- /dev/null
+++ b/docs/tutorials/frontend-multiple-contracts/0-introduction.md
@@ -0,0 +1,49 @@
+---
+id: introduction
+title: Connecting Frontend to Multiple Contracts
+sidebar_label: Introduction
+description: "Learn how to build a frontend application that interacts with multiple NEAR smart contracts simultaneously."
+---
+
+Building applications that interact with multiple smart contracts is a common pattern in Web3 development. This tutorial demonstrates how to query data from and send transactions to multiple NEAR contracts from a single frontend application.
+
+## What You'll Build
+
+A web application that simultaneously interacts with two deployed contracts:
+- **Hello NEAR** (`hello.near-examples.testnet`) - stores and retrieves greeting messages
+- **Guest Book** (`guestbook.near-examples.testnet`) - manages a list of messages with optional premium features
+
+
+
+## How It Works
+
+The application uses the NEAR Wallet Selector to authenticate users and enables them to:
+
+1. **View data** from both contracts simultaneously without authentication
+2. **Send transactions** to multiple contracts in a single wallet interaction
+3. **Batch actions** within the same contract for atomic operations
+
+:::info Repository
+
+The complete source code is available in the [GitHub repository](https://github.com/near-examples/frontend-multiple-contracts).
+
+The contracts are already deployed on testnet:
+- Hello NEAR: `hello.near-examples.testnet`
+- Guest Book: `guestbook.near-examples.testnet`
+
+:::
+
+## What You Will Learn
+
+- [Set up the project structure](1-setup.md) with wallet integration
+- [Query multiple contracts](2-query.md) to read data
+- [Send transactions to multiple contracts](3-transactions.md) simultaneously
+- [Batch actions within a contract](4-batch-actions.md) for atomic operations
+
+## Prerequisites
+
+- Basic knowledge of JavaScript/TypeScript
+- Understanding of NEAR accounts and transactions
+- Node.js and npm installed
+
+Let's get started by setting up the project!
\ No newline at end of file
diff --git a/docs/tutorials/frontend-multiple-contracts/1-setup.md b/docs/tutorials/frontend-multiple-contracts/1-setup.md
new file mode 100644
index 00000000000..08f994b8b61
--- /dev/null
+++ b/docs/tutorials/frontend-multiple-contracts/1-setup.md
@@ -0,0 +1,106 @@
+---
+id: setup
+title: Project Setup and Wallet Integration
+sidebar_label: Project Setup
+---
+
+import {Github} from "@site/src/components/codetabs"
+
+In this section, we'll set up the project structure and implement wallet integration using NEAR Wallet Selector.
+
+## Clone and Install
+
+Start by cloning the repository and installing dependencies:
+
+```bash
+git clone https://github.com/near-examples/frontend-multiple-contracts
+cd frontend-multiple-contracts
+npm install
+```
+
+## Project Structure
+
+The project has a simple structure:
+
+```
+frontend-multiple-contracts/
+├── frontend/
+│ ├── index.html
+│ ├── index.js
+│ └── wallet.js
+└── package.json
+```
+
+## Wallet Setup
+
+The `Wallet` class handles authentication and blockchain interactions. Let's examine the key components:
+
+### Initialize Wallet Selector
+
+
+
+The wallet selector supports multiple wallet providers (MyNearWallet, HereWallet) and manages the connection state.
+
+### Starting Up the Wallet
+
+
+
+The `startUp` method:
+- Initializes the wallet selector with testnet configuration
+- Checks if a user is already signed in
+- Sets up an observable to track account changes
+- Executes a callback function when authentication state changes
+
+### Sign In and Sign Out
+
+
+
+These methods display the wallet modal for authentication and handle logout.
+
+## Main Application Setup
+
+Configure your main application to use the wallet:
+
+
+
+Define the contract addresses you'll interact with and initialize the wallet instance.
+
+### Page Load Handler
+
+
+
+On page load:
+1. Start the wallet and handle authentication state
+2. Load data from both contracts
+
+## HTML Interface
+
+Set up the basic HTML structure:
+
+
+
+The interface displays greetings from Hello NEAR and messages from the Guest Book side by side.
+
+## Running the Application
+
+Start the development server:
+
+```bash
+npm start
+```
+
+The application opens at `http://localhost:1234`. You can now view data from both contracts, but you'll need to sign in to send transactions.
+
+Next, we'll implement querying data from multiple contracts simultaneously.
\ No newline at end of file
diff --git a/docs/tutorials/frontend-multiple-contracts/2-query.md b/docs/tutorials/frontend-multiple-contracts/2-query.md
new file mode 100644
index 00000000000..a37560f04d1
--- /dev/null
+++ b/docs/tutorials/frontend-multiple-contracts/2-query.md
@@ -0,0 +1,102 @@
+---
+id: query
+title: Querying Multiple Contracts
+sidebar_label: Query Data
+---
+
+import {Github} from "@site/src/components/codetabs"
+
+Reading data from multiple contracts is straightforward - simply make multiple view calls. No authentication is required for reading blockchain data.
+
+## The View Method
+
+The wallet's `viewMethod` function handles read-only calls to contracts:
+
+
+
+This method:
+- Connects to the NEAR RPC endpoint
+- Encodes the arguments in base64
+- Queries the contract method
+- Decodes and returns the result
+
+## Querying Multiple Contracts
+
+To read from multiple contracts, simply call `viewMethod` multiple times:
+
+
+
+### Breaking It Down
+
+**Query Hello NEAR contract:**
+```javascript
+const currentGreeting = await wallet.viewMethod({
+ method: 'get_greeting',
+ contractId: HELLO_ADDRESS
+});
+```
+
+**Query Guest Book contract:**
+```javascript
+// First, get total message count
+const totalMessages = await wallet.viewMethod({
+ method: 'total_messages',
+ contractId: GUEST_ADDRESS
+});
+
+// Then fetch the last 4 messages
+const from_index = (totalMessages > 4 ? totalMessages - 4 : 0).toString();
+const latestMessages = await wallet.viewMethod({
+ method: 'get_messages',
+ contractId: GUEST_ADDRESS,
+ args: { from_index, limit: "4" }
+});
+```
+
+## Updating the UI
+
+Once you have the data, update your interface:
+
+
+
+The `update_UI` function:
+1. Displays the current greeting from Hello NEAR
+2. Iterates through Guest Book messages
+3. Creates table rows with message details (sender, text, premium status)
+
+## Key Points
+
+- View calls don't require authentication
+- View calls don't cost gas fees
+- Multiple view calls execute independently
+- Failed view calls don't affect other queries
+- Results are returned as JSON
+
+## Testing
+
+You can test querying contracts directly in the browser console:
+
+```javascript
+// Query Hello NEAR
+const greeting = await wallet.viewMethod({
+ method: 'get_greeting',
+ contractId: 'hello.near-examples.testnet'
+});
+console.log(greeting);
+
+// Query Guest Book
+const messages = await wallet.viewMethod({
+ method: 'get_messages',
+ contractId: 'guestbook.near-examples.testnet',
+ args: { from_index: "0", limit: "10" }
+});
+console.log(messages);
+```
+
+Now that you can read data from multiple contracts, let's learn how to send transactions to them.
\ No newline at end of file
diff --git a/docs/tutorials/frontend-multiple-contracts/3-transactions.md b/docs/tutorials/frontend-multiple-contracts/3-transactions.md
new file mode 100644
index 00000000000..52b14408290
--- /dev/null
+++ b/docs/tutorials/frontend-multiple-contracts/3-transactions.md
@@ -0,0 +1,132 @@
+---
+id: transactions
+title: Sending Transactions to Multiple Contracts
+sidebar_label: Multiple Transactions
+---
+
+import {Github} from "@site/src/components/codetabs"
+
+NEAR allows you to dispatch multiple transactions simultaneously, so users only interact with their wallet once. However, these transactions remain **independent** - if one fails, the others are not rolled back.
+
+## Transaction Structure
+
+Each transaction targets a specific contract and contains one or more actions:
+
+
+
+## Building Transactions
+
+Let's break down the transaction structure:
+
+### Guest Book Transaction
+
+```javascript
+const guestTx = {
+ receiverId: GUEST_ADDRESS,
+ actions: [
+ {
+ type: 'FunctionCall',
+ params: {
+ methodName: 'add_message',
+ args: { text: greeting.value },
+ gas: THIRTY_TGAS,
+ deposit: GUEST_DEPOSIT
+ }
+ }
+ ]
+}
+```
+
+- `receiverId`: The contract account ID (`guestbook.near-examples.testnet`)
+- `actions`: Array of actions to execute on this contract
+- `methodName`: The contract method to call
+- `args`: Arguments passed to the method
+- `gas`: Gas limit (30 TGas = 30,000,000,000,000 gas units)
+- `deposit`: NEAR tokens to attach (0 for regular, 0.1Ⓝ for premium)
+
+### Hello NEAR Transaction
+
+```javascript
+const helloTx = {
+ receiverId: HELLO_ADDRESS,
+ actions: [
+ {
+ type: 'FunctionCall',
+ params: {
+ methodName: 'set_greeting',
+ args: { greeting: greeting.value },
+ gas: THIRTY_TGAS,
+ deposit: NO_DEPOSIT
+ }
+ }
+ ]
+}
+```
+
+Similar structure, but:
+- Targets a different contract (`hello.near-examples.testnet`)
+- Calls `set_greeting` method
+- No deposit required
+
+## Signing and Sending
+
+The wallet selector handles signing and broadcasting:
+
+
+
+```javascript
+await wallet.signAndSendTransactions({
+ transactions: [ helloTx, guestTx ]
+});
+```
+
+This opens the wallet once for the user to approve both transactions.
+
+## Important Characteristics
+
+:::caution Independence
+Even though transactions are signed together, they are **completely independent**:
+- If `helloTx` succeeds and `guestTx` fails, `helloTx` is **NOT** rolled back
+- Each transaction is processed separately by the network
+- Success of one does not guarantee success of another
+:::
+
+## Handling Transaction Responses
+
+After submission, you'll receive transaction outcomes:
+
+```javascript
+const outcome = await wallet.signAndSendTransactions({
+ transactions: [ helloTx, guestTx ]
+});
+
+// Each transaction has its own outcome
+console.log(outcome);
+```
+
+## Complete Form Handler
+
+
+
+The form handler:
+1. Prevents default form submission
+2. Extracts form values (greeting text and premium checkbox)
+3. Shows loading state
+4. Calculates deposit based on premium selection
+5. Constructs both transactions
+6. Submits them for signing
+
+## Testing Multiple Transactions
+
+Try these scenarios:
+1. **Both succeed**: Valid greeting text, sufficient balance
+2. **One fails**: Insufficient balance for premium message (Guest Book fails, Hello NEAR succeeds)
+3. **Network issues**: Transactions may process at different times
+
+Next, we'll explore batching actions within a single contract for atomic operations.
\ No newline at end of file
diff --git a/docs/tutorials/frontend-multiple-contracts/4-batch-actions.md b/docs/tutorials/frontend-multiple-contracts/4-batch-actions.md
new file mode 100644
index 00000000000..fb95707ea4a
--- /dev/null
+++ b/docs/tutorials/frontend-multiple-contracts/4-batch-actions.md
@@ -0,0 +1,196 @@
+---
+id: batch-actions
+title: Batching Actions Within a Contract
+sidebar_label: Batch Actions
+---
+
+While transactions to different contracts are independent, you can batch multiple actions targeting the **same contract** into a single transaction. If any action in the batch fails, they **all get reverted**.
+
+## Why Batch Actions?
+
+Batching ensures atomicity - either all actions succeed or none do. Common use cases include:
+
+- Registering a user and transferring tokens in one transaction
+- Approving and transferring NFTs atomically
+- Multiple sequential contract calls that must all succeed
+
+## Action Types
+
+NEAR supports several action types:
+- `FunctionCall` - Call a contract method
+- `Transfer` - Send NEAR tokens
+- `CreateAccount` - Create a new account
+- `AddKey` - Add an access key
+- `DeleteKey` - Remove an access key
+- `DeleteAccount` - Delete an account
+
+## Batching Actions Example
+
+Here's a practical example of registering a user for FT storage and transferring tokens:
+
+```javascript
+const REGISTER_DEPOSIT = "1250000000000000000000"; // 0.00125 NEAR
+const THIRTY_TGAS = "30000000000000";
+
+const ftTx = {
+ receiverId: "token.near",
+ actions: [
+ // First action: Register storage for receiver
+ {
+ type: 'FunctionCall',
+ params: {
+ methodName: 'storage_deposit',
+ args: { account_id: "alice.near" },
+ gas: THIRTY_TGAS,
+ deposit: REGISTER_DEPOSIT
+ }
+ },
+ // Second action: Transfer tokens
+ {
+ type: 'FunctionCall',
+ params: {
+ methodName: 'ft_transfer',
+ args: {
+ receiver_id: "alice.near",
+ amount: "1000000000000000000" // 1 token
+ },
+ gas: THIRTY_TGAS,
+ deposit: "1" // 1 yoctoNEAR required by standard
+ }
+ }
+ ]
+};
+
+await wallet.signAndSendTransactions({ transactions: [ftTx] });
+```
+
+## Execution Flow
+
+Actions execute **sequentially** within the transaction:
+
+1. `storage_deposit` registers alice.near for FT storage
+2. If successful, `ft_transfer` transfers tokens to alice.near
+3. If either fails, **both revert**
+
+## Example: Guest Book Actions
+
+The Guest Book transaction in our example already uses action batching:
+
+```javascript
+const guestTx = {
+ receiverId: GUEST_ADDRESS,
+ actions: [
+ {
+ type: 'FunctionCall',
+ params: {
+ methodName: 'add_message',
+ args: { text: greeting.value },
+ gas: THIRTY_TGAS,
+ deposit: GUEST_DEPOSIT
+ }
+ }
+ // You could add more actions here targeting the same contract
+ ]
+};
+```
+
+While this example has only one action, you could add more:
+
+```javascript
+const guestTx = {
+ receiverId: GUEST_ADDRESS,
+ actions: [
+ {
+ type: 'FunctionCall',
+ params: {
+ methodName: 'add_message',
+ args: { text: "First message" },
+ gas: THIRTY_TGAS,
+ deposit: NO_DEPOSIT
+ }
+ },
+ {
+ type: 'FunctionCall',
+ params: {
+ methodName: 'add_message',
+ args: { text: "Second message" },
+ gas: THIRTY_TGAS,
+ deposit: NO_DEPOSIT
+ }
+ }
+ ]
+};
+```
+
+Both messages get added, or neither does.
+
+## Key Differences
+
+**Multiple Transactions (Independent):**
+```javascript
+const tx1 = { receiverId: "contract1.near", actions: [...] };
+const tx2 = { receiverId: "contract2.near", actions: [...] };
+await wallet.signAndSendTransactions({ transactions: [tx1, tx2] });
+// If tx1 fails, tx2 can still succeed
+```
+
+**Batched Actions (Atomic):**
+```javascript
+const tx = {
+ receiverId: "contract.near",
+ actions: [action1, action2, action3]
+};
+await wallet.signAndSendTransactions({ transactions: [tx] });
+// If any action fails, ALL revert
+```
+
+## Error Handling
+
+When a batch fails, you'll receive an error indicating which action failed:
+
+```javascript
+try {
+ await wallet.signAndSendTransactions({ transactions: [ftTx] });
+} catch (error) {
+ console.error("Batch failed:", error);
+ // All actions in the batch have been reverted
+}
+```
+
+## Best Practices
+
+1. **Use batching for atomic operations**: When actions must succeed together
+2. **Keep batches small**: Large batches may exceed gas limits
+3. **Order matters**: Actions execute sequentially, so order dependencies correctly
+4. **Test failure scenarios**: Ensure rollback behavior is correct
+
+## Combining Both Approaches
+
+You can combine multiple transactions AND batch actions:
+
+```javascript
+const tx1 = {
+ receiverId: "contract1.near",
+ actions: [action1, action2] // Batched, atomic
+};
+
+const tx2 = {
+ receiverId: "contract2.near",
+ actions: [action3, action4] // Batched, atomic
+};
+
+// Independent transactions, each with batched actions
+await wallet.signAndSendTransactions({ transactions: [tx1, tx2] });
+```
+
+This provides maximum flexibility - atomic operations within each contract, with independent execution across contracts.
+
+## Summary
+
+You've learned how to:
+- Set up wallet integration for multiple contracts
+- Query data from multiple contracts simultaneously
+- Send independent transactions to multiple contracts
+- Batch actions within a single contract for atomic operations
+
+This pattern is essential for building complex Web3 applications that interact with multiple protocols and contracts in the NEAR ecosystem.
\ No newline at end of file
diff --git a/docs/tutorials/update-state/0-introduction.md b/docs/tutorials/update-state/0-introduction.md
new file mode 100644
index 00000000000..c2e39b05a35
--- /dev/null
+++ b/docs/tutorials/update-state/0-introduction.md
@@ -0,0 +1,56 @@
+---
+id: introduction
+title: Contract State Migration
+sidebar_label: Introduction
+description: "Learn how to safely migrate contract state when updating smart contracts on NEAR Protocol."
+---
+
+When you update a smart contract that has existing state, you often need to migrate that state to match the new contract's structure. This tutorial shows you how to handle state migrations safely and effectively.
+
+## What You'll Build
+
+You'll learn three approaches to state migration:
+
+1. **Basic Migration**: Manually migrate state using a dedicated `migrate` method
+2. **Versioned State**: Use enums to version your state structures for seamless updates
+3. **Self-Updating Contracts**: Build contracts that can update and migrate themselves
+
+
+
+## Why State Migration Matters
+
+When you deploy a new contract version over an existing one, the new code expects state in a specific format. If your state structure changed, you'll get deserialization errors:
+
+```
+panicked at 'Cannot deserialize the contract state.'
+```
+
+State migration solves this by transforming old state into the new format.
+
+## Common State Changes
+
+- **Adding fields**: New field in a struct
+- **Removing fields**: Deleting unnecessary data
+- **Restructuring**: Combining or splitting data structures
+- **Type changes**: Changing field types (e.g., separate payment tracking to embedded payment)
+
+## What You Will Learn
+
+- [Implement basic state migration](1-basic-migration.md) with a migrate method
+- [Use state versioning](2-versioned-state.md) with enums for easier updates
+- [Build self-updating contracts](3-self-update.md) that migrate automatically
+- [Test state migrations](4-testing.md) to ensure correctness
+
+## Prerequisites
+
+- Understanding of NEAR smart contracts
+- Knowledge of Rust and Borsh serialization
+- Familiarity with contract deployment
+
+:::tip Repository
+
+Complete code examples are in the [GitHub repository](https://github.com/near-examples/update-migrate-rust).
+
+:::
+
+Let's start with basic state migration!
\ No newline at end of file
diff --git a/docs/tutorials/update-state/1-basic-migration.md b/docs/tutorials/update-state/1-basic-migration.md
new file mode 100644
index 00000000000..fa4a0e2ea14
--- /dev/null
+++ b/docs/tutorials/update-state/1-basic-migration.md
@@ -0,0 +1,171 @@
+---
+id: basic-migration
+title: Basic State Migration
+sidebar_label: Basic Migration
+---
+
+import {Github} from "@site/src/components/codetabs"
+
+The most straightforward migration approach uses a dedicated `migrate` method that transforms old state into new state format.
+
+## The Problem
+
+Our base contract stores messages and payments separately:
+
+
+
+We want to update it to store payment information directly in each message:
+
+
+
+If we deploy this directly, the contract can't deserialize the old state because it expects `PostedMessage` with 4 fields but finds only 3.
+
+## The Solution: Migrate Method
+
+Create a migration method that reads the old state and writes the new state:
+
+
+
+### How It Works
+
+**1. Define Old State Structure**
+
+
+
+Mirror the previous contract's state structure so we can deserialize it.
+
+**2. Read Old State**
+
+```rust
+let mut old_state: OldState = env::state_read().expect("failed");
+```
+
+Deserialize the current state from storage using the old structure.
+
+**3. Transform Data**
+
+```rust
+let mut new_messages: Vector = Vector::new(MESSAGES_PREFIX);
+
+for (idx, posted) in old_state.messages.iter().enumerate() {
+ let payment = old_state.payments.get(idx as u64)
+ .expect("failed to get payment")
+ .clone();
+
+ new_messages.push(&PostedMessage {
+ payment,
+ premium: posted.premium,
+ sender: posted.sender.clone(),
+ text: posted.text.clone(),
+ })
+}
+```
+
+Iterate through old messages, combining them with their corresponding payments.
+
+**4. Clean Up Old Data**
+
+```rust
+old_state.payments.clear();
+```
+
+Remove the old payments vector from storage to avoid wasting storage space.
+
+**5. Return New State**
+
+```rust
+Self { messages: new_messages }
+```
+
+Return the transformed state which gets written to storage.
+
+## Key Annotations
+
+The `migrate` method uses special annotations:
+
+```rust
+#[private]
+#[init(ignore_state)]
+pub fn migrate() -> Self
+```
+
+- `#[private]`: Only the contract account can call this method
+- `#[init(ignore_state)]`: Allows rewriting state (normally initialization fails if state exists)
+
+## Deploying with Migration
+
+Build the updated contract:
+
+```bash
+cd basic-updates/update
+cargo near build
+```
+
+Deploy and migrate in one transaction:
+
+```bash
+cargo near deploy \
+ with-init-call migrate json-args {} \
+ prepaid-gas '100.0 Tgas' \
+ attached-deposit '0 NEAR' \
+ network-config testnet \
+ sign-with-keychain send
+```
+
+This:
+1. Deploys the new contract code
+2. Immediately calls `migrate` to transform the state
+3. Ensures the contract is usable after deployment
+
+## Verifying Migration
+
+Check that messages now include payment information:
+
+```bash
+near contract call-function as-read-only \
+ get_messages \
+ json-args {} \
+ network-config testnet now
+```
+
+Expected result:
+```json
+[
+ {
+ "payment": "90000000000000000000000",
+ "premium": false,
+ "sender": "user.testnet",
+ "text": "hello"
+ }
+]
+```
+
+The old `get_payments` method no longer exists:
+
+```bash
+near contract call-function as-read-only \
+ get_payments \
+ json-args {} \
+ network-config testnet now
+# Error: MethodNotFound
+```
+
+## Important Considerations
+
+**Gas Requirements**: Large state migrations may require more gas. Test with your data volume.
+
+**Atomicity**: If migration fails, the entire transaction reverts. The old contract remains deployed.
+
+**Testing**: Always test migrations on testnet with realistic data before mainnet deployment.
+
+**Backup**: Consider exporting state before migration for additional safety.
+
+Next, we'll explore versioned state that makes future migrations easier.
\ No newline at end of file
diff --git a/docs/tutorials/update-state/2-versioned-state.md b/docs/tutorials/update-state/2-versioned-state.md
new file mode 100644
index 00000000000..c93b62ac7bb
--- /dev/null
+++ b/docs/tutorials/update-state/2-versioned-state.md
@@ -0,0 +1,214 @@
+---
+id: versioned-state
+title: State Versioning with Enums
+sidebar_label: Versioned State
+---
+
+import {Github} from "@site/src/components/codetabs"
+
+State versioning uses Rust enums to allow multiple versions of your data structures to coexist, making future updates much simpler.
+
+## The Versioning Approach
+
+Instead of storing messages directly, wrap them in a versioned enum:
+
+
+
+This lets you add new versions without breaking existing data.
+
+## Base Contract with Versioning
+
+Store versioned messages:
+
+
+
+When adding messages, wrap them in the version enum:
+
+
+
+## Retrieving Messages
+
+Convert versioned messages to a specific version when reading:
+
+
+
+The `From` implementation handles the conversion:
+
+
+
+## Adding a New Version
+
+When you need to update the message structure, add a new enum variant:
+
+
+
+Update the enum to include both versions:
+
+
+
+## Automatic Migration
+
+The key benefit: implement `From` to automatically convert old versions:
+
+
+
+When reading messages, V1 messages automatically convert to V2 with default values for new fields.
+
+## Updated Contract
+
+The updated contract uses V2 for new messages:
+
+
+
+Reading still works seamlessly:
+
+
+
+## No Migration Method Needed
+
+Deploy the updated contract directly:
+
+```bash
+cd enum-updates/update
+cargo near build
+
+cargo near deploy \
+ without-init-call \
+ network-config testnet \
+ sign-with-keychain send
+```
+
+No migration method needed! Old V1 messages coexist with new V2 messages.
+
+## Testing the Update
+
+Add a new message:
+
+```bash
+near contract call-function as-transaction \
+ add_message \
+ json-args '{"text": "new message"}' \
+ prepaid-gas '100.0 Tgas' \
+ attached-deposit '0.1 NEAR' \
+ sign-as \
+ network-config testnet \
+ sign-with-keychain send
+```
+
+Retrieve all messages:
+
+```bash
+near contract call-function as-read-only \
+ get_messages \
+ json-args {} \
+ network-config testnet now
+```
+
+Result shows both old and new messages in V2 format:
+
+```json
+[
+ {
+ "payment": "0",
+ "premium": false,
+ "sender": "user1.testnet",
+ "text": "old message"
+ },
+ {
+ "payment": "100000000000000000000000",
+ "premium": true,
+ "sender": "user2.testnet",
+ "text": "new message"
+ }
+]
+```
+
+Old V1 messages get `payment: "0"` automatically.
+
+## Advantages of Versioning
+
+**No downtime**: Deploy updates without migration methods
+
+**Gradual migration**: Old data converts on-read, not all at once
+
+**Gas efficiency**: No large migration transaction needed
+
+**Flexibility**: Easy to add multiple versions over time
+
+**Safety**: Old data remains unchanged in storage
+
+## When to Use Versioning
+
+Versioning works best when:
+- You anticipate future schema changes
+- You want zero-downtime updates
+- Reading old data with default values is acceptable
+- Storage efficiency isn't critical (versions add overhead)
+
+## Versioning Multiple Structures
+
+You can version multiple structures:
+
+```rust
+pub enum VersionedUser {
+ V1(UserV1),
+ V2(UserV2),
+}
+
+pub enum VersionedPost {
+ V1(PostV1),
+ V2(PostV2),
+}
+
+pub struct Contract {
+ users: Vector,
+ posts: Vector,
+}
+```
+
+Each structure versions independently.
+
+## Handling Many Versions
+
+As versions accumulate, consider consolidating:
+
+```rust
+// After several versions, manually migrate V1-V3 to V4
+pub fn consolidate_old_versions(&mut self) {
+ let mut new_messages = Vector::new(b"m");
+
+ for msg in self.messages.iter() {
+ let v4_msg = match msg {
+ VersionedMessage::V1(m) => convert_v1_to_v4(m),
+ VersionedMessage::V2(m) => convert_v2_to_v4(m),
+ VersionedMessage::V3(m) => convert_v3_to_v4(m),
+ VersionedMessage::V4(m) => m,
+ };
+ new_messages.push(v4_msg);
+ }
+
+ self.messages = new_messages;
+}
+```
+
+Next, we'll explore self-updating contracts that can deploy and migrate themselves.
\ No newline at end of file
diff --git a/docs/tutorials/update-state/3-self-update.md b/docs/tutorials/update-state/3-self-update.md
new file mode 100644
index 00000000000..36141748383
--- /dev/null
+++ b/docs/tutorials/update-state/3-self-update.md
@@ -0,0 +1,293 @@
+---
+id: self-update
+title: Self-Updating Contracts
+sidebar_label: Self-Update
+---
+
+import {Github} from "@site/src/components/codetabs"
+
+Self-updating contracts can deploy new code and migrate state on themselves, enabling autonomous upgrades without external deployment tools.
+
+## The Update Method
+
+Add an `update_contract` method that deploys new code:
+
+
+
+### How It Works
+
+**1. Authorization Check**
+
+```rust
+assert!(
+ env::predecessor_account_id() == self.manager,
+ "Only the manager can update the code"
+);
+```
+
+Only the designated manager account can update the contract.
+
+**2. Receive Contract Code**
+
+```rust
+let code = env::input().expect("Error: No input").to_vec();
+```
+
+Read the contract bytecode directly from input to avoid gas overhead of deserializing large parameters.
+
+**3. Deploy and Migrate**
+
+```rust
+Promise::new(env::current_account_id())
+ .deploy_contract(code)
+ .function_call(
+ "migrate".to_string(),
+ NO_ARGS,
+ NearToken::from_near(0),
+ CALL_GAS,
+ )
+ .as_return()
+```
+
+Chain two actions:
+- Deploy the new contract code
+- Call `migrate` to transform the state
+
+Both actions execute atomically - if either fails, both revert.
+
+## Base Contract Setup
+
+Store the manager address in state:
+
+
+
+Initialize with a manager:
+
+
+
+## Updated Contract with Migration
+
+The updated contract includes both the update method and migration logic:
+
+
+
+## Deployment Process
+
+### Initial Deployment
+
+Deploy the base contract:
+
+```bash
+cd self-updates/base
+cargo near build
+
+cargo near deploy \
+ with-init-call init \
+ json-args '{"manager":""}' \
+ prepaid-gas '100.0 Tgas' \
+ attached-deposit '0 NEAR' \
+ network-config testnet \
+ sign-with-keychain send
+```
+
+### Locking the Contract
+
+For maximum security, remove the contract's access keys so only the `update_contract` method can change it:
+
+```bash
+# List current keys
+near account list-keys \
+ network-config testnet now
+
+# Delete the full access key
+near account delete-keys \
+ public-keys \
+ network-config testnet \
+ sign-with-keychain send
+```
+
+Now the contract is "locked" - it can only be updated through its own `update_contract` method.
+
+## Performing Self-Update
+
+Build the updated contract:
+
+```bash
+cd self-updates/update
+cargo near build
+```
+
+Call the update method from the manager account:
+
+```bash
+near contract call-function as-transaction \
+ update_contract \
+ file-args ../../target/near/self_update/self_update.wasm \
+ prepaid-gas '300.0 Tgas' \
+ attached-deposit '0 NEAR' \
+ sign-as \
+ network-config testnet \
+ sign-with-keychain send
+```
+
+The contract:
+1. Verifies the caller is the manager
+2. Deploys the new code on itself
+3. Calls its own `migrate` method
+4. Transforms the state to the new format
+
+## Verifying the Update
+
+Check the updated state:
+
+```bash
+near contract call-function as-read-only \
+ get_messages \
+ json-args {} \
+ network-config testnet now
+```
+
+Messages now include payment information:
+
+```json
+[
+ {
+ "payment": "90000000000000000000000",
+ "premium": false,
+ "sender": "user.testnet",
+ "text": "hello"
+ }
+]
+```
+
+The old method is gone:
+
+```bash
+near contract call-function as-read-only \
+ get_payments \
+ json-args {} \
+ network-config testnet now
+# Error: MethodNotFound
+```
+
+## Access Control Patterns
+
+### Single Manager
+
+Basic pattern with one manager:
+
+```rust
+pub struct Contract {
+ manager: AccountId,
+}
+
+pub fn update_contract(&self) -> Promise {
+ assert_eq!(
+ env::predecessor_account_id(),
+ self.manager,
+ "Unauthorized"
+ );
+ // ... update logic
+}
+```
+
+### Multiple Managers
+
+Allow multiple authorized updaters:
+
+```rust
+pub struct Contract {
+ managers: UnorderedSet,
+}
+
+pub fn update_contract(&self) -> Promise {
+ assert!(
+ self.managers.contains(&env::predecessor_account_id()),
+ "Unauthorized"
+ );
+ // ... update logic
+}
+
+#[private]
+pub fn add_manager(&mut self, account: AccountId) {
+ self.managers.insert(account);
+}
+```
+
+### DAO Governance
+
+Integrate with a DAO for decentralized updates:
+
+```rust
+pub struct Contract {
+ dao_contract: AccountId,
+}
+
+pub fn update_contract(&self) -> Promise {
+ // Verify this is a callback from the DAO
+ assert_eq!(
+ env::predecessor_account_id(),
+ self.dao_contract,
+ "Must be called by DAO"
+ );
+ // ... update logic
+}
+```
+
+The DAO contract would include a proposal system for voting on updates.
+
+## Security Considerations
+
+**Manager Key Security**: The manager account key controls contract updates. Use a hardware wallet or multisig.
+
+**Lock Early**: Deploy with the update method, then lock the contract by removing keys.
+
+**Audit Update Methods**: The update method is a critical security point - audit it carefully.
+
+**Test Updates**: Always test the full update flow on testnet first.
+
+**Emergency Stop**: Consider adding a pause mechanism:
+
+```rust
+pub struct Contract {
+ manager: AccountId,
+ paused: bool,
+}
+
+pub fn pause(&mut self) {
+ assert_eq!(env::predecessor_account_id(), self.manager);
+ self.paused = true;
+}
+
+pub fn update_contract(&self) -> Promise {
+ assert!(!self.paused, "Contract is paused");
+ assert_eq!(env::predecessor_account_id(), self.manager);
+ // ... update logic
+}
+```
+
+## Multi-Version Updates
+
+For complex updates spanning multiple versions, track state version:
+
+
+
+Handle incremental migrations:
+
+
+
+This allows migrating through multiple versions: V1 → V2 → V3.
+
+Next, we'll cover testing strategies for state migrations.
\ No newline at end of file
diff --git a/docs/tutorials/update-state/4-testing.md b/docs/tutorials/update-state/4-testing.md
new file mode 100644
index 00000000000..7858b62a40e
--- /dev/null
+++ b/docs/tutorials/update-state/4-testing.md
@@ -0,0 +1,393 @@
+---
+id: testing
+title: Testing State Migrations
+sidebar_label: Testing
+---
+
+import {Github} from "@site/src/components/codetabs"
+
+Thorough testing ensures your state migrations work correctly and don't lose data. Use `near-workspaces` for integration testing.
+
+## Test Setup
+
+Create a test fixture that deploys the base contract and adds data:
+
+
+
+This creates:
+- A sandbox environment
+- The base contract with initial data
+- Test accounts to interact with the contract
+
+## Testing Basic Migration
+
+### Verify Base Contract State
+
+First, confirm the base contract works:
+
+
+
+### Test the Migration
+
+Deploy the updated contract and verify migration:
+
+
+
+Key assertions:
+- Migration succeeds without errors
+- Old data is preserved with new structure
+- Payments are correctly moved into messages
+- Old methods are removed
+
+## Testing Versioned State
+
+For versioned state, test that old and new versions coexist:
+
+
+
+Verify:
+- Old V1 messages convert to V2 with defaults
+- New V2 messages store complete data
+- No migration method needed
+
+## Testing Self-Updates
+
+Test that the contract can update itself:
+
+
+
+Verify:
+- Only the manager can trigger updates
+- Update and migration happen atomically
+- State is correctly transformed
+
+## Running Tests
+
+Execute all migration tests:
+
+```bash
+# Test basic migration
+cd basic-updates/update
+cargo test --test workspaces
+
+# Test versioned state
+cd enum-updates/update
+cargo test --test workspaces
+
+# Test self-updates
+cd self-updates/update
+cargo test --test workspaces
+```
+
+## Test Best Practices
+
+### Test with Realistic Data Volumes
+
+Don't just test with 2 messages - test with hundreds:
+
+```rust
+#[tokio::test]
+async fn test_large_migration() {
+ // ... setup
+
+ // Add 500 messages
+ for i in 0..500 {
+ alice.call(contract.id(), "add_message")
+ .args_json(json!({"text": format!("Message {}", i)}))
+ .deposit(ONE_TENTH_NEAR)
+ .transact()
+ .await
+ .unwrap();
+ }
+
+ // Deploy and migrate
+ let updated_wasm = near_workspaces::compile_project("./").await.unwrap();
+ // ... test migration
+}
+```
+
+### Test Gas Limits
+
+Ensure migrations don't exceed gas limits:
+
+```rust
+let migrate_outcome = guest_book
+ .call(migrated_contract.id(), "migrate")
+ .args_json(json!({}))
+ .gas(Gas::from_tgas(300)) // Test with appropriate gas
+ .transact()
+ .await
+ .unwrap();
+
+assert!(migrate_outcome.is_success());
+
+// Check actual gas used
+let gas_used = migrate_outcome.total_gas_burnt;
+println!("Migration used {} Tgas", gas_used.as_tgas());
+```
+
+### Test Edge Cases
+
+```rust
+#[tokio::test]
+async fn test_empty_state_migration() {
+ // Test migrating with no data
+}
+
+#[tokio::test]
+async fn test_partial_data_migration() {
+ // Test migrating when some fields are missing
+}
+
+#[tokio::test]
+async fn test_failed_migration_rollback() {
+ // Verify state doesn't change if migration fails
+}
+```
+
+### Test Authorization
+
+For self-updating contracts, verify access control:
+
+```rust
+#[tokio::test]
+async fn test_unauthorized_update() {
+ let base_contract = base_contract().await;
+ let updated_wasm = near_workspaces::compile_project("./").await.unwrap();
+
+ // Try to update from non-manager account
+ let unauthorized_outcome = base_contract.bob
+ .call(base_contract.guest_book.id(), "update_contract")
+ .args(updated_wasm)
+ .gas(Gas::from_tgas(300))
+ .transact()
+ .await
+ .unwrap();
+
+ // Should fail
+ assert!(unauthorized_outcome.is_failure());
+}
+```
+
+## Manual Testing on Testnet
+
+After automated tests pass, manually test on testnet:
+
+### 1. Deploy Base Contract
+
+```bash
+cargo near deploy test-migrate.testnet \
+ with-init-call init json-args '{"manager":"manager.testnet"}' \
+ prepaid-gas '100.0 Tgas' \
+ attached-deposit '0 NEAR' \
+ network-config testnet \
+ sign-with-keychain send
+```
+
+### 2. Add Test Data
+
+```bash
+# Add several messages with varying payments
+near contract call-function as-transaction \
+ test-migrate.testnet add_message \
+ json-args '{"text": "Test message 1"}' \
+ prepaid-gas '100.0 Tgas' \
+ attached-deposit '0.05 NEAR' \
+ sign-as your-account.testnet \
+ network-config testnet \
+ sign-with-keychain send
+```
+
+### 3. Export State Before Migration
+
+```bash
+near contract call-function as-read-only \
+ test-migrate.testnet get_messages \
+ json-args {} \
+ network-config testnet now > messages-before.json
+
+near contract call-function as-read-only \
+ test-migrate.testnet get_payments \
+ json-args {} \
+ network-config testnet now > payments-before.json
+```
+
+### 4. Deploy Updated Contract
+
+```bash
+cargo near deploy test-migrate.testnet \
+ with-init-call migrate json-args {} \
+ prepaid-gas '100.0 Tgas' \
+ attached-deposit '0 NEAR' \
+ network-config testnet \
+ sign-with-keychain send
+```
+
+### 5. Verify Migration
+
+```bash
+near contract call-function as-read-only \
+ test-migrate.testnet get_messages \
+ json-args {} \
+ network-config testnet now > messages-after.json
+
+# Compare before and after
+diff messages-before.json messages-after.json
+```
+
+### 6. Test New Functionality
+
+```bash
+# Add a message with the new contract
+near contract call-function as-transaction \
+ test-migrate.testnet add_message \
+ json-args '{"text": "Post-migration message"}' \
+ prepaid-gas '100.0 Tgas' \
+ attached-deposit '0.1 NEAR' \
+ sign-as your-account.testnet \
+ network-config testnet \
+ sign-with-keychain send
+
+# Verify it has payment embedded
+near contract call-function as-read-only \
+ test-migrate.testnet get_messages \
+ json-args {} \
+ network-config testnet now
+```
+
+## Testing Checklist
+
+Before deploying to mainnet, verify:
+
+- [ ] All automated tests pass
+- [ ] Manual testnet migration successful
+- [ ] Data integrity confirmed (no lost messages/payments)
+- [ ] Gas usage acceptable for your data volume
+- [ ] New contract methods work correctly
+- [ ] Removed methods properly deleted
+- [ ] Authorization works as expected (for self-updates)
+- [ ] State size doesn't exceed limits
+- [ ] Multiple migrations tested (for multi-version updates)
+
+## Debugging Failed Migrations
+
+If a migration fails:
+
+**1. Check Error Messages**
+
+```bash
+# The transaction will show the panic message
+near tx-status --accountId
+```
+
+**2. Verify State Structure**
+
+Ensure your `OldState` structure matches the deployed contract exactly.
+
+**3. Test Serialization**
+
+```rust
+#[test]
+fn test_old_state_deserialization() {
+ let old_state = OldState {
+ messages: Vector::new(b"m"),
+ payments: Vector::new(b"p"),
+ };
+
+ // Try to serialize and deserialize
+ let bytes = borsh::to_vec(&old_state).unwrap();
+ let deserialized: OldState = borsh::from_slice(&bytes).unwrap();
+}
+```
+
+**4. Check Storage Keys**
+
+Ensure storage key prefixes match the old contract:
+
+```rust
+// Old contract used b"m" and b"p"
+Vector::new(b"m") // for messages
+Vector::new(b"p") // for payments
+```
+
+## Summary
+
+You've learned how to:
+- Write comprehensive integration tests for state migrations
+- Test basic migrations, versioned state, and self-updates
+- Verify data integrity across contract updates
+- Test with realistic data volumes and gas limits
+- Manually test migrations on testnet
+- Debug failed migrations
+
+## Key Testing Principles
+
+**Always test before mainnet**: Never deploy a migration to mainnet without thorough testnet testing.
+
+**Test with production-like data**: Use similar data volumes to what you have in production.
+
+**Verify data integrity**: Confirm no data is lost during migration.
+
+**Test incrementally**: For complex migrations, test each version transition separately.
+
+**Monitor gas usage**: Ensure migrations complete within gas limits for your data size.
+
+## Migration Strategies Comparison
+
+### Basic Migration
+- **Best for**: One-time schema changes
+- **Pros**: Simple, explicit control
+- **Cons**: Requires migration transaction, potential downtime
+- **Testing focus**: Data transformation correctness
+
+### Versioned State
+- **Best for**: Evolving schemas with frequent updates
+- **Pros**: No downtime, gradual migration, flexible
+- **Cons**: Storage overhead, complexity increases over time
+- **Testing focus**: Version conversion logic
+
+### Self-Update
+- **Best for**: Autonomous contracts, DAO-governed protocols
+- **Pros**: No external deployment needed, atomic update+migration
+- **Cons**: More complex, critical security point
+- **Testing focus**: Authorization, atomic execution
+
+## Production Checklist
+
+Before deploying to mainnet:
+
+1. **Code Review**: Have migrations reviewed by other developers
+2. **Testnet Testing**: Complete full migration cycle on testnet
+3. **Data Export**: Back up current state before migration
+4. **Gas Estimation**: Verify gas costs for your data volume
+5. **Rollback Plan**: Know how to revert if something goes wrong
+6. **Monitoring**: Watch contract after migration for issues
+7. **Communication**: Inform users about the update if needed
+
+## Next Steps
+
+With these migration patterns, you can:
+
+- **Update contracts safely**: Change state structures without losing data
+- **Plan for evolution**: Use versioning for contracts that will evolve
+- **Build autonomous systems**: Create self-updating contracts for DAOs
+- **Test thoroughly**: Ensure migrations work before mainnet deployment
+
+## Additional Resources
+
+- [NEAR Contract Standards](https://github.com/near/NEPs/tree/master/neps)
+- [State Migration Examples](https://github.com/near-examples/update-migrate-rust)
+- [near-workspaces Documentation](https://docs.rs/near-workspaces/)
+- [Upgrade Pattern Best Practices](https://docs.near.org/develop/upgrade)
+
+Remember: State migrations are critical operations. Always prioritize data safety and thorough testing!
\ No newline at end of file
diff --git a/website/sidebars.js b/website/sidebars.js
index 11f9e4bbace..993f1f34d20 100644
--- a/website/sidebars.js
+++ b/website/sidebars.js
@@ -267,7 +267,7 @@ const sidebar = {
'tutorials/examples/guest-book',
// 'tutorials/examples/donation',
// 'tutorials/examples/coin-flip',
- 'tutorials/examples/factory',
+ // 'tutorials/examples/factory',
{
"LinkDrops": [
'tutorials/neardrop/introduction',
@@ -279,6 +279,14 @@ const sidebar = {
'tutorials/neardrop/account-creation',
'tutorials/neardrop/frontend'
]},
+ {
+ "Update Contract & Migrate State": [
+ 'tutorials/update-state/introduction',
+ 'tutorials/update-state/basic-migration',
+ 'tutorials/update-state/versioned-state',
+ 'tutorials/update-state/self-update',
+ 'tutorials/update-state/testing',
+ ]},
{
"Advanced Cross-Contract Call": [
'tutorials/advanced-xcc/introduction',
@@ -287,14 +295,14 @@ const sidebar = {
'tutorials/advanced-xcc/parallel-execution',
'tutorials/advanced-xcc/testing-deployment'
]},
- // {
- // "Factory": [
- // 'tutorials/factory/0-introduction',
- // 'tutorials/factory/1-factory-contract',
- // 'tutorials/factory/2-deploy-factory',
- // 'tutorials/factory/3-create-instances',
- // 'tutorials/factory/4-update-contract',
- // ]},
+ {
+ "Factory": [
+ 'tutorials/factory/introduction',
+ 'tutorials/factory/setup',
+ 'tutorials/factory/deploy',
+ 'tutorials/factory/manager',
+ 'tutorials/factory/testing',
+ ]},
{
"Coin Flip": [
'tutorials/coin-flip/introduction',
@@ -316,7 +324,7 @@ const sidebar = {
'tutorials/examples/xcc',
// 'tutorials/examples/advanced-xcc',
'tutorials/examples/global-contracts',
- 'tutorials/examples/update-contract-migrate-state',
+ //'tutorials/examples/update-contract-migrate-state',
{
"Build a FT Contract from Scratch": [
'tutorials/fts/introduction',
@@ -401,7 +409,15 @@ const sidebar = {
{
"Tutorials": [
'web3-apps/integrate-contracts',
- 'tutorials/examples/frontend-multiple-contracts',
+ //'tutorials/examples/frontend-multiple-contracts',
+ {
+ "Frontend Multipe Contracts": [
+ 'tutorials/frontend-multiple-contracts/introduction',
+ 'tutorials/frontend-multiple-contracts/setup',
+ 'tutorials/frontend-multiple-contracts/query',
+ 'tutorials/frontend-multiple-contracts/transactions',
+ 'tutorials/frontend-multiple-contracts/batch-actions'
+ ]},
'web3-apps/ethereum-wallets',
'web3-apps/backend/backend-login',
'chain-abstraction/meta-transactions-relayer',