Skip to content

gl-sdk: Split input handling into sync parse_input + async resolve_input#707

Open
angelix wants to merge 1 commit into2026w15-lnurlfrom
ave-lnurl-improvements
Open

gl-sdk: Split input handling into sync parse_input + async resolve_input#707
angelix wants to merge 1 commit into2026w15-lnurlfrom
ave-lnurl-improvements

Conversation

@angelix
Copy link
Copy Markdown
Contributor

@angelix angelix commented Apr 26, 2026

Two entry points with explicit cost contracts replace the single
async parse_input that absorbed both classification and HTTP
resolution:

  • parse_input(input) -> ParsedInput — synchronous, offline, no
    I/O. Identifies BOLT11 invoices, node IDs, LNURL bech32 strings
    (decoded to their underlying URL), and Lightning Addresses
    (returned as the unparsed user@host form). LNURL inputs are
    classified but NOT fetched.

  • resolve_input(input) -> ResolvedInput — asynchronous,
    network-touching. Internally calls parse_input, then for the
    LNURL / Lightning Address branches performs the HTTP GET to the
    service endpoint and returns typed pay or withdraw request data.

Wallets that want offline classification (clipboard validation,
invoice sanity checks on the send screen, debounced input
classification as the user types) call parse_input. Wallets handling
a QR scan that should proceed straight to a pay/withdraw screen call
resolve_input.

Public surface

  • ParsedInput { Bolt11, NodeId, LnUrl { url }, LnUrlAddress { address } }
    — offline classification result.
  • ResolvedInput { Bolt11, NodeId, LnUrlPay { data }, LnUrlWithdraw { data } }
    — fully-resolved result.
  • parse_input(input) — sync, no I/O.
  • resolve_input(input) — async, may HTTP, delegates to parse_input
    internally.

Foreign bindings mirror the same split:

  • Python: glsdk.parse_input (sync) + glsdk.resolve_input (async).
  • TypeScript: parseInput(input): ParsedInput +
    resolveInput(input): Promise.
  • Kotlin: parseInput is sync (no runBlocking / suspend fun for
    the offline classification anymore); resolveInput is suspend.

Tests

  • 19 Rust unit tests (was 9): split coverage of parse_input
    (BOLT11, NodeId, LNURL bech32 decoding, Lightning Address
    classification, all error cases) and resolve_input pass-through
    paths (BOLT11/NodeId without HTTP, error-before-HTTP for invalid
    LNURL).
  • gl-sdk Python: test_parse_input.py rewritten for the sync API,
    with new tests for offline LNURL/Lightning-Address classification.
    test_lnurl.py switched its HTTP-resolving cases to resolve_input
    and ResolvedInput.LN_URL_*.
  • gl-sdk-android: ParseInputTest.kt / LnurlParseTest.kt — runBlocking
    removed, parseInput called synchronously, with new offline LNURL
    classification cases.
  • gl-sdk-napi: parse-input.spec.ts — sync calls (expect(() => …) .toThrow() instead of await expect(...).rejects.toThrow()),
    with new LNURL bech32 / Lightning Address classification cases.

Verified: cargo build -p gl-sdk -p gl-sdk-node clean; cargo test
-p gl-sdk --lib 19/19 pass; Python and TypeScript bindings
regenerated and verified to expose both ParsedInput / ResolvedInput
types and both parse_input / resolve_input free fns.

@cdecker
Copy link
Copy Markdown
Collaborator

cdecker commented Apr 27, 2026

I thought the whole point of parse_input was to be guaranteed to be a local operation, so people would not worry about calling it. With the introduction of the resolution this isn't as clear cut.

Do you think a parse_input and a resolve_input (which internally delegates BOLT11 and so on to parse_input, but also resolves things over the net) would be a better split maybe?

@angelix angelix force-pushed the ave-lnurl-improvements branch 2 times, most recently from 2158773 to ab9ea0a Compare April 27, 2026 15:49
@angelix angelix changed the title gl-sdk: Make parse_input async and absorb LNURL resolution gl-sdk: Split input handling into sync parse_input + async resolve_input Apr 27, 2026
@angelix
Copy link
Copy Markdown
Contributor Author

angelix commented Apr 27, 2026

I thought the whole point of parse_input was to be guaranteed to be a local operation, so people would not worry about calling it. With the introduction of the resolution this isn't as clear cut.

Do you think a parse_input and a resolve_input (which internally delegates BOLT11 and so on to parse_input, but also resolves things over the net) would be a better split maybe?

Split into 2 in ab9ea0a

  • sync & offline parse_input
  • async resolve_input

Two entry points with explicit cost contracts replace the single
async parse_input that absorbed both classification and HTTP
resolution:

  * parse_input(input) -> ParsedInput — synchronous, offline, no
    I/O. Identifies BOLT11 invoices, node IDs, LNURL bech32 strings
    (decoded to their underlying URL), and Lightning Addresses
    (returned as the unparsed `user@host` form). LNURL inputs are
    classified but NOT fetched.

  * resolve_input(input) -> ResolvedInput — asynchronous,
    network-touching. Internally calls parse_input, then for the
    LNURL / Lightning Address branches performs the HTTP GET to the
    service endpoint and returns typed pay or withdraw request data.

Wallets that want offline classification (clipboard validation,
invoice sanity checks on the send screen, debounced input
classification as the user types) call parse_input. Wallets handling
a QR scan that should proceed straight to a pay/withdraw screen call
resolve_input.

Public surface
- ParsedInput { Bolt11, NodeId, LnUrl { url }, LnUrlAddress { address } }
  — offline classification result.
- ResolvedInput { Bolt11, NodeId, LnUrlPay { data }, LnUrlWithdraw { data } }
  — fully-resolved result.
- parse_input(input) — sync, no I/O.
- resolve_input(input) — async, may HTTP, delegates to parse_input
  internally.

Foreign bindings mirror the same split:
- Python: glsdk.parse_input (sync) + glsdk.resolve_input (async).
- TypeScript: parseInput(input): ParsedInput +
  resolveInput(input): Promise<ResolvedInput>.
- Kotlin: parseInput is sync (no `runBlocking` / `suspend fun` for
  the offline classification anymore); resolveInput is suspend.

Tests
- 19 Rust unit tests (was 9): split coverage of parse_input
  (BOLT11, NodeId, LNURL bech32 decoding, Lightning Address
  classification, all error cases) and resolve_input pass-through
  paths (BOLT11/NodeId without HTTP, error-before-HTTP for invalid
  LNURL).
- gl-sdk Python: test_parse_input.py rewritten for the sync API,
  with new tests for offline LNURL/Lightning-Address classification.
  test_lnurl.py switched its HTTP-resolving cases to resolve_input
  and ResolvedInput.LN_URL_*.
- gl-sdk-android: ParseInputTest.kt / LnurlParseTest.kt — runBlocking
  removed, parseInput called synchronously, with new offline LNURL
  classification cases.
- gl-sdk-napi: parse-input.spec.ts — sync calls (`expect(() => …)
  .toThrow()` instead of `await expect(...).rejects.toThrow()`),
  with new LNURL bech32 / Lightning Address classification cases.

Verified: cargo build -p gl-sdk -p gl-sdk-node clean; cargo test
-p gl-sdk --lib 19/19 pass; Python and TypeScript bindings
regenerated and verified to expose both ParsedInput / ResolvedInput
types and both parse_input / resolve_input free fns.
@angelix angelix force-pushed the ave-lnurl-improvements branch from ab9ea0a to 6df08df Compare April 28, 2026 10:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants