Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
145d598
feat: txstore, resolver, cascade, git-remote-gib push/fetch
shruggr Sep 18, 2026
566fb77
fix: ordfs outpoints use internal txid byte order
shruggr Sep 19, 2026
1dab623
fix: cascade cites untouched dirs, validate before broadcast, spend b…
shruggr Sep 19, 2026
4056589
refactor: use @1sat/actions, templates, and client instead of copies
shruggr Sep 19, 2026
8542ad5
gib init, .gib repository metadata, genesis remote rewrite, push fixes
shruggr Sep 19, 2026
b3d80eb
git-remote-gib: advertise HEAD from .gib defaultBranch
shruggr Sep 19, 2026
e6a599d
git-remote-gib: fetch walks the head chain so clones get full history
shruggr Sep 19, 2026
721a6d5
gib: .gib is read from the genesis tree only
shruggr Sep 19, 2026
a76163e
publish: content outputs are OP_FALSE OP_RETURN; refuse dust otherwise
shruggr Sep 19, 2026
f2efc71
script: build zero-sat data outputs with BitCom's scriptPrefix (OP_FA…
shruggr Sep 19, 2026
a4606da
Use SDK codecs and PushDrop helpers; drop vendored actions tarball
shruggr Sep 19, 2026
a7e4d7c
Add .gib repository metadata
shruggr Sep 19, 2026
7bee0b0
Readable wallet errors, no explicit signAndProcess, GIB_WALLET_URL ov…
shruggr Sep 19, 2026
864b044
Fixed action labels; commit sha moves to a head output tag
shruggr Sep 19, 2026
4f8eefa
Deprecate in favour of the Go implementation
shruggr Sep 20, 2026
72363ad
gib: parse gib:// remotes and discover overlays with BRC-180
shruggr Sep 20, 2026
18fa592
gib: the two sync lookups as a client
shruggr Sep 20, 2026
81d6e03
gib: one head per commit
shruggr Sep 20, 2026
700522d
gib: fetch and pull over the sync lookups
shruggr Sep 20, 2026
f4924ca
gib: advertise from the store, not the wallet's basket
shruggr Sep 20, 2026
63fb3ef
gib: drive the whole model through real git
shruggr Sep 20, 2026
370b9ea
gib: a push is told what the peer has, not what we minted
shruggr Sep 20, 2026
a188d8d
docs: state the model the code now implements
shruggr Sep 20, 2026
dddd08f
gib: record each content transaction as it is published
shruggr Sep 20, 2026
df20a46
gib: import saveRepoState at the top of the CLI
shruggr Sep 20, 2026
f2ada0d
gib: test the chain across more than one content transaction
shruggr Sep 20, 2026
2b78f5c
gib: name the deleted-branch gap where it bites
shruggr Sep 20, 2026
7e244b2
gib: a fetch must satisfy every parent it has imported, not the last one
shruggr Sep 20, 2026
348780e
gib: check the wallet returned the outputs we planned
shruggr Sep 20, 2026
49547ee
gib: survive a bad peer, a bad fetch and a commit git does not have
shruggr Sep 20, 2026
2ced0f7
gib: the rest of the review pass
shruggr Sep 20, 2026
26fce9d
gib: one head per push, with the commit objects in the tree
shruggr Sep 21, 2026
b784d8c
docs: describe the model as it now is
shruggr Sep 21, 2026
83ab048
gib: send a peer the content a head's tree cites
shruggr Sep 21, 2026
9ef15c2
Merge pull request #3 from opldotdev/feat/commit-chain
shruggr Sep 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gib
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "gib-cli",
"description": "git remote helper for gib, on-chain git on BSV",
"defaultBranch": "feat/gib"
}
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
node_modules/
.gib/
*.rawtx.hex
88 changes: 88 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Working on gib-cli

Read `README.md` first: it states the model, and the model is not up for
re-litigation here. This file is about how to work in the repository.

## Ground rules

- `bun run typecheck` and `bun test` must both pass before anything is committed.
- **No network, no wallet, no chain in tests.** `test/fakes/` has a BRC-100 wallet that
funds, signs and verifies in memory, a gib peer that serves the BRC-180 manifest, both
BRC-24 lookups and the BRC-22 submit route, and git helpers. Anything that would touch
mainnet belongs in a fake.
- Never perform an on-chain transaction or call a real wallet while developing.
- Say **"repository origin"** for the genesis `ordfs/dir` root outpoint, in code,
comments, errors and commit messages. Never bare "origin": ordinals have origins and
git has a remote named `origin`.
- Outpoints are `txid_vout` everywhere they are written down (`parseOutpoint` also reads
`txid.vout`, because BRC-100 wallets use the dot form).
- Comments explain *why*. The what is in the code.

## What is where

| Area | Files |
| --- | --- |
| remote URLs, discovery, peer client | `src/remote/url.ts`, `discover.ts`, `peer.ts` |
| syncing, ref naming, helper protocol | `src/remote/sync.ts`, `advertise.ts`, `helper.ts` |
| planning a tree, packing it into transactions | `src/cascade.ts`, `src/chain.ts` |
| push, fetch, init | `src/push.ts`, `src/fetch.ts`, `src/init.ts` |
| published trees, and the `.git` store | `src/tree.ts` |
| head token | `src/token.ts`, `src/seal.ts`, `src/head.ts`, `src/publish.ts` |
| byte formats | `src/ordfs/` |
| local state | `src/txstore.ts`, `src/refs.ts`, `src/identity.ts`, `src/pending.ts` |

## Things that will bite you

- **`.git` in a published root is not a git directory.** It is the repository's object
store, keyed by sha: commit objects as files, their trees as directories, and a `.`
default entry aliasing the tip commit. `stripGitDir` removes it, and every path that
turns a published tree back into git's tree must call it or the sha will not match.
- **Ancestor trees are not optional.** git's connectivity check is
`git rev-list --objects`, which walks commit to tree to blob; a history missing one
ancestor tree is one git refuses to fetch. Test it with `git fsck --strict`.
- **A same-transaction directory reference is one byte.** A transaction holds at most
256 outputs. `chain.ts` packs a plan across as many transactions as it needs, laying
nodes down in dependency order, so nothing has to fit in one.
- **A patch needs a base with a txid.** Bytes still waiting in the transaction being
built cannot be patched against, so they are written whole. Identical content is a
citation, never a no-op patch.
- **A head has no inscription.** It is a bare PushDrop of six fields plus the signature.
Reading the commit it publishes means reading its root's `.git`, which costs content —
so `list` resolves the sha for a branch's newest head only, never for every head on
the chain.
- **A directory reached twice is not a cycle.** Ancestor trees share every subdirectory
that has not changed. A cycle is a directory that contains itself.
- **A loose git object is written read-only.** Never rewrite one that exists.
- **`list for-push` must advertise the peer's view**, not everything this client knows,
or git will decide the remote already has a head that was only ever minted locally and
send nothing.
- **The wallet's basket is spend authority, not a ref list.** Refs come from the store
and `src/refs.ts`; the basket is only consulted to find the head this wallet may spend.
- BRC-24 answers carry `result` as a JSON document encoded *into a string*: parse twice.
- The client never asks a third party for a repository's transactions. There is no
default gateway; the only host it contacts is the one in the `gib://` URL (resolved
through BRC-180).

## Known gaps

- `ls_gib` has no query that enumerates a repository's branches. `branchCandidates` in
`src/remote/sync.ts` guesses from what is already known, the genesis tree's `.gib`
`defaultBranch`, and `main`/`master`; `gib sync <url> <branch>` is how a user names one
it could not guess. That is why `gib init` still writes `defaultBranch`, and it is the
one gap with a visible cost to users.
- A push reads every reachable commit's tree it has not published before, one
`git ls-tree` at a time, and holds the tree being planned in memory. The first push of
a long history is bounded by that.
- Every push walks the peer's whole branch twice — once for `list for-push`, once in
`syncPeer` — because both start from an empty cursor on purpose. Correct, but linear in
history, for ever.
- A `.git` manifest lists every reachable commit and tree, so it is rewritten in full on
every push and grows linearly with history. `ordfs/dir` counts entries in a uint16, so
a repository is capped at ~32k commits (two entries each).
- An octopus merge of three or more parents only fits two lineages in a head: the spend
and the one `branchedFrom` field.
- A file edited in N commits ends up behind an N-deep patch chain; there is no
"rewrite it whole after N" policy, and `prefetchTree` stops following at depth 64.
- `headsSince` says nothing about whether a branch's last head has been spent, so a
branch deleted by burning its head still advertises to anyone who learns about it from
a peer rather than from their own delete. See the TODO on `pullBranch`.
160 changes: 142 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,148 @@
# gib

> **Deprecated.** This TypeScript implementation is superseded by the Go one at
> [b-open-io/gib](https://github.com/b-open-io/gib), which runs the gib overlay
> locally, speaks the same on-chain formats, and is the supported `git-remote-gib`.
> Published on chain as repository origin
> `6d46f3406ee04d1b9b1379d0c012b6b0bb8ff0bc18f19d4709f9f6cefb94569d_82`.
> This repository is kept for history only and receives no further work.

On-chain git for BSV. Content is write-once chain outputs; directories are `ordfs/dir`
inscriptions; branch pointers are sealed push-drop coins ("commit tokens"); gib sits on
top of local git as the chain codec + pointer/authority layer via a `git-remote-gib`
remote helper. Git stays git — only push/fetch touch the chain.
manifests; a branch is a chain of sealed push-drop coins ("commit heads"), one per push.
gib sits on top of local git as the chain codec plus pointer/authority layer, via a
`git-remote-gib` remote helper. Git stays git — only push and fetch touch the chain.

## The model

**One head per push.** A push mints a single head token, spending the branch's previous
head. Commits are hash-linked, so a signature over the tip commits to every ancestor: a
head per commit would buy nothing and cost a transaction each.

**The published root is git's tree, plus `.git`.** One extra entry on the root holds the
repository's own object store, keyed by sha: every commit object reachable from the tip,
and every one of those commits' trees. Strip that entry and what is left is byte for
byte the tree git hashed, which is why the commit still verifies — see `stripGitDir`,
the only place the model bends.

The store holds ancestors' *trees* and not only their commit objects because git will
not accept a history without them: its connectivity check walks commit to tree to blob,
so a fetch missing one ancestor tree is a fetch git rejects. It also carries a `.`
default entry aliasing the tip commit object, which is how a reader learns which commit
a head publishes without reading the whole store.

**Names are shas, so nothing is published twice.** A commit or a tree already on chain
is cited at the outpoint holding it, exactly like an unchanged file. Branching from
someone else's head therefore copies none of their objects, and an incremental fetch
skips every name git already has.

**The head token** is a bare 1-satoshi PushDrop — nothing is inscribed beside it:
fields `["gib", <repository origin>, <branch>, <root>, <identity>, <branched from>]`,
protocol `[1, "gib branch"]`, keyID = the root outpoint, counterparty `anyone`, basket
`gib`, labels `gib push` / `gib delete`, tags `origin:<o>`, `branch:<n>`,
`commit:<sha>`, `randomizeOutputs: false`.

The sixth field is empty on an ordinary push, set on a branch's first head (naming the
head it forked from) and on a merge (naming a head publishing the second parent, while
the spend covers the first). A head's parents mirror its commit's parents by
construction. The five-field heads with an inscribed commit that gib published before
this are a different format and do not decode: a clean break, no compatibility path.

**The client holds no overlay.** No engine, no database, no topic manager, no chain
tracker; the client never validates a merkle proof. It keeps the transactions it has
been given in `$GIB_HOME/txstore` and, beside them, the newest head it has seen per
`(identity, branch)` in `$GIB_HOME/repos/<repository origin>.json`. The peer's overlay
validates; the client asked it for what it got.

**Remotes are peers.** A remote URL is `gib://<host>/<repository origin>`, or
`gib://<repository origin>` for local only. "Repository origin" is always the genesis
`ordfs/dir` root outpoint — never bare "origin", which ordinals and git both already
use for something else.

**Syncing is the lookup service.** Two BRC-24 questions on `ls_gib`: `headsSince` walks
one branch's heads from a point forward, oldest first, each with its own BEEF; `txs`
fetches whole transactions by txid (at most 50) as one merged BEEF. Publishing is a
BRC-22 submission of an atomic BEEF to `tm_gib`.

**Discovery is BRC-180.** A host is resolved by fetching `https://<host>/manifest.json`
and reading `metanet.overlays`: `tm_gib` is the submit endpoint, `ls_gib` the lookup
endpoint, each used verbatim. A host with no manifest, or no entry for gib, is treated
as the overlay itself at `/1sat/gib/overlay` — that is not probing, it is contacting
exactly the host the user named. `gibhub.net` publishes
`https://api.1sat.app/1sat/gib/overlay` for both; `api.1sat.app` serves no manifest and
works through the fallback.

**Ref naming.** Heads signed by this wallet advertise as `refs/heads/<branch>`; every
other publisher's as `refs/heads/@<66-hex identity>/<branch>`. Pushing an `@…` ref is
refused. The identity is cached in `$GIB_HOME/identity`, so listing works with no
wallet; with neither wallet nor cache, nothing is bare. `HEAD` resolves to the genesis
head's branch — the earliest head on the repository origin, the one whose root *is* the
origin.

**`.gib`** carries a name and a description, and for now a `defaultBranch`: no lookup
enumerates a repository's branches, so a clone that has never heard of a repository has
nothing else to ask a peer for. The file may one day also carry publishing hints — patch
depth, outputs per transaction, stream sizes — but those would be hints a client may
honour, not rules anyone can enforce. None is implemented.

## Use it

```bash
bun install
ln -s "$PWD/src/git-remote-gib.ts" ~/.local/bin/git-remote-gib # git finds helpers on PATH
ln -s "$PWD/src/main.ts" ~/.local/bin/gib

cd my-project
git init && git add -A && git commit -m init # gib init needs a commit
gib init # mints the repository; writes .gib
git remote add gib gib://gibhub.net/<repository origin>
git push gib main # publishes it to that peer

git clone gib://gibhub.net/<repository origin> # anyone, no wallet needed
```

`gib init` creates the repository and nothing else does: a push joins the repository its
URL names and never mints a second one. It adds a `local` remote (`gib://<origin>`, the
local store only) and prints the peer remote to add.

Other commands: `gib sync <gib url> [branch...]` refreshes a repository from its peer —
naming a branch teaches this client one it could not otherwise discover, which is how
you pick up a branch someone else created — `gib doctor` checks the wallet and store,
and `gib put <file>` stores a signed transaction.

Publishing needs a BRC-100 wallet on `http://127.0.0.1:3321` (`1sat serve wallet-api`,
or set `GIB_WALLET_URL`) and its monitor running (`1sat serve monitor`) so delayed
broadcasts go out. Reading needs no wallet at all.

## Environment

| Variable | Meaning |
| --- | --- |
| `GIB_HOME` | store, per-repository state, identity cache (default `~/.gib`) |
| `GIB_WALLET_URL` | BRC-100 wallet endpoint (default `http://127.0.0.1:3321`) |

## Layout

- `src/remote/` — `gib://` URLs, BRC-180 discovery, the peer client (both lookups and
submit), syncing, ref naming, and the remote-helper protocol.
- `src/cascade.ts` — planning one commit's tree into nodes; `src/chain.ts` — packing
those nodes into transactions.
- `src/push.ts` — building the root and minting the head; `src/fetch.ts` — turning a
published root back into git objects.
- `src/tree.ts` — reading a published tree, and `stripGitDir`.
- `src/ordfs/` — the `ordfs/dir` and `ordfs/patch` codecs and vcdiff.
- `src/token.ts`, `src/seal.ts`, `src/head.ts` — the head token: fields, sealing, reading.
- `test/fakes/` — a fake BRC-100 wallet, a fake gib peer, and git helpers. Tests never
touch a network, a real wallet or a chain.

## Reading

**Start here:**
- `docs/plans/ROADMAP.md` — workstreams, build order, settled decisions.
- `docs/plans/ordfs-formats.html` — the `ordfs/dir` / `ordfs/patch` byte specs (the
contract between 1sat-stack, 1sat-sdk, and gib).
contract between 1sat-stack, 1sat-sdk and gib).
- `docs/plans/gib-token.html`, `gib-cli.html`, `gib-format.html`, `gib-rationale.html` —
design. `gib-status.html` — what was proven on mainnet (full txids inside).
- `docs/plans/questions.md` — open items vs answered decisions.

**This branch is docs-only.** The working tree is greenfield: no implementation yet.
The first prototype (clone/commit/push proven end-to-end on mainnet, wallet API,
push-drop token chain) lives on branch **`archive/prototype`**. It uses a superseded
model (full-tree republish, `.gib` project state, ORDFS content reads, pre-final token
fields) — read it for the wallet/BRC-100 mechanics that work, not for architecture.

Related repos: `b-open-io/1sat-sdk` (dir/patch encoding + push-drop lifecycle
abstraction land there directly; opldotdev is the same repo under rename),
`b-open-io/1sat-stack` (gateway serving the new content types).
design. `gib-status.html` — what was proven on mainnet.
- `docs/plans/ROADMAP.md`, `docs/plans/questions.md` — sequencing and open items.
- BRC-180 (overlay service discovery at an internet domain) for the manifest.

Related repos: `b-open-io/1sat-sdk` (dir/patch encoding and push-drop lifecycle),
`b-open-io/1sat-stack` (the gib overlay: `tm_gib`, `ls_gib`, and the gateway serving
the content types).
Loading