# forge An on-chain software forge, browsable in gnoweb and writable only through signed transactions. The domain model lives in [`gno.land/p/moul/forge/v0`](../../../../p/moul/forge/v0); this realm is the wiring, the routes and the events. The code itself is not here. Git objects stay in git, behind whatever mirror a repo declares. What the chain keeps is the part a forge is trusted for: which object a branch points at, in what order, on whose authority, and what was reviewed before it moved. ## Routes | path | page | | --- | --- | | `/` | every repo, with counts | | `//` | repo overview: refs, recent log, open changes and issues | | `///log` | the full reference log, paginated with `?page=N` | | `///issues` | issues, `?page=N` | | `///issues/` | one issue and its replies | | `///changes` | change requests, `?page=N` | | `///changes/` | one change, its reviews and its replies | | `/help` | what the realm is and how to call it | Every action link on those pages is a `txlink` into the matching function, so the whole forge is usable from gnoweb without a client. ## Writing ```sh gnokey maketx call -pkgpath gno.land/r/moul/forge/v0 \ -func CreateRepo -args "moul/forge" -args "an on-chain forge" -args "" \ -gas-fee 1000000ugnot -gas-wanted 3000000 \ -broadcast -chainid -remote ``` ```sh # move a branch: the fourth argument is the tip you expect to replace gnokey maketx call -pkgpath gno.land/r/moul/forge/v0 \ -func SetRef -args "moul/forge" -args "refs/heads/main" \ -args "" -args "" -args "ship it" \ -gas-fee 1000000ugnot -gas-wanted 3000000 \ -broadcast -chainid -remote ``` A stale expectation aborts the transaction instead of overwriting the branch. A move with no expectation is `ForceSetRef`, needs `maintainer`, and is recorded as a force forever. Arguments that would be slices are comma-separated strings (`SetMirrors`, `OpenIssue` labels): a transaction can only carry strings, so a `[]string` parameter would not be callable from gnokey. ## Reading Read paths are free. Browse the routes above, or query: ```sh gnokey query vm/qeval -data 'gno.land/r/moul/forge/v0.RefOID("moul/forge","refs/heads/main")' -remote gnokey query vm/qeval -data 'gno.land/r/moul/forge/v0.LogHead("moul/forge")' -remote ``` `LogHead` is the digest of the last log entry, committing to every entry before it. Pin it in a release note or a package manifest and the repo's whole history becomes falsifiable by anyone who can read the chain. ## Namespaces A repo id is `/`. The namespace is either a name the caller holds in `r/sys/users` or the caller's own bech32 address, checked on every `CreateRepo` and `Fork`: - `g1.../forge` works for any account with nothing to register and nothing to lose, including a realm, whose address is its namespace. That is what makes a DAO-owned repo work. - `moul/forge` requires the `moul` name to resolve to the caller, renames included, since the registry resolves aliases to the same record. Nobody can claim a namespace they do not own, so there is no squatting to arbitrate and no reservation list to maintain. ## Roles `reader < writer < maintainer < admin < owner`. Writers move refs, maintainers force and merge, admins manage members and the merge policy, and the last owner cannot be demoted. Opening an issue or a change request needs no role at all: the author pays gas and locks the storage deposit for their own bytes, which is the spam gate. A role can be held by **another realm**, because the realm resolves its caller without requiring an end user. A repo whose owner is a DAO realm is a repo whose merge button is a governance vote, which is the thing a centralized forge structurally cannot offer. ## Events Every mutation emits one: `RepoCreated`, `RepoForked`, `RepoUpdated`, `MemberSet`, `RefLog`, `IssueOpened`, `IssueComment`, `IssueClosed`, `IssueReopened`, `IssueLabeled`, `ChangeOpened`, `ChangeUpdated`, `ChangeReviewed`, `ChangeComment`, `ChangeClosed`, `ChangeMerged`. `RefLog` carries the sequence number and the chain digest, so an indexer can mirror the log without replaying state. ## Costs A realm write locks 100ugnot per byte, refundable when the data is deleted. A log entry is roughly 200 bytes including the digest, so a push costs about 0.02 GNOT of deposit plus gas: the reason this design anchors refs instead of storing objects. An issue with a 500-byte body is about 0.05 GNOT. --- Part of **[moul/gno-contracts](https://github.com/moul/gno-contracts)** — moul's versioned gno.land contracts. See the repository for the full catalog, build/test tooling, and usage. **Dependency graph:** ![gno.land/r/moul/forge/v0 dependency graph](https://raw.githubusercontent.com/moul/gno-contracts/main/_assets/gno.land/r/moul/forge/v0/deps.png) > ⚠️ **Disclaimer:** provided as-is, without warranty; not security-audited. Full disclaimer: [DISCLAIMER](https://github.com/moul/gno-contracts/blob/main/DISCLAIMER.md).