Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -673,7 +673,7 @@ object JsonSerializers {
data class OfferSurrogate(
val chain: String,
val chainHashes: List<BlockHash>?,
val amount: MilliSatoshi?,
val amount: Long?,
val currency: String?,
val issuer: String?,
val quantityMax: Long?,
Expand All @@ -698,7 +698,7 @@ object JsonSerializers {
else -> "unknown"
}.lowercase(),
chainHashes = o.records.get<OfferChains>()?.chains,
amount = o.amount,
amount = o.records.get<OfferTypes.OfferAmount>()?.amount,
currency = o.currency,
issuer = o.issuer,
quantityMax = o.quantityMax,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,8 @@ data class Bolt12Invoice(val records: TlvStream<InvoiceTlv>) : PaymentRequest()
additionalTlvs: Set<InvoiceTlv> = setOf(),
customTlvs: Set<GenericTlv> = setOf()
): Bolt12Invoice {
require(request.amount != null || request.offer.amount != null)
val amount = request.amount ?: (request.offer.amount!! * request.quantity)
require(request.amount != null || request.offer.amountMsat != null)
val amount = request.amount ?: (request.offer.amountMsat!! * request.quantity)
val tlvs: Set<InvoiceTlv> = removeSignature(request.records).records + setOfNotNull(
InvoicePaths(paths.map { it.route }),
InvoiceBlindedPay(paths.map { it.paymentInfo }),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ class OfferManager(val nodeParams: NodeParams, val walletParams: WalletParams, v
private fun isOurOffer(offer: OfferTypes.Offer, pathId: ByteVector?, blindedPrivateKey: PrivateKey): Boolean = when {
pathId != null && pathId.size() != 32 -> false
else -> {
val expected = deterministicOffer(nodeParams.chainHash, nodeParams.nodePrivateKey, walletParams.trampolineNode.id, offer.amount, offer.description, pathId?.let { ByteVector32(it) })
val expected = deterministicOffer(nodeParams.chainHash, nodeParams.nodePrivateKey, walletParams.trampolineNode.id, offer.amountMsat, offer.description, pathId?.let { ByteVector32(it) })
expected == Pair(offer, blindedPrivateKey)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,17 +133,17 @@ object OfferTypes {
/**
* Amount to pay per item. As we only support bitcoin, the amount is in msat.
*/
data class OfferAmount(val amount: MilliSatoshi) : OfferTlv() {
data class OfferAmount(val amount: Long) : OfferTlv() {
override val tag: Long get() = OfferAmount.tag

override fun write(out: Output) {
LightningCodecs.writeTU64(amount.toLong(), out)
LightningCodecs.writeTU64(amount, out)
}

companion object : TlvValueReader<OfferAmount> {
const val tag: Long = 8
override fun read(input: Input): OfferAmount {
return OfferAmount(MilliSatoshi(LightningCodecs.tu64(input)))
return OfferAmount(LightningCodecs.tu64(input))
}
}
}
Expand Down Expand Up @@ -725,11 +725,12 @@ object OfferTypes {
val chains: List<BlockHash> = records.get<OfferChains>()?.chains ?: listOf(Block.LivenetGenesisBlock.hash)
val metadata: ByteVector? = records.get<OfferMetadata>()?.data
val currency: String? = records.get<OfferCurrency>()?.iso4217
val amount: MilliSatoshi? = if (currency == null) {
records.get<OfferAmount>()?.amount
val amount: Either<MilliSatoshi, Long>? = if (currency == null) {
records.get<OfferAmount>()?.amount?.let { Left(MilliSatoshi(it)) }
} else {
null // TODO: add exchange rates
records.get<OfferAmount>()?.amount?.let { Right(it) }
}
val amountMsat: MilliSatoshi? = amount?.left
val description: String? = records.get<OfferDescription>()?.description
val features: Features = records.get<OfferFeatures>()?.features?.let { Features(it) } ?: Features.empty
val expirySeconds: Long? = records.get<OfferAbsoluteExpiry>()?.absoluteExpirySeconds
Expand Down Expand Up @@ -774,7 +775,7 @@ object OfferTypes {
if (description == null) require(amount == null) { "an offer description must be provided if the amount isn't null" }
val tlvs: Set<OfferTlv> = setOfNotNull(
if (chain != Block.LivenetGenesisBlock.hash) OfferChains(listOf(chain)) else null,
amount?.let { OfferAmount(it) },
amount?.let { OfferAmount(it.toLong()) },
description?.let { OfferDescription(it) },
features.bolt12Features().let { if (it != Features.empty) OfferFeatures(it.toByteArray().toByteVector()) else null },
OfferIssuerId(nodeId)
Expand Down Expand Up @@ -810,7 +811,7 @@ object OfferTypes {
)
val tlvs = setOfNotNull(
if (chainHash != Block.LivenetGenesisBlock.hash) OfferChains(listOf(chainHash)) else null,
amount?.let { OfferAmount(it) },
amount?.let { OfferAmount(it.toLong()) },
description?.let { OfferDescription(it) },
features.bolt12Features().let { if (it != Features.empty) OfferFeatures(it.toByteArray().toByteVector()) else null },
// Note that we don't include an offer_node_id since we're using a blinded path.
Expand Down Expand Up @@ -878,14 +879,14 @@ object OfferTypes {
val quantity_opt: Long? = records.get<InvoiceRequestQuantity>()?.quantity
val quantity: Long = quantity_opt ?: 1
// A valid invoice_request must either specify an amount, or the offer itself must specify an amount.
val requestedAmount: MilliSatoshi = amount ?: (offer.amount!! * quantity)
val requestedAmount: MilliSatoshi = amount ?: (offer.amountMsat!! * quantity)
val payerId: PublicKey = records.get<InvoiceRequestPayerId>()!!.publicKey
val payerNote: String? = records.get<InvoiceRequestPayerNote>()?.note
private val signature: ByteVector64 = records.get<Signature>()!!.signature

fun isValid(): Boolean =
(offer.amount == null || amount == null || offer.amount * quantity <= amount) &&
(offer.amount != null || amount != null) &&
(offer.amountMsat == null || amount == null || offer.amountMsat * quantity <= amount) &&
(offer.amountMsat != null || amount != null) &&
offer.chains.contains(chain) &&
((offer.quantityMax == null && quantity_opt == null) || (offer.quantityMax != null && quantity_opt != null && quantity <= offer.quantityMax)) &&
Features.areCompatible(offer.features, features) &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ class Bolt12InvoiceTestsCommon : LightningTestSuite() {
assertFalse(withModifiedUnknownTlv.checkSignature())
val withModifiedAmount = Bolt12Invoice(TlvStream(invoice.records.records.map {
when (it) {
is OfferAmount -> OfferAmount(it.amount + 100.msat)
is OfferAmount -> OfferAmount(it.amount + 100)
else -> it
}
}.toSet(), invoice.records.unknown))
Expand Down Expand Up @@ -156,7 +156,7 @@ class Bolt12InvoiceTestsCommon : LightningTestSuite() {
// amount must match the request
val withOtherAmount = signInvoice(Bolt12Invoice(TlvStream(invoice.records.records.map {
when (it) {
is OfferAmount -> OfferAmount(9000.msat)
is OfferAmount -> OfferAmount(9000)
else -> it
}
}.toSet())), nodeKey)
Expand Down Expand Up @@ -356,7 +356,7 @@ class Bolt12InvoiceTestsCommon : LightningTestSuite() {
setOf(
InvoiceRequestMetadata(payerInfo),
OfferChains(listOf(chain)),
OfferAmount(amount),
OfferAmount(amount.toLong()),
OfferDescription(description),
OfferFeatures(ByteVector.empty),
OfferIssuer(issuer),
Expand Down Expand Up @@ -485,7 +485,7 @@ class Bolt12InvoiceTestsCommon : LightningTestSuite() {
val offer = Offer(
TlvStream(
OfferChains(listOf(Block.Testnet3GenesisBlock.hash)),
OfferAmount(100000.msat),
OfferAmount(100000),
OfferDescription("offer with quantity"),
OfferIssuer("[email protected]"),
OfferQuantityMax(1000),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import fr.acinq.bitcoin.io.ByteArrayInput
import fr.acinq.bitcoin.io.ByteArrayOutput
import fr.acinq.bitcoin.io.Input
import fr.acinq.bitcoin.io.Output
import fr.acinq.bitcoin.utils.Either
import fr.acinq.lightning.*
import fr.acinq.lightning.Lightning.randomBytes32
import fr.acinq.lightning.Lightning.randomKey
Expand All @@ -27,6 +28,7 @@ import fr.acinq.lightning.wire.OfferTypes.InvoiceRequestTlv
import fr.acinq.lightning.wire.OfferTypes.Offer
import fr.acinq.lightning.wire.OfferTypes.OfferAmount
import fr.acinq.lightning.wire.OfferTypes.OfferChains
import fr.acinq.lightning.wire.OfferTypes.OfferCurrency
import fr.acinq.lightning.wire.OfferTypes.OfferDescription
import fr.acinq.lightning.wire.OfferTypes.OfferIssuer
import fr.acinq.lightning.wire.OfferTypes.OfferIssuerId
Expand Down Expand Up @@ -71,7 +73,7 @@ class OfferTypesTestsCommon : LightningTestSuite() {
val offer = Offer(
TlvStream(
OfferChains(listOf(Block.Testnet3GenesisBlock.hash)),
OfferAmount(50.msat),
OfferAmount(50),
OfferDescription("offer with quantity"),
OfferIssuer("[email protected]"),
OfferQuantityMax(0),
Expand All @@ -80,7 +82,7 @@ class OfferTypesTestsCommon : LightningTestSuite() {
)
val encoded = "lno1qgsyxjtl6luzd9t3pr62xr7eemp6awnejusgf6gw45q75vcfqqqqqqqgqyeq5ym0venx2u3qwa5hg6pqw96kzmn5d968jys3v9kxjcm9gp3xjemndphhqtnrdak3gqqkyypsmuhrtwfzm85mht4a3vcp0yrlgua3u3m5uqpc6kf7nqjz6v70qwg"
assertEquals(offer, Offer.decode(encoded).get())
assertEquals(50.msat, offer.amount)
assertEquals(Either.Left(50.msat), offer.amount)
assertEquals("offer with quantity", offer.description)
assertEquals(nodeId, offer.issuerId)
assertEquals("[email protected]", offer.issuer)
Expand Down Expand Up @@ -147,7 +149,7 @@ class OfferTypesTestsCommon : LightningTestSuite() {

@Test
fun `check that invoice request matches offer - without chain`() {
val offer = Offer(TlvStream(OfferAmount(100.msat), OfferDescription("offer without chains"), OfferIssuerId(randomKey().publicKey())))
val offer = Offer(TlvStream(OfferAmount(100), OfferDescription("offer without chains"), OfferIssuerId(randomKey().publicKey())))
val payerKey = randomKey()
val tlvs: Set<InvoiceRequestTlv> = offer.records.records + setOf(
InvoiceRequestMetadata(ByteVector.fromHex("012345")),
Expand All @@ -169,7 +171,7 @@ class OfferTypesTestsCommon : LightningTestSuite() {
fun `check that invoice request matches offer - with chains`() {
val chain1 = BlockHash(randomBytes32())
val chain2 = BlockHash(randomBytes32())
val offer = Offer(TlvStream(OfferChains(listOf(chain1, chain2)), OfferAmount(100.msat), OfferDescription("offer with chains"), OfferIssuerId(randomKey().publicKey())))
val offer = Offer(TlvStream(OfferChains(listOf(chain1, chain2)), OfferAmount(100), OfferDescription("offer with chains"), OfferIssuerId(randomKey().publicKey())))
val payerKey = randomKey()
val request1 = InvoiceRequest(offer, 100.msat, 1, Features.empty, payerKey, null, chain1)
assertTrue(request1.isValid())
Expand All @@ -192,7 +194,7 @@ class OfferTypesTestsCommon : LightningTestSuite() {
fun `check that invoice request matches offer - multiple items`() {
val offer = Offer(
TlvStream(
OfferAmount(500.msat),
OfferAmount(500),
OfferDescription("offer for multiple items"),
OfferIssuerId(randomKey().publicKey()),
OfferQuantityMax(10),
Expand Down Expand Up @@ -614,4 +616,15 @@ class OfferTypesTestsCommon : LightningTestSuite() {
assertTrue(Offer.decode(it).isFailure)
}
}

@Test
fun `offer with fiat currency`(){
val offer = Offer(TlvStream(
OfferAmount(123),
OfferCurrency("EUR"),
OfferDescription("offer for 1.23€"),
OfferIssuerId(randomKey().publicKey())))
val invoiceRequest = InvoiceRequest(offer, 1269486.msat, 1, Features.empty, randomKey(), null, Block.LivenetGenesisBlock.hash)
assertTrue(invoiceRequest.isValid())
}
}
Loading