Skip to content

Commit 608e9b9

Browse files
committed
Add L402 paywall support, remove keys.ListAsync()
- Add L402Resource with CreateChallengeAsync(), VerifyAsync(), PayAsync() - Add L402 request/response models - Register L402 on ILnBotClient interface and LnBotClient - Remove Keys.ListAsync() — server endpoint removed - Remove ApiKeyResponse type - Bump version to 0.5.0 Made-with: Cursor
1 parent 53468fd commit 608e9b9

8 files changed

Lines changed: 161 additions & 26 deletions

File tree

README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,32 @@ using var client = new LnBotClient("key_...", new LnBotClientOptions
143143

144144
---
145145

146+
## L402 paywalls
147+
148+
```csharp
149+
// Create a challenge (server side)
150+
var challenge = await client.L402.CreateChallengeAsync(new CreateL402ChallengeRequest
151+
{
152+
Amount = 100,
153+
Description = "API access",
154+
ExpirySeconds = 3600,
155+
});
156+
157+
// Pay the challenge (client side)
158+
var result = await client.L402.PayAsync(new PayL402Request
159+
{
160+
WwwAuthenticate = challenge.WwwAuthenticate,
161+
});
162+
163+
// Verify a token (server side, stateless)
164+
var v = await client.L402.VerifyAsync(new VerifyL402Request
165+
{
166+
Authorization = result.Authorization!,
167+
});
168+
```
169+
170+
---
171+
146172
## Features
147173

148174
- **Zero dependencies**`System.Net.Http` + `System.Text.Json` only

src/LnBot/ILnBotClient.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ public interface ILnBotClient : IDisposable
2828
/// <summary>API key management.</summary>
2929
KeysResource Keys { get; }
3030

31+
/// <summary>L402 paywall authentication.</summary>
32+
L402Resource L402 { get; }
33+
3134
/// <summary>Real-time event stream.</summary>
3235
EventsResource Events { get; }
3336

src/LnBot/LnBot.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
<!-- NuGet package metadata -->
1212
<PackageId>LnBot</PackageId>
13-
<Version>0.4.0</Version>
13+
<Version>0.5.0</Version>
1414
<Authors>LnBot</Authors>
1515
<Description>The official .NET SDK for LnBot — Bitcoin for AI Agents. Create wallets, send and receive sats over the Lightning Network.</Description>
1616
<PackageTags>bitcoin;lightning;lightning-network;lnbot;sdk;payments;sats</PackageTags>

src/LnBot/LnBotClient.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ namespace LnBot;
1212
/// </summary>
1313
public sealed class LnBotClient : ILnBotClient
1414
{
15-
internal const string Version = "0.4.0";
15+
internal const string Version = "0.5.0";
1616
internal static readonly string DefaultBaseUrl = "https://api.ln.bot";
1717

1818
private static readonly JsonSerializerOptions JsonOptions = new()
@@ -46,6 +46,9 @@ public sealed class LnBotClient : ILnBotClient
4646
/// <summary>API key management.</summary>
4747
public KeysResource Keys { get; }
4848

49+
/// <summary>L402 paywall authentication.</summary>
50+
public L402Resource L402 { get; }
51+
4952
/// <summary>Real-time event stream.</summary>
5053
public EventsResource Events { get; }
5154

@@ -96,6 +99,7 @@ public LnBotClient(string? apiKey = null, LnBotClientOptions? options = null)
9699
Transactions = new TransactionsResource(this);
97100
Webhooks = new WebhooksResource(this);
98101
Keys = new KeysResource(this);
102+
L402 = new L402Resource(this);
99103
Events = new EventsResource(this);
100104
Backup = new BackupResource(this);
101105
Restore = new RestoreResource(this);

src/LnBot/Models/Requests.cs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,3 +121,46 @@ public sealed class PaginationParams
121121
public int? Limit { get; set; }
122122
public int? After { get; set; }
123123
}
124+
125+
// ---------------------------------------------------------------------------
126+
// L402
127+
// ---------------------------------------------------------------------------
128+
129+
public sealed class CreateL402ChallengeRequest
130+
{
131+
[JsonPropertyName("amount")]
132+
public required long Amount { get; set; }
133+
134+
[JsonPropertyName("description")]
135+
public string? Description { get; set; }
136+
137+
[JsonPropertyName("expirySeconds")]
138+
public int? ExpirySeconds { get; set; }
139+
140+
[JsonPropertyName("caveats")]
141+
public List<string>? Caveats { get; set; }
142+
}
143+
144+
public sealed class VerifyL402Request
145+
{
146+
[JsonPropertyName("authorization")]
147+
public required string Authorization { get; set; }
148+
}
149+
150+
public sealed class PayL402Request
151+
{
152+
[JsonPropertyName("wwwAuthenticate")]
153+
public required string WwwAuthenticate { get; set; }
154+
155+
[JsonPropertyName("maxFee")]
156+
public long? MaxFee { get; set; }
157+
158+
[JsonPropertyName("reference")]
159+
public string? Reference { get; set; }
160+
161+
[JsonPropertyName("wait")]
162+
public bool? Wait { get; set; }
163+
164+
[JsonPropertyName("timeout")]
165+
public int? Timeout { get; set; }
166+
}

src/LnBot/Models/Responses.cs

Lines changed: 61 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -182,24 +182,6 @@ public sealed class TransactionResponse
182182
public DateTimeOffset? CreatedAt { get; init; }
183183
}
184184

185-
public sealed class ApiKeyResponse
186-
{
187-
[JsonPropertyName("id")]
188-
public required string Id { get; init; }
189-
190-
[JsonPropertyName("name")]
191-
public required string Name { get; init; }
192-
193-
[JsonPropertyName("hint")]
194-
public required string Hint { get; init; }
195-
196-
[JsonPropertyName("createdAt")]
197-
public DateTimeOffset? CreatedAt { get; init; }
198-
199-
[JsonPropertyName("lastUsedAt")]
200-
public DateTimeOffset? LastUsedAt { get; init; }
201-
}
202-
203185
public sealed class RotateApiKeyResponse
204186
{
205187
[JsonPropertyName("key")]
@@ -301,3 +283,64 @@ public sealed class RestorePasskeyCompleteResponse
301283
[JsonPropertyName("secondaryKey")]
302284
public required string SecondaryKey { get; init; }
303285
}
286+
287+
// ---------------------------------------------------------------------------
288+
// L402
289+
// ---------------------------------------------------------------------------
290+
291+
public sealed class L402ChallengeResponse
292+
{
293+
[JsonPropertyName("macaroon")]
294+
public required string Macaroon { get; init; }
295+
296+
[JsonPropertyName("invoice")]
297+
public required string Invoice { get; init; }
298+
299+
[JsonPropertyName("paymentHash")]
300+
public required string PaymentHash { get; init; }
301+
302+
[JsonPropertyName("expiresAt")]
303+
public required DateTimeOffset ExpiresAt { get; init; }
304+
305+
[JsonPropertyName("wwwAuthenticate")]
306+
public required string WwwAuthenticate { get; init; }
307+
}
308+
309+
public sealed class VerifyL402Response
310+
{
311+
[JsonPropertyName("valid")]
312+
public required bool Valid { get; init; }
313+
314+
[JsonPropertyName("paymentHash")]
315+
public string? PaymentHash { get; init; }
316+
317+
[JsonPropertyName("caveats")]
318+
public List<string>? Caveats { get; init; }
319+
320+
[JsonPropertyName("error")]
321+
public string? Error { get; init; }
322+
}
323+
324+
public sealed class L402PayResponse
325+
{
326+
[JsonPropertyName("authorization")]
327+
public string? Authorization { get; init; }
328+
329+
[JsonPropertyName("paymentHash")]
330+
public required string PaymentHash { get; init; }
331+
332+
[JsonPropertyName("preimage")]
333+
public string? Preimage { get; init; }
334+
335+
[JsonPropertyName("amount")]
336+
public required long Amount { get; init; }
337+
338+
[JsonPropertyName("fee")]
339+
public long? Fee { get; init; }
340+
341+
[JsonPropertyName("paymentNumber")]
342+
public required int PaymentNumber { get; init; }
343+
344+
[JsonPropertyName("status")]
345+
public required string Status { get; init; }
346+
}

src/LnBot/Resources/KeysResource.cs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,6 @@ public sealed class KeysResource
1010
private readonly LnBotClient _client;
1111
internal KeysResource(LnBotClient client) => _client = client;
1212

13-
/// <summary>
14-
/// Lists all API keys (metadata only, not the key values).
15-
/// </summary>
16-
public Task<List<ApiKeyResponse>> ListAsync(CancellationToken cancellationToken = default)
17-
=> _client.GetAsync<List<ApiKeyResponse>>("/v1/keys", cancellationToken);
18-
1913
/// <summary>
2014
/// Rotates an API key. The old key is immediately invalidated.
2115
/// </summary>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
namespace LnBot.Resources;
2+
3+
using LnBot.Models;
4+
5+
/// <summary>L402 paywall authentication.</summary>
6+
public sealed class L402Resource
7+
{
8+
private readonly LnBotClient _client;
9+
internal L402Resource(LnBotClient client) => _client = client;
10+
11+
/// <summary>Creates an L402 challenge (invoice + macaroon) for paywall authentication.</summary>
12+
public Task<L402ChallengeResponse> CreateChallengeAsync(CreateL402ChallengeRequest request, CancellationToken cancellationToken = default)
13+
=> _client.PostAsync<L402ChallengeResponse>("/v1/l402/challenges", request, cancellationToken);
14+
15+
/// <summary>Verifies an L402 authorization token (stateless).</summary>
16+
public Task<VerifyL402Response> VerifyAsync(VerifyL402Request request, CancellationToken cancellationToken = default)
17+
=> _client.PostAsync<VerifyL402Response>("/v1/l402/verify", request, cancellationToken);
18+
19+
/// <summary>Pays an L402 challenge and returns a ready-to-use Authorization header.</summary>
20+
public Task<L402PayResponse> PayAsync(PayL402Request request, CancellationToken cancellationToken = default)
21+
=> _client.PostAsync<L402PayResponse>("/v1/l402/pay", request, cancellationToken);
22+
}

0 commit comments

Comments
 (0)