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 {
api.router.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)
}
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)
func (api *API) GetDerivedAddresses(ctx context.Context, password string, derivedFrom string, paths []string) ([]*DerivedAddress, error) {
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"}
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"}
ErrDisabledChainFoundAmongLockedNetworks = &errors.ErrorResponse{Code: errors.ErrorCode("WR-019"), Details: "disabled chain found among locked networks"}
)

View File

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

View File

@ -288,6 +288,7 @@ func NewRouter(rpcClient *rpc.Client, transactor *transactions.Transactor, token
stickersService: stickersService,
feesManager: &FeeManager{rpcClient},
pathProcessors: processors,
scheduler: async.NewScheduler(),
}
}
@ -295,6 +296,10 @@ func (r *Router) AddPathProcessor(processor pathprocessor.PathProcessor) {
r.pathProcessors[processor.Name()] = processor
}
func (r *Router) Stop() {
r.scheduler.Stop()
}
func (r *Router) GetFeesManager() *FeeManager {
return r.feesManager
}
@ -303,9 +308,9 @@ func (r *Router) GetPathProcessors() map[string]pathprocessor.PathProcessor {
return r.pathProcessors
}
func containsNetworkChainID(network *params.Network, chainIDs []uint64) bool {
for _, chainID := range chainIDs {
if chainID == network.ChainID {
func containsNetworkChainID(chainID uint64, chainIDs []uint64) bool {
for _, cID := range chainIDs {
if cID == chainID {
return true
}
}
@ -323,6 +328,7 @@ type Router struct {
stickersService *stickers.Service
feesManager *FeeManager
pathProcessors map[string]pathprocessor.PathProcessor
scheduler *async.Scheduler
}
func (r *Router) requireApproval(ctx context.Context, sendType SendType, approvalContractAddress *common.Address, params pathprocessor.ProcessorInputParams) (
@ -478,7 +484,7 @@ func (r *Router) SuggestedRoutes(
continue
}
if containsNetworkChainID(network, disabledFromChainIDs) {
if containsNetworkChainID(network.ChainID, disabledFromChainIDs) {
continue
}
@ -562,10 +568,10 @@ func (r *Router) SuggestedRoutes(
continue
}
if len(preferedChainIDs) > 0 && !containsNetworkChainID(dest, preferedChainIDs) {
if len(preferedChainIDs) > 0 && !containsNetworkChainID(dest.ChainID, preferedChainIDs) {
continue
}
if containsNetworkChainID(dest, disabledToChainIDs) {
if containsNetworkChainID(dest.ChainID, disabledToChainIDs) {
continue
}
@ -710,6 +716,9 @@ func (r *Router) SuggestedRoutes(
FromToken: &token.Token{
Symbol: tokenID,
},
ToToken: &token.Token{
Symbol: toTokenID,
},
}
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"
"github.com/status-im/status-go/services/wallet/router/pathprocessor"
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 (
@ -33,6 +41,7 @@ var (
)
type RouteInputParams struct {
Uuid string `json:"uuid"`
SendType SendType `json:"sendType" validate:"required"`
AddrFrom common.Address `json:"addrFrom" validate:"required"`
AddrTo common.Address `json:"addrTo" validate:"required"`
@ -106,12 +115,18 @@ func (p *PathV2) Equal(o *PathV2) bool {
}
type SuggestedRoutesV2 struct {
Uuid string
Best []*PathV2
Candidates []*PathV2
TokenPrice float64
NativeChainTokenPrice float64
}
type ErrorResponseWithUUID struct {
Uuid string
ErrorResponse error
}
type GraphV2 = []*NodeV2
type NodeV2 struct {
@ -120,6 +135,7 @@ type NodeV2 struct {
}
func newSuggestedRoutesV2(
uuid string,
amountIn *big.Int,
candidates []*PathV2,
fromLockedAmount map[uint64]*hexutil.Big,
@ -127,6 +143,7 @@ func newSuggestedRoutesV2(
nativeChainTokenPrice float64,
) *SuggestedRoutesV2 {
suggestedRoutes := &SuggestedRoutesV2{
Uuid: uuid,
Candidates: candidates,
Best: candidates,
TokenPrice: tokenPrice,
@ -388,6 +405,9 @@ func validateInputData(input *RouteInputParams) error {
totalLockedAmount := big.NewInt(0)
for chainID, amount := range input.FromLockedAmount {
if containsNetworkChainID(chainID, input.DisabledFromChainIDs) {
return ErrDisabledChainFoundAmongLockedNetworks
}
if input.testnetMode {
if !supportedTestNetworks[chainID] {
return ErrLockedAmountNotSupportedForNetwork
@ -417,7 +437,34 @@ func validateInputData(input *RouteInputParams) error {
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) {
testnetMode, err := r.rpcClient.NetworkManager.GetTestNetworksEnabled()
if err != nil {
return nil, errors.CreateErrorResponseFromError(err)
}
input.testnetMode = testnetMode
// clear all processors
for _, processor := range r.pathProcessors {
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 {
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)
if err != nil {
return nil, errors.CreateErrorResponseFromError(err)
@ -467,7 +507,7 @@ func (r *Router) resolveCandidates(ctx context.Context, input *RouteInputParams)
continue
}
if containsNetworkChainID(network, input.DisabledFromChainIDs) {
if containsNetworkChainID(network.ChainID, input.DisabledFromChainIDs) {
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 {
return errors.CreateErrorResponseFromError(err)
continue
}
}
group.Add(func(c context.Context) error {
for _, pProcessor := range r.pathProcessors {
// 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.
@ -546,7 +592,7 @@ func (r *Router) resolveCandidates(ctx context.Context, input *RouteInputParams)
continue
}
if containsNetworkChainID(dest, input.DisabledToChainIDs) {
if containsNetworkChainID(dest.ChainID, input.DisabledToChainIDs) {
continue
}
@ -558,7 +604,7 @@ func (r *Router) resolveCandidates(ctx context.Context, input *RouteInputParams)
ToAddr: input.AddrTo,
FromAddr: input.AddrFrom,
AmountIn: amountToSend,
AmountOut: amountToSend,
AmountOut: input.AmountOut.ToInt(),
Username: input.Username,
PublicKey: input.PublicKey,
@ -606,16 +652,6 @@ func (r *Router) resolveCandidates(ctx context.Context, input *RouteInputParams)
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)
if err != nil {
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
for _, path := range suggestedRoutes.Best {
@ -693,12 +729,12 @@ func (r *Router) resolveRoutes(ctx context.Context, input *RouteInputParams, can
if input.SendType == ERC1155Transfer {
tokenBalance, err = r.getERC1155Balance(ctx, path.FromChain, path.FromToken, input.AddrFrom)
if err != nil {
return nil, errors.CreateErrorResponseFromError(err)
return suggestedRoutes, errors.CreateErrorResponseFromError(err)
}
} else if input.SendType != ERC721Transfer {
tokenBalance, err = r.getBalance(ctx, path.FromChain, path.FromToken, input.AddrFrom)
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 {
nativeToken := r.tokenManager.FindToken(path.FromChain, path.FromChain.NativeCurrencySymbol)
if nativeToken == nil {
return nil, ErrNativeTokenNotFound
return suggestedRoutes, ErrNativeTokenNotFound
}
nativeBalance, err = r.getBalance(ctx, path.FromChain, nativeToken, input.AddrFrom)
if err != nil {
return nil, errors.CreateErrorResponseFromError(err)
return suggestedRoutes, errors.CreateErrorResponseFromError(err)
}
}

View File

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

View File

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