Skip to content
lnbotdevPublic

About

The official Go SDK for ln.bot - Bitcoin for AI Agents.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

16 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ln.bot-go

Go Reference License: MIT

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.


Install

go get github.com/lnbotdev/go-sdk

Quick start

Register an account

package 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)
}

Receive sats

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)

Wait for payment (SSE)

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.

Send sats

payment, _ := w.Payments.Create(ctx, &lnbot.CreatePaymentParams{
    Target: "alice@ln.bot",
    Amount: lnbot.Ptr(int64(500)),
})

Price a payment before sending

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.

Follow a payment's progress

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.

Check the service status

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.

Check balance

wal, _ := w.Get(ctx)
fmt.Printf("%d sats available\n", wal.Available)

List wallets

wallets, _ := client.Wallets.List(ctx)
for _, w := range wallets {
    fmt.Printf("%s — %s\n", w.WalletID, w.Name)
}

Authentication

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.


L402 paywalls

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)

Error handling

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, &notFound):
        // 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
    }
}

Configuration

client := lnbot.New("uk_...",
    lnbot.WithBaseURL("https://api.ln.bot"),
    lnbot.WithHTTPClient(customHTTPClient),
)

API reference

Client (top-level)

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

Wallet handle

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

Features

  • Zero dependencies — stdlib net/http + encoding/json only
  • Context-first — every method takes context.Context as 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 — Watch returns channels for real-time events

Requirements

  • Go 1.21+
  • Get your API key at ln.bot

Links

Other SDKs

License

MIT

About

The official Go SDK for ln.bot - Bitcoin for AI Agents.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages