Skip to content

feat(cli): 'pay' and 'serve' commands for x402 (#44) - #62

Merged
Eras256 merged 6 commits into
nirium-protocol:mainfrom
M0nsxx:feat/cli-pay-serve
Aug 26, 2026
Merged

Eras256 merged 6 commits into
nirium-protocol:mainfrom
M0nsxx:feat/cli-pay-serve

Conversation

@M0nsxx

@M0nsxx M0nsxx commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

CLI: 'pay' and 'serve' commands for x402 (#44)

Overview

This PR extends packages/cli (nirium) with two core x402 developer commands:

  1. nirium pay <url>: Terminal payment client for x402 endpoints, automatically signing Stellar authorization entries and completing pay-to-retry cycles.
  2. nirium serve: Zero-code local x402-protected HTTP server wrapping the SDK's x402Serve() middleware.
  3. nirium config: Secure local configuration store (~/.niriumrc.json) with automatic secret key masking (S***...XXXX).

Deliverables & Features

  1. nirium pay <url> [--amount] [--network] [--secret] [--config] [--json] (packages/cli/src/pay.ts):

    • Parses payment challenges (402 Payment Required), signs auth entries using Stellar Ed25519 keypairs, and sends PAYMENT-SIGNATURE headers.
    • Extracts on-chain transaction references and links directly to Stellar Expert transaction pages.
    • Masks secret keys in all console outputs and logs. Supports structured --json output.
  2. nirium serve [--price] [--pay-to] [--port] [--route] [--network] (packages/cli/src/serve.ts):

    • Wraps the SDK's x402Serve() middleware over Express to spin up a mock x402-protected endpoint for testing nirium pay.
  3. nirium config [set|get|list|delete] (packages/cli/src/configStore.ts & packages/cli/src/config.ts):

    • Manages configuration values (secretKey, payTo, network) with automatic secret masking.
  4. Tests & Verification (packages/cli/test/pay-serve.test.js):

    • Automated tests for argument validation, secret key validation, config masking, and error reporting.

Verified Live Testnet Payment & Tx Reference

Executed nirium pay https://nirium-agent.fly.dev/api/v1/premium/signals against Nirium's live testnet endpoint using a real testnet account (GAKBHMZEWVVW6ST7D6GNR3LTDNYTMQT3K64X2D7BN35QWJ2T7IA5AXCI).

M0nsxx and others added 3 commits August 23, 2026 18:17
… 0600

serve.ts: the facilitatorApiKey resolution fell back to the literal
string 'demo-key' when no real key was configured.  The server would
start and look fully ready, but every request would 503 because the
facilitator rejects the fake credential.  Now it fails fast at startup
with actionable instructions, same pattern as doctor.ts.

configStore.ts: ~/.niriumrc.json can hold secretKey (a real Stellar
SECRET key) and facilitatorApiKey.  writeFileSync with default mode
leaves the file world-readable (0644 after umask) on Linux/Mac.  Now
writes with mode 0o600 and chmodSync afterward to also fix pre-existing
files.  Best-effort on Windows where POSIX modes don't apply.

Wires up the test script in package.json (was a placeholder echo).
Adds a file-permission test (skipped on Windows).

Co-authored-by: Claude AI (Anthropic) <noreply@anthropic.com>
@M0nsxx

M0nsxx commented Aug 25, 2026

Copy link
Copy Markdown
Contributor Author

Fix: silent \demo-key\ fallback + world-readable config file

Two issues fixed:

1. serve.ts — silent fake facilitator key
\ acilitatorApiKey\ resolved to the literal string 'demo-key'\ when no real key was configured. The server would start, print the \Listening\ banner, and look fully operational — but every request would 503 because the facilitator rejects the fake credential. Removed the fallback entirely; now fails fast at startup with actionable instructions (same pattern as \doctor.ts).

2. configStore.ts — secretKey written world-readable
\writeFileSync\ with no \mode\ option creates files 0644 (after umask) on Linux/Mac. ~/.niriumrc.json\ can hold \secretKey\ — a real Stellar SECRET key. Now writes with \mode: 0o600\ and \chmodSync(0o600)\ afterward to also fix pre-existing files. Best-effort on Windows where POSIX modes don't apply.

Also:

  • Wired up the test script in \package.json\ (was a placeholder \echo).
  • Added file-permission test asserting 0600 bits after \saveConfig()\ (skipped on Windows).
  • All 4 tests pass (3 run + 1 skipped on Windows).

pay.ts imports Keypair from @stellar/stellar-sdk but the package was
never listed in dependencies — a clean npm install would fail to
resolve it.  Pins ^14.5.0 to match packages/sdk.

Verified: rm -rf node_modules && npm install && npm test passes.

Co-authored-by: Claude AI (Anthropic) <noreply@anthropic.com>
Eras256 added a commit to M0nsxx/nirium-sdk that referenced this pull request Aug 26, 2026
- Kept both CLI commands (verify + doctor, from nirium-protocol#59 already merged) in
  index.ts, bin/nirium.js, and README — this branch and nirium-protocol#59 each added
  one, neither replaces the other.
- Combined test scripts to run both test/verify.test.js and
  test/doctor.test.js under one `npm test`.
- Aligned @stellar/stellar-sdk to ^14.5.0 (matching packages/sdk's own
  pin) instead of this branch's ^17.0.0, to avoid two different major
  versions of the same dependency living side by side once nirium-protocol#62 also
  merges its own ^14.5.0 addition.
- Kept all six CLI commands side by side (create/pay/serve/config from
  this branch, verify/doctor from nirium-protocol#59+nirium-protocol#60 already merged) in both
  src/index.ts and bin/nirium.js — removed a stale duplicate doctor and
  a broken verify (calling a nonexistent runAuditVerifier) that this
  branch's own index.ts still had from before verify.ts existed.
- Added pay/serve/config to bin/nirium.js too — the real published
  entrypoint (package.json's "bin") only had create/doctor/verify; this
  branch's new commands were only reachable from the unpublished
  src/index.ts.
- Added express + @types/express as real dependencies — serve.ts
  imports express directly but it was never declared.
- Combined test scripts to run all three suites (verify/doctor/
  pay-serve) under one npm test. Aligned @stellar/stellar-sdk to
  ^14.5.0 across the merge.
- Rebuilt package-lock.json for both packages/cli and packages/sdk.

Known issue NOT fixed here, left for a follow-up (see PR comment):
`npm run build` still fails for pay.ts/serve.ts/config.ts — they import
sibling files with an explicit .ts extension, which the test runner's
native TS stripping needs but tsc's NodeNext resolution rejects. Fixing
it by switching to .js extensions breaks the test runner instead (no
dist/ exists when running from source). Needs an actual resolution
strategy, not a one-line swap.
@Eras256

Eras256 commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

Pushed a merge-conflict resolution against current main. Along the way I found and fixed two real gaps this PR had beyond the conflict itself:

  1. express was never declared as a dependency despite serve.ts importing it directly — added, plus @types/express.
  2. bin/nirium.js (the actual published CLI entrypoint per package.json's "bin" field) never got the pay/serve/config commands — they only existed in the unpublished src/index.ts. Added them there too, matching the ../dist/*.js import pattern the existing doctor/verify commands there already use.

One real issue I found but did NOT fix, left for you: npm run build (tsc) currently fails —

src/config.ts(1,66): error TS5097: An import path can only end with a '.ts'
extension when 'allowImportingTsExtensions' is enabled.

pay.ts, serve.ts, and config.ts all import ./configStore.ts with an explicit .ts extension. That's required for npm test to work (it runs the raw .ts sources via node --experimental-strip-types, which needs the literal file to exist at that path) — but it's rejected by tsc's NodeNext module resolution, which is what bin/nirium.js actually depends on (it imports ../dist/pay.js etc., which never gets emitted right now because the build fails before it gets there). I tried the obvious fix (swap to .js) and it just breaks the test runner instead, so this isn't a one-line change — it needs an actual resolution strategy for the package (e.g. building to a temp location before running tests against dist, or restructuring the sibling-file imports) rather than picking one extension and breaking the other consumer.

Also noting, not blocking: test/pay-serve.test.js only imports configStore.ts and pay.ts — serve.ts has zero test coverage despite being half of what this PR is named for.

Once npm run build actually succeeds, this is ready.

- Fixed TS5097 build error by changing ./configStore.ts imports in src/config.ts, src/pay.ts, and src/serve.ts to .js extension (required under NodeNext module resolution for tsc build).
- Updated test runner to execute 	sc && node --test ... against compiled dist/*.js artifacts instead of raw .ts source files.
- Added startup validation unit tests for executeServeCommand in 	est/pay-serve.test.js covering missing acilitatorApiKey and missing payTo address.

Co-authored-by: Gemini AI (Google DeepMind) <noreply@google.com>
@M0nsxx

M0nsxx commented Aug 26, 2026

Copy link
Copy Markdown
Contributor Author

Fix: TS5097 NodeNext import extensions, dist testing strategy & \executeServeCommand\ unit tests

  1. Resolved TS5097 build error:

    • Replaced ./configStore.ts\ import paths in \src/config.ts, \src/pay.ts, and \src/serve.ts\ with .js\ extensions required by TypeScript under \NodeNext\ module resolution.
      pm run build\ (\ sc) now succeeds with zero errors.
  2. Unified test resolution strategy:

    • Restructured the test runner to run \ sc && node --test test/verify.test.js test/doctor.test.js test/pay-serve.test.js\ testing directly against compiled \dist/*.js\ modules. This ensures both
      pm run build\ and
      pm test\ pass harmoniously in the same environment without path extension collisions.
  3. Added \executeServeCommand\ unit tests:

    • Added automated test cases in \ est/pay-serve.test.js\ covering startup failure assertions for missing \ acilitatorApiKey\ and missing \payTo\ recipient address.
  4. Verification:

    • Both
      pm run build\ and
      pm test\ execute cleanly (15 passed, 1 skipped on Windows).

@Eras256

Eras256 commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

Verified from a completely clean install (removed node_modules/dist/lockfiles in both packages/sdk and packages/cli, reinstalled from scratch): the TS5097 fix is correct, npm run build succeeds with zero errors, and 15/16 tests passed — the one failure was viem not being resolvable, which turned out to be this branch predating #68 (viem restored as a direct SDK dependency), already merged to main.

Merged main into this branch to pick up that fix and re-verified from another totally clean install: 16/16 tests pass, build clean. Pushed that merge directly to this PR's branch.

Merging. Thanks for chasing the build error all the way through — the dist-testing restructure is the right call.

@Eras256
Eras256 merged commit 8ddcee7 into nirium-protocol:main Aug 26, 2026
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