diff --git a/content/contracts-sui/1.x/learn/access-walkthrough.mdx b/content/contracts-sui/1.x/learn/access-walkthrough.mdx
new file mode 100644
index 0000000..f4fc9a6
--- /dev/null
+++ b/content/contracts-sui/1.x/learn/access-walkthrough.mdx
@@ -0,0 +1,331 @@
+---
+title: Access Walkthrough
+---
+
+
+The example code snippets used within this walkthrough are experimental and have not been audited. They simply help exemplify the OpenZeppelin Sui package usage.
+
+
+This guide provides a detailed walkthrough of the `openzeppelin_access` package. It explains the design behind each transfer policy, walks through the full lifecycle of wrapping and transferring capabilities, and covers the borrow patterns, events, and error handling you need to integrate these modules into a protocol. For a quick overview and getting-started examples, see the [Access package guide](https://docs.openzeppelin.com/contracts-sui/1.x/access). For function-level signatures, see the [Access API reference](https://docs.openzeppelin.com/contracts-sui/1.x/api/access).
+
+[Source Code](https://github.com/OpenZeppelin/contracts-sui/tree/main/contracts/access)
+
+---
+
+## Why controlled transfers matter
+
+On Sui, `sui::transfer::transfer` is instant and irreversible. There is no confirmation step, no waiting period, and no cancel mechanism. For everyday objects this is fine. For privileged capability objects, such as admin caps, treasury caps, or upgrade authorities, a single mistaken or malicious transfer permanently moves control with no recourse.
+
+The `openzeppelin_access` package adds two transfer policies that sit between you and that irreversible `transfer` call:
+
+| Module | What it enforces | Analogy from Solidity |
+| --- | --- | --- |
+| `two_step_transfer` | Recipient must explicitly accept before the transfer completes | `Ownable2Step` |
+| `delayed_transfer` | Mandatory time delay before execution; anyone can observe the pending action | `TimelockController` on ownership |
+
+If you already know which policy you need, skip to [Choosing a transfer policy](#choosing-a-transfer-policy) or jump directly to [two\_step\_transfer](#two_step_transfer) or [delayed\_transfer](#delayed_transfer).
+
+---
+
+## Wrapping and transfer policies
+
+Both modules use the same underlying mechanism: wrapping a capability inside a new object that enforces a transfer policy on it.
+
+When you call `wrap`, the capability is stored as a dynamic object field inside the wrapper. This means:
+
+- **The wrapper becomes the custody object.** You hold the wrapper, not the capability directly. To transfer or recover the capability, you go through the wrapper's policy.
+- **The underlying capability retains its on-chain ID.** Off-chain indexers and explorers can still discover and track it via the dynamic object field. Wrapping does not make the capability invisible.
+- **The wrapper intentionally omits the `store` ability.** Without `store`, the wrapper cannot be moved via `transfer::public_transfer`. Only the module's own functions (which use the privileged `transfer::transfer` internally) can move it. This is a deliberate design choice that prevents accidental transfers outside the policy.
+
+A `WrapExecuted` event is emitted when a capability is wrapped, creating an on-chain record of when the policy was applied.
+
+### Borrowing without unwrapping
+
+Both modules provide three ways to use the wrapped capability without changing ownership:
+
+**Immutable borrow** for read-only access:
+
+```move
+let cap_ref = wrapper.borrow(); // &AdminCap
+```
+
+**Mutable borrow** for updating the capability's internal state:
+
+```move
+let cap_mut = wrapper.borrow_mut(); // &mut AdminCap
+```
+
+**Temporary move** for functions that require the capability by value. This uses the hot potato pattern: `borrow_val` returns a `Borrow` struct with no abilities (`copy`, `drop`, `store`, `key` are all absent). The Move compiler enforces that it must be consumed by `return_val` before the transaction ends.
+
+```move
+let (cap, borrow_token) = wrapper.borrow_val();
+
+/// Use cap in functions that require it by value.
+wrapper.return_val(cap, borrow_token); // compiler enforces this call
+```
+
+If you try to drop the borrow token, return a different capability, or return it to the wrong wrapper, the transaction either won't compile or will abort at runtime.
+
+---
+
+## `two_step_transfer`
+
+[Source Code](https://github.com/OpenZeppelin/contracts-sui/blob/v1.0.0/contracts/access/sources/ownership_transfer/two_step.move)
+
+A transfer policy that requires the designated recipient to explicitly accept before the wrapper changes hands. The initiator retains cancel authority until acceptance. There is no time delay, thus the transfer executes immediately once the recipient accepts.
+
+This is the right choice when the principal initiating the transfer is a known, controlled key (a multisig, a hot wallet operated by the same team) and the risk you are guarding against is sending the capability to a wrong or non-existent address, which would permanently lock the capability with no way to recover it.
+
+### Step 1: Wrap the capability
+
+```move
+module my_sui_app::admin;
+
+use openzeppelin_access::two_step_transfer;
+
+public struct AdminCap has key, store { id: UID }
+
+/// Wrap and immediately initiate a transfer to `new_admin`.
+/// The wrapper is consumed by `initiate_transfer` and held
+/// inside the shared `PendingOwnershipTransfer` until the
+/// recipient accepts or the initiator cancels.
+public fun wrap_and_transfer(cap: AdminCap, new_admin: address, ctx: &mut TxContext) {
+ let wrapper = two_step_transfer::wrap(cap, ctx);
+ // Emits WrapExecuted
+
+ wrapper.initiate_transfer(new_admin, ctx);
+ // Emits TransferInitiated
+}
+```
+
+`wrap` stores the `AdminCap` inside a `TwoStepTransferWrapper` and emits a `WrapExecuted` event. Because the wrapper lacks the `store` ability, it cannot be sent via `transfer::public_transfer`. The intended next step is to call `initiate_transfer`, which consumes the wrapper and creates a shared `PendingOwnershipTransfer` object that both parties can interact with.
+
+### Step 2: Initiate a transfer
+
+`initiate_transfer` consumes the wrapper by value and creates a shared `PendingOwnershipTransfer` object. The sender's address is recorded as `from` (the cancel authority), and the recipient's address is recorded as `to`.
+
+```move
+/// Called by the current wrapper owner. Consumes the wrapper.
+wrapper.initiate_transfer(new_admin_address, ctx);
+/// Emits TransferInitiated { wrapper_id, from, to }
+```
+
+After this call, the wrapper is held inside the pending request via transfer-to-object. The original owner no longer has it in their scope, but they retain the ability to cancel because their address is recorded as `from`.
+
+The `TransferInitiated` event contains the pending request's ID and the wrapper ID, allowing off-chain indexers to discover the shared `PendingOwnershipTransfer` object for the next step.
+
+### Step 3: Recipient accepts (or initiator cancels)
+
+The designated recipient calls `accept_transfer` to complete the handoff. This step uses Sui's [transfer-to-object (TTO)](https://docs.sui.io/guides/developer/objects/transfers/transfer-to-object) pattern: the wrapper was transferred to the `PendingOwnershipTransfer` object in Step 2, so the recipient must provide a `Receiving>` ticket to claim it. The `Receiving` type is Sui's mechanism for retrieving objects that were sent to another object rather than to a wallet.
+
+```move
+/// Called by the address recorded as `to` (new_admin_address).
+/// `request` is the shared PendingOwnershipTransfer object.
+/// `wrapper_ticket` is the Receiving> for the wrapper
+/// that was transferred to the request object.
+two_step_transfer::accept_transfer(request, wrapper_ticket, ctx);
+// Emits TransferAccepted { wrapper_id, from, to }
+```
+
+If the initiator changes their mind before the recipient accepts, they can cancel. The cancel call also requires the `Receiving` ticket for the wrapper:
+
+```move
+/// Called by the address recorded as `from` (the original initiator).
+two_step_transfer::cancel_transfer(request, wrapper_ticket, ctx);
+/// Wrapper is returned to the `from` address.
+```
+
+### Unwrapping
+
+To permanently reclaim the raw capability and destroy the wrapper:
+
+```move
+let admin_cap = wrapper.unwrap(ctx);
+```
+
+This bypasses the transfer flow entirely. Only the current wrapper owner can call it.
+
+### Security note on shared-object flows
+
+`initiate_transfer` records `ctx.sender()` as the cancel authority. In normal single-owner usage, this is the wallet holding the wrapper. However, if `initiate_transfer` is called inside a shared-object executor where any user can be the transaction sender, a malicious user could call `initiate_transfer` targeting their own address as recipient. They would become both the pending recipient and the sole cancel authority, locking out the legitimate owner.
+
+Avoid using `two_step_transfer` in shared-object executor flows unless your design explicitly maps signer identity to cancel authority.
+
+---
+
+## `delayed_transfer`
+
+[Source Code](https://github.com/OpenZeppelin/contracts-sui/blob/v1.0.0/contracts/access/sources/ownership_transfer/delayed.move)
+
+A transfer policy that enforces a configurable minimum delay between scheduling and executing a transfer. The delay is set at wrap time and cannot be changed afterward. This creates a publicly visible window before any authority change takes effect, giving monitoring systems, DAOs, and individual users time to detect and respond.
+
+This is the right choice when your protocol requires on-chain lead time before a capability changes hands, for example, to allow an incident response process to detect a compromised key, or to give depositors time to exit before governance parameters change.
+
+### Step 1: Wrap with a delay
+
+```move
+module my_sui_app::treasury;
+
+use openzeppelin_access::delayed_transfer;
+
+public struct TreasuryCap has key, store { id: UID }
+
+const MIN_DELAY_MS: u64 = 86_400_000; // 24 hours
+
+/// Creates the wrapper and transfers it to ctx.sender() internally
+public fun wrap_treasury_cap(cap: TreasuryCap, ctx: &mut TxContext) {
+ delayed_transfer::wrap(cap, MIN_DELAY_MS, ctx.sender(), ctx);
+}
+```
+
+`wrap` creates a `DelayedTransferWrapper`, stores the capability inside it as a dynamic object field, and transfers the wrapper to the specified `recipient` (here, the caller). A `WrapExecuted` event is emitted. Unlike `two_step_transfer::wrap` which returns the wrapper, `delayed_transfer::wrap` handles the transfer internally and has no return value.
+
+### Step 2: Schedule a transfer
+
+```move
+/// Called by the current wrapper owner.
+wrapper.schedule_transfer(new_owner_address, &clock, ctx);
+/// Emits TransferScheduled with execute_after_ms = clock.timestamp_ms() + min_delay_ms
+```
+
+The `Clock` object is Sui's shared on-chain clock. The deadline is computed as `clock.timestamp_ms() + min_delay_ms` and stored in the wrapper. Only one action can be pending at a time; scheduling a second without canceling the first aborts with `ETransferAlreadyScheduled`.
+
+During the delay window, the `TransferScheduled` event is visible on-chain. Monitoring systems, governance dashboards, or individual users watching the chain can detect the pending transfer and take action (e.g., withdrawing funds from the protocol) before it executes.
+
+
+The `recipient` in `schedule_transfer` must be a wallet address, not an object ID. If the wrapper is transferred to an object via transfer-to-object (TTO), both the wrapper and the capability inside it become permanently locked. The `delayed_transfer` module does not implement a `Receiving`-based retrieval mechanism, so there is no way to borrow, unwrap, or further transfer a wrapper that has been sent to an object. Always verify that the scheduled recipient is an address controlled by a keypair.
+
+
+### Step 3: Wait, then execute
+
+```move
+/// Callable after the delay window has passed.
+wrapper.execute_transfer(&clock, ctx);
+/// Emits OwnershipTransferred. Consumes the wrapper and delivers it to the recipient.
+```
+
+`execute_transfer` consumes the wrapper by value. After this call, the wrapper has been transferred to the scheduled recipient and no longer exists in the caller's scope. Calling it before `execute_after_ms` aborts with `EDelayNotElapsed`.
+
+### Scheduling an unwrap
+
+The same delay enforcement applies to recovering the raw capability:
+
+```move
+/// Schedule the unwrap
+wrapper.schedule_unwrap(&clock, ctx);
+/// Emits UnwrapScheduled
+
+/// After the delay has elapsed, executes the unwrap: Emits UnwrapExecuted, wrapper is consumed, and capability is returned.
+let treasury_cap = wrapper.unwrap(&clock, ctx);
+```
+
+### Canceling
+
+The owner can cancel a pending action at any time before execution:
+
+```move
+wrapper.cancel_schedule();
+```
+
+This clears the pending slot immediately, allowing a new action to be scheduled.
+
+---
+
+## Choosing a transfer policy
+
+**Use `two_step_transfer` when:**
+
+- The transfer can execute immediately once confirmed.
+- The principal initiating the transfer is a known, controlled key.
+- The risk you are guarding against is human error (wrong or non-existent address), not timing.
+
+**Use `delayed_transfer` when:**
+
+- Your protocol requires on-chain lead time before authority changes.
+- Users, DAOs, or monitoring systems need a window to detect and respond.
+- The delay should be a reliable, inspectable commitment visible to anyone.
+
+**Combining both:** The modules accept any `T: key + store`, so they compose. You could wrap a capability in `delayed_transfer` for the timing guarantee and use a `two_step_transfer` flow at the scheduling step for address-confirmation safety.
+
+---
+
+## Putting it together
+
+Here is a protocol example that uses `delayed_transfer` to wrap its admin capability, ensuring any ownership change is visible on-chain for 24 hours before it takes effect:
+
+```move
+module my_sui_app::governed_protocol;
+
+use openzeppelin_access::delayed_transfer::{Self, DelayedTransferWrapper};
+use openzeppelin_math::rounding;
+use openzeppelin_math::u64 as math_u64;
+use sui::clock::Clock;
+
+const MIN_DELAY_MS: u64 = 86_400_000; // 24 hours
+const EMathOverflow: u64 = 0;
+
+public struct ProtocolAdmin has key, store {
+ id: UID,
+ fee_bps: u64,
+}
+
+/// Initialize: create the admin cap and wrap it with a 24-hour transfer delay.
+/// `delayed_transfer::wrap` transfers the wrapper to the deployer internally.
+fun init(ctx: &mut TxContext) {
+ let admin = ProtocolAdmin {
+ id: object::new(ctx),
+ fee_bps: 30, // 0.3%
+ };
+ delayed_transfer::wrap(admin, MIN_DELAY_MS, ctx.sender(), ctx);
+}
+
+/// Update the fee rate. Borrows the admin cap mutably without changing ownership.
+public fun update_fee(
+ wrapper: &mut DelayedTransferWrapper,
+ new_fee_bps: u64,
+) {
+ let admin = delayed_transfer::borrow_mut(wrapper);
+ admin.fee_bps = new_fee_bps;
+}
+
+/// Compute a fee using the admin-configured rate and safe math.
+public fun compute_fee(
+ wrapper: &DelayedTransferWrapper,
+ amount: u64,
+): u64 {
+ let admin = delayed_transfer::borrow(wrapper);
+ math_u64::mul_div(amount, admin.fee_bps, 10_000, rounding::up())
+ .destroy_or!(abort EMathOverflow)
+}
+
+/// Schedule a transfer to a new admin. Visible on-chain for 24 hours.
+public fun schedule_admin_transfer(
+ wrapper: &mut DelayedTransferWrapper,
+ new_admin: address,
+ clock: &Clock,
+ ctx: &mut TxContext,
+) {
+ wrapper.schedule_transfer(new_admin, clock, ctx);
+}
+```
+
+This module combines both packages: `openzeppelin_math` for the fee calculation (explicit rounding, overflow handling) and `openzeppelin_access` for the ownership policy (24-hour delay, on-chain observability). Users monitoring the chain see the `TransferScheduled` event and can exit before a new admin takes over.
+
+Build and test:
+
+```bash
+sui move build
+sui move test
+```
+
+---
+
+## Next steps
+
+- [Access package guide](https://docs.openzeppelin.com/contracts-sui/1.x/access) for a quick overview and examples
+- [Integer Math package guide](/contracts-sui/1.x/math) for a quick overview and examples
+- [Access API reference](https://docs.openzeppelin.com/contracts-sui/1.x/api/access) for full function signatures and error codes
+- [Math Walkthrough](/contracts-sui/1.x/learn/math-walkthrough) for a detailed walkthrough of the math library
+- [Source code](https://github.com/OpenZeppelin/contracts-sui/tree/main/contracts/access) for inline documentation and implementation details
+- [GitHub issue tracker](https://github.com/OpenZeppelin/contracts-sui/issues) to report bugs or request features
+- [Sui Discord](https://discord.gg/sui) and [Sui Developer Forum](https://forums.sui.io/) to connect with other builders
diff --git a/content/contracts-sui/1.x/learn/index.mdx b/content/contracts-sui/1.x/learn/index.mdx
new file mode 100644
index 0000000..8b8908a
--- /dev/null
+++ b/content/contracts-sui/1.x/learn/index.mdx
@@ -0,0 +1,8 @@
+---
+title: Learn
+---
+
+Comprehensive guides for building with OpenZeppelin Contracts for Sui.
+
+* [Math Walkthrough](/contracts-sui/1.x/learn/math-walkthrough) - A detailed walkthrough of the `openzeppelin_math` package: rounding modes, overflow handling, and safe arithmetic primitives
+* [Access Walkthrough](/contracts-sui/1.x/learn/access-walkthrough) - A detailed walkthrough of the `openzeppelin_access` package: two-step and delayed ownership transfer policies for privileged capabilities
diff --git a/content/contracts-sui/1.x/learn/math-walkthrough.mdx b/content/contracts-sui/1.x/learn/math-walkthrough.mdx
new file mode 100644
index 0000000..ae77622
--- /dev/null
+++ b/content/contracts-sui/1.x/learn/math-walkthrough.mdx
@@ -0,0 +1,342 @@
+---
+title: Math Walkthrough
+---
+
+
+The example code snippets used within this walkthrough are experimental and have not been audited. They simply help exemplify the OpenZeppelin Sui Package usage.
+
+
+This walkthrough provides a detailed walkthrough of the `openzeppelin_math` package. It is intended for developers who want to understand not just *what* each function does, but *when* and *why* to use it. For a quick overview and getting-started examples, see the [Integer Math package guide](https://docs.openzeppelin.com/contracts-sui/1.x/math). For function-level signatures and parameters, see the [Integer Math API reference](https://docs.openzeppelin.com/contracts-sui/1.x/api/math).
+
+[Source Code](https://github.com/OpenZeppelin/contracts-sui/tree/main/math/core)
+
+---
+
+## Why explicit math matters on-chain
+
+Move's native integer arithmetic aborts on overflow and truncates on division. Both behaviors are silent protocol decisions. An abort at an unexpected boundary can lock a transaction. A truncation that always rounds against your users creates a systematic value leak.
+
+In May 2025, a single flawed overflow check in a shared math library led to the Cetus exploit, where approximately $223 million was drained from the largest DEX on Sui. The bug was not in novel math. It was in a `checked_shl`-type function that silently passed a value it should have rejected, corrupting a fixed-point intermediate that multiple protocols depended on.
+
+`openzeppelin_math` addresses these problems by making every boundary decision explicit: rounding is a named parameter you pass in, overflow returns `Option` that you then handle, and intermediate products are computed in wider types so they don't silently overflow before the final result is computed.
+
+---
+
+## Rounding as a protocol decision
+
+The package guide introduces the three rounding modes (`Down`, `Up`, `Nearest`). Here we'll dig deeper into *why* this matters and how to think about rounding in practice.
+
+Every function that divides, shifts, or roots a value requires a `RoundingMode` argument. There is no default, and the choice is not cosmetic. Rounding direction determines which side of a transaction absorbs fractional remainders, and in modern day blockchain applications, that determines where value leaks.
+
+### The vault example
+
+Consider a vault that issues shares for deposits:
+
+```move
+use openzeppelin_math::{rounding, u64 as math_u64};
+
+const EMathOverflow: u64 = 0;
+
+/// Deposit: convert tokens to shares, rounding DOWN.
+/// The depositor receives slightly fewer shares than the exact ratio.
+/// The vault keeps the fractional remainder.
+public fun deposit_shares(amount: u64, total_assets: u64, total_supply: u64): u64 {
+ math_u64::mul_div(amount, total_supply, total_assets, rounding::down())
+ .destroy_or!(abort EMathOverflow)
+}
+
+/// Withdrawal: convert shares to tokens, rounding DOWN.
+/// The withdrawer receives slightly fewer tokens than the exact ratio.
+/// The vault keeps the fractional remainder.
+public fun withdraw_assets(shares: u64, total_assets: u64, total_supply: u64): u64 {
+ math_u64::mul_div(shares, total_assets, total_supply, rounding::down())
+ .destroy_or!(abort EMathOverflow)
+}
+```
+
+Both directions round down so the vault never gives away more than it holds. If you rounded up on deposits (giving the depositor extra shares) or up on withdrawals (giving the withdrawer extra tokens), an attacker could repeatedly deposit and withdraw small amounts, extracting the rounding remainder each time. Making rounding explicit in every call forces you to make this decision consciously and makes it visible in code review.
+
+### When to use each mode
+
+- **`rounding::down()`** rounds toward zero (truncation). Use when the caller should absorb the remainder. This is the safe default for most protocol-to-user calculations.
+- **`rounding::up()`** rounds away from zero (ceiling). Use when the protocol should absorb the remainder, or when you need a conservative upper bound.
+- **`rounding::nearest()`** rounds to the nearest integer, with ties rounded up (round-half-up). Use for fee quotes and display calculations where rounding against either party on non-tie values is undesirable.
+
+---
+
+## Handling `Option` at the boundary
+
+The package guide shows `Option` returns briefly. Here we'll cover the patterns you'll use in practice.
+
+Every function whose result could exceed the type range returns `Option`. This includes `mul_div`, `mul_shr`, `checked_shl`, `checked_shr`, and `inv_mod`. Functions that cannot overflow (`average`, `sqrt`, logarithms) return the value directly.
+
+### Pattern: abort with a domain-specific error
+
+The most common pattern. Define an error constant in your module and abort if the math overflows:
+
+```move
+use openzeppelin_math::rounding;
+use openzeppelin_math::u64::{mul_div};
+
+const EMathOverflow: u64 = 0;
+
+public fun compute_fee(amount: u64, fee_bps: u64): u64 {
+ mul_div(amount, fee_bps, 10_000u64, rounding::up())
+ .destroy_or!(abort EMathOverflow)
+}
+```
+
+### Pattern: fallback to a safe value
+
+Useful when overflow means "cap at maximum" rather than "this is an error":
+
+```move
+use openzeppelin_math::{rounding};
+use openzeppelin_math::u64::{mul_div};
+
+public fun capped_scale(value: u64, multiplier: u64): u64 {
+ let result = mul_div(value, multiplier, 1_000u64, rounding::down());
+ if (result.is_some()) {
+ result.destroy_some()
+ } else {
+ 18_446_744_073_709_551_615u64 // u64::MAX
+ }
+}
+```
+
+### Pattern: propagate to the caller
+
+When your function is itself a building block, return `Option` and let the caller decide:
+
+```move
+use openzeppelin_math::{rounding};
+use openzeppelin_math::u64::{mul_div};
+
+public fun try_compute_shares(amount: u64, total_assets: u64, total_supply: u64): Option {
+ mul_div(amount, total_supply, total_assets, rounding::down())
+}
+```
+
+---
+
+## Core arithmetic: `mul_div`, `mul_shr`, `average`
+
+These three functions cover the fundamental operations behind swap pricing, fee calculations, interest accrual, and share-based accounting.
+
+
+The functions shown in this walkthrough are simplified to illustrate their usage. In a real implementation, they must be defined within proper Move function syntax.
+
+
+### `mul_div`
+
+Computes `(a * b) / denominator` with explicit rounding. The intermediate product is computed in a wider type (up to `u512` for `u256` inputs) so it never silently overflows. Returns `Option`.
+
+```move
+use openzeppelin_math::{rounding, u256};
+use openzeppelin_math::u256::{mul_div};
+
+// Price calculation: (reserve_out * amount_in) / (reserve_in + amount_in)
+let output = mul_div(reserve_out, amount_in, reserve_in + amount_in, rounding::down());
+```
+
+Instead of carrying out any multiply-then-divide operation manually (e.g., `(a * b) / c`), you can simply use `mul_div` instead. The manual version overflows when `a * b` exceeds the type range, even if the final result after division would have been safe.
+
+### `mul_shr`
+
+Computes `(a * b) >> shift` with rounding. Equivalent to `mul_div` with a power-of-two denominator, but faster. Returns `Option`.
+
+```move
+use openzeppelin_math::{rounding, u128};
+use openzeppelin_math::u128::{mul_shr};
+
+// Fixed-point multiplication: (price * quantity) / 2^64
+let result = mul_shr(price, quantity, 64u8, rounding::down());
+```
+
+Use `mul_shr` instead of `mul_div` when your denominator is a power of two. This is common in Q-notation fixed-point math (Q64.64, Q96, etc.) and the tick-math used in concentrated liquidity AMMs. The Cetus exploit involved exactly this class of operation.
+
+### `average`
+
+Computes the arithmetic mean of two values without overflow. Does not return `Option` since the result always fits in the input type.
+
+```move
+use openzeppelin_math::{rounding, u64};
+use openzeppelin_math::u64::{average};
+
+let midpoint = average(price_a, price_b, rounding::nearest());
+```
+
+A simple operation of `(a + b) / 2` overflows when both values are near the type maximum. `average` uses bit manipulation to avoid the intermediate sum entirely, making it safe regardless of input size. Use it for TWAP midpoints, interpolation, and any pairwise averaging.
+
+---
+
+## Safe bit operations: `checked_shl`, `checked_shr`
+
+Move's standard shift operators silently discard bits. `checked_shl` and `checked_shr` return `None` if any non-zero bit would be lost.
+
+```move
+use openzeppelin_math::u64::{checked_shl};
+
+let scaled = checked_shl(1u64, 63u8); // Some(9223372036854775808)
+let overflow = checked_shl(1u64, 64u8); // None: bit pushed out
+```
+
+This is the exact vulnerability class behind the Cetus exploit: a function that checked for overflow on a left shift had an off-by-one in its threshold comparison, allowing a corrupted value through. `checked_shl` catches this by shifting left and then shifting back; if the round-trip doesn't preserve the original value, it returns `None`.
+
+Use these anywhere you are scaling values via bit shifts and need a guarantee that no data is silently discarded.
+
+---
+
+## Introspection and logarithms
+
+These functions tell you about the structure of a number. They are used internally by the library and are useful in your own code for tick-math, encoding, and storage optimization.
+
+### `clz` and `msb`
+
+`clz` counts leading zero bits. `msb` returns the position of the most significant bit. Both return 0 for input 0.
+
+```move
+use openzeppelin_math::u64::{clz, msb};
+
+let leading_zeros = clz(256u64); // 55 (bit 8 is set)
+let highest_bit = msb(256u64); // 8
+```
+
+Use these when you need to know the minimum bit width required to represent a value: packing, encoding, or choosing a type width for further computation. Return types vary by module width (`u16` for `u256::clz`, `u8` for narrower types).
+
+### `log2`, `log10`, `log256`
+
+Integer logarithms with configurable rounding. All return 0 for input 0.
+
+```move
+use openzeppelin_math::{rounding};
+use openzeppelin_math::u256::{log2, log10, log256};
+
+let bits_needed = log2(1000u256, rounding::up()); // 10
+let decimal_digits = log10(1000u256, rounding::down()); // 3
+let bytes_needed = log256(1000u256, rounding::up()); // 2
+```
+
+**`log2`** is used in tick-math for concentrated liquidity AMMs, where price ranges map to integer ticks via a logarithmic function. Return type is `u16` for `u256`, `u8` for narrower types.
+
+**`log10`** appears in decimal-aware scaling and display formatting. Returns `u8` across all types.
+
+**`log256`** determines byte-length for encoding and serialization. Returns `u8` across all types.
+
+### `sqrt`
+
+Integer square root with configurable rounding. Returns 0 for input 0.
+
+```move
+use openzeppelin_math::{rounding};
+use openzeppelin_math::u256::{sqrt};
+
+let root_down = sqrt(10u256, rounding::down()); // 3
+let root_up = sqrt(10u256, rounding::up()); // 4
+```
+
+Square roots appear in concentrated liquidity pricing (`sqrtPriceX96`), geometric mean oracles, and volatility calculations. Rounding direction matters: rounding the wrong way in a price calculation creates an exploitable discrepancy.
+
+---
+
+## Modular arithmetic: `inv_mod`, `mul_mod`
+
+These functions operate in modular arithmetic, where values wrap around a modulus. They are essential for cryptographic verification, on-chain randomness schemes, and certain pricing algorithms.
+
+### `inv_mod`
+
+Returns the unique `x` where `value * x = 1 (mod modulus)` when the inputs are coprime. Returns `None` otherwise. Aborts if `modulus` is zero.
+
+```move
+use openzeppelin_math::u256::{inv_mod};
+
+let inv = inv_mod(19u256, 1_000_000_007u256); // Some(157_894_738)
+let none = inv_mod(50u256, 100u256); // None: not coprime
+```
+
+### `mul_mod`
+
+Multiplies two values modulo a modulus without intermediate overflow. Uses `u512` internally when both operands exceed `u128::MAX`.
+
+```move
+use openzeppelin_math::u256::{mul_mod};
+
+let product = mul_mod(a, b, 1_000_000_007u256);
+```
+
+---
+
+## Decimal scaling
+
+When bridging between tokens with different decimal precisions, `decimal_scaling` handles the multiplier arithmetic safely: overflow-checked on upcasts, explicitly truncating on downcasts.
+
+```move
+use openzeppelin_math::decimal_scaling;
+
+// Convert a 6-decimal token amount to 9-decimal internal representation
+let scaled_up = decimal_scaling::safe_upcast_balance(amount, 6, 9);
+
+// Convert back, truncating any fractional remainder
+let scaled_down = decimal_scaling::safe_downcast_balance(amount_9, 9, 6);
+```
+
+`safe_downcast_balance` discards any fractional remainder in the lower digits. If your protocol must account for that remainder rather than silently dropping it, capture it before the downcast.
+
+---
+
+## A note on `u512` and integer width
+
+The `openzeppelin_math` package includes a `u512` module that provides 512-bit unsigned integer operations. This module exists to make `u256` arithmetic consistent and overflow-safe. The per-width modules (`u64`, `u128`, `u256`) use `u512` internally for operations like `mul_div`, `mul_shr`, and `mul_mod` where intermediate products can exceed `u256`.
+
+For most protocol development on Sui, `u64` is the standard and recommended integer width. It is what the Sui framework uses for coin balances, timestamps, and gas accounting. Reach for `u128` or `u256` only when your domain genuinely requires wider values (e.g., high-precision fixed-point representations or cross-chain interoperability with systems that use 256-bit integers).
+
+`u512` is not intended for direct use in application code. If you find yourself reaching for it, consider whether the computation can be restructured to use `mul_div` or `mul_shr` on a narrower type instead, which handle the widening internally.
+
+---
+
+## Putting it together
+
+Here is a pricing module example that combines several functions from the library:
+
+```move
+use openzeppelin_math::{rounding};
+use openzeppelin_math::u64::{mul_div, sqrt};
+
+const EMathOverflow: u64 = 0;
+
+/// Quote a swap output with a 0.25% fee, rounded to nearest.
+public fun quote_with_fee(amount_in: u64, reserve_in: u64, reserve_out: u64): u64 {
+ // Apply fee: amount_in * 997.5 / 1000 (approximated as 9975 / 10000)
+ let effective_in = mul_div(amount_in, 9975u64, 10000u64, rounding::down())
+ .destroy_or!(abort EMathOverflow);
+
+ // Constant-product swap output
+ mul_div(reserve_out, effective_in, reserve_in + effective_in, rounding::down())
+ .destroy_or!(abort EMathOverflow)
+}
+
+/// Geometric mean of two prices, rounded down.
+public fun geometric_mean(price_a: u64, price_b: u64): u64 {
+ let product = mul_div(price_a, price_b, 1u64, rounding::down())
+ .destroy_or!(abort EMathOverflow);
+ sqrt(product, rounding::down())
+}
+```
+
+Build and test:
+
+```bash
+sui move build
+sui move test
+```
+
+---
+
+## Next steps
+
+- [Integer Math package guide](https://docs.openzeppelin.com/contracts-sui/1.x/math) for a quick overview and examples
+- [Integer Math API reference](https://docs.openzeppelin.com/contracts-sui/1.x/api/math) for full function signatures
+- [Access Walkthrough](/contracts-sui/1.x/learn/access-walkthrough) for the Access package walkthrough
+- [Source code](https://github.com/OpenZeppelin/contracts-sui/tree/main/math/core) for inline documentation and implementation details
+- [GitHub issue tracker](https://github.com/OpenZeppelin/contracts-sui/issues) to report bugs or request features
+- [Sui Discord](https://discord.gg/sui) and [Sui Developer Forum](https://forums.sui.io/) to connect with other builders
diff --git a/content/contracts-sui/index.mdx b/content/contracts-sui/index.mdx
index 850497c..038dfaf 100644
--- a/content/contracts-sui/index.mdx
+++ b/content/contracts-sui/index.mdx
@@ -12,6 +12,9 @@ import { latestStable } from "./latest-versions.js";
Install packages, set up a Move project, and run a first end-to-end integration.
+
+ In-depth walkthroughs covering safe arithmetic, rounding, overflow handling, and ownership transfer policies.
+
## Packages
diff --git a/src/navigation/sui/current.json b/src/navigation/sui/current.json
index 521f0e6..f278187 100644
--- a/src/navigation/sui/current.json
+++ b/src/navigation/sui/current.json
@@ -13,6 +13,28 @@
"name": "Overview",
"url": "/contracts-sui/1.x"
},
+ {
+ "type": "folder",
+ "name": "Learn",
+ "defaultOpen": false,
+ "children": [
+ {
+ "type": "page",
+ "name": "Overview",
+ "url": "/contracts-sui/1.x/learn"
+ },
+ {
+ "type": "page",
+ "name": "Math Walkthrough",
+ "url": "/contracts-sui/1.x/learn/math-walkthrough"
+ },
+ {
+ "type": "page",
+ "name": "Access Walkthrough",
+ "url": "/contracts-sui/1.x/learn/access-walkthrough"
+ }
+ ]
+ },
{
"type": "folder",
"name": "Packages",