feat_: endpoint for getting address details with/without blocking added

`AddressDetails` is added, basically it is the same as `GetAddressDetails`,
but does the check for address activity (if has balance) across all chains if the
chainIDs list is empty. Setting `timeoutInMilliseconds` param ensures that in case
of network congestion or no internet `AddressDetails` will return the response
setting `hasActivity` property to `false`.
This commit is contained in:
Sale Djenic 2024-08-20 12:09:39 +02:00 committed by saledjenic
parent cc722359b5
commit 1418d40a63
3 changed files with 189 additions and 1 deletions

View File

@ -4,6 +4,7 @@ import (
"context"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"math/big"
"strings"
@ -28,6 +29,7 @@ import (
"github.com/status-im/status-go/services/wallet/currency"
"github.com/status-im/status-go/services/wallet/history"
"github.com/status-im/status-go/services/wallet/onramp"
"github.com/status-im/status-go/services/wallet/requests"
"github.com/status-im/status-go/services/wallet/router"
"github.com/status-im/status-go/services/wallet/router/pathprocessor"
"github.com/status-im/status-go/services/wallet/thirdparty"
@ -589,7 +591,66 @@ func (api *API) AddressExists(ctx context.Context, address types.Address) (bool,
return api.s.accountsDB.AddressExists(address)
}
// Returns details for the passed address (response doesn't include derivation path)
// AddressDetails returns details for passed params (passed address, chains to check, timeout for the call to complete)
// if chainIDs is empty, it will use all active chains
// if timeout is zero, it will wait until the call completes
// response doesn't include derivation path
func (api *API) AddressDetails(ctx context.Context, params *requests.AddressDetails) (*DerivedAddress, error) {
if err := params.Validate(); err != nil {
return nil, err
}
result := &DerivedAddress{
Address: common.HexToAddress(params.Address),
}
addressExists, err := api.s.accountsDB.AddressExists(types.Address(result.Address))
if err != nil {
return result, err
}
result.AlreadyCreated = addressExists
chainIDs := params.ChainIDs
if len(chainIDs) == 0 {
activeNetworks, err := api.s.rpcClient.NetworkManager.GetActiveNetworks()
if err != nil {
return nil, err
}
chainIDs = wcommon.NetworksToChainIDs(activeNetworks)
}
clients, err := api.s.rpcClient.EthClients(chainIDs)
if err != nil {
return nil, err
}
if params.TimeoutInMilliseconds > 0 {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, time.Duration(params.TimeoutInMilliseconds)*time.Millisecond)
defer cancel()
}
for _, client := range clients {
balance, err := api.s.tokenManager.GetChainBalance(ctx, client, result.Address)
if err != nil {
if err != nil && errors.Is(err, context.DeadlineExceeded) {
return result, nil
}
return result, err
}
result.HasActivity = balance.Cmp(big.NewInt(0)) != 0
if result.HasActivity {
break
}
}
return result, nil
}
// @deprecated replaced by AddressDetails
// GetAddressDetails returns details for the passed address (response doesn't include derivation path)
func (api *API) GetAddressDetails(ctx context.Context, chainID uint64, address string) (*DerivedAddress, error) {
result := &DerivedAddress{
Address: common.HexToAddress(address),

View File

@ -3,15 +3,28 @@ package wallet
import (
"context"
"errors"
"fmt"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/ethereum/go-ethereum/event"
"github.com/stretchr/testify/require"
gomock "go.uber.org/mock/gomock"
"github.com/status-im/status-go/appdatabase"
"github.com/status-im/status-go/multiaccounts/accounts"
"github.com/status-im/status-go/params"
"github.com/status-im/status-go/rpc"
"github.com/status-im/status-go/services/wallet/onramp"
mock_onramp "github.com/status-im/status-go/services/wallet/onramp/mock"
"github.com/status-im/status-go/services/wallet/requests"
"github.com/status-im/status-go/services/wallet/walletconnect"
"github.com/status-im/status-go/t/helpers"
"github.com/status-im/status-go/walletdatabase"
)
// TestAPI_GetWalletConnectActiveSessions tames coverage
@ -90,3 +103,94 @@ func TestAPI_GetCryptoOnRamps(t *testing.T) {
require.NoError(t, err)
require.Equal(t, "url", url)
}
func TestAPI_GetAddressDetails(t *testing.T) {
appDB, err := helpers.SetupTestMemorySQLDB(appdatabase.DbInitializer{})
require.NoError(t, err)
defer appDB.Close()
accountsDb, err := accounts.NewDB(appDB)
require.NoError(t, err)
defer accountsDb.Close()
db, err := helpers.SetupTestMemorySQLDB(walletdatabase.DbInitializer{})
require.NoError(t, err)
defer db.Close()
accountFeed := &event.Feed{}
chainID := uint64(1)
address := "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
providerConfig := params.ProviderConfig{
Enabled: true,
Name: rpc.ProviderStatusProxy,
User: "user1",
Password: "pass1",
}
providerConfigs := []params.ProviderConfig{providerConfig}
// Create a new server that delays the response by 1 second
serverWith1SecDelay := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
time.Sleep(1 * time.Second)
fmt.Fprintln(w, `{"result": "0x10"}`)
}))
defer serverWith1SecDelay.Close()
networks := []params.Network{
{
ChainID: chainID,
DefaultRPCURL: serverWith1SecDelay.URL,
DefaultFallbackURL: serverWith1SecDelay.URL,
},
}
c, err := rpc.NewClient(nil, chainID, params.UpstreamRPCConfig{}, networks, appDB, providerConfigs)
require.NoError(t, err)
chainClient, err := c.EthClient(chainID)
require.NoError(t, err)
chainClient.SetWalletNotifier(func(chainID uint64, message string) {})
c.SetWalletNotifier(func(chainID uint64, message string) {})
service := NewService(db, accountsDb, appDB, c, accountFeed, nil, nil, nil, &params.NodeConfig{}, nil, nil, nil, nil, nil, "")
api := &API{
s: service,
}
// Test getting address details using `GetAddressDetails` call, that always waits for the request to finish
details, err := api.GetAddressDetails(context.Background(), 1, address)
require.NoError(t, err)
require.Equal(t, true, details.HasActivity)
// empty params
details, err = api.AddressDetails(context.Background(), &requests.AddressDetails{})
require.Error(t, err)
require.ErrorIs(t, err, requests.ErrAddresInvalid)
require.Nil(t, details)
// no response longer than the set timeout
details, err = api.AddressDetails(context.Background(), &requests.AddressDetails{
Address: address,
TimeoutInMilliseconds: 500,
})
require.NoError(t, err)
require.Equal(t, false, details.HasActivity)
// timeout longer than the response time
details, err = api.AddressDetails(context.Background(), &requests.AddressDetails{
Address: address,
TimeoutInMilliseconds: 1200,
})
require.NoError(t, err)
require.Equal(t, true, details.HasActivity)
// specific chain and timeout longer than the response time
details, err = api.AddressDetails(context.Background(), &requests.AddressDetails{
Address: address,
ChainIDs: []uint64{chainID},
TimeoutInMilliseconds: 1200,
})
require.NoError(t, err)
require.Equal(t, true, details.HasActivity)
}

View File

@ -0,0 +1,23 @@
package requests
import (
"errors"
"github.com/ethereum/go-ethereum/common"
)
var ErrAddresInvalid = errors.New("address-details: invalid address")
type AddressDetails struct {
Address string `json:"address"`
ChainIDs []uint64 `json:"chainIds"`
TimeoutInMilliseconds int64 `json:"timeoutInMilliseconds"`
}
func (a *AddressDetails) Validate() error {
if !common.IsHexAddress(a.Address) {
return ErrAddresInvalid
}
return nil
}