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
39 changes: 39 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,45 @@ jobs:
- name: Build
run: pnpm build

cli:
name: signet CLI (Go)
runs-on: ubuntu-latest
timeout-minutes: 10
defaults:
run:
working-directory: cli
steps:
- uses: actions/checkout@v7

- uses: actions/setup-go@v5
with:
go-version-file: cli/go.mod

- name: Build
run: go build ./...

- name: Vet
run: go vet ./...

- name: Test
run: go test ./...

# Three things have to agree here, and the original v6 + `version: latest`
# got two of them wrong:
# * cli/.golangci.yml is a v2 config (`version: "2"`, and the v2-only
# `formatters:` block), so the action must be v8 — v6 drives
# golangci-lint v1 and passes v1-only flags.
# * golangci-lint refuses to run when the Go version it was *built with*
# is older than the module's target, so this release has to be one
# built with >= go 1.25 to match cli/go.mod. v2.1.6 is built with
# go1.24 and fails with "can't load config: the Go language version
# (go1.24) ... is lower than the targeted Go version (1.25.0)".
# * `latest` would let both of those drift again without a commit here.
- uses: golangci/golangci-lint-action@v8
with:
version: v2.13.2
working-directory: cli

contracts:
name: soroban contract tests
runs-on: ubuntu-latest
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ set from the event stream.
| `packages/db` | Prisma schema + generated client |
| `packages/sdk` | External SDK for integrators |
| `packages/types` | Shared TypeScript types |
| `cli` | `signet` CLI (Go) — links wallets, manages keys, talks to a Signet deployment |
| `infra` | Local dev infra (Docker Postgres) |

## Scripts
Expand All @@ -183,6 +184,7 @@ set from the event stream.
| `pnpm db:migrate` | Run Prisma migrations |
| `pnpm test` | All TypeScript tests via Turborepo |
| `cargo test` (in `packages/contracts`) | Identity Registry unit tests |
| `go build ./...` / `go test ./...` (in `cli`) | Build / test the `signet` CLI |

## Tests

Expand Down
1 change: 1 addition & 0 deletions cli/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/bin/
16 changes: 16 additions & 0 deletions cli/.golangci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
version: "2"

linters:
default: standard
enable:
- bodyclose
- errcheck
- govet
- ineffassign
- staticcheck
- unused

formatters:
enable:
- gofmt
- goimports
52 changes: 52 additions & 0 deletions cli/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# signet CLI

The `signet` command-line companion to the Signet developer identity registry
on Stellar/Soroban. It lives beside the pnpm workspace rather than inside it —
this is a standalone Go module, not a pnpm package — mirroring the split
between orchestration/UX (Go, here) and anything that must execute Soroban
semantics (Rust, `packages/contracts`).

This module is currently a scaffold: the command tree exists (`signet
--help`, `signet --version`), but the actual subcommands (linking a wallet,
managing keys, talking to a deployment) land in follow-up issues.

## Build

```bash
cd cli
go build -o bin/signet ./cmd/signet
```

To bake a version string and commit hash into the binary:

```bash
go build \
-ldflags "-X main.version=$(git describe --tags --always) -X main.commit=$(git rev-parse --short HEAD)" \
-o bin/signet ./cmd/signet
```

## Test / lint

```bash
go vet ./...
go test ./...
golangci-lint run ./...
```

No cgo is used anywhere in this module, so it cross-compiles with the
standard `GOOS`/`GOARCH` combinations, e.g.:

```bash
GOOS=darwin GOARCH=arm64 go build -o bin/signet-darwin-arm64 ./cmd/signet
GOOS=linux GOARCH=amd64 go build -o bin/signet-linux-amd64 ./cmd/signet
```

## Layout

| Path | Purpose |
|------|---------|
| `cmd/signet` | `main.go` — the binary's entrypoint |
| `internal/cmd` | Cobra command tree |
| `internal/link` | Bind a wallet to a Signet handle (scaffolded, not yet implemented) |
| `internal/keys` | Local signing key management (scaffolded, not yet implemented) |
| `internal/spec` | Typed request/response models for a Signet deployment's HTTP API (scaffolded, not yet implemented) |
28 changes: 28 additions & 0 deletions cli/cmd/signet/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Command signet is the CLI companion to the Signet developer identity
// registry on Stellar/Soroban: it links wallets to on-chain handles, manages
// local signing keys, and talks to a Signet deployment over its HTTP API.
package main

import (
"fmt"
"os"

"github.com/blockchain-maxis/signet/cli/internal/cmd"
)

// version and commit are overridden at build time via:
//
// go build -ldflags "-X main.version=$(git describe --tags) -X main.commit=$(git rev-parse --short HEAD)"
//
// They default to "dev"/"none" for a plain `go build` or `go run`.
var (
version = "dev"
commit = "none"
)

func main() {
if err := cmd.Execute(version, commit); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
11 changes: 11 additions & 0 deletions cli/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module github.com/blockchain-maxis/signet/cli

go 1.25.0

require github.com/spf13/cobra v1.8.1

require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/net v0.58.0 // indirect
)
12 changes: 12 additions & 0 deletions cli/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To=
golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
46 changes: 46 additions & 0 deletions cli/internal/cmd/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Package cmd wires up the signet CLI's command tree with Cobra. Subcommands
// that talk to a wallet, a keyring, or a Signet deployment live in their own
// internal packages (link, keys, spec) and are attached here as they land.
package cmd

import (
"fmt"

"github.com/spf13/cobra"
)

func newRootCmd(version, commit string) *cobra.Command {
root := &cobra.Command{
Use: "signet",
Short: "Link wallets and manage identity on the Signet registry",
Long: `signet is the command-line companion to the Signet developer identity
registry on Stellar/Soroban.

It links a local wallet to an on-chain handle, manages signing keys, and
talks to a Signet deployment (the default hosted one, or a self-hosted
instance) over its HTTP API.`,
SilenceUsage: true,
SilenceErrors: true,
}

// Cobra only renders the "Usage:" section of --help (and of a bare
// invocation) when the command is Runnable() or has subcommands — neither
// is true yet for a fresh scaffold with no subcommands attached. Giving it
// a RunE that just prints help keeps `signet` and `signet --help` both
// showing real usage instead of only the Long description.
root.RunE = func(cmd *cobra.Command, _ []string) error {
return cmd.Help()
}

// Cobra wires --version to this automatically once Version is non-empty.
root.Version = fmt.Sprintf("%s (commit %s)", version, commit)
root.SetVersionTemplate("signet version {{.Version}}\n")

return root
}

// Execute builds the command tree and runs it against the process's
// arguments. main() only needs to report the error and set an exit code.
func Execute(version, commit string) error {
return newRootCmd(version, commit).Execute()
}
63 changes: 63 additions & 0 deletions cli/internal/cmd/root_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package cmd

import (
"bytes"
"strings"
"testing"
)

func TestRootCmdVersion(t *testing.T) {
root := newRootCmd("1.2.3", "abc1234")
out := &bytes.Buffer{}
root.SetOut(out)
root.SetErr(out)
root.SetArgs([]string{"--version"})

if err := root.Execute(); err != nil {
t.Fatalf("--version returned an error: %v", err)
}

got := out.String()
want := "signet version 1.2.3 (commit abc1234)\n"
if got != want {
t.Fatalf("--version output = %q, want %q", got, want)
}
}

func TestRootCmdHelp(t *testing.T) {
root := newRootCmd("dev", "none")
out := &bytes.Buffer{}
root.SetOut(out)
root.SetErr(out)
root.SetArgs([]string{"--help"})

if err := root.Execute(); err != nil {
t.Fatalf("--help returned an error: %v", err)
}

got := out.String()
if !strings.Contains(got, "signet") {
t.Fatalf("--help output does not mention the command name: %q", got)
}
if !strings.Contains(got, "Usage:") {
t.Fatalf("--help output does not print usage: %q", got)
}
}

func TestRootCmdRunsWithoutArgs(t *testing.T) {
// No subcommand is implemented yet — the bare root command should still
// print its own help rather than erroring, so `signet` alone is a
// friendly landing page rather than a crash.
root := newRootCmd("dev", "none")
out := &bytes.Buffer{}
root.SetOut(out)
root.SetErr(out)
root.SetArgs([]string{})

if err := root.Execute(); err != nil {
t.Fatalf("running with no args returned an error: %v", err)
}
if !strings.Contains(out.String(), "Usage:") {
t.Fatalf("running with no args did not print help: %q", out.String())
}
}
4 changes: 4 additions & 0 deletions cli/internal/keys/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Package keys will manage local Stellar signing keys used by the CLI —
// generating, storing, and loading them for the link and spec commands to
// sign with. Scaffolded here; implemented in a follow-up issue.
package keys
4 changes: 4 additions & 0 deletions cli/internal/link/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Package link will bind a local wallet key to a Signet handle through the
// Identity Registry's claim/release/transfer operations. Scaffolded here;
// implemented in a follow-up issue.
package link
5 changes: 5 additions & 0 deletions cli/internal/spec/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Package spec will hold the request/response types for a Signet
// deployment's HTTP API, so the rest of the CLI talks to it through typed
// Go values instead of hand-built JSON. Scaffolded here; implemented in a
// follow-up issue.
package spec
Loading