* chore: rebrand user-facing gitlawb references to Twigpine
Rename the product name in docs, README, CLI help/output, MCP tool
descriptions, macOS app strings, installers, npm metadata and license
headers from gitlawb/Gitlawb to Twigpine. Point GitHub links at the
renamed Twigpine org (and fix the release workflow's repository guard,
which still matched Gitlawb/node), and switch the public node hosts and
website/install URLs to twigpine.com.
Protocol and operator identifiers are intentionally unchanged
(did:gitlawb, gitlawb://, signing domains, GITLAWB_* env vars, crate
and binary names, X-Gitlawb-* headers, icaptcha and peer-network URLs);
they need a follow-up migration.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* docs: point image pulls at ghcr.io/twigpine/node and fix macOS DMG name
The release workflow publishes the container image to
ghcr.io/${GITHUB_REPOSITORY,,}, which is ghcr.io/twigpine/node since the
org rename; the old ghcr.io/gitlawb/node path no longer resolves for
anonymous pulls. Point the RUN-A-NODE docker run example, the OSS
readiness audit's claim about it, and the macOS app's generated compose
file at the published path.
The README listed the macOS DMG as "Twigpine Node.dmg", but
build-macos-app.sh writes GitlawbNode-macOS.dmg (kept on purpose), so
document the real file name.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(infra): keep the AWS security group description to avoid replacement
`description` on aws_security_group is ForceNew, so renaming it forced a
replace of the in-use group on the next apply of an existing deployment.
Restore the original text and note why it must stay until a separate
infra migration.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(macos): regenerate the app's compose file instead of treating it as a repo's
Config.repoPath fell back to the data directory when no repo clone was
found, and DockerCompose then picked up the compose file it had generated
there as if it belonged to a repo. The generator therefore only ran on
first launch, so upgraded installs kept their original compose, including
the old ghcr.io/gitlawb/node image, which no longer serves anonymous pulls.
repoPath now returns nil when there is no clone, and the app regenerates
its compose file whenever no repo compose is in use. Project directory and
named volumes are unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(infra): default the AWS node image to ghcr.io/twigpine/node
release.yml publishes to ghcr.io/${GITHUB_REPOSITORY,,}, which is now
ghcr.io/twigpine/node; ghcr.io/gitlawb/node denies anonymous pulls, so the
default made new deployments fail to start and the upgrade command fail
on existing ones. On an existing deployment `terraform apply` only updates
the upgrade SSM document in place (user_data drift is ignored); the new
image is pulled on the next upgrade_command.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(macos): keep operator edits to the generated compose, migrate only the image
e04efe4 regenerated the app's compose file on every access, overwriting
edits operators make to pass settings the generated environment block
omits (e.g. GITLAWB_OPERATOR_STRICT_MODE). Generate the file only when it
is missing; when it exists, keep it and rewrite only references to the
retired ghcr.io/gitlawb/node image, keeping tags and digests.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(macos): only migrate complete ghcr.io/gitlawb/node references
Without a left boundary the migration also rewrote the retired path when
it appeared inside a longer reference, such as a registry mirror
(registry.example.com/ghcr.io/gitlawb/node), pointing it at a path the
mirror may not have.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
6.4 KiB
Twigpine economics
How $GITLAWB flows through the protocol and how stakers earn.
TL;DR
- Every protocol fee (5% of completed bounties, plus future services) flows into
GitlawbFeeDistributor— the protocol's reward wallet. - Once per week, anyone can call
distribute()to split the accumulated balance. - 75% goes to node operators (PoS staking — skin in the game).
- 24% goes to user stakers (tier-weighted passive yield).
- 1% goes to the caller of
distribute()(keeper reward).
Fee sources
All routed to GitlawbFeeDistributor:
| Source | Rate | How it gets there |
|---|---|---|
| Bounty protocol fee | 5% of payout | GitlawbBounty.treasury is set to the distributor — arrives on approveBounty() |
| Manual deposits (bankr) | variable | token.transfer(feeDistributor, amount) — any $GITLAWB sent to the address counts |
| Future: push/PR fees | TBD | node fees collected by operators, routed to distributor |
The distributor is a plain ERC20 holder — anything sent to it is fair game for the weekly split.
The weekly split
distribute() — permissionless, enforced ≥ 7 days since last call
├── 1% → msg.sender (keeper reward)
├── 75% → GitlawbNodeStaking (operator PoS rewards)
└── 24% → GitlawbStaking (user tier-weighted rewards)
Split is owner-adjustable but capped to ±5% change per update (prevents abrupt reallocations).
Node operator staking (75% track)
Contract: GitlawbNodeStaking.sol
Node operators must stake to run a node and earn rewards.
| Parameter | Value |
|---|---|
| Minimum stake | 10,000 $GITLAWB |
| Heartbeat cadence | 24 hours |
| Inactive threshold | 3 days (no heartbeat → excluded from rewards) |
| Unstake cooldown | 7 days |
Reward formula (per node, per weekly distribution):
nodeReward_i = nodeShare × (myStake / totalActiveStake) if heartbeat within 3 days
nodeReward_i = 0 otherwise
Worked example — 5 active nodes, all at 10k stake, weekly pot = 100k $GITLAWB:
- Node share (75%) = 75k
- Total active stake = 50k
- Each node earns:
75k × (10k / 50k)= 15,000 $GITLAWB/week
If one node goes offline, the active pool shrinks and remaining nodes earn more — the offline node gets nothing, its share redistributes.
Skin in the game: no automatic token slashing in v1, but offline nodes earn zero. Repeated offenses + inflated stake = dead capital.
User staking (24% track)
Contract: GitlawbStaking.sol
Anyone can stake $GITLAWB for passive yield. Four tiers with multipliers:
| Tier | Min stake | Multiplier |
|---|---|---|
| Observer | 1,000 | 1x |
| Curator | 10,000 | 2x |
| Steward | 100,000 | 4x |
| Validator | 1,000,000 | 8x |
Reward formula (per staker, per weekly distribution):
userReward_i = userShare × (myStake × myMultiplier / totalWeightedStake)
Worked example — mixed tiers, weekly pot = 100k → user share = 24k:
| Staker | Stake | Multiplier | Weight |
|---|---|---|---|
| 5× Observer | 1k each | 1x | 5,000 |
| 3× Curator | 10k each | 2x | 60,000 |
| 2× Steward | 100k each | 4x | 800,000 |
| Total weighted | 865,000 |
- Each Observer:
24k × (1k / 865k)≈ 27.7 $GITLAWB/week - Each Curator:
24k × (20k / 865k)≈ 555 $GITLAWB/week - Each Steward:
24k × (400k / 865k)≈ 11,098 $GITLAWB/week
Unstake cooldown: 7 days.
Keeper reward (1% track)
The caller of distribute() receives 1% of the pot. At 100k weekly, that's 1,000 $GITLAWB.
Why: removes operational burden. Even if the official keeper misses a run, any staker or searcher has financial incentive to call it.
Operators can run a weekly keeper job that calls distribute(); the caller wallet self-funds its gas from the 1% reward.
End-to-end flow
┌─────────────────────────────────────────┐
│ Fee sources │
│ ┌─────────────┐ ┌─────────────┐ │
│ │Bounty 5% │ │Bankr manual │ ··· │
│ └──────┬──────┘ └──────┬──────┘ │
└─────────┼─────────────────┼─────────────┘
↓ ↓
┌─────────────────────────────────────────┐
│ GitlawbFeeDistributor │
│ (the protocol reward wallet) │
└──────────────────┬──────────────────────┘
│
distribute() │ weekly, permissionless
↓
┌──────────────────────┼──────────────────────┐
│ │ │
┌────▼────┐ ┌────────▼────────┐ ┌──────▼──────┐
│ 1% │ │ 75% │ │ 24% │
│ caller │ │ NodeStaking │ │ UserStaking │
└─────────┘ │ (pro-rata: │ │ (pro-rata: │
│ stake × active)│ │ stake × m) │
└─────────────────┘ └─────────────┘
Tuning
FeeDistributor.setSplit(nodeBps, userBps, keeperBps)— owner only, sum must = 10000, each field can change ≤ 500 bps per update.FeeDistributor.setSinks(...)— swap staking contracts if upgrading.GitlawbBounty.setProtocolFee(bps)— fee cap 1000 bps (10%).
Any change is observable on-chain before it takes effect — integrators watch events.