Skip to content

lnbotdev/csharp-l402

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

LnBot.L402

NuGet NuGet License: MIT

L402 Lightning payment middleware for .NET — paywall any API in one line. Built on ln.bot.

Two NuGet packages:

  • LnBot.L402 — Client-side. Auto-pay L402-protected APIs with any HttpClient. Works in console apps, background services, MAUI — anything with HttpClient.
  • LnBot.L402.AspNetCore — Server-side. Protect ASP.NET Core routes behind L402 paywalls with middleware, [L402] attributes, or endpoint filters.

Both packages are thin glue layers. All L402 logic — macaroon creation, signature verification, preimage checking — lives in the ln.bot API via the LnBot SDK. Zero crypto dependencies.


What is L402?

L402 is a protocol built on HTTP 402 Payment Required. It enables machine-to-machine micropayments over the Lightning Network:

  1. Client requests a protected resource
  2. Server returns 402 with a Lightning invoice and a macaroon token
  3. Client pays the invoice, obtains the preimage as proof of payment
  4. Client retries the request with Authorization: L402 <macaroon>:<preimage>
  5. Server verifies the token and grants access

L402 is ideal for API monetization, AI agent tool access, pay-per-request data feeds, and any scenario where you want instant, permissionless, per-request payments without subscriptions or API key provisioning.


Install

dotnet add package LnBot.L402.AspNetCore   # Server (includes client package)
dotnet add package LnBot.L402              # Client only (no ASP.NET Core dependency)

Server — Protect Routes with L402

Middleware pipeline

using LnBot;
using LnBot.L402;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton(new LnBotClient("key_..."));

var app = builder.Build();

app.UseL402Paywall("/api/premium", new L402Options
{
    Price = 10,
    Description = "API access",
});

app.MapGet("/api/premium/data", (HttpContext context) =>
{
    var l402 = context.GetL402();
    return Results.Ok(new { data = "premium content", paymentHash = l402?.PaymentHash });
});
app.MapGet("/api/free/health", () => Results.Ok(new { status = "ok" }));

app.Run();

Controller attribute

[ApiController]
[Route("api/[controller]")]
public class WeatherController : ControllerBase
{
    [L402(Price = 50, Description = "Weather forecast", ExpirySeconds = 3600, Caveats = new[] { "tier=pro" })]
    [HttpGet("forecast")]
    public IActionResult GetForecast()
        => Ok(new { forecast = "sunny" });
}

Minimal API endpoint filter

app.MapGet("/api/premium/data", () => Results.Ok(new { data = "premium" }))
   .AddEndpointFilter(new L402EndpointFilter(price: 10, description: "API access"));

Dynamic pricing

app.UseL402Paywall("/api/dynamic", new L402Options
{
    PriceFactory = context =>
    {
        if (context.Request.Path.StartsWithSegments("/api/dynamic/bulk"))
            return Task.FromResult(50);
        return Task.FromResult(5);
    }
});

Paywall options

Option Type Description
Price int Price in satoshis per request
PriceFactory Func<HttpContext, Task<int>>? Dynamic pricing callback (takes precedence over Price)
Description string? Invoice memo shown in wallets
ExpirySeconds int? Challenge expiry in seconds
Caveats List<string>? Macaroon caveats to attach

Accessing L402 data after verification

After a successful paywall check, use the GetL402() extension method to access the verification result:

app.MapGet("/api/premium/data", (HttpContext context) =>
{
    var l402 = context.GetL402();
    return Results.Ok(new
    {
        data = "premium content",
        paymentHash = l402?.PaymentHash,
        caveats = l402?.Caveats,
    });
});

Client — Auto-Pay L402 APIs

With ASP.NET Core DI

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton(new LnBotClient("key_..."));

builder.Services.AddHttpClient("paid-apis")
    .AddL402Handler(new L402ClientOptions
    {
        MaxPrice = 100,
        BudgetSats = 50_000,
        BudgetPeriod = BudgetPeriod.Day,
    });

var app = builder.Build();

app.MapGet("/proxy", async (IHttpClientFactory factory) =>
{
    var http = factory.CreateClient("paid-apis");
    var data = await http.GetStringAsync("https://api.example.com/premium/data");
    return Results.Ok(data);
});

Console app (no ASP.NET Core)

using LnBot;
using LnBot.L402;
using Microsoft.Extensions.DependencyInjection;

var services = new ServiceCollection();
services.AddSingleton(new LnBotClient("key_..."));
services.AddSingleton<ITokenStore, MemoryTokenStore>();
services.AddHttpClient("paid-apis").AddL402Handler();

var provider = services.BuildServiceProvider();
var http = provider.GetRequiredService<IHttpClientFactory>().CreateClient("paid-apis");

// Auto-pays any 402 responses transparently
var response = await http.GetStringAsync("https://api.example.com/premium/data");
Console.WriteLine(response);

Header Utilities

using LnBot.L402;

// Parse Authorization: L402 <macaroon>:<preimage>
var auth = L402Headers.ParseAuthorization("L402 mac_base64:preimage_hex");
// → (Macaroon: "mac_base64", Preimage: "preimage_hex")

// Parse WWW-Authenticate: L402 macaroon="...", invoice="..."
var challenge = L402Headers.ParseChallenge("L402 macaroon=\"abc\", invoice=\"lnbc1...\"");
// → (Macaroon: "abc", Invoice: "lnbc1...")

// Format headers
L402Headers.FormatAuthorization("mac", "pre");     // → "L402 mac:pre"
L402Headers.FormatChallenge("mac", "lnbc1...");    // → "L402 macaroon=\"mac\", invoice=\"lnbc1...\""

Custom Token Store

Implement ITokenStore for Redis, file system, or any persistence layer:

public class RedisTokenStore : ITokenStore
{
    public Task<L402Token?> GetAsync(string url) { /* ... */ }
    public Task SetAsync(string url, L402Token token) { /* ... */ }
    public Task DeleteAsync(string url) { /* ... */ }
}

// Register in DI
services.AddSingleton<ITokenStore, RedisTokenStore>();

How It Works

Server middleware makes two SDK calls:

  • client.L402.CreateChallengeAsync() — creates an invoice + macaroon when a client needs to pay
  • client.L402.VerifyAsync() — verifies an L402 authorization token when a client presents one

Client handler makes one SDK call:

  • client.L402.PayAsync() — pays a Lightning invoice and returns a ready-to-use Authorization header

Requirements

Related packages

Links

License

MIT

About

L402 Lightning payment middleware for .NET — paywall any ASP.NET Core API in one line

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages