@@ -24,7 +24,7 @@ import akka.pattern._
24
24
import akka .util .Timeout
25
25
import com .softwaremill .quicklens .ModifyPimp
26
26
import fr .acinq .bitcoin .scalacompat .Crypto .PublicKey
27
- import fr .acinq .bitcoin .scalacompat .{BlockHash , ByteVector32 , ByteVector64 , Crypto , OutPoint , Satoshi , Script , TxId , addressToPublicKeyScript }
27
+ import fr .acinq .bitcoin .scalacompat .{BlockHash , ByteVector32 , ByteVector64 , Crypto , OutPoint , Satoshi , SatoshiLong , Script , TxId , addressToPublicKeyScript }
28
28
import fr .acinq .eclair .ApiTypes .ChannelNotFound
29
29
import fr .acinq .eclair .balance .CheckBalance .GlobalBalance
30
30
import fr .acinq .eclair .balance .{BalanceActor , ChannelsListener }
@@ -88,13 +88,13 @@ trait Eclair {
88
88
89
89
def disconnect (nodeId : PublicKey )(implicit timeout : Timeout ): Future [String ]
90
90
91
- def open (nodeId : PublicKey , fundingAmount : Satoshi , pushAmount_opt : Option [MilliSatoshi ], channelType_opt : Option [SupportedChannelType ], fundingFeerate_opt : Option [FeeratePerByte ], fundingFeeBudget_opt : Option [Satoshi ], announceChannel_opt : Option [Boolean ], openTimeout_opt : Option [Timeout ])(implicit timeout : Timeout ): Future [OpenChannelResponse ]
91
+ def open (nodeId : PublicKey , fundingAmount : Satoshi , pushAmount_opt : Option [MilliSatoshi ], channelType_opt : Option [SupportedChannelType ], fundingFeerate_opt : Option [FeeratePerByte ], fundingFeeBudget_opt : Option [Satoshi ], requestFunding_opt : Option [ Satoshi ], announceChannel_opt : Option [Boolean ], openTimeout_opt : Option [Timeout ])(implicit timeout : Timeout ): Future [OpenChannelResponse ]
92
92
93
- def rbfOpen (channelId : ByteVector32 , targetFeerate : FeeratePerKw , fundingFeeBudget : Satoshi , lockTime_opt : Option [Long ])(implicit timeout : Timeout ): Future [CommandResponse [CMD_BUMP_FUNDING_FEE ]]
93
+ def rbfOpen (channelId : ByteVector32 , targetFeerate : FeeratePerKw , fundingFeeBudget : Satoshi , requestFunding_opt : Option [ Satoshi ], lockTime_opt : Option [Long ])(implicit timeout : Timeout ): Future [CommandResponse [CMD_BUMP_FUNDING_FEE ]]
94
94
95
- def spliceIn (channelId : ByteVector32 , amountIn : Satoshi , pushAmount_opt : Option [MilliSatoshi ])(implicit timeout : Timeout ): Future [CommandResponse [CMD_SPLICE ]]
95
+ def spliceIn (channelId : ByteVector32 , amountIn : Satoshi , requestFunding_opt : Option [ Satoshi ], pushAmount_opt : Option [MilliSatoshi ])(implicit timeout : Timeout ): Future [CommandResponse [CMD_SPLICE ]]
96
96
97
- def spliceOut (channelId : ByteVector32 , amountOut : Satoshi , scriptOrAddress : Either [ByteVector , String ])(implicit timeout : Timeout ): Future [CommandResponse [CMD_SPLICE ]]
97
+ def spliceOut (channelId : ByteVector32 , amountOut : Satoshi , scriptOrAddress : Either [ByteVector , String ], requestFunding_opt : Option [ Satoshi ] )(implicit timeout : Timeout ): Future [CommandResponse [CMD_SPLICE ]]
98
98
99
99
def rbfSplice (channelId : ByteVector32 , targetFeerate : FeeratePerKw , fundingFeeBudget : Satoshi , lockTime_opt : Option [Long ])(implicit timeout : Timeout ): Future [CommandResponse [CMD_BUMP_FUNDING_FEE ]]
100
100
@@ -212,55 +212,72 @@ class EclairImpl(appKit: Kit) extends Eclair with Logging {
212
212
(appKit.switchboard ? Peer .Disconnect (nodeId)).mapTo[Peer .DisconnectResponse ].map(_.toString)
213
213
}
214
214
215
- override def open (nodeId : PublicKey , fundingAmount : Satoshi , pushAmount_opt : Option [MilliSatoshi ], channelType_opt : Option [SupportedChannelType ], fundingFeerate_opt : Option [FeeratePerByte ], fundingFeeBudget_opt : Option [Satoshi ], announceChannel_opt : Option [Boolean ], openTimeout_opt : Option [Timeout ])(implicit timeout : Timeout ): Future [OpenChannelResponse ] = {
215
+ override def open (nodeId : PublicKey , fundingAmount : Satoshi , pushAmount_opt : Option [MilliSatoshi ], channelType_opt : Option [SupportedChannelType ], fundingFeerate_opt : Option [FeeratePerByte ], fundingFeeBudget_opt : Option [Satoshi ], requestFunding_opt : Option [ Satoshi ], announceChannel_opt : Option [Boolean ], openTimeout_opt : Option [Timeout ])(implicit timeout : Timeout ): Future [OpenChannelResponse ] = {
216
216
// we want the open timeout to expire *before* the default ask timeout, otherwise user will get a generic response
217
217
val openTimeout = openTimeout_opt.getOrElse(Timeout (20 seconds))
218
218
// if no budget is provided for the mining fee of the funding tx, we use a default of 0.1% of the funding amount as a safety measure
219
219
val fundingFeeBudget = fundingFeeBudget_opt.getOrElse(fundingAmount * 0.001 )
220
220
for {
221
- _ <- Future .successful( 0 )
221
+ purchaseFunding_opt <- createLiquidityRequest(nodeId, requestFunding_opt )
222
222
open = Peer .OpenChannel (
223
223
remoteNodeId = nodeId,
224
224
fundingAmount = fundingAmount,
225
225
channelType_opt = channelType_opt,
226
226
pushAmount_opt = pushAmount_opt,
227
227
fundingTxFeerate_opt = fundingFeerate_opt.map(FeeratePerKw (_)),
228
228
fundingTxFeeBudget_opt = Some (fundingFeeBudget),
229
- requestFunding_opt = None ,
229
+ requestFunding_opt = purchaseFunding_opt ,
230
230
channelFlags_opt = announceChannel_opt.map(announceChannel => ChannelFlags (announceChannel = announceChannel)),
231
231
timeout_opt = Some (openTimeout))
232
232
res <- (appKit.switchboard ? open).mapTo[OpenChannelResponse ]
233
233
} yield res
234
234
}
235
235
236
- override def rbfOpen (channelId : ByteVector32 , targetFeerate : FeeratePerKw , fundingFeeBudget : Satoshi , lockTime_opt : Option [Long ])(implicit timeout : Timeout ): Future [CommandResponse [CMD_BUMP_FUNDING_FEE ]] = {
237
- sendToChannelTyped(
238
- channel = Left (channelId),
239
- cmdBuilder = CMD_BUMP_FUNDING_FEE (_, targetFeerate, fundingFeeBudget, lockTime_opt.getOrElse(appKit.nodeParams.currentBlockHeight.toLong), requestFunding_opt = None )
240
- )
236
+ override def rbfOpen (channelId : ByteVector32 , targetFeerate : FeeratePerKw , fundingFeeBudget : Satoshi , requestFunding_opt : Option [Satoshi ], lockTime_opt : Option [Long ])(implicit timeout : Timeout ): Future [CommandResponse [CMD_BUMP_FUNDING_FEE ]] = {
237
+ for {
238
+ purchaseFunding_opt <- createLiquidityRequest(channelId, requestFunding_opt)
239
+ res <- sendToChannelTyped[CMD_BUMP_FUNDING_FEE , CommandResponse [CMD_BUMP_FUNDING_FEE ]](
240
+ channel = Left (channelId),
241
+ cmdBuilder = CMD_BUMP_FUNDING_FEE (_, targetFeerate, fundingFeeBudget, lockTime_opt.getOrElse(appKit.nodeParams.currentBlockHeight.toLong), purchaseFunding_opt)
242
+ )
243
+ } yield res
241
244
}
242
245
243
- override def spliceIn (channelId : ByteVector32 , amountIn : Satoshi , pushAmount_opt : Option [MilliSatoshi ])(implicit timeout : Timeout ): Future [CommandResponse [CMD_SPLICE ]] = {
244
- val spliceIn = SpliceIn (additionalLocalFunding = amountIn, pushAmount = pushAmount_opt.getOrElse(0 .msat))
245
- sendToChannelTyped(
246
- channel = Left (channelId),
247
- cmdBuilder = CMD_SPLICE (_, spliceIn_opt = Some (spliceIn), spliceOut_opt = None , requestFunding_opt = None )
248
- )
246
+ override def spliceIn (channelId : ByteVector32 , amountIn : Satoshi , requestFunding_opt : Option [Satoshi ], pushAmount_opt : Option [MilliSatoshi ])(implicit timeout : Timeout ): Future [CommandResponse [CMD_SPLICE ]] = {
247
+ for {
248
+ purchaseFunding_opt <- createLiquidityRequest(channelId, requestFunding_opt)
249
+ spliceIn_opt = if (amountIn > 0 .sat) Some (SpliceIn (additionalLocalFunding = amountIn, pushAmount = pushAmount_opt.getOrElse(0 msat))) else None
250
+ res <- sendToChannelTyped[CMD_SPLICE , CommandResponse [CMD_SPLICE ]](
251
+ channel = Left (channelId),
252
+ cmdBuilder = CMD_SPLICE (_,
253
+ spliceIn_opt = spliceIn_opt,
254
+ spliceOut_opt = None ,
255
+ requestFunding_opt = purchaseFunding_opt,
256
+ )
257
+ )
258
+ } yield res
249
259
}
250
260
251
- override def spliceOut (channelId : ByteVector32 , amountOut : Satoshi , scriptOrAddress : Either [ByteVector , String ])(implicit timeout : Timeout ): Future [CommandResponse [CMD_SPLICE ]] = {
261
+ override def spliceOut (channelId : ByteVector32 , amountOut : Satoshi , scriptOrAddress : Either [ByteVector , String ], requestFunding_opt : Option [ Satoshi ] )(implicit timeout : Timeout ): Future [CommandResponse [CMD_SPLICE ]] = {
252
262
val script = scriptOrAddress match {
253
263
case Left (script) => script
254
264
case Right (address) => addressToPublicKeyScript(this .appKit.nodeParams.chainHash, address) match {
255
265
case Left (failure) => throw new IllegalArgumentException (failure.toString)
256
266
case Right (script) => Script .write(script)
257
267
}
258
268
}
259
- val spliceOut = SpliceOut (amount = amountOut, scriptPubKey = script)
260
- sendToChannelTyped(
261
- channel = Left (channelId),
262
- cmdBuilder = CMD_SPLICE (_, spliceIn_opt = None , spliceOut_opt = Some (spliceOut), requestFunding_opt = None )
263
- )
269
+ for {
270
+ purchaseFunding_opt <- createLiquidityRequest(channelId, requestFunding_opt)
271
+ spliceOut_opt = if (amountOut > 0 .sat) Some (SpliceOut (amount = amountOut, scriptPubKey = script)) else None
272
+ res <- sendToChannelTyped[CMD_SPLICE , CommandResponse [CMD_SPLICE ]](
273
+ channel = Left (channelId),
274
+ cmdBuilder = CMD_SPLICE (_,
275
+ spliceIn_opt = None ,
276
+ spliceOut_opt = spliceOut_opt,
277
+ requestFunding_opt = purchaseFunding_opt,
278
+ )
279
+ )
280
+ } yield res
264
281
}
265
282
266
283
override def rbfSplice (channelId : ByteVector32 , targetFeerate : FeeratePerKw , fundingFeeBudget : Satoshi , lockTime_opt : Option [Long ])(implicit timeout : Timeout ): Future [CommandResponse [CMD_BUMP_FUNDING_FEE ]] = {
@@ -629,6 +646,37 @@ class EclairImpl(appKit: Kit) extends Eclair with Logging {
629
646
} yield res
630
647
}
631
648
649
+ private def createLiquidityRequest (nodeId : PublicKey , requestedAmount_opt : Option [Satoshi ])(implicit timeout : Timeout ): Future [Option [LiquidityAds .RequestFunding ]] = {
650
+ requestedAmount_opt match {
651
+ case Some (requestedAmount) =>
652
+ getLiquidityRate(nodeId, requestedAmount)
653
+ .map(fundingRate => Some (LiquidityAds .RequestFunding (requestedAmount, fundingRate, LiquidityAds .PaymentDetails .FromChannelBalance )))
654
+ case None => Future .successful(Option .empty[LiquidityAds .RequestFunding ])
655
+ }
656
+ }
657
+
658
+ private def createLiquidityRequest (channelId : ByteVector32 , requestedAmount_opt : Option [Satoshi ])(implicit timeout : Timeout ): Future [Option [LiquidityAds .RequestFunding ]] = {
659
+ requestedAmount_opt match {
660
+ case Some (requestedAmount) =>
661
+ channelInfo(Left (channelId)).map(_.nodeId)
662
+ .flatMap(nodeId => getLiquidityRate(nodeId, requestedAmount))
663
+ .map(fundingRate => Some (LiquidityAds .RequestFunding (requestedAmount, fundingRate, LiquidityAds .PaymentDetails .FromChannelBalance )))
664
+ case None => Future .successful(Option .empty[LiquidityAds .RequestFunding ])
665
+ }
666
+ }
667
+
668
+ private def getLiquidityRate (nodeId : PublicKey , requestedAmount : Satoshi )(implicit timeout : Timeout ): Future [LiquidityAds .FundingRate ] = {
669
+ appKit.switchboard.toTyped.ask[Peer .PeerInfoResponse ] { replyTo =>
670
+ Switchboard .GetPeerInfo (replyTo, nodeId)
671
+ }.map {
672
+ case p : PeerInfo => p.fundingRates_opt.flatMap(_.findRate(requestedAmount)) match {
673
+ case Some (fundingRate) => fundingRate
674
+ case None => throw new RuntimeException (s " peer $nodeId doesn't support funding $requestedAmount, please check their funding rates " )
675
+ }
676
+ case _ : Peer .PeerNotFound => throw new RuntimeException (s " peer $nodeId not connected " )
677
+ }
678
+ }
679
+
632
680
override def getInfo ()(implicit timeout : Timeout ): Future [GetInfoResponse ] = Future .successful(
633
681
GetInfoResponse (
634
682
version = Kit .getVersionLong,
0 commit comments