@@ -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 }
@@ -86,13 +86,13 @@ trait Eclair {
86
86
87
87
def disconnect (nodeId : PublicKey )(implicit timeout : Timeout ): Future [String ]
88
88
89
- 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 ]
89
+ 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 ]
90
90
91
- def rbfOpen (channelId : ByteVector32 , targetFeerate : FeeratePerKw , fundingFeeBudget : Satoshi , lockTime_opt : Option [Long ])(implicit timeout : Timeout ): Future [CommandResponse [CMD_BUMP_FUNDING_FEE ]]
91
+ 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 ]]
92
92
93
- def spliceIn (channelId : ByteVector32 , amountIn : Satoshi , pushAmount_opt : Option [MilliSatoshi ])(implicit timeout : Timeout ): Future [CommandResponse [CMD_SPLICE ]]
93
+ def spliceIn (channelId : ByteVector32 , amountIn : Satoshi , requestFunding_opt : Option [ Satoshi ], pushAmount_opt : Option [MilliSatoshi ])(implicit timeout : Timeout ): Future [CommandResponse [CMD_SPLICE ]]
94
94
95
- def spliceOut (channelId : ByteVector32 , amountOut : Satoshi , scriptOrAddress : Either [ByteVector , String ])(implicit timeout : Timeout ): Future [CommandResponse [CMD_SPLICE ]]
95
+ def spliceOut (channelId : ByteVector32 , amountOut : Satoshi , scriptOrAddress : Either [ByteVector , String ], requestFunding_opt : Option [ Satoshi ] )(implicit timeout : Timeout ): Future [CommandResponse [CMD_SPLICE ]]
96
96
97
97
def close (channels : List [ApiTypes .ChannelIdentifier ], scriptPubKey_opt : Option [ByteVector ], closingFeerates_opt : Option [ClosingFeerates ])(implicit timeout : Timeout ): Future [Map [ApiTypes .ChannelIdentifier , Either [Throwable , CommandResponse [CMD_CLOSE ]]]]
98
98
@@ -206,55 +206,72 @@ class EclairImpl(appKit: Kit) extends Eclair with Logging {
206
206
(appKit.switchboard ? Peer .Disconnect (nodeId)).mapTo[Peer .DisconnectResponse ].map(_.toString)
207
207
}
208
208
209
- 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 ] = {
209
+ 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 ] = {
210
210
// we want the open timeout to expire *before* the default ask timeout, otherwise user will get a generic response
211
211
val openTimeout = openTimeout_opt.getOrElse(Timeout (20 seconds))
212
212
// 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
213
213
val fundingFeeBudget = fundingFeeBudget_opt.getOrElse(fundingAmount * 0.001 )
214
214
for {
215
- _ <- Future .successful( 0 )
215
+ purchaseFunding_opt <- createLiquidityRequest(nodeId, requestFunding_opt )
216
216
open = Peer .OpenChannel (
217
217
remoteNodeId = nodeId,
218
218
fundingAmount = fundingAmount,
219
219
channelType_opt = channelType_opt,
220
220
pushAmount_opt = pushAmount_opt,
221
221
fundingTxFeerate_opt = fundingFeerate_opt.map(FeeratePerKw (_)),
222
222
fundingTxFeeBudget_opt = Some (fundingFeeBudget),
223
- requestFunding_opt = None ,
223
+ requestFunding_opt = purchaseFunding_opt ,
224
224
channelFlags_opt = announceChannel_opt.map(announceChannel => ChannelFlags (announceChannel = announceChannel)),
225
225
timeout_opt = Some (openTimeout))
226
226
res <- (appKit.switchboard ? open).mapTo[OpenChannelResponse ]
227
227
} yield res
228
228
}
229
229
230
- override def rbfOpen (channelId : ByteVector32 , targetFeerate : FeeratePerKw , fundingFeeBudget : Satoshi , lockTime_opt : Option [Long ])(implicit timeout : Timeout ): Future [CommandResponse [CMD_BUMP_FUNDING_FEE ]] = {
231
- sendToChannelTyped(channel = Left (channelId),
232
- cmdBuilder = CMD_BUMP_FUNDING_FEE (_, targetFeerate, fundingFeeBudget, lockTime_opt.getOrElse(appKit.nodeParams.currentBlockHeight.toLong), requestFunding_opt = None ))
230
+ 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 ]] = {
231
+ for {
232
+ purchaseFunding_opt <- createLiquidityRequest(channelId, requestFunding_opt)
233
+ res <- sendToChannelTyped[CMD_BUMP_FUNDING_FEE , CommandResponse [CMD_BUMP_FUNDING_FEE ]](
234
+ channel = Left (channelId),
235
+ cmdBuilder = CMD_BUMP_FUNDING_FEE (_, targetFeerate, fundingFeeBudget, lockTime_opt.getOrElse(appKit.nodeParams.currentBlockHeight.toLong), purchaseFunding_opt)
236
+ )
237
+ } yield res
233
238
}
234
239
235
- override def spliceIn (channelId : ByteVector32 , amountIn : Satoshi , pushAmount_opt : Option [MilliSatoshi ])(implicit timeout : Timeout ): Future [CommandResponse [CMD_SPLICE ]] = {
236
- sendToChannelTyped(channel = Left (channelId),
237
- cmdBuilder = CMD_SPLICE (_,
238
- spliceIn_opt = Some (SpliceIn (additionalLocalFunding = amountIn, pushAmount = pushAmount_opt.getOrElse(0 .msat))),
239
- spliceOut_opt = None ,
240
- requestFunding_opt = None ,
241
- ))
240
+ override def spliceIn (channelId : ByteVector32 , amountIn : Satoshi , requestFunding_opt : Option [Satoshi ], pushAmount_opt : Option [MilliSatoshi ])(implicit timeout : Timeout ): Future [CommandResponse [CMD_SPLICE ]] = {
241
+ for {
242
+ purchaseFunding_opt <- createLiquidityRequest(channelId, requestFunding_opt)
243
+ spliceIn_opt = if (amountIn > 0 .sat) Some (SpliceIn (additionalLocalFunding = amountIn, pushAmount = pushAmount_opt.getOrElse(0 msat))) else None
244
+ res <- sendToChannelTyped[CMD_SPLICE , CommandResponse [CMD_SPLICE ]](
245
+ channel = Left (channelId),
246
+ cmdBuilder = CMD_SPLICE (_,
247
+ spliceIn_opt = spliceIn_opt,
248
+ spliceOut_opt = None ,
249
+ requestFunding_opt = purchaseFunding_opt,
250
+ )
251
+ )
252
+ } yield res
242
253
}
243
254
244
- override def spliceOut (channelId : ByteVector32 , amountOut : Satoshi , scriptOrAddress : Either [ByteVector , String ])(implicit timeout : Timeout ): Future [CommandResponse [CMD_SPLICE ]] = {
255
+ override def spliceOut (channelId : ByteVector32 , amountOut : Satoshi , scriptOrAddress : Either [ByteVector , String ], requestFunding_opt : Option [ Satoshi ] )(implicit timeout : Timeout ): Future [CommandResponse [CMD_SPLICE ]] = {
245
256
val script = scriptOrAddress match {
246
257
case Left (script) => script
247
258
case Right (address) => addressToPublicKeyScript(this .appKit.nodeParams.chainHash, address) match {
248
259
case Left (failure) => throw new IllegalArgumentException (failure.toString)
249
260
case Right (script) => Script .write(script)
250
261
}
251
262
}
252
- sendToChannelTyped(channel = Left (channelId),
253
- cmdBuilder = CMD_SPLICE (_,
254
- spliceIn_opt = None ,
255
- spliceOut_opt = Some (SpliceOut (amount = amountOut, scriptPubKey = script)),
256
- requestFunding_opt = None ,
257
- ))
263
+ for {
264
+ purchaseFunding_opt <- createLiquidityRequest(channelId, requestFunding_opt)
265
+ spliceOut_opt = if (amountOut > 0 .sat) Some (SpliceOut (amount = amountOut, scriptPubKey = script)) else None
266
+ res <- sendToChannelTyped[CMD_SPLICE , CommandResponse [CMD_SPLICE ]](
267
+ channel = Left (channelId),
268
+ cmdBuilder = CMD_SPLICE (_,
269
+ spliceIn_opt = None ,
270
+ spliceOut_opt = spliceOut_opt,
271
+ requestFunding_opt = purchaseFunding_opt,
272
+ )
273
+ )
274
+ } yield res
258
275
}
259
276
260
277
override def close (channels : List [ApiTypes .ChannelIdentifier ], scriptPubKey_opt : Option [ByteVector ], closingFeerates_opt : Option [ClosingFeerates ])(implicit timeout : Timeout ): Future [Map [ApiTypes .ChannelIdentifier , Either [Throwable , CommandResponse [CMD_CLOSE ]]]] = {
@@ -616,6 +633,37 @@ class EclairImpl(appKit: Kit) extends Eclair with Logging {
616
633
} yield res
617
634
}
618
635
636
+ private def createLiquidityRequest (nodeId : PublicKey , requestedAmount_opt : Option [Satoshi ])(implicit timeout : Timeout ): Future [Option [LiquidityAds .RequestFunding ]] = {
637
+ requestedAmount_opt match {
638
+ case Some (requestedAmount) =>
639
+ getLiquidityRate(nodeId, requestedAmount)
640
+ .map(fundingRate => Some (LiquidityAds .RequestFunding (requestedAmount, fundingRate, LiquidityAds .PaymentDetails .FromChannelBalance )))
641
+ case None => Future .successful(Option .empty[LiquidityAds .RequestFunding ])
642
+ }
643
+ }
644
+
645
+ private def createLiquidityRequest (channelId : ByteVector32 , requestedAmount_opt : Option [Satoshi ])(implicit timeout : Timeout ): Future [Option [LiquidityAds .RequestFunding ]] = {
646
+ requestedAmount_opt match {
647
+ case Some (requestedAmount) =>
648
+ channelInfo(Left (channelId)).map(_.nodeId)
649
+ .flatMap(nodeId => getLiquidityRate(nodeId, requestedAmount))
650
+ .map(fundingRate => Some (LiquidityAds .RequestFunding (requestedAmount, fundingRate, LiquidityAds .PaymentDetails .FromChannelBalance )))
651
+ case None => Future .successful(Option .empty[LiquidityAds .RequestFunding ])
652
+ }
653
+ }
654
+
655
+ private def getLiquidityRate (nodeId : PublicKey , requestedAmount : Satoshi )(implicit timeout : Timeout ): Future [LiquidityAds .FundingRate ] = {
656
+ appKit.switchboard.toTyped.ask[Peer .PeerInfoResponse ] { replyTo =>
657
+ Switchboard .GetPeerInfo (replyTo, nodeId)
658
+ }.map {
659
+ case p : PeerInfo => p.fundingRates_opt.flatMap(_.findRate(requestedAmount)) match {
660
+ case Some (fundingRate) => fundingRate
661
+ case None => throw new RuntimeException (s " peer $nodeId doesn't support funding $requestedAmount, please check their funding rates " )
662
+ }
663
+ case _ : Peer .PeerNotFound => throw new RuntimeException (s " peer $nodeId not connected " )
664
+ }
665
+ }
666
+
619
667
override def getInfo ()(implicit timeout : Timeout ): Future [GetInfoResponse ] = Future .successful(
620
668
GetInfoResponse (
621
669
version = Kit .getVersionLong,
0 commit comments