Skip to content

feat(payouts): explicit minimum deposit on destinations, reserves decided at payout time, destinations delete and reserves (release v1.8.0) - #191

Merged
pviti merged 9 commits into
mainfrom
feature/payout-minimum-required
Nov 4, 2026
Merged

pviti merged 9 commits into
mainfrom
feature/payout-minimum-required

Conversation

@pviti

@pviti pviti commented Oct 30, 2026 •

Copy link
Copy Markdown
Member

The gateway (main 161d3ff) now requires a payout destination's minimum deposit as an explicit answer: either a figure (min_amount) or the flag no_minimum: true, exactly one. Neither, or a blank figure, gets 422 minimum_required; both get 422 minimum_conflict. This PR adds that to the CLI on top of rail0-go#79 (CreatePayoutDestinationRequest.MinAmount *Uint256String + NoMinimum bool, the same on update, and PayoutDestination.NoMinimum).

Dependency: needs rail0-go released with #79. Until then go.mod pins a pseudo-version of an earlier commit on that branch (4c24316), and the release guard refuses it. Bump to the tag before merging.

rail0 payouts destinations add

  • --min-amount <decimal> takes the venue's figure, converted with the token's decimals as before. 0 is an ordinary figure, and a negative value is refused up front.
  • --no-minimum means the venue has none. It sends no_minimum: true and never a 0.
  • Neither flag:
    • on a terminal, the CLI prompts for the minimum, naming the token and network. An empty answer is not accepted; typing none states the venue has none.
    • with --agent or no terminal, it is a usage error minimum_required, raised before any request, telling you to look up the venue's deposit page.
  • Both flags: usage error minimum_conflict, before any request.
  • The summary shown before the details confirmation states the figure, or none (no minimum at this venue).

rail0 payouts destinations update <id>

  • --no-minimum sends no_minimum: true, which removes a figure.
  • --min-amount <decimal> sets a figure and clears the flag.
  • --min-amount "" is refused locally (minimum_required). Passing both flags is minimum_conflict.
  • A change of the [figure, no_minimum] pair asks for the details consent, the same y/N prompt or --confirm-details as before. Re-sending the current answer does not, which matches the gateway's comparison.

Output

list, get, add, update, enable, disable and confirm print a minimum companion field. It is the figure (from min_amount_formatted), none when no_minimum is set, or not set for a destination registered before the minimum was asked. Raw fields are unchanged, and -q still prints only the id.

Hints

  • New hints for minimum_required and minimum_conflict, naming --min-amount / --no-minimum.
  • payout_destination_minimum_unset no longer exists in the gateway (destinations with no minimum stated are not blocked), so it has no hint and send / submit say nothing about it.

Behaviour change

A script that calls destinations add without a minimum now gets a usage error. The updated gateway refuses such a request anyway (minimum_required). This PR does not mark the change as breaking (no !, no footer): decide that when preparing the release.

Tests

In cmd/payouts_test.go, against the fake gateway:

  • add with neither flag, with both, or with a negative minimum is refused with the right code, and no request at all is sent.
  • --no-minimum sends no_minimum: true and no min_amount. --min-amount 0 sends "0" without the flag. The summary is checked for both.
  • update --min-amount "" is refused with no request. update --no-minimum needs consent, then sends no_minimum: true, no figure and confirm_details; re-sending it on a destination that is already none sends no confirm_details. Both flags give minimum_conflict with no request.
  • The minimum field is checked for a figure, 0, none and not set, plus through get.
  • The hints are checked.

Gate run locally: gofmt, go build ./..., go vet ./..., go test ./..., gendocs drift and the SOURCE_DATE_EPOCH clock check, sh -n install.sh, bin/release-prepare-test. README Payouts section, help text, docs/cli and docs/man are updated.

Reserves decided at payout time, destinations delete and reserves (rail0-gateway e615989)

rail0-go bumped to the rail0-go#79 head 5f91344 (v1.9.1-0.20261102160918-5f913446474d, no replace directive).

  • payouts destinations add / update: --reserve removed — the gateway no longer accepts reserve_amount on a destination. Breaking for scripts that passed it. Update's "nothing to update" message and the help text follow.
  • payouts send / prepare: --no-refund-reserve and --no-gas-reserve send keep_refund_reserve / keep_gas_reserve: false for that payout only (a usage error with --proof). Unset, nothing is sent and the gateway keeps both (its default). The help says both are kept by default and that skipping one may make a later refund or capture fail.
  • send's confirmation shows the reserves kept back (or SKIPPED, with the risk) from GET /payout_destinations/{id}/reserves (best effort: a failed read leaves the prompt without figures), and for --amount all what it comes to now. submit's confirmation shows the refund_reserve / gas_reserve the prepared payout recorded; prepare prints them with the payout.
  • New payouts destinations delete <id>: soft delete behind the same y/N / --yes gate as confirm; prints the destination with deleted_at.
  • New payouts destinations reserves <id>: balance, refund reserve with its refundable-payment count, gas reserve and gas allowance (native-gas chains only), available. --json (or a pipe / --agent) prints the gateway's record unchanged.
  • payouts destinations [list] --include-deleted.
  • insufficient_balance hint: "after the reserves kept back", pointing at destinations reserves and the opt-out flags.
  • docs/cli and docs/man regenerated with tools/gendocs; README Payouts section updated (new "Reserves (decided at payout time)" section, delete/reserves/--include-deleted).
  • Tests: add sends no reserve_amount and --reserve is gone from add/update; reserve flags → request (unset / false / refused with proof); send with --no-refund-reserve; the send and submit prompts with the reserves (native gas, non-native, proof); delete gate and DELETE; reserves route, JSON output and terminal view; --include-deleted query; command tree.

Checks run locally: gofmt, go build ./..., go vet ./..., go test ./..., docs freshness (go run ./tools/gendocs + clean diff, also with SOURCE_DATE_EPOCH), sh -n install.sh, VERSION format, bin/release-prepare-test — all pass. golangci-lint is not configured in this repo.

No destination kind (rail0-gateway be29f83)

The kind changed nothing at the gateway and the label already names the venue, so it is gone.

Gate: go build, go vet, go test ./..., bin/docs.

One destination per payment method

Gateway 50e309b: a wallet has at most one live destination per token and chain. A second address for the same payment method is now 409 payout_destination_exists (before, only the same address was). The error hint and the delete docs say so: delete the destination to register another address.

Destination label required and unique

Gateway 2a08a95: POST /payout_destinations requires label (trimmed; blank is 422 label_required), unique among the account's live destinations ignoring case (409 payout_destination_label_taken); deleting a destination frees its label. PATCH may not blank it.

  • payouts destinations add: --label is required. Missing or blank (spaces only) is a usage error label_required without a terminal or with --agent, before any request; on a terminal it is prompted for (blank answers refused). The label is trimmed, sent, and shown in the confirmation summary.
  • payouts destinations update --label: blank is refused locally (label_required); the label is sent trimmed.
  • Error hints for label_required and payout_destination_label_taken.
  • Help/Long/Examples, README (new "Label" section) and the generated docs: required, unique per account (ignoring case), freed by delete.
  • Tests: missing/blank label refused with no request, trimmed label sent and shown in the summary, update blank refused / trimmed, hints. Existing add tests now pass a label.

gorilla/websocket 1.4.2 → 1.5.3 (folded in from #190)

The Dependabot bump from #190, cherry-picked here so the release ships from one pull request. It is an indirect dependency (go.mod and go.sum only), and 1.5.3 is the first patched version for the open Dependabot alert (weak PRNG for the WebSocket mask key). Gate after the cherry-pick: gofmt, go build ./..., go vet ./..., go test ./..., bin/release-prepare-test.

🤖 Generated with Claude Code

rail0 CLI v1.8.0

Added

Full diff: v1.7.1...v1.8.0


Version: minor, computed from the pull requests merged since v1.7.1 (a breaking change → major, feat → minor, anything else → patch). Decided by:

Merging this pull request also releases v1.8.0: its changes and the version bump
ship together, under the one approval. On the push to main,
.github/workflows/release.yml re-runs CI on the merge commit, creates the tag, and GoReleaser builds the binaries and publishes the GitHub release, and
posts the release to Slack.

The release notes are the text between the release-notes markers above, read from this
pull request when it merges: edit them here, before merging.

Opened by bin/release-prepare.

pviti and others added 4 commits October 30, 2026 19:41
Pseudo-version until rail0-go is released with #79; the release guard refuses it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…swer

The gateway (main 161d3ff) requires a destination's minimum deposit as an explicit
answer: a figure (min_amount) or the flag no_minimum: true, exactly one —
422 minimum_required for neither or a blank figure, minimum_conflict for both.

- destinations add: --min-amount <decimal> (0 is an ordinary figure, never
  negative) or --no-minimum (no_minimum: true, never a 0). Neither: a prompt on a
  terminal (an empty answer is not accepted; "none" states the venue has none), a
  usage error (minimum_required) without one or with --agent, before any request.
  Both: usage error (minimum_conflict). The summary before the details
  confirmation states the figure or "none (no minimum at this venue)".
- destinations update: --no-minimum sends no_minimum: true; --min-amount "" is
  refused locally; both are a conflict. A change of [figure, no_minimum] needs the
  details consent; re-sending the current answer does not.
- list / get / add / update / enable / disable / confirm print a "minimum"
  companion field: the figure, "none" (no_minimum) or "not set" (a destination
  registered before the minimum was asked). Raw fields unchanged.
- Hints for minimum_required and minimum_conflict.
- Help, README, docs/cli + docs/man; tests against the fake gateway.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ger marked breaking)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nd reserves

Follows rail0-gateway e615989 through rail0-go 5f91344 (rail0-go#79,
pseudo-version v1.9.1-0.20261102160918-5f913446474d).

- payouts destinations add / update: --reserve removed (the gateway no
  longer stores a reserve on the destination); update's 'nothing to
  update' message and help follow.
- payouts send / prepare: --no-refund-reserve and --no-gas-reserve send
  keep_refund_reserve / keep_gas_reserve false for that payout only
  (refused with --proof). Both reserves are kept by default; the help
  says skipping one may make a later refund or capture fail.
- send's confirmation shows the reserves kept back (or skipped) from
  GET .../reserves, best effort, and what 'all' comes to now; submit's
  shows the refund_reserve / gas_reserve the payout recorded.
- payouts destinations delete <id>: soft delete behind the y/N / --yes
  gate. payouts destinations reserves <id>: balance, refund reserve with
  its payment count, gas reserve and allowance on native-gas chains,
  available; the gateway record with --json.
- payouts destinations list --include-deleted.
- insufficient_balance hint: after the reserves kept back.
- docs/cli and docs/man regenerated, README and tests updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@pviti pviti changed the title feat(payouts): require an explicit minimum deposit on payout destinations (--min-amount or --no-minimum) feat(payouts): explicit minimum deposit on destinations, reserves decided at payout time, destinations delete and reserves Nov 2, 2026
pviti and others added 4 commits November 3, 2026 08:37
Follows rail0-gateway be29f83 and rail0-go 4c24316: the destination kind changed
nothing and is gone. rail0-go pinned to the PR branch's pseudo-version.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Gateway 50e309b allows one live destination per wallet, chain and token: a second
address for the same payment method is now 409 payout_destination_exists (before,
only the same address was). The error hint and the delete docs say so: delete the
destination to register another address.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Gateway 2a08a95 requires a label on POST /payout_destinations (trimmed; blank is
422 label_required) and keeps it unique among the account's live destinations,
ignoring case (409 payout_destination_label_taken); PATCH may not blank it.

- destinations add: --label is required. A missing or blank label is a usage error
  (label_required) without a terminal or with --agent, before any request; on a
  terminal it is prompted for. The label is trimmed, sent, and shown in the
  confirmation summary.
- destinations update: a blank --label is refused locally (label_required); a
  label is sent trimmed.
- Hints for label_required and payout_destination_label_taken.
- Help, README and generated docs: the label is required and unique per account
  (ignoring case); delete frees it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Bumps [github.com/gorilla/websocket](https://github.com/gorilla/websocket) from 1.4.2 to 1.5.3.
- [Release notes](https://github.com/gorilla/websocket/releases)
- [Commits](gorilla/websocket@v1.4.2...v1.5.3)

---
updated-dependencies:
- dependency-name: github.com/gorilla/websocket
  dependency-version: 1.5.3
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Merging pull request #191 publishes v1.8.0 through .github/workflows/release.yml.
@pviti pviti changed the title feat(payouts): explicit minimum deposit on destinations, reserves decided at payout time, destinations delete and reserves feat(payouts): explicit minimum deposit on destinations, reserves decided at payout time, destinations delete and reserves (release v1.8.0) Nov 4, 2026
@pviti pviti self-assigned this Nov 4, 2026
@pviti
pviti merged commit e72ceb8 into main Nov 4, 2026
1 of 2 checks passed
pviti added a commit that referenced this pull request Nov 4, 2026
…e ships (release v1.8.1) (#192)

rail0-cli#191 merged as v1.8.0, but its release commit moved `go.mod` to
rail0-go v1.10.0 without the matching `go.sum` entries. CI on the
release commit stopped on `missing go.sum entry for module providing
package github.com/commercelayer/rail0-go`, so nothing was tagged or
published: v1.8.0 does not exist.

A resume ("Run workflow") cannot fix it: the release workflow always
builds the commit that SET the version (e72ceb8), which keeps the broken
`go.sum`. So this PR carries the fix and a new version: **v1.8.1**,
which ships everything from #191 (payout destinations with an explicit
minimum, reserves decided at payout time, destinations delete and
reserves, one destination per payment method, label required and unique,
gorilla/websocket 1.5.3).

The change: `go mod tidy` adds the rail0-go v1.10.0 checksums and drops
the two stale pseudo-version lines. Gate run locally: gofmt, `go build
./...`, `go vet ./...`, `go test ./...`.

Root cause, to fix separately: `bin/release-prepare` rewrites the
rail0-go version in `go.mod` but does not refresh `go.sum`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- release:start -->
## rail0 CLI v1.8.1

<!-- release-notes:start -->
### Added

- **payouts:** explicit minimum deposit on destinations, reserves
decided at payout time, destinations delete and reserves (#191)

### Fixed

- **deps:** go.sum entries for rail0-go v1.10.0, so the payouts release
ships (#192)

**Full diff:**
v1.7.1...v1.8.1
<!-- release-notes:end -->

---

**Version:** **1.8.1**, set by hand (`bin/release-prepare 1.8.1`); the
computed bump would be **minor**.


Merging this pull request also releases **v1.8.1**: its changes and the
version bump
ship together, under the one approval. On the push to `main`,
`.github/workflows/release.yml` re-runs CI on the merge commit, creates
the tag, and GoReleaser builds the binaries and publishes the GitHub
release, and
posts the release to Slack.

The release notes are the text between the `release-notes` markers
above, read from this
pull request when it merges: edit them here, before merging.

_Opened by `bin/release-prepare`._
<!-- release:end -->
pviti added a commit that referenced this pull request Nov 4, 2026
… (release v1.8.2) (#193)

Root cause of the failed v1.8.0 release (#191): when
`bin/release-prepare` offers to move `go.mod` to the latest rail0-go tag
and you accept, it rewrote the version in `go.mod` as text and committed
only `VERSION` and `go.mod`. CI on the release commit then stopped on
`missing go.sum entry for module providing package
github.com/commercelayer/rail0-go`, and the release workflow published
nothing. #192 repairs `go.sum` and releases v1.8.1; this PR stops it
from happening again.

**Change**
- After the `go.mod` bump: `go mod download <rail0-go>@vX.Y.Z`, `go mod
tidy`, `go build ./...`. Each step aborts the run with a clear message
if it fails, before anything is committed or pushed.
- `go.sum` joins `VERSION_FILES`, so it is committed with the bump. It
is also accepted by the "files outside the version bump" guard.
- `bin/release-prepare-test`: `FILES` gains `go.sum`, so the fixture has
it. Only `FILES` differs between the four repos' copies, so the shared
script stays identical.

**Checks:** `bash -n bin/release-prepare`; `bin/release-prepare-test`
passes. The interactive bump path needs a terminal, so the test suite
does not run it. I reviewed it by reading.

No release: this is tooling only. It can merge before or after #192.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- release:start -->
## rail0 CLI v1.8.2

<!-- release-notes:start -->
### Fixed

- **release:** release-prepare refreshes go.sum when it moves rail0-go
(#193)

**Full diff:**
v1.8.1...v1.8.2
<!-- release-notes:end -->

---

**Version:** **patch**, computed from the pull requests merged since
v1.8.1 (a breaking change → major, `feat` → minor, anything else →
patch). Decided by:

- fix(release): release-prepare refreshes go.sum when it moves rail0-go
(#193)


Merging this pull request also releases **v1.8.2**: its changes and the
version bump
ship together, under the one approval. On the push to `main`,
`.github/workflows/release.yml` re-runs CI on the merge commit, creates
the tag, and GoReleaser builds the binaries and publishes the GitHub
release, and
posts the release to Slack.

The release notes are the text between the `release-notes` markers
above, read from this
pull request when it merges: edit them here, before merging.

_Opened by `bin/release-prepare`._
<!-- release:end -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants