Skip to content

feat: generate merkle tree#826

Merged
ctrlc03 merged 8 commits into
devfrom
feat/generate-merkle-tree
Oct 10, 2025
Merged

feat: generate merkle tree#826
ctrlc03 merged 8 commits into
devfrom
feat/generate-merkle-tree

Conversation

@ctrlc03

@ctrlc03 ctrlc03 commented Oct 10, 2025

Copy link
Copy Markdown
Collaborator

fix #807

Summary by CodeRabbit

  • New Features

    • Added utilities for Merkle leaf hashing, tree construction, and proof generation with input validation.
    • Introduced capability to retrieve historical token voting balances by chain.
  • Changes

    • Tree data is now returned as bigint values.
    • Package root now exposes utility helpers and additional types.
  • Tests

    • Added unit tests for Merkle utilities, including success and error cases.
  • Chores

    • Added dependencies for hashing, Merkle trees, and blockchain client; updated config to include JSON files.
  • Documentation

    • Minor README formatting updates.

@vercel

vercel Bot commented Oct 10, 2025

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Preview Comments Updated (UTC)
crisp Skipped Skipped Oct 10, 2025 9:52am
enclave-docs Skipped Skipped Oct 10, 2025 9:52am

@ctrlc03 ctrlc03 changed the base branch from main to dev October 10, 2025 08:50
@coderabbitai

coderabbitai Bot commented Oct 10, 2025

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

Adds Merkle utilities and types to the CRISP SDK, updates exports, introduces blockchain balance lookup via viem, refines tree data handling to BigInt, adds tests, updates package and TypeScript configs, and makes a minor server import/format change.

Changes

Cohort / File(s) Summary
Docs formatting
examples/CRISP/sdk/README.md
Whitespace/line-ending tweaks; no content or behavior changes.
SDK config
examples/CRISP/sdk/package.json, examples/CRISP/sdk/tsconfig.json
Add dependencies (@zk-kit/lean-imt, poseidon-lite, viem); include JSON files in tsconfig.
Public API surface
examples/CRISP/sdk/src/index.ts, examples/CRISP/sdk/src/types.ts
Export new utils module; add IRoundDetailsResponse and IMerkleProof types.
Token module
examples/CRISP/sdk/src/token.ts
Convert getTreeData to return bigint[]; add getBalanceAt using viem; remove placeholder generateMerkleProof.
Merkle utilities
examples/CRISP/sdk/src/utils.ts
New helpers: hashLeaf, generateMerkleTree, generateMerkleProof using Poseidon and Lean IMT.
Tests
examples/CRISP/sdk/tests/utils.test.ts
New tests for hashing, tree construction, proof generation, and error handling.
Server repo minor edits
examples/CRISP/server/src/server/repo.rs
Add use num_bigint::BigUint; import; minor formatting; no behavioral changes.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Dev as App/SDK User
  participant SDK as CRISP SDK
  participant Utils as Utils (Merkle)
  participant Server as CRISP Server
  participant Chain as Blockchain (via viem)

  Dev->>SDK: getTreeData(serverUrl, e3Id)
  SDK->>Server: HTTP GET /treeData
  Server-->>SDK: [hex leaves]
  SDK-->>Dev: [bigint leaves]

  Dev->>Utils: generateMerkleProof(threshold, balance, address, leaves)
  Utils->>Utils: hashLeaf(address, balance)
  Utils->>Utils: build LeanIMT + create proof
  Utils-->>Dev: { leaf, index, proof }

  Dev->>SDK: getBalanceAt(voterAddress, tokenAddress, block, chainId)
  SDK->>Chain: read getPastVotes(token, voter, block)
  Chain-->>SDK: balance (bigint)
  SDK-->>Dev: balance (bigint)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • hmzakhalid
  • 0xjei

Poem

In rows of leaves I thump with cheer,
A poseidon breeze hums in my ear.
I stitch a proof, so light and neat—
BigInts twirl beneath my feet.
On Sepolia’s trail I take a hop,
Past votes counted—flip, flop, pop! 🐇🌿

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Out of Scope Changes Check ⚠️ Warning The introduction of getBalanceAt and the minor formatting changes in examples/CRISP/server/src/server/repo.rs are not related to the core objective of issue #807, which is focused on Merkle tree proof generation from server data. Although tsconfig adjustments and utility additions support proof functionality, the balance‐fetching function and server-side edits introduce unrelated scope. Including these extra changes increases review complexity and risks diluting the intended feature. Extract the getBalanceAt implementation and server repository formatting updates into a separate PR, ensuring this PR remains dedicated solely to Merkle tree proof generation.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ 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 succinctly and clearly describes the primary feature introduced by this changeset—the ability to generate a Merkle tree—using concise conventional-commit syntax and without extraneous details. It directly reflects the core addition in the PR and follows a recognized format.
Linked Issues Check ✅ Passed The PR fully addresses issue #807 by providing getTreeData to fetch server data, utilities to build Merkle trees and generate corresponding proofs, and type definitions as required. The added tests validate both successful proof generation and appropriate error handling, demonstrating that users can generate Merkle tree proofs from server-supplied data. These changes align directly with the objective of enabling Merkle proof creation in the CRISP SDK.

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.

@ctrlc03 ctrlc03 changed the title Feat/generate merkle tree feat: generate merkle tree Oct 10, 2025
@ctrlc03 ctrlc03 requested a review from cedoor October 10, 2025 08:52
Comment thread examples/CRISP/sdk/src/token.ts
@cedoor

cedoor commented Oct 10, 2025

Copy link
Copy Markdown
Contributor

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Oct 10, 2025

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@vercel vercel Bot temporarily deployed to Preview – crisp October 10, 2025 09:45 Inactive
@vercel vercel Bot temporarily deployed to Preview – enclave-docs October 10, 2025 09:45 Inactive

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
examples/CRISP/sdk/src/token.ts (1)

9-9: Verify that the artifact file is committed and accessible.

A previous review comment noted that ERC20Votes.json was not staged/committed. Ensure this artifact is now properly included in the repository.

Run the following script to verify the artifact exists:

#!/bin/bash
# Description: Verify ERC20Votes.json artifact exists

if [ -f "examples/CRISP/sdk/src/artifacts/ERC20Votes.json" ]; then
  echo "✓ Artifact found"
  echo "Size: $(wc -c < examples/CRISP/sdk/src/artifacts/ERC20Votes.json) bytes"
  echo "Contains ABI: $(grep -q '"abi"' examples/CRISP/sdk/src/artifacts/ERC20Votes.json && echo "yes" || echo "no")"
else
  echo "✗ Artifact NOT found at expected path"
fi
🧹 Nitpick comments (3)
examples/CRISP/sdk/src/token.ts (1)

18-37: Consider adding error handling for malformed hex strings.

While the hex-to-BigInt conversion handles both prefixed (0x) and non-prefixed formats, it doesn't validate hex string format or handle potential conversion errors. Malformed input could cause runtime exceptions.

Consider adding validation:

 export const getTreeData = async (serverUrl: string, e3Id: number): Promise<bigint[]> => {
   const response = await fetch(`${serverUrl}/${CRISP_SERVER_TOKEN_TREE_ENDPOINT}`, {
     method: 'POST',
     headers: {
       'Content-Type': 'application/json',
     },
     body: JSON.stringify({ round_id: e3Id }),
   })

   const hashes = (await response.json()) as string[]

   // Convert hex strings to BigInts
   return hashes.map((hash) => {
+    // Validate hex string format
+    const hexString = hash.startsWith('0x') ? hash : '0x' + hash
+    if (!/^0x[0-9a-fA-F]+$/.test(hexString)) {
+      throw new Error(`Invalid hex string format: ${hash}`)
+    }
+    try {
+      return BigInt(hexString)
+    } catch (error) {
+      throw new Error(`Failed to convert hex string to BigInt: ${hash}`)
+    }
-    // Ensure the hash is treated as a hex string
-    if (!hash.startsWith('0x')) {
-      return BigInt('0x' + hash)
-    }
-    return BigInt(hash)
   })
 }
examples/CRISP/sdk/tests/utils.test.ts (1)

15-17: Test depends on external server availability.

The beforeAll hook fetches data from an external server (CRISP_SERVER_URL). If the server is unavailable or returns unexpected data, all tests will fail. Consider:

  1. Mocking the server response for more reliable tests
  2. Adding error handling in beforeAll with a clear failure message
  3. Documenting that tests require a running CRISP server

Example with error handling:

 beforeAll(async () => {
-  leaves = await getTreeData(CRISP_SERVER_URL, 0)
+  try {
+    leaves = await getTreeData(CRISP_SERVER_URL, 0)
+    if (!leaves || leaves.length === 0) {
+      throw new Error('No leaves returned from server')
+    }
+  } catch (error) {
+    throw new Error(`Failed to fetch test data from CRISP server: ${error}`)
+  }
 })
examples/CRISP/sdk/src/utils.ts (1)

51-60: Consider accepting a pre-built tree to avoid reconstruction.

The function recreates the entire Merkle tree on each call, which is inefficient if multiple proofs need to be generated from the same tree. However, for the current use case of generating a single proof, this is acceptable.

If performance becomes a concern, consider adding an overload that accepts a pre-built tree:

export const generateMerkleProof = (
  threshold: number, 
  balance: number, 
  address: string, 
  leavesOrTree: bigint[] | LeanIMT
): IMerkleProof => {
  if (balance < threshold) {
    throw new Error('Balance is below the threshold')
  }

  const leaf = hashLeaf(address, balance.toString())
  
  const tree = Array.isArray(leavesOrTree) 
    ? generateMerkleTree(leavesOrTree)
    : leavesOrTree
  
  const leaves = Array.isArray(leavesOrTree) 
    ? leavesOrTree 
    : tree.leaves

  const index = leaves.findIndex((l) => l === leaf)

  if (index === -1) {
    throw new Error('Leaf not found in the tree')
  }

  const proof = tree.generateProof(index)

  return {
    leaf,
    index,
    proof,
  }
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0b305d1 and f053f61.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (9)
  • examples/CRISP/sdk/README.md (2 hunks)
  • examples/CRISP/sdk/package.json (1 hunks)
  • examples/CRISP/sdk/src/index.ts (1 hunks)
  • examples/CRISP/sdk/src/token.ts (2 hunks)
  • examples/CRISP/sdk/src/types.ts (2 hunks)
  • examples/CRISP/sdk/src/utils.ts (1 hunks)
  • examples/CRISP/sdk/tests/utils.test.ts (1 hunks)
  • examples/CRISP/sdk/tsconfig.json (1 hunks)
  • examples/CRISP/server/src/server/repo.rs (2 hunks)
🔇 Additional comments (10)
examples/CRISP/sdk/tsconfig.json (1)

12-12: LGTM!

Adding JSON files to the TypeScript project inclusion is necessary for importing ERC20Votes.json in token.ts.

examples/CRISP/sdk/README.md (1)

3-18: LGTM!

Minor documentation formatting changes with no functional impact.

examples/CRISP/server/src/server/repo.rs (1)

248-248: LGTM!

Minor formatting improvements with no functional impact.

Also applies to: 262-265

examples/CRISP/sdk/src/types.ts (1)

7-8: LGTM!

The IMerkleProof interface is well-defined with proper typing and documentation. The integration with LeanIMTMerkleProof from the external library is appropriate.

Also applies to: 56-63

examples/CRISP/sdk/src/token.ts (1)

47-73: Limited chain support may restrict usage.

The getBalanceAt function only supports Sepolia (11155111) and localhost (31337). Consider whether this limitation aligns with the SDK's intended scope or if additional chains should be supported.

If broader chain support is needed, consider:

 export const getBalanceAt = async (voterAddress: string, tokenAddress: string, snapshotBlock: number, chainId: number): Promise<bigint> => {
   let chain
   switch (chainId) {
     case 11155111:
       chain = sepolia
       break
     case 31337:
       chain = localhost
       break
+    case 1:
+      chain = mainnet
+      break
+    // Add other chains as needed
     default:
       throw new Error('Unsupported chainId')
   }

Alternatively, you could dynamically create chain config or accept a chain parameter.

examples/CRISP/sdk/src/index.ts (1)

10-10: LGTM!

The expanded exports properly expose the new utility functions and types (IMerkleProof, IRoundDetailsResponse) added in this PR.

Also applies to: 12-12

examples/CRISP/sdk/tests/utils.test.ts (1)

19-44: LGTM!

The test suite provides good coverage of the utility functions, including both success and error cases. The test structure is clear and assertions are appropriate.

examples/CRISP/sdk/package.json (1)

40-44: Dependencies validated, no vulnerabilities detected.
All specified versions exist on npm and have no reported security advisories. Pinning viem at 2.30.6 is acceptable given the next release is a canary build.

examples/CRISP/sdk/src/utils.ts (2)

27-29: LGTM!

The Merkle tree construction correctly follows the LeanIMT API, providing a hash function and initial leaves.


38-49: Verify the hashLeaf call after fixing the type conversion issue.

The logic correctly validates the threshold and searches for the leaf in the tree. However, line 43 calls hashLeaf with string inputs, which will fail due to the type conversion issue flagged in the hashLeaf function. Once that function is fixed to properly convert inputs to bigint, this code will work correctly.

Comment thread examples/CRISP/sdk/src/utils.ts
Comment thread examples/CRISP/server/src/server/repo.rs Outdated
@ctrlc03 ctrlc03 requested a review from cedoor October 10, 2025 09:52
@vercel vercel Bot temporarily deployed to Preview – crisp October 10, 2025 09:52 Inactive
@vercel vercel Bot temporarily deployed to Preview – enclave-docs October 10, 2025 09:52 Inactive
@ctrlc03 ctrlc03 enabled auto-merge (squash) October 10, 2025 09:56

@cedoor cedoor left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

utACK 👍🏽

@ctrlc03 ctrlc03 merged commit d471fa5 into dev Oct 10, 2025
21 checks passed
@cedoor cedoor deleted the feat/generate-merkle-tree branch October 10, 2025 10:02
ctrlc03 added a commit that referenced this pull request Oct 11, 2025
* fix: server resetting data

* chore: add license

* feat: get round data from crisp server

* feat: generate merkle tree

* feat: generate voter's merkle tree locally and proof

* chore: handle hex and add tests

* chore: add erc20votes abi

* chore: remove redundant import
ctrlc03 added a commit that referenced this pull request Oct 11, 2025
* fix: server resetting data

* chore: add license

* feat: get round data from crisp server

* feat: generate merkle tree

* feat: generate voter's merkle tree locally and proof

* chore: handle hex and add tests

* chore: add erc20votes abi

* chore: remove redundant import
cedoor pushed a commit that referenced this pull request Oct 14, 2025
* fix: server resetting data

* chore: add license

* feat: get round data from crisp server

* feat: generate merkle tree

* feat: generate voter's merkle tree locally and proof

* chore: handle hex and add tests

* chore: add erc20votes abi

* chore: remove redundant import
cedoor pushed a commit that referenced this pull request Oct 14, 2025
* fix: server resetting data

* chore: add license

* feat: get round data from crisp server

* feat: generate merkle tree

* feat: generate voter's merkle tree locally and proof

* chore: handle hex and add tests

* chore: add erc20votes abi

* chore: remove redundant import
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.

Allow users to generate merkle tree proof from server data on CRISP sdk

2 participants