Skip to content

feat(trader): add 'sphere trader withdraw' subcommand#9

Open
vrogojin wants to merge 3 commits intomainfrom
feat/sphere-trader-withdraw
Open

feat(trader): add 'sphere trader withdraw' subcommand#9
vrogojin wants to merge 3 commits intomainfrom
feat/sphere-trader-withdraw

Conversation

@vrogojin
Copy link
Copy Markdown
Contributor

@vrogojin vrogojin commented May 4, 2026

Summary

Adds the missing withdraw subcommand to `sphere trader`. Trader-service's ACP `WITHDRAW_TOKEN` handler has existed for some time, but no operator-facing CLI exposed it — operators could deposit and trade but couldn't pull funds back out.

This PR closes that gap on the canonical CLI side. A parallel commit in vrogojin/trader-service#17 adds the same subcommand to the bundled `trader-ctl` so direct-docker e2e tests also gain the full deposit → trade → withdraw flow.

API

```
sphere trader withdraw \
--tenant @trader-alice \
--asset UCT \
--amount 1000000000 \
--to-address @bob
```

Three required flags mirror the trader-side validation in `trader-service/src/trader/trader-command-handler.ts:633`:

  • `--asset` — symbol (UCT, USDU) or hex coin id; non-empty.
  • `--amount` — positive bigint string in smallest units (the trader's `safeParseBigint` rejects anything else).
  • `--to-address` — `@nametag`, `DIRECT://hex`, or 64-char hex pubkey; non-empty.

CLI-layer validation (in `buildWithdrawParams`) catches trivial cases before the round-trip:

  • Empty / whitespace-only fields
  • Strict positive-integer amount: rejects `0`, negative, decimals, `1e6` scientific notation, leading zeros (`01000`), non-numeric

Tests

```
$ npm run check
Tests 114 passed (114) # was 106, +8 new wire-shape tests
typecheck + lint clean
```

8 new tests pin the wire-shape contract:

  1. Snake_case wire fields (`asset`, `amount`, `to_address` — not camelCase)
  2. Bigint-string amount preserved verbatim (huge values like `99999999999999999999999999`)
  3. Empty asset rejected
  4. Whitespace-only asset rejected
  5. Empty amount rejected
  6. Zero / negative / decimal / `1e6` / leading-zero / non-numeric rejected
  7. Empty to-address rejected
  8. All three address forms accepted

Plus the subcommand-tree test now asserts 7 controller-scoped subcommands (was 6).

Verification

```
$ node bin/sphere.mjs trader --help | grep withdraw
withdraw [options] Withdraw a token from the trader to an external address (ACP WITHDRAW_TOKEN)

$ node bin/sphere.mjs trader withdraw --help
Usage: sphere trader withdraw [options]
Withdraw a token from the trader to an external address (ACP WITHDRAW_TOKEN)
Options:
--asset Asset symbol (e.g. UCT, USDU) or hex coin id
--amount Amount to withdraw in smallest units (string-encoded bigint, must be > 0)
--to-address

Destination address: @NameTag, DIRECT://hex, or 64-char hex pubkey
-h, --help display help for command
```

Test plan

  • CI green
  • Reviewer pulls the branch, runs `npm run check`
  • Reviewer runs the trader-service e2e flow including withdraw to confirm round-trip works (requires trader-service PR #17 active)

Vladimir Rogojin added 3 commits May 4, 2026 14:16
Closes the gap noted in trader-service PR-17 recovery review:
trader-service's ACP WITHDRAW_TOKEN handler exists, but no
operator-facing CLI exposed it. Operators couldn't pull funds back
out of their trader.

Adds:
- 'sphere trader withdraw --asset <symbol> --amount <bigint> --to-address <addr>'
  invokes WITHDRAW_TOKEN over ACP. Three required flags mirror the
  trader-side validation (asset non-empty, amount > 0 positive
  integer string, to_address valid).
- buildWithdrawParams pure function (exported for unit testing).
- 8 unit tests pinning the wire-shape contract: snake_case wire
  fields, bigint-string amount preservation (no Number coercion /
  precision loss), strict positive-integer amount validation
  (rejects 0, negative, decimal, '1e6', leading-zero, non-numeric),
  all three address forms accepted (@NameTag, DIRECT://hex, raw hex).
- Subcommand-tree test now asserts 7 controller-scoped subcommands
  (was 6 — withdraw is the 7th).

Verified: 114/114 tests pass (was 106, +8 new wire-shape tests).
typecheck + lint clean.

The same withdraw subcommand is added to trader-service's bundled
trader-ctl in vrogojin/trader-service PR #17, so direct-docker
e2e tests can also exercise the full deposit→trade→withdraw flow.
Steelman finding (companion to trader-service fix): a leading or
trailing space in --asset or --to-address would pass the
empty-after-trim CLI guard but reach the trader's address regex
verbatim, producing a confusing INVALID_PARAM remote error rather
than a local CLI message. trim() at the wire boundary normalizes
valid inputs without changing the validation contract (empty-after-
trim was and still is rejected at the CLI layer above).

The trader-side regex fix (accepts @NameTag, DIRECT://hex, and raw
hex pubkey rather than only alphanumeric) lands in trader-service's
recover/merge-real-settlement branch. Together the two fixes make
all three address forms advertised in --help actually work end-to-end.

Verified: 114/114 tests pass.
…s help

sphere-sdk's parseAddress does NOT accept bare hex without a
DIRECT://, PROXY://, or @ prefix. The CLI advertised three forms
but only two would actually work. trader-service's strict address
validator (now layered on top of parseAddress + isValidNametag,
plus a hex regex for DIRECT/PROXY) makes the contract explicit.

Verified: 114/114 tests pass.
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.

1 participant