diff --git a/content/contracts-sui/1.x/api/collections.mdx b/content/contracts-sui/1.x/api/collections.mdx
new file mode 100644
index 00000000..432388e0
--- /dev/null
+++ b/content/contracts-sui/1.x/api/collections.mdx
@@ -0,0 +1,902 @@
+---
+title: Collections API Reference
+---
+
+This page documents the public API of `openzeppelin_collections` for OpenZeppelin Contracts for Sui `v1.x`.
+
+### `sorted_map` [toc] [#sorted_map]
+
+
+
+```move
+use openzeppelin_collections::sorted_map;
+```
+
+A generic, ordered key-value map with O(log N) lookup and ordered navigation (head/tail, floor/ceiling, next key, sorted pages). Every operation loads exactly one stored object, so capacity is bounded by Sui's object size limit (~250 KB), not by any per-transaction dynamic-field cap. `SortedMap` is a UID-less value shaped like `sui::vec_map::VecMap`, with no identity of its own or dynamic fields, so you embed it in your own `has key` object and gate access with your own entry functions. The module emits no events.
+
+The API splits into bare and `_by` forms. Bare macros (`upsert!`, `borrow!`, ...) assume the built-in integer `<` and compile only for integer keys; `_by` macros take a strict less-than comparator lambda and are required for `address`, struct, or any other non-integer key. Values only need `store`, so a resource `V` like `Coin` is supported. Such a map cannot be dropped; drain every value via `remove!`/`pop_front`/`pop_back`, then call `destroy_empty`.
+
+
+The map stores no comparator. Ordering is defined per call by the strict less-than lambda you supply, and it MUST be a strict total order threaded consistently to every call on a given map. The library cannot detect a violation, and the failure is silent: a non-strict `<=` causes duplicate inserts and missed lookups/removes, and mixing `<` with `>` across calls corrupts order. In tests, call `is_well_formed_by!` after a `_by` sequence to catch comparator mistakes.
+
+
+Types
+
+- [`Entry`](#sorted_map-Entry)
+- [`SortedMap`](#sorted_map-SortedMap)
+
+Functions
+
+- [`new()`](#sorted_map-new)
+- [`singleton(key, value)`](#sorted_map-singleton)
+- [`destroy_empty(map)`](#sorted_map-destroy_empty)
+- [`length(map)`](#sorted_map-length)
+- [`is_empty(map)`](#sorted_map-is_empty)
+- [`head(map)`](#sorted_map-head)
+- [`tail(map)`](#sorted_map-tail)
+- [`pop_front(map)`](#sorted_map-pop_front)
+- [`pop_back(map)`](#sorted_map-pop_back)
+- [`keys(map)`](#sorted_map-keys)
+
+Macros
+
+- [`from_sorted_keys_values_by!(keys, values, lt)`](#sorted_map-from_sorted_keys_values_by)
+- [`from_sorted_keys_values!(keys, values)`](#sorted_map-from_sorted_keys_values)
+- [`contains_by!(map, key, lt)`](#sorted_map-contains_by)
+- [`contains!(map, key)`](#sorted_map-contains)
+- [`borrow_by!(map, key, lt)`](#sorted_map-borrow_by)
+- [`borrow!(map, key)`](#sorted_map-borrow)
+- [`borrow_mut_by!(map, key, lt)`](#sorted_map-borrow_mut_by)
+- [`borrow_mut!(map, key)`](#sorted_map-borrow_mut)
+- [`add_by!(map, key, value, lt)`](#sorted_map-add_by)
+- [`add!(map, key, value)`](#sorted_map-add)
+- [`upsert_by!(map, key, value, lt)`](#sorted_map-upsert_by)
+- [`upsert!(map, key, value)`](#sorted_map-upsert)
+- [`remove_by!(map, key, lt)`](#sorted_map-remove_by)
+- [`remove!(map, key)`](#sorted_map-remove)
+- [`find_next_by!(map, key, include, lt)`](#sorted_map-find_next_by)
+- [`find_next!(map, key, include)`](#sorted_map-find_next)
+- [`find_prev_by!(map, key, include, lt)`](#sorted_map-find_prev_by)
+- [`find_prev!(map, key, include)`](#sorted_map-find_prev)
+- [`next_key_by!(map, key, lt)`](#sorted_map-next_key_by)
+- [`next_key!(map, key)`](#sorted_map-next_key)
+- [`prev_key_by!(map, key, lt)`](#sorted_map-prev_key_by)
+- [`prev_key!(map, key)`](#sorted_map-prev_key)
+- [`keys_from_by!(map, from, include, limit, lt)`](#sorted_map-keys_from_by)
+- [`keys_from!(map, from, include, limit)`](#sorted_map-keys_from)
+- [`is_well_formed_by!(map, lt)`](#sorted_map-is_well_formed_by)
+- [`is_well_formed!(map)`](#sorted_map-is_well_formed)
+
+Errors
+
+- [`EKeyNotFound`](#sorted_map-EKeyNotFound)
+- [`ENotEmpty`](#sorted_map-ENotEmpty)
+- [`EEmpty`](#sorted_map-EEmpty)
+- [`EKeysNotStrictlyIncreasing`](#sorted_map-EKeysNotStrictlyIncreasing)
+- [`EUnequalLengths`](#sorted_map-EUnequalLengths)
+- [`EKeyAlreadyExists`](#sorted_map-EKeyAlreadyExists)
+
+#### Types [!toc] [#sorted_map-Types]
+
+
+One key-value pair, stored inline in the map's vector. Its fields are private; read them through the forced-public `key`/`value` accessors if a macro body requires it.
+
+Abilities materialize jointly over `K` and `V`: `Entry` is `copy + drop + store`, while `Entry>` is store-only.
+
+
+
+A map kept sorted by key. Every operation loads exactly one stored object, so it is bounded by object size (~250 KB), not by any per-transaction dynamic-field cap; lookup is O(log N) and writes are O(N).
+
+A pure value with no `UID` or dynamic fields, so it embeds directly in an integrator's object, exactly like `sui::vec_map::VecMap`. `copy`/`drop` materialize only when both `K` and `V` allow them: `SortedMap` is `copy + drop + store`; `SortedMap>` is store-only and must be drained then `destroy_empty`'d.
+
+Across the supported (macro) API the backing vector is strictly increasing under the consistently supplied comparator: sorted, with no duplicate keys.
+
+
+#### Functions [!toc] [#sorted_map-Functions]
+
+
+Creates a new, empty map. Takes no `&mut TxContext`: a `SortedMap` is a value, not an object.
+
+
+
+Creates a map holding a single `key`/`value` entry. O(1); needs no comparator, since one entry is trivially sorted. For more than one entry from pre-sorted data, use `from_sorted_keys_values!`.
+
+
+
+Destroys an empty map. This is the only terminal for a map whose value type lacks `drop` (e.g. `SortedMap>`): drain every value via `remove!`/`pop_front`/`pop_back` first, then call this.
+
+Aborts with `ENotEmpty` if the map still holds entries.
+
+
+
+Returns the number of entries.
+
+
+
+Returns `true` iff the map holds no entries.
+
+
+
+Returns the smallest key under the comparator, or `none` if the map is empty. O(1). With a reverse comparator this returns the largest numeric key.
+
+
+
+Returns the largest key under the comparator, or `none` if the map is empty. O(1).
+
+
+
+Removes and returns the smallest entry as a `(key, value)` pair. O(N), as it shifts every remaining entry; prefer `pop_back` for bulk drains, which is O(1).
+
+Aborts with `EEmpty` if the map is empty.
+
+
+
+Removes and returns the largest entry as a `(key, value)` pair. O(1) (no shift).
+
+Aborts with `EEmpty` if the map is empty.
+
+
+
+Returns all keys in ascending comparator order as an owned `vector`. O(N) in output size with no `limit`; for large or near-ceiling maps prefer the paged `keys_from!`. Loads exactly one stored object regardless of N.
+
+
+#### Macros [!toc] [#sorted_map-Macros]
+
+
+Builds a map from parallel `keys`/`values` that MUST already be strictly increasing under `lt` (sorted, no duplicate keys). O(n): one pass validates each adjacent pair, then appends at the back without a per-element search. Prefer this to a loop of `upsert_by!`, which is O(n^2) for unsorted input. If your keys are not yet ordered, sort them under the same comparator first, then call this.
+
+Unlike `sorted_set::from_keys!` (which de-duplicates), this aborts on any out-of-order or duplicate key: values are conserved (a resource `V` can never be silently displaced), so a duplicate cannot be collapsed away.
+
+Aborts with `EUnequalLengths` if `keys` and `values` differ in length.
+
+Aborts with `EKeysNotStrictlyIncreasing` if `keys` is not strictly increasing under `lt`.
+
+
+
+`from_sorted_keys_values_by!` with the built-in integer `<`.
+
+Aborts with `EUnequalLengths` or `EKeysNotStrictlyIncreasing`; see `from_sorted_keys_values_by!`.
+
+
+
+Returns `true` iff `key` is present, under `lt`. Pure, total read: agrees exactly with `borrow_by!` succeeding, since both route through the same binary search.
+
+
+
+`contains_by!` with the built-in integer `<`.
+
+
+
+Immutably borrows `key`'s value, under `lt`. There is deliberately no non-aborting `Option<&V>` variant (Move cannot put a reference in an `Option`); use `contains_by!` first when the key may be absent.
+
+Aborts with `EKeyNotFound` if `key` is absent.
+
+
+
+`borrow_by!` with the built-in integer `<`.
+
+Aborts with `EKeyNotFound` if `key` is absent.
+
+
+
+Mutably borrows `key`'s value, under `lt`. Yields `&mut V`, never a mutable entry reference, so the key cannot be desynced from its sorted position.
+
+Aborts with `EKeyNotFound` if `key` is absent.
+
+
+
+`borrow_mut_by!` with the built-in integer `<`.
+
+Aborts with `EKeyNotFound` if `key` is absent.
+
+
+
+Inserts `key`/`value` under `lt`, aborting if `key` is already present (length + 1). `key` is taken by value and moved into storage; nothing is returned.
+
+Matches `sui::vec_map::insert`: a strict insert that refuses to touch an existing entry, so a duplicate is always a caller bug rather than a silent overwrite. Use `upsert_by!` instead when replacing an existing value is the intended behavior; it returns the displaced value rather than aborting.
+
+Aborts with `EKeyAlreadyExists` if `key` is already present.
+
+
+
+`add_by!` with the built-in integer `<`.
+
+Aborts with `EKeyAlreadyExists` if `key` is already present.
+
+
+
+Inserts `key`/`value`, or replaces the value if `key` is already present, under `lt`. Returns `some(old_value)` on replace (length unchanged), `none` on a fresh insert (length + 1). The displaced value is returned and never dropped, so a resource `V` is safe; `K` must be `drop` only because the previously stored key is dropped on replace (last-write-wins for the key bytes, observable only under a coarse comparator).
+
+This is a deliberate divergence from `sui::vec_map::insert`, which aborts on a duplicate key: `upsert_by!` is a total upsert. For abort-on-duplicate, prefer `add!` (a strict insert); or, when `V` has `drop`, assert on the returned option (`assert!(m.upsert!(k, v).is_none(), E)`). For a resource `V` the returned option cannot be dropped, so bind it, assert `is_none()`, and consume it with `destroy_none()`.
+
+
+
+`upsert_by!` with the built-in integer `<`. Returns `some(old)` on replace, `none` on a fresh insert.
+
+
+
+Removes `key`'s entry and returns the removed `(key, value)` tuple, under `lt` (length - 1, order preserved). Both the key and the value are returned, so a non-`drop` key or resource value is conserved, not destroyed.
+
+Aborts with `EKeyNotFound` if `key` is absent.
+
+
+
+`remove_by!` with the built-in integer `<`.
+
+Aborts with `EKeyNotFound` if `key` is absent.
+
+
+
+Returns the smallest key `>= key` when `include` (the ceiling), else the smallest key `> key` (strict next); `none` if there is no such key. Pure, total read. Any returned key satisfies `contains_by!`.
+
+
+
+`find_next_by!` with the built-in integer `<`.
+
+
+
+Returns the largest key `<= key` when `include` (the floor), else the largest key `< key` (strict prev); `none` if there is no such key. Pure, total read. Any returned key satisfies `contains_by!`.
+
+
+
+`find_prev_by!` with the built-in integer `<`.
+
+
+
+Returns the smallest key strictly greater than `key`, or `none`. Sugar for `find_next_by!` with `include = false`. `next_key!` of the tail returning `none` is the forward-cursor termination signal.
+
+
+
+`next_key_by!` with the built-in integer `<`.
+
+
+
+Returns the largest key strictly less than `key`, or `none`. Sugar for `find_prev_by!` with `include = false`. `prev_key!` of the head returning `none` is the backward-cursor termination signal.
+
+
+
+`prev_key_by!` with the built-in integer `<`.
+
+
+
+Returns up to `limit` keys in strict ascending order: a contiguous run starting at the first key `>= from` (when `include`) or `> from` (strict). Returns at most `limit` keys; fewer if the tail is reached.
+
+Resume a page by passing the last returned key back as `from` with `include == false`: successive pages have no overlap and no gap, so concatenating them reconstructs the tail exactly. `limit == 0`, an empty map, or `from` past the tail all yield the empty vector. There is no descending pagination; use a reverse comparator.
+
+
+
+`keys_from_by!` with the built-in integer `<`.
+
+
+
+Returns `true` iff the map's entries are strictly increasing under `lt`, meaning sorted with no duplicate keys. This is the order check for tests, directly re-verifying the sorted-order property that production code maintains by construction but never re-validates. Call it after a `_by` sequence to catch a comparator mistake. Returns `bool` rather than asserting, so callers compose it into their own assertions.
+
+`#[test_only]`: absent from published bytecode and not callable from production consumer code, but visible to a dependent's test code.
+
+
+
+`is_well_formed_by!` with the built-in integer `<`. `#[test_only]`.
+
+
+#### Errors [!toc] [#sorted_map-Errors]
+
+
+A key was not present in the map. Raised by `borrow!`/`borrow_mut!`/`remove!` (and their `_by` forms) on an absent key.
+
+
+
+`destroy_empty` was called on a non-empty map.
+
+
+
+`pop_front`/`pop_back` was called on an empty map.
+
+
+
+A bulk constructor (`from_sorted_keys_values!`/`_by`) received keys that are not strictly increasing under the comparator (out of order, or a duplicate).
+
+
+
+A bulk constructor (`from_sorted_keys_values!`/`_by`) received `keys` and `values` of different lengths.
+
+
+
+A key was already present in the map. Raised by `add!`/`add_by!` on a duplicate key.
+
+
+#### Internal helpers [!toc] [#sorted_map-Internals]
+
+
+Move 2024 macro hygiene requires every symbol a macro body references to be `public` at the consumer's expansion site, so the following helpers are `public` but are NOT part of the supported API. In particular, `insert_at`/`remove_at` write at a caller-given index with no ordering check, so calling them directly can silently corrupt sorted order and invalidate every subsequent lookup, insertion, and removal. Use the macro API instead.
+
+
+Forced-public internals: `search!`, `entries`, `key`, `value`, `insert_at`, `remove_at`, `value_at`, `value_at_mut`, `assert_key_found`, `assert_key_absent`, `assert_equal_lengths`, `assert_strictly_increasing`.
+
+### `sorted_set` [toc] [#sorted_set]
+
+
+
+```move
+use openzeppelin_collections::sorted_set;
+```
+
+A generic, ordered set of unique keys that iterates in comparator order, filling the gap `sui::vec_set` leaves. `SortedSet` is a thin wrapper over `SortedMap` (a set is a map whose values carry no information, exactly as `BTreeSet = BTreeMap` in Rust). It is a UID-less value with every key inline in one vector; a bare set is not `key`, so embed it in your own `has key` object. Because the value is always the trivial `copy + drop + store` `Unit`, `SortedSet` has exactly the abilities `K` does.
+
+The set stores no comparator. Order is defined per call by a strict less-than lambda `lt` you supply, and it must be the same strict total order on every call for a given set. The library cannot detect a violation. An inconsistent or non-strict comparator desorts the set (wrong membership answers, though no key's bytes are lost and all keys remain recoverable via `keys`); a coarse (non-injective) comparator collapses byte-distinct compare-equal keys into one element, keeping the last-written key's bytes. The bare (non-`_by`) macros use the built-in integer `<` and remove the footgun for integer keys.
+
+`upsert!` returns `bool` and never aborts on a duplicate (diverging from `sui::vec_set::insert`); `add!` is the strict counterpart that aborts on a duplicate, and `remove!` aborts on an absent key, matching `vec_set`. Costs mirror the wrapped map: O(log N) membership, O(N) insert/remove, exactly one stored-object access per operation, and the same ~250 KB object-size ceiling. The module emits no events. A UID-less value has no on-chain identity, so emit events from your own entry functions.
+
+Types
+
+- [`Unit`](#sorted_set-Unit)
+- [`SortedSet`](#sorted_set-SortedSet)
+
+Functions
+
+- [`new()`](#sorted_set-new)
+- [`singleton(key)`](#sorted_set-singleton)
+- [`destroy_empty(set)`](#sorted_set-destroy_empty)
+- [`length(set)`](#sorted_set-length)
+- [`is_empty(set)`](#sorted_set-is_empty)
+- [`head(set)`](#sorted_set-head)
+- [`tail(set)`](#sorted_set-tail)
+- [`pop_front(set)`](#sorted_set-pop_front)
+- [`pop_back(set)`](#sorted_set-pop_back)
+- [`keys(set)`](#sorted_set-keys)
+
+Macros
+
+- [`from_keys_by!(keys, lt)`](#sorted_set-from_keys_by)
+- [`from_keys!(keys)`](#sorted_set-from_keys)
+- [`from_sorted_keys_by!(keys, lt)`](#sorted_set-from_sorted_keys_by)
+- [`from_sorted_keys!(keys)`](#sorted_set-from_sorted_keys)
+- [`contains_by!(set, key, lt)`](#sorted_set-contains_by)
+- [`contains!(set, key)`](#sorted_set-contains)
+- [`add_by!(set, key, lt)`](#sorted_set-add_by)
+- [`add!(set, key)`](#sorted_set-add)
+- [`upsert_by!(set, key, lt)`](#sorted_set-upsert_by)
+- [`upsert!(set, key)`](#sorted_set-upsert)
+- [`remove_by!(set, key, lt)`](#sorted_set-remove_by)
+- [`remove!(set, key)`](#sorted_set-remove)
+- [`find_next_by!(set, key, include, lt)`](#sorted_set-find_next_by)
+- [`find_next!(set, key, include)`](#sorted_set-find_next)
+- [`find_prev_by!(set, key, include, lt)`](#sorted_set-find_prev_by)
+- [`find_prev!(set, key, include)`](#sorted_set-find_prev)
+- [`next_key_by!(set, key, lt)`](#sorted_set-next_key_by)
+- [`next_key!(set, key)`](#sorted_set-next_key)
+- [`prev_key_by!(set, key, lt)`](#sorted_set-prev_key_by)
+- [`prev_key!(set, key)`](#sorted_set-prev_key)
+- [`keys_from_by!(set, from, include, limit, lt)`](#sorted_set-keys_from_by)
+- [`keys_from!(set, from, include, limit)`](#sorted_set-keys_from)
+
+Errors
+
+- [`EEmpty`](#sorted_set-EEmpty)
+- [`EKeysNotSorted`](#sorted_set-EKeysNotSorted)
+- [`ENotEmpty`](#sorted_set-ENotEmpty)
+
+#### Types [!toc] [#sorted_set-Types]
+
+
+Internal membership marker so a set can reuse `SortedMap`. A zero-field empty struct carrying no data; it serializes to 1 BCS byte and has nothing to read or destructure.
+
+Declared `copy, drop, store` so `SortedSet` has exactly the abilities `K` does. It is `public` only because it appears in the forced-public accessors' signatures (macro hygiene); consumers never construct it or read it. Its layout is frozen at first publish and must stay a zero-field empty struct.
+
+
+
+An ordered set of unique keys, backed by one sorted vector via `SortedMap`.
+
+A pure value with no `UID` or dynamic fields, so it embeds directly in an integrator's object, exactly like `sui::vec_set::VecSet`. Because the value is always the `copy + drop + store` `Unit`, `SortedSet` has exactly the abilities `K` does: with a `copy + drop + store` key it is `copy + drop + store`; with a non-`drop` (e.g. resource) key it is store-only and must be drained then `destroy_empty`'d.
+
+Across every public operation, the keys are strictly increasing under the (consistently supplied) comparator: sorted, no duplicates.
+
+
+#### Functions [!toc] [#sorted_set-Functions]
+
+
+Creates a new, empty set. Takes no `&mut TxContext`: a `SortedSet` is a value, not an object. `length` is 0 and `is_empty` is true.
+
+
+
+Returns a set containing exactly the one given key.
+
+
+
+Destroys an empty set.
+
+Only needed when `K` lacks `drop` (e.g. a resource key): such a `SortedSet` is itself non-`drop` and cannot fall out of scope, so drain every key via `remove!`/`pop_front`/`pop_back` first, then call this. A set of droppable keys never needs it.
+
+Aborts with `ENotEmpty` if the set still holds keys. The abort fires at this module's location, not the wrapped map's.
+
+
+
+Returns the number of distinct keys.
+
+
+
+Returns `true` iff the set holds no keys.
+
+
+
+Returns the smallest key under the comparator, or `none` if the set is empty. O(1). With a reverse comparator this is the largest numeric key.
+
+
+
+Returns the largest key under the comparator, or `none` if the set is empty. O(1).
+
+
+
+Removes and returns the smallest key. Length decreases by 1. O(N): it shifts every remaining entry (`pop_back` is O(1)), so a front-heavy drain loop is quadratic.
+
+Aborts with `EEmpty` if the set is empty, at this module's location (the wrapped map's `EEmpty` is never reached through here).
+
+
+
+Removes and returns the largest key. Length decreases by 1. O(1).
+
+Aborts with `EEmpty` if the set is empty, at this module's location (the wrapped map's `EEmpty` is never reached through here).
+
+
+
+Returns all keys in strict ascending (comparator) order as an owned, sorted, and duplicate-free `vector`. Not a reference: the set stores `Entry` internally, so there is no `vector` to borrow (contrast `vec_set::keys`).
+
+O(N) in output size with no limit; this is the one read whose result scales with N. For large or near-ceiling sets, prefer the paged `keys_from!`.
+
+
+#### Macros [!toc] [#sorted_set-Macros]
+
+
+Builds a set from a vector of keys by idempotent insertion, under `$lt`. O(N^2) (one search per key). Duplicates are silently collapsed; the result holds each distinct key once, in comparator order, so the result's `length` is the number of distinct keys, not the input length. This diverges from `sui::vec_set::from_keys`, which aborts on a duplicate; to reject duplicates instead, build then assert `length` equals the input length.
+
+Under a coarse (non-injective) comparator, byte-distinct compare-equal keys collapse to one element keeping the last one's bytes (each re-insert is a last-write-wins upsert).
+
+
+
+`from_keys_by!` with the built-in integer `<`. De-duplicates; see [`from_keys_by!`](#sorted_set-from_keys_by). Returns a set of the distinct keys, in ascending order.
+
+
+
+Builds a set from keys that are already sorted (non-decreasing) under `$lt`. O(N): one pass validates each adjacent pair and appends at the back without a per-element search, so prefer this over the O(N^2) `from_keys!` when the input is pre-sorted.
+
+De-duplicates exactly like `from_keys!`: a run of compare-equal keys collapses to one element, keeping the last key's bytes (observable only under a coarse, non-injective comparator), matching `upsert!`'s last-write-wins rule. A set has no value to lose, so a duplicate collapses rather than aborts, diverging from the map's `from_sorted_keys_values!`, which aborts on a duplicate to conserve values. The only rejection is genuinely unsorted input; if your keys are not yet ordered, use `from_keys!` or sort them first.
+
+Aborts with `EKeysNotSorted` if `keys` has an adjacent pair not sorted under `lt` (a strictly decreasing pair), at this module's location.
+
+
+
+`from_sorted_keys_by!` with the built-in integer `<`. De-duplicates; see [`from_sorted_keys_by!`](#sorted_set-from_sorted_keys_by). Returns a set of the distinct keys, in ascending order.
+
+Aborts with `EKeysNotSorted` if `keys` is not ascending.
+
+
+
+Returns `true` iff `key` is present, under `$lt`. Pure, total read (never aborts). O(log N).
+
+
+
+`contains_by!` with the built-in integer `<`. Returns `true` iff `key` is present.
+
+
+
+Inserts `key`, under `$lt`, aborting if it is already present (length + 1; `contains!(key)` flips false to true). Nothing is returned. The strict counterpart to the total `upsert_by!`: this matches `sui::vec_set::insert`, which also aborts on a duplicate, so a duplicate is a caller bug rather than a silent no-op. Use `upsert!`/`upsert_by!` when a duplicate should be absorbed silently (and reported via the returned bool) instead of aborting.
+
+Aborts with `sorted_map::EKeyAlreadyExists` if `key` is already present (delegated: the abort fires at the wrapped map's location).
+
+
+
+`add_by!` with the built-in integer `<`.
+
+Aborts with `sorted_map::EKeyAlreadyExists` if `key` is already present.
+
+
+
+Inserts `key`, under `$lt`. Idempotent and total (never aborts); `key` is taken by value. On a fresh insert: length + 1 and `contains!(key)` flips false to true. On a duplicate: length unchanged, but the stored key is overwritten with this one (last-write-wins for the key bytes, observable only under a coarse comparator). Returns `true` iff the key was newly added, `false` if it was already present.
+
+Diverges from `vec_set::insert` (which aborts on a duplicate); for that behavior call `add!`, or assert on the returned bool.
+
+
+
+`upsert_by!` with the built-in integer `<`. Returns `true` iff the key was newly added.
+
+
+
+Removes `key`, under `$lt`, and returns the removed key. On success: length - 1 and `contains!(key)` flips true to false. Matches `vec_set::remove`, which also aborts when the key is absent.
+
+Aborts with `sorted_map::EKeyNotFound` if `key` is absent (delegated: the abort fires at the wrapped map's location).
+
+
+
+`remove_by!` with the built-in integer `<`. Returns the removed key.
+
+Aborts with `sorted_map::EKeyNotFound` if `key` is absent.
+
+
+
+Returns the smallest key `>= key` when `include` is true (the ceiling), else the smallest key `> key` (strict next); `none` if there is no such key. Pure, total read; any returned key satisfies `contains!`.
+
+
+
+`find_next_by!` with the built-in integer `<`. Returns the ceiling/strict-next key, or `none`.
+
+
+
+Returns the largest key `<= key` when `include` is true (the floor), else the largest key `< key` (strict prev); `none` if there is no such key. Pure, total read; any returned key satisfies `contains!`.
+
+
+
+`find_prev_by!` with the built-in integer `<`. Returns the floor/strict-prev key, or `none`.
+
+
+
+Returns the smallest key strictly greater than `key`, or `none`. Sugar for `find_next_by!` with `include == false`. `next_key!` of the tail returning `none` is the forward-cursor termination signal.
+
+
+
+`next_key_by!` with the built-in integer `<`. Returns the strict-next key, or `none`.
+
+
+
+Returns the largest key strictly less than `key`, or `none`. Sugar for `find_prev_by!` with `include == false`. `prev_key!` of the head returning `none` is the backward-cursor termination signal.
+
+
+
+`prev_key_by!` with the built-in integer `<`. Returns the strict-prev key, or `none`.
+
+
+
+Returns up to `limit` keys in strict ascending order: a contiguous run starting at the first key `>= from` (when `include`) or `> from` (strict); fewer than `limit` only at the tail. Resume a page by passing the last returned key back as `from` with `include == false`: successive pages have no overlap and no gap. `limit == 0`, an empty set, or `from` past the tail all yield the empty vector.
+
+
+
+`keys_from_by!` with the built-in integer `<`. Returns up to `limit` keys in ascending order.
+
+
+#### Errors [!toc] [#sorted_set-Errors]
+
+
+Raised when `pop_front`/`pop_back` is called on an empty set. Asserted at this module's location, distinct from the wrapped map's `EEmpty`.
+
+
+
+Raised when a sorted constructor (`from_sorted_keys!`/`from_sorted_keys_by!`) receives keys that are not sorted under the comparator, meaning a strictly decreasing adjacent pair. Asserted at this module's location.
+
+
+
+Raised when `destroy_empty` is called on a set that still holds keys. Asserted at this module's location, distinct from the wrapped map's `ENotEmpty`.
+
+
+#### Internal helpers [!toc] [#sorted_set-Internals]
+
+
+The functions `inner`, `inner_mut`, `unit`, and `assert_sorted` are `public` only because Move 2024 macro hygiene requires every symbol a macro body references to be public at the consumer's expansion site. They are not part of the supported API. In particular, `inner_mut` hands out `&mut SortedMap`: driving map operations on it directly with an inconsistent comparator (or `insert_at`/`remove_at` at a wrong index) can desort the set and invalidate every subsequent lookup, insertion, and removal. The corruption is order-only and local to that one set; no value is ever lost. Use the macro API instead.
+
diff --git a/content/contracts-sui/1.x/collections.mdx b/content/contracts-sui/1.x/collections.mdx
new file mode 100644
index 00000000..7296a623
--- /dev/null
+++ b/content/contracts-sui/1.x/collections.mdx
@@ -0,0 +1,112 @@
+---
+title: Collections
+---
+
+The `openzeppelin_collections` package is the ordered-collections family for OpenZeppelin Contracts for Sui. It provides `SortedMap`, an ordered key/value collection kept in one sorted vector, and `SortedSet`, a thin set wrapper over `SortedMap`, mirroring Rust's `BTreeSet` relationship to `BTreeMap`.
+
+Both are UID-less value types, shaped like `sui::vec_map::VecMap` and `sui::vec_set::VecSet` but kept in key order: you embed them as fields in your own `has key` objects, and every operation touches exactly one stored object. Beyond point lookups they answer ordered questions such as head/tail, floor/ceiling, next/previous key, and sorted pages via `keys_from!`.
+
+Order is comparator-driven. The bare macros (`upsert!`, `contains!`, `remove!`, ...) use the built-in integer `<` for unsigned-integer keys; the `_by` variants take a strict less-than comparator for custom key types or descending order.
+
+
+Collections store no comparator. Order is defined per call by the `lt` closure you supply to the `_by` macros, and it must be a strict total order threaded consistently to every call on a given collection. The library cannot detect a violation: a non-strict (`<=`) or inconsistent comparator silently corrupts order, causing duplicate inserts, missed removes, and wrong membership answers. For integer keys, the bare (non-`_by`) macros remove this footgun entirely.
+
+
+## Usage
+
+The package is not yet published to the Move Registry (MVR), so install it as a git dependency pinned to the release tag in `Move.toml`:
+
+```toml
+[dependencies]
+openzeppelin_collections = { git = "https://github.com/OpenZeppelin/contracts-sui.git", subdir = "collections", rev = "v1.5.0" }
+```
+
+Import the module you need:
+
+```move
+use openzeppelin_collections::sorted_map;
+// or
+use openzeppelin_collections::sorted_set;
+```
+
+## Examples
+
+### Price book with a `SortedMap`
+
+A shared book embeds a `SortedMap` (price to resting size). `u64` keys sort under the built-in integer `<`, so every call uses the bare macros.
+
+```move
+module my_protocol::price_book;
+
+use openzeppelin_collections::sorted_map::{Self, SortedMap};
+
+public struct PriceBook has key {
+ id: UID,
+ levels: SortedMap, // price -> resting size, ascending (best = head)
+}
+
+public fun create_and_share(ctx: &mut TxContext) {
+ let book = PriceBook { id: object::new(ctx), levels: sorted_map::new() };
+ transfer::share_object(book);
+}
+
+/// Add `size` at `price`, merging into an existing level if present.
+public fun place(book: &mut PriceBook, price: u64, size: u64) {
+ if (book.levels.contains!(&price)) {
+ let level = book.levels.borrow_mut!(&price);
+ *level = *level + size;
+ } else {
+ book.levels.upsert!(price, size);
+ };
+}
+
+/// Best (lowest) price, or `none` if the book is empty.
+public fun best_price(book: &PriceBook): Option {
+ book.levels.head()
+}
+
+/// Up to `limit` prices ascending from the first price `>= from`. Resume a page by
+/// passing the last returned price back as `from` with `include = false`.
+public fun page(book: &PriceBook, from: u64, include: bool, limit: u64): vector {
+ book.levels.keys_from!(&from, include, limit)
+}
+```
+
+### Watchlist with a `SortedSet`
+
+A watchlist embeds a `SortedSet` and uses `upsert!`'s `bool` return (`true` only on a fresh insert, never aborting on a duplicate) to emit an event exactly the first time an id is watched.
+
+```move
+module my_app::watchlist;
+
+use openzeppelin_collections::sorted_set::{Self, SortedSet};
+use sui::event;
+
+public struct Watchlist has key {
+ id: UID,
+ ids: SortedSet,
+}
+
+public struct IdWatched has copy, drop { id: u64 }
+
+public fun create(ctx: &mut TxContext): Watchlist {
+ Watchlist { id: object::new(ctx), ids: sorted_set::new() }
+}
+
+/// Add a token id; emit only the first time it is watched.
+public fun watch(watchlist: &mut Watchlist, id: u64) {
+ if (watchlist.ids.upsert!(id)) {
+ event::emit(IdWatched { id });
+ }
+}
+```
+
+## Choosing between `sorted_map` and `sorted_set`
+
+- Use `sorted_map` when each key carries a value, such as price levels, tick registries, leaderboards, or payout vaults. Values may be resources like `Coin`: drain every entry, then call `destroy_empty`.
+- Use `sorted_set` when only ordered membership matters, such as watchlists, allow/deny lists, or deduplicated id registries. Its `upsert!` returns a `bool` instead of aborting on duplicates.
+- Both answer the same ordered queries (`head`/`tail`, `find_next!`/`find_prev!`, `keys_from!` pages) and keep a single-object footprint; byte size is the only capacity ceiling.
+
+## API Reference
+
+Use the full function-level reference here: [Collections API](/contracts-sui/1.x/api/collections).
diff --git a/content/contracts-sui/1.x/index.mdx b/content/contracts-sui/1.x/index.mdx
index 368b80bc..b19b6d58 100644
--- a/content/contracts-sui/1.x/index.mdx
+++ b/content/contracts-sui/1.x/index.mdx
@@ -2,11 +2,12 @@
title: Contracts for Sui 1.x
---
-**OpenZeppelin Contracts for Sui v1.x** ships four core packages:
+**OpenZeppelin Contracts for Sui v1.x** ships five core packages:
- `openzeppelin_math` for deterministic integer arithmetic, configurable rounding, and decimal scaling.
- `openzeppelin_fp_math` for 9-decimal fixed-point arithmetic (`UD30x9`, `SD29x9`) backed by `u128`.
- `openzeppelin_access` for role-based authorization and ownership-transfer wrappers around privileged `key + store` objects.
+- `openzeppelin_collections` for ordered key/value and set data structures (`sorted_map`, `sorted_set`).
- `openzeppelin_utils` for embeddable building blocks; its first module, `rate_limiter`, provides multi-strategy rate limiting.
## Quickstart
@@ -37,7 +38,7 @@ You only need the dependencies your app actually uses. Add what you need and dro
### 3. Verify `Move.toml`
-`mvr add` updates `Move.toml` automatically. With all three installed it should include:
+`mvr add` updates `Move.toml` automatically. With all four installed it should include:
```toml
[dependencies]
@@ -80,6 +81,7 @@ sui move test
## Picking a package
- Need role-based authorization for privileged functions or controlled transfer of admin/treasury/upgrade capabilities? Use [Access](/contracts-sui/1.x/access).
+- Need ordered maps or sets with deterministic iteration? Use [Collections](/contracts-sui/1.x/collections).
- Need fractional values like prices, fees, rates, or signed deltas? Use [Fixed-Point Math](/contracts-sui/1.x/fixed-point).
- Need integer arithmetic with safe overflow and explicit rounding? Use [Integer Math](/contracts-sui/1.x/math).
- Need to throttle withdrawals, meter per-user budgets, gate action reuse, or delay an action? Use [Rate Limiter](/contracts-sui/1.x/rate-limiter).
@@ -88,8 +90,8 @@ The packages compose. A typical protocol module imports `openzeppelin_math` for
## Next steps
-- Package guides: [Integer Math](/contracts-sui/1.x/math), [Fixed-Point Math](/contracts-sui/1.x/fixed-point), [Access](/contracts-sui/1.x/access), [Utilities](/contracts-sui/1.x/utils).
+- Package guides: [Integer Math](/contracts-sui/1.x/math), [Fixed-Point Math](/contracts-sui/1.x/fixed-point), [Access](/contracts-sui/1.x/access), [Collections](/contracts-sui/1.x/collections), [Utilities](/contracts-sui/1.x/utils).
- Access modules: [RBAC](/contracts-sui/1.x/access-control), [Two-Step Transfer](/contracts-sui/1.x/two-step-transfer), [Delayed Transfer](/contracts-sui/1.x/delayed-transfer).
- Utilities modules: [Rate Limiter](/contracts-sui/1.x/rate-limiter).
- Learn: [Role Based Access Control](/contracts-sui/1.x/guides/access-control).
-- API reference: [Integer Math](/contracts-sui/1.x/api/math), [Fixed-Point Math](/contracts-sui/1.x/api/fixed-point), [Access](/contracts-sui/1.x/api/access), [Utilities](/contracts-sui/1.x/api/utils).
+- API reference: [Integer Math](/contracts-sui/1.x/api/math), [Fixed-Point Math](/contracts-sui/1.x/api/fixed-point), [Access](/contracts-sui/1.x/api/access), [Collections](/contracts-sui/1.x/api/collections), [Utilities](/contracts-sui/1.x/api/utils).
diff --git a/content/contracts-sui/index.mdx b/content/contracts-sui/index.mdx
index ba9f99a1..9ee7b606 100644
--- a/content/contracts-sui/index.mdx
+++ b/content/contracts-sui/index.mdx
@@ -23,6 +23,9 @@ import { latestStable } from "./latest-versions.js";
Role-based authorization and ownership-transfer policies for privileged protocol operations.
+
+ Package-level guide for ordered data structures, including the comparator model and usage boundaries.
+
9-decimal fixed-point types (`UD30x9`, `SD29x9`) for prices, fees, rates, and signed balance deltas.
@@ -40,6 +43,9 @@ import { latestStable } from "./latest-versions.js";
Explore the complete access API, including its functions, types, events, and errors.
+
+ Explore the complete collections API, including all functions, macros, types, and expected error conditions for integration.
+
Explore the complete fixed-point math API, including its types, functions, and errors.
diff --git a/public/llms.txt b/public/llms.txt
index c706723f..e8800424 100644
--- a/public/llms.txt
+++ b/public/llms.txt
@@ -316,11 +316,13 @@ Each ecosystem section lists the smart-contract libraries and language-specific
- [Packages — Access — RBAC](https://docs.openzeppelin.com/contracts-sui/1.x/access-control)
- [Packages — Access — Two-Step Transfer](https://docs.openzeppelin.com/contracts-sui/1.x/two-step-transfer)
- [Packages — Access — Delayed Transfer](https://docs.openzeppelin.com/contracts-sui/1.x/delayed-transfer)
+- [Packages — Collections](https://docs.openzeppelin.com/contracts-sui/1.x/collections)
- [Packages — Utils — Overview](https://docs.openzeppelin.com/contracts-sui/1.x/utils)
- [Packages — Utils — Rate Limiter](https://docs.openzeppelin.com/contracts-sui/1.x/rate-limiter)
- [API Reference — Integer Math](https://docs.openzeppelin.com/contracts-sui/1.x/api/math)
- [API Reference — Fixed-Point Math](https://docs.openzeppelin.com/contracts-sui/1.x/api/fixed-point)
- [API Reference — Access](https://docs.openzeppelin.com/contracts-sui/1.x/api/access)
+- [API Reference — Collections](https://docs.openzeppelin.com/contracts-sui/1.x/api/collections)
- [API Reference — Utils](https://docs.openzeppelin.com/contracts-sui/1.x/api/utils)
## Midnight
diff --git a/src/navigation/sui/current.json b/src/navigation/sui/current.json
index 55aef69f..d19ab816 100644
--- a/src/navigation/sui/current.json
+++ b/src/navigation/sui/current.json
@@ -65,6 +65,11 @@
}
]
},
+ {
+ "type": "page",
+ "name": "Collections",
+ "url": "/contracts-sui/1.x/collections"
+ },
{
"type": "folder",
"name": "Utils",
@@ -103,6 +108,11 @@
"name": "Access",
"url": "/contracts-sui/1.x/api/access"
},
+ {
+ "type": "page",
+ "name": "Collections",
+ "url": "/contracts-sui/1.x/api/collections"
+ },
{
"type": "page",
"name": "Utils",