Skip to content

feat: allow crisp to work with constant credits#1263

Merged
ctrlc03 merged 13 commits into
mainfrom
feat/constant-credits-crisp
Feb 6, 2026
Merged

feat: allow crisp to work with constant credits#1263
ctrlc03 merged 13 commits into
mainfrom
feat/constant-credits-crisp

Conversation

@ctrlc03

@ctrlc03 ctrlc03 commented Feb 4, 2026

Copy link
Copy Markdown
Collaborator

fix #1256
fix #1004

Summary by CodeRabbit

  • New Features

    • Two credit modes for rounds: CONSTANT (fixed credits) and CUSTOM (voting-power-based).
    • Rounds now surface credit mode and optional credits in round state.
    • New eligibility path deriving voters from transfer/delegation logs with constant-balance assignment.
    • Snapshot timing for eligibility adjusted.
  • Chores

    • SDK exports CreditMode; packages bumped to v0.5.10.
  • API

    • Public API updated to accept consolidated custom parameters for round initialization.

@vercel

vercel Bot commented Feb 4, 2026

Copy link
Copy Markdown

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

2 Skipped Deployments
Project Deployment Actions Updated (UTC)
crisp Skipped Skipped Feb 6, 2026 9:23am
enclave-docs Skipped Skipped Feb 6, 2026 9:23am

Request Review

@coderabbitai

coderabbitai Bot commented Feb 4, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Adds CreditMode (CONSTANT vs CUSTOM) and credits across contracts, SDK, client, and server; propagates new fields through models and repo; introduces a constant-balance token-holder discovery path and adjusts snapshot block handling in the indexer/etherscan logic.

Changes

Cohort / File(s) Summary
Client
examples/CRISP/client/src/model/vote.model.ts, examples/CRISP/client/package.json
Import/export CreditMode; added credit_mode: CreditMode and optional credits to VoteStateLite; bumped @crisp-e3/sdk version.
SDK
examples/CRISP/packages/crisp-sdk/src/types.ts, examples/CRISP/packages/crisp-sdk/src/index.ts, examples/CRISP/packages/crisp-sdk/package.json
Added exported CreditMode enum (CONSTANT=0, CUSTOM=1) and re-exported it; package version bumped.
Smart contracts & mocks
examples/CRISP/packages/crisp-contracts/contracts/CRISPProgram.sol, examples/CRISP/packages/crisp-contracts/contracts/Mocks/MockEnclave.sol, examples/CRISP/packages/crisp-contracts/package.json
Added CreditMode enum and RoundData.creditMode; decode/assign creditMode from customParams; updated mocks to encode 5-tuple customParams; version bumped.
Server models & repo
examples/CRISP/server/src/server/models.rs, examples/CRISP/server/src/server/repo.rs
Added CreditMode enum and TryFrom; extended CustomParams, E3StateLite, E3Crisp with credit_mode and credits; initialize_round now accepts CustomParams and persists new fields; API surface updated.
Indexer & token-holder logic
examples/CRISP/server/src/server/indexer.rs, examples/CRISP/server/src/cli/commands.rs, examples/CRISP/server/src/server/token_holders/etherscan.rs
Expanded encoded customParams to include credit_mode and credits; indexer branches on credit_mode: CONSTANT uses new get_token_holders_with_constant_balance (assigns fixed credits), CUSTOM uses voting-power path; snapshot block adjusted to requestBlock - 1 for constant mode; added new Etherscan client method.
Dependencies & metadata
examples/CRISP/server/Cargo.toml, packages/enclave-contracts/artifacts/.../IEnclave.json, examples/CRISP/packages/crisp-*/package.json
Added serde_repr dependency to server Cargo.toml; updated enclave artifact buildInfoId; bumped versions across crisp packages.

Sequence Diagram

sequenceDiagram
    participant Client
    participant CRISPContract as CRISP Contract
    participant ServerIndexer as Server Indexer
    participant EtherscanClient
    participant Repository
    participant Blockchain

    Client->>CRISPContract: startRound(customParams with creditMode, credits)
    CRISPContract->>ServerIndexer: emit validate event (includes customParams)
    ServerIndexer->>ServerIndexer: decode customParams -> token, threshold, num_options, credit_mode, credits

    alt credit_mode == CONSTANT
        ServerIndexer->>EtherscanClient: get_token_holders_with_constant_balance(token, snapshotBlock-1, credits)
        EtherscanClient->>EtherscanClient: analyze transfer & delegation logs
        EtherscanClient-->>ServerIndexer: TokenHolders (assigned constant balance)
    else credit_mode == CUSTOM
        ServerIndexer->>EtherscanClient: get_token_holders_with_voting_power(token, snapshotBlock-1, threshold)
        EtherscanClient->>Blockchain: query voting power per holder
        EtherscanClient-->>ServerIndexer: TokenHolders (variable balances)
    end

    ServerIndexer->>Repository: initialize_round(customParams, requester)
    Repository-->>ServerIndexer: persisted E3 state
    ServerIndexer->>ServerIndexer: compute merkle root
    ServerIndexer->>Blockchain: set merkle root on-chain
    Blockchain-->>ServerIndexer: tx confirmation
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested labels

crisp, sdk

Suggested reviewers

  • cedoor
  • ryardley

Poem

🐇
I nibble at bytes and hop through code,
Constant carrots lined the road.
Snapshots step back, credits in bloom,
Merkle roots sprout in tidy groom.
Hooray — ballots bound, the system glows!

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: allow crisp to work with constant credits' accurately summarizes the main change of enabling CRISP to support constant voting power allocation.
Linked Issues check ✅ Passed The PR successfully implements both linked issue requirements: constant voting balance support (#1256) with new CreditMode enum and mode-dependent logic, and block-1 indexing (#1004) with updated Etherscan calls and block references.
Out of Scope Changes check ✅ Passed All changes are within scope: CreditMode enum addition, CustomParams extension, repository API refactoring, constant-balance token holder retrieval, and supporting SDK/contract updates align with the two linked objectives.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/constant-credits-crisp

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.

@vercel vercel Bot temporarily deployed to Preview – crisp February 4, 2026 20:03 Inactive
@vercel vercel Bot temporarily deployed to Preview – enclave-docs February 4, 2026 20:03 Inactive
@ctrlc03 ctrlc03 force-pushed the feat/constant-credits-crisp branch from df4f118 to 9fbd138 Compare February 5, 2026 09:27
@vercel vercel Bot temporarily deployed to Preview – enclave-docs February 5, 2026 09:28 Inactive
@vercel vercel Bot temporarily deployed to Preview – enclave-docs February 5, 2026 10:18 Inactive
@vercel vercel Bot temporarily deployed to Preview – crisp February 5, 2026 10:18 Inactive
@vercel vercel Bot temporarily deployed to Preview – crisp February 5, 2026 10:38 Inactive
@vercel vercel Bot temporarily deployed to Preview – enclave-docs February 5, 2026 10:38 Inactive
@ctrlc03 ctrlc03 marked this pull request as ready for review February 5, 2026 10:40
@ctrlc03 ctrlc03 force-pushed the feat/constant-credits-crisp branch from 522caf7 to 387cec4 Compare February 5, 2026 14:07
@vercel vercel Bot temporarily deployed to Preview – enclave-docs February 5, 2026 14:07 Inactive
@ctrlc03 ctrlc03 requested a review from cedoor February 5, 2026 14:08
@vercel vercel Bot temporarily deployed to Preview – enclave-docs February 5, 2026 14:12 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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
examples/CRISP/packages/crisp-contracts/contracts/Mocks/MockEnclave.sol (1)

20-40: ⚠️ Potential issue | 🟠 Major

Fix validate call to pass the new 5-field customParams encoding.

Line 31 encodes five fields: abi.encode(address(0), nextE3Id, 2, 0, 0), but line 39 passes only three: abi.encode(address(0), nextE3Id, 2). CRISPProgram's validate method (line 133) decodes all five fields—address, uint256, uint256, CreditMode, and uint256—so this mismatch will cause a revert on abi.decode.

Proposed fix
-    IE3Program(program).validate(nextE3Id, 0, bytes(""), bytes(""), abi.encode(address(0), nextE3Id, 2));
+    IE3Program(program).validate(nextE3Id, 0, bytes(""), bytes(""), abi.encode(address(0), nextE3Id, 2, 0, 0));
examples/CRISP/server/src/server/models.rs (1)

142-252: ⚠️ Potential issue | 🟠 Major

Back-compat risk: new non-optional fields will break deserialization of existing persisted records.

The addition of credit_mode and credits fields to CustomParams, E3StateLite, and E3Crisp creates a breaking change. Data is serialized to JSON via serde_json and persisted in a sled database. Any existing stored records lacking these fields will fail to deserialize when get_crisp() or related methods attempt to load them, causing runtime panics.

Add serde defaults to handle records created before these fields existed:

Suggested fix
+fn default_credit_mode() -> CreditMode {
+    CreditMode::Custom
+}
+
 #[derive(Debug, Deserialize, Serialize)]
 pub struct CustomParams {
     pub token_address: String,
     pub balance_threshold: String,
     pub num_options: String,
+    #[serde(default = "default_credit_mode")]
     pub credit_mode: CreditMode,
+    #[serde(default)]
     pub credits: Option<String>,
 }

 #[derive(Debug, Deserialize, Serialize)]
 pub struct E3StateLite {
     // ...
+    #[serde(default = "default_credit_mode")]
     pub credit_mode: CreditMode,
+    #[serde(default)]
     pub credits: Option<String>,
 }

 #[derive(Debug, Deserialize, Serialize)]
 pub struct E3Crisp {
     // ...
+    #[serde(default = "default_credit_mode")]
     pub credit_mode: CreditMode,
+    #[serde(default)]
     pub credits: Option<String>,
 }
examples/CRISP/server/src/server/repo.rs (1)

168-188: ⚠️ Potential issue | 🟡 Minor

Add validation for credit_mode/credits consistency in initialize_round.

While CustomParams is currently constructed with a match statement in indexer.rs that ensures CreditMode::Constant always has credits set, initialize_round accepts CustomParams without explicit validation. To make the contract explicit and prevent issues if new code paths create CustomParams in the future, add a guard to reject CreditMode::Constant with credits = None before persisting.

🔧 Suggested guard
+    if matches!(&custom_params.credit_mode, crate::server::models::CreditMode::Constant)
+        && custom_params.credits.is_none()
+    {
+        return Err(eyre::eyre!(
+            "credits must be provided when credit_mode is Constant"
+        ));
+    }
     self.set_crisp(E3Crisp {
         has_voted: vec![],
         start_time: 0u64,
         status: "Requested".to_string(),
🤖 Fix all issues with AI agents
In `@examples/CRISP/packages/crisp-sdk/src/types.ts`:
- Around line 226-234: The SDK's CreditMode enum in types.ts uses string values
('0'/'1') which won't strictly equal the server's numeric JSON discriminants;
update the enum CreditMode to use numeric values (0 and 1) so deserialized JSON
numbers match (e.g., export enum CreditMode { CONSTANT = 0, CUSTOM = 1 }), and
then audit any strict comparisons or switch statements that reference CreditMode
to ensure they still work with numbers.

In `@examples/CRISP/server/src/server/token_holders/etherscan.rs`:
- Around line 582-637: In get_token_holders_with_constant_balance, the
constant-balance branch currently admits any potential voter with nonzero
balance or delegation without consulting the configured balance_threshold;
either enforce the threshold by filtering potential_voters using the configured
balance_threshold (e.g., require v.token_balance >= balance_threshold before
mapping to TokenHolder) or explicitly validate early that balance_threshold ==
U256::ZERO and return an error if not; update the filter that produces
token_holders in get_token_holders_with_constant_balance to reference the
balance_threshold (or add the validation) so eligibility respects the configured
threshold.
🧹 Nitpick comments (4)
examples/CRISP/packages/crisp-contracts/contracts/CRISPProgram.sol (1)

132-139: Add a guard for invalid creditMode values.

abi.decode will accept any numeric value for the enum; a quick bounds check prevents invalid modes from being persisted on-chain.

Suggested guard
+  error InvalidCreditMode();
...
-  (, , uint256 numOptions, CreditMode creditMode, ) = abi.decode(customParams, (address, uint256, uint256, CreditMode, uint256));
+  (, , uint256 numOptions, CreditMode creditMode, ) = abi.decode(customParams, (address, uint256, uint256, CreditMode, uint256));
+  if (uint8(creditMode) > uint8(CreditMode.CUSTOM)) revert InvalidCreditMode();
examples/CRISP/server/src/server/models.rs (1)

277-295: Define a stable serialization format for CreditMode across layers.

Right now the Rust enum will serialize as variant names by default, while the SDK uses '0'/'1' and Solidity uses numeric enum values. Please confirm the intended wire format and add explicit serde mapping (into/try_from u64 or renamed strings) to avoid cross‑layer mismatches.

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

124-146: Guard against requestBlock underflow.

requestBlock - 1 will wrap if the value is 0 (in release builds), producing a huge block number and expensive log queries. A saturating_sub(1) or explicit check is safer.

Safer subtraction
-                                event.e3.requestBlock.to::<u64>() - 1u64,
+                                event.e3.requestBlock.to::<u64>().saturating_sub(1),
examples/CRISP/server/src/server/token_holders/etherscan.rs (1)

438-455: Document get_past_votes parameter semantics for clarity.

The function passes block_number directly to getPastVotes without adjustment. Current callers (e.g., indexer.rs:145) correctly handle this by passing snapshot_block - 1, but the function lacks a docstring explaining this requirement. Consider documenting that the parameter should be a historical block (typically snapshot_block - 1 for snapshot voting), or rename the parameter to make semantics explicit and prevent future mistakes.

Comment thread examples/CRISP/packages/crisp-sdk/src/types.ts
Comment thread examples/CRISP/server/src/server/token_holders/etherscan.rs
@vercel vercel Bot temporarily deployed to Preview – enclave-docs February 5, 2026 15:00 Inactive
@vercel vercel Bot temporarily deployed to Preview – crisp February 5, 2026 15:00 Inactive
@ctrlc03 ctrlc03 force-pushed the feat/constant-credits-crisp branch from 9465cf3 to 71e4bdd Compare February 5, 2026 21:57
@vercel vercel Bot temporarily deployed to Preview – enclave-docs February 5, 2026 21:57 Inactive
@ctrlc03 ctrlc03 enabled auto-merge (squash) February 5, 2026 21:58
cedoor
cedoor previously approved these changes Feb 6, 2026

@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

@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

🤖 Fix all issues with AI agents
In `@examples/CRISP/packages/crisp-contracts/contracts/CRISPProgram.sol`:
- Around line 133-139: The inline comment contains a typo: replace "credit more"
with "credit mode" in the comment above the assignment to
e3Data[e3Id].creditMode; verify the comment near the assignments to
e3Data[e3Id].numOptions and e3Data[e3Id].creditMode correctly reads "we want to
save the credit mode so it can be verified on chain by everyone" and keep the
surrounding logic (decoding customParams, the numOptions check that may revert
InvalidNumOptions()) unchanged.

In `@examples/CRISP/server/src/server/models.rs`:
- Around line 276-296: The enum CreditMode is currently derived with
Serialize/Deserialize which emits variant names ("Constant"/"Custom") instead of
numeric discriminants expected by the TypeScript SDK; update the enum to use
serde_repr's Serialize_repr and Deserialize_repr and add a repr(u64) (or
repr(u8) if smaller) so JSON serializes to 0/1, and also derive Clone and Copy
for ergonomics; keep the existing TryFrom<u64> impl (CreditMode and TryFrom)
unchanged so conversion logic remains intact. Ensure serde_repr is added to
Cargo.toml and the enum declaration uses #[derive(Debug, PartialEq, Clone, Copy,
Serialize_repr, Deserialize_repr)] and #[repr(u64)] (or repr(u8)) to serialize
numeric discriminants.
🧹 Nitpick comments (2)
examples/CRISP/server/src/server/token_holders/etherscan.rs (1)

582-645: Consider extracting shared discovery steps to reduce duplication.

Steps 1–4 (deployment block lookup, transfer log fetch, delegation log fetch, potential voter identification) are identical between get_token_holders_with_voting_power and get_token_holders_with_constant_balance. Extracting these into a shared helper would reduce ~40 lines of duplication and make both paths easier to maintain.

Sketch
// Private helper
async fn discover_potential_voters(
    &self,
    token_address: Address,
    snapshot_block: u64,
) -> Result<Vec<PotentialVoter>> {
    let start_block = self.get_deployment_block(&token_address.to_string()).await?;
    let transfer_logs = self.get_transfer_logs(&token_address.to_string(), start_block, snapshot_block).await?;
    let delegation_logs = self.get_delegate_votes_changed_logs(&token_address.to_string(), start_block, snapshot_block).await?;
    Ok(self.get_potential_voters(&transfer_logs, &delegation_logs))
}

Then both public methods call discover_potential_voters and only diverge in step 5.

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

89-97: credits_clone can be eliminated by referencing custom_params.credits directly inside the match.

The credits_clone variable exists solely because credits is moved into custom_params. Since custom_params is still available inside the match, you can borrow or clone from it directly:

Proposed simplification
-                let credits_clone = credits.clone();
-
                 let custom_params = CustomParams {
                     ...
                 };
                 ...
                 match custom_params.credit_mode {
                     CreditMode::Constant => {
-                        let credits_str = credits_clone.expect("credits must be set for Constant mode");
+                        let credits_str = custom_params.credits.clone().expect("credits must be set for Constant mode");

This removes the intermediate variable and keeps the intent clearer.

Also applies to: 123-161

Comment thread examples/CRISP/packages/crisp-contracts/contracts/CRISPProgram.sol
Comment thread examples/CRISP/server/src/server/models.rs

@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

🤖 Fix all issues with AI agents
In `@examples/CRISP/server/src/cli/commands.rs`:
- Line 104: Fix the typo in the comment that reads "The credit mode is costant
for the CRISP app (everyone gets the same credits)" by changing "costant" to
"constant" in the comment near the credits/credit mode logic in
examples/CRISP/server/src/cli/commands.rs (look for the comment text or the
function handling credit mode/credits to locate it).

In `@examples/CRISP/server/src/server/indexer.rs`:
- Around line 130-138: The subtraction of 1 from
event.e3.requestBlock.to::<u64>() can underflow; update the call sites that pass
the block argument to etherscan_client.get_token_holders_with_constant_balance
(and the equivalent "Custom" branch) to defensively compute the prior block
using saturating_sub(1) or an explicit guard (e.g., let prior_block =
event.e3.requestBlock.to::<u64>().saturating_sub(1); then pass prior_block) so
the code will not panic in debug mode or wrap in release.
🧹 Nitpick comments (3)
examples/CRISP/server/Cargo.toml (1)

57-57: Version pinning style is inconsistent with other dependencies.

Most dependencies in this file use exact pinning with = prefix (e.g., "=0.7.1", "=4.11.0"), but serde_repr uses "0.1.20" (SemVer-compatible range). Consider using "=0.1.20" for consistency.

Proposed fix
-serde_repr = "0.1.20"
+serde_repr = "=0.1.20"
examples/CRISP/server/src/server/token_holders/etherscan.rs (1)

582-644: Extract shared discovery logic (steps 1–4) to reduce duplication with get_token_holders_with_voting_power.

Steps 1–4 (get deployment block → fetch transfer logs → fetch delegation logs → identify potential voters) are copy-pasted from get_token_holders_with_voting_power (lines 519–555). Extract a private helper like discover_potential_voters(token_address, snapshot_block) -> Result<Vec<PotentialVoter>> and call it from both methods.

♻️ Sketch of the extracted helper
+    /// Shared discovery steps: deployment block → transfer logs → delegation logs → potential voters
+    async fn discover_potential_voters(
+        &self,
+        token_address: Address,
+        snapshot_block: u64,
+    ) -> Result<Vec<PotentialVoter>> {
+        let start_block = self
+            .get_deployment_block(&token_address.to_string())
+            .await
+            .context("Failed to get deployment block")?;
+        log::info!("Token deployed at block: {}", start_block);
+
+        let transfer_logs = self
+            .get_transfer_logs(&token_address.to_string(), start_block, snapshot_block)
+            .await
+            .context("Failed to fetch transfer logs")?;
+        log::info!("Found {} transfer events", transfer_logs.len());
+
+        let delegation_logs = self
+            .get_delegate_votes_changed_logs(&token_address.to_string(), start_block, snapshot_block)
+            .await
+            .context("Failed to fetch delegation logs")?;
+        log::info!("Found {} delegation events", delegation_logs.len());
+
+        let potential_voters = self.get_potential_voters(&transfer_logs, &delegation_logs);
+        log::info!("Found {} potential voters", potential_voters.len());
+
+        Ok(potential_voters)
+    }

Then both public methods simply call self.discover_potential_voters(...) before diverging at step 5.

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

124-139: Redundant U256 → String → U256 round-trip for credits.

decoded.4 is already a U256. Converting it to a String (line 81), cloning it (line 89), then parsing it back to U256 (lines 127–128) is unnecessary work in this branch. Consider capturing decoded.4 directly for the Etherscan call and only stringifying it for CustomParams.

♻️ Sketch
+                let credits_u256 = decoded.4; // keep the original U256
+
                 let credits = match credit_mode {
                     CreditMode::Constant => {
                         info!("[e3_id={}] Credit mode: Constant", e3_id);
                         Some(decoded.4.to_string())
                     }
                     CreditMode::Custom => {
                         info!("[e3_id={}] Credit mode: Custom", e3_id);
                         None
                     }
                 };
-
-                let credits_clone = credits.clone();
 
                 // ... later in Constant branch:
-                let credits_str = credits_clone.expect("credits must be set for Constant mode");
-                let credits_u256: alloy_primitives::Uint<256, 4> = U256::from_str_radix(&credits_str, 10)
-                    .map_err(|e| eyre::eyre!("Failed to parse credits: {}", e))?;
+                // use credits_u256 directly

Comment thread examples/CRISP/server/src/cli/commands.rs Outdated
Comment thread examples/CRISP/server/src/server/indexer.rs
@vercel vercel Bot temporarily deployed to Preview – enclave-docs February 6, 2026 09:23 Inactive
@vercel vercel Bot temporarily deployed to Preview – crisp February 6, 2026 09:23 Inactive
@ctrlc03 ctrlc03 merged commit b552c99 into main Feb 6, 2026
62 of 65 checks passed
@ctrlc03 ctrlc03 deleted the feat/constant-credits-crisp branch February 6, 2026 09:55
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 CRISP to work with constant voting balance CRISP token holders indexer should index with request block - 1

2 participants