@@ -75,6 +75,21 @@ sealed class OnionPaymentPayloadTlv : Tlv {
7575 }
7676 }
7777
78+ /* *
79+ * When payment metadata is included in a Bolt 9 invoice, we should send it as-is to the recipient.
80+ * This lets recipients generate invoices without having to store anything on their side until the invoice is paid.
81+ */
82+ @Serializable
83+ data class PaymentMetadata (@Contextual val data : ByteVector ) : OnionPaymentPayloadTlv() {
84+ override val tag: Long get() = PaymentMetadata .tag
85+ override fun write (out : Output ) = LightningCodecs .writeBytes(data, out )
86+
87+ companion object : TlvValueReader <PaymentMetadata > {
88+ const val tag: Long = 16
89+ override fun read (input : Input ): PaymentMetadata = PaymentMetadata (ByteVector (LightningCodecs .bytes(input, input.availableBytes)))
90+ }
91+ }
92+
7893 /* *
7994 * Invoice feature bits. Only included for intermediate trampoline nodes when they should convert to a legacy payment
8095 * because the final recipient doesn't support trampoline.
@@ -176,6 +191,7 @@ object PaymentOnion {
176191 OnionPaymentPayloadTlv .OutgoingCltv .tag to OnionPaymentPayloadTlv .OutgoingCltv .Companion as TlvValueReader <OnionPaymentPayloadTlv >,
177192 OnionPaymentPayloadTlv .OutgoingChannelId .tag to OnionPaymentPayloadTlv .OutgoingChannelId .Companion as TlvValueReader <OnionPaymentPayloadTlv >,
178193 OnionPaymentPayloadTlv .PaymentData .tag to OnionPaymentPayloadTlv .PaymentData .Companion as TlvValueReader <OnionPaymentPayloadTlv >,
194+ OnionPaymentPayloadTlv .PaymentMetadata .tag to OnionPaymentPayloadTlv .PaymentMetadata .Companion as TlvValueReader <OnionPaymentPayloadTlv >,
179195 OnionPaymentPayloadTlv .InvoiceFeatures .tag to OnionPaymentPayloadTlv .InvoiceFeatures .Companion as TlvValueReader <OnionPaymentPayloadTlv >,
180196 OnionPaymentPayloadTlv .OutgoingNodeId .tag to OnionPaymentPayloadTlv .OutgoingNodeId .Companion as TlvValueReader <OnionPaymentPayloadTlv >,
181197 OnionPaymentPayloadTlv .InvoiceRoutingInfo .tag to OnionPaymentPayloadTlv .InvoiceRoutingInfo .Companion as TlvValueReader <OnionPaymentPayloadTlv >,
@@ -198,30 +214,54 @@ object PaymentOnion {
198214 val total = records.get<OnionPaymentPayloadTlv .PaymentData >()!! .totalAmount
199215 if (total > 0 .msat) total else amount
200216 }
217+ val paymentMetadata = records.get<OnionPaymentPayloadTlv .PaymentMetadata >()?.data
201218
202219 override fun write (out : Output ) = tlvSerializer.write(records, out )
203220
204221 companion object : PerHopPayloadReader <FinalPayload > {
205222 override fun read (input : Input ): FinalPayload = FinalPayload (tlvSerializer.read(input))
206223
207224 /* * Create a single-part payment (total amount sent at once). */
208- fun createSinglePartPayload (amount : MilliSatoshi , expiry : CltvExpiry , paymentSecret : ByteVector32 , userCustomTlvs : List <GenericTlv > = listOf()): FinalPayload =
209- FinalPayload (TlvStream (listOf (OnionPaymentPayloadTlv .AmountToForward (amount), OnionPaymentPayloadTlv .OutgoingCltv (expiry), OnionPaymentPayloadTlv .PaymentData (paymentSecret, amount)), userCustomTlvs))
225+ fun createSinglePartPayload (amount : MilliSatoshi , expiry : CltvExpiry , paymentSecret : ByteVector32 , paymentMetadata : ByteVector ? , userCustomTlvs : List <GenericTlv > = listOf()): FinalPayload {
226+ val tlvs = buildList {
227+ add(OnionPaymentPayloadTlv .AmountToForward (amount))
228+ add(OnionPaymentPayloadTlv .OutgoingCltv (expiry))
229+ add(OnionPaymentPayloadTlv .PaymentData (paymentSecret, amount))
230+ paymentMetadata?.let { add(OnionPaymentPayloadTlv .PaymentMetadata (it)) }
231+ }
232+ return FinalPayload (TlvStream (tlvs, userCustomTlvs))
233+ }
210234
211235 /* * Create a partial payment (total amount split between multiple payments). */
212236 fun createMultiPartPayload (
213- amount : MilliSatoshi , totalAmount : MilliSatoshi , expiry : CltvExpiry , paymentSecret : ByteVector32 , additionalTlvs : List <OnionPaymentPayloadTlv > = listOf(), userCustomTlvs : List <GenericTlv > = listOf()
214- ): FinalPayload =
215- FinalPayload (TlvStream (listOf (OnionPaymentPayloadTlv .AmountToForward (amount), OnionPaymentPayloadTlv .OutgoingCltv (expiry), OnionPaymentPayloadTlv .PaymentData (paymentSecret, totalAmount)) + additionalTlvs, userCustomTlvs))
237+ amount : MilliSatoshi ,
238+ totalAmount : MilliSatoshi ,
239+ expiry : CltvExpiry ,
240+ paymentSecret : ByteVector32 ,
241+ paymentMetadata : ByteVector ? ,
242+ additionalTlvs : List <OnionPaymentPayloadTlv > = listOf(),
243+ userCustomTlvs : List <GenericTlv > = listOf()
244+ ): FinalPayload {
245+ val tlvs = buildList {
246+ add(OnionPaymentPayloadTlv .AmountToForward (amount))
247+ add(OnionPaymentPayloadTlv .OutgoingCltv (expiry))
248+ add(OnionPaymentPayloadTlv .PaymentData (paymentSecret, totalAmount))
249+ paymentMetadata?.let { add(OnionPaymentPayloadTlv .PaymentMetadata (it)) }
250+ addAll(additionalTlvs)
251+ }
252+ return FinalPayload (TlvStream (tlvs, userCustomTlvs))
253+ }
216254
217255 /* * Create a trampoline outer payload. */
218- fun createTrampolinePayload (amount : MilliSatoshi , totalAmount : MilliSatoshi , expiry : CltvExpiry , paymentSecret : ByteVector32 , trampolinePacket : OnionRoutingPacket ): FinalPayload = FinalPayload (
219- TlvStream (
220- listOf (
221- OnionPaymentPayloadTlv .AmountToForward (amount), OnionPaymentPayloadTlv .OutgoingCltv (expiry), OnionPaymentPayloadTlv .PaymentData (paymentSecret, totalAmount), OnionPaymentPayloadTlv .TrampolineOnion (trampolinePacket)
222- )
223- )
224- )
256+ fun createTrampolinePayload (amount : MilliSatoshi , totalAmount : MilliSatoshi , expiry : CltvExpiry , paymentSecret : ByteVector32 , trampolinePacket : OnionRoutingPacket ): FinalPayload {
257+ val tlvs = buildList {
258+ add(OnionPaymentPayloadTlv .AmountToForward (amount))
259+ add(OnionPaymentPayloadTlv .OutgoingCltv (expiry))
260+ add(OnionPaymentPayloadTlv .PaymentData (paymentSecret, totalAmount))
261+ add(OnionPaymentPayloadTlv .TrampolineOnion (trampolinePacket))
262+ }
263+ return FinalPayload (TlvStream (tlvs))
264+ }
225265 }
226266 }
227267
@@ -255,6 +295,7 @@ object PaymentOnion {
255295
256296 // NB: the following fields are only included in the trampoline-to-legacy case.
257297 val paymentSecret = records.get<OnionPaymentPayloadTlv .PaymentData >()?.secret
298+ val paymentMetadata = records.get<OnionPaymentPayloadTlv .PaymentMetadata >()?.data
258299 val invoiceFeatures = records.get<OnionPaymentPayloadTlv .InvoiceFeatures >()?.features
259300 val invoiceRoutingInfo = records.get<OnionPaymentPayloadTlv .InvoiceRoutingInfo >()?.extraHops
260301
@@ -279,14 +320,15 @@ object PaymentOnion {
279320 }.map { it.hints }
280321 return NodeRelayPayload (
281322 TlvStream (
282- listOf (
283- OnionPaymentPayloadTlv .AmountToForward (amount),
284- OnionPaymentPayloadTlv .OutgoingCltv (expiry),
285- OnionPaymentPayloadTlv .OutgoingNodeId (targetNodeId),
286- OnionPaymentPayloadTlv .PaymentData (invoice.paymentSecret, totalAmount),
287- OnionPaymentPayloadTlv .InvoiceFeatures (invoice.features),
288- OnionPaymentPayloadTlv .InvoiceRoutingInfo (prunedRoutingHints)
289- )
323+ buildList {
324+ add(OnionPaymentPayloadTlv .AmountToForward (amount))
325+ add(OnionPaymentPayloadTlv .OutgoingCltv (expiry))
326+ add(OnionPaymentPayloadTlv .OutgoingNodeId (targetNodeId))
327+ add(OnionPaymentPayloadTlv .PaymentData (invoice.paymentSecret, totalAmount))
328+ invoice.paymentMetadata?.let { add(OnionPaymentPayloadTlv .PaymentMetadata (it)) }
329+ add(OnionPaymentPayloadTlv .InvoiceFeatures (invoice.features))
330+ add(OnionPaymentPayloadTlv .InvoiceRoutingInfo (prunedRoutingHints))
331+ }
290332 )
291333 )
292334 }
0 commit comments