feat: swap via paraswap
This commit is contained in:
parent
c921079761
commit
7b09ee073d
|
@ -18,7 +18,7 @@ var contractAddressByChainID = map[uint64]common.Address{
|
||||||
func ContractAddress(chainID uint64) (common.Address, error) {
|
func ContractAddress(chainID uint64) (common.Address, error) {
|
||||||
addr, exists := contractAddressByChainID[chainID]
|
addr, exists := contractAddressByChainID[chainID]
|
||||||
if !exists {
|
if !exists {
|
||||||
return *new(common.Address), ErrorNotAvailableOnChainID
|
return common.Address{}, ErrorNotAvailableOnChainID
|
||||||
}
|
}
|
||||||
return addr, nil
|
return addr, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -426,6 +426,7 @@ func (api *API) GetSuggestedRoutes(
|
||||||
addrTo common.Address,
|
addrTo common.Address,
|
||||||
amountIn *hexutil.Big,
|
amountIn *hexutil.Big,
|
||||||
tokenID string,
|
tokenID string,
|
||||||
|
toTokenID string,
|
||||||
disabledFromChainIDs,
|
disabledFromChainIDs,
|
||||||
disabledToChaindIDs,
|
disabledToChaindIDs,
|
||||||
preferedChainIDs []uint64,
|
preferedChainIDs []uint64,
|
||||||
|
@ -433,7 +434,7 @@ func (api *API) GetSuggestedRoutes(
|
||||||
fromLockedAmount map[uint64]*hexutil.Big,
|
fromLockedAmount map[uint64]*hexutil.Big,
|
||||||
) (*SuggestedRoutes, error) {
|
) (*SuggestedRoutes, error) {
|
||||||
log.Debug("call to GetSuggestedRoutes")
|
log.Debug("call to GetSuggestedRoutes")
|
||||||
return api.router.suggestedRoutes(ctx, sendType, addrFrom, addrTo, amountIn.ToInt(), tokenID, disabledFromChainIDs,
|
return api.router.suggestedRoutes(ctx, sendType, addrFrom, addrTo, amountIn.ToInt(), tokenID, toTokenID, disabledFromChainIDs,
|
||||||
disabledToChaindIDs, preferedChainIDs, gasFeeMode, fromLockedAmount)
|
disabledToChaindIDs, preferedChainIDs, gasFeeMode, fromLockedAmount)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,8 @@ import (
|
||||||
"github.com/status-im/status-go/transactions"
|
"github.com/status-im/status-go/transactions"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var ZeroAddress = common.Address{}
|
||||||
|
|
||||||
const IncreaseEstimatedGasFactor = 1.1
|
const IncreaseEstimatedGasFactor = 1.1
|
||||||
|
|
||||||
func getSigner(chainID uint64, from types.Address, verifiedAccount *account.SelectedExtKey) bind.SignerFn {
|
func getSigner(chainID uint64, from types.Address, verifiedAccount *account.SelectedExtKey) bind.SignerFn {
|
||||||
|
@ -31,6 +33,7 @@ type TransactionBridge struct {
|
||||||
CbridgeTx *CBridgeTxArgs
|
CbridgeTx *CBridgeTxArgs
|
||||||
ERC721TransferTx *ERC721TransferTxArgs
|
ERC721TransferTx *ERC721TransferTxArgs
|
||||||
ERC1155TransferTx *ERC1155TransferTxArgs
|
ERC1155TransferTx *ERC1155TransferTxArgs
|
||||||
|
SwapTx *SwapTxArgs
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TransactionBridge) Value() *big.Int {
|
func (t *TransactionBridge) Value() *big.Int {
|
||||||
|
@ -99,9 +102,9 @@ func (t *TransactionBridge) Data() types.HexBytes {
|
||||||
|
|
||||||
type Bridge interface {
|
type Bridge interface {
|
||||||
Name() string
|
Name() string
|
||||||
Can(from *params.Network, to *params.Network, token *token.Token, balance *big.Int) (bool, error)
|
Can(from *params.Network, to *params.Network, token *token.Token, toToken *token.Token, balance *big.Int) (bool, error)
|
||||||
CalculateFees(from, to *params.Network, token *token.Token, amountIn *big.Int, nativeTokenPrice, tokenPrice float64, gasPrice *big.Float) (*big.Int, *big.Int, error)
|
CalculateFees(from, to *params.Network, token *token.Token, amountIn *big.Int, nativeTokenPrice, tokenPrice float64, gasPrice *big.Float) (*big.Int, *big.Int, error)
|
||||||
EstimateGas(fromNetwork *params.Network, toNetwork *params.Network, from common.Address, to common.Address, token *token.Token, amountIn *big.Int) (uint64, error)
|
EstimateGas(fromNetwork *params.Network, toNetwork *params.Network, from common.Address, to common.Address, token *token.Token, toToken *token.Token, amountIn *big.Int) (uint64, error)
|
||||||
CalculateAmountOut(from, to *params.Network, amountIn *big.Int, symbol string) (*big.Int, error)
|
CalculateAmountOut(from, to *params.Network, amountIn *big.Int, symbol string) (*big.Int, error)
|
||||||
Send(sendArgs *TransactionBridge, verifiedAccount *account.SelectedExtKey) (types.Hash, error)
|
Send(sendArgs *TransactionBridge, verifiedAccount *account.SelectedExtKey) (types.Hash, error)
|
||||||
GetContractAddress(network *params.Network, token *token.Token) *common.Address
|
GetContractAddress(network *params.Network, token *token.Token) *common.Address
|
||||||
|
|
|
@ -153,7 +153,7 @@ func (s *CBridge) getTransferConfig(isTest bool) (*cbridge.GetTransferConfigsRes
|
||||||
return &res, nil
|
return &res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CBridge) Can(from, to *params.Network, token *token.Token, balance *big.Int) (bool, error) {
|
func (s *CBridge) Can(from, to *params.Network, token *token.Token, toToken *token.Token, balance *big.Int) (bool, error) {
|
||||||
if from.ChainID == to.ChainID {
|
if from.ChainID == to.ChainID {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
@ -221,7 +221,7 @@ func (s *CBridge) CalculateFees(from, to *params.Network, token *token.Token, am
|
||||||
return big.NewInt(0), new(big.Int).Add(baseFee, percFee), nil
|
return big.NewInt(0), new(big.Int).Add(baseFee, percFee), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CBridge) EstimateGas(fromNetwork *params.Network, toNetwork *params.Network, from common.Address, to common.Address, token *token.Token, amountIn *big.Int) (uint64, error) {
|
func (s *CBridge) EstimateGas(fromNetwork *params.Network, toNetwork *params.Network, from common.Address, to common.Address, token *token.Token, toToken *token.Token, amountIn *big.Int) (uint64, error) {
|
||||||
var input []byte
|
var input []byte
|
||||||
value := new(big.Int)
|
value := new(big.Int)
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,7 @@ func (s *ERC1155TransferBridge) Name() string {
|
||||||
return "ERC1155Transfer"
|
return "ERC1155Transfer"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ERC1155TransferBridge) Can(from, to *params.Network, token *token.Token, balance *big.Int) (bool, error) {
|
func (s *ERC1155TransferBridge) Can(from, to *params.Network, token *token.Token, toToken *token.Token, balance *big.Int) (bool, error) {
|
||||||
return from.ChainID == to.ChainID, nil
|
return from.ChainID == to.ChainID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ func (s *ERC1155TransferBridge) CalculateFees(from, to *params.Network, token *t
|
||||||
return big.NewInt(0), big.NewInt(0), nil
|
return big.NewInt(0), big.NewInt(0), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ERC1155TransferBridge) EstimateGas(fromNetwork *params.Network, toNetwork *params.Network, from common.Address, to common.Address, token *token.Token, amountIn *big.Int) (uint64, error) {
|
func (s *ERC1155TransferBridge) EstimateGas(fromNetwork *params.Network, toNetwork *params.Network, from common.Address, to common.Address, token *token.Token, toToken *token.Token, amountIn *big.Int) (uint64, error) {
|
||||||
ethClient, err := s.rpcClient.EthClient(fromNetwork.ChainID)
|
ethClient, err := s.rpcClient.EthClient(fromNetwork.ChainID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
|
|
|
@ -40,7 +40,7 @@ func (s *ERC721TransferBridge) Name() string {
|
||||||
return "ERC721Transfer"
|
return "ERC721Transfer"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ERC721TransferBridge) Can(from, to *params.Network, token *token.Token, balance *big.Int) (bool, error) {
|
func (s *ERC721TransferBridge) Can(from, to *params.Network, token *token.Token, toToken *token.Token, balance *big.Int) (bool, error) {
|
||||||
return from.ChainID == to.ChainID, nil
|
return from.ChainID == to.ChainID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@ func (s *ERC721TransferBridge) CalculateFees(from, to *params.Network, token *to
|
||||||
return big.NewInt(0), big.NewInt(0), nil
|
return big.NewInt(0), big.NewInt(0), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ERC721TransferBridge) EstimateGas(fromNetwork *params.Network, toNetwork *params.Network, from common.Address, to common.Address, token *token.Token, amountIn *big.Int) (uint64, error) {
|
func (s *ERC721TransferBridge) EstimateGas(fromNetwork *params.Network, toNetwork *params.Network, from common.Address, to common.Address, token *token.Token, toToken *token.Token, amountIn *big.Int) (uint64, error) {
|
||||||
ethClient, err := s.rpcClient.EthClient(fromNetwork.ChainID)
|
ethClient, err := s.rpcClient.EthClient(fromNetwork.ChainID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
|
|
|
@ -108,7 +108,7 @@ func (h *HopBridge) Name() string {
|
||||||
return "Hop"
|
return "Hop"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *HopBridge) Can(from, to *params.Network, token *token.Token, balance *big.Int) (bool, error) {
|
func (h *HopBridge) Can(from, to *params.Network, token *token.Token, toToken *token.Token, balance *big.Int) (bool, error) {
|
||||||
if balance.Cmp(big.NewInt(0)) == 0 {
|
if balance.Cmp(big.NewInt(0)) == 0 {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
@ -132,7 +132,7 @@ func (h *HopBridge) Can(from, to *params.Network, token *token.Token, balance *b
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *HopBridge) EstimateGas(fromNetwork *params.Network, toNetwork *params.Network, from common.Address, to common.Address, token *token.Token, amountIn *big.Int) (uint64, error) {
|
func (h *HopBridge) EstimateGas(fromNetwork *params.Network, toNetwork *params.Network, from common.Address, to common.Address, token *token.Token, toToken *token.Token, amountIn *big.Int) (uint64, error) {
|
||||||
var input []byte
|
var input []byte
|
||||||
value := new(big.Int)
|
value := new(big.Int)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,202 @@
|
||||||
|
package bridge
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"math/big"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
ethTypes "github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/status-im/status-go/account"
|
||||||
|
"github.com/status-im/status-go/eth-node/types"
|
||||||
|
"github.com/status-im/status-go/params"
|
||||||
|
"github.com/status-im/status-go/rpc"
|
||||||
|
walletCommon "github.com/status-im/status-go/services/wallet/common"
|
||||||
|
"github.com/status-im/status-go/services/wallet/thirdparty/paraswap"
|
||||||
|
"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/transactions"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SwapTxArgs struct {
|
||||||
|
transactions.SendTxArgs
|
||||||
|
ChainID uint64 `json:"chainId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SwapParaswap struct {
|
||||||
|
paraswapClient *paraswap.ClientV5
|
||||||
|
priceRoute paraswap.Route
|
||||||
|
transactor *transactions.Transactor
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSwapParaswap(rpcClient *rpc.Client, transactor *transactions.Transactor, tokenManager *walletToken.Manager) *SwapParaswap {
|
||||||
|
return &SwapParaswap{
|
||||||
|
paraswapClient: paraswap.NewClientV5(walletCommon.EthereumMainnet),
|
||||||
|
transactor: transactor,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SwapParaswap) Name() string {
|
||||||
|
return "Paraswap"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SwapParaswap) Can(from, to *params.Network, token *walletToken.Token, toToken *walletToken.Token, balance *big.Int) (bool, error) {
|
||||||
|
if token == nil || toToken == nil {
|
||||||
|
return false, errors.New("token and toToken cannot be nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if from.ChainID != to.ChainID {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
s.paraswapClient.SetChainID(from.ChainID)
|
||||||
|
|
||||||
|
searchForToken := token.Address == ZeroAddress
|
||||||
|
searchForToToken := toToken.Address == ZeroAddress
|
||||||
|
if searchForToToken || searchForToken {
|
||||||
|
tokensList, err := s.paraswapClient.FetchTokensList(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, t := range tokensList {
|
||||||
|
if searchForToken && t.Symbol == token.Symbol {
|
||||||
|
token.Address = common.HexToAddress(t.Address)
|
||||||
|
token.Decimals = t.Decimals
|
||||||
|
if !searchForToToken {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if searchForToToken && t.Symbol == toToken.Symbol {
|
||||||
|
toToken.Address = common.HexToAddress(t.Address)
|
||||||
|
toToken.Decimals = t.Decimals
|
||||||
|
if !searchForToken {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if token.Address == ZeroAddress || toToken.Address == ZeroAddress {
|
||||||
|
return false, errors.New("cannot resolve token/s")
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SwapParaswap) CalculateFees(from, to *params.Network, token *token.Token, amountIn *big.Int, nativeTokenPrice, tokenPrice float64, gasPrice *big.Float) (*big.Int, *big.Int, error) {
|
||||||
|
return big.NewInt(0), big.NewInt(0), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SwapParaswap) EstimateGas(fromNetwork *params.Network, toNetwork *params.Network, from common.Address, to common.Address, token *token.Token, toToken *token.Token, amountIn *big.Int) (uint64, error) {
|
||||||
|
priceRoute, err := s.paraswapClient.FetchPriceRoute(context.Background(), token.Address, token.Decimals, toToken.Address, toToken.Decimals, amountIn, from, to)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.priceRoute = priceRoute
|
||||||
|
|
||||||
|
return priceRoute.GasCost.Uint64(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SwapParaswap) GetContractAddress(network *params.Network, token *token.Token) *common.Address {
|
||||||
|
var address common.Address
|
||||||
|
if network.ChainID == walletCommon.EthereumMainnet {
|
||||||
|
address = common.HexToAddress("0x216b4b4ba9f3e719726886d34a177484278bfcae")
|
||||||
|
} else if network.ChainID == walletCommon.ArbitrumMainnet {
|
||||||
|
address = common.HexToAddress("0x216b4b4ba9f3e719726886d34a177484278bfcae")
|
||||||
|
} else if network.ChainID == walletCommon.OptimismMainnet {
|
||||||
|
address = common.HexToAddress("0x216b4b4ba9f3e719726886d34a177484278bfcae")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &address
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SwapParaswap) BuildTx(network *params.Network, fromAddress common.Address, toAddress common.Address, token *token.Token, amountIn *big.Int) (*ethTypes.Transaction, error) {
|
||||||
|
toAddr := types.Address(toAddress)
|
||||||
|
sendArgs := &TransactionBridge{
|
||||||
|
SwapTx: &SwapTxArgs{
|
||||||
|
SendTxArgs: transactions.SendTxArgs{
|
||||||
|
From: types.Address(fromAddress),
|
||||||
|
To: &toAddr,
|
||||||
|
Value: (*hexutil.Big)(amountIn),
|
||||||
|
Data: types.HexBytes("0x0"),
|
||||||
|
Symbol: token.Symbol,
|
||||||
|
},
|
||||||
|
ChainID: network.ChainID,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.BuildTransaction(sendArgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SwapParaswap) prepareTransaction(sendArgs *TransactionBridge) error {
|
||||||
|
tx, err := s.paraswapClient.BuildTransaction(context.Background(), s.priceRoute.SrcTokenAddress, s.priceRoute.SrcTokenDecimals, s.priceRoute.SrcAmount.Int,
|
||||||
|
s.priceRoute.DestTokenAddress, s.priceRoute.DestTokenDecimals, s.priceRoute.DestAmount.Int, common.Address(sendArgs.SwapTx.From), common.Address(*sendArgs.SwapTx.To), s.priceRoute.RawPriceRoute)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
value, ok := new(big.Int).SetString(tx.Value, 10)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("error converting amount to big.Int")
|
||||||
|
}
|
||||||
|
|
||||||
|
gas, err := strconv.ParseUint(tx.Gas, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
gasPrice, ok := new(big.Int).SetString(tx.GasPrice, 10)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("error converting amount to big.Int")
|
||||||
|
}
|
||||||
|
|
||||||
|
sendArgs.ChainID = tx.ChainID
|
||||||
|
sendArgs.SwapTx.ChainID = tx.ChainID
|
||||||
|
toAddr := types.HexToAddress(tx.To)
|
||||||
|
sendArgs.SwapTx.From = types.HexToAddress(tx.From)
|
||||||
|
sendArgs.SwapTx.To = &toAddr
|
||||||
|
sendArgs.SwapTx.Value = (*hexutil.Big)(value)
|
||||||
|
sendArgs.SwapTx.Gas = (*hexutil.Uint64)(&gas)
|
||||||
|
sendArgs.SwapTx.GasPrice = (*hexutil.Big)(gasPrice)
|
||||||
|
sendArgs.SwapTx.Data = types.Hex2Bytes(tx.Data)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SwapParaswap) BuildTransaction(sendArgs *TransactionBridge) (*ethTypes.Transaction, error) {
|
||||||
|
err := s.prepareTransaction(sendArgs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return s.transactor.ValidateAndBuildTransaction(sendArgs.ChainID, sendArgs.SwapTx.SendTxArgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SwapParaswap) Send(sendArgs *TransactionBridge, verifiedAccount *account.SelectedExtKey) (types.Hash, error) {
|
||||||
|
|
||||||
|
txBridgeArgs := &TransactionBridge{
|
||||||
|
SwapTx: &SwapTxArgs{
|
||||||
|
SendTxArgs: transactions.SendTxArgs{
|
||||||
|
From: sendArgs.SwapTx.From,
|
||||||
|
To: sendArgs.SwapTx.To,
|
||||||
|
MultiTransactionID: sendArgs.SwapTx.MultiTransactionID,
|
||||||
|
Symbol: sendArgs.SwapTx.Symbol,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := s.prepareTransaction(txBridgeArgs)
|
||||||
|
if err != nil {
|
||||||
|
return types.Hash{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.transactor.SendTransactionWithChainID(txBridgeArgs.ChainID, txBridgeArgs.SwapTx.SendTxArgs, verifiedAccount)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SwapParaswap) CalculateAmountOut(from, to *params.Network, amountIn *big.Int, symbol string) (*big.Int, error) {
|
||||||
|
return s.priceRoute.DestAmount.Int, nil
|
||||||
|
}
|
|
@ -32,7 +32,7 @@ func (s *TransferBridge) Name() string {
|
||||||
return "Transfer"
|
return "Transfer"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TransferBridge) Can(from, to *params.Network, token *token.Token, balance *big.Int) (bool, error) {
|
func (s *TransferBridge) Can(from, to *params.Network, token *token.Token, toToken *token.Token, balance *big.Int) (bool, error) {
|
||||||
return from.ChainID == to.ChainID, nil
|
return from.ChainID == to.ChainID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ func (s *TransferBridge) CalculateFees(from, to *params.Network, token *token.To
|
||||||
return big.NewInt(0), big.NewInt(0), nil
|
return big.NewInt(0), big.NewInt(0), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TransferBridge) EstimateGas(fromNetwork *params.Network, toNetwork *params.Network, from common.Address, to common.Address, token *token.Token, amountIn *big.Int) (uint64, error) {
|
func (s *TransferBridge) EstimateGas(fromNetwork *params.Network, toNetwork *params.Network, from common.Address, to common.Address, token *token.Token, toToken *token.Token, amountIn *big.Int) (uint64, error) {
|
||||||
estimation := uint64(0)
|
estimation := uint64(0)
|
||||||
var err error
|
var err error
|
||||||
if token.Symbol == "ETH" {
|
if token.Symbol == "ETH" {
|
||||||
|
|
|
@ -26,6 +26,7 @@ import (
|
||||||
"github.com/status-im/status-go/services/wallet/bridge"
|
"github.com/status-im/status-go/services/wallet/bridge"
|
||||||
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/token"
|
"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/transactions"
|
"github.com/status-im/status-go/transactions"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -45,6 +46,7 @@ const (
|
||||||
Bridge
|
Bridge
|
||||||
ERC721Transfer
|
ERC721Transfer
|
||||||
ERC1155Transfer
|
ERC1155Transfer
|
||||||
|
Swap
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s SendType) IsCollectiblesTransfer() bool {
|
func (s SendType) IsCollectiblesTransfer() bool {
|
||||||
|
@ -96,7 +98,7 @@ func (s SendType) FindToken(service *Service, account common.Address, network *p
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s SendType) isTransfer() bool {
|
func (s SendType) isTransfer() bool {
|
||||||
return s == Transfer || s.IsCollectiblesTransfer()
|
return s == Transfer || s == Swap || s.IsCollectiblesTransfer()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s SendType) needL1Fee() bool {
|
func (s SendType) needL1Fee() bool {
|
||||||
|
@ -112,6 +114,10 @@ func (s SendType) isAvailableBetween(from, to *params.Network) bool {
|
||||||
return from.ChainID != to.ChainID
|
return from.ChainID != to.ChainID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s == Swap {
|
||||||
|
return from.ChainID == to.ChainID
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,11 +142,19 @@ func (s SendType) canUseBridge(b bridge.Bridge) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s SendType) isAvailableFor(network *params.Network) bool {
|
func (s SendType) isAvailableFor(network *params.Network) bool {
|
||||||
|
if s == Swap {
|
||||||
|
return network.ChainID == walletCommon.EthereumMainnet ||
|
||||||
|
network.ChainID == walletCommon.OptimismMainnet ||
|
||||||
|
network.ChainID == walletCommon.ArbitrumMainnet
|
||||||
|
}
|
||||||
|
|
||||||
if s == Transfer || s == Bridge || s.IsCollectiblesTransfer() {
|
if s == Transfer || s == Bridge || s.IsCollectiblesTransfer() {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if network.ChainID == 1 || network.ChainID == 5 || network.ChainID == 11155111 {
|
if network.ChainID == walletCommon.EthereumMainnet ||
|
||||||
|
network.ChainID == walletCommon.EthereumGoerli ||
|
||||||
|
network.ChainID == walletCommon.EthereumSepolia {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -430,11 +444,13 @@ func NewRouter(s *Service) *Router {
|
||||||
erc1155Transfer := bridge.NewERC1155TransferBridge(s.rpcClient, s.transactor)
|
erc1155Transfer := bridge.NewERC1155TransferBridge(s.rpcClient, s.transactor)
|
||||||
cbridge := bridge.NewCbridge(s.rpcClient, s.transactor, s.tokenManager)
|
cbridge := bridge.NewCbridge(s.rpcClient, s.transactor, s.tokenManager)
|
||||||
hop := bridge.NewHopBridge(s.rpcClient, s.transactor, s.tokenManager)
|
hop := bridge.NewHopBridge(s.rpcClient, s.transactor, s.tokenManager)
|
||||||
|
paraswap := bridge.NewSwapParaswap(s.rpcClient, s.transactor, s.tokenManager)
|
||||||
bridges[transfer.Name()] = transfer
|
bridges[transfer.Name()] = transfer
|
||||||
bridges[erc721Transfer.Name()] = erc721Transfer
|
bridges[erc721Transfer.Name()] = erc721Transfer
|
||||||
bridges[hop.Name()] = hop
|
bridges[hop.Name()] = hop
|
||||||
bridges[cbridge.Name()] = cbridge
|
bridges[cbridge.Name()] = cbridge
|
||||||
bridges[erc1155Transfer.Name()] = erc1155Transfer
|
bridges[erc1155Transfer.Name()] = erc1155Transfer
|
||||||
|
bridges[paraswap.Name()] = paraswap
|
||||||
|
|
||||||
return &Router{s, bridges, s.rpcClient}
|
return &Router{s, bridges, s.rpcClient}
|
||||||
}
|
}
|
||||||
|
@ -455,55 +471,54 @@ type Router struct {
|
||||||
rpcClient *rpc.Client
|
rpcClient *rpc.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Router) requireApproval(ctx context.Context, sendType SendType, bridge bridge.Bridge, account common.Address, network *params.Network, token *token.Token, amountIn *big.Int) (
|
func (r *Router) requireApproval(ctx context.Context, sendType SendType, approvalContractAddress *common.Address, account common.Address, network *params.Network, token *token.Token, amountIn *big.Int) (
|
||||||
bool, *big.Int, uint64, uint64, *common.Address, error) {
|
bool, *big.Int, uint64, uint64, error) {
|
||||||
if sendType.IsCollectiblesTransfer() {
|
if sendType.IsCollectiblesTransfer() {
|
||||||
return false, nil, 0, 0, nil, nil
|
return false, nil, 0, 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if token.IsNative() {
|
if token.IsNative() {
|
||||||
return false, nil, 0, 0, nil, nil
|
return false, nil, 0, 0, nil
|
||||||
}
|
}
|
||||||
contractMaker, err := contracts.NewContractMaker(r.rpcClient)
|
contractMaker, err := contracts.NewContractMaker(r.rpcClient)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, nil, 0, 0, nil, err
|
return false, nil, 0, 0, err
|
||||||
}
|
|
||||||
|
|
||||||
bridgeAddress := bridge.GetContractAddress(network, token)
|
|
||||||
if bridgeAddress == nil {
|
|
||||||
return false, nil, 0, 0, nil, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
contract, err := contractMaker.NewERC20(network.ChainID, token.Address)
|
contract, err := contractMaker.NewERC20(network.ChainID, token.Address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, nil, 0, 0, nil, err
|
return false, nil, 0, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if approvalContractAddress == nil || *approvalContractAddress == bridge.ZeroAddress {
|
||||||
|
return false, nil, 0, 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
allowance, err := contract.Allowance(&bind.CallOpts{
|
allowance, err := contract.Allowance(&bind.CallOpts{
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
}, account, *bridgeAddress)
|
}, account, *approvalContractAddress)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, nil, 0, 0, nil, err
|
return false, nil, 0, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if allowance.Cmp(amountIn) >= 0 {
|
if allowance.Cmp(amountIn) >= 0 {
|
||||||
return false, nil, 0, 0, nil, nil
|
return false, nil, 0, 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
ethClient, err := r.rpcClient.EthClient(network.ChainID)
|
ethClient, err := r.rpcClient.EthClient(network.ChainID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, nil, 0, 0, nil, err
|
return false, nil, 0, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
erc20ABI, err := abi.JSON(strings.NewReader(ierc20.IERC20ABI))
|
erc20ABI, err := abi.JSON(strings.NewReader(ierc20.IERC20ABI))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, nil, 0, 0, nil, err
|
return false, nil, 0, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := erc20ABI.Pack("approve", bridgeAddress, amountIn)
|
data, err := erc20ABI.Pack("approve", approvalContractAddress, amountIn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, nil, 0, 0, nil, err
|
return false, nil, 0, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
estimate, err := ethClient.EstimateGas(context.Background(), ethereum.CallMsg{
|
estimate, err := ethClient.EstimateGas(context.Background(), ethereum.CallMsg{
|
||||||
|
@ -513,25 +528,25 @@ func (r *Router) requireApproval(ctx context.Context, sendType SendType, bridge
|
||||||
Data: data,
|
Data: data,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, nil, 0, 0, nil, err
|
return false, nil, 0, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// fetching l1 fee
|
// fetching l1 fee
|
||||||
|
var l1Fee uint64
|
||||||
oracleContractAddress, err := gaspriceoracle.ContractAddress(network.ChainID)
|
oracleContractAddress, err := gaspriceoracle.ContractAddress(network.ChainID)
|
||||||
if err != nil {
|
if err == nil {
|
||||||
return false, nil, 0, 0, nil, err
|
oracleContract, err := gaspriceoracle.NewGaspriceoracleCaller(oracleContractAddress, ethClient)
|
||||||
|
if err != nil {
|
||||||
|
return false, nil, 0, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
callOpt := &bind.CallOpts{}
|
||||||
|
|
||||||
|
l1FeeResult, _ := oracleContract.GetL1Fee(callOpt, data)
|
||||||
|
l1Fee = l1FeeResult.Uint64()
|
||||||
}
|
}
|
||||||
|
|
||||||
oracleContract, err := gaspriceoracle.NewGaspriceoracleCaller(oracleContractAddress, ethClient)
|
return true, amountIn, estimate, l1Fee, nil
|
||||||
if err != nil {
|
|
||||||
return false, nil, 0, 0, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
callOpt := &bind.CallOpts{}
|
|
||||||
|
|
||||||
l1Fee, _ := oracleContract.GetL1Fee(callOpt, data)
|
|
||||||
|
|
||||||
return true, amountIn, estimate, l1Fee.Uint64(), bridgeAddress, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Router) getBalance(ctx context.Context, network *params.Network, token *token.Token, account common.Address) (*big.Int, error) {
|
func (r *Router) getBalance(ctx context.Context, network *params.Network, token *token.Token, account common.Address) (*big.Int, error) {
|
||||||
|
@ -574,6 +589,7 @@ func (r *Router) suggestedRoutes(
|
||||||
addrTo common.Address,
|
addrTo common.Address,
|
||||||
amountIn *big.Int,
|
amountIn *big.Int,
|
||||||
tokenID string,
|
tokenID string,
|
||||||
|
toTokenID string,
|
||||||
disabledFromChainIDs,
|
disabledFromChainIDs,
|
||||||
disabledToChaindIDs,
|
disabledToChaindIDs,
|
||||||
preferedChainIDs []uint64,
|
preferedChainIDs []uint64,
|
||||||
|
@ -599,6 +615,7 @@ func (r *Router) suggestedRoutes(
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
candidates = make([]*Path, 0)
|
candidates = make([]*Path, 0)
|
||||||
)
|
)
|
||||||
|
|
||||||
for networkIdx := range networks {
|
for networkIdx := range networks {
|
||||||
network := networks[networkIdx]
|
network := networks[networkIdx]
|
||||||
if network.IsTest != areTestNetworksEnabled {
|
if network.IsTest != areTestNetworksEnabled {
|
||||||
|
@ -618,6 +635,11 @@ func (r *Router) suggestedRoutes(
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var toToken *walletToken.Token
|
||||||
|
if sendType == Swap {
|
||||||
|
toToken = sendType.FindToken(r.s, common.Address{}, network, toTokenID)
|
||||||
|
}
|
||||||
|
|
||||||
nativeToken := r.s.tokenManager.FindToken(network, network.NativeCurrencySymbol)
|
nativeToken := r.s.tokenManager.FindToken(network, network.NativeCurrencySymbol)
|
||||||
if nativeToken == nil {
|
if nativeToken == nil {
|
||||||
continue
|
continue
|
||||||
|
@ -683,7 +705,7 @@ func (r *Router) suggestedRoutes(
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
can, err := bridge.Can(network, dest, token, maxAmountIn.ToInt())
|
can, err := bridge.Can(network, dest, token, toToken, maxAmountIn.ToInt())
|
||||||
if err != nil || !can {
|
if err != nil || !can {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -704,7 +726,7 @@ func (r *Router) suggestedRoutes(
|
||||||
}
|
}
|
||||||
gasLimit := uint64(0)
|
gasLimit := uint64(0)
|
||||||
if sendType.isTransfer() {
|
if sendType.isTransfer() {
|
||||||
gasLimit, err = bridge.EstimateGas(network, dest, addrFrom, addrTo, token, amountIn)
|
gasLimit, err = bridge.EstimateGas(network, dest, addrFrom, addrTo, token, toToken, amountIn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -712,12 +734,13 @@ func (r *Router) suggestedRoutes(
|
||||||
gasLimit = sendType.EstimateGas(r.s, network, addrFrom, tokenID)
|
gasLimit = sendType.EstimateGas(r.s, network, addrFrom, tokenID)
|
||||||
}
|
}
|
||||||
|
|
||||||
approvalRequired, approvalAmountRequired, approvalGasLimit, l1ApprovalFee, approvalContractAddress, err := r.requireApproval(ctx, sendType, bridge, addrFrom, network, token, amountIn)
|
approvalContractAddress := bridge.GetContractAddress(network, token)
|
||||||
|
approvalRequired, approvalAmountRequired, approvalGasLimit, l1ApprovalFee, err := r.requireApproval(ctx, sendType, approvalContractAddress, addrFrom, network, token, amountIn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
var l1GasFeeWei uint64 = 0
|
var l1GasFeeWei uint64
|
||||||
if sendType.needL1Fee() {
|
if sendType.needL1Fee() {
|
||||||
tx, err := bridge.BuildTx(network, addrFrom, addrTo, token, amountIn)
|
tx, err := bridge.BuildTx(network, addrFrom, addrTo, token, amountIn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -727,6 +750,7 @@ func (r *Router) suggestedRoutes(
|
||||||
l1GasFeeWei, _ = r.s.feesManager.getL1Fee(ctx, network.ChainID, tx)
|
l1GasFeeWei, _ = r.s.feesManager.getL1Fee(ctx, network.ChainID, tx)
|
||||||
l1GasFeeWei += l1ApprovalFee
|
l1GasFeeWei += l1ApprovalFee
|
||||||
}
|
}
|
||||||
|
|
||||||
gasFees.L1GasFee = weiToGwei(big.NewInt(int64(l1GasFeeWei)))
|
gasFees.L1GasFee = weiToGwei(big.NewInt(int64(l1GasFeeWei)))
|
||||||
|
|
||||||
requiredNativeBalance := new(big.Int).Mul(gweiToWei(maxFees), big.NewInt(int64(gasLimit)))
|
requiredNativeBalance := new(big.Int).Mul(gweiToWei(maxFees), big.NewInt(int64(gasLimit)))
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
package paraswap
|
||||||
|
|
||||||
|
type ClientV5 struct {
|
||||||
|
httpClient *HTTPClient
|
||||||
|
chainID uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClientV5(chainID uint64) *ClientV5 {
|
||||||
|
return &ClientV5{
|
||||||
|
httpClient: NewHTTPClient(),
|
||||||
|
chainID: chainID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientV5) SetChainID(chainID uint64) {
|
||||||
|
c.chainID = chainID
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
package paraswap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
netUrl "net/url"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const requestTimeout = 5 * time.Second
|
||||||
|
|
||||||
|
type HTTPClient struct {
|
||||||
|
client *http.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHTTPClient() *HTTPClient {
|
||||||
|
return &HTTPClient{
|
||||||
|
client: &http.Client{
|
||||||
|
Timeout: requestTimeout,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *HTTPClient) doGetRequest(ctx context.Context, url string, params netUrl.Values) ([]byte, error) {
|
||||||
|
if len(params) > 0 {
|
||||||
|
url = url + "?" + params.Encode()
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := c.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return body, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *HTTPClient) doPostRequest(ctx context.Context, url string, params map[string]interface{}) ([]byte, error) {
|
||||||
|
jsonData, err := json.Marshal(params)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(jsonData))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
req.Header.Set("User-Agent", "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:96.0) Gecko/20100101 Firefox/96.0")
|
||||||
|
|
||||||
|
resp, err := c.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return body, nil
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
package paraswap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
const transactionsURL = "https://apiv5.paraswap.io/transactions/%d"
|
||||||
|
|
||||||
|
type Transaction struct {
|
||||||
|
From string `json:"from"`
|
||||||
|
To string `json:"to"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
Data string `json:"data"`
|
||||||
|
GasPrice string `json:"gasPrice"`
|
||||||
|
Gas string `json:"gas"`
|
||||||
|
ChainID uint64 `json:"chainId"`
|
||||||
|
Error string `json:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientV5) BuildTransaction(ctx context.Context, srcTokenAddress common.Address, srcTokenDecimals uint, srcAmountWei *big.Int,
|
||||||
|
destTokenAddress common.Address, destTokenDecimals uint, destAmountWei *big.Int,
|
||||||
|
addressFrom common.Address, addressTo common.Address, priceRoute json.RawMessage) (Transaction, error) {
|
||||||
|
|
||||||
|
params := map[string]interface{}{}
|
||||||
|
params["srcToken"] = srcTokenAddress.Hex()
|
||||||
|
params["srcDecimals"] = srcTokenDecimals
|
||||||
|
params["srcAmount"] = srcAmountWei.String()
|
||||||
|
params["destToken"] = destTokenAddress.Hex()
|
||||||
|
params["destDecimals"] = destTokenDecimals
|
||||||
|
// params["destAmount"] = destAmountWei.String()
|
||||||
|
params["userAddress"] = addressFrom.Hex()
|
||||||
|
// params["receiver"] = addressTo.Hex() // at this point paraswap doesn't allow swap and transfer transaction
|
||||||
|
params["slippage"] = "500"
|
||||||
|
params["priceRoute"] = priceRoute
|
||||||
|
|
||||||
|
url := fmt.Sprintf(transactionsURL, c.chainID)
|
||||||
|
response, err := c.httpClient.doPostRequest(ctx, url, params)
|
||||||
|
if err != nil {
|
||||||
|
return Transaction{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tx, err := handleBuildTransactionResponse(response)
|
||||||
|
if err != nil {
|
||||||
|
return Transaction{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tx, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleBuildTransactionResponse(response []byte) (Transaction, error) {
|
||||||
|
var transactionResponse Transaction
|
||||||
|
err := json.Unmarshal(response, &transactionResponse)
|
||||||
|
if err != nil {
|
||||||
|
return Transaction{}, err
|
||||||
|
}
|
||||||
|
if transactionResponse.Error != "" {
|
||||||
|
return Transaction{}, errors.New(transactionResponse.Error)
|
||||||
|
}
|
||||||
|
return transactionResponse, nil
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
package paraswap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUnmarshallBuildTransaction(t *testing.T) {
|
||||||
|
|
||||||
|
tx := Transaction{
|
||||||
|
From: "0xbe0eb53f46cd790cd13851d5eff43d12404d33e8",
|
||||||
|
To: "0xDEF171Fe48CF0115B1d80b88dc8eAB59176FEe57",
|
||||||
|
Value: "10000000000000000",
|
||||||
|
Data: "0xf566103400000000000000000000000075e48c954594d64ef9613aeef97ad85370f13807b2b53dca60cae1d1f93f64d80703b888689f28b63c483459183f2f4271fa0308000000000000000000000000000000000000000000000000002386f26fc100000000000000000000000000000000000000000000000000000000000001c2354900000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7",
|
||||||
|
GasPrice: "47490307239",
|
||||||
|
Gas: "197142",
|
||||||
|
ChainID: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
data := []byte(`{
|
||||||
|
"from": "0xbe0eb53f46cd790cd13851d5eff43d12404d33e8",
|
||||||
|
"to": "0xDEF171Fe48CF0115B1d80b88dc8eAB59176FEe57",
|
||||||
|
"value": "10000000000000000",
|
||||||
|
"data": "0xf566103400000000000000000000000075e48c954594d64ef9613aeef97ad85370f13807b2b53dca60cae1d1f93f64d80703b888689f28b63c483459183f2f4271fa0308000000000000000000000000000000000000000000000000002386f26fc100000000000000000000000000000000000000000000000000000000000001c2354900000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7",
|
||||||
|
"gasPrice": "47490307239",
|
||||||
|
"gas": "197142",
|
||||||
|
"chainId": 1
|
||||||
|
}`)
|
||||||
|
|
||||||
|
receivedTx, err := handleBuildTransactionResponse(data)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tx, receivedTx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestForErrorOnBuildingTransaction(t *testing.T) {
|
||||||
|
data := []byte(`{
|
||||||
|
"error": "Invalid tokens"
|
||||||
|
}`)
|
||||||
|
|
||||||
|
_, err := handleBuildTransactionResponse(data)
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
package paraswap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"math/big"
|
||||||
|
netUrl "net/url"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
|
||||||
|
"github.com/status-im/status-go/services/wallet/bigint"
|
||||||
|
)
|
||||||
|
|
||||||
|
const pricesURL = "https://apiv5.paraswap.io/prices"
|
||||||
|
|
||||||
|
type Route struct {
|
||||||
|
GasCost *bigint.BigInt `json:"gasCost"`
|
||||||
|
SrcAmount *bigint.BigInt `json:"srcAmount"`
|
||||||
|
SrcTokenAddress common.Address `json:"srcToken"`
|
||||||
|
SrcTokenDecimals uint `json:"srcDecimals"`
|
||||||
|
DestAmount *bigint.BigInt `json:"destAmount"`
|
||||||
|
DestTokenAddress common.Address `json:"destToken"`
|
||||||
|
DestTokenDecimals uint `json:"destDecimals"`
|
||||||
|
RawPriceRoute json.RawMessage `json:"rawPriceRoute"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PriceRouteResponse struct {
|
||||||
|
PriceRoute json.RawMessage `json:"priceRoute"`
|
||||||
|
Error string `json:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientV5) FetchPriceRoute(ctx context.Context, srcTokenAddress common.Address, srcTokenDecimals uint,
|
||||||
|
destTokenAddress common.Address, destTokenDecimals uint, amountWei *big.Int, addressFrom common.Address,
|
||||||
|
addressTo common.Address) (Route, error) {
|
||||||
|
|
||||||
|
params := netUrl.Values{}
|
||||||
|
params.Add("srcToken", srcTokenAddress.Hex())
|
||||||
|
params.Add("srcDecimals", strconv.Itoa(int(srcTokenDecimals)))
|
||||||
|
params.Add("destToken", destTokenAddress.Hex())
|
||||||
|
params.Add("destDecimals", strconv.Itoa(int(destTokenDecimals)))
|
||||||
|
params.Add("userAddress", addressFrom.Hex())
|
||||||
|
// params.Add("receiver", addressTo.Hex()) // at this point paraswap doesn't allow swap and transfer transaction
|
||||||
|
params.Add("network", strconv.FormatUint(c.chainID, 10))
|
||||||
|
params.Add("amount", amountWei.String())
|
||||||
|
params.Add("side", "SELL")
|
||||||
|
|
||||||
|
url := pricesURL
|
||||||
|
response, err := c.httpClient.doGetRequest(ctx, url, params)
|
||||||
|
if err != nil {
|
||||||
|
return Route{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return handlePriceRouteResponse(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handlePriceRouteResponse(response []byte) (Route, error) {
|
||||||
|
var priceRouteResponse PriceRouteResponse
|
||||||
|
err := json.Unmarshal(response, &priceRouteResponse)
|
||||||
|
if err != nil {
|
||||||
|
return Route{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if priceRouteResponse.Error != "" {
|
||||||
|
return Route{}, errors.New(priceRouteResponse.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
var route Route
|
||||||
|
err = json.Unmarshal(priceRouteResponse.PriceRoute, &route)
|
||||||
|
if err != nil {
|
||||||
|
return Route{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
route.RawPriceRoute = priceRouteResponse.PriceRoute
|
||||||
|
|
||||||
|
return route, nil
|
||||||
|
}
|
|
@ -0,0 +1,126 @@
|
||||||
|
package paraswap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/status-im/status-go/services/wallet/bigint"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUnmarshallPriceRoute(t *testing.T) {
|
||||||
|
|
||||||
|
data := []byte(`{
|
||||||
|
"blockNumber": 13015909,
|
||||||
|
"network": 1,
|
||||||
|
"srcToken": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
|
||||||
|
"srcDecimals": 18,
|
||||||
|
"srcAmount": "1000000000000000000",
|
||||||
|
"destToken": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
||||||
|
"destDecimals": 18,
|
||||||
|
"destAmount": "1000000000000000000",
|
||||||
|
"bestRoute": {
|
||||||
|
"percent": 100,
|
||||||
|
"swaps": [
|
||||||
|
{
|
||||||
|
"srcToken": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
|
||||||
|
"srcDecimals": 0,
|
||||||
|
"destToken": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
||||||
|
"destDecimals": 0,
|
||||||
|
"swapExchanges": [
|
||||||
|
{
|
||||||
|
"exchange": "UniswapV2",
|
||||||
|
"srcAmount": "1000000000000000000",
|
||||||
|
"destAmount": "1000000000000000000",
|
||||||
|
"percent": 100,
|
||||||
|
"data": {
|
||||||
|
"router": "0x0000000000000000000000000000000000000000",
|
||||||
|
"path": [
|
||||||
|
"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
|
||||||
|
"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
|
||||||
|
],
|
||||||
|
"factory": "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f",
|
||||||
|
"initCode": "0x96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f",
|
||||||
|
"feeFactor": 10000,
|
||||||
|
"pools": [
|
||||||
|
{
|
||||||
|
"address": "0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc",
|
||||||
|
"fee": 30,
|
||||||
|
"direction": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"gasUSD": "13.227195"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"others": {
|
||||||
|
"exchange": "UniswapV2",
|
||||||
|
"srcAmount": "1000000000000000000",
|
||||||
|
"destAmount": "3255989380",
|
||||||
|
"unit": "3255989380",
|
||||||
|
"data": {
|
||||||
|
"router": "0x0000000000000000000000000000000000000000",
|
||||||
|
"path": [
|
||||||
|
"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
|
||||||
|
"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
|
||||||
|
],
|
||||||
|
"factory": "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f",
|
||||||
|
"initCode": "0x96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f",
|
||||||
|
"feeFactor": 10000,
|
||||||
|
"pools": [
|
||||||
|
{
|
||||||
|
"address": "0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc",
|
||||||
|
"fee": 30,
|
||||||
|
"direction": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"gasUSD": "13.227195"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"gasCostUSD": "11.947163",
|
||||||
|
"gasCost": "111435",
|
||||||
|
"side": "SELL",
|
||||||
|
"tokenTransferProxy": "0x3e7d31751347BAacf35945074a4a4A41581B2271",
|
||||||
|
"contractAddress": "0x485D2446711E141D2C8a94bC24BeaA5d5A110D74",
|
||||||
|
"contractMethod": "swapOnUniswap",
|
||||||
|
"srcUSD": "3230.3000000000",
|
||||||
|
"destUSD": "3218.9300566052",
|
||||||
|
"partner": "paraswap.io",
|
||||||
|
"partnerFee": 0,
|
||||||
|
"maxImpactReached": false,
|
||||||
|
"hmac": "319c5cf83098a07aeebb11bed6310db51311201f"
|
||||||
|
}`)
|
||||||
|
|
||||||
|
responseData := []byte(fmt.Sprintf(`{"priceRoute":%s}`, string(data)))
|
||||||
|
|
||||||
|
route := Route{
|
||||||
|
GasCost: &bigint.BigInt{Int: big.NewInt(111435)},
|
||||||
|
SrcAmount: &bigint.BigInt{Int: big.NewInt(1000000000000000000)},
|
||||||
|
SrcTokenAddress: common.HexToAddress("0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"),
|
||||||
|
SrcTokenDecimals: 18,
|
||||||
|
DestAmount: &bigint.BigInt{Int: big.NewInt(1000000000000000000)},
|
||||||
|
DestTokenAddress: common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"),
|
||||||
|
DestTokenDecimals: 18,
|
||||||
|
RawPriceRoute: data,
|
||||||
|
}
|
||||||
|
|
||||||
|
receivedRoute, err := handlePriceRouteResponse(responseData)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, route, receivedRoute)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestForErrorOnFetchingPriceRoute(t *testing.T) {
|
||||||
|
data := []byte(`{
|
||||||
|
"error": "Invalid tokens"
|
||||||
|
}`)
|
||||||
|
|
||||||
|
_, err := handlePriceRouteResponse(data)
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
package paraswap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
const tokensURL = "https://apiv5.paraswap.io/tokens/%d" // nolint: gosec
|
||||||
|
|
||||||
|
type Token struct {
|
||||||
|
Symbol string `json:"symbol"`
|
||||||
|
Address string `json:"address"`
|
||||||
|
Decimals uint `json:"decimals"`
|
||||||
|
Img string `json:"img"`
|
||||||
|
Network int `json:"network"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TokensResponse struct {
|
||||||
|
Tokens []Token `json:"tokens"`
|
||||||
|
Error string `json:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientV5) FetchTokensList(ctx context.Context) ([]Token, error) {
|
||||||
|
url := fmt.Sprintf(tokensURL, c.chainID)
|
||||||
|
response, err := c.httpClient.doGetRequest(ctx, url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return handleTokensListResponse(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleTokensListResponse(response []byte) ([]Token, error) {
|
||||||
|
var tokensResponse TokensResponse
|
||||||
|
err := json.Unmarshal(response, &tokensResponse)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if tokensResponse.Error != "" {
|
||||||
|
return nil, errors.New(tokensResponse.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tokensResponse.Tokens, nil
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
package paraswap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUnmarshallTokensList(t *testing.T) {
|
||||||
|
|
||||||
|
tokens := []Token{
|
||||||
|
{
|
||||||
|
Symbol: "ETH",
|
||||||
|
Address: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
|
||||||
|
Decimals: 18,
|
||||||
|
Img: "https://img.paraswap.network/ETH.png",
|
||||||
|
Network: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Symbol: "USDT",
|
||||||
|
Address: "0xdac17f958d2ee523a2206206994597c13d831ec7",
|
||||||
|
Decimals: 6,
|
||||||
|
Img: "https://img.paraswap.network/USDT.png",
|
||||||
|
Network: 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
data := []byte(`{
|
||||||
|
"tokens": [
|
||||||
|
{
|
||||||
|
"symbol": "ETH",
|
||||||
|
"address": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
|
||||||
|
"decimals": 18,
|
||||||
|
"img": "https://img.paraswap.network/ETH.png",
|
||||||
|
"network": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"symbol": "USDT",
|
||||||
|
"address": "0xdac17f958d2ee523a2206206994597c13d831ec7",
|
||||||
|
"decimals": 6,
|
||||||
|
"img": "https://img.paraswap.network/USDT.png",
|
||||||
|
"network": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`)
|
||||||
|
|
||||||
|
receivedTokens, err := handleTokensListResponse(data)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tokens, receivedTokens)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestForErrorOnFetchingTokensList(t *testing.T) {
|
||||||
|
data := []byte(`{
|
||||||
|
"error": "Only chainId 1 is supported"
|
||||||
|
}`)
|
||||||
|
|
||||||
|
_, err := handleTokensListResponse(data)
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
|
@ -72,7 +72,7 @@ type ReceivedToken struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Token) IsNative() bool {
|
func (t *Token) IsNative() bool {
|
||||||
return t.Address == nativeChainAddress
|
return strings.EqualFold(t.Symbol, "ETH")
|
||||||
}
|
}
|
||||||
|
|
||||||
type List struct {
|
type List struct {
|
||||||
|
|
|
@ -347,6 +347,10 @@ func (tm *TransactionManager) sendTransactions(multiTransaction *MultiTransactio
|
||||||
tx.ERC1155TransferTx.MultiTransactionID = multiTransaction.ID
|
tx.ERC1155TransferTx.MultiTransactionID = multiTransaction.ID
|
||||||
tx.ERC1155TransferTx.Symbol = multiTransaction.FromAsset
|
tx.ERC1155TransferTx.Symbol = multiTransaction.FromAsset
|
||||||
}
|
}
|
||||||
|
if tx.SwapTx != nil {
|
||||||
|
tx.SwapTx.MultiTransactionID = multiTransaction.ID
|
||||||
|
tx.SwapTx.Symbol = multiTransaction.FromAsset
|
||||||
|
}
|
||||||
|
|
||||||
hash, err := bridges[tx.BridgeName].Send(tx, selectedAccount)
|
hash, err := bridges[tx.BridgeName].Send(tx, selectedAccount)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
Loading…
Reference in New Issue