The official Go SDK for ln.bot — Bitcoin for AI Agents.
Give your AI agents, apps, and services access to Bitcoin over the Lightning Network. Create wallets, send and receive sats, and get real-time payment notifications.
client := lnbot.New("uk_...")
w := client.Wallet("wal_...")
invoice, _ := w.Invoices.Create(ctx, &lnbot.CreateInvoiceParams{
Amount: 1000,
Memo: lnbot.Ptr("Coffee"),
})ln.bot also ships a TypeScript SDK, Python SDK, Rust SDK, CLI, and MCP server.
go get github.com/lnbotdev/go-sdkpackage main
import (
"context"
"fmt"
lnbot "github.com/lnbotdev/go-sdk"
)
func main() {
ctx := context.Background()
// Register (no auth needed)
client := lnbot.New("")
account, _ := client.Register(ctx)
fmt.Println("User key:", account.PrimaryKey)
fmt.Println("Recovery:", account.RecoveryPassphrase)
// Create a wallet
authed := lnbot.New(account.PrimaryKey)
wallet, _ := authed.Wallets.Create(ctx)
fmt.Println("Wallet:", wallet.WalletID)
fmt.Println("Address:", wallet.Address)
}client := lnbot.New("uk_...")
w := client.Wallet("wal_...")
invoice, _ := w.Invoices.Create(ctx, &lnbot.CreateInvoiceParams{
Amount: 1000,
Memo: lnbot.Ptr("Payment for task #42"),
})
fmt.Println(invoice.Bolt11)Event streams work with a user key or a wallet key:
events, errs := w.Invoices.Watch(ctx, invoice.Number, nil)
for event := range events {
if event.Event == "settled" {
fmt.Println("Paid!")
}
}
if err := <-errs; err != nil {
log.Fatal(err)
}event.Event is settled, expired, or timeout when the wait runs out first (Data is then the current state). The API sends event and webhook payloads in snake_case (actual_fee, created_at); the SDK converts them, so event.Data and WalletEvent.Data decode into the usual types. A webhook handler reads the raw snake_case JSON.
An expired event is not always final: an invoice paid just before it expired can still turn settled. Check it again with Invoices.Get, or keep listening for invoice.settled on the wallet event stream or a webhook.
payment, _ := w.Payments.Create(ctx, &lnbot.CreatePaymentParams{
Target: "alice@ln.bot",
Amount: lnbot.Ptr(int64(500)),
})Payments between ln.bot wallets are free. A payment out to Lightning pays a service fee of 0.25% (minimum 1 sat) and the network fee, which is the Lightning routing fee. Leave MaxFee nil: the server caps the network fee at 1% of the amount (minimum 20 sats) and holds only the estimate plus a small buffer while the payment is in flight.
q, _ := w.Payments.ResolveWithAmount(ctx, "alice@example.com", 250000)
if q.FeeWarning != nil {
log.Fatal(*q.FeeWarning) // paying without MaxFee would be rejected
}
fmt.Printf("network fee ~%d, service fee %d, total %d\n", *q.NetworkFeeEstimate, *q.ServiceFee, *q.Total)Resolve(ctx, target) without an amount prices a BOLT11 invoice at its own amount. For a Lightning address or LNURL it returns MaxSendable, the most the wallet can send there now after fees, which is how to send everything:
res, _ := w.Payments.Resolve(ctx, "alice@example.com")
if res.MaxSendable != nil && *res.MaxSendable > 0 {
w.Payments.Create(ctx, &lnbot.CreatePaymentParams{
Target: "alice@example.com",
Amount: res.MaxSendable,
})
}Pricing a Lightning address or LNURL fetches an invoice from the recipient, so call it once the amount is settled. A payment that can't be sent fails quickly, and its funds are released at once: within about half a minute if the Lightning service refuses it, or after 10 minutes of automatic retries for a temporary problem (PaymentStageRetrying, until RetryUntil). If its invoice runs out first, it fails with Invoice expired before it could be paid. A payment stays processing longer only when its funds are already on the Lightning network: it settles or fails as soon as the network does, usually within seconds, and is never refunded while it could still be paid. Show FailureReason as is; when it ends in "Please try again.", sending the payment again later can work.
A payment carries Stage, where it is more precisely than Status, and StatusDetail, a sentence to show the user as is:
Stage |
With Status |
StatusDetail |
|---|---|---|
queued |
pending |
Waiting to be sent. |
retrying |
pending |
Not sent yet; retrying for a few minutes. If it still can't be sent, it fails and the funds are released. |
sending |
pending or processing |
Being sent. Between ln.bot wallets: Being transferred to the recipient's wallet. |
in_flight |
processing |
Sent; the Lightning network is completing it, usually within seconds. |
confirming |
processing |
Checking with the Lightning network whether it went out. The funds stay on hold until then. |
on_hold |
processing |
Interrupted by an earlier version of the service and on hold until it is reviewed. The funds are held, not lost. |
settled |
settled |
nil |
failed |
failed |
The failure reason |
p, _ := w.Payments.Get(ctx, payment.Number)
switch p.Stage {
case lnbot.PaymentStageRetrying:
fmt.Println(*p.StatusDetail, "until", p.RetryUntil.Local().Format("15:04"))
case lnbot.PaymentStageSettled, lnbot.PaymentStageFailed:
// final
default:
// A stage this version doesn't know, or an API that doesn't send one:
// go by p.Status.
}A retrying payment carries RetryUntil: if it still can't be sent by then, it fails within about a minute and the funds are released. A payment can go from processing back to pending when it is retried, so treat both as in progress. More stages may be added, so for one you don't know, go by Status. Payments.Watch reports the stage too, on its timeout event.
Whether ln.bot is working right now, and over the last 24 hours, 7, 30 or 90 days. No API key is needed:
status, _ := lnbot.New("").Status(ctx)
fmt.Println(status.Status) // operational, degraded, outage or unknown
if status.Notice != nil {
fmt.Println(status.Notice.Message)
}
history, _ := lnbot.New("").StatusHistory(ctx, &lnbot.StatusHistoryParams{Range: lnbot.StatusRange7d})
fmt.Println(*history.Summary.Uptime.Overall.Operational, "% operational")Both calls share the API's limit for public routes, 10 requests a minute per IP; over it they return a *lnbot.TooManyRequestsError. A history bucket with Samples 0 wasn't measured: its percentiles and gauges are nil. States, notice levels, stages and ranges may gain new values, so don't fail on one you don't know.
wal, _ := w.Get(ctx)
fmt.Printf("%d sats available\n", wal.Available)wallets, _ := client.Wallets.List(ctx)
for _, w := range wallets {
fmt.Printf("%s — %s\n", w.WalletID, w.Name)
}ln.bot uses two key types:
| Key type | Prefix | Scope | Created by |
|---|---|---|---|
| User key | uk_ |
All wallets under the account | Register() |
| Wallet key | wk_ |
Single wallet only | wallet.Key.Create() |
Use user keys for most operations. Use wallet keys to give a service or an agent access to a single wallet.
w := client.Wallet("wal_...")
// Create a challenge (server side)
challenge, _ := w.L402.CreateChallenge(ctx, &lnbot.CreateL402ChallengeParams{
Amount: 100,
Description: lnbot.Ptr("API access"),
})
// Pay the challenge (client side)
result, _ := w.L402.Pay(ctx, &lnbot.PayL402Params{
WwwAuthenticate: challenge.WwwAuthenticate,
})
// Verify a token (server side, stateless)
v, _ := w.L402.Verify(ctx, &lnbot.VerifyL402Params{
Authorization: *result.Authorization,
})
fmt.Println(v.Valid)import "errors"
wal, err := client.Wallet("wal_...").Get(ctx)
if err != nil {
// Match any API error
var apiErr *lnbot.APIError
if errors.As(err, &apiErr) {
fmt.Println(apiErr.StatusCode, apiErr.Message)
}
// Match specific error types
var notFound *lnbot.NotFoundError
var badReq *lnbot.BadRequestError
var conflict *lnbot.ConflictError
var tooMany *lnbot.TooManyRequestsError
switch {
case errors.As(err, ¬Found):
// 404
case errors.As(err, &badReq):
// 400
case errors.As(err, &conflict):
// 409
case errors.As(err, &tooMany):
// 429: the public routes allow 10 requests a minute per IP
default:
// other error
}
}client := lnbot.New("uk_...",
lnbot.WithBaseURL("https://api.ln.bot"),
lnbot.WithHTTPClient(customHTTPClient),
)| Method | Description |
|---|---|
Register(ctx) |
Create a new account (no auth) |
Me(ctx) |
Get authenticated identity |
Status(ctx) |
Service status right now (no auth) |
StatusHistory(ctx, params) |
Service status over 24h, 7d, 30d or 90d (no auth) |
Wallet(id) |
Get a wallet handle |
Wallets.Create(ctx) |
Create a new wallet |
Wallets.List(ctx) |
List all wallets |
Keys.Rotate(ctx, slot) |
Rotate user key (0=primary, 1=secondary) |
Invoices.CreateForWallet(ctx, params) |
Public invoice by wallet ID |
Invoices.CreateForAddress(ctx, params) |
Public invoice by Lightning address |
Backup.Recovery(ctx) |
Get recovery passphrase |
Backup.PasskeyBegin(ctx) |
Start passkey registration |
Backup.PasskeyComplete(ctx, params) |
Complete passkey registration |
Restore.Recovery(ctx, params) |
Restore via passphrase |
Restore.PasskeyBegin(ctx) |
Start passkey auth |
Restore.PasskeyComplete(ctx, params) |
Complete passkey restore |
| Method | Description |
|---|---|
Get(ctx) |
Get wallet details |
Update(ctx, params) |
Update wallet (rename) |
Key.Create(ctx) |
Create wallet key (max 1) |
Key.Get(ctx) |
Get wallet key info |
Key.Delete(ctx) |
Revoke wallet key |
Key.Rotate(ctx) |
Rotate wallet key |
Invoices.Create(ctx, params) |
Create invoice |
Invoices.List(ctx, params) |
List invoices |
Invoices.Get(ctx, number) |
Get invoice by number |
Invoices.Watch(ctx, number, timeout) |
SSE stream until settled, expired or timeout |
Payments.Create(ctx, params) |
Send payment |
Payments.List(ctx, params) |
List payments |
Payments.Get(ctx, number) |
Get payment by number |
Payments.Resolve(ctx, target) |
Inspect target before paying |
Payments.ResolveWithAmount(ctx, target, amount) |
Price a payment: fees and total |
Payments.Watch(ctx, number, timeout) |
SSE stream until settled, failed or timeout |
Addresses.Create(ctx, params) |
Create Lightning address |
Addresses.List(ctx) |
List addresses |
Addresses.Delete(ctx, address) |
Delete address |
Addresses.Transfer(ctx, address, params) |
Transfer address |
Transactions.List(ctx, params) |
List transactions |
Webhooks.Create(ctx, params) |
Register webhook |
Webhooks.List(ctx) |
List webhooks |
Webhooks.Delete(ctx, id) |
Delete webhook |
Events.Stream(ctx) |
SSE stream of all wallet events |
L402.CreateChallenge(ctx, params) |
Create L402 challenge |
L402.Verify(ctx, params) |
Verify L402 token |
L402.Pay(ctx, params) |
Pay L402 challenge |
- Zero dependencies — stdlib
net/http+encoding/jsononly - Context-first — every method takes
context.Contextas the first argument - Wallet-scoped —
client.Wallet(id)returns a handle with all sub-resources - Typed errors —
BadRequestError,NotFoundError,ConflictError,UnauthorizedError,ForbiddenError - Generic helpers —
Ptr[T]for optional fields - SSE support —
Watchreturns channels for real-time events
- Go 1.21+
- Get your API key at ln.bot
- ln.bot — website
- Documentation
- GitHub
- pkg.go.dev
- TypeScript SDK · npm
- Python SDK · pypi
- Rust SDK · crates.io · docs.rs
MIT