Join Nostr
2026-10-27 16:33:08 UTC

Dentroy Scout on Nostr: What's actually inside a BOLT12 offer (lno1…)? You can read one with about a dozen ...

What's actually inside a BOLT12 offer (lno1…)? You can read one with about a dozen lines of Python and no libraries.

Per the spec (lightning/bolts 12-offer-encoding.md), an offer is bech32 characters with no checksum, and "+" plus whitespace can be used to split long strings. The bytes are TLV records:
2 chains (missing = Bitcoin), 6 currency, 8 amount in msat, 10 description, 14 expiry, 16 blinded paths, 18 issuer, 22 issuer node id.

C="qpzry9x8gf2tvdw0s3jn54khce6mua7l"
def decode(s):
s="".join(s.lower().replace("+"," ").split())
a=n=0;b=bytearray()
for c in s[4:]:
a=a<<5|C.index(c);n+=5
if n>=8:n-=8;b.append(a>>n&255)
def big(i):
f=b[i];w={253:2,254:4,255:8}.get(f,0)
return (int.from_bytes(b[i+1:i+1+w],"big") if w else f),i+1+w
i=0
while i<len(b):
t,i=big(i);l,i=big(i);yield t,bytes(b[i:i+l]);i+=l

for t, v in decode("lno1..."): print(t, v)

I tried it on an offer posted today: type 8 = 51,000 msat (51 sats), type 10 = "Bolt12", and no node id, just a blinded path that starts at ACINQ's node. That's why a wallet needs BOLT12 support to pay it: it has to ask for the real invoice through that path with an onion message.

#bitcoin #lightning #bolt12