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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
node_modules
build
src/test/e2e/albyhub-*
src/test/e2e/albyhub-*
.env
117 changes: 71 additions & 46 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,12 @@ npx @getalby/hub-cli balances --hub production

**Token storage locations:**

| Location | Used when |
| --- | --- |
| `~/.hub-cli/token.jwt` | `--save` flag or default |
| Location | Used when |
| ----------------------------- | ------------------------------------ |
| `~/.hub-cli/token.jwt` | `--save` flag or default |
| `~/.hub-cli/token-<name>.jwt` | `--save-as <name>` or `--hub <name>` |
| `HUB_TOKEN` env var | Always checked before file |
| `-t, --token <jwt>` | Highest priority |
| `HUB_TOKEN` env var | Always checked before file |
| `-t, --token <jwt>` | Highest priority |

## Testing with Mutinynet

Expand Down Expand Up @@ -146,8 +146,8 @@ npx @getalby/hub-cli list-channels
# List LSP providers with fees and channel size limits
npx @getalby/hub-cli get-channel-suggestions

# Get Alby LSP offer (requires linked Alby account)
npx @getalby/hub-cli get-channel-offer
# Request Alby LSP offer (requires linked Alby account)
npx @getalby/hub-cli request-alby-lsp-channel-offer

# Get your node's connection info (pubkey, address, port)
npx @getalby/hub-cli get-node-connection-info
Expand Down Expand Up @@ -242,68 +242,93 @@ npx @getalby/hub-cli create-app --name "Isolated App" --isolated --unlock-passwo

### Setup & Auth

| Command | Description | Required Options |
| --- | --- | --- |
| `setup` | Initialize hub for the first time (one-time) | `--password` |
| `start` | Start the node after setup or restart; returns a JWT token | `--password` |
| `unlock` | Get a JWT token for an already-running hub (no restart) | `--password` |
| Command | Description | Required Options |
| -------- | ---------------------------------------------------------- | ---------------- |
| `setup` | Initialize hub for the first time (one-time) | `--password` |
| `start` | Start the node after setup or restart; returns a JWT token | `--password` |
| `unlock` | Get a JWT token for an already-running hub (no restart) | `--password` |

### Info & Status

| Command | Description | Required Options |
| --- | --- | --- |
| `get-info` | Hub status, version, backend type | — |
| `get-node-status` | Lightning node readiness | — |
| `get-health` | Health check and active alarms | — |
| Command | Description | Required Options |
| ----------------- | --------------------------------- | ---------------- |
| `get-info` | Hub status, version, backend type | — |
| `get-node-status` | Lightning node readiness | — |
| `get-health` | Health check and active alarms | — |

### Balances & Wallet

| Command | Description | Required Options |
| --- | --- | --- |
| `balances` | Lightning + on-chain balances | — |
| `get-onchain-address` | On-chain deposit address | — |
| Command | Description | Required Options |
| --------------------- | ----------------------------- | ---------------- |
| `balances` | Lightning + on-chain balances | — |
| `get-onchain-address` | On-chain deposit address | — |

### Channels & Peers

| Command | Description | Required Options |
| --- | --- | --- |
| `list-channels` | List Lightning channels | — |
| `get-channel-suggestions` | List LSP providers with fees | — |
| `get-channel-offer` | Get Alby LSP offer | — |
| `get-node-connection-info` | Get node pubkey, address, port | — |
| `list-peers` | List connected peers | — |
| `connect-peer` | Connect to a Lightning peer | `--pubkey`, `--address`, `--port` |
| `open-channel` | Open an outbound channel to a peer | `--pubkey`, `--amount-sats` |
| `close-channel` | Close a lightning channel (cooperative or force) | `--peer-id`, `--channel-id` |
| `request-lsp-order` | Request LSP channel invoice | `--amount`, `--lsp-type`, `--lsp-identifier` |
| Command | Description | Required Options |
| -------------------------------- | ------------------------------------------------ | -------------------------------------------- |
| `list-channels` | List Lightning channels | — |
| `get-channel-suggestions` | List LSP providers with fees | — |
| `request-alby-lsp-channel-offer` | Request Alby LSP offer | — |
| `get-node-connection-info` | Get node pubkey, address, port | — |
| `list-peers` | List connected peers | — |
| `connect-peer` | Connect to a Lightning peer | `--pubkey`, `--address`, `--port` |
| `open-channel` | Open an outbound channel to a peer | `--pubkey`, `--amount-sats` |
| `close-channel` | Close a lightning channel (cooperative or force) | `--peer-id`, `--channel-id` |
| `request-lsp-order` | Request LSP channel invoice | `--amount`, `--lsp-type`, `--lsp-identifier` |

### Node Management

| Command | Description | Required Options |
| --- | --- | --- |
| `stop` | Stop the Lightning node (HTTP server keeps running) | — |
| Command | Description | Required Options |
| ------- | --------------------------------------------------- | ---------------- |
| `stop` | Stop the Lightning node (HTTP server keeps running) | — |

### Payments

| Command | Description | Required Options |
| --- | --- | --- |
| `pay-invoice` | Pay a BOLT11 invoice | `<invoice>` (argument) |
| `make-invoice` | Create a BOLT11 invoice | `--amount` |
| Command | Description | Required Options |
| -------------- | ----------------------- | ---------------------- |
| `pay-invoice` | Pay a BOLT11 invoice | `<invoice>` (argument) |
| `make-invoice` | Create a BOLT11 invoice | `--amount` |

### Transactions

| Command | Description | Required Options |
| --- | --- | --- |
| `list-transactions` | List payment history | — |
| Command | Description | Required Options |
| -------------------- | ------------------------- | -------------------------- |
| `list-transactions` | List payment history | — |
| `lookup-transaction` | Look up a payment by hash | `<paymentHash>` (argument) |

### NWC Apps

| Command | Description | Required Options |
| --- | --- | --- |
| `apps` | List NWC app connections | — |
| `create-app` | Create a new NWC connection | `--name` |
| Command | Description | Required Options |
| ------------ | --------------------------- | ---------------- |
| `apps` | List NWC app connections | — |
| `create-app` | Create a new NWC connection | `--name` |

## Output

All commands output JSON to stdout. Errors are written to stderr as JSON with a `message` field.

## Development

`yarn install`

`yarn dev`

`yarn test`

### E2E Testing

End-to-end tests spawn a real Alby Hub binary and exercise the CLI against it. Full setup instructions are in [`src/test/e2e/README.md`](src/test/e2e/README.md).

**Quick start:**

1. Download the Linux Ubuntu 24.04 Alby Hub binary from [GitHub releases](https://github.com/getAlby/hub/releases) and extract it to `src/test/e2e/albyhub-Server-Linux-x86_64/`
2. For regtest channel tests: install [Polar](https://lightningpolar.com/) and start a network with a Bitcoin Core node
3. For Mutinynet LSP tests: copy `src/test/e2e/.env.example` → `src/test/e2e/.env` and fill in `MUTINYNET_NWC_URL` (you need a Mutinynet hub running with sufficient liquidity, ideally a direct channel directly to Megalith Mutinynet LSP)

```bash
cd hub-cli
yarn test:e2e
```

Mutinynet tests are skipped automatically when `MUTINYNET_NWC_URL` is not set.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"commander": "^13.1.0"
},
"devDependencies": {
"@getalby/sdk": "^7.0.0",
"@types/node": "^22.0.0",
"typescript": "^5.5.0",
"vitest": "^3.0.0"
Expand Down
4 changes: 2 additions & 2 deletions src/commands/channel-offer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import { getClient, handleError, output } from "../utils.js";

export function registerChannelOfferCommand(program: Command): void {
program
.command("get-channel-offer")
.command("request-alby-lsp-channel-offer")
.description(
"Get Alby LSP channel offer with recommended size and fee (requires linked Alby account)",
"Request Alby LSP channel offer with recommended size and fee (requires linked Alby account)",
)
.action(async () => {
await handleError(async () => {
Expand Down
2 changes: 2 additions & 0 deletions src/test/e2e/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# NWC connection URL from a funded Mutinynet Alby Hub, used to pay LSP invoices in E2E tests
MUTINYNET_NWC_URL="nostr+walletconnect://..."
43 changes: 32 additions & 11 deletions src/test/e2e/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,29 +16,50 @@ The directory must contain at minimum:
- `bin/albyhub` — the executable
- `lib/libldk_node.so` — the LDK node shared library

### Polar (optional — for future `start`/`unlock` tests)
### Polar (regtest channel lifecycle tests)

For tests that require Bitcoin connectivity (not needed for `setup`):
The `channel-lifecycle.e2e.test.ts` suite requires a local Bitcoin Core node accessible via RPC.

1. Download [Polar](https://lightningpolar.com/)
2. Create a network with a Bitcoin Core node
2. Create a network with a Bitcoin Core node using the default credentials (`polaruser` / `polarpass`)
3. Start the network
4. Set `POLAR_ESPLORA_URL` env var to your Polar Esplora URL (e.g. `http://127.0.0.1:3000`)

The tests connect to Bitcoin Core on `127.0.0.1:18443` using those defaults — no extra env vars needed.

### Mutinynet NWC URL (Mutinynet LSP test)

The `mutinynet-lsp.e2e.test.ts` suite requires a pre-funded Mutinynet (signet) Alby Hub with an NWC connection URL so it can pay LSP invoices automatically.

1. Copy the example env file:
```bash
cp src/test/e2e/.env.example src/test/e2e/.env
```
2. Edit `src/test/e2e/.env` and set `MUTINYNET_NWC_URL` to your NWC connection URL.

Without this file (or with the variable unset) the Mutinynet tests are skipped automatically — no failures.

## Running

```bash
# All E2E tests
yarn test:e2e

# Individual suite (vitest pattern matching)
yarn test:e2e --reporter=verbose
```

## Environment variables
## Test suites

| Variable | Default | Description |
|----------|---------|-------------|
| `POLAR_ESPLORA_URL` | `http://127.0.0.1:3000` | Esplora URL from Polar (only needed for `start`/`unlock` tests) |
| File | Requires | Description |
|------|----------|-------------|
| `setup.e2e.test.ts` | Hub binary | Hub initialisation |
| `start.e2e.test.ts` | Hub binary | Node start + JWT |
| `unlock.e2e.test.ts` | Hub binary | Token refresh |
| `stop.e2e.test.ts` | Hub binary | Node stop |
| `channel-lifecycle.e2e.test.ts` | Hub binary + Polar | Two-hub regtest channel open, payments, close |
| `mutinynet-lsp.e2e.test.ts` | Hub binary + `MUTINYNET_NWC_URL` | Signet LSP channel open via NWC payment, payments, close |

## Notes

- The hub is started on port `18080` to avoid conflicts with a locally running hub
- A temporary `WORK_DIR` is created per test run and cleaned up automatically
- The `setup` test does not require Bitcoin/Polar connectivity — it only calls `POST /api/setup`
- Each test suite spawns its own hub on a dedicated port and temporary `WORK_DIR`, cleaned up automatically
- Mutinynet tests are skipped (not failed) when `MUTINYNET_NWC_URL` is not set, so CI stays green without credentials
32 changes: 32 additions & 0 deletions src/test/e2e/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,38 @@ export async function spawnHub(
return { hubProcess, workDir };
}

export async function spawnMutinynetHub(
port: number,
tmpPrefix: string,
ldkPort = DEFAULT_LDK_PORT,
): Promise<{ hubProcess: ChildProcess; workDir: string }> {
const workDir = mkdtempSync(join(tmpdir(), tmpPrefix));

console.log("Hub WORK_DIR:", workDir);

const hubProcess = spawn(HUB_BINARY, [], {
env: {
...process.env,
WORK_DIR: workDir,
PORT: String(port),
NETWORK: "signet",
MEMPOOL_API: "https://mutinynet.com/api",
LDK_ESPLORA_SERVER: "https://mutinynet.com/api",
LDK_GOSSIP_SOURCE: "https://rgs.mutinynet.com/snapshot",
LDK_LISTENING_ADDRESSES: `0.0.0.0:${ldkPort}`,
LDK_ANNOUNCEMENT_ADDRESSES: `127.0.0.1:${ldkPort}`,
},
stdio: "pipe",
});

hubProcess.stdout?.on("data", (d) => process.stdout.write(`[hub] ${d}`));
hubProcess.stderr?.on("data", (d) => process.stderr.write(`[hub] ${d}`));

await waitForHub(`http://localhost:${port}`);

return { hubProcess, workDir };
}

export async function waitForHub(
url: string,
timeoutMs = 20_000,
Expand Down
Loading
Loading