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) {
|
||||
addr, exists := contractAddressByChainID[chainID]
|
||||
if !exists {
|
||||
return *new(common.Address), ErrorNotAvailableOnChainID
|
||||
return common.Address{}, ErrorNotAvailableOnChainID
|
||||
}
|
||||
return addr, nil
|
||||
}
|
||||
|
|
|
@ -426,6 +426,7 @@ func (api *API) GetSuggestedRoutes(
|
|||
addrTo common.Address,
|
||||
amountIn *hexutil.Big,
|
||||
tokenID string,
|
||||
toTokenID string,
|
||||
disabledFromChainIDs,
|
||||
disabledToChaindIDs,
|
||||
preferedChainIDs []uint64,
|
||||
|
@ -433,7 +434,7 @@ func (api *API) GetSuggestedRoutes(
|
|||
fromLockedAmount map[uint64]*hexutil.Big,
|
||||
) (*SuggestedRoutes, error) {
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,8 @@ import (
|
|||
"github.com/status-im/status-go/transactions"
|
||||
)
|
||||
|
||||
var ZeroAddress = common.Address{}
|
||||
|
||||
const IncreaseEstimatedGasFactor = 1.1
|
||||
|
||||
func getSigner(chainID uint64, from types.Address, verifiedAccount *account.SelectedExtKey) bind.SignerFn {
|
||||
|
@ -31,6 +33,7 @@ type TransactionBridge struct {
|
|||
CbridgeTx *CBridgeTxArgs
|
||||
ERC721TransferTx *ERC721TransferTxArgs
|
||||
ERC1155TransferTx *ERC1155TransferTxArgs
|
||||
SwapTx *SwapTxArgs
|
||||
}
|
||||
|
||||
func (t *TransactionBridge) Value() *big.Int {
|
||||
|
@ -99,9 +102,9 @@ func (t *TransactionBridge) Data() types.HexBytes {
|
|||
|
||||
type Bridge interface {
|
||||
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)
|
||||
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)
|
||||
Send(sendArgs *TransactionBridge, verifiedAccount *account.SelectedExtKey) (types.Hash, error)
|
||||
GetContractAddress(network *params.Network, token *token.Token) *common.Address
|
||||
|
|
|
@ -153,7 +153,7 @@ func (s *CBridge) getTransferConfig(isTest bool) (*cbridge.GetTransferConfigsRes
|
|||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
value := new(big.Int)
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@ func (s *ERC1155TransferBridge) Name() string {
|
|||
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
|
||||
}
|
||||
|
||||
|
@ -49,7 +49,7 @@ func (s *ERC1155TransferBridge) CalculateFees(from, to *params.Network, token *t
|
|||
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)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
|
|
|
@ -40,7 +40,7 @@ func (s *ERC721TransferBridge) Name() string {
|
|||
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
|
||||
}
|
||||
|
||||
|
@ -48,7 +48,7 @@ func (s *ERC721TransferBridge) CalculateFees(from, to *params.Network, token *to
|
|||
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)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
|
|
|
@ -108,7 +108,7 @@ func (h *HopBridge) Name() string {
|
|||
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 {
|
||||
return false, nil
|
||||
}
|
||||
|
@ -132,7 +132,7 @@ func (h *HopBridge) Can(from, to *params.Network, token *token.Token, balance *b
|
|||
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
|
||||
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"
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -40,7 +40,7 @@ func (s *TransferBridge) CalculateFees(from, to *params.Network, token *token.To
|
|||
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)
|
||||
var err error
|
||||
if token.Symbol == "ETH" {
|
||||
|
|
|
@ -26,6 +26,7 @@ import (
|
|||
"github.com/status-im/status-go/services/wallet/bridge"
|
||||
walletCommon "github.com/status-im/status-go/services/wallet/common"
|
||||
"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"
|
||||
)
|
||||
|
||||
|
@ -45,6 +46,7 @@ const (
|
|||
Bridge
|
||||
ERC721Transfer
|
||||
ERC1155Transfer
|
||||
Swap
|
||||
)
|
||||
|
||||
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 {
|
||||
return s == Transfer || s.IsCollectiblesTransfer()
|
||||
return s == Transfer || s == Swap || s.IsCollectiblesTransfer()
|
||||
}
|
||||
|
||||
func (s SendType) needL1Fee() bool {
|
||||
|
@ -112,6 +114,10 @@ func (s SendType) isAvailableBetween(from, to *params.Network) bool {
|
|||
return from.ChainID != to.ChainID
|
||||
}
|
||||
|
||||
if s == Swap {
|
||||
return from.ChainID == to.ChainID
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -136,11 +142,19 @@ func (s SendType) canUseBridge(b bridge.Bridge) 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() {
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -430,11 +444,13 @@ func NewRouter(s *Service) *Router {
|
|||
erc1155Transfer := bridge.NewERC1155TransferBridge(s.rpcClient, s.transactor)
|
||||
cbridge := bridge.NewCbridge(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[erc721Transfer.Name()] = erc721Transfer
|
||||
bridges[hop.Name()] = hop
|
||||
bridges[cbridge.Name()] = cbridge
|
||||
bridges[erc1155Transfer.Name()] = erc1155Transfer
|
||||
bridges[paraswap.Name()] = paraswap
|
||||
|
||||
return &Router{s, bridges, s.rpcClient}
|
||||
}
|
||||
|
@ -455,55 +471,54 @@ type Router struct {
|
|||
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) (
|
||||
bool, *big.Int, uint64, uint64, *common.Address, error) {
|
||||
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, error) {
|
||||
if sendType.IsCollectiblesTransfer() {
|
||||
return false, nil, 0, 0, nil, nil
|
||||
return false, nil, 0, 0, nil
|
||||
}
|
||||
|
||||
if token.IsNative() {
|
||||
return false, nil, 0, 0, nil, nil
|
||||
return false, nil, 0, 0, nil
|
||||
}
|
||||
contractMaker, err := contracts.NewContractMaker(r.rpcClient)
|
||||
if err != nil {
|
||||
return false, nil, 0, 0, nil, err
|
||||
}
|
||||
|
||||
bridgeAddress := bridge.GetContractAddress(network, token)
|
||||
if bridgeAddress == nil {
|
||||
return false, nil, 0, 0, nil, nil
|
||||
return false, nil, 0, 0, err
|
||||
}
|
||||
|
||||
contract, err := contractMaker.NewERC20(network.ChainID, token.Address)
|
||||
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{
|
||||
Context: ctx,
|
||||
}, account, *bridgeAddress)
|
||||
}, account, *approvalContractAddress)
|
||||
|
||||
if err != nil {
|
||||
return false, nil, 0, 0, nil, err
|
||||
return false, nil, 0, 0, err
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return false, nil, 0, 0, nil, err
|
||||
return false, nil, 0, 0, err
|
||||
}
|
||||
|
||||
erc20ABI, err := abi.JSON(strings.NewReader(ierc20.IERC20ABI))
|
||||
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 {
|
||||
return false, nil, 0, 0, nil, err
|
||||
return false, nil, 0, 0, err
|
||||
}
|
||||
|
||||
estimate, err := ethClient.EstimateGas(context.Background(), ethereum.CallMsg{
|
||||
|
@ -513,25 +528,25 @@ func (r *Router) requireApproval(ctx context.Context, sendType SendType, bridge
|
|||
Data: data,
|
||||
})
|
||||
if err != nil {
|
||||
return false, nil, 0, 0, nil, err
|
||||
return false, nil, 0, 0, err
|
||||
}
|
||||
|
||||
// fetching l1 fee
|
||||
var l1Fee uint64
|
||||
oracleContractAddress, err := gaspriceoracle.ContractAddress(network.ChainID)
|
||||
if err != nil {
|
||||
return false, nil, 0, 0, nil, err
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
oracleContract, err := gaspriceoracle.NewGaspriceoracleCaller(oracleContractAddress, ethClient)
|
||||
if err != nil {
|
||||
return false, nil, 0, 0, nil, err
|
||||
return false, nil, 0, 0, err
|
||||
}
|
||||
|
||||
callOpt := &bind.CallOpts{}
|
||||
|
||||
l1Fee, _ := oracleContract.GetL1Fee(callOpt, data)
|
||||
l1FeeResult, _ := oracleContract.GetL1Fee(callOpt, data)
|
||||
l1Fee = l1FeeResult.Uint64()
|
||||
}
|
||||
|
||||
return true, amountIn, estimate, l1Fee.Uint64(), bridgeAddress, nil
|
||||
return true, amountIn, estimate, l1Fee, nil
|
||||
}
|
||||
|
||||
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,
|
||||
amountIn *big.Int,
|
||||
tokenID string,
|
||||
toTokenID string,
|
||||
disabledFromChainIDs,
|
||||
disabledToChaindIDs,
|
||||
preferedChainIDs []uint64,
|
||||
|
@ -599,6 +615,7 @@ func (r *Router) suggestedRoutes(
|
|||
mu sync.Mutex
|
||||
candidates = make([]*Path, 0)
|
||||
)
|
||||
|
||||
for networkIdx := range networks {
|
||||
network := networks[networkIdx]
|
||||
if network.IsTest != areTestNetworksEnabled {
|
||||
|
@ -618,6 +635,11 @@ func (r *Router) suggestedRoutes(
|
|||
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)
|
||||
if nativeToken == nil {
|
||||
continue
|
||||
|
@ -683,7 +705,7 @@ func (r *Router) suggestedRoutes(
|
|||
continue
|
||||
}
|
||||
|
||||
can, err := bridge.Can(network, dest, token, maxAmountIn.ToInt())
|
||||
can, err := bridge.Can(network, dest, token, toToken, maxAmountIn.ToInt())
|
||||
if err != nil || !can {
|
||||
continue
|
||||
}
|
||||
|
@ -704,7 +726,7 @@ func (r *Router) suggestedRoutes(
|
|||
}
|
||||
gasLimit := uint64(0)
|
||||
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 {
|
||||
continue
|
||||
}
|
||||
|
@ -712,12 +734,13 @@ func (r *Router) suggestedRoutes(
|
|||
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 {
|
||||
continue
|
||||
}
|
||||
|
||||
var l1GasFeeWei uint64 = 0
|
||||
var l1GasFeeWei uint64
|
||||
if sendType.needL1Fee() {
|
||||
tx, err := bridge.BuildTx(network, addrFrom, addrTo, token, amountIn)
|
||||
if err != nil {
|
||||
|
@ -727,6 +750,7 @@ func (r *Router) suggestedRoutes(
|
|||
l1GasFeeWei, _ = r.s.feesManager.getL1Fee(ctx, network.ChainID, tx)
|
||||
l1GasFeeWei += l1ApprovalFee
|
||||
}
|
||||
|
||||
gasFees.L1GasFee = weiToGwei(big.NewInt(int64(l1GasFeeWei)))
|
||||
|
||||
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 {
|
||||
return t.Address == nativeChainAddress
|
||||
return strings.EqualFold(t.Symbol, "ETH")
|
||||
}
|
||||
|
||||
type List struct {
|
||||
|
|
|
@ -347,6 +347,10 @@ func (tm *TransactionManager) sendTransactions(multiTransaction *MultiTransactio
|
|||
tx.ERC1155TransferTx.MultiTransactionID = multiTransaction.ID
|
||||
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)
|
||||
if err != nil {
|
||||
|
|
Loading…
Reference in New Issue