Add k1 session token to ndebit for reliable request matching #23

Open
opened 2026-06-13 22:02:49 +00:00 by padreug · 3 comments
Owner

Migrated from aiolabs/lamassu-next#23 — opened by @padreug on 2026-01-31.\n\n## Problem

When an ATM displays an ndebit QR code and a customer scans it, the ATM receives the debit request via GetLiveDebitRequests. However, there's no way to reliably match the incoming request to the specific ATM session that generated the QR.

Current ndebit format:

ndebit:pubkey?relay=wss://...&pointer=atm

The pointer field must be a valid Lightning.Pub account ID (e.g., "atm"), not a session-specific value. This means all debit requests look identical from the ATM's perspective.

Current workaround: Amount-based matching — the ATM matches incoming requests by the sats amount. This works but has edge cases (two customers wanting the same amount simultaneously).

Proposed Solution

Add an optional k1 parameter to ndebit, following the LNURL-withdraw pattern:

ndebit:pubkey?relay=wss://...&pointer=atm&k1=abc123uniquetoken

Flow

  1. ATM generates unique k1 token per session
  2. ATM encodes k1 in ndebit QR
  3. Customer scans with ShockWallet
  4. ShockWallet includes k1 in the debit request event
  5. Lightning.Pub forwards k1 in GetLiveDebitRequests response
  6. ATM matches request by k1 — exact session identification

Changes Required

Component Change
CLINK spec Add optional k1 param to ndebit encoding
@lamassu/clink Update encodeNdebit() / decodeNdebit()
ShockWallet Parse k1 from ndebit, include in debit request
Lightning.Pub Forward k1 in GetLiveDebitRequests

Why k1?

  • Familiar pattern from LNURL spec
  • Single unique token, no ambiguity
  • Minimal spec change (one optional param)
  • Solves the session matching problem cleanly
> _Migrated from [aiolabs/lamassu-next#23](https://git.atitlan.io/aiolabs/lamassu-next/issues/23) — opened by @padreug on 2026-01-31._\n\n## Problem When an ATM displays an ndebit QR code and a customer scans it, the ATM receives the debit request via `GetLiveDebitRequests`. However, there's no way to reliably match the incoming request to the specific ATM session that generated the QR. **Current ndebit format:** ``` ndebit:pubkey?relay=wss://...&pointer=atm ``` The `pointer` field must be a valid Lightning.Pub account ID (e.g., `"atm"`), not a session-specific value. This means all debit requests look identical from the ATM's perspective. **Current workaround:** Amount-based matching — the ATM matches incoming requests by the sats amount. This works but has edge cases (two customers wanting the same amount simultaneously). ## Proposed Solution Add an optional `k1` parameter to ndebit, following the LNURL-withdraw pattern: ``` ndebit:pubkey?relay=wss://...&pointer=atm&k1=abc123uniquetoken ``` ### Flow 1. **ATM** generates unique `k1` token per session 2. **ATM** encodes `k1` in ndebit QR 3. **Customer** scans with ShockWallet 4. **ShockWallet** includes `k1` in the debit request event 5. **Lightning.Pub** forwards `k1` in `GetLiveDebitRequests` response 6. **ATM** matches request by `k1` — exact session identification ### Changes Required | Component | Change | |-----------|--------| | **CLINK spec** | Add optional `k1` param to ndebit encoding | | **@lamassu/clink** | Update `encodeNdebit()` / `decodeNdebit()` | | **ShockWallet** | Parse `k1` from ndebit, include in debit request | | **Lightning.Pub** | Forward `k1` in `GetLiveDebitRequests` | ### Why k1? - Familiar pattern from LNURL spec - Single unique token, no ambiguity - Minimal spec change (one optional param) - Solves the session matching problem cleanly ## Related - LNURL-withdraw uses `k1` for the same purpose: https://github.com/lnurl/luds/blob/luds/03.md - Current amount-based matching implementation in `apps/machine/src/services/lightning.ts`
Author
Owner

@padreug commented on 2026-02-14 (lamassu-next#23):

Server-side changes tracked in lightning-pub#7

> _@padreug commented on 2026-02-14 ([lamassu-next#23](https://git.atitlan.io/aiolabs/lamassu-next/issues/23#issuecomment-101)):_ Server-side changes tracked in [lightning-pub#7](https://git.atitlan.io/aiolabs/lightning-pub/issues/7)
Author
Owner

@padreug commented on 2026-03-01 (lamassu-next#23):

UI code removed pending k1 fix

CLINK ndebit has been disabled in the ATM UI until this issue is resolved. The QR now shows LNURL-withdraw only.

Removed from CashInView.vue

Mode selector (Universal/LNURL/CLINK toggle buttons):

<!-- Mode selector (web-ui only) -->
<div v-if="!isElectron" class="pt-4 text-center">
  <p class="text-xs text-muted-foreground mb-2">Scan with</p>
  <div class="flex flex-wrap gap-2 justify-center">
    <Button :variant="qrMode === 'unified' ? 'default' : 'outline'" size="sm" class="rounded-full px-4" @click="qrMode = 'unified'">Universal</Button>
    <Button :variant="qrMode === 'lnurl' ? 'default' : 'outline'" size="sm" class="rounded-full px-4" @click="qrMode = 'lnurl'">LNURL</Button>
    <Button :variant="qrMode === 'clink' ? 'default' : 'outline'" size="sm" class="rounded-full px-4" @click="qrMode = 'clink'">CLINK</Button>
  </div>
</div>

Unified QR value (combined lightning + clink):

const qrMode = ref<'unified' | 'lnurl' | 'clink'>('unified')

const unifiedQrValue = computed(() => {
  const ndebit = atmStore.context?.ndebitUri
  const lnurl = lnurlWithdraw.value
  if (lnurl && ndebit) return `lightning:${lnurl}\n${ndebit}`
  else if (lnurl) return `lightning:${lnurl}`
  else if (ndebit) return ndebit
  return null
})

const currentQrValue = computed(() => {
  switch (qrMode.value) {
    case 'unified': return unifiedQrValue.value
    case 'lnurl': return lnurlWithdraw.value ? `lightning:${lnurlWithdraw.value}` : null
    case 'clink': return atmStore.context?.ndebitUri || null
  }
})

CLINK Debit by pubkey (manual npub entry):

<div class="space-y-2">
  <p class="text-xs font-medium text-accent">Enter your npub/pubkey:</p>
  <div class="flex gap-2">
    <Input v-model="pubkeyInput" placeholder="npub1... or hex pubkey" class="flex-1 font-mono-code text-xs" :disabled="isProcessing" />
    <Button variant="outline" size="sm" :disabled="!pubkeyInput.trim() || isProcessing" @click="submitDebitRequest">
      {{ atmStore.isRequestingDebit ? '...' : 'Send' }}
    </Button>
  </div>
</div>

Once the k1 session token is implemented, re-add these with proper session matching.

> _@padreug commented on 2026-03-01 ([lamassu-next#23](https://git.atitlan.io/aiolabs/lamassu-next/issues/23#issuecomment-112)):_ ## UI code removed pending k1 fix CLINK ndebit has been disabled in the ATM UI until this issue is resolved. The QR now shows LNURL-withdraw only. ### Removed from `CashInView.vue` **Mode selector** (Universal/LNURL/CLINK toggle buttons): ```vue <!-- Mode selector (web-ui only) --> <div v-if="!isElectron" class="pt-4 text-center"> <p class="text-xs text-muted-foreground mb-2">Scan with</p> <div class="flex flex-wrap gap-2 justify-center"> <Button :variant="qrMode === 'unified' ? 'default' : 'outline'" size="sm" class="rounded-full px-4" @click="qrMode = 'unified'">Universal</Button> <Button :variant="qrMode === 'lnurl' ? 'default' : 'outline'" size="sm" class="rounded-full px-4" @click="qrMode = 'lnurl'">LNURL</Button> <Button :variant="qrMode === 'clink' ? 'default' : 'outline'" size="sm" class="rounded-full px-4" @click="qrMode = 'clink'">CLINK</Button> </div> </div> ``` **Unified QR value** (combined lightning + clink): ```ts const qrMode = ref<'unified' | 'lnurl' | 'clink'>('unified') const unifiedQrValue = computed(() => { const ndebit = atmStore.context?.ndebitUri const lnurl = lnurlWithdraw.value if (lnurl && ndebit) return `lightning:${lnurl}\n${ndebit}` else if (lnurl) return `lightning:${lnurl}` else if (ndebit) return ndebit return null }) const currentQrValue = computed(() => { switch (qrMode.value) { case 'unified': return unifiedQrValue.value case 'lnurl': return lnurlWithdraw.value ? `lightning:${lnurlWithdraw.value}` : null case 'clink': return atmStore.context?.ndebitUri || null } }) ``` **CLINK Debit by pubkey** (manual npub entry): ```vue <div class="space-y-2"> <p class="text-xs font-medium text-accent">Enter your npub/pubkey:</p> <div class="flex gap-2"> <Input v-model="pubkeyInput" placeholder="npub1... or hex pubkey" class="flex-1 font-mono-code text-xs" :disabled="isProcessing" /> <Button variant="outline" size="sm" :disabled="!pubkeyInput.trim() || isProcessing" @click="submitDebitRequest"> {{ atmStore.isRequestingDebit ? '...' : 'Send' }} </Button> </div> </div> ``` Once the k1 session token is implemented, re-add these with proper session matching.
Author
Owner

@padreug commented on 2026-05-13 (lamassu-next#23):

This issue is about a spec-level change to ndebit (CLINK kind-21002) — adding k1 to the request envelope so debit requests can be correlated to a session token. ndebit and CLINK are Lightning.Pub-only; LNbits has no knowledge of CLINK and the nostr-native-transport work (see #22) does not change that.

So this issue stays:

  • Scoped to: the CLINK spec at https://github.com/shocknet/CLINK, ShockWallet, and Lightning.Pub's GetLiveDebitRequests handler.
  • Not affected by: the LNbits migration of cash-in / cash-out flows.

If LNbits ever grows first-class CLINK support (deferred — see #22 and the LP-vs-LNbits separation memo), this issue's k1 requirement would carry over into that implementation. Until then, treat it as an LP-only spec-and-impl change. The LNbits transport's subscribe_payments({wallet_id?, payment_hash?, tag?, link_id?}) filter is the outcome-level analog (correlate a settlement to a session by payment_hash or by an opaque extra.session_id), but it's not a CLINK feature and doesn't need a k1 field.

> _@padreug commented on 2026-05-13 ([lamassu-next#23](https://git.atitlan.io/aiolabs/lamassu-next/issues/23#issuecomment-565)):_ ## Pure CLINK / Lightning.Pub scope — unaffected by the LNbits migration This issue is about a spec-level change to **ndebit (CLINK kind-21002)** — adding `k1` to the request envelope so debit requests can be correlated to a session token. ndebit and CLINK are Lightning.Pub-only; LNbits has no knowledge of CLINK and the `nostr-native-transport` work (see #22) does not change that. So this issue stays: - **Scoped to:** the CLINK spec at https://github.com/shocknet/CLINK, ShockWallet, and Lightning.Pub's `GetLiveDebitRequests` handler. - **Not affected by:** the LNbits migration of cash-in / cash-out flows. If LNbits ever grows first-class CLINK support (deferred — see #22 and the LP-vs-LNbits separation memo), this issue's `k1` requirement would carry over into that implementation. Until then, treat it as an LP-only spec-and-impl change. The LNbits transport's `subscribe_payments({wallet_id?, payment_hash?, tag?, link_id?})` filter is the *outcome*-level analog (correlate a settlement to a session by `payment_hash` or by an opaque `extra.session_id`), but it's not a CLINK feature and doesn't need a `k1` field.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
aiolabs/bitspire#23
No description provided.