Automated Milestone & Issue Escrow Release – WhoZNext lets repository maintainers lock an asset budget against a GitHub Milestone. When issues tied to that milestone are closed and merged, the contract automatically releases micro-payouts to developers — no manual reconciliation, no end-of-month bottlenecks.
Built on Stellar Soroban and designed to integrate with WaveGuard for access control, WhoZNext keeps engineering momentum consistent during time-boxed sprints by automating financial closure alongside every PR merge.
- Project Purpose
- Why WhoZNext?
- Target Users
- Architecture Overview
- Asset Escrow Lifecycle
- Smart Contract Design
- Getting Started
- Contract Methods
- Integration with WaveGuard
- Security & Vulnerability Protections
- Testing
- Project Structure
- Contributing
- License
WhoZNext is an automated milestone & issue escrow release system designed to solve the fundamental problem of predictable cash flow in time-boxed open-source development. It bridges the gap between engineering velocity and financial settlement, ensuring that contributor compensation keeps pace with sprint cycles.
In modern open-source projects, especially within the Stellar ecosystem, teams operate in intensive, time-boxed sprints (e.g., 1-week cycles). Traditional milestone payout systems create friction by:
- Decoupling financial settlement from engineering progress
- Creating administrative bottlenecks at arbitrary intervals
- Breaking development momentum with payment delays
WhoZNext addresses this by implementing a trustless, on-chain escrow system that automatically releases micro-payouts when engineering work is completed, eliminating manual reconciliation and end-of-month bottlenecks.
During intensive development sprints, predictable cash flow is as critical as predictable code flow. WhoZNext ensures that financial settlement keeps pace with engineering velocity, removing the most common friction point in contributor retention and enabling sustainable open-source development.
- For Contributors: Receive instant, on-chain payments the moment their PR is merged — no waiting for end-of-month processing.
- For Maintainers: Automate financial closure alongside every PR merge, reducing administrative overhead and maintaining sprint momentum.
- For the Ecosystem: Establish a reliable, trustless payment infrastructure that scales with open-source development velocity.
WhoZNext is built on three core principles:
- Automation: Eliminate manual processes through smart contract automation
- Trustlessness: Use blockchain technology to remove counterparty risk
- Speed: Enable micro-payouts that settle in near real-time
This foundation enables a new paradigm where financial settlement is an automatic byproduct of engineering progress, not a separate administrative task.
During intensive, time-boxed sprints (e.g., Drips Wave's 1-week cycles), predictable cash flow is as important as predictable code flow. WhoZNext ensures that financial settlement keeps pace with engineering velocity, removing the most common friction point in contributor retention.
| Role | How They Use WhoZNext |
|---|---|
| Repository Maintainer | Creates milestone pools, funds them with Stellar assets, and authorizes payouts as issues close. |
| Contributor | Receives instant, on-chain payments the moment their PR is merged — no waiting for end-of-month processing. |
┌─────────────────────────────────────────────────────┐
│ WaveGuard │
│ (Access Registry / Maintainer ID) │
└────────────┬─────────────────────────────┬──────────┘
│ │
▼ ▼
┌──────────────────────────────────────────────────────┐
│ WhoZNext │
│ Escrow Asset Vault │
│ ┌──────────────┐ ┌──────────────┐ ┌────────────┐ │
│ │ Milestone │ │ Issue │ │ Clawback │ │
│ │ Pool Records │ │ Claim Logs │ │ Mechanism │ │
│ └──────────────┘ └──────────────┘ └────────────┘ │
└─────────────────────┬────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────┐
│ Stellar Asset Contract (SAC) │
│ (Token Transfer Execution) │
└──────────────────────────────────────────────────────┘
- Maintainer calls
create_milestone_pool, funding the contract with a SAC token budget. - Maintainer (or CI) calls
release_issue_bountywith the issue ID, developer address, and amount. The contract verifies the maintainer's authority via WaveGuard. - Contract atomically marks the issue as claimed and transfers the payout.
- Maintainer can claw back unclaimed funds after the milestone deadline via
clawback_expired_funds.
Tokens move through four distinct phases from deposit to final settlement:
-
Pool Creation — Deposit & Lock The maintainer calls
create_milestone_pool, transferringtotal_fundsfrom their wallet into the contract's asset vault. Tokens are locked on-chain and unavailable for withdrawal until the milestone concludes. -
Issue Payout — Transfer to Developer When an issue is closed and merged, the maintainer (or CI) calls
release_issue_bounty. The contract deducts the specifiedamountfrom the pool balance and atomically transfers it to thedeveloperaddress. The issue is markedcompleted = true. -
Duplicate Claim Protection — No Double-Spend Every claim is keyed by
(repo_hash, issue_id). Ifrelease_issue_bountyis called again for the same pair, the contract reverts withBountyAlreadyClaimedbefore touching any tokens — the pool balance is unchanged. -
Clawback — Unclaimed Funds Returned After the milestone deadline, the maintainer calls
clawback_expired_funds. Any remaining balance in the vault is transferred back to the maintainer, and the pool is cleared. No funds are ever stranded on-chain.
| Storage Tier | Data | Key Schema | Rationale |
|---|---|---|---|
| Instance | Milestone pool metadata (asset address, total budget, guard contract ref) | DataKey::Pool (singleton) |
Persists for the contract's lifetime; cheap one-time bump. |
| Persistent | Individual issue claim records | DataKey::IssueClaim(repo_hash, issue_id) → IssueClaim |
Security fix (CM-01): Temporary storage TTL expiry allowed replay attacks. Persistent storage ensures the duplicate-claim guard is durable for the contract's lifetime. |
Dual validation gates every payout:
maintainer.require_auth()– The calling address must be an authorized maintainer registered in WaveGuard.- Recipient match – The
developeraddress in the call must match the contributor record associated with the issue.
Failed validation → BountyAlreadyClaimed or UnauthorizedMaintainer error (contract reverts).
#[derive(Clone)]
#[contracttype]
pub struct IssueClaim {
pub issue_id: u32,
pub developer: Address,
pub payment_amount: u128,
pub completed: bool,
pub maintainer: Address,
pub claimed_at: u64,
}Claim records are stored under a composite key of (repo_hash, issue_id), guaranteeing uniqueness across repositories. Each record includes the maintainer who authorized the payout and the claimed_at ledger timestamp for auditing.
- Rust 1.81+
- Soroban CLI
- A Stellar testnet/futurenet identity for deployment
cd contracts/whoz_next
cargo build --releasecargo test -- --nocaptureSee Testing for details on the integration test suite.
Initializes a new milestone escrow pool. Transfers total_funds from the caller to the contract's asset vault, links to a WaveGuard registry identity, and sets the milestone deadline.
fn create_milestone_pool(
env: Env,
maintainer: Address,
guard_contract: Address,
asset: Address,
total_funds: u128,
expiry: u64,
) -> Result<(), Error>;| Parameter | Type | Description |
|---|---|---|
maintainer |
Address |
Stellar address of the pool creator. Must be registered in WaveGuard. |
guard_contract |
Address |
Address of the deployed WaveGuard contract instance. Cannot be the same as the WhoZNext contract. |
asset |
Address |
Stellar Asset Contract (SAC) token ID to use for payouts. Must be a trusted SAC instance. |
total_funds |
u128 |
Total budget to lock for this milestone (in the smallest unit of asset, e.g. stroops). Must be > 0. |
expiry |
u64 |
Unix timestamp (in seconds) after which unclaimed funds can be clawed back. Must be strictly in the future. |
maintainer.require_auth()— the caller must sign the transaction.- WaveGuard
is_maintainercheck — the caller must be registered as an active maintainer in the WaveGuard registry.
-
Prerequisites:
- Deploy a WaveGuard contract instance and register at least one maintainer address.
- Deploy or identify a Stellar Asset Contract (SAC) for the token you want to use for payouts.
- Ensure the maintainer address holds sufficient tokens (
≥ total_funds) and has the deployer identity funded with XLM for transaction fees.
-
Compute the
expirytimestamp:# Example: 30 days from now (Unix seconds) date -d "+30 days" +%s
-
Invoke the function:
soroban contract invoke \ --id <WHOZNEXT_CONTRACT_ID> \ --source <MAINTAINER_KEY> \ --network-passphrase "Test SDF Network ; September 2015" \ --rpc-url https://soroban-testnet.stellar.org \ -- \ create_milestone_pool \ --maintainer <MAINTAINER_ADDRESS> \ --guard_contract <WAVEGUARD_ID> \ --asset <SAC_TOKEN_ID> \ --total_funds <AMOUNT> \ --expiry <UNIX_TIMESTAMP> -
Verify the pool:
soroban contract invoke \ --id <WHOZNEXT_CONTRACT_ID> \ --source <MAINTAINER_KEY> \ --network-passphrase "Test SDF Network ; September 2015" \ --rpc-url https://soroban-testnet.stellar.org \ -- \ milestone_info
// Funds: 10,000 USDC (using 7-decimal places → 100_000_000_000 stroops)
// Expiry: Unix timestamp for 30 days from creation
client.create_milestone_pool(
&maintainer, // Pool creator / maintainer
&guard_contract, // Deployed WaveGuard address
&usdc_token, // USDC Stellar Asset Contract
&100_000_000_000u128, // 10,000 USDC in smallest unit
&1_700_000_000u64, // Expiry timestamp
);| Error | Condition |
|---|---|
UnauthorizedMaintainer |
Caller is not registered in WaveGuard. |
InvalidGuard |
guard_contract equals the WhoZNext contract's own address. |
InvalidAmount |
total_funds is zero. |
InvalidExpiry |
expiry is zero. |
ExpiryInPast |
expiry is ≤ current ledger timestamp. |
PoolAlreadyExists |
A milestone pool has already been created (singleton contract). |
TransferFailed |
Maintainer has insufficient token balance. |
Releases a micro-payout to a developer once their issue is closed and merged.
fn release_issue_bounty(
e: Env,
repo_hash: BytesN<32>,
issue_id: u32,
developer: Address,
amount: u128,
) -> Result<(), Error>;| Parameter | Description |
|---|---|
repo_hash |
SHA-256 hash identifying the GitHub repository (linked to a milestone). |
issue_id |
GitHub issue number (composite key with repo_hash prevents cross-repo collisions). |
developer |
Stellar address receiving the payout. |
amount |
Payout amount (must not exceed remaining pool balance). |
Auth: maintainer.require_auth() + recipient address validation.
Safety: Once completed is set to true for a given (repo_hash, issue_id) pair, any subsequent call reverts with BountyAlreadyClaimed.
Returns unclaimed funds to the maintainer after a milestone deadline passes.
fn clawback_expired_funds(env: Env, maintainer: Address) -> Result<(), Error>;| Parameter | Type | Description |
|---|---|---|
maintainer |
Address |
The original pool creator. Must match pool.maintainer exactly. |
Auth: maintainer.require_auth(). Only callable after milestone expiry. WaveGuard is NOT checked — clawback uses direct address equality against pool.maintainer so that a compromised WaveGuard registry cannot block fund recovery.
| Error | Condition |
|---|---|
PoolNotFound |
No milestone pool has been created. |
PoolNotExpired |
Current ledger timestamp is before or at pool.expiry. |
UnauthorizedCaller |
maintainer does not match pool.maintainer. |
NoFundsToClawback |
Remaining balance is zero (fully claimed or already clawed back). |
(Implement as needed for front-end integration)
| Method | Returns | Description |
|---|---|---|
milestone_balance(e) |
u128 |
Current remaining balance in the milestone pool. |
is_claimed(repo_hash, issue_id) |
bool |
Whether a specific issue has already been paid out. |
milestone_info(repo_hash) |
MilestoneData |
Retrieve pool metadata for a given repo. |
WhoZNext depends on WaveGuard as its identity and access registry. It is used as the source of truth for who is an authorized maintainer.
WhoZNext makes a single cross-contract call:
/// Returns true if `address` is a registered, authorized maintainer.
fn is_maintainer(env: Env, address: Address) -> bool;| Operation | Stellar require_auth |
WaveGuard is_maintainer |
|---|---|---|
create_milestone_pool |
✅ | ✅ |
release_issue_bounty |
✅ | ✅ |
clawback_expired_funds |
✅ | ❌ (direct pool.maintainer address check) |
clawback_expired_funds uses a direct address equality check against the original pool.maintainer (set at pool creation) rather than re-consulting WaveGuard. This design decision isolates the clawback path from a potential WaveGuard compromise — a rogue WaveGuard upgrade cannot reroute unclaimed funds.
If the WaveGuard check fails, the call reverts immediately with:
| Situation | Error Code |
|---|---|
| Caller not registered in WaveGuard | UnauthorizedMaintainer (5) |
| Clawback caller ≠ original pool maintainer | UnauthorizedCaller (6) |
guard_contract address is zero/missing |
MissingGuardContract (11) |
- Deploy a WaveGuard instance and register maintainer addresses.
- Pass the WaveGuard contract address as
guard_contractwhen callingcreate_milestone_pool. - The
guard_contractaddress is fixed at pool creation — it cannot be changed afterward. If the WaveGuard registry is ever compromised, a new pool must be created with a fresh guard address. - WhoZNext handles all cross-contract calls internally; the integration is transparent to contributors.
The contract uses a strict composite storage key combining repo_hash + issue_id. Claim records are stored in Persistent storage (security fix CM-01), ensuring the duplicate-claim guard survives for the contract's lifetime. Once completed is set to true, any subsequent release_issue_bounty for that exact (repo_hash, issue_id) pair immediately reverts with a BountyAlreadyClaimed error code — before any token transfer occurs.
If a maintainer attempts to allocate more tokens than remain in the milestone pool, the contract detects the insufficient balance and gracefully reverts without locking up or losing any assets. Remaining funds stay accessible for future legitimate claims or clawback.
Only WaveGuard-verified maintainer addresses can create pools, release bounties, or claw back funds. Contributor addresses are write-restricted — they can only receive payouts, not trigger them.
The integration test suite covers:
- Happy path: Create pool, fund, release bounty, verify recipient balance.
- Duplicate claim: Attempt double-spend of the same issue ID → expect revert.
- Over-allocation: Attempt payout exceeding pool balance → expect graceful revert with no asset loss.
- Clawback: Claim expiry, return unclaimed funds to maintainer.
- Unauthorized caller: Non-maintainer addresses rejected.
# Run all tests (unit + integration) with output
cargo test -- --nocapture
# Run only unit tests
cargo test --package whoz-next --lib -- --nocapture
# Run only integration tests
cargo test --package whoz-next --test '*' -- --nocapture
# Run a specific test by name
cargo test test_duplicate_claim_rejected -- --nocaptureIntegration tests require the wasm32-unknown-unknown target:
rustup target add wasm32-unknown-unknown# Run all tests in a Docker container
docker compose -f docker/docker-compose.yml run --rm testThe test suite is organized into two categories:
Unit tests (contracts/whoz_next/src/test.rs):
- Test individual function behavior and error paths
- Validate input validation and authentication logic
- Run quickly with no external dependencies
Integration tests (contracts/whoz_next/tests/*.rs):
- Test cross-contract interactions with real Soroban environment
- Deploy a mock SAC token and mock WaveGuard contract
- Simulate the full milestone lifecycle
- Happy path (
full_lifecycle.rs): Create pool, fund, release bounty, verify recipient balance. - Duplicate claim (
duplicate_claim.rs): Attempt double-spend of the same issue ID → expect revert. - Over-allocation (
over_allocation.rs): Attempt payout exceeding pool balance → expect graceful revert with no asset loss. - Clawback (
clawback.rs): Claim expiry, return unclaimed funds to maintainer. - Unauthorized access (
unauthorized_access.rs): Non-maintainer addresses rejected.
# Run all tests via the convenience script
./scripts/test.sh
# Run a specific test
./scripts/test.sh test_duplicate_claim_rejected
# Run with environment overrides
RUST_LOG=debug ./scripts/test.shwhoz-next/
├── contracts/
│ └── whoz_next/
│ ├── src/
│ │ ├── lib.rs # Core ledger tracking and token distribution execution
│ │ └── types.rs # Milestone allocations, error enums, and storage keys
│ └── Cargo.toml
├── tests/ # Integration test suite
├── Cargo.toml # Workspace manifest
└── README.md
| File | Purpose |
|---|---|
contracts/whoz_next/src/lib.rs |
Core contract logic — pool creation, bounty release, clawback. |
contracts/whoz_next/src/types.rs |
MilestoneAllocation, error enum (BountyAlreadyClaimed, InsufficientPoolBalance, UnauthorizedMaintainer), storage key definitions. |
contracts/whoz_next/Cargo.toml |
Contract dependencies (soroban-sdk, waveguard-client, etc.). |
tests/*.rs |
Integration tests with mock SAC token and full lifecycle coverage. |
Contributions are welcome! Please follow the standard workflow:
- Fork the repository.
- Create a feature branch (
git checkout -b feat/my-feature). - Commit your changes (
git commit -am 'Add feature'). - Push to the branch (
git push origin feat/my-feature). - Open a Pull Request.
Ensure all tests pass and new code includes appropriate test coverage. For major changes, please open an issue first to discuss what you would like to change.
This project is licensed under the Apache License 2.0. See LICENSE for details.