Contract reference
Contract: YieldStreamDistributor · source: contracts/distributor/src/lib.rs · testnet: CC25WDHZ7SQC2UMZJHIPYM2U5ROYFXVDZMP3THMIL3IJY6HOCGGX2FPG
Signatures
| Function | Returns | Authorized by |
|---|---|---|
| initialize(admin: Address) | () | admin |
| create_distribution(creator, token, merkle_root, total_amount, num_recipients, end_time) | u32 (new id) | creator |
| claim(distribution_id, index, recipient, amount, proof) | () | recipient |
| cancel(distribution_id) | () | campaign creator |
| sweep(distribution_id, to) | i128 (swept) | campaign creator |
| get_distribution(distribution_id) | Distribution | — view |
| is_claimed(distribution_id, index) | bool | — view |
| get_distribution_count() | u32 | — view |
| get_admin() | Address | — view |
| compute_leaf(distribution_id, index, recipient, amount) | BytesN<32> | — pure |
| verify_proof(proof, root, leaf) | bool | — pure |
Types
struct Distribution {
id: u32
creator: Address
token: Address
merkle_root: BytesN<32>
total_amount: i128
claimed_amount: i128
num_recipients: u32
claimed_count: u32
created_at: u64 // ledger timestamp
end_time: u64 // 0 = no expiration
status: DistributionStatus
}
enum DistributionStatus { Active = 0, Cancelled = 1, Completed = 2, Expired = 3 }Write functions
initialize(admin)
Stores the administrator address. Callable once; a second call returns AlreadyInitialized. The stored admin is readable by get_admin but is not consulted by any other function — there is no admin-only behaviour yet (issue #9).
create_distribution(creator, token, merkle_root, total_amount, num_recipients, end_time) → u32
Allocates the next sequential id, pulls total_amount of token from creator into the contract, and writes an Active campaign record. Returns the new id.
total_amountmust be > 0 → elseInvalidAmountnum_recipientsmust be > 0 → elseInvalidRecipientCountend_timemay be 0 (no expiry) or a future timestamp → a past value returnsInvalidEndTime
The contract does not verify that the root commits to num_recipients leaves, that allocations sum to total_amount, or that token is a real token contract. Those are issuer responsibilities today and the subject of issue #7. A root that under-commits the budget leaves tokens stranded; one that over-commits lets claims exceed escrow and later claimers get reverted transfers.
claim(distribution_id, index, recipient, amount, proof)
The core path. Checks run in this order, and the order is what you will debug against:
recipient.require_auth()— the claimer signs- campaign must exist →
DistributionNotFound amount > 0→InvalidAmountindex < num_recipients→InvalidLeafIndex- leaf not already claimed →
AlreadyClaimed - status not
Cancelled→DistributionCancelled; status must beActive→DistributionNotActive end_time == 0or not yet passed →DistributionExpired- recompute the leaf from the arguments and walk the proof to the root →
InvalidProof
On success it marks the leaf claimed, increments claimed_amount and claimed_count, flips status to Completed when every leaf or every token is accounted for, transfers amount to the recipient, and emits claimed.
Note that (distribution_id, index, recipient, amount) is what the leaf commits to — the recipient cannot substitute a different recipient or amount than the issuer encoded, because the recomputed leaf would not reach the root.
cancel(distribution_id)
Creator-only. Requires status Active, sets Cancelled, and emits cancel with the remaining amount. It does not move tokens — recovery is a separate sweep call.
sweep(distribution_id, to) → i128
Creator-only. Allowed when the campaign is Cancelled or its end_time has passed; otherwise DistributionNotExpired. Transfers the remaining balance to to and returns the amount. Sweeping a Completed campaign, or one with nothing left, returns NothingToSweep.
Known defect at this version: sweep writes claimed_amount = total_amount, so a campaign that was cancelled with 80% unclaimed reads back as 100% claimed. The tokens are correct; the reported statistics are not. Tracked as issue #8.
Events
Topics are short symbols; the campaign id and the acting address are in the topic list, the payload follows as data.
| Topic | Topics | Data |
|---|---|---|
| created | ("created", id, creator) | (token, merkle_root, total_amount, end_time) |
| claimed | ("claimed", id, recipient) | (index, amount) |
| cancel | ("cancel", id, creator) | remaining_amount |
| swept | ("swept", id, creator) | (to, swept_amount) |
Error codes
Returned as ContractError and surfaced on-chain as Contract error #N in transaction results.
| # | Name | Raised when |
|---|---|---|
| 1 | NotInitialized | get_admin before initialize |
| 2 | AlreadyInitialized | second initialize call |
| 3 | Unauthorized | reserved — not currently raised |
| 4 | InvalidAmount | amount ≤ 0 on create or claim |
| 5 | InvalidLeafIndex | index ≥ num_recipients |
| 6 | DistributionNotFound | unknown campaign id |
| 7 | DistributionNotActive | status is not Active |
| 8 | DistributionCancelled | claim against a cancelled campaign |
| 9 | DistributionExpired | claim after end_time |
| 10 | DistributionNotExpired | sweep before cancel or expiry |
| 11 | AlreadyClaimed | leaf index already spent |
| 12 | InvalidProof | proof does not reach the root |
| 13 | ArithmeticOverflow | checked add/sub failed |
| 14 | InvalidEndTime | end_time in the past |
| 15 | NothingToSweep | completed, or zero remaining |
| 16 | InvalidRecipientCount | num_recipients == 0 |
| 17 | ZeroAddress | reserved — not currently raised |
Codes 3 and 17 are defined but never returned by the current code — auth failures come from require_auth itself, not the error enum.