Skip to content

Comments

Mini-DIT: Experimental Decentralized Identity Token Prototype with Foundry Tests#6

Open
VivekLatpate wants to merge 12 commits intoStabilityNexus:mainfrom
VivekLatpate:main
Open

Mini-DIT: Experimental Decentralized Identity Token Prototype with Foundry Tests#6
VivekLatpate wants to merge 12 commits intoStabilityNexus:mainfrom
VivekLatpate:main

Conversation

@VivekLatpate
Copy link

@VivekLatpate VivekLatpate commented Jan 27, 2026

Overview

This Pull Request introduces Mini-DIT, a lightweight experimental prototype exploring early design and security trade-offs for Decentralized Identity Tokens (DIT) as part of the DIT 2026 research direction under Stability Nexus discussions.

This implementation is a learning-by-building prototype, intended to validate protocol assumptions and identity-endorsement flows before formal specification work.
It is not production-ready and is submitted for review and feedback only.


Key Features Implemented

Identity Token Core

  • Self-issued Identity NFTs built on ERC-721
  • Transferable identity tokens (supports key rotation / recovery flows)
  • On-chain metadata URI references

Trust & Endorsements

  • On-chain endorsements between identity tokens
  • Customizable endorsement tags (e.g., worked-with, verified)
  • Revocable endorsements by original endorser

Security & Revocation

  • Identity compromise / revocation signaling
  • Compromised identities blocked from issuing endorsements

Testing Framework

  • Full Foundry-based test suite
  • Tests cover minting, transfers, endorsements, revocations, and compromise flows
  • All tests passing locally and in CI

Tooling & Repository Improvements

  • Foundry project structure (src/, test/, foundry.toml)
  • Proper .gitignore for build artifacts
  • Initialized OpenZeppelin and forge-std submodules
  • GitHub Actions CI for automated testing
  • Updated README with research context and usage instructions

Out of Scope (By Design)

  • Zero-knowledge claim verification
  • Reputation weighting or governance logic
  • Sybil-resistance mechanisms
  • Production access control or economics
  • Formal verification

These are intentionally excluded to keep this prototype focused on core identity and endorsement mechanics.


How to Run

git checkout mini-dit-prototype
git submodule update --init --recursive
forge install
forge test


<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

## Release Notes

* **New Features**
  * Introduced MiniDIT smart contract enabling identity management and endorsement capabilities for decentralized identity tokens.
  * Added sample Counter smart contract for demonstration.

* **Documentation**
  * Completely redesigned README with clearer prototype overview, setup instructions, and tech stack details.

* **Tests**
  * Added comprehensive test suite covering all new smart contract functionality.

* **Chores**
  * Set up GitHub Actions CI pipeline for automated testing.
  * Added Foundry configuration and project dependencies.
  * Refactored project-wide ignore patterns.

<sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 27, 2026

📝 Walkthrough

Walkthrough

This PR establishes a Foundry-based smart contract project featuring a Decentralized Identity Tokens (MiniDIT) system. Changes include CI/CD workflow configuration, foundry setup, two smart contracts (Counter as a basic example and MiniDIT as an ERC721-based identity system with endorsement mechanics), corresponding test suites, updated documentation, and git submodule dependencies.

Changes

Cohort / File(s) Summary
CI/CD & Configuration
.github/workflows/test.yml, foundry.toml, .gitmodules
Adds GitHub Actions workflow to run Foundry tests on push/PR, foundry configuration with profiles and OpenZeppelin remapping, and git submodule declarations for forge-std and openzeppelin-contracts libraries
Git Configuration
.gitignore
Replaces LaTeX-specific ignore rules with generalized development patterns (build artifacts, dependencies, environment files, OS/IDE files, logs)
Documentation
README.md
Rewrites documentation from maturity-focused to prototype-oriented, includes tech stack, installation steps, and project scope details
Library Submodules
lib/forge-std, lib/openzeppelin-contracts
Pins specific commits for Foundry standard library and OpenZeppelin contract dependencies
Smart Contracts
src/Counter.sol, src/MiniDIT.sol
Introduces basic Counter contract with increment/setNumber, and main MiniDIT contract implementing ERC721-based identity management with endorsements, compromise tracking, and revocation
Deployment Scripts
script/Counter.s.sol
Adds Forge deployment script for Counter contract
Test Suites
test/Counter.t.sol, test/MiniDIT.t.sol
Provides Foundry tests covering Counter operations and MiniDIT identity/endorsement/compromise workflows

Sequence Diagram(s)

sequenceDiagram
    actor Alice as Alice (Caller 1)
    participant MiniDIT
    actor Bob as Bob (Caller 2)

    Alice->>MiniDIT: mintIdentity("alice.json")
    MiniDIT->>MiniDIT: nextTokenId = 1, identities[1] = Identity(...)
    MiniDIT->>Alice: ERC721 mint token `#1`

    Bob->>MiniDIT: mintIdentity("bob.json")
    MiniDIT->>MiniDIT: nextTokenId = 2, identities[2] = Identity(...)
    MiniDIT->>Bob: ERC721 mint token `#2`

    Alice->>MiniDIT: endorse(tokenId=1, toTokenId=2, tag="trusted")
    MiniDIT->>MiniDIT: Validate: token 2 exists, token 1 not compromised
    MiniDIT->>MiniDIT: endorsements[2].push(Endorsement(1, "trusted", false))
    MiniDIT->>Alice: emit Endorsed event

    Alice->>MiniDIT: revokeEndorsement(toTokenId=2, index=0)
    MiniDIT->>MiniDIT: endorsements[2][0].revoked = true
    MiniDIT->>Alice: emit EndorsementRevoked event
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 Hop, hop, the code hops in anew,
With identities minted and endorsements true,
Forge-std and OpenZeppelin light the way,
A DIT prototype born this wonderful day! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and accurately describes the main change: introducing Mini-DIT, an experimental Decentralized Identity Token prototype with Foundry-based testing infrastructure and supporting setup.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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


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
Contributor

@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: 2

🤖 Fix all issues with AI agents
In `@README.md`:
- Around line 72-114: README.md has an unclosed code block and leftover git
instructions causing formatting to break; fix by closing the opening
triple-backtick after the initial git checkout (re-insert ```bash around the
"Install dependencies:" section and add matching ``` before "forge install" and
around the "forge test" example), remove the accidental commit/push lines at the
bottom, add proper Markdown headings (e.g., "## 🧪 Run Tests", "## 🔍 Smart
Contract Overview", "## 🧠 Research Context"), and replace the bulleted function
list with a table documenting mintIdentity(string metadataURI),
endorse(fromTokenId, toTokenId, tag), revokeEndorsement(toTokenId, index), and
markCompromised(tokenId) so the README renders correctly.

In `@src/MiniDIT.sol`:
- Around line 46-63: The endorse function currently allows self-endorsement; add
a check in endorse to require(fromTokenId != toTokenId, "Cannot self-endorse")
(or similar) immediately after the ownerOf(fromTokenId) check so that an
identity cannot endorse itself; this touches the endorse function and prevents
pushing Endorsement(fromTokenId, tag, false) into endorsements[toTokenId] when
fromTokenId equals toTokenId.
🧹 Nitpick comments (6)
.github/workflows/test.yml (2)

26-29: Using nightly Foundry version may cause flaky CI.

Nightly builds can introduce breaking changes or regressions that fail your CI unexpectedly. Consider pinning to a stable release for reproducibility:

♻️ Suggested change
       - name: Install Foundry
         uses: foundry-rs/foundry-toolchain@v1
         with:
-          version: nightly
+          version: stable

3-6: Consider restricting trigger branches to avoid unnecessary CI runs.

Without branch filters, CI runs on every push to every branch. For typical workflows:

♻️ Optional: Add branch filters
 on:
   push:
+    branches:
+      - main
   pull_request:
   workflow_dispatch:
src/MiniDIT.sol (2)

31-37: Consider reordering for clearer checks-effects-interactions pattern.

_safeMint can trigger a callback to the recipient if it's a contract. While the current code is safe (tokenId is captured before the call), setting identities[tokenId] before _safeMint would follow the CEI pattern more explicitly.

♻️ Optional reorder
 function mintIdentity(string calldata metadataURI) external {
     uint256 tokenId = nextTokenId++;
+    identities[tokenId] = Identity(metadataURI, false);
     _safeMint(msg.sender, tokenId);
-    identities[tokenId] = Identity(metadataURI, false);
     emit IdentityMinted(msg.sender, tokenId);
 }

65-71: Consider adding idempotency check for revocation.

The function allows revoking an already-revoked endorsement, which emits a misleading EndorsementRevoked event. Adding a check prevents redundant event emissions.

♻️ Optional improvement
 function revokeEndorsement(uint256 toTokenId, uint256 index) external {
     Endorsement storage e = endorsements[toTokenId][index];
     require(ownerOf(e.fromTokenId) == msg.sender, "Not endorser");
+    require(!e.revoked, "Already revoked");

     e.revoked = true;
     emit EndorsementRevoked(e.fromTokenId, toTokenId);
 }
test/MiniDIT.t.sol (2)

24-37: Assert the tag field for complete coverage.

The test skips asserting the tag value. Consider adding an assertion to verify the endorsement tag is stored correctly.

✅ Proposed improvement
-    (uint256 fromToken,, bool revoked) = dit.endorsements(1,0);
+    (uint256 fromToken, string memory tag, bool revoked) = dit.endorsements(1,0);
     assertEq(fromToken, 0);
+    assertEq(tag, "worked-with");
     assertFalse(revoked);

56-65: Add test for compromised identity endorsement rejection.

The contract blocks compromised identities from issuing endorsements (a key security feature), but there's no test verifying this behavior.

✅ Proposed test addition
function testCompromisedCannotEndorse() public {
    vm.prank(alice);
    dit.mintIdentity("ipfs://alice");

    vm.prank(bob);
    dit.mintIdentity("ipfs://bob");

    vm.prank(alice);
    dit.markCompromised(0);

    vm.prank(alice);
    vm.expectRevert("From identity compromised");
    dit.endorse(0, 1, "worked-with");
}

Comment on lines 72 to +114
```bash
npm run dev
# or
yarn dev
# or
pnpm dev
```
git clone https://github.com/<your-username>/IdentityTokens-EVM-Contracts.git
cd IdentityTokens-EVM-Contracts
git checkout mini-dit-prototype
Install dependencies:

#### 4. Open your Browser
forge install
🧪 Run Tests
forge test
Expected output:

Navigate to [http://localhost:3000](http://localhost:3000) to see the application.
All MiniDIT tests passing
🔍 Smart Contract Overview
Mint Identity NFT
mintIdentity(string metadataURI)
Endorse Another Identity
endorse(fromTokenId, toTokenId, tag)
Revoke Endorsement
revokeEndorsement(toTokenId, index)
Mark Identity as Compromised
markCompromised(tokenId)
🧠 Research Context
This prototype is based on ongoing discussions around:

---
DIT (Decentralized Identity Tokens)

## Contributing
TNT (Trust Network Tokens)

We welcome contributions of all kinds! To contribute:
VouchMe-style identity endorsement flows

1. Fork the repository and create your feature branch (`git checkout -b feature/AmazingFeature`).
2. Commit your changes (`git commit -m 'Add some AmazingFeature'`).
3. Run the development workflow commands to ensure code quality:
- `npm run format:write`
- `npm run lint:fix`
- `npm run typecheck`
4. Push your branch (`git push origin feature/AmazingFeature`).
5. Open a Pull Request for review.
Under the Stability Nexus / The Stable Order research direction.

If you encounter bugs, need help, or have feature requests:

- Please open an issue in this repository providing detailed information.
- Describe the problem clearly and include any relevant logs or screenshots.
---

# ✅ After pasting

We appreciate your feedback and contributions!
Run:

© 2025 The Stable Order.
```bash
git add README.md
git commit -m "Fix README markdown formatting and links"
git push origin mini-dit-prototype
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix broken markdown formatting and remove leftover instructions.

The markdown structure breaks starting at line 72. The code block is not properly closed, causing subsequent sections (Installation, Run Tests, Smart Contract Overview, Research Context) to render incorrectly. Additionally, lines 107-114 contain git commit instructions that appear to be accidentally included.

📝 Proposed fix
 ```bash
 git clone https://github.com/<your-username>/IdentityTokens-EVM-Contracts.git
 cd IdentityTokens-EVM-Contracts
 git checkout mini-dit-prototype
-Install dependencies:
+```
+
+Install dependencies:
 
+```bash
 forge install
-🧪 Run Tests
+```
+
+---
+
+## 🧪 Run Tests
+
+```bash
 forge test
-Expected output:
+```
 
-All MiniDIT tests passing
-🔍 Smart Contract Overview
-Mint Identity NFT
-mintIdentity(string metadataURI)
-Endorse Another Identity
-endorse(fromTokenId, toTokenId, tag)
-Revoke Endorsement
-revokeEndorsement(toTokenId, index)
-Mark Identity as Compromised
-markCompromised(tokenId)
-🧠 Research Context
-This prototype is based on ongoing discussions around:
+Expected output: All MiniDIT tests passing.
 
-DIT (Decentralized Identity Tokens)
+---
 
-TNT (Trust Network Tokens)
+## 🔍 Smart Contract Overview
 
-VouchMe-style identity endorsement flows
+| Function | Description |
+|----------|-------------|
+| `mintIdentity(string metadataURI)` | Mint Identity NFT |
+| `endorse(fromTokenId, toTokenId, tag)` | Endorse Another Identity |
+| `revokeEndorsement(toTokenId, index)` | Revoke Endorsement |
+| `markCompromised(tokenId)` | Mark Identity as Compromised |
 
-Under the Stability Nexus / The Stable Order research direction.
+---
 
+## 🧠 Research Context
 
----
-
-# ✅ After pasting
-
-Run:
+This prototype is based on ongoing discussions around:
 
-```bash
-git add README.md
-git commit -m "Fix README markdown formatting and links"
-git push origin mini-dit-prototype
+- DIT (Decentralized Identity Tokens)
+- TNT (Trust Network Tokens)
+- VouchMe-style identity endorsement flows
+
+Under the Stability Nexus / The Stable Order research direction.
🤖 Prompt for AI Agents
In `@README.md` around lines 72 - 114, README.md has an unclosed code block and
leftover git instructions causing formatting to break; fix by closing the
opening triple-backtick after the initial git checkout (re-insert ```bash around
the "Install dependencies:" section and add matching ``` before "forge install"
and around the "forge test" example), remove the accidental commit/push lines at
the bottom, add proper Markdown headings (e.g., "## 🧪 Run Tests", "## 🔍 Smart
Contract Overview", "## 🧠 Research Context"), and replace the bulleted function
list with a table documenting mintIdentity(string metadataURI),
endorse(fromTokenId, toTokenId, tag), revokeEndorsement(toTokenId, index), and
markCompromised(tokenId) so the README renders correctly.

Comment on lines +46 to +63
function endorse(
uint256 fromTokenId,
uint256 toTokenId,
string calldata tag
) external {
require(ownerOf(fromTokenId) == msg.sender, "Not owner of fromToken");

// Will revert if toTokenId does not exist
ownerOf(toTokenId);

require(!identities[fromTokenId].compromised, "From identity compromised");

endorsements[toTokenId].push(
Endorsement(fromTokenId, tag, false)
);

emit Endorsed(fromTokenId, toTokenId, tag);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Missing self-endorsement prevention.

The function allows an identity to endorse itself (fromTokenId == toTokenId), which may not be semantically meaningful and could be exploited to artificially inflate endorsement counts.

🐛 Proposed fix
 function endorse(
     uint256 fromTokenId,
     uint256 toTokenId,
     string calldata tag
 ) external {
     require(ownerOf(fromTokenId) == msg.sender, "Not owner of fromToken");
+    require(fromTokenId != toTokenId, "Cannot self-endorse");

     // Will revert if toTokenId does not exist
     ownerOf(toTokenId);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function endorse(
uint256 fromTokenId,
uint256 toTokenId,
string calldata tag
) external {
require(ownerOf(fromTokenId) == msg.sender, "Not owner of fromToken");
// Will revert if toTokenId does not exist
ownerOf(toTokenId);
require(!identities[fromTokenId].compromised, "From identity compromised");
endorsements[toTokenId].push(
Endorsement(fromTokenId, tag, false)
);
emit Endorsed(fromTokenId, toTokenId, tag);
}
function endorse(
uint256 fromTokenId,
uint256 toTokenId,
string calldata tag
) external {
require(ownerOf(fromTokenId) == msg.sender, "Not owner of fromToken");
require(fromTokenId != toTokenId, "Cannot self-endorse");
// Will revert if toTokenId does not exist
ownerOf(toTokenId);
require(!identities[fromTokenId].compromised, "From identity compromised");
endorsements[toTokenId].push(
Endorsement(fromTokenId, tag, false)
);
emit Endorsed(fromTokenId, toTokenId, tag);
}
🤖 Prompt for AI Agents
In `@src/MiniDIT.sol` around lines 46 - 63, The endorse function currently allows
self-endorsement; add a check in endorse to require(fromTokenId != toTokenId,
"Cannot self-endorse") (or similar) immediately after the ownerOf(fromTokenId)
check so that an identity cannot endorse itself; this touches the endorse
function and prevents pushing Endorsement(fromTokenId, tag, false) into
endorsements[toTokenId] when fromTokenId equals toTokenId.

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