151 lines
5.4 KiB
Go
151 lines
5.4 KiB
Go
package evm
|
|
|
|
import (
|
|
"context"
|
|
"math/big"
|
|
"time"
|
|
|
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/core/types"
|
|
"github.com/pkg/errors"
|
|
"github.com/rs/zerolog/log"
|
|
"github.com/shopspring/decimal"
|
|
)
|
|
|
|
type UniswapRouterContract interface {
|
|
SwapExactTokensForTokens(opts *bind.TransactOpts, amountIn *big.Int, amountOutMin *big.Int, path []common.Address, to common.Address, deadline *big.Int) (*types.Transaction, error)
|
|
GetAmountsOut(opts *bind.CallOpts, amountIn *big.Int, path []common.Address) ([]*big.Int, error)
|
|
AddLiquidity(opts *bind.TransactOpts, tokenA common.Address, tokenB common.Address, amountADesired *big.Int, amountBDesired *big.Int, amountAMin *big.Int, amountBMin *big.Int, to common.Address, deadline *big.Int) (*types.Transaction, error)
|
|
}
|
|
|
|
type UniswapRouter struct {
|
|
name string
|
|
client Client
|
|
contract UniswapRouterContract
|
|
|
|
deadline time.Duration
|
|
}
|
|
|
|
type UniswapRouterOption func(s *UniswapRouter) *UniswapRouter
|
|
|
|
func (s *UniswapRouter) WithOperationDeadline(deadline time.Duration) UniswapRouterOption {
|
|
return func(s *UniswapRouter) *UniswapRouter {
|
|
s.deadline = deadline
|
|
return s
|
|
}
|
|
}
|
|
|
|
func NewUniswapRouter(name string, client Client, contract UniswapRouterContract) *UniswapRouter {
|
|
return &UniswapRouter{
|
|
name: name,
|
|
client: client,
|
|
contract: contract,
|
|
deadline: 15 * time.Minute,
|
|
}
|
|
}
|
|
|
|
func (s *UniswapRouter) Name() string {
|
|
return s.name
|
|
}
|
|
|
|
func (s *UniswapRouter) SwapTokenToToken(ctx context.Context, path []common.Address, amount decimal.Decimal, slippage decimal.Decimal, opts ...ExecutionOption) (Transaction, error) {
|
|
if len(path) < 2 {
|
|
return nil, errors.Errorf("swap path must contain at least 2 addresses, currently %d", len(path))
|
|
}
|
|
|
|
tokenInput, err := s.client.TokenService().TokenByAddress(path[0])
|
|
if err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
|
|
tokenOutput, err := s.client.TokenService().TokenByAddress(path[len(path)-1])
|
|
if err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
|
|
log.Debug().Msgf("[SWAP] swaping %s %s to %s with path %v", amount, tokenInput.Symbol(), tokenOutput.Symbol(), path)
|
|
|
|
inputBalance, err := tokenInput.Balance()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "getting from token balance")
|
|
}
|
|
|
|
log.Debug().Msgf("[SWAP] balance of token to swap : %s", inputBalance)
|
|
if inputBalance.LessThan(amount) {
|
|
return nil, errors.Errorf("token balance %s is less than swap amount asked %s", inputBalance, amount)
|
|
}
|
|
|
|
expectedout, err := s.contract.GetAmountsOut(&bind.CallOpts{}, tokenInput.ValueToBigInt(amount), path)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "getting expected amount in target token")
|
|
}
|
|
decexp := tokenOutput.ValueFromBigInt(expectedout[len(expectedout)-1])
|
|
expectedwithslippage := decexp.Sub(decexp.Mul(slippage).Div(decimal.NewFromInt(100)))
|
|
log.Debug().Msgf("[SWAP] expected amounts : %v", expectedout)
|
|
log.Debug().Msgf("[SWAP] expected ratio : %s", decexp.Div(amount))
|
|
log.Debug().Msgf("[SWAP] expected out with slippage : %s (slippage %s%%)", expectedwithslippage, slippage)
|
|
log.Debug().Msgf("[SWAP] swaping...")
|
|
tx, err := s.client.Execute(ctx, func(ctx context.Context, options *TransactOpts) (*types.Transaction, error) {
|
|
return s.contract.SwapExactTokensForTokens(
|
|
options.TransactOpts,
|
|
tokenInput.ValueToBigInt(amount),
|
|
tokenOutput.ValueToBigInt(expectedwithslippage),
|
|
path,
|
|
s.client.PublicAddress(),
|
|
s.getDeadline())
|
|
}, opts...)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "swaping")
|
|
}
|
|
log.Debug().Msgf("[SWAP] swaping done")
|
|
|
|
return tx, nil
|
|
}
|
|
|
|
func (s *UniswapRouter) getDeadline() *big.Int {
|
|
return big.NewInt(time.Now().Add(s.deadline).Unix())
|
|
}
|
|
|
|
func (s *UniswapRouter) GetPriceInUSDC(token Token) (decimal.Decimal, error) {
|
|
return s.GetTokenQuoteInUSDC(token, decimal.NewFromInt(1))
|
|
}
|
|
|
|
func (s *UniswapRouter) GetTokenQuoteInUSDC(token Token, amount decimal.Decimal) (decimal.Decimal, error) {
|
|
usdc, err := s.client.TokenService().TokenBySymbol("USDC")
|
|
if err != nil {
|
|
return decimal.Zero, errors.Wrap(err, "getting usdc token")
|
|
}
|
|
return s.GetAmountRate(token, usdc, amount)
|
|
}
|
|
|
|
func (s *UniswapRouter) GetRate(token0 Token, token1 Token) (decimal.Decimal, error) {
|
|
return s.GetAmountRate(token0, token1, decimal.NewFromInt(1))
|
|
}
|
|
|
|
func (s *UniswapRouter) GetAmountRate(token0 Token, token1 Token, amount decimal.Decimal) (decimal.Decimal, error) {
|
|
if amount.LessThanOrEqual(decimal.Zero) {
|
|
return decimal.Zero, nil
|
|
}
|
|
expectedout, err := s.contract.GetAmountsOut(&bind.CallOpts{}, token0.ValueToBigInt(amount), []common.Address{token0.Address(), token1.Address()})
|
|
if err != nil {
|
|
return decimal.Zero, errors.Wrap(err, "getting expected amount in quote token")
|
|
}
|
|
return token1.ValueFromBigInt(expectedout[len(expectedout)-1]), nil
|
|
}
|
|
|
|
func (s *UniswapRouter) AddLiquidity(ctx context.Context, token1 Token, token2 Token, amounttoken1 decimal.Decimal, amounttoken2 decimal.Decimal, opts ...ExecutionOption) (Transaction, error) {
|
|
return s.client.Execute(ctx, func(ctx context.Context, options *TransactOpts) (*types.Transaction, error) {
|
|
return s.contract.AddLiquidity(
|
|
options.TransactOpts,
|
|
token1.Address(),
|
|
token2.Address(),
|
|
token1.ValueToBigInt(amounttoken1),
|
|
token2.ValueToBigInt(amounttoken2),
|
|
token1.ValueToBigInt(amounttoken1.Sub(amounttoken1.Mul(decimal.NewFromFloat(0.01)))),
|
|
token2.ValueToBigInt(amounttoken2.Sub(amounttoken2.Mul(decimal.NewFromFloat(0.01)))),
|
|
s.client.PublicAddress(),
|
|
s.getDeadline())
|
|
}, opts...)
|
|
}
|