Skip to content

Add support for separate disbursers#22

Merged
pkoch merged 1 commit intomasterfrom
feat/separate-disbursers
Mar 2, 2026
Merged

Add support for separate disbursers#22
pkoch merged 1 commit intomasterfrom
feat/separate-disbursers

Conversation

@pkoch
Copy link
Member

@pkoch pkoch commented Mar 2, 2026

Summary by CodeRabbit

  • Refactor

    • Modularized chain/wallet initialization and introduced separate TDE and Tracker disbursement wallets; contract clients now use wallet-specific configurations.
    • Environment variables renamed to TDE_DISBURSER_PRIVATE_KEY and TRACKER_DISBURSER_PRIVATE_KEY.
  • New Features

    • Contract ABI updates: added DISBURSER view, VESTING_PARAMS_FOR_MODALITY, a Disbursed event, and vesting-related function variants.
    • New contract error types for clearer on-chain failure signals.

@coderabbitai
Copy link

coderabbitai bot commented Mar 2, 2026

Warning

Rate limit exceeded

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

⌛ 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 7855e40 and 3f86ded.

📒 Files selected for processing (6)
  • script/initial-distribution/src/abis.ts
  • script/initial-distribution/src/cca.ts
  • script/initial-distribution/src/chains.ts
  • script/initial-distribution/src/claimAllBids.ts
  • script/initial-distribution/src/tde.ts
  • script/initial-distribution/src/tdeSetup.ts
📝 Walkthrough

Walkthrough

Refactors wallet and chain initialization by splitting chainSetup into baseSetup (chain/transport/publicClient) and makeWallet (wallet creation from private key). Replaces single DISBURSER_PRIVATE_KEY with TDE_DISBURSER_PRIVATE_KEY and TRACKER_DISBURSER_PRIVATE_KEY. Updates scripts to instantiate and use role-specific tdeDisburser and trackerDisburser wallet objects throughout.

Changes

Cohort / File(s) Summary
Chain Setup Refactor
script/initial-distribution/src/chains.ts
Added baseSetup(chainId, rpcUrl) and makeWallet(chain, transport, privateKey). chainSetup() now returns only { chain, transport, publicClient }; wallet creation moved to makeWallet.
CCA Flow
script/initial-distribution/src/cca.ts
Replaced single wallet flow with two disburser wallets: tdeDisburser and trackerDisburser. Contract clients, transfer/balance checks, on-chain disburser verification, and final sweep logic now use the role-specific *.walletClient and *.account.address.
TDE Environment & Setup
script/initial-distribution/src/tdeSetup.ts, script/initial-distribution/src/tde.ts
Switched to baseSetup() + makeWallet(); renamed env var to TDE_DISBURSER_PRIVATE_KEY. setupTdeEnvironment() now returns tdeDisburser (used instead of account); contract clients and allowance flows use tdeDisburser.walletClient / address.
Claim/Tracker
script/initial-distribution/src/claimAllBids.ts
Switched to baseSetup() + makeWallet() and replaced DISBURSER_PRIVATE_KEY with TRACKER_DISBURSER_PRIVATE_KEY. Tracker/CCA contract client now uses trackerDisburser.walletClient.
ABIs Update
script/initial-distribution/src/abis.ts
Modified TDE ABI: added DISBURSER function, VESTING_PARAMS_FOR_MODALITY, vesting-related signature changes, new Disbursed event, and several new error definitions.

Sequence Diagram(s)

sequenceDiagram
  participant Script as Script
  participant Chain as Chain (baseSetup)
  participant Transport as Transport
  participant Public as PublicClient
  participant WalletMaker as makeWallet
  participant TDEWallet as tdeDisburser
  participant TrackerWallet as trackerDisburser
  participant Contracts as Contracts (TDE/Tracker/Token)

  Script->>Chain: call baseSetup(chainId, rpcUrl)
  Chain->>Transport: init transport
  Chain->>Public: init publicClient
  Script->>WalletMaker: makeWallet(chain, transport, TDE_KEY)
  WalletMaker->>TDEWallet: return { account, walletClient }
  Script->>WalletMaker: makeWallet(chain, transport, TRACKER_KEY)
  WalletMaker->>TrackerWallet: return { account, walletClient }
  Script->>Contracts: bind contracts using TDEWallet.walletClient / TrackerWallet.walletClient
  Script->>Contracts: perform allowance/transfer/balance checks using respective disburser addresses
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰
Two wallets I fashioned with whiskered delight,
TDE for the tokens, Tracker for right,
baseSetup laid the path, makeWallet the key,
Now disbursements hop free as can be,
A rabbit's small cheer for code running bright!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add support for separate disbursers' accurately summarizes the primary change: refactoring the codebase to support multiple distinct disbursers (TDE and Tracker) instead of a single shared one.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/separate-disbursers

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

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@script/initial-distribution/src/cca.ts`:
- Around line 78-83: The assertion is reading the disburser from trackerContract
by mistake; change the source of the on-chain value to the TDE disbursement
contract (i.e., call the TDEDisbursement contract's read.disburser() rather than
trackerContract.read.disburser()) so that onChainTDEDisburser is the actual
on-chain disburser for comparison against tdeDisburser.address in the assertion.

In `@script/initial-distribution/src/chains.ts`:
- Around line 51-59: chainSetup duplicates baseSetup logic; change chainSetup to
delegate to baseSetup to avoid drift—inside the chainSetup function call and
return the result of baseSetup (preserving the returned shape { chain,
transport, publicClient }); ensure baseSetup is referenced (and imported if
necessary) and remove the duplicated
resolveChain/http/createPublicClient/validateRpcChainId steps from chainSetup so
there is a single source of truth in baseSetup.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9877da4 and ef44210.

📒 Files selected for processing (5)
  • script/initial-distribution/src/cca.ts
  • script/initial-distribution/src/chains.ts
  • script/initial-distribution/src/claimAllBids.ts
  • script/initial-distribution/src/tde.ts
  • script/initial-distribution/src/tdeSetup.ts
📜 Review details
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: pkoch
Repo: idos-network/contracts PR: 13
File: src/WhaleDisburser.sol:23-28
Timestamp: 2026-02-23T19:57:43.946Z
Learning: In src/WhaleDisburser.sol, the disburse function intentionally has no access control. Anyone can call disburse(IERC20 token, address beneficiary, uint256 totalAmount, uint64 vestingStart), which is by design. The function uses safeTransferFrom(msg.sender, ...) so it only pulls from the caller's own balance, and no external funds are at risk.
📚 Learning: 2026-03-01T13:52:37.088Z
Learnt from: pkoch
Repo: idos-network/contracts PR: 19
File: script/initial-distribution/src/tde.ts:38-48
Timestamp: 2026-03-01T13:52:37.088Z
Learning: In script/initial-distribution, when using getContract({ client: walletClient }), it is possible to call read.* methods on the resulting contract even if walletClient is not explicitly extended with publicActions (as observed in tde.ts and cca.ts with viem 2.23.x). Treat this as an intentional pattern to verify: - confirm that the behavior is supported by the specific viem version and contract bindings, - document any assumptions about publicActions not being required for read operations, - ensure there are no security implications (e.g., unintended read access) and that access controls are still correct, - consider adding comments or tests that demonstrate expected usage and edge cases, and - if this behavior is relied upon in other files, apply the same pattern consistently within the script/initial-distribution directory.

Applied to files:

  • script/initial-distribution/src/claimAllBids.ts
  • script/initial-distribution/src/cca.ts
  • script/initial-distribution/src/tde.ts
  • script/initial-distribution/src/chains.ts
  • script/initial-distribution/src/tdeSetup.ts
📚 Learning: 2026-02-23T19:57:43.946Z
Learnt from: pkoch
Repo: idos-network/contracts PR: 13
File: src/WhaleDisburser.sol:23-28
Timestamp: 2026-02-23T19:57:43.946Z
Learning: In src/WhaleDisburser.sol, the disburse function intentionally has no access control. Anyone can call disburse(IERC20 token, address beneficiary, uint256 totalAmount, uint64 vestingStart), which is by design. The function uses safeTransferFrom(msg.sender, ...) so it only pulls from the caller's own balance, and no external funds are at risk.

Applied to files:

  • script/initial-distribution/src/cca.ts
  • script/initial-distribution/src/tde.ts
🧬 Code graph analysis (3)
script/initial-distribution/src/claimAllBids.ts (3)
script/initial-distribution/src/lib.ts (2)
  • ensureHex (10-16)
  • requireEnv (3-7)
script/initial-distribution/src/chains.ts (2)
  • baseSetup (37-43)
  • makeWallet (45-49)
script/initial-distribution/src/abis.ts (1)
  • ccaAbi (68-201)
script/initial-distribution/src/cca.ts (3)
script/initial-distribution/src/lib.ts (3)
  • ensureHex (10-16)
  • requireEnv (3-7)
  • assertCondition (57-59)
script/initial-distribution/src/chains.ts (2)
  • baseSetup (37-43)
  • makeWallet (45-49)
script/initial-distribution/src/abis.ts (2)
  • tdeDisbursementAbi (203-255)
  • trackerAbi (3-66)
script/initial-distribution/src/tdeSetup.ts (3)
script/initial-distribution/src/lib.ts (3)
  • ensureHex (10-16)
  • requireEnv (3-7)
  • iso8601ToTimestamp (44-51)
script/initial-distribution/src/chains.ts (2)
  • chainSetup (51-59)
  • makeWallet (45-49)
script/initial-distribution/src/batch.ts (1)
  • BatchCallerConfig (38-42)
🔇 Additional comments (6)
script/initial-distribution/src/claimAllBids.ts (2)

27-37: Role-specific tracker disburser wiring looks correct.

The migration to TRACKER_DISBURSER_PRIVATE_KEY plus baseSetup/makeWallet cleanly aligns this script with the separate-disburser model.


33-37: 🧹 Nitpick | 🔵 Trivial

Use explicit { public, wallet } client wiring for mixed read/write contract usage.

This contract is used for both read.* and write.* operations with only a wallet client. Per viem best practices, explicitly provide both clients: { public: publicClient, wallet: walletClient }.

♻️ Proposed change
 const ccaContract = getContract({
   address: CCA_ADDRESS,
   abi: ccaAbi,
-  client: trackerDisburser.walletClient,
+  client: {
+    public: publicClient,
+    wallet: trackerDisburser.walletClient,
+  },
 });
⛔ Skipped due to learnings
Learnt from: pkoch
Repo: idos-network/contracts PR: 19
File: script/initial-distribution/src/tde.ts:38-48
Timestamp: 2026-03-01T13:52:37.088Z
Learning: In the idos-network/contracts repository, the team uses `getContract({ client: walletClient })` and successfully calls `.read.*` methods on the resulting contract instances in viem 2.23.x, even though the walletClient is not explicitly extended with publicActions. This pattern is used throughout script/initial-distribution/src/ (tde.ts, cca.ts).
script/initial-distribution/src/tde.ts (1)

49-52: Allowance checks are now aligned to the correct signer address.

Using tdeDisburser.address here avoids allowance mismatches across disburser wallets.

script/initial-distribution/src/chains.ts (1)

37-49: baseSetup + makeWallet split is a solid refactor.

This separation improves composability for multi-wallet flows and matches the PR objective.

script/initial-distribution/src/tdeSetup.ts (1)

27-49: TDE disburser propagation is consistent across setup outputs and clients.

batchConfig, tdeDisbursementContract, and tokenContract now all use tdeDisburser.walletClient, which keeps signer identity coherent.

script/initial-distribution/src/cca.ts (1)

34-60: Role-specific wallet binding across contracts is well-structured.

Using tdeDisburser.walletClient for token/TDE writes and trackerDisburser.walletClient for tracker writes is the right split.

@pkoch pkoch force-pushed the feat/separate-disbursers branch 2 times, most recently from 17a3781 to 7855e40 Compare March 2, 2026 14:21
Copy link

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

♻️ Duplicate comments (1)
script/initial-distribution/src/chains.ts (1)

51-59: 🧹 Nitpick | 🔵 Trivial

Deduplicate chainSetup by delegating to baseSetup.

chainSetup re-implements the same logic as baseSetup, creating maintenance overhead and divergence risk.

♻️ Proposed fix
 export async function chainSetup(chainId: string, rpcUrl: string) {
-  const chain = resolveChain(chainId);
-  const transport = http(rpcUrl);
-  const publicClient = createPublicClient({ chain, transport });
-
-  await validateRpcChainId(publicClient, chain);
-
-  return { chain, transport, publicClient };
+  return baseSetup(chainId, rpcUrl);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@script/initial-distribution/src/chains.ts` around lines 51 - 59, chainSetup
duplicates baseSetup logic; change chainSetup to call and return the result of
baseSetup instead of re-implementing transport/publicClient creation and chain
resolution. Replace the body of chainSetup to delegate to baseSetup(chainId,
rpcUrl) (and, if needed, keep or remove validateRpcChainId depending on whether
baseSetup already performs it) so that chainSetup simply returns the { chain,
transport, publicClient } produced by baseSetup; reference the functions
chainSetup and baseSetup when making this change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@script/initial-distribution/src/chains.ts`:
- Line 2: Remove the unused type import `Address` from the import list in this
file (the `Address` symbol is not referenced anywhere in chains.ts); update the
import statement to omit `Address` so only used types/values are imported to
avoid dead code and linter errors.

In `@script/initial-distribution/src/tde.ts`:
- Line 51: The code incorrectly accesses tdeDisburser.address — update any usage
to access the returned account object from makeWallet by replacing
tdeDisburser.address with tdeDisburser.account.address (or destructure account
from makeWallet earlier and use account.address); ensure references to
tdeDisburser in this file (including where tdeDisburser is created by
makeWallet) consistently use the account.address property so the runtime no
longer sees undefined.

In `@script/initial-distribution/src/tdeSetup.ts`:
- Around line 27-28: Replace the call to chainSetup with baseSetup for
consistency: in tdeSetup.ts swap the line using chainSetup(CHAIN_ID, RPC_URL) to
use baseSetup(CHAIN_ID, RPC_URL), keeping the destructuring { chain,
publicClient, transport } and then continue creating the wallet with
makeWallet(chain, transport, TDE_DISBURSER_PRIVATE_KEY); ensure you import
baseSetup if not already and remove or update any unused chainSetup import/usage
to match other files (e.g., cca.ts, claimAllBids.ts).

---

Duplicate comments:
In `@script/initial-distribution/src/chains.ts`:
- Around line 51-59: chainSetup duplicates baseSetup logic; change chainSetup to
call and return the result of baseSetup instead of re-implementing
transport/publicClient creation and chain resolution. Replace the body of
chainSetup to delegate to baseSetup(chainId, rpcUrl) (and, if needed, keep or
remove validateRpcChainId depending on whether baseSetup already performs it) so
that chainSetup simply returns the { chain, transport, publicClient } produced
by baseSetup; reference the functions chainSetup and baseSetup when making this
change.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ef44210 and 7855e40.

📒 Files selected for processing (6)
  • script/initial-distribution/src/abis.ts
  • script/initial-distribution/src/cca.ts
  • script/initial-distribution/src/chains.ts
  • script/initial-distribution/src/claimAllBids.ts
  • script/initial-distribution/src/tde.ts
  • script/initial-distribution/src/tdeSetup.ts
📜 Review details
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: pkoch
Repo: idos-network/contracts PR: 13
File: src/WhaleDisburser.sol:23-28
Timestamp: 2026-02-23T19:57:43.946Z
Learning: In src/WhaleDisburser.sol, the disburse function intentionally has no access control. Anyone can call disburse(IERC20 token, address beneficiary, uint256 totalAmount, uint64 vestingStart), which is by design. The function uses safeTransferFrom(msg.sender, ...) so it only pulls from the caller's own balance, and no external funds are at risk.
📚 Learning: 2026-03-01T13:52:37.088Z
Learnt from: pkoch
Repo: idos-network/contracts PR: 19
File: script/initial-distribution/src/tde.ts:38-48
Timestamp: 2026-03-01T13:52:37.088Z
Learning: In script/initial-distribution, when using getContract({ client: walletClient }), it is possible to call read.* methods on the resulting contract even if walletClient is not explicitly extended with publicActions (as observed in tde.ts and cca.ts with viem 2.23.x). Treat this as an intentional pattern to verify: - confirm that the behavior is supported by the specific viem version and contract bindings, - document any assumptions about publicActions not being required for read operations, - ensure there are no security implications (e.g., unintended read access) and that access controls are still correct, - consider adding comments or tests that demonstrate expected usage and edge cases, and - if this behavior is relied upon in other files, apply the same pattern consistently within the script/initial-distribution directory.

Applied to files:

  • script/initial-distribution/src/claimAllBids.ts
  • script/initial-distribution/src/chains.ts
  • script/initial-distribution/src/cca.ts
  • script/initial-distribution/src/tdeSetup.ts
  • script/initial-distribution/src/abis.ts
  • script/initial-distribution/src/tde.ts
📚 Learning: 2026-02-23T19:57:43.946Z
Learnt from: pkoch
Repo: idos-network/contracts PR: 13
File: src/WhaleDisburser.sol:23-28
Timestamp: 2026-02-23T19:57:43.946Z
Learning: In src/WhaleDisburser.sol, the disburse function intentionally has no access control. Anyone can call disburse(IERC20 token, address beneficiary, uint256 totalAmount, uint64 vestingStart), which is by design. The function uses safeTransferFrom(msg.sender, ...) so it only pulls from the caller's own balance, and no external funds are at risk.

Applied to files:

  • script/initial-distribution/src/cca.ts
  • script/initial-distribution/src/abis.ts
  • script/initial-distribution/src/tde.ts
🧬 Code graph analysis (1)
script/initial-distribution/src/claimAllBids.ts (2)
script/initial-distribution/src/lib.ts (2)
  • ensureHex (10-16)
  • requireEnv (3-7)
script/initial-distribution/src/chains.ts (2)
  • baseSetup (37-43)
  • makeWallet (45-49)
🔇 Additional comments (17)
script/initial-distribution/src/chains.ts (2)

37-43: LGTM!

Clean separation of concerns: baseSetup properly initializes the chain infrastructure (chain resolution, transport, public client) and validates the RPC chain ID before returning.


45-49: LGTM!

makeWallet is a clean, synchronous helper that creates a wallet from a private key. Returning both account and walletClient provides flexibility for callers needing the address (account.address) or client operations.

script/initial-distribution/src/tde.ts (1)

14-23: LGTM on destructuring update.

The transition from account to tdeDisburser in the destructuring is correct and aligns with the new wallet abstraction pattern.

script/initial-distribution/src/claimAllBids.ts (3)

14-14: LGTM!

Correctly imports baseSetup and makeWallet from the updated chains module.


27-31: LGTM!

Clean initialization pattern: environment variable renamed appropriately, and wallet created using the new makeWallet helper.


33-37: LGTM!

Contract client correctly bound to trackerDisburser.walletClient. Based on learnings, using getContract({ client: walletClient }) for read operations is an intentional pattern verified to work with viem 2.23.x.

script/initial-distribution/src/tdeSetup.ts (2)

36-46: LGTM!

Contract clients correctly bound to tdeDisburser.walletClient. Based on learnings, this pattern allows calling read.* methods even without explicitly extending with publicActions in viem 2.23.x.


48-57: LGTM!

Return object correctly includes tdeDisburser and all necessary configuration for downstream consumers.

script/initial-distribution/src/abis.ts (4)

203-210: LGTM!

The DISBURSER view function is correctly defined to read the on-chain disburser address, enabling the verification in cca.ts.


218-228: LGTM!

VESTING_PARAMS_FOR_MODALITY is properly defined as a pure function returning vesting parameters.


263-273: LGTM!

The Disbursed event includes all necessary fields for tracking disbursements: beneficiary (indexed for filtering), transferTarget, amount, and modality.


274-288: LGTM!

Comprehensive error definitions provide clear failure modes for input validation and access control.

script/initial-distribution/src/cca.ts (5)

34-37: LGTM!

Clean dual-wallet initialization: baseSetup for chain infrastructure, then separate makeWallet calls for TDE and tracker disbursers with their respective private keys.


45-61: LGTM!

Contract clients are correctly bound to their respective wallet clients: soldTokenContract and tdeDisbursementContract use tdeDisburser.walletClient, while trackerContract uses trackerDisburser.walletClient.


71-83: LGTM!

On-chain disburser verification correctly validates both disbursers:

  • Reads disburser() from tracker contract and validates against trackerDisburser.account.address
  • Reads DISBURSER() from TDE contract and validates against tdeDisburser.account.address

This ensures the configured wallets match the on-chain authorized disbursers before proceeding.


169-180: LGTM!

ensureTdeAllowance correctly uses tdeDisburser.account.address when checking and setting token allowance for the TDE disbursement contract.


330-348: LGTM!

Final balance check and sweep logic correctly uses tdeDisburser.account.address for balance reads and the post-sweep assertion.

@pkoch pkoch force-pushed the feat/separate-disbursers branch from 7855e40 to 3f86ded Compare March 2, 2026 14:30
@pkoch pkoch merged commit 7c2c51c into master Mar 2, 2026
3 checks passed
@pkoch pkoch deleted the feat/separate-disbursers branch March 2, 2026 14:32
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