diff --git a/services/rfq/api/config/config.go b/services/rfq/api/config/config.go index 67ff8c8c6e..4c4456f246 100644 --- a/services/rfq/api/config/config.go +++ b/services/rfq/api/config/config.go @@ -38,7 +38,7 @@ func (c Config) GetRelayAckTimeout() time.Duration { return c.RelayAckTimeout } -const defaultMaxQuoteAge = 1 * time.Hour +const defaultMaxQuoteAge = 5 * time.Minute // GetMaxQuoteAge returns the max quote age. func (c Config) GetMaxQuoteAge() time.Duration { diff --git a/services/rfq/api/rest/export_test.go b/services/rfq/api/rest/export_test.go index 32775fed39..bc7a7cca27 100644 --- a/services/rfq/api/rest/export_test.go +++ b/services/rfq/api/rest/export_test.go @@ -3,9 +3,15 @@ package rest import ( "github.com/synapsecns/sanguine/services/rfq/api/config" "github.com/synapsecns/sanguine/services/rfq/api/db" + "github.com/synapsecns/sanguine/services/rfq/api/model" ) // FilterQuoteAge exports filterQuoteAge for testing. func FilterQuoteAge(cfg config.Config, dbQuotes []*db.Quote) []*db.Quote { return filterQuoteAge(cfg, dbQuotes) } + +// GetPassiveQuote exports getPassiveQuote for testing. +func GetPassiveQuote(cfg config.Config, quotes []*db.Quote, request *model.PutRFQRequest) (*model.QuoteData, error) { + return getPassiveQuote(cfg, quotes, request) +} diff --git a/services/rfq/api/rest/rfq.go b/services/rfq/api/rest/rfq.go index 66691f73fe..213adcc714 100644 --- a/services/rfq/api/rest/rfq.go +++ b/services/rfq/api/rest/rfq.go @@ -10,6 +10,7 @@ import ( "github.com/google/uuid" "github.com/synapsecns/sanguine/core/metrics" + "github.com/synapsecns/sanguine/services/rfq/api/config" "github.com/synapsecns/sanguine/services/rfq/api/db" "github.com/synapsecns/sanguine/services/rfq/api/model" "go.opentelemetry.io/otel/attribute" @@ -226,6 +227,17 @@ func (r *QuoterAPIServer) handlePassiveRFQ(ctx context.Context, request *model.P return nil, fmt.Errorf("failed to get quotes: %w", err) } + quote, err := getPassiveQuote(r.cfg, quotes, request) + if err != nil { + return nil, fmt.Errorf("failed to get passive quote: %w", err) + } + + return quote, nil +} + +func getPassiveQuote(cfg config.Config, quotes []*db.Quote, request *model.PutRFQRequest) (*model.QuoteData, error) { + quotes = filterQuoteAge(cfg, quotes) + originAmount, ok := new(big.Int).SetString(request.Data.OriginAmountExact, 10) if !ok { return nil, errors.New("invalid origin amount exact") diff --git a/services/rfq/api/rest/server_test.go b/services/rfq/api/rest/server_test.go index 647ced6293..8f9431ee75 100644 --- a/services/rfq/api/rest/server_test.go +++ b/services/rfq/api/rest/server_test.go @@ -5,10 +5,12 @@ import ( "encoding/json" "fmt" "io" + "math/big" "net/http" "strconv" "time" + "github.com/shopspring/decimal" apiClient "github.com/synapsecns/sanguine/services/rfq/api/client" "github.com/synapsecns/sanguine/services/rfq/api/db" "github.com/synapsecns/sanguine/services/rfq/api/rest" @@ -390,6 +392,75 @@ func (c *ServerSuite) TestFilterQuoteAge() { c.Equal(quotes[1], filteredQuotes[0]) } +func (c *ServerSuite) TestGetPassiveQuote() { + userRequestAmount := big.NewInt(1_000_000) + + passiveQuotes := []*db.Quote{ + { + RelayerAddr: "0x1", + OriginChainID: uint64(c.originChainID), + OriginTokenAddr: originTokenAddr, + DestChainID: uint64(c.destChainID), + DestTokenAddr: destTokenAddr, + DestAmount: decimal.NewFromBigInt(new(big.Int).Sub(userRequestAmount, big.NewInt(104)), 0), + MaxOriginAmount: decimal.NewFromBigInt(userRequestAmount, 0), + FixedFee: decimal.NewFromInt(1000), + UpdatedAt: time.Now(), + }, + { + RelayerAddr: "0x2", + OriginChainID: uint64(c.originChainID), + OriginTokenAddr: originTokenAddr, + DestChainID: uint64(c.destChainID), + DestTokenAddr: destTokenAddr, + DestAmount: decimal.NewFromBigInt(new(big.Int).Sub(userRequestAmount, big.NewInt(103)), 0), + MaxOriginAmount: decimal.NewFromBigInt(userRequestAmount, 0), + FixedFee: decimal.NewFromInt(1000), + UpdatedAt: time.Now().Add(-time.Minute), + }, + { + RelayerAddr: "0x3", + OriginChainID: uint64(c.originChainID), + OriginTokenAddr: originTokenAddr, + DestChainID: uint64(c.destChainID), + DestTokenAddr: destTokenAddr, + DestAmount: decimal.NewFromBigInt(new(big.Int).Sub(userRequestAmount, big.NewInt(102)), 0), + MaxOriginAmount: decimal.NewFromBigInt(userRequestAmount, 0), + FixedFee: decimal.NewFromInt(1000), + UpdatedAt: time.Now().Add(-time.Minute * 15), + }, + { + RelayerAddr: "0x4", + OriginChainID: uint64(c.originChainID), + OriginTokenAddr: originTokenAddr, + DestChainID: uint64(c.destChainID), + DestTokenAddr: destTokenAddr, + DestAmount: decimal.NewFromBigInt(new(big.Int).Sub(userRequestAmount, big.NewInt(101)), 0), + MaxOriginAmount: decimal.NewFromBigInt(userRequestAmount, 0), + FixedFee: decimal.NewFromInt(1000), + UpdatedAt: time.Now().Add(-time.Hour), + }, + } + + userQuoteReq := &model.PutRFQRequest{ + Data: model.QuoteData{ + OriginChainID: c.originChainID, + OriginTokenAddr: originTokenAddr, + DestChainID: c.destChainID, + DestTokenAddr: destTokenAddr, + OriginAmountExact: userRequestAmount.String(), + ExpirationWindow: 0, + }, + QuoteTypes: []string{"active", "passive"}, + } + + // get the best passive quote; this should be the one with highest dest amount, but within the MaxQuoteAge window + quote, err := rest.GetPassiveQuote(c.cfg, passiveQuotes, userQuoteReq) + c.Require().NoError(err) + c.Assert().Equal("998897", *quote.DestAmount) + c.Assert().Equal(passiveQuotes[1].RelayerAddr, *quote.RelayerAddress) +} + func (c *ServerSuite) TestPutAck() { c.startQuoterAPIServer()