diff --git a/cmd/loop/liquidity.go b/cmd/loop/liquidity.go index c0a2b08..c382d7e 100644 --- a/cmd/loop/liquidity.go +++ b/cmd/loop/liquidity.go @@ -170,3 +170,31 @@ func setParam(ctx *cli.Context) error { return err } + +var suggestSwapCommand = cli.Command{ + Name: "suggestswaps", + Usage: "show a list of suggested swaps", + Description: "Displays a list of suggested swaps that aim to obtain " + + "the liquidity balance as specified by the rules set in " + + "the liquidity manager.", + Action: suggestSwap, +} + +func suggestSwap(ctx *cli.Context) error { + client, cleanup, err := getClient(ctx) + if err != nil { + return err + } + defer cleanup() + + resp, err := client.SuggestSwaps( + context.Background(), &looprpc.SuggestSwapsRequest{}, + ) + if err != nil { + return err + } + + printJSON(resp) + + return nil +} diff --git a/cmd/loop/main.go b/cmd/loop/main.go index fd4878c..1e7e7ff 100644 --- a/cmd/loop/main.go +++ b/cmd/loop/main.go @@ -110,7 +110,7 @@ func main() { loopOutCommand, loopInCommand, termsCommand, monitorCommand, quoteCommand, listAuthCommand, listSwapsCommand, swapInfoCommand, getLiquidityParamsCommand, - setLiquidityParamCommand, + setLiquidityParamCommand, suggestSwapCommand, } err := app.Run(os.Args) diff --git a/liquidity/balances.go b/liquidity/balances.go new file mode 100644 index 0000000..051cff5 --- /dev/null +++ b/liquidity/balances.go @@ -0,0 +1,33 @@ +package liquidity + +import ( + "github.com/btcsuite/btcutil" + "github.com/lightninglabs/lndclient" + "github.com/lightningnetwork/lnd/lnwire" +) + +// balances summarizes the state of the balances of a channel. Channel reserve, +// fees and pending htlc balances are not included in these balances. +type balances struct { + // capacity is the total capacity of the channel. + capacity btcutil.Amount + + // incoming is the remote balance of the channel. + incoming btcutil.Amount + + // outgoing is the local balance of the channel. + outgoing btcutil.Amount + + // channelID is the channel that has these balances. + channelID lnwire.ShortChannelID +} + +// newBalances creates a balances struct from lndclient channel information. +func newBalances(info lndclient.ChannelInfo) *balances { + return &balances{ + capacity: info.Capacity, + incoming: info.RemoteBalance, + outgoing: info.LocalBalance, + channelID: lnwire.NewShortChanIDFromInt(info.ChannelID), + } +} diff --git a/liquidity/liquidity.go b/liquidity/liquidity.go index c9e9aa4..f90fa7c 100644 --- a/liquidity/liquidity.go +++ b/liquidity/liquidity.go @@ -9,6 +9,7 @@ import ( "strings" "sync" + "github.com/lightninglabs/lndclient" "github.com/lightningnetwork/lnd/lnwire" ) @@ -23,6 +24,9 @@ type Config struct { // LoopOutRestrictions returns the restrictions that the server applies // to loop out swaps. LoopOutRestrictions func(ctx context.Context) (*Restrictions, error) + + // Lnd provides us with access to lnd's main rpc. + Lnd lndclient.LightningClient } // Parameters is a set of parameters provided by the user which guide @@ -131,3 +135,51 @@ func cloneParameters(params Parameters) Parameters { return paramCopy } + +// SuggestSwaps returns a set of swap suggestions based on our current liquidity +// balance for the set of rules configured for the manager, failing if there are +// no rules set. +func (m *Manager) SuggestSwaps(ctx context.Context) ( + []*LoopOutRecommendation, error) { + + m.paramsLock.Lock() + defer m.paramsLock.Unlock() + + // If we have no rules set, exit early to avoid unnecessary calls to + // lnd and the server. + if len(m.params.ChannelRules) == 0 { + return nil, nil + } + + channels, err := m.cfg.Lnd.ListChannels(ctx) + if err != nil { + return nil, err + } + + // Get the current server side restrictions. + outRestrictions, err := m.cfg.LoopOutRestrictions(ctx) + if err != nil { + return nil, err + } + + var suggestions []*LoopOutRecommendation + for _, channel := range channels { + channelID := lnwire.NewShortChanIDFromInt(channel.ChannelID) + rule, ok := m.params.ChannelRules[channelID] + if !ok { + continue + } + + balance := newBalances(channel) + + suggestion := rule.suggestSwap(balance, outRestrictions) + + // We can have nil suggestions in the case where no action is + // required, so only add non-nil suggestions. + if suggestion != nil { + suggestions = append(suggestions, suggestion) + } + } + + return suggestions, nil +} diff --git a/liquidity/liquidity_test.go b/liquidity/liquidity_test.go index 774109d..67fe1f8 100644 --- a/liquidity/liquidity_test.go +++ b/liquidity/liquidity_test.go @@ -4,6 +4,8 @@ import ( "context" "testing" + "github.com/lightninglabs/lndclient" + "github.com/lightninglabs/loop/test" "github.com/lightningnetwork/lnd/lnwire" "github.com/stretchr/testify/require" ) @@ -16,6 +18,7 @@ func newTestConfig() *Config { return NewRestrictions(1, 10000), nil }, + Lnd: test.NewMockLnd().Client, } } @@ -64,3 +67,89 @@ func TestParameters(t *testing.T) { err = manager.SetParameters(expected) require.Equal(t, ErrZeroChannelID, err) } + +// TestSuggestSwaps tests getting of swap suggestions. +func TestSuggestSwaps(t *testing.T) { + var ( + chanID1 = lnwire.NewShortChanIDFromInt(1) + chanID2 = lnwire.NewShortChanIDFromInt(2) + ) + + tests := []struct { + name string + channels []lndclient.ChannelInfo + parameters Parameters + swaps []*LoopOutRecommendation + }{ + { + name: "no rules", + channels: nil, + parameters: newParameters(), + }, + { + name: "loop out", + channels: []lndclient.ChannelInfo{ + { + ChannelID: 1, + Capacity: 1000, + LocalBalance: 1000, + RemoteBalance: 0, + }, + }, + parameters: Parameters{ + ChannelRules: map[lnwire.ShortChannelID]*ThresholdRule{ + chanID1: NewThresholdRule( + 10, 10, + ), + }, + }, + swaps: []*LoopOutRecommendation{ + { + Channel: chanID1, + Amount: 500, + }, + }, + }, + { + name: "no rule for channel", + channels: []lndclient.ChannelInfo{ + { + ChannelID: 1, + Capacity: 1000, + LocalBalance: 0, + RemoteBalance: 1000, + }, + }, + parameters: Parameters{ + ChannelRules: map[lnwire.ShortChannelID]*ThresholdRule{ + chanID2: NewThresholdRule(10, 10), + }, + }, + swaps: nil, + }, + } + + for _, testCase := range tests { + testCase := testCase + + t.Run(testCase.name, func(t *testing.T) { + cfg := newTestConfig() + + // Create a mock lnd with the set of channels set in our + // test case. + mock := test.NewMockLnd() + mock.Channels = testCase.channels + cfg.Lnd = mock.Client + + manager := NewManager(cfg) + + // Set our test case parameters. + err := manager.SetParameters(testCase.parameters) + require.NoError(t, err) + + swaps, err := manager.SuggestSwaps(context.Background()) + require.NoError(t, err) + require.Equal(t, testCase.swaps, swaps) + }) + } +} diff --git a/liquidity/suggestions.go b/liquidity/suggestions.go new file mode 100644 index 0000000..681d105 --- /dev/null +++ b/liquidity/suggestions.go @@ -0,0 +1,26 @@ +package liquidity + +import ( + "github.com/btcsuite/btcutil" + "github.com/lightningnetwork/lnd/lnwire" +) + +// LoopOutRecommendation contains the information required to recommend a loop +// out. +type LoopOutRecommendation struct { + // Amount is the total amount to swap. + Amount btcutil.Amount + + // Channel is the target outgoing channel. + Channel lnwire.ShortChannelID +} + +// newLoopOutRecommendation creates a new loop out swap. +func newLoopOutRecommendation(amount btcutil.Amount, + channelID lnwire.ShortChannelID) *LoopOutRecommendation { + + return &LoopOutRecommendation{ + Amount: amount, + Channel: channelID, + } +} diff --git a/liquidity/threshold_rule.go b/liquidity/threshold_rule.go index eac0429..559449d 100644 --- a/liquidity/threshold_rule.go +++ b/liquidity/threshold_rule.go @@ -3,6 +3,8 @@ package liquidity import ( "errors" "fmt" + + "github.com/btcsuite/btcutil" ) var ( @@ -59,3 +61,88 @@ func (r *ThresholdRule) validate() error { return nil } + +// suggestSwap suggests a swap based on the liquidity thresholds configured, +// returning nil if no swap is recommended. +func (r *ThresholdRule) suggestSwap(channel *balances, + outRestrictions *Restrictions) *LoopOutRecommendation { + + // Examine our total balance and required ratios to decide whether we + // need to swap. + amount := loopOutSwapAmount( + channel, r.MinimumIncoming, r.MinimumOutgoing, + ) + + // Limit our swap amount by the minimum/maximum thresholds set. + switch { + case amount < outRestrictions.Minimum: + return nil + + case amount > outRestrictions.Maximum: + return newLoopOutRecommendation( + outRestrictions.Maximum, channel.channelID, + ) + + default: + return newLoopOutRecommendation( + amount, channel.channelID, + ) + } +} + +// loopOutSwapAmount determines whether we can perform a loop out swap, and +// returns the amount we need to swap to reach the desired liquidity balance +// specified by the incoming and outgoing thresholds. +func loopOutSwapAmount(balances *balances, incomingThresholdPercent, + outgoingThresholdPercent int) btcutil.Amount { + + minimumIncoming := btcutil.Amount(uint64( + balances.capacity) * + uint64(incomingThresholdPercent) / 100, + ) + + minimumOutgoing := btcutil.Amount( + uint64(balances.capacity) * + uint64(outgoingThresholdPercent) / 100, + ) + + switch { + // If we have sufficient incoming capacity, we do not need to loop out. + case balances.incoming >= minimumIncoming: + return 0 + + // If we are already below the threshold set for outgoing capacity, we + // cannot take any further action. + case balances.outgoing <= minimumOutgoing: + return 0 + + } + + // Express our minimum outgoing amount as a maximum incoming amount. + // We will use this value to limit the amount that we swap, so that we + // do not dip below our outgoing threshold. + maximumIncoming := balances.capacity - minimumOutgoing + + // Calculate the midpoint between our minimum and maximum incoming + // values. We will aim to swap this amount so that we do not tip our + // outgoing balance beneath the desired level. + midpoint := (minimumIncoming + maximumIncoming) / 2 + + // Calculate the amount of incoming balance we need to shift to reach + // this desired midpoint. + required := midpoint - balances.incoming + + // Since we can have pending htlcs on our channel, we check the amount + // of outbound capacity that we can shift before we fall below our + // threshold. + available := balances.outgoing - minimumOutgoing + + // If we do not have enough balance available to reach our midpoint, we + // take no action. This is the case when we have a large portion of + // pending htlcs. + if available < required { + return 0 + } + + return required +} diff --git a/liquidity/threshold_rule_test.go b/liquidity/threshold_rule_test.go index d94f629..ea6aa1e 100644 --- a/liquidity/threshold_rule_test.go +++ b/liquidity/threshold_rule_test.go @@ -3,6 +3,7 @@ package liquidity import ( "testing" + "github.com/btcsuite/btcutil" "github.com/stretchr/testify/require" ) @@ -91,3 +92,164 @@ func TestValidateThreshold(t *testing.T) { }) } } + +// TestLoopOutAmount tests assessing of a set of balances to determine whether +// we should perform a loop out. +func TestLoopOutAmount(t *testing.T) { + tests := []struct { + name string + minIncoming int + minOutgoing int + balances *balances + amt btcutil.Amount + }{ + { + name: "insufficient surplus", + balances: &balances{ + capacity: 100, + incoming: 20, + outgoing: 20, + }, + minOutgoing: 40, + minIncoming: 40, + amt: 0, + }, + { + name: "loop out", + balances: &balances{ + capacity: 100, + incoming: 20, + outgoing: 80, + }, + minOutgoing: 20, + minIncoming: 60, + amt: 50, + }, + { + name: "pending htlcs", + balances: &balances{ + capacity: 100, + incoming: 20, + outgoing: 30, + }, + minOutgoing: 20, + minIncoming: 60, + amt: 0, + }, + { + name: "loop in", + balances: &balances{ + capacity: 100, + incoming: 50, + outgoing: 50, + }, + minOutgoing: 60, + minIncoming: 30, + amt: 0, + }, + { + name: "liquidity ok", + balances: &balances{ + capacity: 100, + incoming: 50, + outgoing: 50, + }, + minOutgoing: 40, + minIncoming: 40, + amt: 0, + }, + } + + for _, test := range tests { + test := test + + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + amt := loopOutSwapAmount( + test.balances, test.minIncoming, + test.minOutgoing, + ) + require.Equal(t, test.amt, amt) + }) + } +} + +// TestSuggestSwaps tests swap suggestions for the threshold rule. It does not +// many different values because we have separate tests for swap amount +// calculation. +func TestSuggestSwap(t *testing.T) { + tests := []struct { + name string + rule *ThresholdRule + channel *balances + outRestrictions *Restrictions + swap *LoopOutRecommendation + }{ + { + name: "liquidity ok", + rule: NewThresholdRule(10, 10), + outRestrictions: NewRestrictions(10, 100), + channel: &balances{ + capacity: 100, + incoming: 50, + outgoing: 50, + }, + }, + { + name: "loop out", + rule: NewThresholdRule(40, 40), + outRestrictions: NewRestrictions(10, 100), + channel: &balances{ + capacity: 100, + incoming: 0, + outgoing: 100, + }, + swap: &LoopOutRecommendation{Amount: 50}, + }, + { + name: "amount below minimum", + rule: NewThresholdRule(40, 40), + outRestrictions: NewRestrictions(200, 300), + channel: &balances{ + capacity: 100, + incoming: 0, + outgoing: 100, + }, + swap: nil, + }, + { + name: "amount above maximum", + rule: NewThresholdRule(40, 40), + outRestrictions: NewRestrictions(10, 20), + channel: &balances{ + capacity: 100, + incoming: 0, + outgoing: 100, + }, + swap: &LoopOutRecommendation{Amount: 20}, + }, + { + name: "loop in", + rule: NewThresholdRule(10, 10), + outRestrictions: NewRestrictions(10, 100), + channel: &balances{ + capacity: 100, + incoming: 100, + outgoing: 0, + }, + swap: nil, + }, + } + + for _, test := range tests { + test := test + + t.Run(test.name, func(t *testing.T) { + swap := test.rule.suggestSwap( + test.channel, test.outRestrictions, + ) + require.Equal(t, test.swap, swap) + }) + } +} diff --git a/loopd/swapclient_server.go b/loopd/swapclient_server.go index d604394..b7a28c2 100644 --- a/loopd/swapclient_server.go +++ b/loopd/swapclient_server.go @@ -632,6 +632,32 @@ func rpcToRule(rule *looprpc.LiquidityRule) (*liquidity.ThresholdRule, error) { } +// SuggestSwaps provides a list of suggested swaps based on lnd's current +// channel balances and rules set by the liquidity manager. +func (s *swapClientServer) SuggestSwaps(ctx context.Context, + _ *looprpc.SuggestSwapsRequest) (*looprpc.SuggestSwapsResponse, error) { + + swaps, err := s.liquidityMgr.SuggestSwaps(ctx) + if err != nil { + return nil, err + } + + var loopOut []*looprpc.LoopOutRequest + + for _, swap := range swaps { + loopOut = append(loopOut, &looprpc.LoopOutRequest{ + Amt: int64(swap.Amount), + OutgoingChanSet: []uint64{ + swap.Channel.ToUint64(), + }, + }) + } + + return &looprpc.SuggestSwapsResponse{ + LoopOut: loopOut, + }, nil +} + // processStatusUpdates reads updates on the status channel and processes them. // // NOTE: This must run inside a goroutine as it blocks until the main context diff --git a/loopd/utils.go b/loopd/utils.go index b0116a4..42278bc 100644 --- a/loopd/utils.go +++ b/loopd/utils.go @@ -46,6 +46,7 @@ func getLiquidityManager(client *loop.Client) *liquidity.Manager { outTerms.MinSwapAmount, outTerms.MaxSwapAmount, ), nil }, + Lnd: client.LndServices.Client, } return liquidity.NewManager(mngrCfg) diff --git a/looprpc/client.pb.go b/looprpc/client.pb.go index 8f9591e..f1c2bd5 100644 --- a/looprpc/client.pb.go +++ b/looprpc/client.pb.go @@ -1732,6 +1732,78 @@ func (m *SetLiquidityParamsResponse) XXX_DiscardUnknown() { var xxx_messageInfo_SetLiquidityParamsResponse proto.InternalMessageInfo +type SuggestSwapsRequest struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *SuggestSwapsRequest) Reset() { *m = SuggestSwapsRequest{} } +func (m *SuggestSwapsRequest) String() string { return proto.CompactTextString(m) } +func (*SuggestSwapsRequest) ProtoMessage() {} +func (*SuggestSwapsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_014de31d7ac8c57c, []int{22} +} + +func (m *SuggestSwapsRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_SuggestSwapsRequest.Unmarshal(m, b) +} +func (m *SuggestSwapsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_SuggestSwapsRequest.Marshal(b, m, deterministic) +} +func (m *SuggestSwapsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_SuggestSwapsRequest.Merge(m, src) +} +func (m *SuggestSwapsRequest) XXX_Size() int { + return xxx_messageInfo_SuggestSwapsRequest.Size(m) +} +func (m *SuggestSwapsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_SuggestSwapsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_SuggestSwapsRequest proto.InternalMessageInfo + +type SuggestSwapsResponse struct { + // + //The set of recommended loop outs. + LoopOut []*LoopOutRequest `protobuf:"bytes,1,rep,name=loop_out,json=loopOut,proto3" json:"loop_out,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *SuggestSwapsResponse) Reset() { *m = SuggestSwapsResponse{} } +func (m *SuggestSwapsResponse) String() string { return proto.CompactTextString(m) } +func (*SuggestSwapsResponse) ProtoMessage() {} +func (*SuggestSwapsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_014de31d7ac8c57c, []int{23} +} + +func (m *SuggestSwapsResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_SuggestSwapsResponse.Unmarshal(m, b) +} +func (m *SuggestSwapsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_SuggestSwapsResponse.Marshal(b, m, deterministic) +} +func (m *SuggestSwapsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_SuggestSwapsResponse.Merge(m, src) +} +func (m *SuggestSwapsResponse) XXX_Size() int { + return xxx_messageInfo_SuggestSwapsResponse.Size(m) +} +func (m *SuggestSwapsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_SuggestSwapsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_SuggestSwapsResponse proto.InternalMessageInfo + +func (m *SuggestSwapsResponse) GetLoopOut() []*LoopOutRequest { + if m != nil { + return m.LoopOut + } + return nil +} + func init() { proto.RegisterEnum("looprpc.SwapType", SwapType_name, SwapType_value) proto.RegisterEnum("looprpc.SwapState", SwapState_name, SwapState_value) @@ -1759,142 +1831,148 @@ func init() { proto.RegisterType((*LiquidityRule)(nil), "looprpc.LiquidityRule") proto.RegisterType((*SetLiquidityParamsRequest)(nil), "looprpc.SetLiquidityParamsRequest") proto.RegisterType((*SetLiquidityParamsResponse)(nil), "looprpc.SetLiquidityParamsResponse") + proto.RegisterType((*SuggestSwapsRequest)(nil), "looprpc.SuggestSwapsRequest") + proto.RegisterType((*SuggestSwapsResponse)(nil), "looprpc.SuggestSwapsResponse") } func init() { proto.RegisterFile("client.proto", fileDescriptor_014de31d7ac8c57c) } var fileDescriptor_014de31d7ac8c57c = []byte{ - // 2073 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x58, 0xdd, 0x6e, 0x23, 0x49, - 0x15, 0x1e, 0xff, 0xc5, 0xf6, 0x71, 0xbb, 0xdd, 0xa9, 0xcc, 0x26, 0x8e, 0x37, 0x68, 0x32, 0x3d, - 0x3b, 0x90, 0x0d, 0xbb, 0x63, 0x36, 0x7b, 0xc5, 0x68, 0x41, 0xf2, 0x38, 0xce, 0xc6, 0x43, 0x62, - 0x9b, 0xb6, 0x33, 0xab, 0x41, 0x48, 0xad, 0x8a, 0x5d, 0x89, 0x5b, 0xf4, 0xdf, 0x74, 0x97, 0x67, - 0x12, 0xad, 0x00, 0x89, 0x17, 0xd8, 0x0b, 0xde, 0x80, 0x67, 0xe0, 0x0e, 0x1e, 0x81, 0x2b, 0x78, - 0x05, 0x24, 0xc4, 0x05, 0x77, 0x3c, 0x00, 0xaa, 0x53, 0xdd, 0xed, 0x6e, 0xc7, 0x09, 0xe2, 0x82, - 0xbb, 0xf4, 0x77, 0xbe, 0x3a, 0x55, 0xe7, 0xff, 0x38, 0xa0, 0x4c, 0x6d, 0x8b, 0xb9, 0xfc, 0x85, - 0x1f, 0x78, 0xdc, 0x23, 0x65, 0xdb, 0xf3, 0xfc, 0xc0, 0x9f, 0xb6, 0xf6, 0xae, 0x3d, 0xef, 0xda, - 0x66, 0x6d, 0xea, 0x5b, 0x6d, 0xea, 0xba, 0x1e, 0xa7, 0xdc, 0xf2, 0xdc, 0x50, 0xd2, 0xf4, 0xef, - 0x8a, 0xa0, 0x9e, 0x79, 0x9e, 0x3f, 0x5c, 0x70, 0x83, 0xbd, 0x5b, 0xb0, 0x90, 0x13, 0x0d, 0x0a, - 0xd4, 0xe1, 0xcd, 0xdc, 0x7e, 0xee, 0xa0, 0x60, 0x88, 0x3f, 0x09, 0x81, 0xe2, 0x8c, 0x85, 0xbc, - 0x99, 0xdf, 0xcf, 0x1d, 0x54, 0x0d, 0xfc, 0x9b, 0xb4, 0xe1, 0xb1, 0x43, 0x6f, 0xcc, 0xf0, 0x03, - 0xf5, 0xcd, 0xc0, 0x5b, 0x70, 0xcb, 0xbd, 0x36, 0xaf, 0x18, 0x6b, 0x16, 0xf0, 0xd8, 0xa6, 0x43, - 0x6f, 0xc6, 0x1f, 0xa8, 0x6f, 0x48, 0xc9, 0x09, 0x63, 0xe4, 0x4b, 0xd8, 0x16, 0x07, 0xfc, 0x80, - 0xf9, 0xf4, 0x36, 0x73, 0xa4, 0x88, 0x47, 0xb6, 0x1c, 0x7a, 0x33, 0x42, 0x61, 0xea, 0xd0, 0x3e, - 0x28, 0xc9, 0x2d, 0x82, 0x5a, 0x42, 0x2a, 0x44, 0xda, 0x05, 0xe3, 0x13, 0x50, 0x53, 0x6a, 0xc5, - 0xc3, 0x37, 0x90, 0xa3, 0x24, 0xea, 0x3a, 0x0e, 0x27, 0x3a, 0xd4, 0x05, 0xcb, 0xb1, 0x5c, 0x16, - 0xa0, 0xa2, 0x32, 0x92, 0x6a, 0x0e, 0xbd, 0x39, 0x17, 0x98, 0xd0, 0xf4, 0x19, 0x68, 0xc2, 0x67, - 0xa6, 0xb7, 0xe0, 0xe6, 0x74, 0x4e, 0x5d, 0x97, 0xd9, 0xcd, 0xca, 0x7e, 0xee, 0xa0, 0xf8, 0x2a, - 0xdf, 0xcc, 0x19, 0xaa, 0x2d, 0xbd, 0xd4, 0x95, 0x12, 0x72, 0x08, 0x9b, 0xde, 0x82, 0x5f, 0x7b, - 0xc2, 0x08, 0xc1, 0x36, 0x43, 0xc6, 0x9b, 0xb5, 0xfd, 0xc2, 0x41, 0xd1, 0x68, 0xc4, 0x02, 0xc1, - 0x1d, 0x33, 0x2e, 0xb8, 0xe1, 0x07, 0xc6, 0x7c, 0x73, 0xea, 0xb9, 0x57, 0x26, 0xa7, 0xc1, 0x35, - 0xe3, 0xcd, 0xea, 0x7e, 0xee, 0xa0, 0x64, 0x34, 0x50, 0xd0, 0xf5, 0xdc, 0xab, 0x09, 0xc2, 0xe4, - 0x73, 0x20, 0x73, 0x6e, 0x4f, 0x91, 0x6a, 0x05, 0x8e, 0x0c, 0x56, 0xb3, 0x8e, 0xe4, 0x4d, 0x21, - 0xe9, 0xa6, 0x05, 0xe4, 0x25, 0xec, 0xa2, 0x73, 0xfc, 0xc5, 0xa5, 0x6d, 0x4d, 0x11, 0x34, 0x67, - 0x8c, 0xce, 0x6c, 0xcb, 0x65, 0x4d, 0x10, 0xaf, 0x37, 0x76, 0x04, 0x61, 0xb4, 0x94, 0x1f, 0x47, - 0x62, 0xf2, 0x18, 0x4a, 0x36, 0xbd, 0x64, 0x76, 0x53, 0xc1, 0xb8, 0xca, 0x0f, 0xfd, 0x1f, 0x39, - 0xa8, 0x8b, 0x8c, 0xe8, 0xbb, 0xf7, 0x27, 0xc4, 0x6a, 0x58, 0xf2, 0x77, 0xc2, 0x72, 0xc7, 0xe1, - 0x85, 0xbb, 0x0e, 0xdf, 0x85, 0x8a, 0x4d, 0x43, 0x6e, 0xce, 0x3d, 0x1f, 0x73, 0x40, 0x31, 0xca, - 0xe2, 0xfb, 0xd4, 0xf3, 0xc9, 0x33, 0xa8, 0xb3, 0x1b, 0xce, 0x02, 0x97, 0xda, 0xa6, 0x30, 0x1a, - 0x03, 0x5f, 0x31, 0x94, 0x18, 0x3c, 0xe5, 0xf6, 0x94, 0x1c, 0x80, 0x96, 0xb8, 0x2a, 0xf6, 0xea, - 0x06, 0x3a, 0x4a, 0x8d, 0x1d, 0x15, 0x39, 0x35, 0xb1, 0xb4, 0x9c, 0xb6, 0xf4, 0x9f, 0x39, 0x50, - 0x30, 0x49, 0x59, 0xe8, 0x7b, 0x6e, 0xc8, 0x08, 0x81, 0xbc, 0x35, 0x43, 0x3b, 0xab, 0x18, 0xf3, - 0xbc, 0x35, 0x13, 0x8f, 0xb4, 0x66, 0xe6, 0xe5, 0x2d, 0x67, 0x21, 0xda, 0xa0, 0x18, 0x65, 0x6b, - 0xf6, 0x4a, 0x7c, 0x92, 0xe7, 0xa0, 0xe0, 0xfd, 0x74, 0x36, 0x0b, 0x58, 0x18, 0xca, 0xf2, 0xc0, - 0x83, 0x35, 0x81, 0x77, 0x24, 0x4c, 0x5e, 0xc0, 0x56, 0x9a, 0x66, 0xba, 0xfe, 0xd1, 0x87, 0x70, - 0x8e, 0x16, 0x57, 0x65, 0x48, 0x23, 0xe6, 0x00, 0x05, 0xe4, 0xb3, 0x28, 0x03, 0x62, 0xbe, 0xa4, - 0x97, 0x90, 0xae, 0xa5, 0xe8, 0x23, 0x64, 0x3f, 0x07, 0x35, 0x64, 0xc1, 0x7b, 0x16, 0x98, 0x0e, - 0x0b, 0x43, 0x7a, 0xcd, 0xd0, 0x05, 0x55, 0xa3, 0x2e, 0xd1, 0x73, 0x09, 0xea, 0x1a, 0xa8, 0xe7, - 0x9e, 0x6b, 0x71, 0x2f, 0x88, 0xa2, 0xaa, 0xff, 0xb1, 0x08, 0x20, 0xac, 0x1f, 0x73, 0xca, 0x17, - 0xe1, 0xda, 0xaa, 0x17, 0xde, 0xc8, 0xdf, 0xeb, 0x8d, 0xda, 0xaa, 0x37, 0x8a, 0xfc, 0xd6, 0x97, - 0x81, 0x56, 0x8f, 0x36, 0x5f, 0x44, 0xfd, 0xe7, 0x85, 0xb8, 0x63, 0x72, 0xeb, 0x33, 0x03, 0xc5, - 0xe4, 0x00, 0x4a, 0x21, 0xa7, 0x5c, 0x56, 0xbd, 0x7a, 0x44, 0x32, 0x3c, 0xf1, 0x16, 0x66, 0x48, - 0x02, 0xf9, 0x09, 0xa8, 0x57, 0xd4, 0xb2, 0x17, 0x01, 0x33, 0x03, 0x46, 0x43, 0xcf, 0x6d, 0xaa, - 0x78, 0x64, 0x3b, 0x39, 0x72, 0x22, 0xc5, 0x06, 0x4a, 0x8d, 0xfa, 0x55, 0xfa, 0x93, 0xfc, 0x00, - 0x1a, 0x96, 0x6b, 0x71, 0x4b, 0xd6, 0x04, 0xb7, 0x9c, 0xb8, 0x7b, 0xa8, 0x4b, 0x78, 0x62, 0x39, - 0xe2, 0x45, 0x1a, 0xa6, 0xe1, 0xc2, 0x9f, 0x51, 0xce, 0x24, 0x53, 0xf6, 0x10, 0x55, 0xe0, 0x17, - 0x08, 0x23, 0x73, 0x35, 0xe0, 0xe5, 0xf5, 0x01, 0x5f, 0x1f, 0x40, 0xe5, 0x9e, 0x00, 0xde, 0x93, - 0x1e, 0xf5, 0xfb, 0xd2, 0xe3, 0x09, 0xd4, 0xa6, 0x5e, 0xc8, 0x4d, 0x19, 0x5f, 0xec, 0x50, 0x05, - 0x03, 0x04, 0x34, 0x46, 0x84, 0x3c, 0x05, 0x05, 0x09, 0x9e, 0x3b, 0x9d, 0x53, 0xcb, 0xc5, 0x46, - 0x53, 0x30, 0xf0, 0xd0, 0x50, 0x42, 0xa2, 0xbc, 0x24, 0xe5, 0xea, 0x4a, 0x72, 0x40, 0xf6, 0x4c, - 0xe4, 0x44, 0xd8, 0xb2, 0x68, 0x1a, 0xe9, 0xa2, 0x21, 0xa0, 0x9d, 0x59, 0x21, 0x17, 0xd1, 0x0a, - 0xe3, 0x54, 0xfa, 0x29, 0x6c, 0xa6, 0xb0, 0xa8, 0x98, 0x3e, 0x85, 0x92, 0xe8, 0x0f, 0x61, 0x33, - 0xb7, 0x5f, 0x38, 0xa8, 0x1d, 0x6d, 0xdd, 0x09, 0xf4, 0x22, 0x34, 0x24, 0x43, 0x7f, 0x0a, 0x0d, - 0x01, 0xf6, 0xdd, 0x2b, 0x2f, 0xee, 0x39, 0x6a, 0x52, 0x8a, 0x8a, 0x48, 0x3c, 0x5d, 0x05, 0x65, - 0xc2, 0x02, 0x27, 0xb9, 0xf2, 0xb7, 0xd0, 0xe8, 0xbb, 0x11, 0x12, 0x5d, 0xf8, 0x7d, 0x68, 0x38, - 0x96, 0x2b, 0x9b, 0x12, 0x75, 0xbc, 0x85, 0xcb, 0xa3, 0x80, 0xd7, 0x1d, 0xcb, 0x15, 0xfa, 0x3b, - 0x08, 0x22, 0x2f, 0x6e, 0x5e, 0x11, 0x6f, 0x23, 0xe2, 0xc9, 0xfe, 0x25, 0x79, 0xaf, 0x8b, 0x95, - 0x9c, 0x96, 0x7f, 0x5d, 0xac, 0xe4, 0xb5, 0xc2, 0xeb, 0x62, 0xa5, 0xa0, 0x15, 0x5f, 0x17, 0x2b, - 0x45, 0xad, 0xf4, 0xba, 0x58, 0x29, 0x6b, 0x15, 0xfd, 0x2f, 0x39, 0xd0, 0x86, 0x0b, 0xfe, 0x7f, - 0x7d, 0x02, 0x0e, 0x37, 0xcb, 0x35, 0xa7, 0x36, 0x7f, 0x6f, 0xce, 0x98, 0xcd, 0x29, 0x86, 0xbb, - 0x64, 0x28, 0x8e, 0xe5, 0x76, 0x6d, 0xfe, 0xfe, 0x58, 0x60, 0xf1, 0x08, 0x4c, 0xb1, 0xaa, 0x11, - 0x8b, 0xde, 0x24, 0xac, 0xff, 0x62, 0xce, 0x1f, 0x72, 0xa0, 0xfc, 0x7c, 0xe1, 0x71, 0x76, 0x7f, - 0xd3, 0xc7, 0xc4, 0x5b, 0x76, 0xda, 0x3c, 0xde, 0x01, 0xd3, 0x65, 0x97, 0xbd, 0xd3, 0xb4, 0x0b, - 0x6b, 0x9a, 0xf6, 0x83, 0x03, 0xab, 0xf8, 0xe0, 0xc0, 0xd2, 0xbf, 0xcb, 0x89, 0xa8, 0x47, 0xcf, - 0x8c, 0x5c, 0xbe, 0x0f, 0x4a, 0x3c, 0x86, 0xcc, 0x90, 0xc6, 0x0f, 0x86, 0x50, 0xce, 0xa1, 0x31, - 0xc5, 0x4d, 0x05, 0x0b, 0x0c, 0x6f, 0x0c, 0xe7, 0x09, 0x33, 0xda, 0x54, 0x84, 0x6c, 0x24, 0x45, - 0xd1, 0x81, 0xef, 0x01, 0xa4, 0x7c, 0x59, 0x42, 0x3b, 0xab, 0xd3, 0x94, 0x23, 0xa5, 0x0b, 0x8b, - 0x5a, 0x49, 0xff, 0xab, 0xcc, 0x82, 0xff, 0xf5, 0x49, 0x9f, 0x80, 0xba, 0x5c, 0x58, 0x90, 0x23, - 0x27, 0xa8, 0xe2, 0xc7, 0x1b, 0x8b, 0x60, 0xfd, 0x30, 0xea, 0x23, 0x72, 0x77, 0xc8, 0x3e, 0xbb, - 0x21, 0x24, 0x63, 0x21, 0x88, 0x54, 0xe2, 0x8e, 0x21, 0xfc, 0x4a, 0x6f, 0x1d, 0xe6, 0x72, 0x13, - 0x17, 0x36, 0x39, 0x55, 0x1b, 0xe8, 0x4f, 0x89, 0x1f, 0x8b, 0xd8, 0x3e, 0x6c, 0xa0, 0xde, 0x80, - 0xfa, 0xc4, 0xfb, 0x15, 0x73, 0x93, 0x62, 0xfb, 0x0a, 0xd4, 0x18, 0x88, 0x4c, 0x3c, 0x84, 0x0d, - 0x8e, 0x48, 0x54, 0xdd, 0xcb, 0x36, 0x7e, 0x16, 0x52, 0x8e, 0x64, 0x23, 0x62, 0xe8, 0x7f, 0xca, - 0x43, 0x35, 0x41, 0x45, 0x92, 0x5c, 0xd2, 0x90, 0x99, 0x0e, 0x9d, 0xd2, 0xc0, 0xf3, 0xdc, 0xa8, - 0xc6, 0x15, 0x01, 0x9e, 0x47, 0x98, 0x68, 0x61, 0xb1, 0x1d, 0x73, 0x1a, 0xce, 0xd1, 0x3b, 0x8a, - 0x51, 0x8b, 0xb0, 0x53, 0x1a, 0xce, 0xc9, 0xa7, 0xa0, 0xc5, 0x14, 0x3f, 0x60, 0x96, 0x23, 0x26, - 0x9f, 0x9c, 0xcf, 0x8d, 0x08, 0x1f, 0x45, 0xb0, 0x68, 0xf0, 0xb2, 0xc8, 0x4c, 0x9f, 0x5a, 0x33, - 0xd3, 0x11, 0x5e, 0x94, 0x3b, 0xa7, 0x2a, 0xf1, 0x11, 0xb5, 0x66, 0xe7, 0x21, 0xe5, 0xe4, 0x0b, - 0xf8, 0x28, 0xb5, 0x98, 0xa6, 0xe8, 0xb2, 0x8a, 0x49, 0x90, 0x6c, 0xa6, 0xc9, 0x91, 0xa7, 0xa0, - 0x88, 0x89, 0x61, 0x4e, 0x03, 0x46, 0x39, 0x9b, 0x45, 0x75, 0x5c, 0x13, 0x58, 0x57, 0x42, 0xa4, - 0x09, 0x65, 0x76, 0xe3, 0x5b, 0x01, 0x9b, 0xe1, 0xc4, 0xa8, 0x18, 0xf1, 0xa7, 0x38, 0x1c, 0x72, - 0x2f, 0xa0, 0xd7, 0xcc, 0x74, 0xa9, 0xc3, 0xb0, 0xba, 0xab, 0x46, 0x2d, 0xc2, 0x06, 0xd4, 0x61, - 0xfa, 0xc7, 0xb0, 0xfb, 0x35, 0xe3, 0x67, 0xd6, 0xbb, 0x85, 0x35, 0xb3, 0xf8, 0xed, 0x88, 0x06, - 0x74, 0xd9, 0x05, 0xbb, 0xb0, 0x95, 0x95, 0x30, 0xce, 0x02, 0x31, 0x80, 0x4a, 0xc1, 0xc2, 0x66, - 0x71, 0x70, 0x96, 0x03, 0x33, 0x21, 0x1b, 0x0b, 0x9b, 0x19, 0x92, 0xa4, 0xff, 0x59, 0x2c, 0x7c, - 0x69, 0x01, 0xe6, 0x87, 0x5c, 0x73, 0xcd, 0xa8, 0x09, 0x17, 0x8d, 0x6a, 0x84, 0xf4, 0x67, 0xe4, - 0x45, 0x34, 0xe9, 0xf3, 0x38, 0x8e, 0x5b, 0xeb, 0xb5, 0xa7, 0x46, 0xfe, 0xe7, 0x40, 0x2c, 0x77, - 0xea, 0x39, 0xc2, 0xad, 0x7c, 0x1e, 0xb0, 0x70, 0xee, 0xd9, 0x33, 0x0c, 0x56, 0xdd, 0xd8, 0x8c, - 0x25, 0x93, 0x58, 0x20, 0xe8, 0xc9, 0x66, 0xbd, 0xa4, 0x17, 0x25, 0x3d, 0x96, 0x24, 0x74, 0xfd, - 0x2d, 0xec, 0x8e, 0xef, 0x73, 0x10, 0xf9, 0x0a, 0xc0, 0x4f, 0xfc, 0x82, 0x96, 0xd4, 0x8e, 0xf6, - 0xee, 0x3e, 0x78, 0xe9, 0x3b, 0x23, 0xc5, 0xd7, 0xf7, 0xa0, 0xb5, 0x4e, 0xb5, 0xac, 0x81, 0xc3, - 0xe7, 0x50, 0x89, 0x77, 0x1b, 0xa2, 0x40, 0xe5, 0x6c, 0x38, 0x1c, 0x99, 0xc3, 0x8b, 0x89, 0xf6, - 0x88, 0xd4, 0xa0, 0x8c, 0x5f, 0xfd, 0x81, 0x96, 0x3b, 0x0c, 0xa1, 0x9a, 0xac, 0x36, 0xa4, 0x0e, - 0xd5, 0xfe, 0xa0, 0x3f, 0xe9, 0x77, 0x26, 0xbd, 0x63, 0xed, 0x11, 0xf9, 0x08, 0x36, 0x47, 0x46, - 0xaf, 0x7f, 0xde, 0xf9, 0xba, 0x67, 0x1a, 0xbd, 0x37, 0xbd, 0xce, 0x59, 0xef, 0x58, 0xcb, 0x11, - 0x02, 0xea, 0xe9, 0xe4, 0xac, 0x6b, 0x8e, 0x2e, 0x5e, 0x9d, 0xf5, 0xc7, 0xa7, 0xbd, 0x63, 0x2d, - 0x2f, 0x74, 0x8e, 0x2f, 0xba, 0xdd, 0xde, 0x78, 0xac, 0x15, 0x08, 0xc0, 0xc6, 0x49, 0xa7, 0x2f, - 0xc8, 0x45, 0xb2, 0x05, 0x8d, 0xfe, 0xe0, 0xcd, 0xb0, 0xdf, 0xed, 0x99, 0xe3, 0xde, 0x64, 0x22, - 0xc0, 0xd2, 0xe1, 0xbf, 0x72, 0x50, 0xcf, 0x6c, 0x47, 0x64, 0x07, 0xb6, 0xc4, 0x91, 0x0b, 0x43, - 0xdc, 0xd4, 0x19, 0x0f, 0x07, 0xe6, 0x60, 0x38, 0xe8, 0x69, 0x8f, 0xc8, 0xc7, 0xb0, 0xb3, 0x22, - 0x18, 0x9e, 0x9c, 0x74, 0x4f, 0x3b, 0xe2, 0xf1, 0xa4, 0x05, 0xdb, 0x2b, 0xc2, 0x49, 0xff, 0xbc, - 0x27, 0xac, 0xcc, 0x93, 0x7d, 0xd8, 0x5b, 0x91, 0x8d, 0xbf, 0xe9, 0xf5, 0x46, 0x09, 0xa3, 0x40, - 0x9e, 0xc3, 0xd3, 0x15, 0x46, 0x7f, 0x30, 0xbe, 0x38, 0x39, 0xe9, 0x77, 0xfb, 0xbd, 0xc1, 0xc4, - 0x7c, 0xd3, 0x39, 0xbb, 0xe8, 0x69, 0x45, 0xb2, 0x07, 0xcd, 0xd5, 0x4b, 0x7a, 0xe7, 0xa3, 0xa1, - 0xd1, 0x31, 0xde, 0x6a, 0x25, 0xf2, 0x0c, 0x9e, 0xdc, 0x51, 0xd2, 0x1d, 0x1a, 0x46, 0xaf, 0x3b, - 0x31, 0x3b, 0xe7, 0xc3, 0x8b, 0xc1, 0x44, 0xdb, 0x38, 0x6c, 0x8b, 0x0d, 0x64, 0x25, 0xfb, 0x84, - 0xcb, 0x2e, 0x06, 0x3f, 0x1b, 0x0c, 0xbf, 0x19, 0x68, 0x8f, 0x84, 0xe7, 0x27, 0xa7, 0x46, 0x6f, - 0x7c, 0x3a, 0x3c, 0x3b, 0xd6, 0x72, 0x47, 0xff, 0xae, 0xc8, 0xed, 0xb7, 0x8b, 0xbf, 0x99, 0x89, - 0x01, 0xe5, 0xe8, 0x57, 0x30, 0xd9, 0x59, 0xa6, 0x47, 0xe6, 0x77, 0x71, 0xeb, 0xa3, 0xcc, 0x06, - 0x13, 0xa7, 0x81, 0xbe, 0xf3, 0xbb, 0xbf, 0xfd, 0xfd, 0xf7, 0xf9, 0x4d, 0x5d, 0x69, 0xbf, 0xff, - 0xa2, 0x2d, 0x18, 0x6d, 0x6f, 0xc1, 0x5f, 0xe6, 0x0e, 0xc9, 0x10, 0x36, 0xe4, 0xef, 0x28, 0xb2, - 0x9d, 0x51, 0x99, 0xfc, 0xb0, 0xba, 0x4f, 0xe3, 0x36, 0x6a, 0xd4, 0xf4, 0x5a, 0xa2, 0xd1, 0x72, - 0x85, 0xc2, 0x1f, 0x43, 0x39, 0xda, 0xe1, 0x53, 0x8f, 0xcc, 0x6e, 0xf5, 0xad, 0x75, 0x6b, 0xd6, - 0x8f, 0x72, 0xe4, 0x17, 0x50, 0x4d, 0x36, 0x34, 0xb2, 0x9b, 0x2a, 0x80, 0xec, 0x26, 0xd7, 0x6a, - 0xad, 0x13, 0x65, 0x9f, 0x45, 0xd4, 0xe4, 0x59, 0xb8, 0xbd, 0x91, 0x0b, 0x59, 0x07, 0x62, 0x7b, - 0x23, 0xcd, 0xcc, 0xf5, 0xa9, 0x85, 0x6e, 0xed, 0xc3, 0xf4, 0x16, 0xaa, 0x7c, 0x4c, 0x48, 0x46, - 0x65, 0xfb, 0x5b, 0x6b, 0xf6, 0x6b, 0xf2, 0x4b, 0x50, 0xa2, 0x00, 0xe0, 0x8e, 0x45, 0x96, 0xce, - 0x4a, 0x2f, 0x82, 0xad, 0xa5, 0x31, 0xab, 0xdb, 0xd8, 0x1a, 0xed, 0xde, 0x82, 0xb7, 0x39, 0x6a, - 0xbb, 0x4c, 0xb4, 0xe3, 0xec, 0x4e, 0x69, 0x4f, 0x6f, 0x41, 0x59, 0xed, 0x99, 0x29, 0xaf, 0xef, - 0xa3, 0xf6, 0x16, 0x69, 0x66, 0xb4, 0xbf, 0x13, 0x9c, 0xf6, 0xb7, 0xd4, 0xe1, 0xc2, 0x02, 0x55, - 0xb4, 0x6e, 0x0c, 0xf9, 0x83, 0x36, 0x2c, 0xbd, 0xb6, 0xb2, 0xd3, 0xea, 0xbb, 0x78, 0xc9, 0x16, - 0xd9, 0x4c, 0xa5, 0x42, 0x62, 0xc1, 0x52, 0xfb, 0x83, 0x36, 0xa4, 0xb5, 0x67, 0x4d, 0x78, 0x82, - 0xda, 0x77, 0xc9, 0x4e, 0x5a, 0x7b, 0xda, 0x82, 0xb7, 0x50, 0x17, 0x77, 0xc4, 0xc3, 0x3b, 0x4c, - 0x65, 0x72, 0x66, 0x43, 0x68, 0xed, 0xdc, 0xc1, 0xb3, 0xd5, 0x41, 0x1a, 0x78, 0x45, 0x48, 0x79, - 0x5b, 0x6e, 0x05, 0x84, 0x03, 0xb9, 0x3b, 0xd7, 0x88, 0x9e, 0xe8, 0xb9, 0x77, 0xe8, 0xb5, 0x1e, - 0xec, 0xdf, 0xfa, 0x1e, 0x5e, 0xb8, 0x4d, 0x1e, 0xe3, 0x85, 0x31, 0xa1, 0xed, 0x4b, 0xfd, 0xbf, - 0x01, 0x32, 0x7e, 0xe8, 0xd6, 0x7b, 0x27, 0x49, 0xeb, 0xd9, 0x83, 0x9c, 0xac, 0x43, 0xf5, 0xb5, - 0x97, 0xbf, 0xcc, 0x1d, 0x5e, 0x6e, 0xe0, 0x7f, 0xdd, 0xbe, 0xfc, 0x4f, 0x00, 0x00, 0x00, 0xff, - 0xff, 0x46, 0x7a, 0xcd, 0x4b, 0xac, 0x13, 0x00, 0x00, + // 2133 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x58, 0xcd, 0x6e, 0x23, 0xc7, + 0xf1, 0x5f, 0x7e, 0x89, 0x64, 0x71, 0x48, 0x8e, 0x5a, 0xbb, 0x12, 0x45, 0xcb, 0x58, 0xed, 0xac, + 0xf7, 0xff, 0x97, 0x15, 0x5b, 0x8c, 0xe5, 0x53, 0x0c, 0x27, 0x00, 0x97, 0xa2, 0x2c, 0x2a, 0x12, + 0xc9, 0x0c, 0xa9, 0x35, 0x36, 0x08, 0x30, 0x68, 0x91, 0x2d, 0x71, 0x10, 0xce, 0xc7, 0xce, 0x34, + 0x77, 0x25, 0x18, 0x49, 0x80, 0xbc, 0x80, 0x0f, 0x79, 0x83, 0x3c, 0x43, 0x6e, 0xc9, 0x23, 0xe4, + 0x94, 0x1c, 0x73, 0x0d, 0x10, 0xe4, 0x90, 0x77, 0x08, 0xba, 0xba, 0x67, 0x38, 0xa4, 0x48, 0x05, + 0x39, 0xe4, 0x26, 0xfe, 0xea, 0xd7, 0x55, 0x5d, 0x55, 0x5d, 0x1f, 0x23, 0xd0, 0x46, 0x53, 0x9b, + 0xb9, 0xfc, 0xc8, 0x0f, 0x3c, 0xee, 0x91, 0xfc, 0xd4, 0xf3, 0xfc, 0xc0, 0x1f, 0xd5, 0xf7, 0x6e, + 0x3d, 0xef, 0x76, 0xca, 0x1a, 0xd4, 0xb7, 0x1b, 0xd4, 0x75, 0x3d, 0x4e, 0xb9, 0xed, 0xb9, 0xa1, + 0xa4, 0x19, 0xdf, 0x67, 0xa1, 0x72, 0xe1, 0x79, 0x7e, 0x6f, 0xc6, 0x4d, 0xf6, 0x6e, 0xc6, 0x42, + 0x4e, 0x74, 0xc8, 0x50, 0x87, 0xd7, 0x52, 0xfb, 0xa9, 0x83, 0x8c, 0x29, 0xfe, 0x24, 0x04, 0xb2, + 0x63, 0x16, 0xf2, 0x5a, 0x7a, 0x3f, 0x75, 0x50, 0x34, 0xf1, 0x6f, 0xd2, 0x80, 0xa7, 0x0e, 0xbd, + 0xb3, 0xc2, 0x0f, 0xd4, 0xb7, 0x02, 0x6f, 0xc6, 0x6d, 0xf7, 0xd6, 0xba, 0x61, 0xac, 0x96, 0xc1, + 0x63, 0x9b, 0x0e, 0xbd, 0x1b, 0x7c, 0xa0, 0xbe, 0x29, 0x25, 0xa7, 0x8c, 0x91, 0x2f, 0x61, 0x5b, + 0x1c, 0xf0, 0x03, 0xe6, 0xd3, 0xfb, 0x85, 0x23, 0x59, 0x3c, 0xb2, 0xe5, 0xd0, 0xbb, 0x3e, 0x0a, + 0x13, 0x87, 0xf6, 0x41, 0x8b, 0xad, 0x08, 0x6a, 0x0e, 0xa9, 0xa0, 0xb4, 0x0b, 0xc6, 0x27, 0x50, + 0x49, 0xa8, 0x15, 0x17, 0xdf, 0x40, 0x8e, 0x16, 0xab, 0x6b, 0x3a, 0x9c, 0x18, 0x50, 0x16, 0x2c, + 0xc7, 0x76, 0x59, 0x80, 0x8a, 0xf2, 0x48, 0x2a, 0x39, 0xf4, 0xee, 0x52, 0x60, 0x42, 0xd3, 0x67, + 0xa0, 0x8b, 0x98, 0x59, 0xde, 0x8c, 0x5b, 0xa3, 0x09, 0x75, 0x5d, 0x36, 0xad, 0x15, 0xf6, 0x53, + 0x07, 0xd9, 0xd7, 0xe9, 0x5a, 0xca, 0xac, 0x4c, 0x65, 0x94, 0x5a, 0x52, 0x42, 0x0e, 0x61, 0xd3, + 0x9b, 0xf1, 0x5b, 0x4f, 0x38, 0x21, 0xd8, 0x56, 0xc8, 0x78, 0xad, 0xb4, 0x9f, 0x39, 0xc8, 0x9a, + 0xd5, 0x48, 0x20, 0xb8, 0x03, 0xc6, 0x05, 0x37, 0xfc, 0xc0, 0x98, 0x6f, 0x8d, 0x3c, 0xf7, 0xc6, + 0xe2, 0x34, 0xb8, 0x65, 0xbc, 0x56, 0xdc, 0x4f, 0x1d, 0xe4, 0xcc, 0x2a, 0x0a, 0x5a, 0x9e, 0x7b, + 0x33, 0x44, 0x98, 0x7c, 0x0e, 0x64, 0xc2, 0xa7, 0x23, 0xa4, 0xda, 0x81, 0x23, 0x93, 0x55, 0x2b, + 0x23, 0x79, 0x53, 0x48, 0x5a, 0x49, 0x01, 0xf9, 0x0a, 0x76, 0x31, 0x38, 0xfe, 0xec, 0x7a, 0x6a, + 0x8f, 0x10, 0xb4, 0xc6, 0x8c, 0x8e, 0xa7, 0xb6, 0xcb, 0x6a, 0x20, 0x6e, 0x6f, 0xee, 0x08, 0x42, + 0x7f, 0x2e, 0x3f, 0x51, 0x62, 0xf2, 0x14, 0x72, 0x53, 0x7a, 0xcd, 0xa6, 0x35, 0x0d, 0xf3, 0x2a, + 0x7f, 0x18, 0xff, 0x48, 0x41, 0x59, 0xbc, 0x88, 0x8e, 0xbb, 0xfe, 0x41, 0x2c, 0xa7, 0x25, 0xfd, + 0x20, 0x2d, 0x0f, 0x02, 0x9e, 0x79, 0x18, 0xf0, 0x5d, 0x28, 0x4c, 0x69, 0xc8, 0xad, 0x89, 0xe7, + 0xe3, 0x1b, 0xd0, 0xcc, 0xbc, 0xf8, 0x7d, 0xe6, 0xf9, 0xe4, 0x25, 0x94, 0xd9, 0x1d, 0x67, 0x81, + 0x4b, 0xa7, 0x96, 0x70, 0x1a, 0x13, 0x5f, 0x30, 0xb5, 0x08, 0x3c, 0xe3, 0xd3, 0x11, 0x39, 0x00, + 0x3d, 0x0e, 0x55, 0x14, 0xd5, 0x0d, 0x0c, 0x54, 0x25, 0x0a, 0x94, 0x0a, 0x6a, 0xec, 0x69, 0x3e, + 0xe9, 0xe9, 0x3f, 0x53, 0xa0, 0xe1, 0x23, 0x65, 0xa1, 0xef, 0xb9, 0x21, 0x23, 0x04, 0xd2, 0xf6, + 0x18, 0xfd, 0x2c, 0x62, 0xce, 0xd3, 0xf6, 0x58, 0x5c, 0xd2, 0x1e, 0x5b, 0xd7, 0xf7, 0x9c, 0x85, + 0xe8, 0x83, 0x66, 0xe6, 0xed, 0xf1, 0x6b, 0xf1, 0x93, 0xbc, 0x02, 0x0d, 0xed, 0xd3, 0xf1, 0x38, + 0x60, 0x61, 0x28, 0xcb, 0x03, 0x0f, 0x96, 0x04, 0xde, 0x94, 0x30, 0x39, 0x82, 0xad, 0x24, 0xcd, + 0x72, 0xfd, 0xe3, 0x0f, 0xe1, 0x04, 0x3d, 0x2e, 0xca, 0x94, 0x2a, 0x66, 0x17, 0x05, 0xe4, 0x33, + 0xf5, 0x02, 0x22, 0xbe, 0xa4, 0xe7, 0x90, 0xae, 0x27, 0xe8, 0x7d, 0x64, 0xbf, 0x82, 0x4a, 0xc8, + 0x82, 0xf7, 0x2c, 0xb0, 0x1c, 0x16, 0x86, 0xf4, 0x96, 0x61, 0x08, 0x8a, 0x66, 0x59, 0xa2, 0x97, + 0x12, 0x34, 0x74, 0xa8, 0x5c, 0x7a, 0xae, 0xcd, 0xbd, 0x40, 0x65, 0xd5, 0xf8, 0x43, 0x16, 0x40, + 0x78, 0x3f, 0xe0, 0x94, 0xcf, 0xc2, 0x95, 0x55, 0x2f, 0xa2, 0x91, 0x5e, 0x1b, 0x8d, 0xd2, 0x72, + 0x34, 0xb2, 0xfc, 0xde, 0x97, 0x89, 0xae, 0x1c, 0x6f, 0x1e, 0xa9, 0xfe, 0x73, 0x24, 0x6c, 0x0c, + 0xef, 0x7d, 0x66, 0xa2, 0x98, 0x1c, 0x40, 0x2e, 0xe4, 0x94, 0xcb, 0xaa, 0xaf, 0x1c, 0x93, 0x05, + 0x9e, 0xb8, 0x0b, 0x33, 0x25, 0x81, 0xfc, 0x18, 0x2a, 0x37, 0xd4, 0x9e, 0xce, 0x02, 0x66, 0x05, + 0x8c, 0x86, 0x9e, 0x5b, 0xab, 0xe0, 0x91, 0xed, 0xf8, 0xc8, 0xa9, 0x14, 0x9b, 0x28, 0x35, 0xcb, + 0x37, 0xc9, 0x9f, 0xe4, 0xff, 0xa1, 0x6a, 0xbb, 0x36, 0xb7, 0x65, 0x4d, 0x70, 0xdb, 0x89, 0xba, + 0x47, 0x65, 0x0e, 0x0f, 0x6d, 0x47, 0xdc, 0x48, 0xc7, 0x67, 0x38, 0xf3, 0xc7, 0x94, 0x33, 0xc9, + 0x94, 0x3d, 0xa4, 0x22, 0xf0, 0x2b, 0x84, 0x91, 0xb9, 0x9c, 0xf0, 0xfc, 0xea, 0x84, 0xaf, 0x4e, + 0xa0, 0xb6, 0x26, 0x81, 0x6b, 0x9e, 0x47, 0x79, 0xdd, 0xf3, 0x78, 0x0e, 0xa5, 0x91, 0x17, 0x72, + 0x4b, 0xe6, 0x17, 0x3b, 0x54, 0xc6, 0x04, 0x01, 0x0d, 0x10, 0x21, 0x2f, 0x40, 0x43, 0x82, 0xe7, + 0x8e, 0x26, 0xd4, 0x76, 0xb1, 0xd1, 0x64, 0x4c, 0x3c, 0xd4, 0x93, 0x90, 0x28, 0x2f, 0x49, 0xb9, + 0xb9, 0x91, 0x1c, 0x90, 0x3d, 0x13, 0x39, 0x0a, 0x9b, 0x17, 0x4d, 0x35, 0x59, 0x34, 0x04, 0xf4, + 0x0b, 0x3b, 0xe4, 0x22, 0x5b, 0x61, 0xf4, 0x94, 0x7e, 0x02, 0x9b, 0x09, 0x4c, 0x15, 0xd3, 0xa7, + 0x90, 0x13, 0xfd, 0x21, 0xac, 0xa5, 0xf6, 0x33, 0x07, 0xa5, 0xe3, 0xad, 0x07, 0x89, 0x9e, 0x85, + 0xa6, 0x64, 0x18, 0x2f, 0xa0, 0x2a, 0xc0, 0x8e, 0x7b, 0xe3, 0x45, 0x3d, 0xa7, 0x12, 0x97, 0xa2, + 0x26, 0x1e, 0x9e, 0x51, 0x01, 0x6d, 0xc8, 0x02, 0x27, 0x36, 0xf9, 0x1b, 0xa8, 0x76, 0x5c, 0x85, + 0x28, 0x83, 0xff, 0x07, 0x55, 0xc7, 0x76, 0x65, 0x53, 0xa2, 0x8e, 0x37, 0x73, 0xb9, 0x4a, 0x78, + 0xd9, 0xb1, 0x5d, 0xa1, 0xbf, 0x89, 0x20, 0xf2, 0xa2, 0xe6, 0xa5, 0x78, 0x1b, 0x8a, 0x27, 0xfb, + 0x97, 0xe4, 0x9d, 0x67, 0x0b, 0x29, 0x3d, 0x7d, 0x9e, 0x2d, 0xa4, 0xf5, 0xcc, 0x79, 0xb6, 0x90, + 0xd1, 0xb3, 0xe7, 0xd9, 0x42, 0x56, 0xcf, 0x9d, 0x67, 0x0b, 0x79, 0xbd, 0x60, 0xfc, 0x39, 0x05, + 0x7a, 0x6f, 0xc6, 0xff, 0xa7, 0x57, 0xc0, 0xe1, 0x66, 0xbb, 0xd6, 0x68, 0xca, 0xdf, 0x5b, 0x63, + 0x36, 0xe5, 0x14, 0xd3, 0x9d, 0x33, 0x35, 0xc7, 0x76, 0x5b, 0x53, 0xfe, 0xfe, 0x44, 0x60, 0xd1, + 0x08, 0x4c, 0xb0, 0x8a, 0x8a, 0x45, 0xef, 0x62, 0xd6, 0x7f, 0x70, 0xe7, 0xf7, 0x29, 0xd0, 0x7e, + 0x36, 0xf3, 0x38, 0x5b, 0xdf, 0xf4, 0xf1, 0xe1, 0xcd, 0x3b, 0x6d, 0x1a, 0x6d, 0xc0, 0x68, 0xde, + 0x65, 0x1f, 0x34, 0xed, 0xcc, 0x8a, 0xa6, 0xfd, 0xe8, 0xc0, 0xca, 0x3e, 0x3a, 0xb0, 0x8c, 0xef, + 0x53, 0x22, 0xeb, 0xea, 0x9a, 0x2a, 0xe4, 0xfb, 0xa0, 0x45, 0x63, 0xc8, 0x0a, 0x69, 0x74, 0x61, + 0x08, 0xe5, 0x1c, 0x1a, 0x50, 0xdc, 0x54, 0xb0, 0xc0, 0xd0, 0x62, 0x38, 0x89, 0x99, 0x6a, 0x53, + 0x11, 0xb2, 0xbe, 0x14, 0xa9, 0x03, 0x1f, 0x03, 0x24, 0x62, 0x99, 0x43, 0x3f, 0x8b, 0xa3, 0x44, + 0x20, 0x65, 0x08, 0xb3, 0x7a, 0xce, 0xf8, 0x8b, 0x7c, 0x05, 0xff, 0xed, 0x95, 0x3e, 0x81, 0xca, + 0x7c, 0x61, 0x41, 0x8e, 0x9c, 0xa0, 0x9a, 0x1f, 0x6d, 0x2c, 0x82, 0xf5, 0x03, 0xd5, 0x47, 0xe4, + 0xee, 0xb0, 0x78, 0xed, 0xaa, 0x90, 0x0c, 0x84, 0x40, 0xa9, 0xc4, 0x1d, 0x43, 0xc4, 0x95, 0xde, + 0x3b, 0xcc, 0xe5, 0x16, 0x2e, 0x6c, 0x72, 0xaa, 0x56, 0x31, 0x9e, 0x12, 0x3f, 0x11, 0xb9, 0x7d, + 0xdc, 0x41, 0xa3, 0x0a, 0xe5, 0xa1, 0xf7, 0x4b, 0xe6, 0xc6, 0xc5, 0xf6, 0x35, 0x54, 0x22, 0x40, + 0xb9, 0x78, 0x08, 0x1b, 0x1c, 0x11, 0x55, 0xdd, 0xf3, 0x36, 0x7e, 0x11, 0x52, 0x8e, 0x64, 0x53, + 0x31, 0x8c, 0x3f, 0xa6, 0xa1, 0x18, 0xa3, 0xe2, 0x91, 0x5c, 0xd3, 0x90, 0x59, 0x0e, 0x1d, 0xd1, + 0xc0, 0xf3, 0x5c, 0x55, 0xe3, 0x9a, 0x00, 0x2f, 0x15, 0x26, 0x5a, 0x58, 0xe4, 0xc7, 0x84, 0x86, + 0x13, 0x8c, 0x8e, 0x66, 0x96, 0x14, 0x76, 0x46, 0xc3, 0x09, 0xf9, 0x14, 0xf4, 0x88, 0xe2, 0x07, + 0xcc, 0x76, 0xc4, 0xe4, 0x93, 0xf3, 0xb9, 0xaa, 0xf0, 0xbe, 0x82, 0x45, 0x83, 0x97, 0x45, 0x66, + 0xf9, 0xd4, 0x1e, 0x5b, 0x8e, 0x88, 0xa2, 0xdc, 0x39, 0x2b, 0x12, 0xef, 0x53, 0x7b, 0x7c, 0x19, + 0x52, 0x4e, 0xbe, 0x80, 0x67, 0x89, 0xc5, 0x34, 0x41, 0x97, 0x55, 0x4c, 0x82, 0x78, 0x33, 0x8d, + 0x8f, 0xbc, 0x00, 0x4d, 0x4c, 0x0c, 0x6b, 0x14, 0x30, 0xca, 0xd9, 0x58, 0xd5, 0x71, 0x49, 0x60, + 0x2d, 0x09, 0x91, 0x1a, 0xe4, 0xd9, 0x9d, 0x6f, 0x07, 0x6c, 0x8c, 0x13, 0xa3, 0x60, 0x46, 0x3f, + 0xc5, 0xe1, 0x90, 0x7b, 0x01, 0xbd, 0x65, 0x96, 0x4b, 0x1d, 0x86, 0xd5, 0x5d, 0x34, 0x4b, 0x0a, + 0xeb, 0x52, 0x87, 0x19, 0x1f, 0xc1, 0xee, 0x37, 0x8c, 0x5f, 0xd8, 0xef, 0x66, 0xf6, 0xd8, 0xe6, + 0xf7, 0x7d, 0x1a, 0xd0, 0x79, 0x17, 0x6c, 0xc1, 0xd6, 0xa2, 0x84, 0x71, 0x16, 0x88, 0x01, 0x94, + 0x0b, 0x66, 0x53, 0x16, 0x25, 0x67, 0x3e, 0x30, 0x63, 0xb2, 0x39, 0x9b, 0x32, 0x53, 0x92, 0x8c, + 0x3f, 0x89, 0x85, 0x2f, 0x29, 0xc0, 0xf7, 0x21, 0xd7, 0x5c, 0x4b, 0x35, 0xe1, 0xac, 0x59, 0x54, + 0x48, 0x67, 0x4c, 0x8e, 0xd4, 0xa4, 0x4f, 0xe3, 0x38, 0xae, 0xaf, 0xd6, 0x9e, 0x18, 0xf9, 0x9f, + 0x03, 0xb1, 0xdd, 0x91, 0xe7, 0x88, 0xb0, 0xf2, 0x49, 0xc0, 0xc2, 0x89, 0x37, 0x1d, 0x63, 0xb2, + 0xca, 0xe6, 0x66, 0x24, 0x19, 0x46, 0x02, 0x41, 0x8f, 0x37, 0xeb, 0x39, 0x3d, 0x2b, 0xe9, 0x91, + 0x24, 0xa6, 0x1b, 0x6f, 0x61, 0x77, 0xb0, 0x2e, 0x40, 0xe4, 0x6b, 0x00, 0x3f, 0x8e, 0x0b, 0x7a, + 0x52, 0x3a, 0xde, 0x7b, 0x78, 0xe1, 0x79, 0xec, 0xcc, 0x04, 0xdf, 0xd8, 0x83, 0xfa, 0x2a, 0xd5, + 0xb2, 0x06, 0x8c, 0x67, 0xb0, 0x35, 0x98, 0xdd, 0xde, 0xb2, 0xa5, 0x61, 0x78, 0x0e, 0x4f, 0x17, + 0x61, 0x55, 0x32, 0xc7, 0x50, 0x88, 0x3e, 0x2f, 0x54, 0x5e, 0x76, 0xe6, 0x17, 0x59, 0xf8, 0x02, + 0x33, 0xf3, 0xea, 0x5b, 0xe3, 0xf0, 0x15, 0x14, 0xa2, 0xf5, 0x89, 0x68, 0x50, 0xb8, 0xe8, 0xf5, + 0xfa, 0x56, 0xef, 0x6a, 0xa8, 0x3f, 0x21, 0x25, 0xc8, 0xe3, 0xaf, 0x4e, 0x57, 0x4f, 0x1d, 0x86, + 0x50, 0x8c, 0xb7, 0x27, 0x52, 0x86, 0x62, 0xa7, 0xdb, 0x19, 0x76, 0x9a, 0xc3, 0xf6, 0x89, 0xfe, + 0x84, 0x3c, 0x83, 0xcd, 0xbe, 0xd9, 0xee, 0x5c, 0x36, 0xbf, 0x69, 0x5b, 0x66, 0xfb, 0x4d, 0xbb, + 0x79, 0xd1, 0x3e, 0xd1, 0x53, 0x84, 0x40, 0xe5, 0x6c, 0x78, 0xd1, 0xb2, 0xfa, 0x57, 0xaf, 0x2f, + 0x3a, 0x83, 0xb3, 0xf6, 0x89, 0x9e, 0x16, 0x3a, 0x07, 0x57, 0xad, 0x56, 0x7b, 0x30, 0xd0, 0x33, + 0x04, 0x60, 0xe3, 0xb4, 0xd9, 0x11, 0xe4, 0x2c, 0xd9, 0x82, 0x6a, 0xa7, 0xfb, 0xa6, 0xd7, 0x69, + 0xb5, 0xad, 0x41, 0x7b, 0x38, 0x14, 0x60, 0xee, 0xf0, 0x5f, 0x29, 0x28, 0x2f, 0x2c, 0x60, 0x64, + 0x07, 0xb6, 0xc4, 0x91, 0x2b, 0x53, 0x58, 0x6a, 0x0e, 0x7a, 0x5d, 0xab, 0xdb, 0xeb, 0xb6, 0xf5, + 0x27, 0xe4, 0x23, 0xd8, 0x59, 0x12, 0xf4, 0x4e, 0x4f, 0x5b, 0x67, 0x4d, 0x71, 0x79, 0x52, 0x87, + 0xed, 0x25, 0xe1, 0xb0, 0x73, 0xd9, 0x16, 0x5e, 0xa6, 0xc9, 0x3e, 0xec, 0x2d, 0xc9, 0x06, 0xdf, + 0xb6, 0xdb, 0xfd, 0x98, 0x91, 0x21, 0xaf, 0xe0, 0xc5, 0x12, 0xa3, 0xd3, 0x1d, 0x5c, 0x9d, 0x9e, + 0x76, 0x5a, 0x9d, 0x76, 0x77, 0x68, 0xbd, 0x69, 0x5e, 0x5c, 0xb5, 0xf5, 0x2c, 0xd9, 0x83, 0xda, + 0xb2, 0x91, 0xf6, 0x65, 0xbf, 0x67, 0x36, 0xcd, 0xb7, 0x7a, 0x8e, 0xbc, 0x84, 0xe7, 0x0f, 0x94, + 0xb4, 0x7a, 0xa6, 0xd9, 0x6e, 0x0d, 0xad, 0xe6, 0x65, 0xef, 0xaa, 0x3b, 0xd4, 0x37, 0x0e, 0x1b, + 0x62, 0xc9, 0x59, 0x7a, 0xe0, 0x22, 0x64, 0x57, 0xdd, 0x9f, 0x76, 0x7b, 0xdf, 0x76, 0xf5, 0x27, + 0x22, 0xf2, 0xc3, 0x33, 0xb3, 0x3d, 0x38, 0xeb, 0x5d, 0x9c, 0xe8, 0xa9, 0xe3, 0xbf, 0x15, 0xe5, + 0x82, 0xdd, 0xc2, 0xcf, 0x72, 0x62, 0x42, 0x5e, 0xa5, 0x99, 0xac, 0x4b, 0x7c, 0xfd, 0xd9, 0xc2, + 0x92, 0x14, 0xbf, 0xb4, 0x9d, 0xdf, 0xfe, 0xf5, 0xef, 0xbf, 0x4b, 0x6f, 0x1a, 0x5a, 0xe3, 0xfd, + 0x17, 0x0d, 0xc1, 0x68, 0x78, 0x33, 0xfe, 0x55, 0xea, 0x90, 0xf4, 0x60, 0x43, 0x7e, 0xaa, 0x91, + 0xed, 0x05, 0x95, 0xf1, 0xb7, 0xdb, 0x3a, 0x8d, 0xdb, 0xa8, 0x51, 0x37, 0x4a, 0xb1, 0x46, 0xdb, + 0x15, 0x0a, 0x7f, 0x04, 0x79, 0xf5, 0x99, 0x90, 0xb8, 0xe4, 0xe2, 0x87, 0x43, 0x7d, 0xd5, 0x26, + 0xf7, 0xc3, 0x14, 0xf9, 0x39, 0x14, 0xe3, 0x25, 0x90, 0xec, 0x26, 0x6a, 0x6c, 0xb1, 0x3e, 0xea, + 0xf5, 0x55, 0xa2, 0xc5, 0x6b, 0x91, 0x4a, 0x7c, 0x2d, 0x5c, 0x10, 0xc9, 0x95, 0xac, 0x03, 0xb1, + 0x20, 0x92, 0xda, 0x82, 0xf9, 0xc4, 0xce, 0xb8, 0xf2, 0x62, 0x46, 0x1d, 0x55, 0x3e, 0x25, 0x64, + 0x41, 0x65, 0xe3, 0x3b, 0x7b, 0xfc, 0x2b, 0xf2, 0x0b, 0xd0, 0x54, 0x02, 0x70, 0x8d, 0x23, 0xf3, + 0x60, 0x25, 0x77, 0xcd, 0xfa, 0xdc, 0x99, 0xe5, 0x85, 0x6f, 0x85, 0x76, 0x6f, 0xc6, 0x1b, 0x1c, + 0xb5, 0x5d, 0xc7, 0xda, 0x71, 0x3d, 0x48, 0x68, 0x4f, 0x2e, 0x5a, 0x8b, 0xda, 0x17, 0x16, 0x09, + 0x63, 0x1f, 0xb5, 0xd7, 0x49, 0x6d, 0x41, 0xfb, 0x3b, 0xc1, 0x69, 0x7c, 0x47, 0x1d, 0x2e, 0x3c, + 0xa8, 0x88, 0xe9, 0x80, 0x29, 0x7f, 0xd4, 0x87, 0x79, 0xd4, 0x96, 0xd6, 0x66, 0x63, 0x17, 0x8d, + 0x6c, 0x91, 0xcd, 0xc4, 0x53, 0x88, 0x3d, 0x98, 0x6b, 0x7f, 0xd4, 0x87, 0xa4, 0xf6, 0x45, 0x17, + 0x9e, 0xa3, 0xf6, 0x5d, 0xb2, 0x93, 0xd4, 0x9e, 0xf4, 0xe0, 0x2d, 0x94, 0x85, 0x8d, 0x68, 0x3f, + 0x08, 0x13, 0x2f, 0x79, 0x61, 0x09, 0xa9, 0xef, 0x3c, 0xc0, 0x17, 0xab, 0x83, 0x54, 0xd1, 0x44, + 0x48, 0x79, 0x43, 0x2e, 0x1e, 0x84, 0x03, 0x79, 0x38, 0x3a, 0x89, 0x11, 0xeb, 0x59, 0x3b, 0x57, + 0xeb, 0x8f, 0x8e, 0x08, 0x63, 0x0f, 0x0d, 0x6e, 0x93, 0xa7, 0x68, 0x30, 0x22, 0x34, 0x7c, 0xa9, + 0xff, 0xd7, 0x40, 0x06, 0x8f, 0x59, 0x5d, 0x3b, 0xac, 0xea, 0x2f, 0x1f, 0xe5, 0x2c, 0x06, 0xd4, + 0x58, 0x69, 0x5c, 0x94, 0x30, 0x03, 0x2d, 0x39, 0x7f, 0xc8, 0xdc, 0x97, 0x15, 0xd3, 0xaa, 0xfe, + 0xf1, 0x1a, 0xa9, 0xb2, 0x56, 0x43, 0x6b, 0x84, 0xe8, 0xc2, 0x1a, 0x9d, 0x71, 0xaf, 0x11, 0x4a, + 0xda, 0xf5, 0x06, 0xfe, 0xff, 0xf0, 0xcb, 0x7f, 0x07, 0x00, 0x00, 0xff, 0xff, 0x65, 0x6d, 0xf3, + 0xf6, 0x76, 0x14, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -1958,6 +2036,12 @@ type SwapClientClient interface { //this call fully overwrites our existing parameters. //[EXPERIMENTAL]: endpoint is subject to change. SetLiquidityParams(ctx context.Context, in *SetLiquidityParamsRequest, opts ...grpc.CallOption) (*SetLiquidityParamsResponse, error) + // + //SuggestSwaps returns a list of recommended swaps based on the current + //state of your node's channels and it's liquidity manager parameters. + //Note that only loop out suggestions are currently supported. + //[EXPERIMENTAL]: endpoint is subject to change. + SuggestSwaps(ctx context.Context, in *SuggestSwapsRequest, opts ...grpc.CallOption) (*SuggestSwapsResponse, error) } type swapClientClient struct { @@ -2099,6 +2183,15 @@ func (c *swapClientClient) SetLiquidityParams(ctx context.Context, in *SetLiquid return out, nil } +func (c *swapClientClient) SuggestSwaps(ctx context.Context, in *SuggestSwapsRequest, opts ...grpc.CallOption) (*SuggestSwapsResponse, error) { + out := new(SuggestSwapsResponse) + err := c.cc.Invoke(ctx, "/looprpc.SwapClient/SuggestSwaps", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // SwapClientServer is the server API for SwapClient service. type SwapClientServer interface { //* loop: `out` @@ -2150,6 +2243,12 @@ type SwapClientServer interface { //this call fully overwrites our existing parameters. //[EXPERIMENTAL]: endpoint is subject to change. SetLiquidityParams(context.Context, *SetLiquidityParamsRequest) (*SetLiquidityParamsResponse, error) + // + //SuggestSwaps returns a list of recommended swaps based on the current + //state of your node's channels and it's liquidity manager parameters. + //Note that only loop out suggestions are currently supported. + //[EXPERIMENTAL]: endpoint is subject to change. + SuggestSwaps(context.Context, *SuggestSwapsRequest) (*SuggestSwapsResponse, error) } // UnimplementedSwapClientServer can be embedded to have forward compatible implementations. @@ -2192,6 +2291,9 @@ func (*UnimplementedSwapClientServer) GetLiquidityParams(ctx context.Context, re func (*UnimplementedSwapClientServer) SetLiquidityParams(ctx context.Context, req *SetLiquidityParamsRequest) (*SetLiquidityParamsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method SetLiquidityParams not implemented") } +func (*UnimplementedSwapClientServer) SuggestSwaps(ctx context.Context, req *SuggestSwapsRequest) (*SuggestSwapsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method SuggestSwaps not implemented") +} func RegisterSwapClientServer(s *grpc.Server, srv SwapClientServer) { s.RegisterService(&_SwapClient_serviceDesc, srv) @@ -2416,6 +2518,24 @@ func _SwapClient_SetLiquidityParams_Handler(srv interface{}, ctx context.Context return interceptor(ctx, in, info, handler) } +func _SwapClient_SuggestSwaps_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SuggestSwapsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SwapClientServer).SuggestSwaps(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/looprpc.SwapClient/SuggestSwaps", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SwapClientServer).SuggestSwaps(ctx, req.(*SuggestSwapsRequest)) + } + return interceptor(ctx, in, info, handler) +} + var _SwapClient_serviceDesc = grpc.ServiceDesc{ ServiceName: "looprpc.SwapClient", HandlerType: (*SwapClientServer)(nil), @@ -2464,6 +2584,10 @@ var _SwapClient_serviceDesc = grpc.ServiceDesc{ MethodName: "SetLiquidityParams", Handler: _SwapClient_SetLiquidityParams_Handler, }, + { + MethodName: "SuggestSwaps", + Handler: _SwapClient_SuggestSwaps_Handler, + }, }, Streams: []grpc.StreamDesc{ { diff --git a/looprpc/client.pb.gw.go b/looprpc/client.pb.gw.go index 9f3dee0..ed14192 100644 --- a/looprpc/client.pb.gw.go +++ b/looprpc/client.pb.gw.go @@ -415,6 +415,24 @@ func local_request_SwapClient_SetLiquidityParams_0(ctx context.Context, marshale } +func request_SwapClient_SuggestSwaps_0(ctx context.Context, marshaler runtime.Marshaler, client SwapClientClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq SuggestSwapsRequest + var metadata runtime.ServerMetadata + + msg, err := client.SuggestSwaps(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_SwapClient_SuggestSwaps_0(ctx context.Context, marshaler runtime.Marshaler, server SwapClientServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq SuggestSwapsRequest + var metadata runtime.ServerMetadata + + msg, err := server.SuggestSwaps(ctx, &protoReq) + return msg, metadata, err + +} + // RegisterSwapClientHandlerServer registers the http handlers for service SwapClient to "mux". // UnaryRPC :call SwapClientServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. @@ -640,6 +658,26 @@ func RegisterSwapClientHandlerServer(ctx context.Context, mux *runtime.ServeMux, }) + mux.Handle("GET", pattern_SwapClient_SuggestSwaps_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_SwapClient_SuggestSwaps_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_SwapClient_SuggestSwaps_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -901,6 +939,26 @@ func RegisterSwapClientHandlerClient(ctx context.Context, mux *runtime.ServeMux, }) + mux.Handle("GET", pattern_SwapClient_SuggestSwaps_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_SwapClient_SuggestSwaps_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_SwapClient_SuggestSwaps_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -926,6 +984,8 @@ var ( pattern_SwapClient_GetLiquidityParams_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "liquidity", "params"}, "", runtime.AssumeColonVerbOpt(true))) pattern_SwapClient_SetLiquidityParams_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "liquidity", "params"}, "", runtime.AssumeColonVerbOpt(true))) + + pattern_SwapClient_SuggestSwaps_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "auto", "suggest"}, "", runtime.AssumeColonVerbOpt(true))) ) var ( @@ -950,4 +1010,6 @@ var ( forward_SwapClient_GetLiquidityParams_0 = runtime.ForwardResponseMessage forward_SwapClient_SetLiquidityParams_0 = runtime.ForwardResponseMessage + + forward_SwapClient_SuggestSwaps_0 = runtime.ForwardResponseMessage ) diff --git a/looprpc/client.proto b/looprpc/client.proto index 1365b5a..78453fb 100644 --- a/looprpc/client.proto +++ b/looprpc/client.proto @@ -128,6 +128,18 @@ service SwapClient { body: "*" }; } + + /* + SuggestSwaps returns a list of recommended swaps based on the current + state of your node's channels and it's liquidity manager parameters. + Note that only loop out suggestions are currently supported. + [EXPERIMENTAL]: endpoint is subject to change. + */ + rpc SuggestSwaps(SuggestSwapsRequest) returns (SuggestSwapsResponse){ + option (google.api.http) = { + get: "/v1/auto/suggest" + }; + } } message LoopOutRequest { @@ -743,3 +755,14 @@ message SetLiquidityParamsRequest{ } message SetLiquidityParamsResponse{} + +message SuggestSwapsRequest{ +} + +message SuggestSwapsResponse{ + /* + The set of recommended loop outs. + */ + repeated LoopOutRequest loop_out= 1; +} + diff --git a/looprpc/client.swagger.json b/looprpc/client.swagger.json index 1472cc1..2161365 100644 --- a/looprpc/client.swagger.json +++ b/looprpc/client.swagger.json @@ -11,6 +11,29 @@ "application/json" ], "paths": { + "/v1/auto/suggest": { + "get": { + "summary": "SuggestSwaps returns a list of recommended swaps based on the current\nstate of your node's channels and it's liquidity manager parameters.\nNote that only loop out suggestions are currently supported.\n[EXPERIMENTAL]: endpoint is subject to change.", + "operationId": "SuggestSwaps", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/looprpcSuggestSwapsResponse" + } + }, + "default": { + "description": "An unexpected error response", + "schema": { + "$ref": "#/definitions/runtimeError" + } + } + }, + "tags": [ + "SwapClient" + ] + } + }, "/v1/liquidity/params": { "get": { "summary": "GetLiquidityParams gets the parameters that the daemon's liquidity manager\nis currently configured with. This may be nil if nothing is configured.\n[EXPERIMENTAL]: endpoint is subject to change.", @@ -698,6 +721,18 @@ "looprpcSetLiquidityParamsResponse": { "type": "object" }, + "looprpcSuggestSwapsResponse": { + "type": "object", + "properties": { + "loop_out": { + "type": "array", + "items": { + "$ref": "#/definitions/looprpcLoopOutRequest" + }, + "description": "The set of recommended loop outs." + } + } + }, "looprpcSwapResponse": { "type": "object", "properties": {