Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 3 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: stable
# Lint with the module's pinned toolchain: the pinned golangci-lint
# cannot typecheck a newer stable stdlib (go1.27 broke it).
go-version-file: go.mod
cache: true
- uses: golangci/golangci-lint-action@v7
with:
Expand Down
58 changes: 58 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ bb media <txid>:<vout> --stdout | jq . # stream to stdout
bb key usage # tier, rate limit, today's usage
bb key upgrade --dry-run # fetch the 402 challenge, don't pay
bb key upgrade --tier pro # buy a tier upgrade on-chain
bb key upgrade # resume a saved pending payment (never pays twice)
```

`bb key upgrade` implements the x402 (BRC-120-style, scheme `bsv-tx-v1`)
Expand Down Expand Up @@ -135,6 +136,63 @@ then `--wif`, then `BB_WIF`.
You'll be shown the price, payee, and funding address and asked to confirm
before anything is signed (skip with `--yes`).

#### Pending payments and resuming

The server can answer the proof with **202 Accepted** and a `Retry-After`
header: it broadcast your payment, but the network has not accepted it yet.
A 202 grants nothing, and the challenge stays open. `bb` waits `Retry-After`
seconds (10 if the header is missing or unreadable, clamped to 1–60) and
resubmits the **same** proof until the server answers 200, printing one line
per pending answer to stderr:

```
payment 3f2a… not settled yet (payment broadcast but not yet accepted by the network; resubmit the same proof); resubmitting the same proof in 10s
```

`bb` treats a 429, a 5xx (a node draining or restarting behind the load
balancer, say) and a submit that fails in transit (the connection drops or
`--timeout` fires) the same way: it waits (`Retry-After` when sent, else 10
seconds) and resubmits the same proof.

`--wait` (default `10m`) caps how long `bb` keeps doing that. It is separate
from `--timeout`, which bounds each request. Ctrl-C stops the wait at once.

Before the first submit, `bb` saves the proof to
`<user config dir>/bb/pending-upgrades.json` (mode 0600; `~/.config/bb/` on
Linux, `~/Library/Application Support/bb/` on macOS), keyed by a fingerprint of
the host and API key (the key itself is not written; the host's case, a
default port, a trailing slash and whitespace around the key do not change it)
and the challenge id.
**If the wait runs out, the run is interrupted, or the connection drops, your
payment is saved: rerun `bb key upgrade` and it resubmits the saved proof
instead of building a new payment. Do not pay again.** Resuming works even
after the challenge's `expires_at`, when the server has already moved on to a
new challenge id; a second payment for the same challenge is not credited.
`bb` also resumes, rather than pays, whenever the server hands out a challenge
id that already has a saved proof, even one saved under another spelling of
the host or key.
While a saved proof is unsettled, the rate-limit offer below does not pay
either; it points you at `bb key upgrade`.

`bb` removes the saved entry when the upgrade settles, and when the server
says the proof can never settle (422 rejected by broadcast, 409 payment txid
already used, 410 expired with no payment the network holds, 404 `challenge
not found`, 400/402). Any other 404, such as a bare `404 page not found` from
a server where the upgrade route is not enabled, keeps the entry. If the server
says **challenge already consumed**, an earlier submit most likely settled and
its response was lost: `bb` reads the key's tier from `/api/v1/key/usage` and
reports success when the key is at or above the purchased tier and the proof
was last submitted less than an hour earlier. Below that tier within the hour,
it keeps the entry and asks you to rerun, since a new tier can take a minute to
show on every server.
A submit answered **challenge already consumed** settled nothing, so it does
not count as a submit and rerunning does not restart that hour.
An entry last submitted longer ago can never settle again, so `bb` removes it
and exits non-zero whatever the tier: below it (say the tier has since lapsed)
the payment is gone, and at or above it the payment settled long ago, so it is
not reported as this run's upgrade. Either way this run buys nothing, and the
next `bb key upgrade` buys a fresh upgrade or renewal.

`bb` also advertises `X-Payment-Accept: x402` on every API request, so when
a keyed command gets **rate-limited** the server answers with a payable 402
challenge instead of a bare 429. `bb` prints the upgrade terms and either
Expand Down
Loading
Loading