> For the complete documentation index, see [llms.txt](https://alameen.gitbook.io/streamwage/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://alameen.gitbook.io/streamwage/design-decisions.md).

# Design decisions

Every meaningful choice in StreamWage was made for a reason. This section documents the thinking behind the most significant ones, what problem each decision was solving, and what was knowingly traded away to solve it.

{% stepper %}
{% step %}

### Pull-based claims instead of push-based payments

The contract never automatically sends ETH to workers. Workers initiate their own withdrawals when they choose. This was a fundamental design choice made early and never revisited, it is the right model for an on-chain payroll.

**Why**

Push-based payments require the contract to call external addresses on a schedule, which introduces reentrancy risk, gas estimation problems, and dependency on off-chain triggers like keepers. Pull puts the execution responsibility on the worker, who has every incentive to act correctly.

**Tradeoff**

Workers must actively claim. A worker who never calls `claim()` never receives their pay, the contract will not chase them. This is a deliberate UX tradeoff in exchange for a simpler, safer contract.
{% endstep %}

{% step %}

### Beacon proxy pattern for upgradeability

Each wallet gets their own isolated proxy contract, but all proxies share one implementation through a beacon. Upgrading the beacon upgrades every instance at once.

**Why**

StreamWage is a system under active development. Bugs need to be fixable without requiring every wallet to manually redeploy or migrate. New features need to reach existing contracts without disruption. A regular proxy only handles one instance, the beacon scales that to many.

**Tradeoff**

The beacon owner has unilateral upgrade authority over all proxies. This is a real centralization risk. It was accepted because the alternative, non-upgradeable contracts, would make bugs permanent. The risk is documented, not hidden.
{% endstep %}

{% step %}

### Settle everything before any state mutation

The original accrual function only settled completed intervals. Fractional progress was carried forward as drift. This worked for basic claims but broke under state transitions, a rate change would carry the old drift forward at the new rate, silently mis-pricing earnings. The fix was to settle completely, whole intervals plus the fractional remainder, before touching any worker state.

**Why**

Every function that mutates a worker, rate changes, pauses, interval updates, term proposals, migrations, now starts from a clean, fully settled state. There is no floating drift to carry forward. The math is always correct at the point of mutation.

**Tradeoff**

Settlement on every mutation costs slightly more gas than the drift-carry approach. It is also a convention, not a compiler-enforced constraint, any future function that mutates worker state must follow the same rule or it will silently break the invariant.
{% endstep %}

{% step %}

### Partial treasury coverage instead of reverting on underfunded claims

When a worker claims and the treasury cannot cover their full accrued balance, the contract pays out whatever is available and leaves the remainder in `accruedWei` for a future claim. It does not revert.

**Why**

Reverting on an underfunded claim would leave workers with no recourse until the treasury is topped up. A partial payout means workers always receive something when funds are available, and their remaining balance is preserved accurately for when the treasury is refilled.

**Tradeoff**

Workers may receive less than expected on a single claim and need to claim again later. The `LowTreasury` event exists to alert operators before this situation arises, but it is the operator's responsibility to keep the treasury funded.
{% endstep %}

{% step %}

### Term negotiation as a consent mechanism, not a unilateral update

Operators can update a worker's rate directly via `updateWorkerRate()`. Term negotiation exists alongside that, it is for situations where the operator wants the worker's explicit on-chain consent before a change takes effect.

**Why**

A rate cut applied silently is a broken trust model. Term negotiation gives workers visibility and agency, they see the proposed terms, see the consequences of rejecting them via the `terminateOnReject` flag, and decide. The outcome is recorded on-chain regardless of which way they go.

**Tradeoff**

The worker is paused for the duration of the proposal window, so they do not accrue during negotiation. This was intentional, the contract cannot know what rate to accrue at until the worker decides. Earnings up to the proposal point are fully settled before the pause.
{% endstep %}

{% step %}

### Proposal notes emitted but not stored on-chain

When an operator proposes terms they can attach a `proposalNote` string, a human-readable explanation of the change. This string is emitted in the event log but never written to contract storage.

**Why**

Notes are for human context, they do not affect contract logic. Storing arbitrary-length strings on-chain is expensive and unnecessary. Emitting them in events makes them permanently visible on-chain without paying storage costs.

**Tradeoff**

Notes are not readable from other contracts. Any system that needs to surface the note to a user must index events off-chain. For the intended use case, a human reading a proposal, this is entirely sufficient.
{% endstep %}

{% step %}

### Termination preserves earned balance

When a worker is terminated, whether through rejection of a proposal with `terminateOnReject: true`, or via proposal expiry, their accrued balance is never zeroed. The worker record is deactivated, not deleted.

**Why**

Earned pay belongs to the worker. A termination ends the working relationship, it does not retroactively undo work already done. Zeroing the balance would be a claw-back, and that was never the intent of termination in this system.

**Tradeoff**

Terminated workers remain in the `workerList` array until they claim and their record is effectively dormant. This is a minor storage cost that was considered acceptable given how infrequently termination occurs.
{% endstep %}
{% endstepper %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://alameen.gitbook.io/streamwage/design-decisions.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
