DOCUMENTATION

Provision a chain. Connect your app.

This page covers using Signet Sandbox. Setup for people who change the code lives in the repository, which the GitHub link in the header opens.

Install

The CLI is one binary named signet. One line installs it: no repository clone, no sudo.

$ curl -fsSL https://nully0x.github.io/signet-sandbox/install.sh | sh

The script detects your platform, verifies the release checksum, and installs into ~/.local/bin (override with SIGNET_INSTALL_DIR). It covers linux x86_64/arm64 and macOS x86_64/arm64; windows is not supported.

Upgrade

Re-run the same line. The script skips work when the newest release is already installed.

Pin a version

The default line always installs the newest release. To install an exact one, pass --version with any tag from the releases page; v0.1.0 here is an example.

$ curl -fsSL https://nully0x.github.io/signet-sandbox/install.sh | sh -s -- --version v0.1.0

Uninstall

$ curl -fsSL https://nully0x.github.io/signet-sandbox/install.sh | sh -s -- uninstall

Uninstalling removes only the binary at ~/.local/bin/signet. Your identity and connection profile at ~/.signet are kept; delete that directory too if you want a full cleanup. From a repository clone, scripts/install.sh uninstall does the same thing.

Docker is the only runtime prerequisite. Start the daemon before you provision anything.

Getting started

Four steps take you from an installed CLI to a running chain with a funded address.

Step 1. Bootstrap the stack

bootstrap prepares the local platform, starts the API with its database, and writes a profile. That profile identity owns every environment you create. The API also serves the dashboard.

$ signet bootstrap

Pass the advertised host when other machines must reach the endpoints:

$ signet bootstrap --host <host machine ip>

Endpoints default to localhost, which limits them to this machine. The advertised host is the endpoint host, the certificate name, and the explorer hostname base. Choose it at the first bootstrap, because the flag does nothing on a stack that already runs. To change it later, run signet bootstrap --host again with the new value, then mint new credentials for the new URLs.

Step 2. Check the stack

$ signet status

The report covers the local platform, API health, and the identity that will own your environments.

Step 3. Provision an environment

$ signet up --name my-app --ttl 2h --ui

Expect about a minute on the first run, because the signer premines 101 blocks. A warm stack provisions in about 20 seconds. --ui opens the dashboard once the environment is ready; drop it when you work from the terminal only.

up also starts the stack itself when the API is not running, and forwards its own --host to that bootstrap. Run bootstrap first when the host must be set, or when you want the stack running before the first environment.

Step 4. Read the bundle and fund an address

Provisioning prints the connection bundle. Read it again at any time, then send coin to an address:

$ signet get my-app --json
$ signet fund my-app tb1qexampleaddress --amount 100000

The bundle carries no address. Generate one from something that tracks this chain:

  • Your own wallet, connected to indexer_endpoint or to the node RPC.
  • The environment's node, which answers getnewaddress over rpc_url, authenticated with rpc_auth.
  • A Lightning instance, with lncli newaddress p2wkh in the environment, or GET /v1/newaddress with the bundle macaroon.
$ curl -s --user "$RPC_AUTH" -H 'content-type: text/plain;' \
    --data-binary '{"jsonrpc":"1.0","id":1,"method":"getnewaddress","params":[]}' \
    "$RPC_URL"

Use an address from a wallet that tracks this chain. A key from another chain gives you coin you cannot see.

Every command that takes an environment accepts the id or the name. Names resolve on the server.

Tear down

$ signet teardown

Teardown deletes the local platform and every environment on it. The profile is kept, so the identity keeps ownership of what you create next.

Configure

Pass flags, or keep a file named sandbox.json in the working directory and up reads it. An explicit --config path also works, and --name and --ttl override the file.

{
  "name": "my-app",
  "ttl": "2h",
  "components": {
    "indexer": true,
    "explorer": false,
    "faucet": true,
    "lightning": [
      { "name": "ln-alice", "implementation": "lnd" },
      { "name": "ln-bob", "implementation": "lnd" }
    ]
  },
  "versions": { "bitcoind": "29.4" }
}
FieldMeaning
nameEnvironment name, unique per identity
ttlHuman duration: 30m, 2h, 1d. The API field is ttl_secs
componentsindexer, explorer, and faucet are booleans. lightning is a list of named instances
versionsPin component tags. Keys are component names, values are tags

The chain and its block signer always come up. Everything else is optional, and a disabled component is omitted from the bundle rather than returned as null.

Only lnd is available as a Lightning implementation. Instance names are DNS labels: lowercase letters, digits, and hyphens, up to 63 characters.

Print the full schema when you need field-level detail:

$ signet schema

The bundle

One object carries every endpoint and every credential your application needs.

FieldUse
rpc_urlBitcoin Core JSON-RPC
rpc_authBasic auth value, user:password
indexer_endpointElectrum endpoint for wallets, when the indexer is enabled
explorer_urlBrowser view of blocks and transactions
faucet_urlFaucet, or use signet fund
lightningMap keyed by instance name, each entry described below
signet_challengeThe challenge for this chain. Two environments never share one
block_policyinterval_30s or on_demand
expires_atWall-clock expiry, when a TTL was set

Connect an application

Bitcoin RPC

Point your client at rpc_url and send rpc_auth as basic auth. The node runs on a custom signet, so the client must not expect the public signet chain.

Electrum wallet

Connect to indexer_endpoint. It is a plain endpoint with no authentication, reachable from the machine that runs the stack.

Explorer

Open explorer_url in a browser. The platform routes by hostname, so an explorer hostname needs a hosts entry when you open it from another machine.

Faucet

Fund any address that belongs to that chain. Use the CLI, or post to faucet_url with the environment credentials.

Remote callers

Endpoints default to localhost. When another machine must connect, set the advertised host at the first bootstrap, as shown in Getting started, and read the bundle again for the new URLs.

Lightning

Each instance is reachable by port, not by name. Three consecutive ports serve REST, gRPC, and peering. The range is 21000 to 21199.

rest   https://localhost:21000
grpc   https://localhost:21001
p2p    localhost:21002

Each entry in the map carries rest_url, grpc_url, tls_cert, the auth object with a macaroon, node_id, and node_uri.

Add a node while it runs

environment.update adds lightning instances to a live, ready environment without tearing it down. The lightning array is the complete desired set: include every instance that already exists, because omitting one is rejected as a removal. v1 is add-only — removals, ttl changes, and other components stay create-time decisions. New instances join the bundle's lightning map once their wallets are ready.

$ curl -s -X POST localhost:8081/api/rpc \
    -H 'content-type: application/json' \
    -H "Authorization: Bearer $SIGNET_TOKEN" \
    -d '{
      "jsonrpc": "2.0", "id": 1,
      "method": "environment.update",
      "params": {
        "id": "my-env",
        "components": {
          "lightning": [
            { "name": "ln-alice", "implementation": "lnd" },
            { "name": "ln-carol", "implementation": "lnd" }
          ]
        }
      }
    }'

Authentication and TLS

The macaroon ships hex-encoded. REST clients send it as the Grpc-Metadata-macaroon header. gRPC clients send the macaroon and trust the certificate.

The bundle's tls_cert is the CA, hex-encoded PEM. Decode it and pin it as a root. The served leaf chains to it and covers localhost, the internal service names, and the advertised host. Do not disable verification, because the environment exists to behave like the real thing.

Peering and channels

Instances peer automatically after create. Channels stay manual, because channel liquidity is usually the thing under test. Open, pay, and close with lncli run in the environment, or from a client that uses node_uri.

Nodes start with an empty wallet. Fund an address first, then open a channel.

Manage and tear down

CommandEffect
signet lsList your environments with status and expiry
signet getPrint the bundle for one environment
signet fundSend coin from the faucet to an address
signet stopScale compute to zero and keep storage. The chain survives
signet startResume a stopped environment
signet downDelete the environment and its chain
signet deleteRemove the record of a destroyed or expired environment
signet statusReport the local stack, API health, and identity
signet teardownDelete the local stack. The profile is kept

Expiry

An environment created with a TTL carries an expiry time. A background reaper deletes the environment after that time passes. Stopping an environment does not pause the clock, so a stopped environment still expires.

Names are scoped to your identity. A name held by a destroyed or expired record is free to use again.

Call the API

The CLI is one client. For CI and applications, call the same API directly: JSON-RPC on POST /api/rpc, with a health check on GET /healthz. Failures are error objects inside HTTP 200, never HTTP status codes.

-32002
Unauthenticated. The header is missing, stale, or does not match the URL or method.
-32004
Not the owner. Environments belong to the identity that created them.
-32602
Invalid params. An unknown version key, a bad instance name, or a non-positive ttl_secs.
-32030
Wrong lifecycle state. Stop requires ready, and start requires stopped.

Authentication

Mint a bearer token and send it in the Authorization header. The token is shown once, and only its hash is stored.

$ signet token
$ export SIGNET_TOKEN=sgn_...

The CLI reads the token from the environment or from --token, and it reads the API base URL from SIGNET_API or --api. Bearer callers cannot mint new tokens, so a new token needs the identity that owns the environments.

The alternative is a signed request. Send a Nostr event of kind 27235 as Authorization: Nostr base64(event), with a u tag holding the exact request URL and a method tag holding the HTTP verb. Events expire after 300 seconds, so mint one per request.

Create an environment

$ curl -s -X POST localhost:8081/api/rpc \
    -H 'content-type: application/json' \
    -H "Authorization: Bearer $SIGNET_TOKEN" \
    -d '{
      "jsonrpc": "2.0", "id": 1,
      "method": "environment.create",
      "params": {
        "name": "ci-env",
        "components": { "indexer": true, "faucet": true },
        "versions": { "bitcoind": "29.4" },
        "ttl_secs": 1200
      }
    }'

The response echoes the resolved versions and the expiry time. The same calls drive the CLI, so a pipeline can create an environment, read the bundle, run its tests, and call environment.destroy.

Agent skill

Coding agents can drive the sandbox for you. The repository ships a skill at skills/signet-sandbox/SKILL.md: the same workflow this page documents, compressed for agents — bundle shape, endpoints, funding, lifecycle, multi-node channels, and the LND REST quirks. Reference the file from your agent, or copy it into your agent's skills directory.

Troubleshooting

The CLI cannot reach the API

Start the local stack with signet up, or check what is running with signet status.

The first environment is slow

The signer premines 101 blocks before the chain is usable. Expect about a minute on the first run, and about 20 seconds with a warm stack.

An endpoint works on one machine only

Endpoints default to localhost. Re-bootstrap with --host set to an address the other machine can reach, then read the bundle again for the new URLs.

A signed request fails after a host change

The signed URL must match the URL the server receives. Mint a new header for the new URL.

The explorer does not open from another machine

The platform routes by hostname, so add a hosts entry for the environment's explorer hostname on the machine that opens it.

A Lightning name is rejected

Use lowercase letters, digits, and hyphens, start with a letter or digit, and keep the name within 63 characters. Names must be unique inside one environment.

A channel will not open

Fund the node before opening a channel. Instances peer automatically, but they start with an empty wallet and no channels.

An environment disappeared

The TTL expired and the reaper removed it. Stopping does not pause the clock, so choose a TTL that covers the work.