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
98 changes: 98 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
name: CI

on:
pull_request:

jobs:
unit-tests:
name: Unit tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: "22"
cache: "yarn"
cache-dependency-path: yarn.lock

- run: yarn install --frozen-lockfile

- run: yarn test

e2e-tests:
name: E2E tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: "22"
cache: "yarn"
cache-dependency-path: yarn.lock

- run: yarn install --frozen-lockfile

- name: Download Alby Hub binary
run: |
curl -sL https://github.com/getAlby/hub/releases/download/v1.21.4/albyhub-Server-Linux-x86_64.tar.bz2 \
-o src/test/e2e/albyhub-Server-Linux-x86_64.tar.bz2
mkdir -p src/test/e2e/albyhub-Server-Linux-x86_64
tar -xjf src/test/e2e/albyhub-Server-Linux-x86_64.tar.bz2 -C src/test/e2e/albyhub-Server-Linux-x86_64

- name: Cache Bitcoin Core binary
uses: actions/cache@v4
with:
path: bitcoin-28.1
key: bitcoind-28.1-linux-x86_64

- name: Install and start bitcoind (regtest)
run: |
if [ ! -f bitcoin-28.1/bin/bitcoind ]; then
curl -sL https://bitcoincore.org/bin/bitcoin-core-28.1/bitcoin-28.1-x86_64-linux-gnu.tar.gz \
-o bitcoin-28.1.tar.gz
tar -xzf bitcoin-28.1.tar.gz
rm bitcoin-28.1.tar.gz
fi

mkdir -p /tmp/bitcoin-regtest
printf '%s\n' \
'regtest=1' \
'server=1' \
'daemon=1' \
'' \
'[regtest]' \
'rpcuser=polaruser' \
'rpcpassword=polarpass' \
'rpcbind=127.0.0.1' \
'rpcallowip=127.0.0.1' \
'fallbackfee=0.0002' \
> /tmp/bitcoin-regtest/bitcoin.conf

bitcoin-28.1/bin/bitcoind -datadir=/tmp/bitcoin-regtest

for i in $(seq 1 30); do
if bitcoin-28.1/bin/bitcoin-cli \
-regtest \
-rpcconnect=127.0.0.1 \
-rpcport=18443 \
-rpcuser=polaruser \
-rpcpassword=polarpass \
getblockchaininfo > /dev/null 2>&1; then
echo "bitcoind ready after ${i}s"
break
fi
echo "Waiting for bitcoind... ($i/30)"
sleep 1
done

bitcoin-28.1/bin/bitcoin-cli \
-regtest \
-rpcconnect=127.0.0.1 \
-rpcport=18443 \
-rpcuser=polaruser \
-rpcpassword=polarpass \
createwallet "default"

- run: yarn test:e2e
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
node_modules
build
build
src/test/e2e/albyhub-*
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"editor.formatOnSave": true
}
85 changes: 60 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,10 @@ LDK_ESPLORA_SERVER=https://mutinynet.com/api \
# Set up, start the node, and save the token
npx @getalby/hub-cli setup --password YOUR_PASSWORD --backend LDK
npx @getalby/hub-cli start --password YOUR_PASSWORD --save
npx @getalby/hub-cli info
npx @getalby/hub-cli get-info

# Get initial test funds from https://faucet.mutinynet.com (requires human + GitHub login)
npx @getalby/hub-cli wallet-address
npx @getalby/hub-cli get-onchain-address
```

## Commands
Expand Down Expand Up @@ -118,13 +118,13 @@ npx @getalby/hub-cli unlock --password YOUR_PASSWORD --permission readonly --sav

```bash
# Hub status, version, backend type
npx @getalby/hub-cli info
npx @getalby/hub-cli get-info

# Lightning node readiness
npx @getalby/hub-cli node-status
npx @getalby/hub-cli get-node-status

# Health check with active alarms
npx @getalby/hub-cli health
npx @getalby/hub-cli get-health
```

### Balances & Wallet
Expand All @@ -134,40 +134,65 @@ npx @getalby/hub-cli health
npx @getalby/hub-cli balances

# Get an on-chain deposit address
npx @getalby/hub-cli wallet-address
npx @getalby/hub-cli get-onchain-address
```

### Channels & Peers

```bash
# List Lightning channels
npx @getalby/hub-cli channels
npx @getalby/hub-cli list-channels

# List LSP providers with fees and channel size limits
npx @getalby/hub-cli channel-suggestions
npx @getalby/hub-cli get-channel-suggestions

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

# Get your node's connection info (pubkey, address, port)
npx @getalby/hub-cli get-node-connection-info

# List connected peers
npx @getalby/hub-cli peers
npx @getalby/hub-cli list-peers

# Connect to a peer
npx @getalby/hub-cli connect-peer --pubkey <pubkey> --address <host> --port <port>

# Open an outbound channel to a peer (requires on-chain funds)
npx @getalby/hub-cli open-channel --pubkey <pubkey> --amount-sats 500000

# Open a public channel
npx @getalby/hub-cli open-channel --pubkey <pubkey> --amount-sats 500000 --public

# Close a channel (cooperative)
npx @getalby/hub-cli close-channel --peer-id <pubkey> --channel-id <id>

# Force-close a channel
npx @getalby/hub-cli close-channel --peer-id <pubkey> --channel-id <id> --force
```

### Opening a Channel via LSP

```bash
# 1. Pick an LSP from channel-suggestions
npx @getalby/hub-cli channel-suggestions
# 1. Pick an LSP from get-channel-suggestions
npx @getalby/hub-cli get-channel-suggestions

# 2. Request a Lightning invoice from the LSP
npx @getalby/hub-cli lsp-order --amount 1000000 --lsp-type <type> --lsp-identifier <identifier>
npx @getalby/hub-cli request-lsp-order --amount 1000000 --lsp-type <type> --lsp-identifier <identifier>

# 3. Pay the invoice (mainnet — if you have a funded wallet)
npx @getalby/hub-cli pay-invoice <invoice>

# On Mutinynet, a human must pay the invoice via https://faucet.mutinynet.com
```

### Node Management

```bash
# Stop the Lightning node (hub HTTP server keeps running)
npx @getalby/hub-cli stop
```

### Payments

```bash
Expand All @@ -185,10 +210,10 @@ npx @getalby/hub-cli make-invoice --amount 1000 --description "test"

```bash
# List recent payments
npx @getalby/hub-cli transactions
npx @getalby/hub-cli list-transactions

# With pagination
npx @getalby/hub-cli transactions --limit 50 --offset 0
npx @getalby/hub-cli list-transactions --limit 50 --offset 0

# Look up a specific payment by hash
npx @getalby/hub-cli lookup-transaction <paymentHash>
Expand Down Expand Up @@ -227,26 +252,36 @@ npx @getalby/hub-cli create-app --name "Isolated App" --isolated --unlock-passwo

| Command | Description | Required Options |
| --- | --- | --- |
| `info` | Hub status, version, backend type | — |
| `node-status` | Lightning node readiness | — |
| `health` | Health check and active alarms | — |
| `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 | — |
| `wallet-address` | On-chain deposit address | — |
| `get-onchain-address` | On-chain deposit address | — |

### Channels & Peers

| Command | Description | Required Options |
| --- | --- | --- |
| `channels` | List Lightning channels | — |
| `channel-suggestions` | List LSP providers with fees | — |
| `channel-offer` | Get Alby LSP offer | — |
| `peers` | List connected peers | — |
| `lsp-order` | Request LSP channel invoice | `--amount`, `--lsp-type`, `--lsp-identifier` |
| `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` |

### Node Management

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

### Payments

Expand All @@ -259,7 +294,7 @@ npx @getalby/hub-cli create-app --name "Isolated App" --isolated --unlock-passwo

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

### NWC Apps
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
"build": "tsc && chmod 755 build/index.js",
"start": "node build/index.js",
"dev": "yarn build && node build/index.js",
"test": "yarn build && vitest run",
"test": "yarn build && vitest run --config vitest.config.ts",
"test:e2e": "yarn build && vitest run --config vitest.config.e2e.ts",
"test:watch": "vitest"
},
"keywords": [
Expand Down
8 changes: 8 additions & 0 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ export class HubClient {
return this.handleResponse<T>(res);
}

async delete<T>(path: string): Promise<T> {
const res = await fetch(`${this.baseUrl}${path}`, {
method: "DELETE",
headers: this.headers(),
});
return this.handleResponse<T>(res);
}

private async handleResponse<T>(res: Response): Promise<T> {
const text = await res.text();
if (!res.ok) {
Expand Down
2 changes: 1 addition & 1 deletion src/commands/channel-offer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { getClient, handleError, output } from "../utils.js";

export function registerChannelOfferCommand(program: Command): void {
program
.command("channel-offer")
.command("get-channel-offer")
.description(
"Get Alby LSP channel offer with recommended size and fee (requires linked Alby account)",
)
Expand Down
2 changes: 1 addition & 1 deletion src/commands/channel-suggestions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { getClient, handleError, output } from "../utils.js";

export function registerChannelSuggestionsCommand(program: Command): void {
program
.command("channel-suggestions")
.command("get-channel-suggestions")
.description(
"List available LSP providers with fees and channel size limits",
)
Expand Down
2 changes: 1 addition & 1 deletion src/commands/channels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { getClient, handleError, output } from "../utils.js";

export function registerChannelsCommand(program: Command): void {
program
.command("channels")
.command("list-channels")
.description("List Lightning channels")
.action(async () => {
await handleError(async () => {
Expand Down
27 changes: 27 additions & 0 deletions src/commands/close-channel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Command } from "commander";
import { getClient, handleError, output } from "../utils.js";

export function registerCloseChannelCommand(program: Command): void {
program
.command("close-channel")
.description("Close a lightning channel")
.requiredOption("--peer-id <pubkey>", "Peer's lightning public key")
.requiredOption("--channel-id <id>", "Channel ID")
.option(
"--force",
"Force close the channel (not recommended - only as last resort)",
false,
)
.action(
async (opts: { peerId: string; channelId: string; force: boolean }) => {
await handleError(async () => {
const client = getClient(program);
const query = opts.force ? "?force=true" : "";
const result = await client.delete<Record<string, never>>(
`/api/peers/${opts.peerId}/channels/${opts.channelId}${query}`,
);
output(result);
});
},
);
}
22 changes: 22 additions & 0 deletions src/commands/connect-peer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Command } from "commander";
import { getClient, handleError, output } from "../utils.js";

export function registerConnectPeerCommand(program: Command): void {
program
.command("connect-peer")
.description("Connect to a Lightning peer")
.requiredOption("--pubkey <pubkey>", "Peer's Lightning public key")
.requiredOption("--address <address>", "Peer's IP address or hostname")
.requiredOption("--port <port>", "Peer's port number", parseInt)
.action(async (opts: { pubkey: string; address: string; port: number }) => {
await handleError(async () => {
const client = getClient(program);
await client.post("/api/peers", {
pubkey: opts.pubkey,
address: opts.address,
port: opts.port,
});
output({ success: true });
});
});
}
Loading