-
Notifications
You must be signed in to change notification settings - Fork 122
Add simple asset autoloop #886
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
Changes from all commits
832a052
e3f049a
3c79e9d
9f7249f
a10b483
6e49e37
1dc1192
98e6bf0
e7b2836
014e919
439d64a
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