Skip to content

feat(nft): sentrix-nft crate — native Proof Asset NFT (pure domain logic)#774

Merged
github-actions[bot] merged 2 commits into
mainfrom
feat/sentrix-nft-crate
Jun 3, 2026
Merged

feat(nft): sentrix-nft crate — native Proof Asset NFT (pure domain logic)#774
github-actions[bot] merged 2 commits into
mainfrom
feat/sentrix-nft-crate

Conversation

@satyakwok
Copy link
Copy Markdown
Collaborator

@satyakwok satyakwok commented Jun 3, 2026

Summary

New workspace crate sentrix-nft — native NFT as a Proof Asset / reputation layer (validator/builder/contributor/bug-hunter/genesis proofs, soulbound badges). Not a marketplace, not a JPEG/PFP collection tool. EVM ERC-721/1155 remain a separate path.

Pure domain logic only: no MDBX, trie, block execution, RPC, networking, consensus, marketplace, or bridge. Metadata is URI-only — no image bytes, no IPFS/Arweave upload logic.

Crate layout

  • collection.rsNftCollection + all token state transitions
  • token.rsNftToken (transferable/frozen/burned, optional uri + integrity hashes)
  • registry.rsNftRegistry, deterministic SRC721_<sha256(creator|seed)> id
  • events.rsNftEvent (typed, returned by every state change)
  • error.rsNftError (typed)

Design

  • Collection-scoped; String addresses + u64 token ids (matches the SRC-20 native rail).
  • Soulbound: transferable=false; transfer fails with typed NotTransferableno approval bypasses it (checked before authorization).
  • Per-token + collection freeze; metadata lock.
  • Strict no-reuse: a burned token id is retired forever (tombstone) — append-only audit trail for the reputation layer.
  • max_supply counts ever-minted (burning never frees a slot).

Intentional behavior changes vs the earlier in-core nft.rs

  1. Errors: SentrixError → typed NftError.
  2. Token model: parallel HashMaps → NftToken struct (enables per-token soulbound/freeze).
  3. Strict no-reuse replaces the earlier allow-re-mint-after-burn behavior (and its test), per the audit-trail requirement.

Integration (thin)

sentrix-core depends on sentrix-nft; sentrix-core/src/nft.rs is now a facade (pub use sentrix_nft::*) + nft_err_to_sentrix() to bridge errors at the core boundary. No block-execution/trie/RPC wiring yet (deferred). sentrix-nft has zero sentrix-* deps → no cycle.

Tests / gate

  • 36 unit + 2 doctests in sentrix-nft; 2 facade tests in core. Earlier 21 in-core tests superseded (same behaviors + soulbound, freeze, metadata-lock, operator, strict-no-reuse).
  • cargo test (workspace): pass · cargo fmt --check: clean · cargo clippy --workspace --all-targets -- -D warnings: clean

Deferred

MDBX/state integration, trie/state-root commitment, native NFT tx execution, JSON-RPC, explorer/wallet, ERC-721 adapter.

Summary by CodeRabbit

  • New Features

    • Added native NFT (Proof Asset) system supporting collection deployment, token minting, transfers, approvals, burning, and metadata management with authorization and transfer restriction controls.
  • Documentation

    • Added comprehensive documentation for NFT functionality including usage examples and feature scope.
  • Chores

    • Extended workspace configuration with new NFT and supporting crates.

Review Change Stack

Extracts native NFT logic into a standalone workspace crate, sentrix-nft,
as a Proof Asset / reputation layer (validator/builder/contributor proofs,
soulbound badges) — not a marketplace.

Crate (pure domain only; no storage/trie/consensus/RPC/marketplace/bridge):
- collection.rs  NftCollection + all token state transitions
- token.rs       NftToken (transferable/frozen/burned, optional uri+hashes)
- registry.rs    NftRegistry, deterministic SRC721_ id from sha256(creator|seed)
- events.rs      NftEvent (typed, returned by every state change)
- error.rs       NftError (typed, replaces stringly errors)

Model: collection-scoped, String addresses + u64 token ids (matches SRC-20
native rail). Soulbound supported (transferable=false); soulbound transfer
fails with a typed NotTransferable error and no approval can bypass it.
Per-token + collection freeze, metadata lock. Metadata is URI-only — no
image bytes, no IPFS/Arweave upload logic.

Intentional behavior changes from the earlier in-core nft.rs:
- errors: SentrixError → typed NftError
- token model: parallel HashMaps → NftToken struct (per-token soulbound/freeze)
- STRICT NO-REUSE: a burned token id is retired forever (tombstone). The
  earlier code allowed re-minting a burned id; that behavior and its test are
  replaced, per the reputation-layer audit-trail requirement.
- max_supply now counts ever-minted (burning never frees a slot)

Integration: sentrix-core depends on sentrix-nft; crates/sentrix-core/src/nft.rs
is now a thin facade (pub use sentrix_nft::*) plus nft_err_to_sentrix() to map
NftError into SentrixError at the core boundary (free fn — orphan rule). No
block-execution/trie/RPC wiring yet (deferred).

sentrix-nft has zero sentrix-* dependencies (no cycle risk).

Tests: 36 unit + 2 doctests in sentrix-nft, + 2 facade tests in core. The
earlier 21 in-core tests are superseded by these (same behaviors + soulbound,
freeze, metadata-lock, operator, strict-no-reuse). Full workspace test passes;
cargo fmt --check and clippy --workspace -D warnings clean.
@github-actions github-actions Bot enabled auto-merge (squash) June 3, 2026 11:03
@satyakwok satyakwok self-assigned this Jun 3, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented Jun 3, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 3, 2026

Warning

Rate limit exceeded

@satyakwok has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 32 minutes and 55 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: c26b47d7-4357-41bf-8494-e268e9c2d638

📥 Commits

Reviewing files that changed from the base of the PR and between 7bf3eda and 91820cf.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock, !**/*.lock
📒 Files selected for processing (5)
  • crates/sentrix-nft/Cargo.toml
  • crates/sentrix-nft/src/collection.rs
  • crates/sentrix-nft/src/lib.rs
  • crates/sentrix-nft/src/registry.rs
  • crates/sentrix-nft/src/token.rs
📝 Walkthrough

Walkthrough

This PR introduces a new sentrix-nft crate providing pure domain logic for NFT collections and tokens as a "Proof Asset" layer. The implementation includes a deterministic collection ID derivation system, a state machine for collection operations (mint, transfer, approve, burn, metadata management), event emission for all state changes, and comprehensive validation of permissions and invariants. The crate is integrated into sentrix-core via a facade module that re-exports the NFT API and provides error boundary mapping between the NFT domain errors and the workspace error type.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Description check ❓ Inconclusive The PR description is comprehensive and well-structured, covering summary, scope, design, integration, and tests. However, it does not follow the required template with explicit checkbox sections for scope, checks, and deploy impact. Align the description with the template structure: add explicit Scope checkboxes, Checks section (forge build/test/fmt/slither/storage), Linked issue, and Deploy impact sections for clarity and consistency.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: introducing a new sentrix-nft crate for native Proof Asset NFT logic.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/sentrix-nft-crate

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/sentrix-nft/src/collection.rs`:
- Around line 200-223: The state mutations in mint/transfer/burn are not atomic:
code in functions like mint (where you call self.tokens.insert(...), update
self.balances, self.total_supply, self.total_minted) mutates storage before
performing fallible checked_add/checked_sub, so an error can leave
partially-applied state; change each affected function (mint, transfer, burn) to
perform all validation and arithmetic using local/temporary variables (e.g.,
compute new_balance = bal.checked_add(1)? / checked_sub(1)? and
new_total_supply/new_total_minted using checked_add/checked_sub and return
errors if any) and only after all checks succeed commit the writes (insert
NftToken, set self.balances[to] = new_balance, set self.total_supply =
new_total_supply, set self.total_minted = new_total_minted) to ensure atomic
state transitions.
- Around line 20-62: The struct currently exposes mutable invariants (tokens,
balances, token_approvals, operator_approvals, total_supply, total_minted,
frozen, metadata_mutable, default_transferable) as pub; change those fields to
private (remove pub) on NftCollection and provide a controlled API (impl) with
safe methods such as mint, burn, transfer, approve_token, set_operator_approval,
freeze_collection/unfreeze_collection, update_metadata, and read-only getters
for public info (id, creator, admin, name, symbol, etc.); ensure all state
transitions go through these methods so invariants (max_supply, never-reuse
token ids, balance counts, approval semantics, freeze checks, metadata
mutability) are enforced inside the corresponding methods (e.g.,
NftCollection::mint, ::transfer, ::burn, ::approve_token).
- Around line 432-456: The update_collection_metadata function currently allows
changes even after lock_metadata is called because it never checks the
metadata_mutable flag; add a guard at the top of update_collection_metadata
(after the frozen/admin checks) that returns Err(NftError::MetadataLocked) (or
add that error variant if missing) when self.metadata_mutable is false, and
apply the identical metadata_mutable check to the corresponding token-level
updater (e.g., update_token_metadata or the function covering lines 473-481) so
token metadata cannot be changed after lock_metadata either.
- Around line 340-357: The set_approval_for_all function currently allows anyone
to set approvals for any owner; enforce that only the owner can change their
approvals by validating the caller identity before modifying operator_approvals.
Update set_approval_for_all (or its call sites) to accept or obtain the
authenticated caller (e.g., a caller: &str parameter or fetch from the execution
context) and add a check like caller == owner (returning NftError::Unauthorized
or similar) before the entry/insert into self.operator_approvals; keep the
existing empty/operator equality checks and then perform the mutation only after
the ownership check.

In `@crates/sentrix-nft/src/registry.rs`:
- Around line 16-19: compute_collection_id currently builds the preimage with a
simple "|" separator which is ambiguous when inputs contain "|" and can lead to
hash collisions; change it to use an unambiguous, canonical encoding (e.g.,
length-prefixed concatenation or a binary serializer like bincode/serde) so the
preimage is always uniquely recoverable from (creator, seed). Update
compute_collection_id to build the payload by writing the creator length then
the creator bytes then the seed length then the seed bytes (or use
bincode::serialize) before hashing; apply the same change to the other similar
function referenced at lines 52-54 so both ID derivations use the same
unambiguous encoding.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: c646770c-5817-43c5-b24b-d90060f71af5

📥 Commits

Reviewing files that changed from the base of the PR and between 8dc4f43 and 7bf3eda.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock, !**/*.lock
📒 Files selected for processing (12)
  • Cargo.toml
  • crates/sentrix-core/Cargo.toml
  • crates/sentrix-core/src/lib.rs
  • crates/sentrix-core/src/nft.rs
  • crates/sentrix-nft/Cargo.toml
  • crates/sentrix-nft/README.md
  • crates/sentrix-nft/src/collection.rs
  • crates/sentrix-nft/src/error.rs
  • crates/sentrix-nft/src/events.rs
  • crates/sentrix-nft/src/lib.rs
  • crates/sentrix-nft/src/registry.rs
  • crates/sentrix-nft/src/token.rs

Comment thread crates/sentrix-nft/src/collection.rs
Comment thread crates/sentrix-nft/src/collection.rs
Comment thread crates/sentrix-nft/src/collection.rs
Comment thread crates/sentrix-nft/src/collection.rs Outdated
Comment thread crates/sentrix-nft/src/registry.rs
@satyakwok satyakwok disabled auto-merge June 3, 2026 11:08
Self-reviewed with the code-review skill BEFORE pushing this time.

CodeRabbit findings (all 5 valid, all fixed):
1. set_approval_for_all now takes caller and enforces caller == owner —
   previously any caller could set operator approvals for any owner.
2. compute_collection_id uses length-prefixed hashing
   (len(creator)‖creator‖len(seed)‖seed) instead of "{creator}|{seed}" —
   the separator was ambiguous: ("a|b","c") and ("a","b|c") collided.
   Regression test pins the disambiguation.
3. NftCollection invariant fields are private with read-only getters.
4. mint/transfer/burn compute all checked arithmetic into locals and only
   commit after every fallible step succeeds — no partial state on overflow.
5. update_collection_metadata now enforces metadata_mutable (was only on the
   token path) — lock_metadata can no longer be bypassed at collection level.

Additional gaps found by self-review (same class as #3, fixed):
- NftToken invariant fields (owner/uri/transferable/frozen/burned) are now
  private with getters + pub(crate) setters used only by NftCollection.
  uri_hash/metadata_hash stay public (no behavioral invariant).
- NftRegistry.collections is now private (deploy_collection is the only way
  to create a collection; serde still persists it).

Deliberately NOT changed (design calls, not bugs): approve/burn do not check
the collection/token frozen flag — freeze blocks transfers; whether it should
also block burns/approvals is a policy decision, left as-is to avoid an
unrequested behavior change.

owner_of now returns Option<&str> (was Option<&String>) since token owner is
read through a getter. New regression tests: set_approval_for_all non-owner
rejected, collection-id separator collision, collection metadata lock.

cargo test (workspace) pass; cargo fmt --check + clippy --workspace -D warnings clean.
@github-actions github-actions Bot enabled auto-merge (squash) June 3, 2026 11:30
@github-actions github-actions Bot merged commit 6a4fe2b into main Jun 3, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant