Skip to content

Commit

Permalink
chore_: improvements of the sending route generated by the router pro…
Browse files Browse the repository at this point in the history
…cess

This commit simplifies the sending process of the best route suggested by the router.
It also makes the sending process the same for accounts (key pairs) migrated to a keycard
and those stored locally in local keystore files.

Deprecated endpoints:
- `CreateMultiTransaction`
- `ProceedWithTransactionsSignatures`

Deprecated signal:
- `wallet.sign.transactions`

New endpoints:
- `BuildTransactionsFromRoute`
- `SendRouterTransactionsWithSignatures`

The flow for sending the best router suggested by the router:
- call `BuildTransactionsFromRoute`
- wait for the `wallet.router.sign-transactions` signal
- sign received hashes using `SignMessage` call or sign on keycard
- call `SendRouterTransactionsWithSignatures` with the signatures of signed hashes from the previous step
- `wallet.router.transactions-sent` signal will be sent after sending transactions or an error occurs

New signals:
- `wallet.router.sending-transactions-started`
- `wallet.router.sign-transactions`
- `wallet.router.transactions-sent`
  • Loading branch information
saledjenic committed Sep 23, 2024
1 parent c1ce30a commit 89503c9
Show file tree
Hide file tree
Showing 37 changed files with 1,301 additions and 113 deletions.
178 changes: 178 additions & 0 deletions services/wallet/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
signercore "github.com/ethereum/go-ethereum/signer/core/apitypes"
abi_spec "github.com/status-im/status-go/abi-spec"
"github.com/status-im/status-go/account"
statusErrors "github.com/status-im/status-go/errors"
"github.com/status-im/status-go/eth-node/crypto"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/params"
Expand All @@ -30,13 +31,16 @@ import (
"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/responses"
"github.com/status-im/status-go/services/wallet/router"
"github.com/status-im/status-go/services/wallet/router/fees"
"github.com/status-im/status-go/services/wallet/router/pathprocessor"
"github.com/status-im/status-go/services/wallet/router/sendtype"
"github.com/status-im/status-go/services/wallet/thirdparty"
"github.com/status-im/status-go/services/wallet/token"
"github.com/status-im/status-go/services/wallet/transfer"
"github.com/status-im/status-go/services/wallet/walletconnect"
"github.com/status-im/status-go/signal"
"github.com/status-im/status-go/transactions"
)

Expand Down Expand Up @@ -712,6 +716,15 @@ func (api *API) SendTransactionWithSignature(ctx context.Context, chainID uint64
return api.s.transactionManager.SendTransactionWithSignature(chainID, params, sig)
}

// @deprecated `CreateMultiTransaction`
//
// The flow that should be used instead:
// - call `BuildTransactionsFromRoute`
// - wait for the `wallet.router.sign-transactions` signal
// - sign received hashes using `SignMessage` call or sign on keycard
// - call `SendRouterTransactionsWithSignatures` with the signatures of signed hashes from the previous step
//
// TODO: remove this struct once mobile switches to the new approach
func (api *API) CreateMultiTransaction(ctx context.Context, multiTransactionCommand *transfer.MultiTransactionCommand, data []*pathprocessor.MultipathProcessorTxArgs, password string) (*transfer.MultiTransactionCommandResult, error) {
log.Debug("[WalletAPI:: CreateMultiTransaction] create multi transaction")

Expand Down Expand Up @@ -742,11 +755,176 @@ func (api *API) CreateMultiTransaction(ctx context.Context, multiTransactionComm
return nil, api.s.transactionManager.SendTransactionForSigningToKeycard(ctx, cmd, data, api.router.GetPathProcessors())
}

func updateFields(sd *responses.SendDetails, inputParams requests.RouteInputParams) {
sd.SendType = int(inputParams.SendType)
sd.FromAddress = types.Address(inputParams.AddrFrom)
sd.ToAddress = types.Address(inputParams.AddrTo)
sd.FromToken = inputParams.TokenID
sd.ToToken = inputParams.ToTokenID
sd.FromAmount = inputParams.AmountIn.String()
sd.ToAmount = inputParams.AmountOut.String()
sd.OwnerTokenBeingSent = inputParams.TokenIDIsOwnerToken
}

func (api *API) BuildTransactionsFromRoute(ctx context.Context, buildInputParams *requests.RouterBuildTransactionsParams) {
log.Debug("[WalletAPI::BuildTransactionsFromRoute] builds transactions from the generated best route", "uuid", buildInputParams.Uuid)

go func() {
api.router.StopSuggestedRoutesAsyncCalculation()

var err error
response := &responses.RouterTransactionsForSigning{
SendDetails: &responses.SendDetails{
Uuid: buildInputParams.Uuid,
},
}

defer func() {
if err != nil {
api.s.transactionManager.ClearLocalRouterTransactionsData()
err = statusErrors.CreateErrorResponseFromError(err)
response.SendDetails.ErrorResponse = err.(*statusErrors.ErrorResponse)
}
signal.SendWalletEvent(signal.SignRouterTransactions, response)
}()

route, routeInputParams := api.router.GetBestRouteAndAssociatedInputParams()
if routeInputParams.Uuid != buildInputParams.Uuid {
// should never be here
err = ErrCannotResolveRouteId
return
}

updateFields(response.SendDetails, routeInputParams)

// notify clinet that sending transactions started (has 3 steps, building txs, signing txs, sending txs)
signal.SendWalletEvent(signal.RouterSendingTransactionsStarted, response.SendDetails)

response.SigningDetails, err = api.s.transactionManager.BuildTransactionsFromRoute(
route,
routeInputParams.AddrFrom,
routeInputParams.AddrTo,
api.router.GetPathProcessors(),
routeInputParams.Username,
routeInputParams.PublicKey,
routeInputParams.PackID.ToInt(),
buildInputParams.SlippagePercentage)
}()
}

// @deprecated `ProceedWithTransactionsSignatures`
//
// The flow that should be used instead:
// - call `BuildTransactionsFromRoute`
// - wait for the `wallet.router.sign-transactions` signal
// - sign received hashes using `SignMessage` call or sign on keycard
// - call `SendRouterTransactionsWithSignatures` with the signatures of signed hashes from the previous step
//
// TODO: remove this struct once mobile switches to the new approach
func (api *API) ProceedWithTransactionsSignatures(ctx context.Context, signatures map[string]transfer.SignatureDetails) (*transfer.MultiTransactionCommandResult, error) {
log.Debug("[WalletAPI:: ProceedWithTransactionsSignatures] sign with signatures and send multi transaction")
return api.s.transactionManager.ProceedWithTransactionsSignatures(ctx, signatures)
}

func (api *API) SendRouterTransactionsWithSignatures(ctx context.Context, sendInputParams *requests.RouterSendTransactionsParams) {
log.Debug("[WalletAPI:: SendRouterTransactionsWithSignatures] sign with signatures and send")
go func() {

var (
err error
routeInputParams requests.RouteInputParams
)
response := &responses.RouterSentTransactions{
SendDetails: &responses.SendDetails{
Uuid: sendInputParams.Uuid,
},
}

defer func() {
clearLocalData := true
if routeInputParams.SendType == sendtype.Swap {
// in case of swap don't clear local data if an approval is placed, but swap tx is not sent yet
if api.s.transactionManager.ApprovalRequiredForPath(pathprocessor.ProcessorSwapParaswapName) &&
api.s.transactionManager.ApprovalPlacedForPath(pathprocessor.ProcessorSwapParaswapName) &&
!api.s.transactionManager.TxPlacedForPath(pathprocessor.ProcessorSwapParaswapName) {
clearLocalData = false
}
}

if clearLocalData {
api.s.transactionManager.ClearLocalRouterTransactionsData()
}

if err != nil {
err = statusErrors.CreateErrorResponseFromError(err)
response.SendDetails.ErrorResponse = err.(*statusErrors.ErrorResponse)
}
signal.SendWalletEvent(signal.RouterTransactionsSent, response)
}()

_, routeInputParams = api.router.GetBestRouteAndAssociatedInputParams()
if routeInputParams.Uuid != sendInputParams.Uuid {
err = ErrCannotResolveRouteId
return
}

updateFields(response.SendDetails, routeInputParams)

err = api.s.transactionManager.ValidateAndAddSignaturesToRouterTransactions(sendInputParams.Signatures)
if err != nil {
return
}

//////////////////////////////////////////////////////////////////////////////
// prepare multitx
var mtType transfer.MultiTransactionType = transfer.MultiTransactionSend
if routeInputParams.SendType == sendtype.Bridge {
mtType = transfer.MultiTransactionBridge
} else if routeInputParams.SendType == sendtype.Swap {
mtType = transfer.MultiTransactionSwap
}

multiTx := transfer.NewMultiTransaction(
/* Timestamp: */ uint64(time.Now().Unix()),
/* FromNetworkID: */ 0,
/* ToNetworkID: */ 0,
/* FromTxHash: */ common.Hash{},
/* ToTxHash: */ common.Hash{},
/* FromAddress: */ routeInputParams.AddrFrom,
/* ToAddress: */ routeInputParams.AddrTo,
/* FromAsset: */ routeInputParams.TokenID,
/* ToAsset: */ routeInputParams.ToTokenID,
/* FromAmount: */ routeInputParams.AmountIn,
/* ToAmount: */ routeInputParams.AmountOut,
/* Type: */ mtType,
/* CrossTxID: */ "",
)

_, err = api.s.transactionManager.InsertMultiTransaction(multiTx)
if err != nil {
return
}
//////////////////////////////////////////////////////////////////////////////

response.SentTransactions, err = api.s.transactionManager.SendRouterTransactions(ctx, multiTx)
var (
chainIDs []uint64
addresses []common.Address
)
for _, tx := range response.SentTransactions {
chainIDs = append(chainIDs, tx.FromChain)
addresses = append(addresses, common.Address(tx.FromAddress))
go func(chainId uint64, txHash common.Hash) {
err = api.s.transactionManager.WatchTransaction(context.Background(), chainId, txHash)
if err != nil {
return
}
}(tx.FromChain, common.Hash(tx.Hash))
}
err = api.s.transferController.CheckRecentHistory(chainIDs, addresses)
}()
}

func (api *API) GetMultiTransactions(ctx context.Context, transactionIDs []wcommon.MultiTransactionIDType) ([]*transfer.MultiTransaction, error) {
log.Debug("wallet.api.GetMultiTransactions", "IDs.len", len(transactionIDs))
return api.s.transactionManager.GetMultiTransactions(ctx, transactionIDs)
Expand Down
4 changes: 3 additions & 1 deletion services/wallet/common/const.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package common

import (
"math/big"
"strconv"
"time"

Expand Down Expand Up @@ -32,7 +33,8 @@ const (
)

var (
ZeroAddress = ethCommon.HexToAddress("0x0000000000000000000000000000000000000000")
ZeroAddress = ethCommon.Address{}
ZeroBigIntValue = big.NewInt(0)

SupportedNetworks = map[uint64]bool{
EthereumMainnet: true,
Expand Down
32 changes: 32 additions & 0 deletions services/wallet/common/helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package common

import (
"fmt"
"math/big"
"strings"

"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/status-im/status-go/contracts/ierc20"
)

func PackApprovalInputData(amountIn *big.Int, approvalContractAddress *common.Address) ([]byte, error) {
if approvalContractAddress == nil || *approvalContractAddress == ZeroAddress {
return []byte{}, nil
}

erc20ABI, err := abi.JSON(strings.NewReader(ierc20.IERC20ABI))
if err != nil {
return []byte{}, err
}

return erc20ABI.Pack("approve", approvalContractAddress, amountIn)
}

func GetTokenIdFromSymbol(symbol string) (*big.Int, error) {
id, success := big.NewInt(0).SetString(symbol, 0)
if !success {
return nil, fmt.Errorf("failed to convert %s to big.Int", symbol)
}
return id, nil
}
10 changes: 10 additions & 0 deletions services/wallet/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package wallet

import (
"github.com/status-im/status-go/errors"
)

// Abbreviation `W` for the error code stands for Wallet
var (
ErrCannotResolveRouteId = &errors.ErrorResponse{Code: errors.ErrorCode("W-001"), Details: "cannot resolve route id"}
)
6 changes: 6 additions & 0 deletions services/wallet/requests/router_build_transactions_params.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package requests

type RouterBuildTransactionsParams struct {
Uuid string `json:"uuid"`
SlippagePercentage float32 `json:"slippagePercentage"`
}
5 changes: 3 additions & 2 deletions services/wallet/requests/router_input_params.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ type RouteInputParams struct {
AmountIn *hexutil.Big `json:"amountIn" validate:"required"`
AmountOut *hexutil.Big `json:"amountOut"`
TokenID string `json:"tokenID" validate:"required"`
TokenIDIsOwnerToken bool `json:"tokenIDIsOwnerToken"`
ToTokenID string `json:"toTokenID"`
DisabledFromChainIDs []uint64 `json:"disabledFromChainIDs"`
DisabledToChainIDs []uint64 `json:"disabledToChainIDs"`
Expand Down Expand Up @@ -123,8 +124,8 @@ func (i *RouteInputParams) Validate() error {

if i.AmountIn != nil &&
i.AmountOut != nil &&
i.AmountIn.ToInt().Cmp(pathprocessor.ZeroBigIntValue) > 0 &&
i.AmountOut.ToInt().Cmp(pathprocessor.ZeroBigIntValue) > 0 {
i.AmountIn.ToInt().Cmp(walletCommon.ZeroBigIntValue) > 0 &&
i.AmountOut.ToInt().Cmp(walletCommon.ZeroBigIntValue) > 0 {
return ErrSwapAmountInAmountOutMustBeExclusive
}

Expand Down
8 changes: 8 additions & 0 deletions services/wallet/requests/router_send_transactions_params.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package requests

import "github.com/status-im/status-go/services/wallet/transfer"

type RouterSendTransactionsParams struct {
Uuid string `json:"uuid"`
Signatures map[string]transfer.SignatureDetails `json:"signatures"`
}
68 changes: 68 additions & 0 deletions services/wallet/responses/router_transactions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package responses

import (
"github.com/status-im/status-go/errors"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/transactions"
)

type SendDetails struct {
Uuid string `json:"uuid"`
SendType int `json:"sendType"`
FromAddress types.Address `json:"fromAddress"`
ToAddress types.Address `json:"toAddress"`
FromToken string `json:"fromToken"`
ToToken string `json:"toToken"`
FromAmount string `json:"fromAmount"` // total amount
ToAmount string `json:"toAmount"`
OwnerTokenBeingSent bool `json:"ownerTokenBeingSent"`
ErrorResponse *errors.ErrorResponse `json:"errorResponse,omitempty"`
}

type SigningDetails struct {
Address types.Address `json:"address"`
AddressPath string `json:"addressPath"`
KeyUid string `json:"keyUid"`
SignOnKeycard bool `json:"signOnKeycard"`
Hashes []types.Hash `json:"hashes"`
}

type RouterTransactionsForSigning struct {
SendDetails *SendDetails `json:"sendDetails"`
SigningDetails *SigningDetails `json:"signingDetails"`
}

type RouterSentTransaction struct {
FromAddress types.Address `json:"fromAddress"`
ToAddress types.Address `json:"toAddress"`
FromChain uint64 `json:"fromChain"`
ToChain uint64 `json:"toChain"`
FromToken string `json:"fromToken"`
ToToken string `json:"toToken"`
Amount string `json:"amount"` // amount of the transaction
Hash types.Hash `json:"hash"`
ApprovalTx bool `json:"approvalTx"`
}

type RouterSentTransactions struct {
SendDetails *SendDetails `json:"sendDetails"`
SentTransactions []*RouterSentTransaction `json:"sentTransactions"`
}

func NewRouterSentTransaction(sendArgs *transactions.SendTxArgs, hash types.Hash, approvalTx bool) *RouterSentTransaction {
addr := types.Address{}
if sendArgs.To != nil {
addr = *sendArgs.To
}
return &RouterSentTransaction{
FromAddress: sendArgs.From,
ToAddress: addr,
FromChain: sendArgs.FromChainID,
ToChain: sendArgs.ToChainID,
FromToken: sendArgs.FromTokenID,
ToToken: sendArgs.ToTokenID,
Amount: sendArgs.Value.String(),
Hash: hash,
ApprovalTx: approvalTx,
}
}
Loading

0 comments on commit 89503c9

Please sign in to comment.