-
Notifications
You must be signed in to change notification settings - Fork 118
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add simple asset autoloop #886
base: master
Are you sure you want to change the base?
Changes from all commits
865db66
d8ab955
82d6cac
1eb7b95
26406bf
ca83271
20d65bb
8cf6af9
1478d2e
624d445
012baa4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,6 +9,7 @@ import ( | |
"time" | ||
|
||
"github.com/btcsuite/btcd/btcutil" | ||
"github.com/lightninglabs/taproot-assets/rfqmath" | ||
"github.com/lightninglabs/taproot-assets/tapcfg" | ||
"github.com/lightninglabs/taproot-assets/taprpc" | ||
"github.com/lightninglabs/taproot-assets/taprpc/priceoraclerpc" | ||
|
@@ -184,6 +185,75 @@ func (c *TapdClient) GetAssetName(ctx context.Context, | |
return assetName, nil | ||
} | ||
|
||
// GetAssetPrice returns the price of an asset in satoshis. NOTE: this currently | ||
// uses the rfq process for the asset price. A future implementation should | ||
// use a price oracle to not spam a peer. | ||
func (c *TapdClient) GetAssetPrice(ctx context.Context, assetID string, | ||
peerPubkey []byte, assetAmt uint64, paymentMaxAmt btcutil.Amount) ( | ||
btcutil.Amount, error) { | ||
|
||
// We'll allow a short rfq expiry as we'll only use this rfq to | ||
// gauge a price. | ||
rfqExpiry := time.Now().Add(time.Minute).Unix() | ||
|
||
msatAmt := lnwire.NewMSatFromSatoshis(paymentMaxAmt) | ||
|
||
// First we'll rfq a random peer for the asset. | ||
hieblmi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
rfq, err := c.RfqClient.AddAssetSellOrder( | ||
ctx, &rfqrpc.AddAssetSellOrderRequest{ | ||
AssetSpecifier: &rfqrpc.AssetSpecifier{ | ||
Id: &rfqrpc.AssetSpecifier_AssetIdStr{ | ||
AssetIdStr: assetID, | ||
}, | ||
}, | ||
PaymentMaxAmt: uint64(msatAmt), | ||
Expiry: uint64(rfqExpiry), | ||
TimeoutSeconds: uint32(c.cfg.RFQtimeout.Seconds()), | ||
PeerPubKey: peerPubkey, | ||
}) | ||
if err != nil { | ||
return 0, err | ||
} | ||
if rfq == nil { | ||
return 0, fmt.Errorf("no RFQ response") | ||
} | ||
|
||
if rfq.GetInvalidQuote() != nil { | ||
sputn1ck marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return 0, fmt.Errorf("peer %v sent an invalid quote response %v for "+ | ||
"asset %v", peerPubkey, rfq.GetInvalidQuote(), assetID) | ||
} | ||
|
||
if rfq.GetRejectedQuote() != nil { | ||
return 0, fmt.Errorf("peer %v rejected the quote request for "+ | ||
"asset %v, %v", peerPubkey, assetID, rfq.GetRejectedQuote()) | ||
} | ||
|
||
acceptedRes := rfq.GetAcceptedQuote() | ||
if acceptedRes == nil { | ||
return 0, fmt.Errorf("no accepted quote") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: "quote wasn't accepted", maybe with details? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This actually isn't "quote wasn't accepted" as that would be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. func (x *AddAssetSellOrderResponse) GetAcceptedQuote() *PeerAcceptedSellQuote {
if x, ok := x.GetResponse().(*AddAssetSellOrderResponse_AcceptedQuote); ok {
return x.AcceptedQuote
}
return nil
}
func (x *AddAssetSellOrderResponse) GetInvalidQuote() *InvalidQuoteResponse {
if x, ok := x.GetResponse().(*AddAssetSellOrderResponse_InvalidQuote); ok {
return x.InvalidQuote
}
return nil
}
func (x *AddAssetSellOrderResponse) GetRejectedQuote() *RejectedQuoteResponse {
if x, ok := x.GetResponse().(*AddAssetSellOrderResponse_RejectedQuote); ok {
return x.RejectedQuote
}
return nil
} These are the 3 possibilities |
||
} | ||
|
||
// We'll use the accepted quote to calculate the price. | ||
return getSatsFromAssetAmt(assetAmt, acceptedRes.BidAssetRate) | ||
} | ||
|
||
// getSatsFromAssetAmt returns the amount in satoshis for the given asset amount | ||
// and asset rate. | ||
func getSatsFromAssetAmt(assetAmt uint64, assetRate *rfqrpc.FixedPoint) ( | ||
btcutil.Amount, error) { | ||
|
||
rateFP, err := rfqrpc.UnmarshalFixedPoint(assetRate) | ||
if err != nil { | ||
return 0, fmt.Errorf("cannot unmarshal asset rate: %w", err) | ||
} | ||
|
||
assetUnits := rfqmath.NewBigIntFixedPoint(assetAmt, 0) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here we hold the assumption that an asset unit is at least a millisatoshi. What about fractions? Should we scale both asset units and the rate so we can represent fractions correctly? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do you consider this to be assumed? |
||
|
||
msatAmt := rfqmath.UnitsToMilliSatoshi(assetUnits, *rateFP) | ||
|
||
return msatAmt.ToSatoshis(), nil | ||
} | ||
|
||
// getPaymentMaxAmount returns the milisat amount we are willing to pay for the | ||
// payment. | ||
func getPaymentMaxAmount(satAmount btcutil.Amount, feeLimitMultiplier float64) ( | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
//go:build dev | ||
// +build dev | ||
|
||
package main | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/lightninglabs/loop/looprpc" | ||
"github.com/urfave/cli" | ||
) | ||
|
||
func init() { | ||
// Register the debug command. | ||
commands = append(commands, forceAutoloopCmd) | ||
} | ||
|
||
var forceAutoloopCmd = cli.Command{ | ||
Name: "forceautoloop", | ||
Usage: ` | ||
Forces to trigger an autoloop step, regardless of the current internal | ||
autoloop timer. THIS MUST NOT BE USED IN A PROD ENVIRONMENT. | ||
`, | ||
Action: forceAutoloop, | ||
} | ||
|
||
func forceAutoloop(ctx *cli.Context) error { | ||
client, cleanup, err := getDebugClient(ctx) | ||
if err != nil { | ||
return err | ||
} | ||
defer cleanup() | ||
|
||
cfg, err := client.ForceAutoLoop( | ||
context.Background(), &looprpc.ForceAutoLoopRequest{}, | ||
) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
printRespJSON(cfg) | ||
|
||
return nil | ||
} | ||
|
||
func getDebugClient(ctx *cli.Context) (looprpc.DebugClient, func(), error) { | ||
rpcServer := ctx.GlobalString("rpcserver") | ||
tlsCertPath, macaroonPath, err := extractPathArgs(ctx) | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
conn, err := getClientConn(rpcServer, tlsCertPath, macaroonPath) | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
cleanup := func() { conn.Close() } | ||
|
||
debugClient := looprpc.NewDebugClient(conn) | ||
return debugClient, cleanup, nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to specify expiry? Is there a default?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The sell order expiry is actually unused in tapd. I think we should keep this sane value though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The rfq expiry is checked when this function is called on intercepted incoming HTLCs