feat_: new endpoint added for an async route/s calculation

- `GetSuggestedRoutesV2Async` calculates the route/s based on input parameters and sends
`wallet.suggested.routes` signal to notify a client.
This commit is contained in:
Sale Djenic 2024-06-20 11:03:42 +02:00 committed by saledjenic
parent 8bcb6ce667
commit 92361d9e20
7 changed files with 113 additions and 52 deletions

View File

@ -94,6 +94,7 @@ func (api *API) StartWallet(ctx context.Context) error {
} }
func (api *API) StopWallet(ctx context.Context) error { func (api *API) StopWallet(ctx context.Context) error {
api.router.Stop()
return api.s.Stop() return api.s.Stop()
} }
@ -509,6 +510,18 @@ func (api *API) GetSuggestedRoutesV2(ctx context.Context, input *router.RouteInp
return api.router.SuggestedRoutesV2(ctx, input) return api.router.SuggestedRoutesV2(ctx, input)
} }
func (api *API) GetSuggestedRoutesV2Async(ctx context.Context, input *router.RouteInputParams) {
log.Debug("call to GetSuggestedRoutesV2Async")
api.router.SuggestedRoutesV2Async(input)
}
func (api *API) StopSuggestedRoutesV2AsyncCalcualtion(ctx context.Context) {
log.Debug("call to StopSuggestedRoutesV2AsyncCalcualtion")
api.router.StopSuggestedRoutesV2AsyncCalcualtion()
}
// Generates addresses for the provided paths, response doesn't include `HasActivity` value (if you need it check `GetAddressDetails` function) // Generates addresses for the provided paths, response doesn't include `HasActivity` value (if you need it check `GetAddressDetails` function)
func (api *API) GetDerivedAddresses(ctx context.Context, password string, derivedFrom string, paths []string) ([]*DerivedAddress, error) { func (api *API) GetDerivedAddresses(ctx context.Context, password string, derivedFrom string, paths []string) ([]*DerivedAddress, error) {
info, err := api.s.gethManager.AccountsGenerator().LoadAccount(derivedFrom, password) info, err := api.s.gethManager.AccountsGenerator().LoadAccount(derivedFrom, password)

View File

@ -24,4 +24,5 @@ var (
ErrNotEnoughTokenBalance = &errors.ErrorResponse{Code: errors.ErrorCode("WR-016"), Details: "not enough token balance"} ErrNotEnoughTokenBalance = &errors.ErrorResponse{Code: errors.ErrorCode("WR-016"), Details: "not enough token balance"}
ErrNotEnoughNativeBalance = &errors.ErrorResponse{Code: errors.ErrorCode("WR-017"), Details: "not enough native balance"} ErrNotEnoughNativeBalance = &errors.ErrorResponse{Code: errors.ErrorCode("WR-017"), Details: "not enough native balance"}
ErrNativeTokenNotFound = &errors.ErrorResponse{Code: errors.ErrorCode("WR-018"), Details: "native token not found"} ErrNativeTokenNotFound = &errors.ErrorResponse{Code: errors.ErrorCode("WR-018"), Details: "native token not found"}
ErrDisabledChainFoundAmongLockedNetworks = &errors.ErrorResponse{Code: errors.ErrorCode("WR-019"), Details: "disabled chain found among locked networks"}
) )

View File

@ -8,6 +8,7 @@ import (
"strings" "strings"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/consensus/misc" "github.com/ethereum/go-ethereum/consensus/misc"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
gaspriceoracle "github.com/status-im/status-go/contracts/gas-price-oracle" gaspriceoracle "github.com/status-im/status-go/contracts/gas-price-oracle"
@ -25,9 +26,9 @@ const (
) )
type MaxFeesLevels struct { type MaxFeesLevels struct {
Low *big.Int `json:"low"` Low *hexutil.Big `json:"low"`
Medium *big.Int `json:"medium"` Medium *hexutil.Big `json:"medium"`
High *big.Int `json:"high"` High *hexutil.Big `json:"high"`
} }
type SuggestedFees struct { type SuggestedFees struct {
@ -59,14 +60,14 @@ func (s *SuggestedFees) feeFor(mode GasFeeMode) *big.Int {
} }
if mode == GasFeeLow { if mode == GasFeeLow {
return s.MaxFeesLevels.Low return s.MaxFeesLevels.Low.ToInt()
} }
if mode == GasFeeHigh { if mode == GasFeeHigh {
return s.MaxFeesLevels.High return s.MaxFeesLevels.High.ToInt()
} }
return s.MaxFeesLevels.Medium return s.MaxFeesLevels.Medium.ToInt()
} }
func (s *SuggestedFeesGwei) feeFor(mode GasFeeMode) *big.Float { func (s *SuggestedFeesGwei) feeFor(mode GasFeeMode) *big.Float {
@ -140,9 +141,9 @@ func (f *FeeManager) SuggestedFees(ctx context.Context, chainID uint64) (*Sugges
BaseFee: big.NewInt(0), BaseFee: big.NewInt(0),
MaxPriorityFeePerGas: big.NewInt(0), MaxPriorityFeePerGas: big.NewInt(0),
MaxFeesLevels: &MaxFeesLevels{ MaxFeesLevels: &MaxFeesLevels{
Low: big.NewInt(0), Low: (*hexutil.Big)(big.NewInt(0)),
Medium: big.NewInt(0), Medium: (*hexutil.Big)(big.NewInt(0)),
High: big.NewInt(0), High: (*hexutil.Big)(big.NewInt(0)),
}, },
EIP1559Enabled: false, EIP1559Enabled: false,
}, nil }, nil
@ -158,9 +159,9 @@ func (f *FeeManager) SuggestedFees(ctx context.Context, chainID uint64) (*Sugges
BaseFee: baseFee, BaseFee: baseFee,
MaxPriorityFeePerGas: maxPriorityFeePerGas, MaxPriorityFeePerGas: maxPriorityFeePerGas,
MaxFeesLevels: &MaxFeesLevels{ MaxFeesLevels: &MaxFeesLevels{
Low: new(big.Int).Add(baseFee, maxPriorityFeePerGas), Low: (*hexutil.Big)(new(big.Int).Add(baseFee, maxPriorityFeePerGas)),
Medium: new(big.Int).Add(new(big.Int).Mul(baseFee, big.NewInt(2)), maxPriorityFeePerGas), Medium: (*hexutil.Big)(new(big.Int).Add(new(big.Int).Mul(baseFee, big.NewInt(2)), maxPriorityFeePerGas)),
High: new(big.Int).Add(new(big.Int).Mul(baseFee, big.NewInt(3)), maxPriorityFeePerGas), High: (*hexutil.Big)(new(big.Int).Add(new(big.Int).Mul(baseFee, big.NewInt(3)), maxPriorityFeePerGas)),
}, },
EIP1559Enabled: true, EIP1559Enabled: true,
}, nil }, nil
@ -175,9 +176,9 @@ func (f *FeeManager) SuggestedFeesGwei(ctx context.Context, chainID uint64) (*Su
GasPrice: weiToGwei(fees.GasPrice), GasPrice: weiToGwei(fees.GasPrice),
BaseFee: weiToGwei(fees.BaseFee), BaseFee: weiToGwei(fees.BaseFee),
MaxPriorityFeePerGas: weiToGwei(fees.MaxPriorityFeePerGas), MaxPriorityFeePerGas: weiToGwei(fees.MaxPriorityFeePerGas),
MaxFeePerGasLow: weiToGwei(fees.MaxFeesLevels.Low), MaxFeePerGasLow: weiToGwei(fees.MaxFeesLevels.Low.ToInt()),
MaxFeePerGasMedium: weiToGwei(fees.MaxFeesLevels.Medium), MaxFeePerGasMedium: weiToGwei(fees.MaxFeesLevels.Medium.ToInt()),
MaxFeePerGasHigh: weiToGwei(fees.MaxFeesLevels.High), MaxFeePerGasHigh: weiToGwei(fees.MaxFeesLevels.High.ToInt()),
EIP1559Enabled: fees.EIP1559Enabled, EIP1559Enabled: fees.EIP1559Enabled,
}, nil }, nil
} }

View File

@ -288,6 +288,7 @@ func NewRouter(rpcClient *rpc.Client, transactor *transactions.Transactor, token
stickersService: stickersService, stickersService: stickersService,
feesManager: &FeeManager{rpcClient}, feesManager: &FeeManager{rpcClient},
pathProcessors: processors, pathProcessors: processors,
scheduler: async.NewScheduler(),
} }
} }
@ -295,6 +296,10 @@ func (r *Router) AddPathProcessor(processor pathprocessor.PathProcessor) {
r.pathProcessors[processor.Name()] = processor r.pathProcessors[processor.Name()] = processor
} }
func (r *Router) Stop() {
r.scheduler.Stop()
}
func (r *Router) GetFeesManager() *FeeManager { func (r *Router) GetFeesManager() *FeeManager {
return r.feesManager return r.feesManager
} }
@ -303,9 +308,9 @@ func (r *Router) GetPathProcessors() map[string]pathprocessor.PathProcessor {
return r.pathProcessors return r.pathProcessors
} }
func containsNetworkChainID(network *params.Network, chainIDs []uint64) bool { func containsNetworkChainID(chainID uint64, chainIDs []uint64) bool {
for _, chainID := range chainIDs { for _, cID := range chainIDs {
if chainID == network.ChainID { if cID == chainID {
return true return true
} }
} }
@ -323,6 +328,7 @@ type Router struct {
stickersService *stickers.Service stickersService *stickers.Service
feesManager *FeeManager feesManager *FeeManager
pathProcessors map[string]pathprocessor.PathProcessor pathProcessors map[string]pathprocessor.PathProcessor
scheduler *async.Scheduler
} }
func (r *Router) requireApproval(ctx context.Context, sendType SendType, approvalContractAddress *common.Address, params pathprocessor.ProcessorInputParams) ( func (r *Router) requireApproval(ctx context.Context, sendType SendType, approvalContractAddress *common.Address, params pathprocessor.ProcessorInputParams) (
@ -478,7 +484,7 @@ func (r *Router) SuggestedRoutes(
continue continue
} }
if containsNetworkChainID(network, disabledFromChainIDs) { if containsNetworkChainID(network.ChainID, disabledFromChainIDs) {
continue continue
} }
@ -562,10 +568,10 @@ func (r *Router) SuggestedRoutes(
continue continue
} }
if len(preferedChainIDs) > 0 && !containsNetworkChainID(dest, preferedChainIDs) { if len(preferedChainIDs) > 0 && !containsNetworkChainID(dest.ChainID, preferedChainIDs) {
continue continue
} }
if containsNetworkChainID(dest, disabledToChainIDs) { if containsNetworkChainID(dest.ChainID, disabledToChainIDs) {
continue continue
} }
@ -710,6 +716,9 @@ func (r *Router) SuggestedRoutes(
FromToken: &token.Token{ FromToken: &token.Token{
Symbol: tokenID, Symbol: tokenID,
}, },
ToToken: &token.Token{
Symbol: toTokenID,
},
} }
amountOut, err := r.pathProcessors[path.BridgeName].CalculateAmountOut(processorInputParams) amountOut, err := r.pathProcessors[path.BridgeName].CalculateAmountOut(processorInputParams)

View File

@ -16,6 +16,14 @@ import (
walletCommon "github.com/status-im/status-go/services/wallet/common" walletCommon "github.com/status-im/status-go/services/wallet/common"
"github.com/status-im/status-go/services/wallet/router/pathprocessor" "github.com/status-im/status-go/services/wallet/router/pathprocessor"
walletToken "github.com/status-im/status-go/services/wallet/token" walletToken "github.com/status-im/status-go/services/wallet/token"
"github.com/status-im/status-go/signal"
)
var (
routerTask = async.TaskType{
ID: 1,
Policy: async.ReplacementPolicyCancelOld,
}
) )
var ( var (
@ -33,6 +41,7 @@ var (
) )
type RouteInputParams struct { type RouteInputParams struct {
Uuid string `json:"uuid"`
SendType SendType `json:"sendType" validate:"required"` SendType SendType `json:"sendType" validate:"required"`
AddrFrom common.Address `json:"addrFrom" validate:"required"` AddrFrom common.Address `json:"addrFrom" validate:"required"`
AddrTo common.Address `json:"addrTo" validate:"required"` AddrTo common.Address `json:"addrTo" validate:"required"`
@ -106,12 +115,18 @@ func (p *PathV2) Equal(o *PathV2) bool {
} }
type SuggestedRoutesV2 struct { type SuggestedRoutesV2 struct {
Uuid string
Best []*PathV2 Best []*PathV2
Candidates []*PathV2 Candidates []*PathV2
TokenPrice float64 TokenPrice float64
NativeChainTokenPrice float64 NativeChainTokenPrice float64
} }
type ErrorResponseWithUUID struct {
Uuid string
ErrorResponse error
}
type GraphV2 = []*NodeV2 type GraphV2 = []*NodeV2
type NodeV2 struct { type NodeV2 struct {
@ -120,6 +135,7 @@ type NodeV2 struct {
} }
func newSuggestedRoutesV2( func newSuggestedRoutesV2(
uuid string,
amountIn *big.Int, amountIn *big.Int,
candidates []*PathV2, candidates []*PathV2,
fromLockedAmount map[uint64]*hexutil.Big, fromLockedAmount map[uint64]*hexutil.Big,
@ -127,6 +143,7 @@ func newSuggestedRoutesV2(
nativeChainTokenPrice float64, nativeChainTokenPrice float64,
) *SuggestedRoutesV2 { ) *SuggestedRoutesV2 {
suggestedRoutes := &SuggestedRoutesV2{ suggestedRoutes := &SuggestedRoutesV2{
Uuid: uuid,
Candidates: candidates, Candidates: candidates,
Best: candidates, Best: candidates,
TokenPrice: tokenPrice, TokenPrice: tokenPrice,
@ -388,6 +405,9 @@ func validateInputData(input *RouteInputParams) error {
totalLockedAmount := big.NewInt(0) totalLockedAmount := big.NewInt(0)
for chainID, amount := range input.FromLockedAmount { for chainID, amount := range input.FromLockedAmount {
if containsNetworkChainID(chainID, input.DisabledFromChainIDs) {
return ErrDisabledChainFoundAmongLockedNetworks
}
if input.testnetMode { if input.testnetMode {
if !supportedTestNetworks[chainID] { if !supportedTestNetworks[chainID] {
return ErrLockedAmountNotSupportedForNetwork return ErrLockedAmountNotSupportedForNetwork
@ -417,7 +437,34 @@ func validateInputData(input *RouteInputParams) error {
return nil return nil
} }
func (r *Router) SuggestedRoutesV2Async(input *RouteInputParams) {
r.scheduler.Enqueue(routerTask, func(ctx context.Context) (interface{}, error) {
return r.SuggestedRoutesV2(ctx, input)
}, func(result interface{}, taskType async.TaskType, err error) {
if err != nil {
errResponse := &ErrorResponseWithUUID{
Uuid: input.Uuid,
ErrorResponse: errors.CreateErrorResponseFromError(err),
}
signal.SendWalletEvent(signal.SuggestedRoutes, errResponse)
return
}
signal.SendWalletEvent(signal.SuggestedRoutes, result)
})
}
func (r *Router) StopSuggestedRoutesV2AsyncCalcualtion() {
r.scheduler.Stop()
}
func (r *Router) SuggestedRoutesV2(ctx context.Context, input *RouteInputParams) (*SuggestedRoutesV2, error) { func (r *Router) SuggestedRoutesV2(ctx context.Context, input *RouteInputParams) (*SuggestedRoutesV2, error) {
testnetMode, err := r.rpcClient.NetworkManager.GetTestNetworksEnabled()
if err != nil {
return nil, errors.CreateErrorResponseFromError(err)
}
input.testnetMode = testnetMode
// clear all processors // clear all processors
for _, processor := range r.pathProcessors { for _, processor := range r.pathProcessors {
if clearable, ok := processor.(pathprocessor.PathProcessorClearable); ok { if clearable, ok := processor.(pathprocessor.PathProcessorClearable); ok {
@ -425,18 +472,11 @@ func (r *Router) SuggestedRoutesV2(ctx context.Context, input *RouteInputParams)
} }
} }
err := validateInputData(input) err = validateInputData(input)
if err != nil { if err != nil {
return nil, errors.CreateErrorResponseFromError(err) return nil, errors.CreateErrorResponseFromError(err)
} }
testnetMode, err := r.rpcClient.NetworkManager.GetTestNetworksEnabled()
if err != nil {
return nil, errors.CreateErrorResponseFromError(err)
}
input.testnetMode = testnetMode
candidates, err := r.resolveCandidates(ctx, input) candidates, err := r.resolveCandidates(ctx, input)
if err != nil { if err != nil {
return nil, errors.CreateErrorResponseFromError(err) return nil, errors.CreateErrorResponseFromError(err)
@ -467,7 +507,7 @@ func (r *Router) resolveCandidates(ctx context.Context, input *RouteInputParams)
continue continue
} }
if containsNetworkChainID(network, input.DisabledFromChainIDs) { if containsNetworkChainID(network.ChainID, input.DisabledFromChainIDs) {
continue continue
} }
@ -507,11 +547,17 @@ func (r *Router) resolveCandidates(ctx context.Context, input *RouteInputParams)
} }
} }
group.Add(func(c context.Context) error { var fees *SuggestedFees
if testsMode {
fees = input.testParams.suggestedFees
} else {
fees, err = r.feesManager.SuggestedFees(ctx, network.ChainID)
if err != nil { if err != nil {
return errors.CreateErrorResponseFromError(err) continue
} }
}
group.Add(func(c context.Context) error {
for _, pProcessor := range r.pathProcessors { for _, pProcessor := range r.pathProcessors {
// With the condition below we're eliminating `Swap` as potential path that can participate in calculating the best route // With the condition below we're eliminating `Swap` as potential path that can participate in calculating the best route
// once we decide to inlcude `Swap` in the calculation we need to update `canUseProcessor` function. // once we decide to inlcude `Swap` in the calculation we need to update `canUseProcessor` function.
@ -546,7 +592,7 @@ func (r *Router) resolveCandidates(ctx context.Context, input *RouteInputParams)
continue continue
} }
if containsNetworkChainID(dest, input.DisabledToChainIDs) { if containsNetworkChainID(dest.ChainID, input.DisabledToChainIDs) {
continue continue
} }
@ -558,7 +604,7 @@ func (r *Router) resolveCandidates(ctx context.Context, input *RouteInputParams)
ToAddr: input.AddrTo, ToAddr: input.AddrTo,
FromAddr: input.AddrFrom, FromAddr: input.AddrFrom,
AmountIn: amountToSend, AmountIn: amountToSend,
AmountOut: amountToSend, AmountOut: input.AmountOut.ToInt(),
Username: input.Username, Username: input.Username,
PublicKey: input.PublicKey, PublicKey: input.PublicKey,
@ -606,16 +652,6 @@ func (r *Router) resolveCandidates(ctx context.Context, input *RouteInputParams)
l1FeeWei, _ = r.feesManager.GetL1Fee(ctx, network.ChainID, txInputData) l1FeeWei, _ = r.feesManager.GetL1Fee(ctx, network.ChainID, txInputData)
} }
var fees *SuggestedFees
if testsMode {
fees = input.testParams.suggestedFees
} else {
fees, err = r.feesManager.SuggestedFees(ctx, network.ChainID)
if err != nil {
continue
}
}
amountOut, err := pProcessor.CalculateAmountOut(processorInputParams) amountOut, err := pProcessor.CalculateAmountOut(processorInputParams)
if err != nil { if err != nil {
continue continue
@ -679,7 +715,7 @@ func (r *Router) resolveRoutes(ctx context.Context, input *RouteInputParams, can
} }
} }
suggestedRoutes = newSuggestedRoutesV2(input.AmountIn.ToInt(), candidates, input.FromLockedAmount, prices[input.TokenID], prices[pathprocessor.EthSymbol]) suggestedRoutes = newSuggestedRoutesV2(input.Uuid, input.AmountIn.ToInt(), candidates, input.FromLockedAmount, prices[input.TokenID], prices[pathprocessor.EthSymbol])
// check the best route for the required balances // check the best route for the required balances
for _, path := range suggestedRoutes.Best { for _, path := range suggestedRoutes.Best {
@ -693,12 +729,12 @@ func (r *Router) resolveRoutes(ctx context.Context, input *RouteInputParams, can
if input.SendType == ERC1155Transfer { if input.SendType == ERC1155Transfer {
tokenBalance, err = r.getERC1155Balance(ctx, path.FromChain, path.FromToken, input.AddrFrom) tokenBalance, err = r.getERC1155Balance(ctx, path.FromChain, path.FromToken, input.AddrFrom)
if err != nil { if err != nil {
return nil, errors.CreateErrorResponseFromError(err) return suggestedRoutes, errors.CreateErrorResponseFromError(err)
} }
} else if input.SendType != ERC721Transfer { } else if input.SendType != ERC721Transfer {
tokenBalance, err = r.getBalance(ctx, path.FromChain, path.FromToken, input.AddrFrom) tokenBalance, err = r.getBalance(ctx, path.FromChain, path.FromToken, input.AddrFrom)
if err != nil { if err != nil {
return nil, errors.CreateErrorResponseFromError(err) return suggestedRoutes, errors.CreateErrorResponseFromError(err)
} }
} }
} }
@ -716,12 +752,12 @@ func (r *Router) resolveRoutes(ctx context.Context, input *RouteInputParams, can
} else { } else {
nativeToken := r.tokenManager.FindToken(path.FromChain, path.FromChain.NativeCurrencySymbol) nativeToken := r.tokenManager.FindToken(path.FromChain, path.FromChain.NativeCurrencySymbol)
if nativeToken == nil { if nativeToken == nil {
return nil, ErrNativeTokenNotFound return suggestedRoutes, ErrNativeTokenNotFound
} }
nativeBalance, err = r.getBalance(ctx, path.FromChain, nativeToken, input.AddrFrom) nativeBalance, err = r.getBalance(ctx, path.FromChain, nativeToken, input.AddrFrom)
if err != nil { if err != nil {
return nil, errors.CreateErrorResponseFromError(err) return suggestedRoutes, errors.CreateErrorResponseFromError(err)
} }
} }

View File

@ -67,9 +67,9 @@ var (
BaseFee: big.NewInt(testBaseFee), BaseFee: big.NewInt(testBaseFee),
MaxPriorityFeePerGas: big.NewInt(testPriorityFeeLow), MaxPriorityFeePerGas: big.NewInt(testPriorityFeeLow),
MaxFeesLevels: &MaxFeesLevels{ MaxFeesLevels: &MaxFeesLevels{
Low: big.NewInt(testPriorityFeeLow), Low: (*hexutil.Big)(big.NewInt(testPriorityFeeLow)),
Medium: big.NewInt(testPriorityFeeMedium), Medium: (*hexutil.Big)(big.NewInt(testPriorityFeeMedium)),
High: big.NewInt(testPriorityFeeHigh), High: (*hexutil.Big)(big.NewInt(testPriorityFeeHigh)),
}, },
EIP1559Enabled: false, EIP1559Enabled: false,
} }

View File

@ -5,6 +5,7 @@ type SignalType string
const ( const (
Wallet = SignalType("wallet") Wallet = SignalType("wallet")
SignTransactions = SignalType("wallet.sign.transactions") SignTransactions = SignalType("wallet.sign.transactions")
SuggestedRoutes = SignalType("wallet.suggested.routes")
) )
// SendWalletEvent sends event from services/wallet/events. // SendWalletEvent sends event from services/wallet/events.