Fulmine is a Bitcoin wallet daemon built on Arkade. It can be used as a general-purpose Arkade wallet or as an infrastructure node for Arkade-native services β such as serving VHTLCs or acting as a delegate for automated VTXO refresh.
The easiest way to run fulmine is using Docker. Make sure you have Docker installed on your machine.
docker run -d \
--name fulmine \
-p 7000:7000 \
-p 7001:7001 \
-v fulmine-data:/app/data \
ghcr.io/arklabshq/fulmine:latestPort 7001 serves the web UI and REST API; port 7000 serves the gRPC API. If you intend to run a delegate, also publish the delegate port β see Running as a Delegate.
Once the container is running, you can access the web UI at http://localhost:7001.
To view logs:
docker logs -f fulmineTo stop the container:
docker stop fulmineTo update to the latest version:
docker pull ghcr.io/arklabshq/fulmine:latest
docker stop fulmine && docker rm fulmine
docker run -d \
--name fulmine \
-p 7000:7000 \
-p 7001:7001 \
-v fulmine-data:/app/data \
ghcr.io/arklabshq/fulmine:latestAlternatively, you can download the latest release from the releases page for your platform. After downloading:
- Extract the binary
- Make it executable (on Linux/macOS):
chmod +x fulmine - Run the binary:
./fulmine
All settings are read from environment variables prefixed with FULMINE_. The most common ones are listed below; for the complete auto-generated list see docs/environment.md, or the source of truth, internal/config/config.go.
| Variable | Description | Default |
|---|---|---|
FULMINE_DATADIR |
Directory to store wallet, database and macaroon data | /app/data in Docker; otherwise an OS-specific app dir (~/.fulmine on Linux, ~/Library/Application Support/Fulmine on macOS, %LOCALAPPDATA%\Fulmine on Windows) |
FULMINE_HTTP_PORT |
HTTP port for the web UI and REST API | 7001 |
FULMINE_GRPC_PORT |
gRPC port for service communication | 7000 |
FULMINE_DB_TYPE |
Database backend: sqlite or badger |
sqlite |
FULMINE_LOG_LEVEL |
Log verbosity (logrus levels: 4 = info, 5 = debug, 6 = trace) |
4 |
FULMINE_ARK_SERVER |
URL of the Ark server to connect to. Optional β it can also be set when creating the wallet | Not set |
FULMINE_ESPLORA_URL |
URL of the Esplora-compatible chain API to connect to. Optional | Not set |
FULMINE_NO_MACAROONS |
Disable macaroon authentication on the API (see Authentication) | false (auth enabled) |
Auto-unlock (see Auto-Unlock Feature)
| Variable | Description | Default |
|---|---|---|
FULMINE_UNLOCKER_TYPE |
Auto-unlock method: file or env |
Not set (no auto-unlock) |
FULMINE_UNLOCKER_FILE_PATH |
Path to a file containing the wallet password (when using the file unlocker) |
Not set |
FULMINE_UNLOCKER_PASSWORD |
Wallet password (when using the env unlocker) |
Not set |
Delegate (see Running as a Delegate)
| Variable | Description | Default |
|---|---|---|
FULMINE_DELEGATE_ENABLED |
Run a delegate service that refreshes clients' VTXOs | false |
FULMINE_DELEGATE_PORT |
Port for the delegate API (must differ from the gRPC/HTTP ports) | 7002 |
FULMINE_DELEGATE_FEE |
Service fee charged per delegation, in satoshis | 0 |
| Variable | Description | Default |
|---|---|---|
FULMINE_BOLTZ_URL |
URL of a custom Boltz backend for swaps | Not set |
FULMINE_BOLTZ_WS_URL |
URL of a custom Boltz WebSocket backend for swap events | Not set |
FULMINE_SWAP_TIMEOUT |
Swap timeout, in seconds | 15 |
| Variable | Description | Default |
|---|---|---|
FULMINE_SCHEDULER_POLL_INTERVAL |
How often (seconds) the scheduler polls for VTXOs to refresh | 600 |
FULMINE_REFRESH_DB_INTERVAL |
How often (seconds) the Ark SDK refreshes its local database | 60 |
FULMINE_DISABLE_TELEMETRY |
Opt out of telemetry logs | true |
FULMINE_PROFILING_ENABLED |
Expose a pprof server on :6060 |
false |
FULMINE_OTEL_COLLECTOR_URL |
OpenTelemetry collector endpoint (enables OTel metrics/traces) | Not set |
FULMINE_OTEL_PUSH_INTERVAL |
OpenTelemetry push interval, in seconds | 10 |
FULMINE_PYROSCOPE_URL |
Pyroscope server URL for continuous profiling (requires OTel) | Not set |
When using Docker, you can set these variables using the -e flag:
docker run -d \
--name fulmine \
-p 7000:7000 \
-p 7001:7001 \
-e FULMINE_ARK_SERVER="https://server.example.com" \
-e FULMINE_ESPLORA_URL="https://mempool.space/api" \
-e FULMINE_UNLOCKER_TYPE="file" \
-e FULMINE_UNLOCKER_FILE_PATH="/app/password.txt" \
-v fulmine-data:/app/data \
-v /path/to/password.txt:/app/password.txt \
ghcr.io/arklabshq/fulmine:latestFulmine supports automatic wallet unlocking on startup, which is useful for unattended operation or when running as a service (for example, a delegate). Two methods are available:
-
File-based unlocker: Reads the wallet password from a file
FULMINE_UNLOCKER_TYPE=file FULMINE_UNLOCKER_FILE_PATH=/path/to/password/file -
Environment-based unlocker: Uses a password directly from an environment variable
FULMINE_UNLOCKER_TYPE=env FULMINE_UNLOCKER_PASSWORD=your_wallet_password
Auto-unlock only runs if a wallet already exists; you must create the wallet once first.
- For file-based unlocking, use appropriate file permissions (chmod 600)
- For environment-based unlocking, be cautious about environment variable visibility
- Consider using Docker secrets or similar tools in production environments
A delegate is a Fulmine instance that refreshes other users' VTXOs on their behalf, so their funds don't expire while they are offline. Clients submit a signed intent plus pre-signed forfeit transactions; the delegate stores them and, when a batch starts on the Ark server close to the VTXOs' expiry, joins the batch to refresh them.
- The delegate is disabled by default. Enable it with
FULMINE_DELEGATE_ENABLED=true. - A delegate needs a created and unlocked wallet. The delegate service starts when the wallet is unlocked and stops when it is locked, and it signs batch transactions with the wallet's key. For unattended operation, configure auto-unlock.
- The delegate listens on its own port,
FULMINE_DELEGATE_PORT(default7002), which must differ from the gRPC and HTTP ports. - Optionally set
FULMINE_DELEGATE_FEE(satoshis) to require a service fee; clients must pay at least this amount to the delegate's address in their intent.
docker run -d \
--name fulmine-delegate \
-p 7000:7000 \
-p 7001:7001 \
-p 7002:7002 \
-e FULMINE_DELEGATE_ENABLED=true \
-e FULMINE_DELEGATE_FEE=1000 \
-e FULMINE_ARK_SERVER="https://server.example.com" \
-e FULMINE_UNLOCKER_TYPE=file \
-e FULMINE_UNLOCKER_FILE_PATH=/app/password.txt \
-v fulmine-data:/app/data \
-v /path/to/password.txt:/app/password.txt \
ghcr.io/arklabshq/fulmine:latestCreate and unlock the wallet once (see Wallet Setup & Basic Usage); after that, auto-unlock brings the delegate back up on every restart.
The delegate exposes two public endpoints (no macaroon required) on the delegate port (7002 by default). Note that these are served at the root path, not under /api:
GET /v1/delegate/infoβ the delegate's pubkey, fee and fee addressPOST /v1/delegateβ submit a delegation request
See the Delegate API section for request/response details. To inspect the status of submitted delegation tasks, clients use the authenticated ListDelegates endpoint on the main API (GET /api/v1/delegates).
Fulmine protects its gRPC and REST endpoints with macaroons, and authentication is enabled by default.
- When you create or unlock a wallet, Fulmine bakes an admin macaroon at
<datadir>/macaroons/admin.macaroon(its root key is stored in<datadir>/macaroons/macaroons.db). Theadmin.macaroongrants access to every protected method. - REST: send the macaroon, hex-encoded, in the
X-Macaroonheader. - gRPC: send the macaroon, hex-encoded, in the
macaroonmetadata field.
Get the hex string and call a protected endpoint:
# Binary install (default datadir)
MACAROON=$(xxd -p ~/.fulmine/macaroons/admin.macaroon | tr -d '\n')
# Docker
MACAROON=$(docker exec fulmine xxd -p /app/data/macaroons/admin.macaroon | tr -d '\n')
curl -X GET http://localhost:7001/api/v1/balance -H "X-Macaroon: $MACAROON"The following endpoints are public (no macaroon required):
- Wallet lifecycle:
genseed,create,unlock,lock,auth,status,password/change,wallet/restore - The Delegate API (
/v1/delegate/infoand/v1/delegate)
All other endpoints β balance, addresses, sending, VHTLCs, notifications, chain swaps, ListDelegates, etc. β require the macaroon.
To disable authentication entirely (e.g. for local development), set FULMINE_NO_MACAROONS=true.
β οΈ Do not expose an instance with authentication disabled to the public internet. Even with macaroons enabled, only expose the interfaces you need, and prefer a trusted network or a reverse proxy that terminates TLS (Fulmine does not terminate TLS itself yet). While the wallet seed is encrypted at rest using AES-256 with your password, the API grants full control of the wallet to any holder of the admin macaroon.
Fulmine provides the following interfaces:
- Web UI β available at http://localhost:7001 by default
- REST API β available under http://localhost:7001/api (e.g.
GET /api/v1/wallet/status) - gRPC Service β available at
localhost:7000 - Delegate API β when enabled, available at
localhost:7002(gRPC and REST on the same port). See Running as a Delegate.
REST paths mirror the gRPC method bindings but are prefixed with /api. The examples below use the REST API.
Before using any wallet-dependent feature, you need to set up and unlock your wallet. The wallet lifecycle endpoints are public, but every endpoint in this section after unlock requires the macaroon (omitted below for brevity β add -H "X-Macaroon: $MACAROON").
-
Generate Seed
Returns a new key in both hex and Nostr
nsecform.curl -X GET http://localhost:7001/api/v1/wallet/genseed
-
Create Wallet
Password must:
- Be 8 chars or longer
- Have at least one number
- Have at least one special char
Private key supported formats:
- 64 chars hexadecimal
- Nostr nsec (NIP-19)
curl -X POST http://localhost:7001/api/v1/wallet/create \ -H "Content-Type: application/json" \ -d '{"privateKey": "<hex or nsec>", "password": "<strong password>", "serverUrl": "https://server.example.com"}' -
Unlock Wallet
curl -X POST http://localhost:7001/api/v1/wallet/unlock \ -H "Content-Type: application/json" \ -d '{"password": "<strong password>"}' -
Lock Wallet
Locks the wallet. Takes no parameters.
curl -X POST http://localhost:7001/api/v1/wallet/lock \ -H "Content-Type: application/json" -
Get Wallet Status
curl -X GET http://localhost:7001/api/v1/wallet/status
Returns:
{ "initialized": <bool>, "synced": <bool>, "unlocked": <bool> } -
Get Arkade Address
curl -X GET http://localhost:7001/api/v1/address
Returns:
{ "address": "<ark address>", "pubkey": "<hex>" } -
Get Onboard Address
Returns an onchain address to board the requested amount into Ark.
curl -X POST http://localhost:7001/api/v1/onboard \ -H "Content-Type: application/json" \ -d '{"amount": <amount in sats>}' -
Send funds offchain
curl -X POST http://localhost:7001/api/v1/send/offchain \ -H "Content-Type: application/json" \ -d '{"address": "<ark address>", "amount": <amount in sats>}' -
Send funds onchain
curl -X POST http://localhost:7001/api/v1/send/onchain \ -H "Content-Type: application/json" \ -d '{"address": "<bitcoin address>", "amount": <amount in sats>}'
This is only a subset of the wallet/service API. Other endpoints include balance, transaction history, settlement, invoices (Lightning), chain swaps and VTXO queries β see the proto and OpenAPI specs.
Note: The Notification API does not need wallet keys to function, but β like all
/apiendpoints β it is macaroon-protected by default. With authentication enabled you must have created/unlocked a wallet to obtainadmin.macaroon(or setFULMINE_NO_MACAROONS=true).
Fulmine can track off-chain addresses on behalf of external services and deliver notifications whenever funds are received or spent.
-
Subscribe to Addresses
Ask Fulmine to watch one or more off-chain addresses.
curl -X POST http://localhost:7001/api/v1/subscribe \ -H "Content-Type: application/json" \ -d '{"addresses": ["<ark address>", "<ark address>"]}' -
Unsubscribe from Addresses
Stop watching one or more addresses.
curl -X POST http://localhost:7001/api/v1/unsubscribe \ -H "Content-Type: application/json" \ -d '{"addresses": ["<ark address>"]}' -
Stream Notifications
Open a server-sent event stream to receive real-time notifications for all subscribed addresses. Each event contains the affected addresses, newly received VTXOs (
newVtxos), and spent VTXOs (spentVtxos).curl -X GET http://localhost:7001/api/v1/notifications
Note: Wallet setup is required before using the VHTLC APIs, and these endpoints are macaroon-protected.
Virtual Hash Time-Locked Contracts (VHTLCs) are Arkade-native HTLCs that live off-chain. They enable atomic swaps and conditional payments without touching the base layer.
-
Create VHTLC
Computes a VHTLC address from:
- a preimage hash
- exactly one of
senderPubkeyorreceiverPubkeyβ fulmine supplies the missing key from one of its internal wallets (depending on whether it funds or claims the VHTLC). Setting both, or neither, is rejected. - optional locktimes. If not provided, fulmine uses the following defaults:
refundLocktime: an absolute locktime after which the sender can refund the VHTLC off-chain. Defaults to 24 hours from creation.unilateralClaimDelay: how long the receiver must wait to claim on-chain after the VHTLC is unrolled. Default 512 seconds.unilateralRefundDelay: how long the sender and the counterparty must wait to refund collaboratively on-chain after unroll. Default 1024 seconds.unilateralRefundWithoutReceiverDelay: how long the sender must wait to refund alone on-chain after unroll. Default 2048 blocks (note: this default is block-denominated, not time).
Relative locktimes are objects of the form
{"type": "LOCKTIME_TYPE_SECOND" | "LOCKTIME_TYPE_BLOCK", "value": <number>}.curl -X POST http://localhost:7001/api/v1/vhtlc \ -H "Content-Type: application/json" \ -d '{ "preimageHash": "<hex preimage hash>", "senderPubkey": "<hex sender pubkey>", "refundLocktime": 1750000000, "unilateralClaimDelay": {"type": "LOCKTIME_TYPE_SECOND", "value": 512}, "unilateralRefundDelay": {"type": "LOCKTIME_TYPE_SECOND", "value": 1024}, "unilateralRefundWithoutReceiverDelay": {"type": "LOCKTIME_TYPE_BLOCK", "value": 2048} }'Returns: VHTLC
id,address,claimPubkey,refundPubkey,serverPubkey,swapTree, and the resolved locktime values. Theidis the sha256 hash ofpreimageHash+ sender EC pubkey + receiver EC pubkey. -
List VHTLCs
Returns VTXOs at the VHTLC addresses identified by their
vhtlcIds.curl -X GET "http://localhost:7001/api/v1/vhtlcs?vhtlcIds=id1&vhtlcIds=id2" -
List a single VHTLC
Returns the VTXOs at the VHTLC address identified by its
vhtlcId.curl -X GET "http://localhost:7001/api/v1/vhtlc?vhtlcId=id1" -
Claim VHTLC
Claims a VHTLC by revealing the preimage. Moves the funds into a regular VTXO.
curl -X POST http://localhost:7001/api/v1/vhtlc/claim \ -H "Content-Type: application/json" \ -d '{"vhtlcId": "<vhtlc id>", "preimage": "<hex preimage>"}'Returns:
{ "redeemTxid": "<txid>" } -
Settle VHTLC
Settles a VHTLC via either the claim path (reveal preimage) or the collaborative refund path (delegate params).
Claim path:
curl -X POST http://localhost:7001/api/v1/vhtlc/settle \ -H "Content-Type: application/json" \ -d '{"vhtlcId": "<vhtlc id>", "claim": {"preimage": "<hex preimage>"}}'Refund path:
curl -X POST http://localhost:7001/api/v1/vhtlc/settle \ -H "Content-Type: application/json" \ -d '{ "vhtlcId": "<vhtlc id>", "refund": { "delegateParams": { "signedIntentProof": "<base64>", "intentMessage": "<json string>", "partialForfeitTx": "<base64 psbt>" } } }'Returns:
{ "txid": "<txid>" } -
Refund VHTLC Without Receiver
Unilaterally refunds a VHTLC after the timeout has expired, without requiring the receiver's cooperation.
curl -X POST http://localhost:7001/api/v1/vhtlc/refundWithoutReceiver \ -H "Content-Type: application/json" \ -d '{"vhtlcId": "<vhtlc id>"}'Returns:
{ "redeemTxid": "<txid>" }
The first two endpoints are served on the delegate port (7002 by default), at the root path, and are public (no macaroon). They are only available when the delegate is enabled. ListDelegates is part of the main, authenticated API.
-
Get Delegate Info
Returns the delegate's pubkey (to include in VTXO scripts), service fee, and fee address.
curl -X GET http://localhost:7002/v1/delegate/info
Returns:
{ "pubkey": "<hex>", "fee": "<sats>", "delegateAddress": "<ark address>", "delegatorAddress": "<ark address>" }delegatorAddressis a legacy alias ofdelegateAddress(same value) and will be deprecated. -
Delegate
Submit a delegation request.
intent.messageis a stringified Ark intent (RegisterMessage) describing the VTXOs to refresh, andintent.proofis the partially signed intent transaction (base64 PSBT).forfeitTxsare partially signed forfeit transactions (base64 PSBT), one per VTXO input. SetrejectReplacetotrueto fail rather than replace an existing pending task that shares an input.curl -X POST http://localhost:7002/v1/delegate \ -H "Content-Type: application/json" \ -d '{ "intent": { "message": "<stringified RegisterMessage>", "proof": "<base64 psbt>" }, "forfeitTxs": ["<base64 psbt>"], "rejectReplace": false }'Returns an empty object
{}on success. -
List Delegates
Part of the main API (authenticated, on port
7001). Returns delegate tasks filtered by status, paginated. Thestatusparameter is required and must be one ofpending,completed,failedorcancelled.limitdefaults to100(max1000).curl -X GET "http://localhost:7001/api/v1/delegates?status=pending&limit=10&offset=0" \ -H "X-Macaroon: $MACAROON"
Note: Replace the host and ports above with wherever your Fulmine is running.
The examples above cover the most common operations. For the complete request/response schemas of every endpoint, see:
- Protobuf definitions:
api-spec/protobuf/fulmine/v1/ - OpenAPI / Swagger specs:
api-spec/openapi/swagger/fulmine/v1/
To get started with fulmine development you need Go 1.26.2 or higher and Node.js 18.17.1 or higher (the Docker image builds the web assets with Node 22).
git clone https://github.com/ArkLabsHQ/fulmine.git
cd fulmine
go mod download
make runNow navigate to http://localhost:7001/ to see the web UI.
Run all unit tests:
make testRun integration tests:
make build-test-env
make setup-test-env
make integrationtest
make down-test-envWe welcome contributions to fulmine! Here's how you can help:
- Fork the repository and create your branch from
master - Install dependencies:
go mod download - Make your changes and ensure tests pass:
make test - Run the linter to ensure code quality:
make lint - Submit a pull request
For major changes, please open an issue first to discuss what you would like to change.
The Makefile contains several useful commands for development:
make run: Run in development modemake build: Build the binary for your platformmake test: Run unit testsmake lint: Lint the codebasemake proto: Generate protobuf stubs (requires Docker)
If you encounter any issues or have questions, please file an issue on our GitHub Issues page.
We take the security of Ark seriously. If you discover a security vulnerability, we appreciate your responsible disclosure.
Currently, we do not have an official bug bounty program. However, we value the efforts of security researchers and will consider offering appropriate compensation for significant, responsibly disclosed vulnerabilities.
This project is licensed under the MIT License - see the LICENSE file for details.
