├── utils ├── liquidity_math.go ├── full_math.go ├── calldata.go ├── encode.go ├── most_significant_bit.go ├── encode_test.go ├── tick_math_test.go ├── compute_pool_address_test.go ├── compute_pool_address.go ├── price_tick_conversions.go ├── swap_math.go ├── max_liquidity_for_amounts.go ├── price_tick_conversions_test.go ├── sqrtprice_math.go ├── tick_math.go └── max_liquidity_for_amounts_test.go ├── periphery ├── contracts │ ├── libraries │ │ ├── Path.sol │ │ │ └── Path.json │ │ ├── NFTSVG.sol │ │ │ └── NFTSVG.json │ │ ├── BytesLib.sol │ │ │ └── BytesLib.json │ │ ├── ChainId.sol │ │ │ └── ChainId.json │ │ ├── HexStrings.sol │ │ │ └── HexStrings.json │ │ ├── PoolAddress.sol │ │ │ └── PoolAddress.json │ │ ├── PositionKey.sol │ │ │ └── PositionKey.json │ │ ├── OracleLibrary.sol │ │ │ └── OracleLibrary.json │ │ ├── TransferHelper.sol │ │ │ └── TransferHelper.json │ │ ├── LiquidityAmounts.sol │ │ │ └── LiquidityAmounts.json │ │ ├── PoolTicksCounter.sol │ │ │ └── PoolTicksCounter.json │ │ ├── CallbackValidation.sol │ │ │ └── CallbackValidation.json │ │ └── TokenRatioSortOrder.sol │ │ │ └── TokenRatioSortOrder.json │ ├── interfaces │ │ ├── IMulticall.sol │ │ │ └── IMulticall.json │ │ ├── external │ │ │ ├── IERC1271.sol │ │ │ │ └── IERC1271.json │ │ │ ├── IERC20PermitAllowed.sol │ │ │ │ └── IERC20PermitAllowed.json │ │ │ └── IWETH9.sol │ │ │ │ └── IWETH9.json │ │ ├── IPeripheryImmutableState.sol │ │ │ └── IPeripheryImmutableState.json │ │ ├── INonfungibleTokenPositionDescriptor.sol │ │ │ └── INonfungibleTokenPositionDescriptor.json │ │ ├── IPoolInitializer.sol │ │ │ └── IPoolInitializer.json │ │ ├── ITickLens.sol │ │ │ └── ITickLens.json │ │ ├── IPeripheryPayments.sol │ │ │ └── IPeripheryPayments.json │ │ ├── IPeripheryPaymentsWithFee.sol │ │ │ └── IPeripheryPaymentsWithFee.json │ │ ├── IQuoter.sol │ │ │ └── IQuoter.json │ │ ├── ISelfPermit.sol │ │ │ └── ISelfPermit.json │ │ ├── IERC20Metadata.sol │ │ │ └── IERC20Metadata.json │ │ ├── IQuoterV2.sol │ │ │ └── IQuoterV2.json │ │ ├── ISwapRouter.sol │ │ │ └── ISwapRouter.json │ │ └── IV3Migrator.sol │ │ │ └── IV3Migrator.json │ └── lens │ │ ├── TickLens.sol │ │ └── TickLens.json │ │ └── UniswapInterfaceMulticall.sol │ │ └── UniswapInterfaceMulticall.json ├── multicall.go ├── multicall_test.go ├── payments.go ├── selfpermit.go ├── selfpermit_test.go ├── payments_test.go ├── const_test.go ├── quoter.go └── quoter_test.go ├── examples ├── helper │ ├── number.go │ ├── wallet.go │ ├── constants.go │ ├── tx.go │ └── pool.go ├── quoter │ └── main.go ├── README.md ├── swap │ └── main.go ├── contract │ ├── uniswapv3_quoter.abi │ └── uniswapv3_factory.abi └── liquidity │ └── main.go ├── entities ├── ticklistdataprovider.go ├── tickdataprovider.go ├── nearestusabletick.go ├── nearestusabletick_test.go ├── route.go ├── ticklist.go ├── ticklist_test.go └── route_test.go ├── LICENSE ├── go.mod ├── constants └── constants.go ├── .github └── workflows │ └── test.yml ├── README.md └── go.sum /utils/liquidity_math.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "math/big" 5 | 6 | "github.com/daoleno/uniswapv3-sdk/constants" 7 | ) 8 | 9 | func AddDelta(x, y *big.Int) *big.Int { 10 | if y.Cmp(constants.Zero) < 0 { 11 | return new(big.Int).Sub(x, new(big.Int).Mul(y, constants.NegativeOne)) 12 | } else { 13 | return new(big.Int).Add(x, y) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /utils/full_math.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "math/big" 5 | 6 | "github.com/daoleno/uniswapv3-sdk/constants" 7 | ) 8 | 9 | func MulDivRoundingUp(a, b, denominator *big.Int) *big.Int { 10 | product := new(big.Int).Mul(a, b) 11 | result := new(big.Int).Div(product, denominator) 12 | if new(big.Int).Rem(product, denominator).Cmp(big.NewInt(0)) != 0 { 13 | result.Add(result, constants.One) 14 | } 15 | return result 16 | } 17 | -------------------------------------------------------------------------------- /periphery/contracts/libraries/Path.sol/Path.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "Path", 4 | "sourceName": "contracts/libraries/Path.sol", 5 | "abi": [], 6 | "bytecode": "0x602d6023600b82828239805160001a607314601657fe5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea164736f6c6343000706000a", 7 | "deployedBytecode": "0x73000000000000000000000000000000000000000030146080604052600080fdfea164736f6c6343000706000a", 8 | "linkReferences": {}, 9 | "deployedLinkReferences": {} 10 | } 11 | -------------------------------------------------------------------------------- /periphery/contracts/libraries/NFTSVG.sol/NFTSVG.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "NFTSVG", 4 | "sourceName": "contracts/libraries/NFTSVG.sol", 5 | "abi": [], 6 | "bytecode": "0x602d6023600b82828239805160001a607314601657fe5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea164736f6c6343000706000a", 7 | "deployedBytecode": "0x73000000000000000000000000000000000000000030146080604052600080fdfea164736f6c6343000706000a", 8 | "linkReferences": {}, 9 | "deployedLinkReferences": {} 10 | } 11 | -------------------------------------------------------------------------------- /periphery/contracts/libraries/BytesLib.sol/BytesLib.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "BytesLib", 4 | "sourceName": "contracts/libraries/BytesLib.sol", 5 | "abi": [], 6 | "bytecode": "0x602d6023600b82828239805160001a607314601657fe5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea164736f6c6343000706000a", 7 | "deployedBytecode": "0x73000000000000000000000000000000000000000030146080604052600080fdfea164736f6c6343000706000a", 8 | "linkReferences": {}, 9 | "deployedLinkReferences": {} 10 | } 11 | -------------------------------------------------------------------------------- /periphery/contracts/libraries/ChainId.sol/ChainId.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "ChainId", 4 | "sourceName": "contracts/libraries/ChainId.sol", 5 | "abi": [], 6 | "bytecode": "0x602d6023600b82828239805160001a607314601657fe5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea164736f6c6343000706000a", 7 | "deployedBytecode": "0x73000000000000000000000000000000000000000030146080604052600080fdfea164736f6c6343000706000a", 8 | "linkReferences": {}, 9 | "deployedLinkReferences": {} 10 | } 11 | -------------------------------------------------------------------------------- /periphery/contracts/libraries/HexStrings.sol/HexStrings.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "HexStrings", 4 | "sourceName": "contracts/libraries/HexStrings.sol", 5 | "abi": [], 6 | "bytecode": "0x602d6023600b82828239805160001a607314601657fe5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea164736f6c6343000706000a", 7 | "deployedBytecode": "0x73000000000000000000000000000000000000000030146080604052600080fdfea164736f6c6343000706000a", 8 | "linkReferences": {}, 9 | "deployedLinkReferences": {} 10 | } 11 | -------------------------------------------------------------------------------- /periphery/contracts/libraries/PoolAddress.sol/PoolAddress.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "PoolAddress", 4 | "sourceName": "contracts/libraries/PoolAddress.sol", 5 | "abi": [], 6 | "bytecode": "0x602d6023600b82828239805160001a607314601657fe5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea164736f6c6343000706000a", 7 | "deployedBytecode": "0x73000000000000000000000000000000000000000030146080604052600080fdfea164736f6c6343000706000a", 8 | "linkReferences": {}, 9 | "deployedLinkReferences": {} 10 | } 11 | -------------------------------------------------------------------------------- /periphery/contracts/libraries/PositionKey.sol/PositionKey.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "PositionKey", 4 | "sourceName": "contracts/libraries/PositionKey.sol", 5 | "abi": [], 6 | "bytecode": "0x602d6023600b82828239805160001a607314601657fe5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea164736f6c6343000706000a", 7 | "deployedBytecode": "0x73000000000000000000000000000000000000000030146080604052600080fdfea164736f6c6343000706000a", 8 | "linkReferences": {}, 9 | "deployedLinkReferences": {} 10 | } 11 | -------------------------------------------------------------------------------- /periphery/contracts/libraries/OracleLibrary.sol/OracleLibrary.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "OracleLibrary", 4 | "sourceName": "contracts/libraries/OracleLibrary.sol", 5 | "abi": [], 6 | "bytecode": "0x602d6023600b82828239805160001a607314601657fe5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea164736f6c6343000706000a", 7 | "deployedBytecode": "0x73000000000000000000000000000000000000000030146080604052600080fdfea164736f6c6343000706000a", 8 | "linkReferences": {}, 9 | "deployedLinkReferences": {} 10 | } 11 | -------------------------------------------------------------------------------- /periphery/contracts/libraries/TransferHelper.sol/TransferHelper.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "TransferHelper", 4 | "sourceName": "contracts/libraries/TransferHelper.sol", 5 | "abi": [], 6 | "bytecode": "0x602d6023600b82828239805160001a607314601657fe5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea164736f6c6343000706000a", 7 | "deployedBytecode": "0x73000000000000000000000000000000000000000030146080604052600080fdfea164736f6c6343000706000a", 8 | "linkReferences": {}, 9 | "deployedLinkReferences": {} 10 | } 11 | -------------------------------------------------------------------------------- /utils/calldata.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "math/big" 5 | ) 6 | 7 | type MethodParameters struct { 8 | Calldata []byte // The hex encoded calldata to perform the given operation 9 | Value *big.Int // The amount of ether (wei) to send in hex 10 | } 11 | 12 | /** 13 | * Converts a big int to a hex string 14 | * @param bigintIsh 15 | * @returns The hex encoded calldata 16 | */ 17 | func ToHex(i *big.Int) string { 18 | if i == nil { 19 | return "0x00" 20 | } 21 | 22 | hex := i.Text(16) 23 | if len(hex)%2 != 0 { 24 | hex = "0" + hex 25 | } 26 | return "0x" + hex 27 | } 28 | -------------------------------------------------------------------------------- /periphery/contracts/libraries/LiquidityAmounts.sol/LiquidityAmounts.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "LiquidityAmounts", 4 | "sourceName": "contracts/libraries/LiquidityAmounts.sol", 5 | "abi": [], 6 | "bytecode": "0x602d6023600b82828239805160001a607314601657fe5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea164736f6c6343000706000a", 7 | "deployedBytecode": "0x73000000000000000000000000000000000000000030146080604052600080fdfea164736f6c6343000706000a", 8 | "linkReferences": {}, 9 | "deployedLinkReferences": {} 10 | } 11 | -------------------------------------------------------------------------------- /periphery/contracts/libraries/PoolTicksCounter.sol/PoolTicksCounter.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "PoolTicksCounter", 4 | "sourceName": "contracts/libraries/PoolTicksCounter.sol", 5 | "abi": [], 6 | "bytecode": "0x602d6023600b82828239805160001a607314601657fe5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea164736f6c6343000706000a", 7 | "deployedBytecode": "0x73000000000000000000000000000000000000000030146080604052600080fdfea164736f6c6343000706000a", 8 | "linkReferences": {}, 9 | "deployedLinkReferences": {} 10 | } 11 | -------------------------------------------------------------------------------- /utils/encode.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "math/big" 4 | 5 | /** 6 | * Returns the sqrt ratio as a Q64.96 corresponding to a given ratio of amount1 and amount0 7 | * @param amount1 The numerator amount i.e., the amount of token1 8 | * @param amount0 The denominator amount i.e., the amount of token0 9 | * @returns The sqrt ratio 10 | */ 11 | func EncodeSqrtRatioX96(amount1 *big.Int, amount0 *big.Int) *big.Int { 12 | numerator := new(big.Int).Lsh(amount1, 192) 13 | denominator := amount0 14 | ratioX192 := new(big.Int).Div(numerator, denominator) 15 | return new(big.Int).Sqrt(ratioX192) 16 | } 17 | -------------------------------------------------------------------------------- /periphery/contracts/libraries/CallbackValidation.sol/CallbackValidation.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "CallbackValidation", 4 | "sourceName": "contracts/libraries/CallbackValidation.sol", 5 | "abi": [], 6 | "bytecode": "0x602d6023600b82828239805160001a607314601657fe5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea164736f6c6343000706000a", 7 | "deployedBytecode": "0x73000000000000000000000000000000000000000030146080604052600080fdfea164736f6c6343000706000a", 8 | "linkReferences": {}, 9 | "deployedLinkReferences": {} 10 | } 11 | -------------------------------------------------------------------------------- /periphery/contracts/libraries/TokenRatioSortOrder.sol/TokenRatioSortOrder.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "TokenRatioSortOrder", 4 | "sourceName": "contracts/libraries/TokenRatioSortOrder.sol", 5 | "abi": [], 6 | "bytecode": "0x602d6023600b82828239805160001a607314601657fe5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea164736f6c6343000706000a", 7 | "deployedBytecode": "0x73000000000000000000000000000000000000000030146080604052600080fdfea164736f6c6343000706000a", 8 | "linkReferences": {}, 9 | "deployedLinkReferences": {} 10 | } 11 | -------------------------------------------------------------------------------- /examples/helper/number.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "math" 5 | "math/big" 6 | ) 7 | 8 | func IntWithDecimal(v uint64, decimal int) *big.Int { 9 | pow := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(decimal)), nil) 10 | return new(big.Int).Mul(big.NewInt(int64(v)), pow) 11 | } 12 | 13 | func IntDivDecimal(v *big.Int, decimal int) *big.Int { 14 | pow := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(decimal)), nil) 15 | return new(big.Int).Div(v, pow) 16 | } 17 | 18 | func FloatStringToBigInt(amount string, decimals int) *big.Int { 19 | fAmount, _ := new(big.Float).SetString(amount) 20 | fi, _ := new(big.Float).Mul(fAmount, big.NewFloat(math.Pow10(decimals))).Int(nil) 21 | return fi 22 | } 23 | -------------------------------------------------------------------------------- /examples/helper/wallet.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "crypto/ecdsa" 5 | 6 | "github.com/ethereum/go-ethereum/common" 7 | "github.com/ethereum/go-ethereum/crypto" 8 | ) 9 | 10 | type Wallet struct { 11 | PrivateKey *ecdsa.PrivateKey 12 | PublicKey common.Address 13 | } 14 | 15 | func (w Wallet) PubkeyStr() string { 16 | return w.PublicKey.String() 17 | } 18 | 19 | func InitWallet(privateHexKeys string) *Wallet { 20 | if privateHexKeys == "" { 21 | return nil 22 | } 23 | privateKey, err := crypto.HexToECDSA(privateHexKeys) 24 | if err != nil { 25 | return nil 26 | } 27 | 28 | return &Wallet{ 29 | PrivateKey: privateKey, 30 | PublicKey: crypto.PubkeyToAddress(privateKey.PublicKey), 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /entities/ticklistdataprovider.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | // A data provider for ticks that is backed by an in-memory array of ticks. 4 | type TickListDataProvider struct { 5 | ticks []Tick 6 | } 7 | 8 | func NewTickListDataProvider(ticks []Tick, tickSpacing int) (*TickListDataProvider, error) { 9 | if err := ValidateList(ticks, tickSpacing); err != nil { 10 | return nil, err 11 | } 12 | return &TickListDataProvider{ticks: ticks}, nil 13 | } 14 | 15 | func (p *TickListDataProvider) GetTick(tick int) Tick { 16 | return GetTick(p.ticks, tick) 17 | } 18 | 19 | func (p *TickListDataProvider) NextInitializedTickWithinOneWord(tick int, lte bool, tickSpacing int) (int, bool) { 20 | return NextInitializedTickWithinOneWord(p.ticks, tick, lte, tickSpacing) 21 | } 22 | -------------------------------------------------------------------------------- /entities/tickdataprovider.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | import "math/big" 4 | 5 | type Tick struct { 6 | Index int 7 | LiquidityGross *big.Int 8 | LiquidityNet *big.Int 9 | } 10 | 11 | // Provides information about ticks 12 | type TickDataProvider interface { 13 | /** 14 | * Return information corresponding to a specific tick 15 | * @param tick the tick to load 16 | */ 17 | GetTick(tick int) Tick 18 | 19 | /** 20 | * Return the next tick that is initialized within a single word 21 | * @param tick The current tick 22 | * @param lte Whether the next tick should be lte the current tick 23 | * @param tickSpacing The tick spacing of the pool 24 | */ 25 | NextInitializedTickWithinOneWord(tick int, lte bool, tickSpacing int) (int, bool) 26 | } 27 | -------------------------------------------------------------------------------- /utils/most_significant_bit.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "errors" 5 | "math/big" 6 | 7 | "github.com/daoleno/uniswap-sdk-core/entities" 8 | "github.com/daoleno/uniswapv3-sdk/constants" 9 | ) 10 | 11 | var ErrInvalidInput = errors.New("invalid input") 12 | 13 | func MostSignificantBit(x *big.Int) (int64, error) { 14 | if x.Cmp(constants.Zero) <= 0 { 15 | return 0, ErrInvalidInput 16 | } 17 | if x.Cmp(entities.MaxUint256) > 0 { 18 | return 0, ErrInvalidInput 19 | } 20 | var msb int64 21 | for _, power := range []int64{128, 64, 32, 16, 8, 4, 2, 1} { 22 | min := new(big.Int).Exp(big.NewInt(2), big.NewInt(int64(power)), nil) 23 | if x.Cmp(min) >= 0 { 24 | x = new(big.Int).Rsh(x, uint(power)) 25 | msb += power 26 | } 27 | } 28 | return msb, nil 29 | } 30 | -------------------------------------------------------------------------------- /periphery/contracts/interfaces/IMulticall.sol/IMulticall.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "IMulticall", 4 | "sourceName": "contracts/interfaces/IMulticall.sol", 5 | "abi": [ 6 | { 7 | "inputs": [ 8 | { 9 | "internalType": "bytes[]", 10 | "name": "data", 11 | "type": "bytes[]" 12 | } 13 | ], 14 | "name": "multicall", 15 | "outputs": [ 16 | { 17 | "internalType": "bytes[]", 18 | "name": "results", 19 | "type": "bytes[]" 20 | } 21 | ], 22 | "stateMutability": "payable", 23 | "type": "function" 24 | } 25 | ], 26 | "bytecode": "0x", 27 | "deployedBytecode": "0x", 28 | "linkReferences": {}, 29 | "deployedLinkReferences": {} 30 | } 31 | -------------------------------------------------------------------------------- /periphery/multicall.go: -------------------------------------------------------------------------------- 1 | package periphery 2 | 3 | import ( 4 | _ "embed" 5 | "encoding/json" 6 | 7 | "github.com/ethereum/go-ethereum/accounts/abi" 8 | ) 9 | 10 | //go:embed contracts/interfaces/IMulticall.sol/IMulticall.json 11 | var multicallABI []byte 12 | 13 | type WrappedABI struct { 14 | ABI abi.ABI `json:"abi"` 15 | } 16 | 17 | func EncodeMulticall(calldatas [][]byte) ([]byte, error) { 18 | if len(calldatas) == 1 { 19 | return calldatas[0], nil 20 | } 21 | abi := GetABI(multicallABI) 22 | b, err := abi.Pack("multicall", calldatas) 23 | if err != nil { 24 | return nil, err 25 | } 26 | 27 | return b, nil 28 | } 29 | 30 | func GetABI(abi []byte) abi.ABI { 31 | var wabi WrappedABI 32 | err := json.Unmarshal(abi, &wabi) 33 | if err != nil { 34 | panic(err) 35 | } 36 | return wabi.ABI 37 | } 38 | -------------------------------------------------------------------------------- /periphery/contracts/interfaces/external/IERC1271.sol/IERC1271.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "IERC1271", 4 | "sourceName": "contracts/interfaces/external/IERC1271.sol", 5 | "abi": [ 6 | { 7 | "inputs": [ 8 | { 9 | "internalType": "bytes32", 10 | "name": "hash", 11 | "type": "bytes32" 12 | }, 13 | { 14 | "internalType": "bytes", 15 | "name": "signature", 16 | "type": "bytes" 17 | } 18 | ], 19 | "name": "isValidSignature", 20 | "outputs": [ 21 | { 22 | "internalType": "bytes4", 23 | "name": "magicValue", 24 | "type": "bytes4" 25 | } 26 | ], 27 | "stateMutability": "view", 28 | "type": "function" 29 | } 30 | ], 31 | "bytecode": "0x", 32 | "deployedBytecode": "0x", 33 | "linkReferences": {}, 34 | "deployedLinkReferences": {} 35 | } 36 | -------------------------------------------------------------------------------- /periphery/contracts/interfaces/IPeripheryImmutableState.sol/IPeripheryImmutableState.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "IPeripheryImmutableState", 4 | "sourceName": "contracts/interfaces/IPeripheryImmutableState.sol", 5 | "abi": [ 6 | { 7 | "inputs": [], 8 | "name": "WETH9", 9 | "outputs": [ 10 | { 11 | "internalType": "address", 12 | "name": "", 13 | "type": "address" 14 | } 15 | ], 16 | "stateMutability": "view", 17 | "type": "function" 18 | }, 19 | { 20 | "inputs": [], 21 | "name": "factory", 22 | "outputs": [ 23 | { 24 | "internalType": "address", 25 | "name": "", 26 | "type": "address" 27 | } 28 | ], 29 | "stateMutability": "view", 30 | "type": "function" 31 | } 32 | ], 33 | "bytecode": "0x", 34 | "deployedBytecode": "0x", 35 | "linkReferences": {}, 36 | "deployedLinkReferences": {} 37 | } 38 | -------------------------------------------------------------------------------- /utils/encode_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "math/big" 5 | "testing" 6 | 7 | "github.com/daoleno/uniswapv3-sdk/constants" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestEncodeSqrtRatioX96(t *testing.T) { 12 | assert.Equal(t, EncodeSqrtRatioX96(big.NewInt(1), big.NewInt(1)), constants.Q96, "1/1") 13 | 14 | r0, _ := new(big.Int).SetString("792281625142643375935439503360", 10) 15 | assert.Equal(t, EncodeSqrtRatioX96(big.NewInt(100), big.NewInt(1)), r0, 10, "100/1") 16 | 17 | r1, _ := new(big.Int).SetString("7922816251426433759354395033", 10) 18 | assert.Equal(t, EncodeSqrtRatioX96(big.NewInt(1), big.NewInt(100)), r1, 10, "1/100") 19 | 20 | r2, _ := new(big.Int).SetString("45742400955009932534161870629", 10) 21 | assert.Equal(t, EncodeSqrtRatioX96(big.NewInt(111), big.NewInt(333)), r2, 10, "111/333") 22 | 23 | r3, _ := new(big.Int).SetString("137227202865029797602485611888", 10) 24 | assert.Equal(t, EncodeSqrtRatioX96(big.NewInt(333), big.NewInt(111)), r3, 10, "333/111") 25 | } 26 | -------------------------------------------------------------------------------- /periphery/contracts/interfaces/INonfungibleTokenPositionDescriptor.sol/INonfungibleTokenPositionDescriptor.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "INonfungibleTokenPositionDescriptor", 4 | "sourceName": "contracts/interfaces/INonfungibleTokenPositionDescriptor.sol", 5 | "abi": [ 6 | { 7 | "inputs": [ 8 | { 9 | "internalType": "contract INonfungiblePositionManager", 10 | "name": "positionManager", 11 | "type": "address" 12 | }, 13 | { 14 | "internalType": "uint256", 15 | "name": "tokenId", 16 | "type": "uint256" 17 | } 18 | ], 19 | "name": "tokenURI", 20 | "outputs": [ 21 | { 22 | "internalType": "string", 23 | "name": "", 24 | "type": "string" 25 | } 26 | ], 27 | "stateMutability": "view", 28 | "type": "function" 29 | } 30 | ], 31 | "bytecode": "0x", 32 | "deployedBytecode": "0x", 33 | "linkReferences": {}, 34 | "deployedLinkReferences": {} 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 daoleno 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/quoter/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math/big" 6 | 7 | "log" 8 | 9 | "github.com/daoleno/uniswapv3-sdk/examples/contract" 10 | "github.com/daoleno/uniswapv3-sdk/examples/helper" 11 | "github.com/ethereum/go-ethereum/common" 12 | "github.com/ethereum/go-ethereum/ethclient" 13 | ) 14 | 15 | func main() { 16 | log.SetFlags(log.Lshortfile | log.LstdFlags) 17 | client, err := ethclient.Dial(helper.PolygonRPC) 18 | if err != nil { 19 | panic(err) 20 | } 21 | quoterContract, err := contract.NewUniswapv3Quoter(common.HexToAddress(helper.ContractV3Quoter), client) 22 | if err != nil { 23 | panic(err) 24 | } 25 | 26 | token0 := common.HexToAddress(helper.WMaticAddr) 27 | token1 := common.HexToAddress(helper.AmpAddr) 28 | fee := big.NewInt(3000) 29 | amountIn := helper.FloatStringToBigInt("1.00", 18) 30 | sqrtPriceLimitX96 := big.NewInt(0) 31 | 32 | var out []interface{} 33 | rawCaller := &contract.Uniswapv3QuoterRaw{Contract: quoterContract} 34 | err = rawCaller.Call(nil, &out, "quoteExactInputSingle", token0, token1, 35 | fee, amountIn, sqrtPriceLimitX96) 36 | if err != nil { 37 | log.Fatal(err) 38 | } 39 | 40 | fmt.Println("amountOut: ", out[0].(*big.Int).String()) 41 | } 42 | -------------------------------------------------------------------------------- /periphery/contracts/interfaces/IPoolInitializer.sol/IPoolInitializer.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "IPoolInitializer", 4 | "sourceName": "contracts/interfaces/IPoolInitializer.sol", 5 | "abi": [ 6 | { 7 | "inputs": [ 8 | { 9 | "internalType": "address", 10 | "name": "token0", 11 | "type": "address" 12 | }, 13 | { 14 | "internalType": "address", 15 | "name": "token1", 16 | "type": "address" 17 | }, 18 | { 19 | "internalType": "uint24", 20 | "name": "fee", 21 | "type": "uint24" 22 | }, 23 | { 24 | "internalType": "uint160", 25 | "name": "sqrtPriceX96", 26 | "type": "uint160" 27 | } 28 | ], 29 | "name": "createAndInitializePoolIfNecessary", 30 | "outputs": [ 31 | { 32 | "internalType": "address", 33 | "name": "pool", 34 | "type": "address" 35 | } 36 | ], 37 | "stateMutability": "payable", 38 | "type": "function" 39 | } 40 | ], 41 | "bytecode": "0x", 42 | "deployedBytecode": "0x", 43 | "linkReferences": {}, 44 | "deployedLinkReferences": {} 45 | } 46 | -------------------------------------------------------------------------------- /utils/tick_math_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "math/big" 5 | "testing" 6 | 7 | "github.com/daoleno/uniswapv3-sdk/constants" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestGetSqrtRatioAtTick(t *testing.T) { 12 | _, err := GetSqrtRatioAtTick(MinTick - 1) 13 | assert.ErrorIs(t, err, ErrInvalidTick, "tick tool small") 14 | 15 | _, err = GetSqrtRatioAtTick(MaxTick + 1) 16 | assert.ErrorIs(t, err, ErrInvalidTick, "tick tool large") 17 | 18 | rmax, _ := GetSqrtRatioAtTick(MinTick) 19 | assert.Equal(t, rmax, MinSqrtRatio, "returns the correct value for min tick") 20 | 21 | r0, _ := GetSqrtRatioAtTick(0) 22 | assert.Equal(t, r0, new(big.Int).Lsh(constants.One, 96), "returns the correct value for tick 0") 23 | 24 | rmin, _ := GetSqrtRatioAtTick(MaxTick) 25 | assert.Equal(t, rmin, MaxSqrtRatio, "returns the correct value for max tick") 26 | } 27 | 28 | func TestGetTickAtSqrtRatio(t *testing.T) { 29 | tmin, _ := GetTickAtSqrtRatio(MinSqrtRatio) 30 | assert.Equal(t, tmin, MinTick, "returns the correct value for sqrt ratio at min tick") 31 | 32 | tmax, _ := GetTickAtSqrtRatio(new(big.Int).Sub(MaxSqrtRatio, constants.One)) 33 | assert.Equal(t, tmax, MaxTick-1, "returns the correct value for sqrt ratio at max tick") 34 | } 35 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | [quoter](./quoter/main.go) - get the swapped amount from chain. 3 | [swap](./swap/main.go) - swap two tokens on chain. 4 | [liquidity](liquidity/main.go) - shows how to mint/add/remove/burn a liquidity position. 5 | 6 | ## Usage 7 | If you want to see the code running in real environment, set you private key to environment variable. The variable `MY_PRIVATE_KEY` will be get by each `main` function. 8 | ```bash 9 | MY_PRIVATE_KEY="" 10 | ``` 11 | 12 | Replace `helper.TryTx` to `helper.SendTx` in each example case. 13 | ```go 14 | //try go send a transaction, it try to estimate gas price. 15 | tx, err := helper.TryTX(client, common.HexToAddress(helper.ContractV3SwapRouterV1), 16 | swapValue, params.Calldata, wallet) 17 | 18 | //send a transaction to chain, it will cost your money. 19 | tx, err := helper.SendTX(client, common.HexToAddress(helper.ContractV3SwapRouterV1), 20 | swapValue, params.Calldata, wallet) 21 | ``` 22 | If you just want to check the paramerters are passed correctly, we recommend you use `TryTx`. 23 | 24 | We use Polygon to test our code(it so cheap), you can set for your own. 25 | ``` 26 | client, err := ethclient.Dial(helper.PolygonRPC) 27 | ``` 28 | 29 | ## Run 30 | ``` 31 | go run examples/quoter/*.go 32 | ``` -------------------------------------------------------------------------------- /periphery/multicall_test.go: -------------------------------------------------------------------------------- 1 | package periphery 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/ethereum/go-ethereum/common/hexutil" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestEncodeMulticall(t *testing.T) { 11 | // works for string 12 | b, err := EncodeMulticall([][]byte{hexutil.MustDecode("0x01")}) 13 | assert.NoError(t, err) 14 | assert.Equal(t, "0x01", hexutil.Encode(b)) 15 | 16 | // works for string array with length > 1 17 | b, err = EncodeMulticall([][]byte{ 18 | hexutil.MustDecode("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), 19 | hexutil.MustDecode("0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"), 20 | }) 21 | assert.NoError(t, err) 22 | assert.Equal(t, 23 | "0xac9650d800000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000020aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000000000000000000000000000000000000000000000000000000000020bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", 24 | hexutil.Encode(b)) 25 | } 26 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/daoleno/uniswapv3-sdk 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/daoleno/uniswap-sdk-core v0.1.5 7 | github.com/ethereum/go-ethereum v1.10.20 8 | github.com/shopspring/decimal v1.3.1 9 | github.com/stretchr/testify v1.8.0 10 | ) 11 | 12 | require ( 13 | github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect 14 | github.com/davecgh/go-spew v1.1.1 // indirect 15 | github.com/deckarep/golang-set v1.8.0 // indirect 16 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect 17 | github.com/go-ole/go-ole v1.2.6 // indirect 18 | github.com/go-stack/stack v1.8.1 // indirect 19 | github.com/google/uuid v1.3.0 // indirect 20 | github.com/gorilla/websocket v1.5.0 // indirect 21 | github.com/pmezard/go-difflib v1.0.0 // indirect 22 | github.com/rjeczalik/notify v0.9.2 // indirect 23 | github.com/shirou/gopsutil v3.21.11+incompatible // indirect 24 | github.com/tklauser/go-sysconf v0.3.10 // indirect 25 | github.com/tklauser/numcpus v0.5.0 // indirect 26 | github.com/yusufpapurcu/wmi v1.2.2 // indirect 27 | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect 28 | golang.org/x/sys v0.0.0-20220702020025-31831981b65f // indirect 29 | gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect 30 | gopkg.in/yaml.v3 v3.0.1 // indirect 31 | ) 32 | -------------------------------------------------------------------------------- /examples/helper/constants.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | coreEntities "github.com/daoleno/uniswap-sdk-core/entities" 5 | "github.com/ethereum/go-ethereum/common" 6 | ) 7 | 8 | const ( 9 | PolygonRPC = "https://polygon-rpc.com/" 10 | 11 | PolygonChainID = 137 12 | WMaticAddr = "0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270" 13 | WETHAddr = "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" 14 | UsdcAddr = "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174" 15 | AmpAddr = "0x0621d647cecbFb64b79E44302c1933cB4f27054d" 16 | ) 17 | 18 | const ( 19 | ContractV3Factory = "0x1F98431c8aD98523631AE4a59f267346ea31F984" 20 | ContractV3SwapRouterV1 = "0xE592427A0AEce92De3Edee1F18E0157C05861564" 21 | ContractV3SwapRouterV2 = "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45" 22 | ContractV3NFTPositionManager = "0xC36442b4a4522E871399CD717aBDD847Ab11FE88" 23 | ContractV3Quoter = "0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6" 24 | ) 25 | 26 | var ( 27 | WMATIC = coreEntities.NewToken(PolygonChainID, common.HexToAddress(WMaticAddr), 18, "Matic", "Matic Network(PolyGon)") 28 | AMP = coreEntities.NewToken(PolygonChainID, common.HexToAddress(AmpAddr), 18, "AMP", "Amp") 29 | USDC = coreEntities.NewToken(PolygonChainID, common.HexToAddress(UsdcAddr), 6, "USDC", "USD Coin") 30 | ) 31 | -------------------------------------------------------------------------------- /constants/constants.go: -------------------------------------------------------------------------------- 1 | package constants 2 | 3 | import ( 4 | "math/big" 5 | 6 | "github.com/daoleno/uniswap-sdk-core/entities" 7 | "github.com/ethereum/go-ethereum/common" 8 | ) 9 | 10 | const PoolInitCodeHash = "0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54" 11 | 12 | var ( 13 | FactoryAddress = common.HexToAddress("0x1F98431c8aD98523631AE4a59f267346ea31F984") 14 | AddressZero = common.HexToAddress("0x0000000000000000000000000000000000000000") 15 | ) 16 | 17 | // The default factory enabled fee amounts, denominated in hundredths of bips. 18 | type FeeAmount uint64 19 | 20 | const ( 21 | FeeLowest FeeAmount = 100 22 | FeeLow FeeAmount = 500 23 | FeeMedium FeeAmount = 3000 24 | FeeHigh FeeAmount = 10000 25 | 26 | FeeMax FeeAmount = 1000000 27 | ) 28 | 29 | // The default factory tick spacings by fee amount. 30 | var TickSpacings = map[FeeAmount]int{ 31 | FeeLowest: 1, 32 | FeeLow: 10, 33 | FeeMedium: 60, 34 | FeeHigh: 200, 35 | } 36 | 37 | var ( 38 | NegativeOne = big.NewInt(-1) 39 | Zero = big.NewInt(0) 40 | One = big.NewInt(1) 41 | 42 | // used in liquidity amount math 43 | Q96 = new(big.Int).Exp(big.NewInt(2), big.NewInt(96), nil) 44 | Q192 = new(big.Int).Exp(Q96, big.NewInt(2), nil) 45 | 46 | PercentZero = entities.NewFraction(big.NewInt(0), big.NewInt(1)) 47 | ) 48 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | name: Test 3 | jobs: 4 | test: 5 | strategy: 6 | matrix: 7 | go-version: [1.18.x] 8 | os: [ubuntu-latest, macos-latest, windows-latest] 9 | runs-on: ${{ matrix.os }} 10 | steps: 11 | - name: Install Go 12 | uses: actions/setup-go@v2 13 | with: 14 | go-version: ${{ matrix.go-version }} 15 | - name: Checkout code 16 | uses: actions/checkout@v2 17 | - name: Test 18 | run: go test ./... 19 | 20 | test-cache: 21 | runs-on: ubuntu-latest 22 | steps: 23 | - name: Install Go 24 | uses: actions/setup-go@v2 25 | with: 26 | go-version: 1.18.x 27 | - name: Checkout code 28 | uses: actions/checkout@v2 29 | - uses: actions/cache@v2 30 | with: 31 | # In order: 32 | # * Module download cache 33 | # * Build cache (Linux) 34 | # * Build cache (Mac) 35 | # * Build cache (Windows) 36 | path: | 37 | ~/go/pkg/mod 38 | ~/.cache/go-build 39 | ~/Library/Caches/go-build 40 | %LocalAppData%\go-build 41 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 42 | restore-keys: | 43 | ${{ runner.os }}-go- 44 | - name: Test 45 | run: go test ./... 46 | -------------------------------------------------------------------------------- /entities/nearestusabletick.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | import ( 4 | "math" 5 | 6 | "github.com/daoleno/uniswapv3-sdk/utils" 7 | ) 8 | 9 | /** 10 | * Returns the closest tick that is nearest a given tick and usable for the given tick spacing 11 | * @param tick the target tick 12 | * @param tickSpacing the spacing of the pool 13 | */ 14 | func NearestUsableTick(tick int, tickSpacing int) int { 15 | if tickSpacing <= 0 { 16 | panic("tickSpacing must be greater than 0") 17 | } 18 | if !(tick >= utils.MinTick && tick <= utils.MaxTick) { 19 | panic("tick exceeds bounds") 20 | } 21 | 22 | rounded := Round(float64(tick)/float64(tickSpacing)) * float64(tickSpacing) 23 | if rounded < utils.MinTick { 24 | return int(rounded) + tickSpacing 25 | } 26 | if rounded > utils.MaxTick { 27 | return int(rounded) - tickSpacing 28 | } 29 | return int(rounded) 30 | } 31 | 32 | // Round like javascript Math.round 33 | // Note that this differs from many languages' round() functions, which often round this case to the next integer away from zero, instead giving a different result in the case of negative numbers with a fractional part of exactly 0.5. 34 | // For example, -1.5 rounds to -2, but -1.5 rounds to -1. 35 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round#description 36 | func Round(x float64) float64 { 37 | return math.Floor(x + 0.5) 38 | } 39 | -------------------------------------------------------------------------------- /periphery/contracts/interfaces/ITickLens.sol/ITickLens.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "ITickLens", 4 | "sourceName": "contracts/interfaces/ITickLens.sol", 5 | "abi": [ 6 | { 7 | "inputs": [ 8 | { 9 | "internalType": "address", 10 | "name": "pool", 11 | "type": "address" 12 | }, 13 | { 14 | "internalType": "int16", 15 | "name": "tickBitmapIndex", 16 | "type": "int16" 17 | } 18 | ], 19 | "name": "getPopulatedTicksInWord", 20 | "outputs": [ 21 | { 22 | "components": [ 23 | { 24 | "internalType": "int24", 25 | "name": "tick", 26 | "type": "int24" 27 | }, 28 | { 29 | "internalType": "int128", 30 | "name": "liquidityNet", 31 | "type": "int128" 32 | }, 33 | { 34 | "internalType": "uint128", 35 | "name": "liquidityGross", 36 | "type": "uint128" 37 | } 38 | ], 39 | "internalType": "struct ITickLens.PopulatedTick[]", 40 | "name": "populatedTicks", 41 | "type": "tuple[]" 42 | } 43 | ], 44 | "stateMutability": "view", 45 | "type": "function" 46 | } 47 | ], 48 | "bytecode": "0x", 49 | "deployedBytecode": "0x", 50 | "linkReferences": {}, 51 | "deployedLinkReferences": {} 52 | } 53 | -------------------------------------------------------------------------------- /utils/compute_pool_address_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/daoleno/uniswap-sdk-core/entities" 7 | "github.com/daoleno/uniswapv3-sdk/constants" 8 | "github.com/ethereum/go-ethereum/common" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestComputePoolAddress(t *testing.T) { 13 | factoryAddress := common.HexToAddress("0x1111111111111111111111111111111111111111") 14 | tokenA := entities.NewToken(1, common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"), 18, "USDC", "USD Coin") 15 | tokenB := entities.NewToken(1, common.HexToAddress("0x6B175474E89094C44Da98b954EedeAC495271d0F"), 18, "DAI", "Dai Stablecoin") 16 | result, err := ComputePoolAddress(factoryAddress, tokenA, tokenB, constants.FeeLow, "") 17 | if err != nil { 18 | panic(err) 19 | } 20 | assert.Equal(t, result, common.HexToAddress("0x90B1b09A9715CaDbFD9331b3A7652B24BfBEfD32")) 21 | 22 | USDC := entities.NewToken(1, common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"), 18, "USDC", "USD Coin") 23 | DAI := entities.NewToken(1, common.HexToAddress("0x6B175474E89094C44Da98b954EedeAC495271d0F"), 18, "DAI", "Dai Stablecoin") 24 | resultA, err := ComputePoolAddress(factoryAddress, USDC, DAI, constants.FeeLow, "") 25 | if err != nil { 26 | panic(err) 27 | } 28 | resultB, err := ComputePoolAddress(factoryAddress, DAI, USDC, constants.FeeLow, "") 29 | if err != nil { 30 | panic(err) 31 | } 32 | assert.Equal(t, resultA, resultB, "should correctly compute the pool address") 33 | } 34 | -------------------------------------------------------------------------------- /periphery/contracts/interfaces/IPeripheryPayments.sol/IPeripheryPayments.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "IPeripheryPayments", 4 | "sourceName": "contracts/interfaces/IPeripheryPayments.sol", 5 | "abi": [ 6 | { 7 | "inputs": [], 8 | "name": "refundETH", 9 | "outputs": [], 10 | "stateMutability": "payable", 11 | "type": "function" 12 | }, 13 | { 14 | "inputs": [ 15 | { 16 | "internalType": "address", 17 | "name": "token", 18 | "type": "address" 19 | }, 20 | { 21 | "internalType": "uint256", 22 | "name": "amountMinimum", 23 | "type": "uint256" 24 | }, 25 | { 26 | "internalType": "address", 27 | "name": "recipient", 28 | "type": "address" 29 | } 30 | ], 31 | "name": "sweepToken", 32 | "outputs": [], 33 | "stateMutability": "payable", 34 | "type": "function" 35 | }, 36 | { 37 | "inputs": [ 38 | { 39 | "internalType": "uint256", 40 | "name": "amountMinimum", 41 | "type": "uint256" 42 | }, 43 | { 44 | "internalType": "address", 45 | "name": "recipient", 46 | "type": "address" 47 | } 48 | ], 49 | "name": "unwrapWETH9", 50 | "outputs": [], 51 | "stateMutability": "payable", 52 | "type": "function" 53 | } 54 | ], 55 | "bytecode": "0x", 56 | "deployedBytecode": "0x", 57 | "linkReferences": {}, 58 | "deployedLinkReferences": {} 59 | } 60 | -------------------------------------------------------------------------------- /periphery/contracts/interfaces/external/IERC20PermitAllowed.sol/IERC20PermitAllowed.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "IERC20PermitAllowed", 4 | "sourceName": "contracts/interfaces/external/IERC20PermitAllowed.sol", 5 | "abi": [ 6 | { 7 | "inputs": [ 8 | { 9 | "internalType": "address", 10 | "name": "holder", 11 | "type": "address" 12 | }, 13 | { 14 | "internalType": "address", 15 | "name": "spender", 16 | "type": "address" 17 | }, 18 | { 19 | "internalType": "uint256", 20 | "name": "nonce", 21 | "type": "uint256" 22 | }, 23 | { 24 | "internalType": "uint256", 25 | "name": "expiry", 26 | "type": "uint256" 27 | }, 28 | { 29 | "internalType": "bool", 30 | "name": "allowed", 31 | "type": "bool" 32 | }, 33 | { 34 | "internalType": "uint8", 35 | "name": "v", 36 | "type": "uint8" 37 | }, 38 | { 39 | "internalType": "bytes32", 40 | "name": "r", 41 | "type": "bytes32" 42 | }, 43 | { 44 | "internalType": "bytes32", 45 | "name": "s", 46 | "type": "bytes32" 47 | } 48 | ], 49 | "name": "permit", 50 | "outputs": [], 51 | "stateMutability": "nonpayable", 52 | "type": "function" 53 | } 54 | ], 55 | "bytecode": "0x", 56 | "deployedBytecode": "0x", 57 | "linkReferences": {}, 58 | "deployedLinkReferences": {} 59 | } 60 | -------------------------------------------------------------------------------- /periphery/payments.go: -------------------------------------------------------------------------------- 1 | package periphery 2 | 3 | import ( 4 | _ "embed" 5 | "math/big" 6 | 7 | "github.com/daoleno/uniswap-sdk-core/entities" 8 | "github.com/ethereum/go-ethereum/common" 9 | ) 10 | 11 | //go:embed contracts/interfaces/IPeripheryPaymentsWithFee.sol/IPeripheryPaymentsWithFee.json 12 | var paymentsABI []byte 13 | 14 | type FeeOptions struct { 15 | Fee *entities.Percent // The percent of the output that will be taken as a fee. 16 | Recipient common.Address // The recipient of the fee. 17 | 18 | } 19 | 20 | func encodeFeeBips(fee *entities.Percent) *big.Int { 21 | return fee.Multiply(entities.NewPercent(big.NewInt(10000), big.NewInt(1))).Quotient() 22 | } 23 | 24 | func EncodeUnwrapWETH9(amountMinimum *big.Int, recipient common.Address, feeOptions *FeeOptions) ([]byte, error) { 25 | abi := GetABI(paymentsABI) 26 | if feeOptions != nil { 27 | return abi.Pack("unwrapWETH9WithFee", amountMinimum, &recipient, encodeFeeBips(feeOptions.Fee), feeOptions.Recipient) 28 | } 29 | 30 | return abi.Pack("unwrapWETH9", amountMinimum, recipient) 31 | } 32 | 33 | func EncodeSweepToken(token *entities.Token, amountMinimum *big.Int, recipient common.Address, feeOptions *FeeOptions) ([]byte, error) { 34 | abi := GetABI(paymentsABI) 35 | 36 | if feeOptions != nil { 37 | return abi.Pack("sweepTokenWithFee", token.Address, amountMinimum, recipient, encodeFeeBips(feeOptions.Fee), feeOptions.Recipient) 38 | } 39 | 40 | return abi.Pack("sweepToken", token.Address, amountMinimum, recipient) 41 | } 42 | 43 | func EncodeRefundETH() []byte { 44 | abi := GetABI(paymentsABI) 45 | data, err := abi.Pack("refundETH") 46 | if err != nil { 47 | panic(err) 48 | } 49 | return data 50 | } 51 | -------------------------------------------------------------------------------- /entities/nearestusabletick_test.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/daoleno/uniswapv3-sdk/utils" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestNearestUsableTick(t *testing.T) { 11 | assert.Panics(t, func() { NearestUsableTick(1, 0) }, "panics if tickSpacing is 0") 12 | assert.Panics(t, func() { NearestUsableTick(1, -5) }, "panics if tickSpacing is negative") 13 | assert.Panics(t, func() { NearestUsableTick(utils.MaxTick+1, 1) }, "panics if tick is greater than MaxTick") 14 | assert.Panics(t, func() { NearestUsableTick(utils.MinTick-1, 1) }, "panics if tick is smaller than MinTick") 15 | 16 | type args struct { 17 | ticks int 18 | tickSpacing int 19 | } 20 | tests := []struct { 21 | name string 22 | args args 23 | want int 24 | }{ 25 | {name: "rounds at positive half", args: args{ticks: 5, tickSpacing: 10}, want: 10}, 26 | {name: "rounds down below positive half", args: args{ticks: 4, tickSpacing: 10}, want: 0}, 27 | {name: "rounds up for negative half 0", args: args{ticks: -5, tickSpacing: 10}, want: 0}, 28 | {name: "rounds up for negative half 1", args: args{ticks: -6, tickSpacing: 10}, want: -10}, 29 | {name: "cannot round past MinTick", args: args{ticks: utils.MinTick, tickSpacing: utils.MaxTick/2 + 100}, want: -(utils.MaxTick/2 + 100)}, 30 | {name: "cannot round past MaxTick", args: args{ticks: utils.MaxTick, tickSpacing: utils.MaxTick/2 + 100}, want: utils.MaxTick/2 + 100}, 31 | } 32 | for _, tt := range tests { 33 | t.Run(tt.name, func(t *testing.T) { 34 | if got := NearestUsableTick(tt.args.ticks, tt.args.tickSpacing); got != tt.want { 35 | t.Errorf("NearestUsableTick() = %v, want %v", got, tt.want) 36 | } 37 | }) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/helper/tx.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "math/big" 7 | 8 | "github.com/ethereum/go-ethereum" 9 | "github.com/ethereum/go-ethereum/common" 10 | "github.com/ethereum/go-ethereum/core/types" 11 | "github.com/ethereum/go-ethereum/ethclient" 12 | ) 13 | 14 | //SendTx Send a real transaction to the blockchain. 15 | func SendTX(client *ethclient.Client, toAddress common.Address, value *big.Int, 16 | data []byte, w *Wallet) (*types.Transaction, error) { 17 | signedTx, err := TryTX(client, toAddress, value, data, w) 18 | if err != nil { 19 | return nil, err 20 | } 21 | return signedTx, client.SendTransaction(context.Background(), signedTx) 22 | } 23 | 24 | //Trytx Trying to send a transaction, it just return the transaction hash if success. 25 | func TryTX(client *ethclient.Client, toAddress common.Address, value *big.Int, 26 | data []byte, w *Wallet) (*types.Transaction, error) { 27 | gasPrice, err := client.SuggestGasPrice(context.Background()) 28 | if err != nil { 29 | return nil, err 30 | } 31 | 32 | gasLimit, err := client.EstimateGas(context.Background(), ethereum.CallMsg{ 33 | From: w.PublicKey, 34 | To: &toAddress, 35 | GasPrice: gasPrice, 36 | Value: value, 37 | Data: data, 38 | }) 39 | if err != nil { 40 | return nil, err 41 | } 42 | 43 | fmt.Printf("gasLimit=%d, gasPrice=%d\n", gasLimit, gasPrice.Uint64()) 44 | nounc, err := client.NonceAt(context.Background(), w.PublicKey, nil) 45 | if err != nil { 46 | return nil, err 47 | } 48 | tx := types.NewTransaction(nounc, toAddress, value, 49 | gasLimit, gasPrice, data) 50 | 51 | chainID, err := client.NetworkID(context.Background()) 52 | if err != nil { 53 | return nil, err 54 | } 55 | 56 | signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), w.PrivateKey) 57 | if err != nil { 58 | return nil, err 59 | } 60 | 61 | return signedTx, nil 62 | } 63 | -------------------------------------------------------------------------------- /periphery/selfpermit.go: -------------------------------------------------------------------------------- 1 | package periphery 2 | 3 | import ( 4 | _ "embed" 5 | "encoding/json" 6 | "errors" 7 | "math/big" 8 | 9 | "github.com/daoleno/uniswap-sdk-core/entities" 10 | "github.com/ethereum/go-ethereum/accounts/abi" 11 | ) 12 | 13 | //go:embed contracts/interfaces/ISelfPermit.sol/ISelfPermit.json 14 | var selfpermitABI []byte 15 | 16 | var ( 17 | ErrInvalidOptions = errors.New("invalid options") 18 | ) 19 | 20 | type StandardPermitArguments struct { 21 | V uint8 22 | R [32]byte 23 | S [32]byte 24 | Amount *big.Int 25 | Deadline *big.Int 26 | } 27 | 28 | type AllowedPermitArguments struct { 29 | V uint8 30 | R [32]byte 31 | S [32]byte 32 | Nonce *big.Int 33 | Expiry *big.Int 34 | } 35 | 36 | type PermitOptions struct { 37 | *StandardPermitArguments 38 | *AllowedPermitArguments 39 | } 40 | 41 | func getSelfPermitABI() abi.ABI { 42 | var wabi WrappedABI 43 | err := json.Unmarshal(selfpermitABI, &wabi) 44 | if err != nil { 45 | panic(err) 46 | } 47 | return wabi.ABI 48 | } 49 | 50 | func EncodePermit(token *entities.Token, options *PermitOptions) ([]byte, error) { 51 | if options == nil { 52 | return nil, ErrInvalidOptions 53 | } 54 | 55 | if options.StandardPermitArguments != nil { 56 | return EncodeStandardPermit(token, options.StandardPermitArguments) 57 | } 58 | 59 | if options.AllowedPermitArguments != nil { 60 | return EncodeAllowedPermit(token, options.AllowedPermitArguments) 61 | } 62 | 63 | return nil, ErrInvalidOptions 64 | } 65 | 66 | func EncodeStandardPermit(token *entities.Token, options *StandardPermitArguments) ([]byte, error) { 67 | abi := getSelfPermitABI() 68 | return abi.Pack("selfPermit", token.Address, options.Amount, options.Deadline, options.V, options.R, options.S) 69 | } 70 | 71 | func EncodeAllowedPermit(token *entities.Token, options *AllowedPermitArguments) ([]byte, error) { 72 | abi := getSelfPermitABI() 73 | return abi.Pack("selfPermitAllowed", token.Address, options.Nonce, options.Expiry, options.V, options.R, options.S) 74 | } 75 | -------------------------------------------------------------------------------- /periphery/selfpermit_test.go: -------------------------------------------------------------------------------- 1 | package periphery 2 | 3 | import ( 4 | "math/big" 5 | "testing" 6 | 7 | "github.com/ethereum/go-ethereum/common/hexutil" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestEncodePermit(t *testing.T) { 12 | var ( 13 | byte32r [32]byte 14 | byte32s [32]byte 15 | r, _ = hexutil.Decode("0x0000000000000000000000000000000000000000000000000000000000000001") 16 | s, _ = hexutil.Decode("0x0000000000000000000000000000000000000000000000000000000000000002") 17 | ) 18 | copy(byte32r[:], r) 19 | copy(byte32s[:], s) 20 | 21 | // encode with standard permit 22 | sargs := &StandardPermitArguments{ 23 | V: 0, 24 | R: byte32r, 25 | S: byte32s, 26 | Amount: big.NewInt(123), 27 | Deadline: big.NewInt(123), 28 | } 29 | opts := &PermitOptions{ 30 | StandardPermitArguments: sargs, 31 | } 32 | encoded, err := EncodePermit(token0, opts) 33 | if err != nil { 34 | t.Fatal(err) 35 | } 36 | assert.Equal(t, "0xf3995c670000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000007b000000000000000000000000000000000000000000000000000000000000007b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", hexutil.Encode(encoded)) 37 | 38 | // encode with allowed permit 39 | aargs := &AllowedPermitArguments{ 40 | V: 0, 41 | R: byte32r, 42 | S: byte32s, 43 | Nonce: big.NewInt(123), 44 | Expiry: big.NewInt(123), 45 | } 46 | opts = &PermitOptions{ 47 | AllowedPermitArguments: aargs, 48 | } 49 | encoded, err = EncodePermit(token0, opts) 50 | if err != nil { 51 | t.Fatal(err) 52 | } 53 | assert.Equal(t, "0x4659a4940000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000007b000000000000000000000000000000000000000000000000000000000000007b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002", hexutil.Encode(encoded)) 54 | } 55 | -------------------------------------------------------------------------------- /utils/compute_pool_address.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "math/big" 5 | 6 | "github.com/daoleno/uniswap-sdk-core/entities" 7 | "github.com/daoleno/uniswapv3-sdk/constants" 8 | "github.com/ethereum/go-ethereum/accounts/abi" 9 | "github.com/ethereum/go-ethereum/common" 10 | "github.com/ethereum/go-ethereum/crypto" 11 | ) 12 | 13 | /** 14 | * Computes a pool address 15 | * @param factoryAddress The Uniswap V3 factory address 16 | * @param tokenA The first token of the pair, irrespective of sort order 17 | * @param tokenB The second token of the pair, irrespective of sort order 18 | * @param fee The fee tier of the pool 19 | * @returns The pool address 20 | */ 21 | func ComputePoolAddress(factoryAddress common.Address, tokenA *entities.Token, tokenB *entities.Token, fee constants.FeeAmount, initCodeHashManualOverride string) (common.Address, error) { 22 | isSorted, err := tokenA.SortsBefore(tokenB) 23 | if err != nil { 24 | return common.Address{}, err 25 | } 26 | var ( 27 | token0 *entities.Token 28 | token1 *entities.Token 29 | ) 30 | if isSorted { 31 | token0 = tokenA 32 | token1 = tokenB 33 | } else { 34 | token0 = tokenB 35 | token1 = tokenA 36 | } 37 | return getCreate2Address(factoryAddress, token0.Address, token1.Address, fee, initCodeHashManualOverride), nil 38 | } 39 | 40 | func getCreate2Address(factoyAddress, addressA, addressB common.Address, fee constants.FeeAmount, initCodeHashManualOverride string) common.Address { 41 | var salt [32]byte 42 | copy(salt[:], crypto.Keccak256(abiEncode(addressA, addressB, fee))) 43 | 44 | if initCodeHashManualOverride != "" { 45 | crypto.CreateAddress2(factoyAddress, salt, common.FromHex(initCodeHashManualOverride)) 46 | } 47 | return crypto.CreateAddress2(factoyAddress, salt, common.FromHex(constants.PoolInitCodeHash)) 48 | } 49 | 50 | func abiEncode(addressA, addressB common.Address, fee constants.FeeAmount) []byte { 51 | addressTy, _ := abi.NewType("address", "address", nil) 52 | uint256Ty, _ := abi.NewType("uint256", "uint256", nil) 53 | 54 | arguments := abi.Arguments{{Type: addressTy}, {Type: addressTy}, {Type: uint256Ty}} 55 | 56 | bytes, _ := arguments.Pack( 57 | addressA, 58 | addressB, 59 | big.NewInt(int64(fee)), 60 | ) 61 | return bytes 62 | } 63 | -------------------------------------------------------------------------------- /examples/swap/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "math/big" 5 | "os" 6 | "time" 7 | 8 | "log" 9 | 10 | coreEntities "github.com/daoleno/uniswap-sdk-core/entities" 11 | "github.com/daoleno/uniswapv3-sdk/constants" 12 | "github.com/daoleno/uniswapv3-sdk/entities" 13 | "github.com/daoleno/uniswapv3-sdk/examples/helper" 14 | "github.com/daoleno/uniswapv3-sdk/periphery" 15 | "github.com/ethereum/go-ethereum/common" 16 | "github.com/ethereum/go-ethereum/ethclient" 17 | ) 18 | 19 | func init() { 20 | log.SetFlags(log.Lshortfile | log.LstdFlags) 21 | } 22 | 23 | func main() { 24 | client, err := ethclient.Dial(helper.PolygonRPC) 25 | if err != nil { 26 | log.Fatal(err) 27 | } 28 | wallet := helper.InitWallet(os.Getenv("MY_PRIVATE_KEY")) 29 | if wallet == nil { 30 | log.Fatal("init wallet failed") 31 | } 32 | 33 | pool, err := helper.ConstructV3Pool(client, helper.WMATIC, helper.AMP, uint64(constants.FeeMedium)) 34 | if err != nil { 35 | log.Fatal(err) 36 | } 37 | 38 | //0.01% 39 | slippageTolerance := coreEntities.NewPercent(big.NewInt(1), big.NewInt(1000)) 40 | //after 5 minutes 41 | d := time.Now().Add(time.Minute * time.Duration(15)).Unix() 42 | deadline := big.NewInt(d) 43 | 44 | // single trade input 45 | // single-hop exact input 46 | r, err := entities.NewRoute([]*entities.Pool{pool}, helper.WMATIC, helper.AMP) 47 | if err != nil { 48 | log.Fatal(err) 49 | } 50 | 51 | swapValue := helper.FloatStringToBigInt("0.1", 18) 52 | trade, err := entities.FromRoute(r, coreEntities.FromRawAmount(helper.WMATIC, swapValue), coreEntities.ExactInput) 53 | if err != nil { 54 | log.Fatal(err) 55 | } 56 | log.Printf("%v %v\n", trade.Swaps[0].InputAmount.Quotient(), trade.Swaps[0].OutputAmount.Wrapped().Quotient()) 57 | params, err := periphery.SwapCallParameters([]*entities.Trade{trade}, &periphery.SwapOptions{ 58 | SlippageTolerance: slippageTolerance, 59 | Recipient: wallet.PublicKey, 60 | Deadline: deadline, 61 | }) 62 | if err != nil { 63 | log.Fatal(err) 64 | } 65 | log.Printf("calldata = 0x%x\n", params.Value.String()) 66 | 67 | tx, err := helper.TryTX(client, common.HexToAddress(helper.ContractV3SwapRouterV1), 68 | swapValue, params.Calldata, wallet) 69 | if err != nil { 70 | log.Fatal(err) 71 | } 72 | log.Println(tx.Hash().String()) 73 | } 74 | -------------------------------------------------------------------------------- /utils/price_tick_conversions.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "math/big" 5 | 6 | "github.com/daoleno/uniswap-sdk-core/entities" 7 | "github.com/daoleno/uniswapv3-sdk/constants" 8 | ) 9 | 10 | /** 11 | * Returns a price object corresponding to the input tick and the base/quote token 12 | * Inputs must be tokens because the address order is used to interpret the price represented by the tick 13 | * @param baseToken the base token of the price 14 | * @param quoteToken the quote token of the price 15 | * @param tick the tick for which to return the price 16 | */ 17 | func TickToPrice(baseToken *entities.Token, quoteToken *entities.Token, tick int) (*entities.Price, error) { 18 | sqrtRatioX96, err := GetSqrtRatioAtTick(tick) 19 | if err != nil { 20 | return nil, err 21 | } 22 | ratioX192 := new(big.Int).Mul(sqrtRatioX96, sqrtRatioX96) 23 | 24 | sorted, err := baseToken.SortsBefore(quoteToken) 25 | if err != nil { 26 | return nil, err 27 | } 28 | if sorted { 29 | return entities.NewPrice(baseToken, quoteToken, constants.Q192, ratioX192), nil 30 | } 31 | return entities.NewPrice(baseToken, quoteToken, ratioX192, constants.Q192), nil 32 | } 33 | 34 | /** 35 | * Returns the first tick for which the given price is greater than or equal to the tick price 36 | * @param price for which to return the closest tick that represents a price less than or equal to the input price, 37 | * i.e. the price of the returned tick is less than or equal to the input price 38 | */ 39 | func PriceToClosestTick(price *entities.Price, baseToken, quoteToken *entities.Token) (int, error) { 40 | sorted, err := baseToken.SortsBefore(quoteToken) 41 | if err != nil { 42 | return 0, err 43 | } 44 | var sqrtRatioX96 *big.Int 45 | if sorted { 46 | sqrtRatioX96 = EncodeSqrtRatioX96(price.Numerator, price.Denominator) 47 | } else { 48 | sqrtRatioX96 = EncodeSqrtRatioX96(price.Denominator, price.Numerator) 49 | } 50 | tick, err := GetTickAtSqrtRatio(sqrtRatioX96) 51 | if err != nil { 52 | return 0, err 53 | } 54 | nextTickPrice, err := TickToPrice(baseToken, quoteToken, tick+1) 55 | if err != nil { 56 | return 0, err 57 | } 58 | if sorted { 59 | if !price.LessThan(nextTickPrice.Fraction) { 60 | tick++ 61 | } 62 | } else { 63 | if !price.GreaterThan(nextTickPrice.Fraction) { 64 | tick++ 65 | } 66 | } 67 | return tick, nil 68 | } 69 | -------------------------------------------------------------------------------- /examples/helper/pool.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "errors" 5 | "math/big" 6 | 7 | "github.com/daoleno/uniswapv3-sdk/examples/contract" 8 | 9 | coreEntities "github.com/daoleno/uniswap-sdk-core/entities" 10 | "github.com/daoleno/uniswapv3-sdk/constants" 11 | "github.com/daoleno/uniswapv3-sdk/entities" 12 | sdkutils "github.com/daoleno/uniswapv3-sdk/utils" 13 | "github.com/ethereum/go-ethereum/common" 14 | "github.com/ethereum/go-ethereum/ethclient" 15 | ) 16 | 17 | func GetPoolAddress(client *ethclient.Client, token0, token1 common.Address, fee *big.Int) (common.Address, error) { 18 | f, err := contract.NewUniswapv3Factory(common.HexToAddress(ContractV3Factory), client) 19 | if err != nil { 20 | return common.Address{}, err 21 | } 22 | poolAddr, err := f.GetPool(nil, token0, token1, fee) 23 | if err != nil { 24 | return common.Address{}, err 25 | } 26 | if poolAddr == (common.Address{}) { 27 | return common.Address{}, errors.New("pool is not exist") 28 | } 29 | 30 | return poolAddr, nil 31 | } 32 | 33 | func ConstructV3Pool(client *ethclient.Client, token0, token1 *coreEntities.Token, poolFee uint64) (*entities.Pool, error) { 34 | poolAddress, err := GetPoolAddress(client, token0.Address, token1.Address, new(big.Int).SetUint64(poolFee)) 35 | if err != nil { 36 | return nil, err 37 | } 38 | 39 | contractPool, err := contract.NewUniswapv3Pool(poolAddress, client) 40 | if err != nil { 41 | return nil, err 42 | } 43 | 44 | liquidity, err := contractPool.Liquidity(nil) 45 | if err != nil { 46 | return nil, err 47 | } 48 | 49 | slot0, err := contractPool.Slot0(nil) 50 | if err != nil { 51 | return nil, err 52 | } 53 | 54 | pooltick, err := contractPool.Ticks(nil, big.NewInt(0)) 55 | if err != nil { 56 | return nil, err 57 | } 58 | 59 | feeAmount := constants.FeeAmount(poolFee) 60 | ticks := []entities.Tick{ 61 | { 62 | Index: entities.NearestUsableTick(sdkutils.MinTick, 63 | constants.TickSpacings[feeAmount]), 64 | LiquidityNet: pooltick.LiquidityNet, 65 | LiquidityGross: pooltick.LiquidityGross, 66 | }, 67 | { 68 | Index: entities.NearestUsableTick(sdkutils.MaxTick, 69 | constants.TickSpacings[feeAmount]), 70 | LiquidityNet: pooltick.LiquidityNet, 71 | LiquidityGross: pooltick.LiquidityGross, 72 | }, 73 | } 74 | 75 | // create tick data provider 76 | p, err := entities.NewTickListDataProvider(ticks, constants.TickSpacings[feeAmount]) 77 | if err != nil { 78 | return nil, err 79 | } 80 | 81 | return entities.NewPool(token0, token1, constants.FeeAmount(poolFee), 82 | slot0.SqrtPriceX96, liquidity, int(slot0.Tick.Int64()), p) 83 | } 84 | -------------------------------------------------------------------------------- /periphery/payments_test.go: -------------------------------------------------------------------------------- 1 | package periphery 2 | 3 | import ( 4 | "math/big" 5 | "testing" 6 | 7 | "github.com/daoleno/uniswap-sdk-core/entities" 8 | "github.com/ethereum/go-ethereum/common" 9 | "github.com/ethereum/go-ethereum/common/hexutil" 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | var ( 14 | recipient = common.HexToAddress("0x0000000000000000000000000000000000000003") 15 | amount = big.NewInt(123) 16 | feeOptions = &FeeOptions{ 17 | Fee: entities.NewPercent(big.NewInt(1), big.NewInt(1000)), 18 | Recipient: common.HexToAddress("0x0000000000000000000000000000000000000009"), 19 | } 20 | token = entities.NewToken(1, common.HexToAddress("0x0000000000000000000000000000000000000001"), 18, "t0", "token0") 21 | ) 22 | 23 | func TestEncodeUnwrapWETH9(t *testing.T) { 24 | // works without feeOptions 25 | calldata, err := EncodeUnwrapWETH9(amount, recipient, nil) 26 | assert.NoError(t, err) 27 | assert.Equal(t, "0x49404b7c000000000000000000000000000000000000000000000000000000000000007b0000000000000000000000000000000000000000000000000000000000000003", hexutil.Encode(calldata)) 28 | 29 | // works with feeOptions 30 | calldata, err = EncodeUnwrapWETH9(amount, recipient, feeOptions) 31 | assert.NoError(t, err) 32 | assert.Equal(t, "0x9b2c0a37000000000000000000000000000000000000000000000000000000000000007b0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000009", hexutil.Encode(calldata)) 33 | } 34 | 35 | func TestEncodeSweepToken(t *testing.T) { 36 | // works without feeOptions 37 | calldata, err := EncodeSweepToken(token, amount, recipient, nil) 38 | assert.NoError(t, err) 39 | assert.Equal(t, "0xdf2ab5bb0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000007b0000000000000000000000000000000000000000000000000000000000000003", hexutil.Encode(calldata)) 40 | 41 | // works with feeOptions 42 | calldata, err = EncodeSweepToken(token, amount, recipient, feeOptions) 43 | assert.NoError(t, err) 44 | assert.Equal(t, "0xe0e189a00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000007b0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000009", hexutil.Encode(calldata)) 45 | } 46 | 47 | func TestEncodeRefundETH(t *testing.T) { 48 | // works without feeOptions 49 | calldata := EncodeRefundETH() 50 | assert.Equal(t, "0x12210e8a", hexutil.Encode(calldata)) 51 | } 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Uniswap V3 SDK 2 | 3 | [![API Reference](https://camo.githubusercontent.com/915b7be44ada53c290eb157634330494ebe3e30a/68747470733a2f2f676f646f632e6f72672f6769746875622e636f6d2f676f6c616e672f6764646f3f7374617475732e737667)](https://pkg.go.dev/github.com/daoleno/uniswapv3-sdk) 4 | [![Test](https://github.com/daoleno/uniswapv3-sdk/actions/workflows/test.yml/badge.svg)](https://github.com/daoleno/uniswapv3-sdk/actions/workflows/test.yml) 5 | [![Go Report Card](https://goreportcard.com/badge/github.com/daoleno/uniswapv3-sdk)](https://goreportcard.com/report/github.com/daoleno/uniswapv3-sdk) 6 | 7 | 🛠 A Go SDK for building applications on top of Uniswap V3 8 | 9 | ## Installation 10 | 11 | ```sh 12 | go get github.com/daoleno/uniswapv3-sdk 13 | ``` 14 | 15 | ## Usage 16 | 17 | The following example shows how to create a pool, and get the inputAmount 18 | 19 | ```go 20 | package main 21 | 22 | import ( 23 | "fmt" 24 | "math/big" 25 | 26 | core "github.com/daoleno/uniswap-sdk-core/entities" 27 | "github.com/daoleno/uniswapv3-sdk/constants" 28 | "github.com/daoleno/uniswapv3-sdk/entities" 29 | "github.com/daoleno/uniswapv3-sdk/utils" 30 | "github.com/ethereum/go-ethereum/common" 31 | ) 32 | 33 | var ( 34 | USDC = core.NewToken(1, common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"), 6, "USDC", "USD Coin") 35 | DAI = core.NewToken(1, common.HexToAddress("0x6B175474E89094C44Da98b954EedeAC495271d0F"), 18, "DAI", "Dai Stablecoin") 36 | OneEther = big.NewInt(1e18) 37 | ) 38 | 39 | func main() { 40 | // create demo ticks 41 | ticks := []entities.Tick{ 42 | { 43 | Index: entities.NearestUsableTick(utils.MinTick, constants.TickSpacings[constants.FeeLow]), 44 | LiquidityNet: OneEther, 45 | LiquidityGross: OneEther, 46 | }, 47 | { 48 | Index: entities.NearestUsableTick(utils.MaxTick, constants.TickSpacings[constants.FeeLow]), 49 | LiquidityNet: new(big.Int).Mul(OneEther, constants.NegativeOne), 50 | LiquidityGross: OneEther, 51 | }, 52 | } 53 | 54 | // create tick data provider 55 | p, err := entities.NewTickListDataProvider(ticks, constants.TickSpacings[constants.FeeLow]) 56 | if err != nil { 57 | panic(err) 58 | } 59 | 60 | // new pool 61 | pool, err := entities.NewPool(USDC, DAI, constants.FeeLow, utils.EncodeSqrtRatioX96(constants.One, constants.One), OneEther, 0, p) 62 | if err != nil { 63 | panic(err) 64 | } 65 | 66 | // USDC -> DAI 67 | outputAmount := core.FromRawAmount(DAI, big.NewInt(98)) 68 | inputAmount, _, err := pool.GetInputAmount(outputAmount, nil) 69 | if err != nil { 70 | panic(err) 71 | } 72 | fmt.Println(inputAmount.ToSignificant(4)) 73 | } 74 | ``` 75 | 76 | [More Examples](./examples/README.md) 77 | -------------------------------------------------------------------------------- /utils/swap_math.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "math/big" 5 | 6 | "github.com/daoleno/uniswapv3-sdk/constants" 7 | ) 8 | 9 | var MaxFee = new(big.Int).Exp(big.NewInt(10), big.NewInt(6), nil) 10 | 11 | func ComputeSwapStep(sqrtRatioCurrentX96, sqrtRatioTargetX96, liquidity, amountRemaining *big.Int, feePips constants.FeeAmount) (sqrtRatioNextX96, amountIn, amountOut, feeAmount *big.Int, err error) { 12 | zeroForOne := sqrtRatioCurrentX96.Cmp(sqrtRatioTargetX96) >= 0 13 | exactIn := amountRemaining.Cmp(constants.Zero) >= 0 14 | 15 | if exactIn { 16 | amountRemainingLessFee := new(big.Int).Div(new(big.Int).Mul(amountRemaining, new(big.Int).Sub(MaxFee, big.NewInt(int64(feePips)))), MaxFee) 17 | if zeroForOne { 18 | amountIn = GetAmount0Delta(sqrtRatioTargetX96, sqrtRatioCurrentX96, liquidity, true) 19 | } else { 20 | amountIn = GetAmount1Delta(sqrtRatioCurrentX96, sqrtRatioTargetX96, liquidity, true) 21 | } 22 | if amountRemainingLessFee.Cmp(amountIn) >= 0 { 23 | sqrtRatioNextX96 = sqrtRatioTargetX96 24 | } else { 25 | sqrtRatioNextX96, err = GetNextSqrtPriceFromInput(sqrtRatioCurrentX96, liquidity, amountRemainingLessFee, zeroForOne) 26 | if err != nil { 27 | return 28 | } 29 | } 30 | } else { 31 | if zeroForOne { 32 | amountOut = GetAmount1Delta(sqrtRatioTargetX96, sqrtRatioCurrentX96, liquidity, false) 33 | } else { 34 | amountOut = GetAmount0Delta(sqrtRatioCurrentX96, sqrtRatioTargetX96, liquidity, false) 35 | } 36 | if new(big.Int).Mul(amountRemaining, constants.NegativeOne).Cmp(amountOut) >= 0 { 37 | sqrtRatioNextX96 = sqrtRatioTargetX96 38 | } else { 39 | sqrtRatioNextX96, err = GetNextSqrtPriceFromOutput(sqrtRatioCurrentX96, liquidity, new(big.Int).Mul(amountRemaining, constants.NegativeOne), zeroForOne) 40 | if err != nil { 41 | return 42 | } 43 | } 44 | } 45 | 46 | max := sqrtRatioTargetX96.Cmp(sqrtRatioNextX96) == 0 47 | 48 | if zeroForOne { 49 | if !(max && exactIn) { 50 | amountIn = GetAmount0Delta(sqrtRatioNextX96, sqrtRatioCurrentX96, liquidity, true) 51 | } 52 | if !(max && !exactIn) { 53 | amountOut = GetAmount1Delta(sqrtRatioNextX96, sqrtRatioCurrentX96, liquidity, false) 54 | } 55 | } else { 56 | if !(max && exactIn) { 57 | amountIn = GetAmount1Delta(sqrtRatioCurrentX96, sqrtRatioNextX96, liquidity, true) 58 | } 59 | if !(max && !exactIn) { 60 | amountOut = GetAmount0Delta(sqrtRatioCurrentX96, sqrtRatioNextX96, liquidity, false) 61 | } 62 | } 63 | 64 | if !exactIn && amountOut.Cmp(new(big.Int).Mul(amountRemaining, constants.NegativeOne)) > 0 { 65 | amountOut = new(big.Int).Mul(amountRemaining, constants.NegativeOne) 66 | } 67 | 68 | if exactIn && sqrtRatioNextX96.Cmp(sqrtRatioTargetX96) != 0 { 69 | // we didn't reach the target, so take the remainder of the maximum input as fee 70 | feeAmount = new(big.Int).Sub(amountRemaining, amountIn) 71 | } else { 72 | feeAmount = MulDivRoundingUp(amountIn, big.NewInt(int64(feePips)), new(big.Int).Sub(MaxFee, big.NewInt(int64(feePips)))) 73 | } 74 | 75 | return 76 | } 77 | -------------------------------------------------------------------------------- /periphery/const_test.go: -------------------------------------------------------------------------------- 1 | package periphery 2 | 3 | import ( 4 | "math/big" 5 | 6 | core "github.com/daoleno/uniswap-sdk-core/entities" 7 | "github.com/daoleno/uniswapv3-sdk/constants" 8 | "github.com/daoleno/uniswapv3-sdk/entities" 9 | "github.com/daoleno/uniswapv3-sdk/utils" 10 | "github.com/ethereum/go-ethereum/common" 11 | ) 12 | 13 | var ( 14 | ether = core.EtherOnChain(1) 15 | token0 = core.NewToken(1, common.HexToAddress("0x0000000000000000000000000000000000000001"), 18, "t0", "token0") 16 | token1 = core.NewToken(1, common.HexToAddress("0x0000000000000000000000000000000000000002"), 18, "t1", "token1") 17 | token2 = core.NewToken(1, common.HexToAddress("0x0000000000000000000000000000000000000003"), 18, "t2", "token2") 18 | token3 = core.NewToken(1, common.HexToAddress("0x0000000000000000000000000000000000000004"), 18, "t2", "token3") 19 | 20 | weth = ether.Wrapped() 21 | 22 | pool_0_1_medium, _ = entities.NewPool(token0, token1, constants.FeeMedium, utils.EncodeSqrtRatioX96(constants.One, constants.One), big.NewInt(0), 0, nil) 23 | pool_1_2_low, _ = entities.NewPool(token1, token2, constants.FeeLow, utils.EncodeSqrtRatioX96(constants.One, constants.One), big.NewInt(0), 0, nil) 24 | pool_0_weth, _ = entities.NewPool(token0, weth, constants.FeeMedium, utils.EncodeSqrtRatioX96(constants.One, constants.One), big.NewInt(0), 0, nil) 25 | pool_1_weth, _ = entities.NewPool(token1, weth, constants.FeeMedium, utils.EncodeSqrtRatioX96(constants.One, constants.One), big.NewInt(0), 0, nil) 26 | 27 | route_0_1, _ = entities.NewRoute([]*entities.Pool{pool_0_1_medium}, token0, token1) 28 | route_0_1_2, _ = entities.NewRoute([]*entities.Pool{pool_0_1_medium, pool_1_2_low}, token0, token2) 29 | 30 | route_0_weth, _ = entities.NewRoute([]*entities.Pool{pool_0_weth}, token0, weth) 31 | route_0_1_weth, _ = entities.NewRoute([]*entities.Pool{pool_0_1_medium, pool_1_weth}, token0, weth) 32 | route_weth_0, _ = entities.NewRoute([]*entities.Pool{pool_0_weth}, weth, token0) 33 | route_weth_0_1, _ = entities.NewRoute([]*entities.Pool{pool_0_weth, pool_0_1_medium}, weth, token1) 34 | 35 | feeAmount = constants.FeeMedium 36 | sqrtRatioX96 = utils.EncodeSqrtRatioX96(big.NewInt(1), big.NewInt(1)) 37 | liquidity = big.NewInt(1_000_000) 38 | tick, _ = utils.GetTickAtSqrtRatio(sqrtRatioX96) 39 | ticks = []entities.Tick{ 40 | { 41 | Index: entities.NearestUsableTick(utils.MinTick, constants.TickSpacings[feeAmount]), 42 | LiquidityNet: liquidity, 43 | LiquidityGross: liquidity, 44 | }, 45 | { 46 | Index: entities.NearestUsableTick(utils.MaxTick, constants.TickSpacings[feeAmount]), 47 | LiquidityNet: new(big.Int).Mul(liquidity, constants.NegativeOne), 48 | LiquidityGross: liquidity, 49 | }, 50 | } 51 | 52 | p, _ = entities.NewTickListDataProvider(ticks, constants.TickSpacings[feeAmount]) 53 | makePool = func(token0, token1 *core.Token) *entities.Pool { 54 | pool, _ := entities.NewPool(token0, token1, feeAmount, sqrtRatioX96, liquidity, tick, p) 55 | return pool 56 | } 57 | ) 58 | -------------------------------------------------------------------------------- /periphery/contracts/interfaces/IPeripheryPaymentsWithFee.sol/IPeripheryPaymentsWithFee.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "IPeripheryPaymentsWithFee", 4 | "sourceName": "contracts/interfaces/IPeripheryPaymentsWithFee.sol", 5 | "abi": [ 6 | { 7 | "inputs": [], 8 | "name": "refundETH", 9 | "outputs": [], 10 | "stateMutability": "payable", 11 | "type": "function" 12 | }, 13 | { 14 | "inputs": [ 15 | { 16 | "internalType": "address", 17 | "name": "token", 18 | "type": "address" 19 | }, 20 | { 21 | "internalType": "uint256", 22 | "name": "amountMinimum", 23 | "type": "uint256" 24 | }, 25 | { 26 | "internalType": "address", 27 | "name": "recipient", 28 | "type": "address" 29 | } 30 | ], 31 | "name": "sweepToken", 32 | "outputs": [], 33 | "stateMutability": "payable", 34 | "type": "function" 35 | }, 36 | { 37 | "inputs": [ 38 | { 39 | "internalType": "address", 40 | "name": "token", 41 | "type": "address" 42 | }, 43 | { 44 | "internalType": "uint256", 45 | "name": "amountMinimum", 46 | "type": "uint256" 47 | }, 48 | { 49 | "internalType": "address", 50 | "name": "recipient", 51 | "type": "address" 52 | }, 53 | { 54 | "internalType": "uint256", 55 | "name": "feeBips", 56 | "type": "uint256" 57 | }, 58 | { 59 | "internalType": "address", 60 | "name": "feeRecipient", 61 | "type": "address" 62 | } 63 | ], 64 | "name": "sweepTokenWithFee", 65 | "outputs": [], 66 | "stateMutability": "payable", 67 | "type": "function" 68 | }, 69 | { 70 | "inputs": [ 71 | { 72 | "internalType": "uint256", 73 | "name": "amountMinimum", 74 | "type": "uint256" 75 | }, 76 | { 77 | "internalType": "address", 78 | "name": "recipient", 79 | "type": "address" 80 | } 81 | ], 82 | "name": "unwrapWETH9", 83 | "outputs": [], 84 | "stateMutability": "payable", 85 | "type": "function" 86 | }, 87 | { 88 | "inputs": [ 89 | { 90 | "internalType": "uint256", 91 | "name": "amountMinimum", 92 | "type": "uint256" 93 | }, 94 | { 95 | "internalType": "address", 96 | "name": "recipient", 97 | "type": "address" 98 | }, 99 | { 100 | "internalType": "uint256", 101 | "name": "feeBips", 102 | "type": "uint256" 103 | }, 104 | { 105 | "internalType": "address", 106 | "name": "feeRecipient", 107 | "type": "address" 108 | } 109 | ], 110 | "name": "unwrapWETH9WithFee", 111 | "outputs": [], 112 | "stateMutability": "payable", 113 | "type": "function" 114 | } 115 | ], 116 | "bytecode": "0x", 117 | "deployedBytecode": "0x", 118 | "linkReferences": {}, 119 | "deployedLinkReferences": {} 120 | } 121 | -------------------------------------------------------------------------------- /periphery/contracts/interfaces/IQuoter.sol/IQuoter.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "IQuoter", 4 | "sourceName": "contracts/interfaces/IQuoter.sol", 5 | "abi": [ 6 | { 7 | "inputs": [ 8 | { 9 | "internalType": "bytes", 10 | "name": "path", 11 | "type": "bytes" 12 | }, 13 | { 14 | "internalType": "uint256", 15 | "name": "amountIn", 16 | "type": "uint256" 17 | } 18 | ], 19 | "name": "quoteExactInput", 20 | "outputs": [ 21 | { 22 | "internalType": "uint256", 23 | "name": "amountOut", 24 | "type": "uint256" 25 | } 26 | ], 27 | "stateMutability": "nonpayable", 28 | "type": "function" 29 | }, 30 | { 31 | "inputs": [ 32 | { 33 | "internalType": "address", 34 | "name": "tokenIn", 35 | "type": "address" 36 | }, 37 | { 38 | "internalType": "address", 39 | "name": "tokenOut", 40 | "type": "address" 41 | }, 42 | { 43 | "internalType": "uint24", 44 | "name": "fee", 45 | "type": "uint24" 46 | }, 47 | { 48 | "internalType": "uint256", 49 | "name": "amountIn", 50 | "type": "uint256" 51 | }, 52 | { 53 | "internalType": "uint160", 54 | "name": "sqrtPriceLimitX96", 55 | "type": "uint160" 56 | } 57 | ], 58 | "name": "quoteExactInputSingle", 59 | "outputs": [ 60 | { 61 | "internalType": "uint256", 62 | "name": "amountOut", 63 | "type": "uint256" 64 | } 65 | ], 66 | "stateMutability": "nonpayable", 67 | "type": "function" 68 | }, 69 | { 70 | "inputs": [ 71 | { 72 | "internalType": "bytes", 73 | "name": "path", 74 | "type": "bytes" 75 | }, 76 | { 77 | "internalType": "uint256", 78 | "name": "amountOut", 79 | "type": "uint256" 80 | } 81 | ], 82 | "name": "quoteExactOutput", 83 | "outputs": [ 84 | { 85 | "internalType": "uint256", 86 | "name": "amountIn", 87 | "type": "uint256" 88 | } 89 | ], 90 | "stateMutability": "nonpayable", 91 | "type": "function" 92 | }, 93 | { 94 | "inputs": [ 95 | { 96 | "internalType": "address", 97 | "name": "tokenIn", 98 | "type": "address" 99 | }, 100 | { 101 | "internalType": "address", 102 | "name": "tokenOut", 103 | "type": "address" 104 | }, 105 | { 106 | "internalType": "uint24", 107 | "name": "fee", 108 | "type": "uint24" 109 | }, 110 | { 111 | "internalType": "uint256", 112 | "name": "amountOut", 113 | "type": "uint256" 114 | }, 115 | { 116 | "internalType": "uint160", 117 | "name": "sqrtPriceLimitX96", 118 | "type": "uint160" 119 | } 120 | ], 121 | "name": "quoteExactOutputSingle", 122 | "outputs": [ 123 | { 124 | "internalType": "uint256", 125 | "name": "amountIn", 126 | "type": "uint256" 127 | } 128 | ], 129 | "stateMutability": "nonpayable", 130 | "type": "function" 131 | } 132 | ], 133 | "bytecode": "0x", 134 | "deployedBytecode": "0x", 135 | "linkReferences": {}, 136 | "deployedLinkReferences": {} 137 | } 138 | -------------------------------------------------------------------------------- /entities/route.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/daoleno/uniswap-sdk-core/entities" 7 | ) 8 | 9 | var ( 10 | ErrRouteNoPools = errors.New("route must have at least one pool") 11 | ErrAllOnSameChain = errors.New("all pools must be on the same chain") 12 | ErrInputNotInvolved = errors.New("input token not involved in route") 13 | ErrOutputNotInvolved = errors.New("output token not involved in route") 14 | ErrPathNotContinuous = errors.New("path not continuous") 15 | ) 16 | 17 | // Route represents a list of pools through which a swap can occur 18 | type Route struct { 19 | Pools []*Pool 20 | TokenPath []*entities.Token 21 | Input entities.Currency 22 | Output entities.Currency 23 | 24 | midPrice *entities.Price 25 | } 26 | 27 | /** 28 | * Creates an instance of route. 29 | * @param pools An array of `Pool` objects, ordered by the route the swap will take 30 | * @param input The input token 31 | * @param output The output token 32 | */ 33 | func NewRoute(pools []*Pool, input, output entities.Currency) (*Route, error) { 34 | if len(pools) == 0 { 35 | return nil, ErrRouteNoPools 36 | } 37 | chainID := pools[0].ChainID() 38 | for _, p := range pools { 39 | if p.ChainID() != chainID { 40 | return nil, ErrAllOnSameChain 41 | } 42 | } 43 | wrappedInput := input.Wrapped() 44 | 45 | if !pools[0].InvolvesToken(wrappedInput) { 46 | return nil, ErrInputNotInvolved 47 | } 48 | 49 | // Normalizes token0-token1 order and selects the next token/fee step to add to the path 50 | tokenPath := []*entities.Token{wrappedInput} 51 | for i, p := range pools { 52 | currentInputToken := tokenPath[i] 53 | if !(currentInputToken.Equal(p.Token0) || currentInputToken.Equal(p.Token1)) { 54 | return nil, ErrPathNotContinuous 55 | } 56 | var nextToken *entities.Token 57 | if currentInputToken.Equal(p.Token0) { 58 | nextToken = p.Token1 59 | } else { 60 | nextToken = p.Token0 61 | } 62 | tokenPath = append(tokenPath, nextToken) 63 | } 64 | 65 | if output == nil { 66 | output = tokenPath[len(tokenPath)-1] 67 | } else { 68 | if !pools[len(pools)-1].InvolvesToken(output.Wrapped()) { 69 | return nil, ErrOutputNotInvolved 70 | } 71 | } 72 | return &Route{ 73 | Pools: pools, 74 | TokenPath: tokenPath, 75 | Input: input, 76 | Output: output, 77 | }, nil 78 | } 79 | 80 | func (r *Route) ChainID() uint { 81 | return r.Pools[0].ChainID() 82 | } 83 | 84 | // MidPrice Returns the mid price of the route 85 | func (r *Route) MidPrice() (*entities.Price, error) { 86 | if r.midPrice != nil { 87 | return r.midPrice, nil 88 | } 89 | var ( 90 | nextInput *entities.Token 91 | price *entities.Price 92 | ) 93 | if r.Pools[0].Token0.Equal(r.Input) { 94 | nextInput = r.Pools[0].Token1 95 | price = r.Pools[0].Token0Price() 96 | } else { 97 | nextInput = r.Pools[0].Token0 98 | price = r.Pools[0].Token1Price() 99 | } 100 | price, err := reducePrice(nextInput, price, r.Pools[1:]) 101 | if err != nil { 102 | return nil, err 103 | } 104 | r.midPrice = entities.NewPrice(r.Input, r.Output, price.Denominator, price.Numerator) 105 | return r.midPrice, nil 106 | } 107 | 108 | // reducePrice reduces the price of the route by the given amount 109 | func reducePrice(nextInput *entities.Token, price *entities.Price, pools []*Pool) (*entities.Price, error) { 110 | var err error 111 | for _, p := range pools { 112 | if nextInput.Equal(p.Token0) { 113 | nextInput = p.Token1 114 | price, err = price.Multiply(p.Token0Price()) 115 | if err != nil { 116 | return nil, err 117 | } 118 | } else { 119 | nextInput = p.Token0 120 | price, err = price.Multiply(p.Token1Price()) 121 | if err != nil { 122 | return nil, err 123 | } 124 | } 125 | } 126 | return price, nil 127 | } 128 | -------------------------------------------------------------------------------- /periphery/contracts/interfaces/ISelfPermit.sol/ISelfPermit.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "ISelfPermit", 4 | "sourceName": "contracts/interfaces/ISelfPermit.sol", 5 | "abi": [ 6 | { 7 | "inputs": [ 8 | { 9 | "internalType": "address", 10 | "name": "token", 11 | "type": "address" 12 | }, 13 | { 14 | "internalType": "uint256", 15 | "name": "value", 16 | "type": "uint256" 17 | }, 18 | { 19 | "internalType": "uint256", 20 | "name": "deadline", 21 | "type": "uint256" 22 | }, 23 | { 24 | "internalType": "uint8", 25 | "name": "v", 26 | "type": "uint8" 27 | }, 28 | { 29 | "internalType": "bytes32", 30 | "name": "r", 31 | "type": "bytes32" 32 | }, 33 | { 34 | "internalType": "bytes32", 35 | "name": "s", 36 | "type": "bytes32" 37 | } 38 | ], 39 | "name": "selfPermit", 40 | "outputs": [], 41 | "stateMutability": "payable", 42 | "type": "function" 43 | }, 44 | { 45 | "inputs": [ 46 | { 47 | "internalType": "address", 48 | "name": "token", 49 | "type": "address" 50 | }, 51 | { 52 | "internalType": "uint256", 53 | "name": "nonce", 54 | "type": "uint256" 55 | }, 56 | { 57 | "internalType": "uint256", 58 | "name": "expiry", 59 | "type": "uint256" 60 | }, 61 | { 62 | "internalType": "uint8", 63 | "name": "v", 64 | "type": "uint8" 65 | }, 66 | { 67 | "internalType": "bytes32", 68 | "name": "r", 69 | "type": "bytes32" 70 | }, 71 | { 72 | "internalType": "bytes32", 73 | "name": "s", 74 | "type": "bytes32" 75 | } 76 | ], 77 | "name": "selfPermitAllowed", 78 | "outputs": [], 79 | "stateMutability": "payable", 80 | "type": "function" 81 | }, 82 | { 83 | "inputs": [ 84 | { 85 | "internalType": "address", 86 | "name": "token", 87 | "type": "address" 88 | }, 89 | { 90 | "internalType": "uint256", 91 | "name": "nonce", 92 | "type": "uint256" 93 | }, 94 | { 95 | "internalType": "uint256", 96 | "name": "expiry", 97 | "type": "uint256" 98 | }, 99 | { 100 | "internalType": "uint8", 101 | "name": "v", 102 | "type": "uint8" 103 | }, 104 | { 105 | "internalType": "bytes32", 106 | "name": "r", 107 | "type": "bytes32" 108 | }, 109 | { 110 | "internalType": "bytes32", 111 | "name": "s", 112 | "type": "bytes32" 113 | } 114 | ], 115 | "name": "selfPermitAllowedIfNecessary", 116 | "outputs": [], 117 | "stateMutability": "payable", 118 | "type": "function" 119 | }, 120 | { 121 | "inputs": [ 122 | { 123 | "internalType": "address", 124 | "name": "token", 125 | "type": "address" 126 | }, 127 | { 128 | "internalType": "uint256", 129 | "name": "value", 130 | "type": "uint256" 131 | }, 132 | { 133 | "internalType": "uint256", 134 | "name": "deadline", 135 | "type": "uint256" 136 | }, 137 | { 138 | "internalType": "uint8", 139 | "name": "v", 140 | "type": "uint8" 141 | }, 142 | { 143 | "internalType": "bytes32", 144 | "name": "r", 145 | "type": "bytes32" 146 | }, 147 | { 148 | "internalType": "bytes32", 149 | "name": "s", 150 | "type": "bytes32" 151 | } 152 | ], 153 | "name": "selfPermitIfNecessary", 154 | "outputs": [], 155 | "stateMutability": "payable", 156 | "type": "function" 157 | } 158 | ], 159 | "bytecode": "0x", 160 | "deployedBytecode": "0x", 161 | "linkReferences": {}, 162 | "deployedLinkReferences": {} 163 | } 164 | -------------------------------------------------------------------------------- /utils/max_liquidity_for_amounts.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "math/big" 5 | 6 | "github.com/daoleno/uniswapv3-sdk/constants" 7 | ) 8 | 9 | /** 10 | * Returns an imprecise maximum amount of liquidity received for a given amount of token 0. 11 | * This function is available to accommodate LiquidityAmounts#getLiquidityForAmount0 in the v3 periphery, 12 | * which could be more precise by at least 32 bits by dividing by Q64 instead of Q96 in the intermediate step, 13 | * and shifting the subtracted ratio left by 32 bits. This imprecise calculation will likely be replaced in a future 14 | * v3 router contract. 15 | * @param sqrtRatioAX96 The price at the lower boundary 16 | * @param sqrtRatioBX96 The price at the upper boundary 17 | * @param amount0 The token0 amount 18 | * @returns liquidity for amount0, imprecise 19 | */ 20 | func maxLiquidityForAmount0Imprecise(sqrtRatioAX96, sqrtRatioBX96, amount0 *big.Int) *big.Int { 21 | if sqrtRatioAX96.Cmp(sqrtRatioBX96) > 0 { 22 | sqrtRatioAX96, sqrtRatioBX96 = sqrtRatioBX96, sqrtRatioAX96 23 | } 24 | intermediate := new(big.Int).Div(new(big.Int).Mul(sqrtRatioAX96, sqrtRatioBX96), constants.Q96) 25 | return new(big.Int).Div(new(big.Int).Mul(amount0, intermediate), new(big.Int).Sub(sqrtRatioBX96, sqrtRatioAX96)) 26 | } 27 | 28 | /** 29 | * Returns a precise maximum amount of liquidity received for a given amount of token 0 by dividing by Q64 instead of Q96 in the intermediate step, 30 | * and shifting the subtracted ratio left by 32 bits. 31 | * @param sqrtRatioAX96 The price at the lower boundary 32 | * @param sqrtRatioBX96 The price at the upper boundary 33 | * @param amount0 The token0 amount 34 | * @returns liquidity for amount0, precise 35 | */ 36 | func maxLiquidityForAmount0Precise(sqrtRatioAX96, sqrtRatioBX96, amount0 *big.Int) *big.Int { 37 | if sqrtRatioAX96.Cmp(sqrtRatioBX96) > 0 { 38 | sqrtRatioAX96, sqrtRatioBX96 = sqrtRatioBX96, sqrtRatioAX96 39 | } 40 | numerator := new(big.Int).Mul(new(big.Int).Mul(amount0, sqrtRatioAX96), sqrtRatioBX96) 41 | denominator := new(big.Int).Mul(constants.Q96, new(big.Int).Sub(sqrtRatioBX96, sqrtRatioAX96)) 42 | return new(big.Int).Div(numerator, denominator) 43 | } 44 | 45 | /** 46 | * Computes the maximum amount of liquidity received for a given amount of token1 47 | * @param sqrtRatioAX96 The price at the lower tick boundary 48 | * @param sqrtRatioBX96 The price at the upper tick boundary 49 | * @param amount1 The token1 amount 50 | * @returns liquidity for amount1 51 | */ 52 | func maxLiquidityForAmount1(sqrtRatioAX96, sqrtRatioBX96, amount1 *big.Int) *big.Int { 53 | if sqrtRatioAX96.Cmp(sqrtRatioBX96) > 0 { 54 | sqrtRatioAX96, sqrtRatioBX96 = sqrtRatioBX96, sqrtRatioAX96 55 | } 56 | return new(big.Int).Div(new(big.Int).Mul(amount1, constants.Q96), new(big.Int).Sub(sqrtRatioBX96, sqrtRatioAX96)) 57 | } 58 | 59 | /** 60 | * Computes the maximum amount of liquidity received for a given amount of token0, token1, 61 | * and the prices at the tick boundaries. 62 | * @param sqrtRatioCurrentX96 the current price 63 | * @param sqrtRatioAX96 price at lower boundary 64 | * @param sqrtRatioBX96 price at upper boundary 65 | * @param amount0 token0 amount 66 | * @param amount1 token1 amount 67 | * @param useFullPrecision if false, liquidity will be maximized according to what the router can calculate, 68 | * not what core can theoretically support 69 | */ 70 | func MaxLiquidityForAmounts(sqrtRatioCurrentX96 *big.Int, sqrtRatioAX96, sqrtRatioBX96 *big.Int, amount0, amount1 *big.Int, useFullPrecision bool) *big.Int { 71 | if sqrtRatioAX96.Cmp(sqrtRatioBX96) > 0 { 72 | sqrtRatioAX96, sqrtRatioBX96 = sqrtRatioBX96, sqrtRatioAX96 73 | } 74 | var maxLiquidityForAmount0 func(*big.Int, *big.Int, *big.Int) *big.Int 75 | if useFullPrecision { 76 | maxLiquidityForAmount0 = maxLiquidityForAmount0Precise 77 | } else { 78 | maxLiquidityForAmount0 = maxLiquidityForAmount0Imprecise 79 | } 80 | if sqrtRatioCurrentX96.Cmp(sqrtRatioAX96) <= 0 { 81 | return maxLiquidityForAmount0(sqrtRatioAX96, sqrtRatioBX96, amount0) 82 | } else if sqrtRatioCurrentX96.Cmp(sqrtRatioBX96) < 0 { 83 | liquidity0 := maxLiquidityForAmount0(sqrtRatioCurrentX96, sqrtRatioBX96, amount0) 84 | liquidity1 := maxLiquidityForAmount1(sqrtRatioAX96, sqrtRatioCurrentX96, amount1) 85 | if liquidity0.Cmp(liquidity1) < 0 { 86 | return liquidity0 87 | } 88 | return liquidity1 89 | 90 | } else { 91 | return maxLiquidityForAmount1(sqrtRatioAX96, sqrtRatioBX96, amount1) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /entities/ticklist.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | import ( 4 | "errors" 5 | "math" 6 | "math/big" 7 | ) 8 | 9 | var ( 10 | ErrZeroTickSpacing = errors.New("tick spacing must be greater than 0") 11 | ErrInvalidTickSpacing = errors.New("invalid tick spacing") 12 | ErrZeroNet = errors.New("tick net delta must be zero") 13 | ErrSorted = errors.New("ticks must be sorted") 14 | ) 15 | 16 | func ValidateList(ticks []Tick, tickSpacing int) error { 17 | if tickSpacing <= 0 { 18 | return ErrZeroTickSpacing 19 | } 20 | 21 | // ensure ticks are spaced appropriately 22 | for _, t := range ticks { 23 | if t.Index%tickSpacing != 0 { 24 | return ErrInvalidTickSpacing 25 | } 26 | } 27 | 28 | // ensure tick liquidity deltas sum to 0 29 | sum := big.NewInt(0) 30 | for _, tick := range ticks { 31 | sum.Add(sum, tick.LiquidityNet) 32 | } 33 | if sum.Cmp(big.NewInt(0)) != 0 { 34 | return ErrZeroNet 35 | } 36 | 37 | if !isTicksSorted(ticks) { 38 | return ErrSorted 39 | } 40 | 41 | return nil 42 | } 43 | 44 | func IsBelowSmallest(ticks []Tick, tick int) bool { 45 | if len(ticks) == 0 { 46 | panic("empty tick list") 47 | } 48 | return tick < ticks[0].Index 49 | } 50 | 51 | func IsAtOrAboveLargest(ticks []Tick, tick int) bool { 52 | if len(ticks) == 0 { 53 | panic("empty tick list") 54 | } 55 | return tick >= ticks[len(ticks)-1].Index 56 | } 57 | 58 | func GetTick(ticks []Tick, index int) Tick { 59 | tick := ticks[binarySearch(ticks, index)] 60 | if tick.Index != index { 61 | panic("index is not contained in ticks") 62 | } 63 | return tick 64 | } 65 | 66 | func NextInitializedTick(ticks []Tick, tick int, lte bool) Tick { 67 | if lte { 68 | if IsBelowSmallest(ticks, tick) { 69 | panic("below smallest") 70 | } 71 | if IsAtOrAboveLargest(ticks, tick) { 72 | return ticks[len(ticks)-1] 73 | } 74 | index := binarySearch(ticks, tick) 75 | return ticks[index] 76 | } else { 77 | if IsAtOrAboveLargest(ticks, tick) { 78 | panic("at or above largest") 79 | } 80 | if IsBelowSmallest(ticks, tick) { 81 | return ticks[0] 82 | } 83 | index := binarySearch(ticks, tick) 84 | return ticks[index+1] 85 | } 86 | } 87 | 88 | func NextInitializedTickWithinOneWord(ticks []Tick, tick int, lte bool, tickSpacing int) (int, bool) { 89 | compressed := math.Floor(float64(tick) / float64(tickSpacing)) // matches rounding in the code 90 | 91 | if lte { 92 | wordPos := int(compressed) >> 8 93 | minimum := (wordPos << 8) * tickSpacing 94 | if IsBelowSmallest(ticks, tick) { 95 | return minimum, false 96 | } 97 | index := NextInitializedTick(ticks, tick, lte).Index 98 | nextInitializedTick := math.Max(float64(minimum), float64(index)) 99 | return int(nextInitializedTick), int(nextInitializedTick) == index 100 | } else { 101 | wordPos := int(compressed+1) >> 8 102 | maximum := ((wordPos+1)<<8)*tickSpacing - 1 103 | if IsAtOrAboveLargest(ticks, tick) { 104 | return maximum, false 105 | } 106 | index := NextInitializedTick(ticks, tick, lte).Index 107 | nextInitializedTick := math.Min(float64(maximum), float64(index)) 108 | return int(nextInitializedTick), int(nextInitializedTick) == index 109 | } 110 | } 111 | 112 | // utils 113 | 114 | func isTicksSorted(ticks []Tick) bool { 115 | for i := 0; i < len(ticks)-1; i++ { 116 | if ticks[i].Index > ticks[i+1].Index { 117 | return false 118 | } 119 | } 120 | return true 121 | } 122 | 123 | /** 124 | * Finds the largest tick in the list of ticks that is less than or equal to tick 125 | * @param ticks list of ticks 126 | * @param tick tick to find the largest tick that is less than or equal to tick 127 | * @private 128 | */ 129 | func binarySearch(ticks []Tick, tick int) int { 130 | if IsBelowSmallest(ticks, tick) { 131 | panic("tick is below smallest tick") 132 | } 133 | 134 | // binary search 135 | start := 0 136 | end := len(ticks) - 1 137 | for start <= end { 138 | mid := (start + end) / 2 139 | if ticks[mid].Index == tick { 140 | return mid 141 | } else if ticks[mid].Index < tick { 142 | start = mid + 1 143 | } else { 144 | end = mid - 1 145 | } 146 | } 147 | 148 | // if we get here, we didn't find a tick that is less than or equal to tick 149 | // so we return the index of the tick that is closest to tick 150 | if ticks[start].Index < tick { 151 | return start 152 | } else { 153 | return start - 1 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /utils/price_tick_conversions_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "math/big" 6 | "strings" 7 | "testing" 8 | 9 | "github.com/daoleno/uniswap-sdk-core/entities" 10 | "github.com/ethereum/go-ethereum/common" 11 | "github.com/shopspring/decimal" 12 | ) 13 | 14 | func token(sortOrder, decimals, chainID uint) *entities.Token { 15 | if sortOrder > 9 || sortOrder%1 != 0 { 16 | panic("invalid sort order") 17 | } 18 | address := common.HexToAddress("0x" + strings.Repeat(fmt.Sprint(sortOrder), 40)) 19 | return entities.NewToken(chainID, address, decimals, fmt.Sprintf("T%d", sortOrder), fmt.Sprintf("token%d", sortOrder)) 20 | } 21 | 22 | var ( 23 | token0 = token(0, 18, 1) 24 | token1 = token(1, 18, 1) 25 | token2_6decimals = token(2, 6, 1) 26 | ) 27 | 28 | func TestTickToPrice(t *testing.T) { 29 | type args struct { 30 | baseToken *entities.Token 31 | quoteToken *entities.Token 32 | tick int 33 | } 34 | tests := []struct { 35 | name string 36 | args args 37 | wantSignificant string 38 | }{ 39 | {"1800 t0/1 t1", args{token1, token0, -74959}, "1800"}, 40 | {"1 t1/1800 t0", args{token0, token1, -74959}, "0.00055556"}, 41 | {"1800 t1/1 t0", args{token0, token1, 74959}, "1800"}, 42 | {"1 t0/1800 t1", args{token1, token0, 74959}, "0.00055556"}, 43 | 44 | // 12 decimal difference 45 | {"1.01 t2/1 t0", args{token1, token2_6decimals, -276225}, "1.01"}, 46 | {"1 t0/1.01 t2", args{token2_6decimals, token0, -276225}, "0.99015"}, 47 | {"1 t2/1.01 t0", args{token0, token2_6decimals, -276423}, "0.99015"}, 48 | {"1.01 t0/1 t2", args{token2_6decimals, token0, -276423}, "1.0099"}, 49 | {"1.01 t2/1 t0", args{token0, token2_6decimals, -276225}, "1.01"}, 50 | {"1 t0/1.01 t2", args{token2_6decimals, token0, -276225}, "0.99015"}, 51 | } 52 | for _, tt := range tests { 53 | t.Run(tt.name, func(t *testing.T) { 54 | got, err := TickToPrice(tt.args.baseToken, tt.args.quoteToken, tt.args.tick) 55 | if err != nil { 56 | t.Errorf("TickToPrice() error = %v", err) 57 | return 58 | } 59 | if got.ToSignificant(5) != tt.wantSignificant { 60 | t.Errorf("TickToPrice() = %v, want %v", got, tt.wantSignificant) 61 | } 62 | }) 63 | } 64 | } 65 | 66 | func TestPriceToClosestTick(t *testing.T) { 67 | tickToPriceNoError := func(baseToken *entities.Token, quoteToken *entities.Token, tick int) *entities.Price { 68 | p, err := TickToPrice(baseToken, quoteToken, tick) 69 | if err != nil { 70 | panic(err) 71 | } 72 | return p 73 | } 74 | type args struct { 75 | price *entities.Price 76 | baseToken *entities.Token 77 | quoteToken *entities.Token 78 | } 79 | B100e18 := decimal.NewFromBigInt(big.NewInt(100), 18).BigInt() 80 | 81 | tests := []struct { 82 | name string 83 | args args 84 | wantTick int 85 | }{ 86 | {"1800 t0/1 t1", args{entities.NewPrice(token1, token0, big.NewInt(1), big.NewInt(1800)), token1, token0}, -74960}, 87 | {"1 t1/1800 t0", args{entities.NewPrice(token0, token1, big.NewInt(1800), big.NewInt(1)), token0, token1}, -74960}, 88 | {"1.01 t2/1 t0", args{entities.NewPrice(token0, token2_6decimals, B100e18, big.NewInt(101e6)), token0, token2_6decimals}, -276225}, 89 | {"1 t0/1.01 t2", args{entities.NewPrice(token2_6decimals, token0, big.NewInt(101e6), B100e18), token2_6decimals, token0}, -276225}, 90 | 91 | // reciprocal with tickToPrice 92 | {"1800 t0/1 t1", args{tickToPriceNoError(token1, token0, -74960), token1, token0}, -74960}, 93 | {"1 t0/1800 t1", args{tickToPriceNoError(token1, token0, 74960), token1, token0}, 74960}, 94 | {"1 t1/1800 t0", args{tickToPriceNoError(token0, token1, -74960), token0, token1}, -74960}, 95 | {"1800 t1/1 t0", args{tickToPriceNoError(token0, token1, 74960), token0, token1}, 74960}, 96 | {"1.01 t2/1 t0", args{tickToPriceNoError(token0, token2_6decimals, -276225), token0, token2_6decimals}, -276225}, 97 | {"1 t0/1.01 t2", args{tickToPriceNoError(token2_6decimals, token0, -276225), token2_6decimals, token0}, -276225}, 98 | } 99 | for _, tt := range tests { 100 | t.Run(tt.name, func(t *testing.T) { 101 | got, err := PriceToClosestTick(tt.args.price, tt.args.baseToken, tt.args.quoteToken) 102 | if err != nil { 103 | t.Errorf("PriceToClosestTick() error = %v", err) 104 | return 105 | } 106 | if got != tt.wantTick { 107 | t.Errorf("PriceToClosestTick() = %v, want %v", got, tt.wantTick) 108 | } 109 | }) 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /examples/contract/uniswapv3_quoter.abi: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "inputs": [ 4 | { 5 | "internalType": "address", 6 | "name": "_factory", 7 | "type": "address" 8 | }, 9 | { 10 | "internalType": "address", 11 | "name": "_WETH9", 12 | "type": "address" 13 | } 14 | ], 15 | "stateMutability": "nonpayable", 16 | "type": "constructor" 17 | }, 18 | { 19 | "inputs": [], 20 | "name": "WETH9", 21 | "outputs": [ 22 | { 23 | "internalType": "address", 24 | "name": "", 25 | "type": "address" 26 | } 27 | ], 28 | "stateMutability": "view", 29 | "type": "function" 30 | }, 31 | { 32 | "inputs": [], 33 | "name": "factory", 34 | "outputs": [ 35 | { 36 | "internalType": "address", 37 | "name": "", 38 | "type": "address" 39 | } 40 | ], 41 | "stateMutability": "view", 42 | "type": "function" 43 | }, 44 | { 45 | "inputs": [ 46 | { 47 | "internalType": "bytes", 48 | "name": "path", 49 | "type": "bytes" 50 | }, 51 | { 52 | "internalType": "uint256", 53 | "name": "amountIn", 54 | "type": "uint256" 55 | } 56 | ], 57 | "name": "quoteExactInput", 58 | "outputs": [ 59 | { 60 | "internalType": "uint256", 61 | "name": "amountOut", 62 | "type": "uint256" 63 | } 64 | ], 65 | "stateMutability": "nonpayable", 66 | "type": "function" 67 | }, 68 | { 69 | "inputs": [ 70 | { 71 | "internalType": "address", 72 | "name": "tokenIn", 73 | "type": "address" 74 | }, 75 | { 76 | "internalType": "address", 77 | "name": "tokenOut", 78 | "type": "address" 79 | }, 80 | { 81 | "internalType": "uint24", 82 | "name": "fee", 83 | "type": "uint24" 84 | }, 85 | { 86 | "internalType": "uint256", 87 | "name": "amountIn", 88 | "type": "uint256" 89 | }, 90 | { 91 | "internalType": "uint160", 92 | "name": "sqrtPriceLimitX96", 93 | "type": "uint160" 94 | } 95 | ], 96 | "name": "quoteExactInputSingle", 97 | "outputs": [ 98 | { 99 | "internalType": "uint256", 100 | "name": "amountOut", 101 | "type": "uint256" 102 | } 103 | ], 104 | "stateMutability": "nonpayable", 105 | "type": "function" 106 | }, 107 | { 108 | "inputs": [ 109 | { 110 | "internalType": "bytes", 111 | "name": "path", 112 | "type": "bytes" 113 | }, 114 | { 115 | "internalType": "uint256", 116 | "name": "amountOut", 117 | "type": "uint256" 118 | } 119 | ], 120 | "name": "quoteExactOutput", 121 | "outputs": [ 122 | { 123 | "internalType": "uint256", 124 | "name": "amountIn", 125 | "type": "uint256" 126 | } 127 | ], 128 | "stateMutability": "nonpayable", 129 | "type": "function" 130 | }, 131 | { 132 | "inputs": [ 133 | { 134 | "internalType": "address", 135 | "name": "tokenIn", 136 | "type": "address" 137 | }, 138 | { 139 | "internalType": "address", 140 | "name": "tokenOut", 141 | "type": "address" 142 | }, 143 | { 144 | "internalType": "uint24", 145 | "name": "fee", 146 | "type": "uint24" 147 | }, 148 | { 149 | "internalType": "uint256", 150 | "name": "amountOut", 151 | "type": "uint256" 152 | }, 153 | { 154 | "internalType": "uint160", 155 | "name": "sqrtPriceLimitX96", 156 | "type": "uint160" 157 | } 158 | ], 159 | "name": "quoteExactOutputSingle", 160 | "outputs": [ 161 | { 162 | "internalType": "uint256", 163 | "name": "amountIn", 164 | "type": "uint256" 165 | } 166 | ], 167 | "stateMutability": "nonpayable", 168 | "type": "function" 169 | }, 170 | { 171 | "inputs": [ 172 | { 173 | "internalType": "int256", 174 | "name": "amount0Delta", 175 | "type": "int256" 176 | }, 177 | { 178 | "internalType": "int256", 179 | "name": "amount1Delta", 180 | "type": "int256" 181 | }, 182 | { 183 | "internalType": "bytes", 184 | "name": "path", 185 | "type": "bytes" 186 | } 187 | ], 188 | "name": "uniswapV3SwapCallback", 189 | "outputs": [], 190 | "stateMutability": "view", 191 | "type": "function" 192 | } 193 | ] -------------------------------------------------------------------------------- /utils/sqrtprice_math.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "errors" 5 | "math/big" 6 | 7 | "github.com/daoleno/uniswap-sdk-core/entities" 8 | "github.com/daoleno/uniswapv3-sdk/constants" 9 | ) 10 | 11 | var ( 12 | ErrSqrtPriceLessThanZero = errors.New("sqrt price less than zero") 13 | ErrLiquidityLessThanZero = errors.New("liquidity less than zero") 14 | ErrInvariant = errors.New("invariant violation") 15 | ) 16 | var MaxUint160 = new(big.Int).Sub(new(big.Int).Exp(big.NewInt(2), big.NewInt(160), nil), constants.One) 17 | 18 | func multiplyIn256(x, y *big.Int) *big.Int { 19 | product := new(big.Int).Mul(x, y) 20 | return new(big.Int).And(product, entities.MaxUint256) 21 | } 22 | 23 | func addIn256(x, y *big.Int) *big.Int { 24 | sum := new(big.Int).Add(x, y) 25 | return new(big.Int).And(sum, entities.MaxUint256) 26 | } 27 | 28 | func GetAmount0Delta(sqrtRatioAX96, sqrtRatioBX96, liquidity *big.Int, roundUp bool) *big.Int { 29 | if sqrtRatioAX96.Cmp(sqrtRatioBX96) >= 0 { 30 | sqrtRatioAX96, sqrtRatioBX96 = sqrtRatioBX96, sqrtRatioAX96 31 | } 32 | 33 | numerator1 := new(big.Int).Lsh(liquidity, 96) 34 | numerator2 := new(big.Int).Sub(sqrtRatioBX96, sqrtRatioAX96) 35 | 36 | if roundUp { 37 | return MulDivRoundingUp(MulDivRoundingUp(numerator1, numerator2, sqrtRatioBX96), constants.One, sqrtRatioAX96) 38 | } 39 | return new(big.Int).Div(new(big.Int).Div(new(big.Int).Mul(numerator1, numerator2), sqrtRatioBX96), sqrtRatioAX96) 40 | } 41 | 42 | func GetAmount1Delta(sqrtRatioAX96, sqrtRatioBX96, liquidity *big.Int, roundUp bool) *big.Int { 43 | if sqrtRatioAX96.Cmp(sqrtRatioBX96) >= 0 { 44 | sqrtRatioAX96, sqrtRatioBX96 = sqrtRatioBX96, sqrtRatioAX96 45 | } 46 | 47 | if roundUp { 48 | return MulDivRoundingUp(liquidity, new(big.Int).Sub(sqrtRatioBX96, sqrtRatioAX96), constants.Q96) 49 | } 50 | return new(big.Int).Div(new(big.Int).Mul(liquidity, new(big.Int).Sub(sqrtRatioBX96, sqrtRatioAX96)), constants.Q96) 51 | } 52 | 53 | func GetNextSqrtPriceFromInput(sqrtPX96, liquidity, amountIn *big.Int, zeroForOne bool) (*big.Int, error) { 54 | if sqrtPX96.Cmp(constants.Zero) <= 0 { 55 | return nil, ErrSqrtPriceLessThanZero 56 | } 57 | if liquidity.Cmp(constants.Zero) <= 0 { 58 | return nil, ErrLiquidityLessThanZero 59 | } 60 | if zeroForOne { 61 | return getNextSqrtPriceFromAmount0RoundingUp(sqrtPX96, liquidity, amountIn, true) 62 | } 63 | return getNextSqrtPriceFromAmount1RoundingDown(sqrtPX96, liquidity, amountIn, true) 64 | } 65 | 66 | func GetNextSqrtPriceFromOutput(sqrtPX96, liquidity, amountOut *big.Int, zeroForOne bool) (*big.Int, error) { 67 | if sqrtPX96.Cmp(constants.Zero) <= 0 { 68 | return nil, ErrSqrtPriceLessThanZero 69 | } 70 | if liquidity.Cmp(constants.Zero) <= 0 { 71 | return nil, ErrLiquidityLessThanZero 72 | } 73 | if zeroForOne { 74 | return getNextSqrtPriceFromAmount1RoundingDown(sqrtPX96, liquidity, amountOut, false) 75 | } 76 | return getNextSqrtPriceFromAmount0RoundingUp(sqrtPX96, liquidity, amountOut, false) 77 | } 78 | 79 | func getNextSqrtPriceFromAmount0RoundingUp(sqrtPX96, liquidity, amount *big.Int, add bool) (*big.Int, error) { 80 | if amount.Cmp(constants.Zero) == 0 { 81 | return sqrtPX96, nil 82 | } 83 | 84 | numerator1 := new(big.Int).Lsh(liquidity, 96) 85 | if add { 86 | product := multiplyIn256(amount, sqrtPX96) 87 | if new(big.Int).Div(product, amount).Cmp(sqrtPX96) == 0 { 88 | denominator := addIn256(numerator1, product) 89 | if denominator.Cmp(numerator1) >= 0 { 90 | return MulDivRoundingUp(numerator1, sqrtPX96, denominator), nil 91 | } 92 | } 93 | return MulDivRoundingUp(numerator1, constants.One, new(big.Int).Add(new(big.Int).Div(numerator1, sqrtPX96), amount)), nil 94 | } else { 95 | product := multiplyIn256(amount, sqrtPX96) 96 | if new(big.Int).Div(product, amount).Cmp(sqrtPX96) != 0 { 97 | return nil, ErrInvariant 98 | } 99 | if numerator1.Cmp(product) <= 0 { 100 | return nil, ErrInvariant 101 | } 102 | denominator := new(big.Int).Sub(numerator1, product) 103 | return MulDivRoundingUp(numerator1, sqrtPX96, denominator), nil 104 | } 105 | } 106 | 107 | func getNextSqrtPriceFromAmount1RoundingDown(sqrtPX96, liquidity, amount *big.Int, add bool) (*big.Int, error) { 108 | if add { 109 | var quotient *big.Int 110 | if amount.Cmp(MaxUint160) <= 0 { 111 | quotient = new(big.Int).Div(new(big.Int).Lsh(amount, 96), liquidity) 112 | } else { 113 | quotient = new(big.Int).Div(new(big.Int).Mul(amount, constants.Q96), liquidity) 114 | } 115 | return new(big.Int).Add(sqrtPX96, quotient), nil 116 | } 117 | 118 | quotient := MulDivRoundingUp(amount, constants.Q96, liquidity) 119 | if sqrtPX96.Cmp(quotient) <= 0 { 120 | return nil, ErrInvariant 121 | } 122 | return new(big.Int).Sub(sqrtPX96, quotient), nil 123 | } 124 | -------------------------------------------------------------------------------- /periphery/quoter.go: -------------------------------------------------------------------------------- 1 | package periphery 2 | 3 | import ( 4 | _ "embed" 5 | "errors" 6 | "fmt" 7 | "math/big" 8 | "reflect" 9 | 10 | core "github.com/daoleno/uniswap-sdk-core/entities" 11 | "github.com/daoleno/uniswapv3-sdk/entities" 12 | "github.com/daoleno/uniswapv3-sdk/utils" 13 | "github.com/ethereum/go-ethereum/common" 14 | ) 15 | 16 | //go:embed contracts/lens/Quoter.sol/Quoter.json 17 | var quoterABI []byte 18 | 19 | var ErrMultihopPriceLimit = errors.New("MULTIHOP_PRICE_LIMIT") 20 | 21 | // Optional arguments to send to the quoter. 22 | type QuoteOptions struct { 23 | SqrtPriceLimitX96 *big.Int // The optional price limit for the trade. 24 | } 25 | 26 | /** 27 | * Represents the Uniswap V3 QuoterV1 contract with a method for returning the formatted 28 | * calldata needed to call the quoter contract. 29 | */ 30 | 31 | /** 32 | * Produces the on-chain method name of the appropriate function within QuoterV2, 33 | * and the relevant hex encoded parameters. 34 | * @template TInput The input token, either Ether or an ERC-20 35 | * @template TOutput The output token, either Ether or an ERC-20 36 | * @param route The swap route, a list of pools through which a swap can occur 37 | * @param amount The amount of the quote, either an amount in, or an amount out 38 | * @param tradeType The trade type, either exact input or exact output 39 | * @returns The formatted calldata 40 | */ 41 | func QuoteCallParameters( 42 | route *entities.Route, 43 | amount *core.CurrencyAmount, 44 | tradeType core.TradeType, 45 | options *QuoteOptions, 46 | ) (*utils.MethodParameters, error) { 47 | singleHop := len(route.Pools) == 1 48 | quoteAmount := amount.Quotient() 49 | abi := GetABI(quoterABI) 50 | var ( 51 | calldata []byte 52 | err error 53 | ) 54 | sqrtPriceLimitX96 := big.NewInt(0) 55 | if options != nil { 56 | sqrtPriceLimitX96 = options.SqrtPriceLimitX96 57 | } 58 | 59 | if singleHop { 60 | if tradeType == core.ExactInput { 61 | calldata, err = abi.Pack("quoteExactInputSingle", route.TokenPath[0].Address, route.TokenPath[1].Address, big.NewInt(int64(route.Pools[0].Fee)), quoteAmount, sqrtPriceLimitX96) 62 | } else { 63 | calldata, err = abi.Pack("quoteExactOutputSingle", route.TokenPath[0].Address, route.TokenPath[1].Address, big.NewInt(int64(route.Pools[0].Fee)), quoteAmount, sqrtPriceLimitX96) 64 | } 65 | if err != nil { 66 | return nil, err 67 | } 68 | } else { 69 | if options != nil && sqrtPriceLimitX96 != big.NewInt(0) { 70 | return nil, ErrMultihopPriceLimit 71 | } 72 | path, err := EncodeRouteToPath(route, tradeType == core.ExactOutput) 73 | if err != nil { 74 | return nil, err 75 | } 76 | if tradeType == core.ExactInput { 77 | calldata, err = abi.Pack("quoteExactInput", path, quoteAmount) 78 | } else { 79 | calldata, err = abi.Pack("quoteExactOutput", path, quoteAmount) 80 | } 81 | if err != nil { 82 | return nil, err 83 | } 84 | } 85 | return &utils.MethodParameters{ 86 | Calldata: calldata, 87 | Value: big.NewInt(0), 88 | }, nil 89 | } 90 | 91 | /** 92 | * Converts a route to a hex encoded path 93 | * @param route the v3 path to convert to an encoded path 94 | * @param exactOutput whether the route should be encoded in reverse, for making exact output swaps 95 | */ 96 | func EncodeRouteToPath(route *entities.Route, exactOutput bool) ([]byte, error) { 97 | var ( 98 | inputToken = route.Input.Wrapped() 99 | 100 | types []string 101 | path []interface{} 102 | 103 | addressTy = "address" 104 | uint24Ty = "uint24" 105 | ) 106 | 107 | for i, pool := range route.Pools { 108 | var outputToken *core.Token 109 | if pool.Token0.Equal(inputToken) { 110 | outputToken = pool.Token1 111 | } else { 112 | outputToken = pool.Token0 113 | } 114 | if i == 0 { 115 | types = []string{addressTy, uint24Ty, addressTy} 116 | path = []interface{}{inputToken.Address, uint64(pool.Fee), outputToken.Address} 117 | } else { 118 | types = append(types, uint24Ty, addressTy) 119 | path = append(path, uint64(pool.Fee), outputToken.Address) 120 | } 121 | inputToken = outputToken 122 | } 123 | 124 | if exactOutput { 125 | reverse(types) 126 | reverse(path) 127 | } 128 | 129 | // tight pack the path 130 | var packedPath [][]byte 131 | for i, t := range types { 132 | switch t { 133 | case addressTy: 134 | packedPath = append(packedPath, path[i].(common.Address).Bytes()) 135 | case uint24Ty: 136 | packedPath = append(packedPath, common.LeftPadBytes(PutUint24(path[i].(uint64)), 24/8)) 137 | default: 138 | return nil, fmt.Errorf("unknown type %s", t) 139 | } 140 | 141 | } 142 | 143 | var pack []byte 144 | for _, p := range packedPath { 145 | pack = append(pack, p...) 146 | } 147 | return pack, nil 148 | } 149 | 150 | // PutUint24 put bigendian uint24 151 | func PutUint24(i uint64) []byte { 152 | b := make([]byte, 3) 153 | b[0] = byte(i >> 16) 154 | b[1] = byte(i >> 8) 155 | b[2] = byte(i) 156 | return b 157 | } 158 | 159 | func reverse(s interface{}) { 160 | n := reflect.ValueOf(s).Len() 161 | swap := reflect.Swapper(s) 162 | for i, j := 0, n-1; i < j; i, j = i+1, j-1 { 163 | swap(i, j) 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /periphery/contracts/interfaces/external/IWETH9.sol/IWETH9.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "IWETH9", 4 | "sourceName": "contracts/interfaces/external/IWETH9.sol", 5 | "abi": [ 6 | { 7 | "anonymous": false, 8 | "inputs": [ 9 | { 10 | "indexed": true, 11 | "internalType": "address", 12 | "name": "owner", 13 | "type": "address" 14 | }, 15 | { 16 | "indexed": true, 17 | "internalType": "address", 18 | "name": "spender", 19 | "type": "address" 20 | }, 21 | { 22 | "indexed": false, 23 | "internalType": "uint256", 24 | "name": "value", 25 | "type": "uint256" 26 | } 27 | ], 28 | "name": "Approval", 29 | "type": "event" 30 | }, 31 | { 32 | "anonymous": false, 33 | "inputs": [ 34 | { 35 | "indexed": true, 36 | "internalType": "address", 37 | "name": "from", 38 | "type": "address" 39 | }, 40 | { 41 | "indexed": true, 42 | "internalType": "address", 43 | "name": "to", 44 | "type": "address" 45 | }, 46 | { 47 | "indexed": false, 48 | "internalType": "uint256", 49 | "name": "value", 50 | "type": "uint256" 51 | } 52 | ], 53 | "name": "Transfer", 54 | "type": "event" 55 | }, 56 | { 57 | "inputs": [ 58 | { 59 | "internalType": "address", 60 | "name": "owner", 61 | "type": "address" 62 | }, 63 | { 64 | "internalType": "address", 65 | "name": "spender", 66 | "type": "address" 67 | } 68 | ], 69 | "name": "allowance", 70 | "outputs": [ 71 | { 72 | "internalType": "uint256", 73 | "name": "", 74 | "type": "uint256" 75 | } 76 | ], 77 | "stateMutability": "view", 78 | "type": "function" 79 | }, 80 | { 81 | "inputs": [ 82 | { 83 | "internalType": "address", 84 | "name": "spender", 85 | "type": "address" 86 | }, 87 | { 88 | "internalType": "uint256", 89 | "name": "amount", 90 | "type": "uint256" 91 | } 92 | ], 93 | "name": "approve", 94 | "outputs": [ 95 | { 96 | "internalType": "bool", 97 | "name": "", 98 | "type": "bool" 99 | } 100 | ], 101 | "stateMutability": "nonpayable", 102 | "type": "function" 103 | }, 104 | { 105 | "inputs": [ 106 | { 107 | "internalType": "address", 108 | "name": "account", 109 | "type": "address" 110 | } 111 | ], 112 | "name": "balanceOf", 113 | "outputs": [ 114 | { 115 | "internalType": "uint256", 116 | "name": "", 117 | "type": "uint256" 118 | } 119 | ], 120 | "stateMutability": "view", 121 | "type": "function" 122 | }, 123 | { 124 | "inputs": [], 125 | "name": "deposit", 126 | "outputs": [], 127 | "stateMutability": "payable", 128 | "type": "function" 129 | }, 130 | { 131 | "inputs": [], 132 | "name": "totalSupply", 133 | "outputs": [ 134 | { 135 | "internalType": "uint256", 136 | "name": "", 137 | "type": "uint256" 138 | } 139 | ], 140 | "stateMutability": "view", 141 | "type": "function" 142 | }, 143 | { 144 | "inputs": [ 145 | { 146 | "internalType": "address", 147 | "name": "recipient", 148 | "type": "address" 149 | }, 150 | { 151 | "internalType": "uint256", 152 | "name": "amount", 153 | "type": "uint256" 154 | } 155 | ], 156 | "name": "transfer", 157 | "outputs": [ 158 | { 159 | "internalType": "bool", 160 | "name": "", 161 | "type": "bool" 162 | } 163 | ], 164 | "stateMutability": "nonpayable", 165 | "type": "function" 166 | }, 167 | { 168 | "inputs": [ 169 | { 170 | "internalType": "address", 171 | "name": "sender", 172 | "type": "address" 173 | }, 174 | { 175 | "internalType": "address", 176 | "name": "recipient", 177 | "type": "address" 178 | }, 179 | { 180 | "internalType": "uint256", 181 | "name": "amount", 182 | "type": "uint256" 183 | } 184 | ], 185 | "name": "transferFrom", 186 | "outputs": [ 187 | { 188 | "internalType": "bool", 189 | "name": "", 190 | "type": "bool" 191 | } 192 | ], 193 | "stateMutability": "nonpayable", 194 | "type": "function" 195 | }, 196 | { 197 | "inputs": [ 198 | { 199 | "internalType": "uint256", 200 | "name": "", 201 | "type": "uint256" 202 | } 203 | ], 204 | "name": "withdraw", 205 | "outputs": [], 206 | "stateMutability": "nonpayable", 207 | "type": "function" 208 | } 209 | ], 210 | "bytecode": "0x", 211 | "deployedBytecode": "0x", 212 | "linkReferences": {}, 213 | "deployedLinkReferences": {} 214 | } 215 | -------------------------------------------------------------------------------- /periphery/contracts/interfaces/IERC20Metadata.sol/IERC20Metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "IERC20Metadata", 4 | "sourceName": "contracts/interfaces/IERC20Metadata.sol", 5 | "abi": [ 6 | { 7 | "anonymous": false, 8 | "inputs": [ 9 | { 10 | "indexed": true, 11 | "internalType": "address", 12 | "name": "owner", 13 | "type": "address" 14 | }, 15 | { 16 | "indexed": true, 17 | "internalType": "address", 18 | "name": "spender", 19 | "type": "address" 20 | }, 21 | { 22 | "indexed": false, 23 | "internalType": "uint256", 24 | "name": "value", 25 | "type": "uint256" 26 | } 27 | ], 28 | "name": "Approval", 29 | "type": "event" 30 | }, 31 | { 32 | "anonymous": false, 33 | "inputs": [ 34 | { 35 | "indexed": true, 36 | "internalType": "address", 37 | "name": "from", 38 | "type": "address" 39 | }, 40 | { 41 | "indexed": true, 42 | "internalType": "address", 43 | "name": "to", 44 | "type": "address" 45 | }, 46 | { 47 | "indexed": false, 48 | "internalType": "uint256", 49 | "name": "value", 50 | "type": "uint256" 51 | } 52 | ], 53 | "name": "Transfer", 54 | "type": "event" 55 | }, 56 | { 57 | "inputs": [ 58 | { 59 | "internalType": "address", 60 | "name": "owner", 61 | "type": "address" 62 | }, 63 | { 64 | "internalType": "address", 65 | "name": "spender", 66 | "type": "address" 67 | } 68 | ], 69 | "name": "allowance", 70 | "outputs": [ 71 | { 72 | "internalType": "uint256", 73 | "name": "", 74 | "type": "uint256" 75 | } 76 | ], 77 | "stateMutability": "view", 78 | "type": "function" 79 | }, 80 | { 81 | "inputs": [ 82 | { 83 | "internalType": "address", 84 | "name": "spender", 85 | "type": "address" 86 | }, 87 | { 88 | "internalType": "uint256", 89 | "name": "amount", 90 | "type": "uint256" 91 | } 92 | ], 93 | "name": "approve", 94 | "outputs": [ 95 | { 96 | "internalType": "bool", 97 | "name": "", 98 | "type": "bool" 99 | } 100 | ], 101 | "stateMutability": "nonpayable", 102 | "type": "function" 103 | }, 104 | { 105 | "inputs": [ 106 | { 107 | "internalType": "address", 108 | "name": "account", 109 | "type": "address" 110 | } 111 | ], 112 | "name": "balanceOf", 113 | "outputs": [ 114 | { 115 | "internalType": "uint256", 116 | "name": "", 117 | "type": "uint256" 118 | } 119 | ], 120 | "stateMutability": "view", 121 | "type": "function" 122 | }, 123 | { 124 | "inputs": [], 125 | "name": "decimals", 126 | "outputs": [ 127 | { 128 | "internalType": "uint8", 129 | "name": "", 130 | "type": "uint8" 131 | } 132 | ], 133 | "stateMutability": "view", 134 | "type": "function" 135 | }, 136 | { 137 | "inputs": [], 138 | "name": "name", 139 | "outputs": [ 140 | { 141 | "internalType": "string", 142 | "name": "", 143 | "type": "string" 144 | } 145 | ], 146 | "stateMutability": "view", 147 | "type": "function" 148 | }, 149 | { 150 | "inputs": [], 151 | "name": "symbol", 152 | "outputs": [ 153 | { 154 | "internalType": "string", 155 | "name": "", 156 | "type": "string" 157 | } 158 | ], 159 | "stateMutability": "view", 160 | "type": "function" 161 | }, 162 | { 163 | "inputs": [], 164 | "name": "totalSupply", 165 | "outputs": [ 166 | { 167 | "internalType": "uint256", 168 | "name": "", 169 | "type": "uint256" 170 | } 171 | ], 172 | "stateMutability": "view", 173 | "type": "function" 174 | }, 175 | { 176 | "inputs": [ 177 | { 178 | "internalType": "address", 179 | "name": "recipient", 180 | "type": "address" 181 | }, 182 | { 183 | "internalType": "uint256", 184 | "name": "amount", 185 | "type": "uint256" 186 | } 187 | ], 188 | "name": "transfer", 189 | "outputs": [ 190 | { 191 | "internalType": "bool", 192 | "name": "", 193 | "type": "bool" 194 | } 195 | ], 196 | "stateMutability": "nonpayable", 197 | "type": "function" 198 | }, 199 | { 200 | "inputs": [ 201 | { 202 | "internalType": "address", 203 | "name": "sender", 204 | "type": "address" 205 | }, 206 | { 207 | "internalType": "address", 208 | "name": "recipient", 209 | "type": "address" 210 | }, 211 | { 212 | "internalType": "uint256", 213 | "name": "amount", 214 | "type": "uint256" 215 | } 216 | ], 217 | "name": "transferFrom", 218 | "outputs": [ 219 | { 220 | "internalType": "bool", 221 | "name": "", 222 | "type": "bool" 223 | } 224 | ], 225 | "stateMutability": "nonpayable", 226 | "type": "function" 227 | } 228 | ], 229 | "bytecode": "0x", 230 | "deployedBytecode": "0x", 231 | "linkReferences": {}, 232 | "deployedLinkReferences": {} 233 | } 234 | -------------------------------------------------------------------------------- /examples/liquidity/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "math/big" 6 | "os" 7 | "strings" 8 | "time" 9 | 10 | coreEntities "github.com/daoleno/uniswap-sdk-core/entities" 11 | "github.com/daoleno/uniswapv3-sdk/constants" 12 | "github.com/daoleno/uniswapv3-sdk/entities" 13 | "github.com/daoleno/uniswapv3-sdk/examples/contract" 14 | "github.com/daoleno/uniswapv3-sdk/examples/helper" 15 | "github.com/daoleno/uniswapv3-sdk/periphery" 16 | "github.com/ethereum/go-ethereum/accounts/abi" 17 | "github.com/ethereum/go-ethereum/common" 18 | "github.com/ethereum/go-ethereum/ethclient" 19 | ) 20 | 21 | //mint a new liquidity 22 | func mintOrAdd(client *ethclient.Client, wallet *helper.Wallet, tokenID *big.Int) { 23 | log.SetFlags(log.Lshortfile | log.LstdFlags) 24 | 25 | pool, err := helper.ConstructV3Pool(client, helper.WMATIC, helper.AMP, uint64(constants.FeeMedium)) 26 | if err != nil { 27 | log.Fatal(err) 28 | } 29 | 30 | //0.1 MATIC 31 | amount0 := helper.IntWithDecimal(1, 17) 32 | amount1 := helper.FloatStringToBigInt("5", 18) 33 | pos, err := entities.FromAmounts(pool, -43260, 29400, amount0, amount1, false) 34 | if err != nil { 35 | log.Fatal(err) 36 | } 37 | if err != nil { 38 | log.Fatal(err) 39 | } 40 | onePercent := coreEntities.NewPercent(big.NewInt(1), big.NewInt(100)) 41 | log.Println(pos.MintAmountsWithSlippage(onePercent)) 42 | 43 | d := time.Now().Add(time.Minute * time.Duration(15)).Unix() 44 | deadline := big.NewInt(d) 45 | 46 | var opts *periphery.AddLiquidityOptions 47 | if tokenID == nil { 48 | //mint a new liquidity position 49 | opts = &periphery.AddLiquidityOptions{ 50 | CommonAddLiquidityOptions: &periphery.CommonAddLiquidityOptions{ 51 | SlippageTolerance: onePercent, 52 | Deadline: deadline, 53 | }, 54 | MintSpecificOptions: &periphery.MintSpecificOptions{ 55 | Recipient: wallet.PublicKey, 56 | CreatePool: true, 57 | }, 58 | } 59 | } else { 60 | //add liquidity to an existing pool 61 | opts = &periphery.AddLiquidityOptions{ 62 | IncreaseSpecificOptions: &periphery.IncreaseSpecificOptions{ 63 | TokenID: tokenID, 64 | }, 65 | CommonAddLiquidityOptions: &periphery.CommonAddLiquidityOptions{ 66 | SlippageTolerance: onePercent, 67 | Deadline: deadline, 68 | }, 69 | } 70 | 71 | } 72 | params, err := periphery.AddCallParameters(pos, opts) 73 | if err != nil { 74 | log.Fatal(err) 75 | } 76 | log.Printf("0x%x value=%s\n", params.Calldata, params.Value.String()) 77 | 78 | //matic is a native token, so we need to set the actually value to transfer 79 | tx, err := helper.TryTX(client, common.HexToAddress(helper.ContractV3NFTPositionManager), 80 | amount0, params.Calldata, wallet) 81 | if err != nil { 82 | log.Fatal(err) 83 | } 84 | log.Println(tx.Hash().String()) 85 | } 86 | 87 | func remove(client *ethclient.Client, wallet *helper.Wallet, tokenID *big.Int) { 88 | //our pool is the fee medium pool 89 | pool, err := helper.ConstructV3Pool(client, helper.WMATIC, helper.AMP, uint64(constants.FeeMedium)) 90 | if err != nil { 91 | log.Fatal(err) 92 | } 93 | log.Println("liquidity= ", pool.Liquidity) 94 | 95 | posManager, err := contract.NewUniswapv3NFTPositionManager(common.HexToAddress(helper.ContractV3NFTPositionManager), client) 96 | if err != nil { 97 | log.Fatal(err) 98 | } 99 | contractPos, err := posManager.Positions(nil, tokenID) 100 | if err != nil { 101 | log.Fatal(err) 102 | } 103 | percent25 := coreEntities.NewPercent(big.NewInt(1), big.NewInt(25)) 104 | fullPercent := coreEntities.NewPercent(contractPos.Liquidity, big.NewInt(1)) 105 | removingLiquidity := fullPercent.Multiply(percent25) 106 | 107 | pos, err := entities.NewPosition(pool, removingLiquidity.Quotient(), 108 | int(contractPos.TickLower.Int64()), 109 | int(contractPos.TickUpper.Int64()), 110 | ) 111 | if err != nil { 112 | log.Fatal(err) 113 | } 114 | 115 | d := time.Now().Add(time.Minute * time.Duration(15)).Unix() 116 | deadline := big.NewInt(d) 117 | opts := &periphery.RemoveLiquidityOptions{ 118 | TokenID: tokenID, 119 | LiquidityPercentage: percent25, 120 | SlippageTolerance: coreEntities.NewPercent(big.NewInt(1), big.NewInt(100)), //%1 , 121 | Deadline: deadline, 122 | CollectOptions: &periphery.CollectOptions{ 123 | ExpectedCurrencyOwed0: coreEntities.FromRawAmount(helper.AMP, big.NewInt(0)), 124 | ExpectedCurrencyOwed1: coreEntities.FromRawAmount(helper.WMATIC, big.NewInt(0)), 125 | Recipient: wallet.PublicKey, 126 | }, 127 | } 128 | params, err := periphery.RemoveCallParameters(pos, opts) 129 | if err != nil { 130 | log.Fatal(err) 131 | } 132 | 133 | //matic is a native token, so we need to set the actually value to transfer 134 | tx, err := helper.TryTX(client, common.HexToAddress(helper.ContractV3NFTPositionManager), 135 | big.NewInt(0), params.Calldata, wallet) 136 | if err != nil { 137 | log.Fatal(err) 138 | } 139 | log.Println(tx.Hash().String()) 140 | } 141 | 142 | func burn(client *ethclient.Client, wallet *helper.Wallet, tokenID *big.Int) { 143 | ABI, _ := abi.JSON(strings.NewReader(contract.Uniswapv3NFTPositionManagerABI)) 144 | out, err := ABI.Pack("burn", tokenID) 145 | if err != nil { 146 | log.Fatal(err) 147 | } 148 | 149 | //matic is a native token, so we need to set the actually value to transfer 150 | tx, err := helper.TryTX(client, common.HexToAddress(helper.ContractV3NFTPositionManager), 151 | big.NewInt(0), out, wallet) 152 | if err != nil { 153 | log.Fatal(err) 154 | } 155 | log.Println(tx.Hash().String()) 156 | } 157 | 158 | func main() { 159 | client, err := ethclient.Dial(helper.PolygonRPC) 160 | if err != nil { 161 | log.Fatal(err) 162 | } 163 | wallet := helper.InitWallet(os.Getenv("MY_PRIVATE_KEY")) 164 | if wallet == nil { 165 | log.Fatal("init wallet failed") 166 | } 167 | _ = client 168 | _ = wallet 169 | //mintOrAdd(client, wallet) //it will create a new NFT ID 170 | //remove(client, wallet, nftTokenID) 171 | //burn(client, wallet, nftTokenID) //remove the liquidity 172 | } 173 | -------------------------------------------------------------------------------- /periphery/contracts/interfaces/IQuoterV2.sol/IQuoterV2.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "IQuoterV2", 4 | "sourceName": "contracts/interfaces/IQuoterV2.sol", 5 | "abi": [ 6 | { 7 | "inputs": [ 8 | { 9 | "internalType": "bytes", 10 | "name": "path", 11 | "type": "bytes" 12 | }, 13 | { 14 | "internalType": "uint256", 15 | "name": "amountIn", 16 | "type": "uint256" 17 | } 18 | ], 19 | "name": "quoteExactInput", 20 | "outputs": [ 21 | { 22 | "internalType": "uint256", 23 | "name": "amountOut", 24 | "type": "uint256" 25 | }, 26 | { 27 | "internalType": "uint160[]", 28 | "name": "sqrtPriceX96AfterList", 29 | "type": "uint160[]" 30 | }, 31 | { 32 | "internalType": "uint32[]", 33 | "name": "initializedTicksCrossedList", 34 | "type": "uint32[]" 35 | }, 36 | { 37 | "internalType": "uint256", 38 | "name": "gasEstimate", 39 | "type": "uint256" 40 | } 41 | ], 42 | "stateMutability": "nonpayable", 43 | "type": "function" 44 | }, 45 | { 46 | "inputs": [ 47 | { 48 | "components": [ 49 | { 50 | "internalType": "address", 51 | "name": "tokenIn", 52 | "type": "address" 53 | }, 54 | { 55 | "internalType": "address", 56 | "name": "tokenOut", 57 | "type": "address" 58 | }, 59 | { 60 | "internalType": "uint256", 61 | "name": "amountIn", 62 | "type": "uint256" 63 | }, 64 | { 65 | "internalType": "uint24", 66 | "name": "fee", 67 | "type": "uint24" 68 | }, 69 | { 70 | "internalType": "uint160", 71 | "name": "sqrtPriceLimitX96", 72 | "type": "uint160" 73 | } 74 | ], 75 | "internalType": "struct IQuoterV2.QuoteExactInputSingleParams", 76 | "name": "params", 77 | "type": "tuple" 78 | } 79 | ], 80 | "name": "quoteExactInputSingle", 81 | "outputs": [ 82 | { 83 | "internalType": "uint256", 84 | "name": "amountOut", 85 | "type": "uint256" 86 | }, 87 | { 88 | "internalType": "uint160", 89 | "name": "sqrtPriceX96After", 90 | "type": "uint160" 91 | }, 92 | { 93 | "internalType": "uint32", 94 | "name": "initializedTicksCrossed", 95 | "type": "uint32" 96 | }, 97 | { 98 | "internalType": "uint256", 99 | "name": "gasEstimate", 100 | "type": "uint256" 101 | } 102 | ], 103 | "stateMutability": "nonpayable", 104 | "type": "function" 105 | }, 106 | { 107 | "inputs": [ 108 | { 109 | "internalType": "bytes", 110 | "name": "path", 111 | "type": "bytes" 112 | }, 113 | { 114 | "internalType": "uint256", 115 | "name": "amountOut", 116 | "type": "uint256" 117 | } 118 | ], 119 | "name": "quoteExactOutput", 120 | "outputs": [ 121 | { 122 | "internalType": "uint256", 123 | "name": "amountIn", 124 | "type": "uint256" 125 | }, 126 | { 127 | "internalType": "uint160[]", 128 | "name": "sqrtPriceX96AfterList", 129 | "type": "uint160[]" 130 | }, 131 | { 132 | "internalType": "uint32[]", 133 | "name": "initializedTicksCrossedList", 134 | "type": "uint32[]" 135 | }, 136 | { 137 | "internalType": "uint256", 138 | "name": "gasEstimate", 139 | "type": "uint256" 140 | } 141 | ], 142 | "stateMutability": "nonpayable", 143 | "type": "function" 144 | }, 145 | { 146 | "inputs": [ 147 | { 148 | "components": [ 149 | { 150 | "internalType": "address", 151 | "name": "tokenIn", 152 | "type": "address" 153 | }, 154 | { 155 | "internalType": "address", 156 | "name": "tokenOut", 157 | "type": "address" 158 | }, 159 | { 160 | "internalType": "uint256", 161 | "name": "amount", 162 | "type": "uint256" 163 | }, 164 | { 165 | "internalType": "uint24", 166 | "name": "fee", 167 | "type": "uint24" 168 | }, 169 | { 170 | "internalType": "uint160", 171 | "name": "sqrtPriceLimitX96", 172 | "type": "uint160" 173 | } 174 | ], 175 | "internalType": "struct IQuoterV2.QuoteExactOutputSingleParams", 176 | "name": "params", 177 | "type": "tuple" 178 | } 179 | ], 180 | "name": "quoteExactOutputSingle", 181 | "outputs": [ 182 | { 183 | "internalType": "uint256", 184 | "name": "amountIn", 185 | "type": "uint256" 186 | }, 187 | { 188 | "internalType": "uint160", 189 | "name": "sqrtPriceX96After", 190 | "type": "uint160" 191 | }, 192 | { 193 | "internalType": "uint32", 194 | "name": "initializedTicksCrossed", 195 | "type": "uint32" 196 | }, 197 | { 198 | "internalType": "uint256", 199 | "name": "gasEstimate", 200 | "type": "uint256" 201 | } 202 | ], 203 | "stateMutability": "nonpayable", 204 | "type": "function" 205 | } 206 | ], 207 | "bytecode": "0x", 208 | "deployedBytecode": "0x", 209 | "linkReferences": {}, 210 | "deployedLinkReferences": {} 211 | } 212 | -------------------------------------------------------------------------------- /entities/ticklist_test.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | import ( 4 | "math/big" 5 | "testing" 6 | 7 | "github.com/daoleno/uniswapv3-sdk/utils" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | var ( 12 | lowTick = Tick{ 13 | Index: utils.MinTick + 1, 14 | LiquidityNet: big.NewInt(10), 15 | LiquidityGross: big.NewInt(10), 16 | } 17 | midTick = Tick{ 18 | Index: 0, 19 | LiquidityNet: big.NewInt(-5), 20 | LiquidityGross: big.NewInt(5), 21 | } 22 | highTick = Tick{ 23 | Index: utils.MaxTick - 1, 24 | LiquidityNet: big.NewInt(-5), 25 | LiquidityGross: big.NewInt(5), 26 | } 27 | ) 28 | 29 | func TestValidateList(t *testing.T) { 30 | assert.ErrorIs(t, ValidateList([]Tick{lowTick}, 1), ErrZeroNet, "panics for incomplete lists") 31 | assert.ErrorIs(t, ValidateList([]Tick{highTick, lowTick, midTick}, 1), ErrSorted, "panics for unsorted lists") 32 | assert.ErrorIs(t, ValidateList([]Tick{highTick, midTick, lowTick}, 1337), ErrInvalidTickSpacing, "errors if ticks are not on multiples of tick spacing") 33 | } 34 | 35 | func TestIsBelowSmallest(t *testing.T) { 36 | result := []Tick{lowTick, midTick, highTick} 37 | assert.True(t, IsBelowSmallest(result, utils.MinTick)) 38 | assert.False(t, IsBelowSmallest(result, utils.MinTick+1)) 39 | } 40 | 41 | func TestIsAtOrAboveSmallest(t *testing.T) { 42 | result := []Tick{lowTick, midTick, highTick} 43 | assert.False(t, IsAtOrAboveLargest(result, utils.MaxTick-2)) 44 | assert.True(t, IsAtOrAboveLargest(result, utils.MaxTick-1)) 45 | } 46 | 47 | func TestNextInitializedTick(t *testing.T) { 48 | ticks := []Tick{lowTick, midTick, highTick} 49 | 50 | type args struct { 51 | ticks []Tick 52 | tick int 53 | lte bool 54 | } 55 | tests := []struct { 56 | name string 57 | args args 58 | want Tick 59 | }{ 60 | {name: "low - lte = true 0", args: args{ticks: ticks, tick: utils.MinTick + 1, lte: true}, want: lowTick}, 61 | {name: "low - lte = true 1", args: args{ticks: ticks, tick: utils.MinTick + 2, lte: true}, want: lowTick}, 62 | {name: "low - lte = false 0", args: args{ticks: ticks, tick: utils.MinTick, lte: false}, want: lowTick}, 63 | {name: "low - lte = false 1", args: args{ticks: ticks, tick: utils.MinTick + 1, lte: false}, want: midTick}, 64 | {name: "mid - lte = true 0", args: args{ticks: ticks, tick: 0, lte: true}, want: midTick}, 65 | {name: "mid - lte = true 1", args: args{ticks: ticks, tick: 1, lte: true}, want: midTick}, 66 | {name: "mid - lte = false 0", args: args{ticks: ticks, tick: -1, lte: false}, want: midTick}, 67 | {name: "mid - lte = false 1", args: args{ticks: ticks, tick: 0 + 1, lte: false}, want: highTick}, 68 | {name: "high - lte = true 0", args: args{ticks: ticks, tick: utils.MaxTick - 1, lte: true}, want: highTick}, 69 | {name: "high - lte = true 1", args: args{ticks: ticks, tick: utils.MaxTick, lte: true}, want: highTick}, 70 | {name: "high - lte = false 0", args: args{ticks: ticks, tick: utils.MaxTick - 2, lte: false}, want: highTick}, 71 | {name: "high - lte = false 1", args: args{ticks: ticks, tick: utils.MaxTick - 3, lte: false}, want: highTick}, 72 | } 73 | 74 | for _, tt := range tests { 75 | t.Run(tt.name, func(t *testing.T) { 76 | assert.Equal(t, tt.want, NextInitializedTick(tt.args.ticks, tt.args.tick, tt.args.lte)) 77 | }) 78 | } 79 | 80 | assert.Panics(t, func() { NextInitializedTick(ticks, utils.MinTick, true) }, "blow smallest") 81 | assert.Panics(t, func() { NextInitializedTick(ticks, utils.MaxTick-1, false) }, "at or above largest") 82 | } 83 | 84 | func TestNextInitializedTickWithinOneWord(t *testing.T) { 85 | ticks := []Tick{lowTick, midTick, highTick} 86 | 87 | // words around 0, lte = true 88 | type args struct { 89 | ticks []Tick 90 | tick int 91 | lte bool 92 | tickSpacing int 93 | } 94 | tests := []struct { 95 | name string 96 | args args 97 | want0 int 98 | want1 bool 99 | }{ 100 | // words around 0, lte = true 101 | {name: "lte = true 0", args: args{ticks: ticks, tick: -257, lte: true, tickSpacing: 1}, want0: -512, want1: false}, 102 | {name: "lte = true 1", args: args{ticks: ticks, tick: -256, lte: true, tickSpacing: 1}, want0: -256, want1: false}, 103 | {name: "lte = true 2", args: args{ticks: ticks, tick: -1, lte: true, tickSpacing: 1}, want0: -256, want1: false}, 104 | {name: "lte = true 3", args: args{ticks: ticks, tick: 0, lte: true, tickSpacing: 1}, want0: 0, want1: true}, 105 | {name: "lte = true 4", args: args{ticks: ticks, tick: 1, lte: true, tickSpacing: 1}, want0: 0, want1: true}, 106 | {name: "lte = true 5", args: args{ticks: ticks, tick: 255, lte: true, tickSpacing: 1}, want0: 0, want1: true}, 107 | {name: "lte = true 6", args: args{ticks: ticks, tick: 256, lte: true, tickSpacing: 1}, want0: 256, want1: false}, 108 | {name: "lte = true 7", args: args{ticks: ticks, tick: 257, lte: true, tickSpacing: 1}, want0: 256, want1: false}, 109 | 110 | // words around 0, lte = false 111 | {name: "lte = false 0", args: args{ticks: ticks, tick: -258, lte: false, tickSpacing: 1}, want0: -257, want1: false}, 112 | {name: "lte = false 1", args: args{ticks: ticks, tick: -257, lte: false, tickSpacing: 1}, want0: -1, want1: false}, 113 | {name: "lte = false 2", args: args{ticks: ticks, tick: -256, lte: false, tickSpacing: 1}, want0: -1, want1: false}, 114 | {name: "lte = false 3", args: args{ticks: ticks, tick: -2, lte: false, tickSpacing: 1}, want0: -1, want1: false}, 115 | {name: "lte = false 4", args: args{ticks: ticks, tick: -1, lte: false, tickSpacing: 1}, want0: 0, want1: true}, 116 | {name: "lte = false 5", args: args{ticks: ticks, tick: 0, lte: false, tickSpacing: 1}, want0: 255, want1: false}, 117 | {name: "lte = false 6", args: args{ticks: ticks, tick: 1, lte: false, tickSpacing: 1}, want0: 255, want1: false}, 118 | {name: "lte = false 7", args: args{ticks: ticks, tick: 254, lte: false, tickSpacing: 1}, want0: 255, want1: false}, 119 | {name: "lte = false 8", args: args{ticks: ticks, tick: 255, lte: false, tickSpacing: 1}, want0: 511, want1: false}, 120 | {name: "lte = false 9", args: args{ticks: ticks, tick: 256, lte: false, tickSpacing: 1}, want0: 511, want1: false}, 121 | } 122 | 123 | for _, tt := range tests { 124 | t.Run(tt.name, func(t *testing.T) { 125 | got0, got1 := NextInitializedTickWithinOneWord(tt.args.ticks, tt.args.tick, tt.args.lte, tt.args.tickSpacing) 126 | assert.Equal(t, tt.want0, got0) 127 | assert.Equal(t, tt.want1, got1) 128 | }) 129 | } 130 | 131 | } 132 | -------------------------------------------------------------------------------- /entities/route_test.go: -------------------------------------------------------------------------------- 1 | package entities 2 | 3 | import ( 4 | "math/big" 5 | "testing" 6 | 7 | "github.com/daoleno/uniswap-sdk-core/entities" 8 | "github.com/daoleno/uniswapv3-sdk/constants" 9 | "github.com/daoleno/uniswapv3-sdk/utils" 10 | "github.com/ethereum/go-ethereum/common" 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | var ( 15 | rEther = entities.EtherOnChain(1).Wrapped() 16 | rtoken0 = entities.NewToken(1, common.HexToAddress("0x0000000000000000000000000000000000000001"), 18, "t0", "token0") 17 | rtoken1 = entities.NewToken(1, common.HexToAddress("0x0000000000000000000000000000000000000002"), 18, "t1", "token1") 18 | rtoken2 = entities.NewToken(1, common.HexToAddress("0x0000000000000000000000000000000000000003"), 18, "t2", "token2") 19 | rweth = entities.WETH9[1] 20 | 21 | rpool_0_1, _ = NewPool(rtoken0, rtoken1, constants.FeeMedium, utils.EncodeSqrtRatioX96(constants.One, constants.One), big.NewInt(0), 0, nil) 22 | rpool_0_weth, _ = NewPool(rtoken0, rweth, constants.FeeMedium, utils.EncodeSqrtRatioX96(constants.One, constants.One), big.NewInt(0), 0, nil) 23 | rpool_1_weth, _ = NewPool(rtoken1, rweth, constants.FeeMedium, utils.EncodeSqrtRatioX96(constants.One, constants.One), big.NewInt(0), 0, nil) 24 | ) 25 | 26 | func TestPath(t *testing.T) { 27 | // constructs a path from the tokens 28 | route, err := NewRoute([]*Pool{rpool_0_1}, rtoken0, rtoken1) 29 | if err != nil { 30 | t.Fatal(err) 31 | } 32 | assert.Equal(t, route.Pools, []*Pool{rpool_0_1}) 33 | assert.Equal(t, route.TokenPath, []*entities.Token{rtoken0, rtoken1}) 34 | assert.Equal(t, route.Input, rtoken0) 35 | assert.Equal(t, route.Output, rtoken1) 36 | assert.Equal(t, route.ChainID(), uint(1)) 37 | 38 | _, err = NewRoute([]*Pool{rpool_0_1}, rweth, rtoken1) 39 | assert.ErrorIs(t, err, ErrInputNotInvolved, "should fail if the input is not in the first pool") 40 | 41 | _, err = NewRoute([]*Pool{rpool_0_1}, rtoken0, rweth) 42 | assert.ErrorIs(t, err, ErrOutputNotInvolved, "should fail if the output is not in the last pool") 43 | } 44 | 45 | func TestSameInputOutput(t *testing.T) { 46 | // can have a token as both input and output 47 | route, err := NewRoute([]*Pool{rpool_0_weth, rpool_0_1, rpool_1_weth}, rweth, rweth) 48 | if err != nil { 49 | t.Fatal(err) 50 | } 51 | 52 | assert.Equal(t, route.Pools, []*Pool{rpool_0_weth, rpool_0_1, rpool_1_weth}) 53 | assert.Equal(t, route.Input, rweth) 54 | assert.Equal(t, route.Output, rweth) 55 | } 56 | 57 | func TestEtherInput(t *testing.T) { 58 | // supports ether input 59 | route, err := NewRoute([]*Pool{rpool_0_weth}, rEther, rtoken0) 60 | if err != nil { 61 | t.Fatal(err) 62 | } 63 | 64 | assert.Equal(t, route.Pools, []*Pool{rpool_0_weth}) 65 | assert.Equal(t, route.Input, rEther) 66 | assert.Equal(t, route.Output, rtoken0) 67 | } 68 | 69 | func TestEtherOutput(t *testing.T) { 70 | // supports ether output 71 | route, err := NewRoute([]*Pool{rpool_0_weth}, rtoken0, rEther) 72 | if err != nil { 73 | t.Fatal(err) 74 | } 75 | 76 | assert.Equal(t, route.Pools, []*Pool{rpool_0_weth}) 77 | assert.Equal(t, route.Input, rtoken0) 78 | assert.Equal(t, route.Output, rEther) 79 | } 80 | 81 | func TestMidPrice(t *testing.T) { 82 | r, _ := utils.GetTickAtSqrtRatio(utils.EncodeSqrtRatioX96(big.NewInt(1), big.NewInt(5))) 83 | pool_0_1, _ := NewPool(rtoken0, rtoken1, constants.FeeMedium, utils.EncodeSqrtRatioX96(big.NewInt(1), big.NewInt(5)), big.NewInt(0), r, nil) 84 | 85 | r, _ = utils.GetTickAtSqrtRatio(utils.EncodeSqrtRatioX96(big.NewInt(15), big.NewInt(30))) 86 | pool_1_2, _ := NewPool(rtoken1, rtoken2, constants.FeeMedium, utils.EncodeSqrtRatioX96(big.NewInt(15), big.NewInt(30)), big.NewInt(0), r, nil) 87 | 88 | r, _ = utils.GetTickAtSqrtRatio(utils.EncodeSqrtRatioX96(big.NewInt(3), big.NewInt(1))) 89 | pool_0_weth, _ := NewPool(rtoken0, rweth, constants.FeeMedium, utils.EncodeSqrtRatioX96(big.NewInt(3), big.NewInt(1)), big.NewInt(0), r, nil) 90 | 91 | r, _ = utils.GetTickAtSqrtRatio(utils.EncodeSqrtRatioX96(big.NewInt(1), big.NewInt(7))) 92 | pool_1_weth, _ := NewPool(rtoken1, rweth, constants.FeeMedium, utils.EncodeSqrtRatioX96(big.NewInt(1), big.NewInt(7)), big.NewInt(0), r, nil) 93 | 94 | // correct for 0 -> 1 95 | route, _ := NewRoute([]*Pool{pool_0_1}, rtoken0, rtoken1) 96 | price, _ := route.MidPrice() 97 | assert.True(t, price.BaseCurrency.Equal(rtoken0)) 98 | assert.True(t, price.QuoteCurrency.Equal(rtoken1)) 99 | 100 | // correct for 1 -> 0 101 | route, _ = NewRoute([]*Pool{pool_0_1}, rtoken1, rtoken0) 102 | price, _ = route.MidPrice() 103 | assert.Equal(t, price.ToFixed(4), "5.0000") 104 | assert.True(t, price.BaseCurrency.Equal(rtoken1)) 105 | assert.True(t, price.QuoteCurrency.Equal(rtoken0)) 106 | 107 | // correct for 0 -> 1 -> 2 108 | route, _ = NewRoute([]*Pool{pool_0_1, pool_1_2}, rtoken0, rtoken2) 109 | price, _ = route.MidPrice() 110 | assert.Equal(t, price.ToFixed(4), "0.1000") 111 | assert.True(t, price.BaseCurrency.Equal(rtoken0)) 112 | assert.True(t, price.QuoteCurrency.Equal(rtoken2)) 113 | 114 | // correct for 2 -> 1 -> 0 115 | route, _ = NewRoute([]*Pool{pool_1_2, pool_0_1}, rtoken2, rtoken0) 116 | price, _ = route.MidPrice() 117 | assert.Equal(t, price.ToFixed(4), "10.0000") 118 | assert.True(t, price.BaseCurrency.Equal(rtoken2)) 119 | assert.True(t, price.QuoteCurrency.Equal(rtoken0)) 120 | 121 | // correct for ether -> 0 122 | route, _ = NewRoute([]*Pool{pool_0_weth}, rEther, rtoken0) 123 | price, _ = route.MidPrice() 124 | assert.Equal(t, price.ToFixed(4), "0.3333") 125 | assert.True(t, price.BaseCurrency.Equal(rEther)) 126 | assert.True(t, price.QuoteCurrency.Equal(rtoken0)) 127 | 128 | // correct for 1 -> weth 129 | route, _ = NewRoute([]*Pool{pool_1_weth}, rtoken1, rweth) 130 | price, _ = route.MidPrice() 131 | assert.Equal(t, price.ToFixed(4), "0.1429") 132 | assert.True(t, price.BaseCurrency.Equal(rtoken1)) 133 | assert.True(t, price.QuoteCurrency.Equal(rweth)) 134 | 135 | // correct for ether -> 0 -> 1 -> weth 136 | route, _ = NewRoute([]*Pool{pool_0_weth, pool_0_1, pool_1_weth}, rEther, rweth) 137 | price, _ = route.MidPrice() 138 | assert.Equal(t, price.ToSignificant(4), "0.009524") 139 | assert.True(t, price.BaseCurrency.Equal(rEther)) 140 | assert.True(t, price.QuoteCurrency.Equal(rweth)) 141 | 142 | // correct for weth -> 0 -> 1 -> ether 143 | route, _ = NewRoute([]*Pool{pool_0_weth, pool_0_1, pool_1_weth}, rweth, rEther) 144 | price, _ = route.MidPrice() 145 | assert.Equal(t, price.ToSignificant(4), "0.009524") 146 | assert.True(t, price.BaseCurrency.Equal(rweth)) 147 | assert.True(t, price.QuoteCurrency.Equal(rEther)) 148 | } 149 | -------------------------------------------------------------------------------- /periphery/contracts/lens/TickLens.sol/TickLens.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "TickLens", 4 | "sourceName": "contracts/lens/TickLens.sol", 5 | "abi": [ 6 | { 7 | "inputs": [ 8 | { 9 | "internalType": "address", 10 | "name": "pool", 11 | "type": "address" 12 | }, 13 | { 14 | "internalType": "int16", 15 | "name": "tickBitmapIndex", 16 | "type": "int16" 17 | } 18 | ], 19 | "name": "getPopulatedTicksInWord", 20 | "outputs": [ 21 | { 22 | "components": [ 23 | { 24 | "internalType": "int24", 25 | "name": "tick", 26 | "type": "int24" 27 | }, 28 | { 29 | "internalType": "int128", 30 | "name": "liquidityNet", 31 | "type": "int128" 32 | }, 33 | { 34 | "internalType": "uint128", 35 | "name": "liquidityGross", 36 | "type": "uint128" 37 | } 38 | ], 39 | "internalType": "struct ITickLens.PopulatedTick[]", 40 | "name": "populatedTicks", 41 | "type": "tuple[]" 42 | } 43 | ], 44 | "stateMutability": "view", 45 | "type": "function" 46 | } 47 | ], 48 | "bytecode": "0x608060405234801561001057600080fd5b50610569806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063351fb47814610030575b600080fd5b61004361003e36600461037c565b610059565b60405161005091906104aa565b60405180910390f35b606060008373ffffffffffffffffffffffffffffffffffffffff16635339c296846040518263ffffffff1660e01b8152600401610096919061051b565b60206040518083038186803b1580156100ae57600080fd5b505afa1580156100c2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100e69190610492565b90506000805b610100811015610110576001811b831615610108576001909101905b6001016100ec565b5060008573ffffffffffffffffffffffffffffffffffffffff1663d0c93a7c6040518163ffffffff1660e01b815260040160206040518083038186803b15801561015957600080fd5b505afa15801561016d573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061019191906103ba565b90508167ffffffffffffffff811180156101aa57600080fd5b506040519080825280602002602001820160405280156101e457816020015b6101d1610328565b8152602001906001900390816101c95790505b50935060005b61010081101561031e576001811b841615610316576040517ff30dba93000000000000000000000000000000000000000000000000000000008152600187900b60020b60081b8201830290600090819073ffffffffffffffffffffffffffffffffffffffff8b169063f30dba9390610266908690600401610529565b6101006040518083038186803b15801561027f57600080fd5b505afa158015610293573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102b791906103e2565b5050505050509150915060405180606001604052808460020b815260200182600f0b8152602001836fffffffffffffffffffffffffffffffff168152508887600190039750878151811061030757fe5b60200260200101819052505050505b6001016101ea565b5050505092915050565b604080516060810182526000808252602082018190529181019190915290565b8051801515811461035857600080fd5b919050565b805161035881610537565b805163ffffffff8116811461035857600080fd5b6000806040838503121561038e578182fd5b823561039981610537565b91506020830135600181900b81146103af578182fd5b809150509250929050565b6000602082840312156103cb578081fd5b81518060020b81146103db578182fd5b9392505050565b600080600080600080600080610100898b0312156103fe578384fd5b88516fffffffffffffffffffffffffffffffff8116811461041d578485fd5b80985050602089015180600f0b8114610434578485fd5b80975050604089015195506060890151945060808901518060060b8114610459578485fd5b935061046760a08a0161035d565b925061047560c08a01610368565b915061048360e08a01610348565b90509295985092959890939650565b6000602082840312156104a3578081fd5b5051919050565b602080825282518282018190526000919060409081850190868401855b8281101561050e578151805160020b855286810151600f0b878601528501516fffffffffffffffffffffffffffffffff1685850152606090930192908501906001016104c7565b5091979650505050505050565b60019190910b815260200190565b60029190910b815260200190565b73ffffffffffffffffffffffffffffffffffffffff8116811461055957600080fd5b5056fea164736f6c6343000706000a", 49 | "deployedBytecode": "0x608060405234801561001057600080fd5b506004361061002b5760003560e01c8063351fb47814610030575b600080fd5b61004361003e36600461037c565b610059565b60405161005091906104aa565b60405180910390f35b606060008373ffffffffffffffffffffffffffffffffffffffff16635339c296846040518263ffffffff1660e01b8152600401610096919061051b565b60206040518083038186803b1580156100ae57600080fd5b505afa1580156100c2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100e69190610492565b90506000805b610100811015610110576001811b831615610108576001909101905b6001016100ec565b5060008573ffffffffffffffffffffffffffffffffffffffff1663d0c93a7c6040518163ffffffff1660e01b815260040160206040518083038186803b15801561015957600080fd5b505afa15801561016d573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061019191906103ba565b90508167ffffffffffffffff811180156101aa57600080fd5b506040519080825280602002602001820160405280156101e457816020015b6101d1610328565b8152602001906001900390816101c95790505b50935060005b61010081101561031e576001811b841615610316576040517ff30dba93000000000000000000000000000000000000000000000000000000008152600187900b60020b60081b8201830290600090819073ffffffffffffffffffffffffffffffffffffffff8b169063f30dba9390610266908690600401610529565b6101006040518083038186803b15801561027f57600080fd5b505afa158015610293573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102b791906103e2565b5050505050509150915060405180606001604052808460020b815260200182600f0b8152602001836fffffffffffffffffffffffffffffffff168152508887600190039750878151811061030757fe5b60200260200101819052505050505b6001016101ea565b5050505092915050565b604080516060810182526000808252602082018190529181019190915290565b8051801515811461035857600080fd5b919050565b805161035881610537565b805163ffffffff8116811461035857600080fd5b6000806040838503121561038e578182fd5b823561039981610537565b91506020830135600181900b81146103af578182fd5b809150509250929050565b6000602082840312156103cb578081fd5b81518060020b81146103db578182fd5b9392505050565b600080600080600080600080610100898b0312156103fe578384fd5b88516fffffffffffffffffffffffffffffffff8116811461041d578485fd5b80985050602089015180600f0b8114610434578485fd5b80975050604089015195506060890151945060808901518060060b8114610459578485fd5b935061046760a08a0161035d565b925061047560c08a01610368565b915061048360e08a01610348565b90509295985092959890939650565b6000602082840312156104a3578081fd5b5051919050565b602080825282518282018190526000919060409081850190868401855b8281101561050e578151805160020b855286810151600f0b878601528501516fffffffffffffffffffffffffffffffff1685850152606090930192908501906001016104c7565b5091979650505050505050565b60019190910b815260200190565b60029190910b815260200190565b73ffffffffffffffffffffffffffffffffffffffff8116811461055957600080fd5b5056fea164736f6c6343000706000a", 50 | "linkReferences": {}, 51 | "deployedLinkReferences": {} 52 | } 53 | -------------------------------------------------------------------------------- /examples/contract/uniswapv3_factory.abi: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "inputs": [], 4 | "stateMutability": "nonpayable", 5 | "type": "constructor" 6 | }, 7 | { 8 | "anonymous": false, 9 | "inputs": [ 10 | { 11 | "indexed": true, 12 | "internalType": "uint24", 13 | "name": "fee", 14 | "type": "uint24" 15 | }, 16 | { 17 | "indexed": true, 18 | "internalType": "int24", 19 | "name": "tickSpacing", 20 | "type": "int24" 21 | } 22 | ], 23 | "name": "FeeAmountEnabled", 24 | "type": "event" 25 | }, 26 | { 27 | "anonymous": false, 28 | "inputs": [ 29 | { 30 | "indexed": true, 31 | "internalType": "address", 32 | "name": "oldOwner", 33 | "type": "address" 34 | }, 35 | { 36 | "indexed": true, 37 | "internalType": "address", 38 | "name": "newOwner", 39 | "type": "address" 40 | } 41 | ], 42 | "name": "OwnerChanged", 43 | "type": "event" 44 | }, 45 | { 46 | "anonymous": false, 47 | "inputs": [ 48 | { 49 | "indexed": true, 50 | "internalType": "address", 51 | "name": "token0", 52 | "type": "address" 53 | }, 54 | { 55 | "indexed": true, 56 | "internalType": "address", 57 | "name": "token1", 58 | "type": "address" 59 | }, 60 | { 61 | "indexed": true, 62 | "internalType": "uint24", 63 | "name": "fee", 64 | "type": "uint24" 65 | }, 66 | { 67 | "indexed": false, 68 | "internalType": "int24", 69 | "name": "tickSpacing", 70 | "type": "int24" 71 | }, 72 | { 73 | "indexed": false, 74 | "internalType": "address", 75 | "name": "pool", 76 | "type": "address" 77 | } 78 | ], 79 | "name": "PoolCreated", 80 | "type": "event" 81 | }, 82 | { 83 | "inputs": [ 84 | { 85 | "internalType": "address", 86 | "name": "tokenA", 87 | "type": "address" 88 | }, 89 | { 90 | "internalType": "address", 91 | "name": "tokenB", 92 | "type": "address" 93 | }, 94 | { 95 | "internalType": "uint24", 96 | "name": "fee", 97 | "type": "uint24" 98 | } 99 | ], 100 | "name": "createPool", 101 | "outputs": [ 102 | { 103 | "internalType": "address", 104 | "name": "pool", 105 | "type": "address" 106 | } 107 | ], 108 | "stateMutability": "nonpayable", 109 | "type": "function" 110 | }, 111 | { 112 | "inputs": [ 113 | { 114 | "internalType": "uint24", 115 | "name": "fee", 116 | "type": "uint24" 117 | }, 118 | { 119 | "internalType": "int24", 120 | "name": "tickSpacing", 121 | "type": "int24" 122 | } 123 | ], 124 | "name": "enableFeeAmount", 125 | "outputs": [], 126 | "stateMutability": "nonpayable", 127 | "type": "function" 128 | }, 129 | { 130 | "inputs": [ 131 | { 132 | "internalType": "uint24", 133 | "name": "", 134 | "type": "uint24" 135 | } 136 | ], 137 | "name": "feeAmountTickSpacing", 138 | "outputs": [ 139 | { 140 | "internalType": "int24", 141 | "name": "", 142 | "type": "int24" 143 | } 144 | ], 145 | "stateMutability": "view", 146 | "type": "function" 147 | }, 148 | { 149 | "inputs": [ 150 | { 151 | "internalType": "address", 152 | "name": "", 153 | "type": "address" 154 | }, 155 | { 156 | "internalType": "address", 157 | "name": "", 158 | "type": "address" 159 | }, 160 | { 161 | "internalType": "uint24", 162 | "name": "", 163 | "type": "uint24" 164 | } 165 | ], 166 | "name": "getPool", 167 | "outputs": [ 168 | { 169 | "internalType": "address", 170 | "name": "", 171 | "type": "address" 172 | } 173 | ], 174 | "stateMutability": "view", 175 | "type": "function" 176 | }, 177 | { 178 | "inputs": [], 179 | "name": "owner", 180 | "outputs": [ 181 | { 182 | "internalType": "address", 183 | "name": "", 184 | "type": "address" 185 | } 186 | ], 187 | "stateMutability": "view", 188 | "type": "function" 189 | }, 190 | { 191 | "inputs": [], 192 | "name": "parameters", 193 | "outputs": [ 194 | { 195 | "internalType": "address", 196 | "name": "factory", 197 | "type": "address" 198 | }, 199 | { 200 | "internalType": "address", 201 | "name": "token0", 202 | "type": "address" 203 | }, 204 | { 205 | "internalType": "address", 206 | "name": "token1", 207 | "type": "address" 208 | }, 209 | { 210 | "internalType": "uint24", 211 | "name": "fee", 212 | "type": "uint24" 213 | }, 214 | { 215 | "internalType": "int24", 216 | "name": "tickSpacing", 217 | "type": "int24" 218 | } 219 | ], 220 | "stateMutability": "view", 221 | "type": "function" 222 | }, 223 | { 224 | "inputs": [ 225 | { 226 | "internalType": "address", 227 | "name": "_owner", 228 | "type": "address" 229 | } 230 | ], 231 | "name": "setOwner", 232 | "outputs": [], 233 | "stateMutability": "nonpayable", 234 | "type": "function" 235 | } 236 | ] -------------------------------------------------------------------------------- /periphery/contracts/interfaces/ISwapRouter.sol/ISwapRouter.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "ISwapRouter", 4 | "sourceName": "contracts/interfaces/ISwapRouter.sol", 5 | "abi": [ 6 | { 7 | "inputs": [ 8 | { 9 | "components": [ 10 | { 11 | "internalType": "bytes", 12 | "name": "path", 13 | "type": "bytes" 14 | }, 15 | { 16 | "internalType": "address", 17 | "name": "recipient", 18 | "type": "address" 19 | }, 20 | { 21 | "internalType": "uint256", 22 | "name": "deadline", 23 | "type": "uint256" 24 | }, 25 | { 26 | "internalType": "uint256", 27 | "name": "amountIn", 28 | "type": "uint256" 29 | }, 30 | { 31 | "internalType": "uint256", 32 | "name": "amountOutMinimum", 33 | "type": "uint256" 34 | } 35 | ], 36 | "internalType": "struct ISwapRouter.ExactInputParams", 37 | "name": "params", 38 | "type": "tuple" 39 | } 40 | ], 41 | "name": "exactInput", 42 | "outputs": [ 43 | { 44 | "internalType": "uint256", 45 | "name": "amountOut", 46 | "type": "uint256" 47 | } 48 | ], 49 | "stateMutability": "payable", 50 | "type": "function" 51 | }, 52 | { 53 | "inputs": [ 54 | { 55 | "components": [ 56 | { 57 | "internalType": "address", 58 | "name": "tokenIn", 59 | "type": "address" 60 | }, 61 | { 62 | "internalType": "address", 63 | "name": "tokenOut", 64 | "type": "address" 65 | }, 66 | { 67 | "internalType": "uint24", 68 | "name": "fee", 69 | "type": "uint24" 70 | }, 71 | { 72 | "internalType": "address", 73 | "name": "recipient", 74 | "type": "address" 75 | }, 76 | { 77 | "internalType": "uint256", 78 | "name": "deadline", 79 | "type": "uint256" 80 | }, 81 | { 82 | "internalType": "uint256", 83 | "name": "amountIn", 84 | "type": "uint256" 85 | }, 86 | { 87 | "internalType": "uint256", 88 | "name": "amountOutMinimum", 89 | "type": "uint256" 90 | }, 91 | { 92 | "internalType": "uint160", 93 | "name": "sqrtPriceLimitX96", 94 | "type": "uint160" 95 | } 96 | ], 97 | "internalType": "struct ISwapRouter.ExactInputSingleParams", 98 | "name": "params", 99 | "type": "tuple" 100 | } 101 | ], 102 | "name": "exactInputSingle", 103 | "outputs": [ 104 | { 105 | "internalType": "uint256", 106 | "name": "amountOut", 107 | "type": "uint256" 108 | } 109 | ], 110 | "stateMutability": "payable", 111 | "type": "function" 112 | }, 113 | { 114 | "inputs": [ 115 | { 116 | "components": [ 117 | { 118 | "internalType": "bytes", 119 | "name": "path", 120 | "type": "bytes" 121 | }, 122 | { 123 | "internalType": "address", 124 | "name": "recipient", 125 | "type": "address" 126 | }, 127 | { 128 | "internalType": "uint256", 129 | "name": "deadline", 130 | "type": "uint256" 131 | }, 132 | { 133 | "internalType": "uint256", 134 | "name": "amountOut", 135 | "type": "uint256" 136 | }, 137 | { 138 | "internalType": "uint256", 139 | "name": "amountInMaximum", 140 | "type": "uint256" 141 | } 142 | ], 143 | "internalType": "struct ISwapRouter.ExactOutputParams", 144 | "name": "params", 145 | "type": "tuple" 146 | } 147 | ], 148 | "name": "exactOutput", 149 | "outputs": [ 150 | { 151 | "internalType": "uint256", 152 | "name": "amountIn", 153 | "type": "uint256" 154 | } 155 | ], 156 | "stateMutability": "payable", 157 | "type": "function" 158 | }, 159 | { 160 | "inputs": [ 161 | { 162 | "components": [ 163 | { 164 | "internalType": "address", 165 | "name": "tokenIn", 166 | "type": "address" 167 | }, 168 | { 169 | "internalType": "address", 170 | "name": "tokenOut", 171 | "type": "address" 172 | }, 173 | { 174 | "internalType": "uint24", 175 | "name": "fee", 176 | "type": "uint24" 177 | }, 178 | { 179 | "internalType": "address", 180 | "name": "recipient", 181 | "type": "address" 182 | }, 183 | { 184 | "internalType": "uint256", 185 | "name": "deadline", 186 | "type": "uint256" 187 | }, 188 | { 189 | "internalType": "uint256", 190 | "name": "amountOut", 191 | "type": "uint256" 192 | }, 193 | { 194 | "internalType": "uint256", 195 | "name": "amountInMaximum", 196 | "type": "uint256" 197 | }, 198 | { 199 | "internalType": "uint160", 200 | "name": "sqrtPriceLimitX96", 201 | "type": "uint160" 202 | } 203 | ], 204 | "internalType": "struct ISwapRouter.ExactOutputSingleParams", 205 | "name": "params", 206 | "type": "tuple" 207 | } 208 | ], 209 | "name": "exactOutputSingle", 210 | "outputs": [ 211 | { 212 | "internalType": "uint256", 213 | "name": "amountIn", 214 | "type": "uint256" 215 | } 216 | ], 217 | "stateMutability": "payable", 218 | "type": "function" 219 | }, 220 | { 221 | "inputs": [ 222 | { 223 | "internalType": "int256", 224 | "name": "amount0Delta", 225 | "type": "int256" 226 | }, 227 | { 228 | "internalType": "int256", 229 | "name": "amount1Delta", 230 | "type": "int256" 231 | }, 232 | { 233 | "internalType": "bytes", 234 | "name": "data", 235 | "type": "bytes" 236 | } 237 | ], 238 | "name": "uniswapV3SwapCallback", 239 | "outputs": [], 240 | "stateMutability": "nonpayable", 241 | "type": "function" 242 | } 243 | ], 244 | "bytecode": "0x", 245 | "deployedBytecode": "0x", 246 | "linkReferences": {}, 247 | "deployedLinkReferences": {} 248 | } 249 | -------------------------------------------------------------------------------- /utils/tick_math.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "errors" 5 | "math/big" 6 | 7 | "github.com/daoleno/uniswap-sdk-core/entities" 8 | "github.com/daoleno/uniswapv3-sdk/constants" 9 | ) 10 | 11 | const ( 12 | MinTick = -887272 // The minimum tick that can be used on any pool. 13 | MaxTick = -MinTick // The maximum tick that can be used on any pool. 14 | ) 15 | 16 | var ( 17 | Q32 = big.NewInt(1 << 32) 18 | MinSqrtRatio = big.NewInt(4295128739) // The sqrt ratio corresponding to the minimum tick that could be used on any pool. 19 | MaxSqrtRatio, _ = new(big.Int).SetString("1461446703485210103287273052203988822378723970342", 10) // The sqrt ratio corresponding to the maximum tick that could be used on any pool. 20 | ) 21 | 22 | var ( 23 | ErrInvalidTick = errors.New("invalid tick") 24 | ErrInvalidSqrtRatio = errors.New("invalid sqrt ratio") 25 | ) 26 | 27 | func mulShift(val *big.Int, mulBy *big.Int) *big.Int { 28 | 29 | return new(big.Int).Rsh(new(big.Int).Mul(val, mulBy), 128) 30 | } 31 | 32 | var ( 33 | sqrtConst1, _ = new(big.Int).SetString("fffcb933bd6fad37aa2d162d1a594001", 16) 34 | sqrtConst2, _ = new(big.Int).SetString("100000000000000000000000000000000", 16) 35 | sqrtConst3, _ = new(big.Int).SetString("fff97272373d413259a46990580e213a", 16) 36 | sqrtConst4, _ = new(big.Int).SetString("fff2e50f5f656932ef12357cf3c7fdcc", 16) 37 | sqrtConst5, _ = new(big.Int).SetString("ffe5caca7e10e4e61c3624eaa0941cd0", 16) 38 | sqrtConst6, _ = new(big.Int).SetString("ffcb9843d60f6159c9db58835c926644", 16) 39 | sqrtConst7, _ = new(big.Int).SetString("ff973b41fa98c081472e6896dfb254c0", 16) 40 | sqrtConst8, _ = new(big.Int).SetString("ff2ea16466c96a3843ec78b326b52861", 16) 41 | sqrtConst9, _ = new(big.Int).SetString("fe5dee046a99a2a811c461f1969c3053", 16) 42 | sqrtConst10, _ = new(big.Int).SetString("fcbe86c7900a88aedcffc83b479aa3a4", 16) 43 | sqrtConst11, _ = new(big.Int).SetString("f987a7253ac413176f2b074cf7815e54", 16) 44 | sqrtConst12, _ = new(big.Int).SetString("f3392b0822b70005940c7a398e4b70f3", 16) 45 | sqrtConst13, _ = new(big.Int).SetString("e7159475a2c29b7443b29c7fa6e889d9", 16) 46 | sqrtConst14, _ = new(big.Int).SetString("d097f3bdfd2022b8845ad8f792aa5825", 16) 47 | sqrtConst15, _ = new(big.Int).SetString("a9f746462d870fdf8a65dc1f90e061e5", 16) 48 | sqrtConst16, _ = new(big.Int).SetString("70d869a156d2a1b890bb3df62baf32f7", 16) 49 | sqrtConst17, _ = new(big.Int).SetString("31be135f97d08fd981231505542fcfa6", 16) 50 | sqrtConst18, _ = new(big.Int).SetString("9aa508b5b7a84e1c677de54f3e99bc9", 16) 51 | sqrtConst19, _ = new(big.Int).SetString("5d6af8dedb81196699c329225ee604", 16) 52 | sqrtConst20, _ = new(big.Int).SetString("2216e584f5fa1ea926041bedfe98", 16) 53 | sqrtConst21, _ = new(big.Int).SetString("48a170391f7dc42444e8fa2", 16) 54 | ) 55 | 56 | /** 57 | * Returns the sqrt ratio as a Q64.96 for the given tick. The sqrt ratio is computed as sqrt(1.0001)^tick 58 | * @param tick the tick for which to compute the sqrt ratio 59 | */ 60 | func GetSqrtRatioAtTick(tick int) (*big.Int, error) { 61 | if tick < MinTick || tick > MaxTick { 62 | return nil, ErrInvalidTick 63 | } 64 | absTick := tick 65 | if tick < 0 { 66 | absTick = -tick 67 | } 68 | var ratio *big.Int 69 | if absTick&0x1 != 0 { 70 | ratio = sqrtConst1 71 | } else { 72 | ratio = sqrtConst2 73 | } 74 | if (absTick & 0x2) != 0 { 75 | ratio = mulShift(ratio, sqrtConst3) 76 | } 77 | if (absTick & 0x4) != 0 { 78 | ratio = mulShift(ratio, sqrtConst4) 79 | } 80 | if (absTick & 0x8) != 0 { 81 | ratio = mulShift(ratio, sqrtConst5) 82 | } 83 | if (absTick & 0x10) != 0 { 84 | ratio = mulShift(ratio, sqrtConst6) 85 | } 86 | if (absTick & 0x20) != 0 { 87 | ratio = mulShift(ratio, sqrtConst7) 88 | } 89 | if (absTick & 0x40) != 0 { 90 | ratio = mulShift(ratio, sqrtConst8) 91 | } 92 | if (absTick & 0x80) != 0 { 93 | ratio = mulShift(ratio, sqrtConst9) 94 | } 95 | if (absTick & 0x100) != 0 { 96 | ratio = mulShift(ratio, sqrtConst10) 97 | } 98 | if (absTick & 0x200) != 0 { 99 | ratio = mulShift(ratio, sqrtConst11) 100 | } 101 | if (absTick & 0x400) != 0 { 102 | ratio = mulShift(ratio, sqrtConst12) 103 | } 104 | if (absTick & 0x800) != 0 { 105 | ratio = mulShift(ratio, sqrtConst13) 106 | } 107 | if (absTick & 0x1000) != 0 { 108 | ratio = mulShift(ratio, sqrtConst14) 109 | } 110 | if (absTick & 0x2000) != 0 { 111 | ratio = mulShift(ratio, sqrtConst15) 112 | } 113 | if (absTick & 0x4000) != 0 { 114 | ratio = mulShift(ratio, sqrtConst16) 115 | } 116 | if (absTick & 0x8000) != 0 { 117 | ratio = mulShift(ratio, sqrtConst17) 118 | } 119 | if (absTick & 0x10000) != 0 { 120 | ratio = mulShift(ratio, sqrtConst18) 121 | } 122 | if (absTick & 0x20000) != 0 { 123 | ratio = mulShift(ratio, sqrtConst19) 124 | } 125 | if (absTick & 0x40000) != 0 { 126 | ratio = mulShift(ratio, sqrtConst20) 127 | } 128 | if (absTick & 0x80000) != 0 { 129 | ratio = mulShift(ratio, sqrtConst21) 130 | } 131 | if tick > 0 { 132 | ratio = new(big.Int).Div(entities.MaxUint256, ratio) 133 | } 134 | 135 | // back to Q96 136 | if new(big.Int).Rem(ratio, Q32).Cmp(constants.Zero) > 0 { 137 | return new(big.Int).Add((new(big.Int).Div(ratio, Q32)), constants.One), nil 138 | } else { 139 | return new(big.Int).Div(ratio, Q32), nil 140 | } 141 | } 142 | 143 | var ( 144 | magicSqrt10001, _ = new(big.Int).SetString("255738958999603826347141", 10) 145 | magicTickLow, _ = new(big.Int).SetString("3402992956809132418596140100660247210", 10) 146 | magicTickHigh, _ = new(big.Int).SetString("291339464771989622907027621153398088495", 10) 147 | ) 148 | 149 | /** 150 | * Returns the tick corresponding to a given sqrt ratio, s.t. #getSqrtRatioAtTick(tick) <= sqrtRatioX96 151 | * and #getSqrtRatioAtTick(tick + 1) > sqrtRatioX96 152 | * @param sqrtRatioX96 the sqrt ratio as a Q64.96 for which to compute the tick 153 | */ 154 | func GetTickAtSqrtRatio(sqrtRatioX96 *big.Int) (int, error) { 155 | if sqrtRatioX96.Cmp(MinSqrtRatio) < 0 || sqrtRatioX96.Cmp(MaxSqrtRatio) >= 0 { 156 | return 0, ErrInvalidSqrtRatio 157 | } 158 | sqrtRatioX128 := new(big.Int).Lsh(sqrtRatioX96, 32) 159 | msb, err := MostSignificantBit(sqrtRatioX128) 160 | if err != nil { 161 | return 0, err 162 | } 163 | var r *big.Int 164 | if big.NewInt(msb).Cmp(big.NewInt(128)) >= 0 { 165 | r = new(big.Int).Rsh(sqrtRatioX128, uint(msb-127)) 166 | } else { 167 | r = new(big.Int).Lsh(sqrtRatioX128, uint(127-msb)) 168 | } 169 | 170 | log2 := new(big.Int).Lsh(new(big.Int).Sub(big.NewInt(msb), big.NewInt(128)), 64) 171 | 172 | for i := 0; i < 14; i++ { 173 | r = new(big.Int).Rsh(new(big.Int).Mul(r, r), 127) 174 | f := new(big.Int).Rsh(r, 128) 175 | log2 = new(big.Int).Or(log2, new(big.Int).Lsh(f, uint(63-i))) 176 | r = new(big.Int).Rsh(r, uint(f.Int64())) 177 | } 178 | 179 | logSqrt10001 := new(big.Int).Mul(log2, magicSqrt10001) 180 | 181 | tickLow := new(big.Int).Rsh(new(big.Int).Sub(logSqrt10001, magicTickLow), 128).Int64() 182 | tickHigh := new(big.Int).Rsh(new(big.Int).Add(logSqrt10001, magicTickHigh), 128).Int64() 183 | 184 | if tickLow == tickHigh { 185 | return int(tickLow), nil 186 | } 187 | 188 | sqrtRatio, err := GetSqrtRatioAtTick(int(tickHigh)) 189 | if err != nil { 190 | return 0, err 191 | } 192 | if sqrtRatio.Cmp(sqrtRatioX96) <= 0 { 193 | return int(tickHigh), nil 194 | } else { 195 | return int(tickLow), nil 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /periphery/quoter_test.go: -------------------------------------------------------------------------------- 1 | package periphery 2 | 3 | import ( 4 | "math/big" 5 | "testing" 6 | 7 | core "github.com/daoleno/uniswap-sdk-core/entities" 8 | "github.com/daoleno/uniswapv3-sdk/entities" 9 | "github.com/daoleno/uniswapv3-sdk/utils" 10 | "github.com/ethereum/go-ethereum/common/hexutil" 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func TestEncodeRouteToPath(t *testing.T) { 15 | // packs them for exact input single hop 16 | p, _ := EncodeRouteToPath(route_0_1, false) 17 | assert.Equal(t, "0x0000000000000000000000000000000000000001000bb80000000000000000000000000000000000000002", hexutil.Encode(p)) 18 | 19 | // packs them correctly for exact output single hop 20 | p, _ = EncodeRouteToPath(route_0_1, true) 21 | assert.Equal(t, "0x0000000000000000000000000000000000000002000bb80000000000000000000000000000000000000001", hexutil.Encode(p)) 22 | 23 | // packs them correctly for multihop exact input 24 | p, _ = EncodeRouteToPath(route_0_1_2, false) 25 | assert.Equal(t, "0x0000000000000000000000000000000000000001000bb800000000000000000000000000000000000000020001f40000000000000000000000000000000000000003", hexutil.Encode(p)) 26 | 27 | // packs them correctly for multihop exact output 28 | p, _ = EncodeRouteToPath(route_0_1_2, true) 29 | assert.Equal(t, "0x00000000000000000000000000000000000000030001f40000000000000000000000000000000000000002000bb80000000000000000000000000000000000000001", hexutil.Encode(p)) 30 | 31 | // wraps ether input for exact input single hop 32 | p, _ = EncodeRouteToPath(route_weth_0, false) 33 | assert.Equal(t, "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000bb80000000000000000000000000000000000000001", hexutil.Encode(p)) 34 | 35 | // wraps ether input for exact output single hop 36 | p, _ = EncodeRouteToPath(route_weth_0, true) 37 | assert.Equal(t, "0x0000000000000000000000000000000000000001000bb8c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", hexutil.Encode(p)) 38 | 39 | // wraps ether input for exact input multihop 40 | p, _ = EncodeRouteToPath(route_weth_0_1, false) 41 | assert.Equal(t, "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000bb80000000000000000000000000000000000000001000bb80000000000000000000000000000000000000002", hexutil.Encode(p)) 42 | 43 | // wraps ether input for exact output multihop 44 | p, _ = EncodeRouteToPath(route_weth_0_1, true) 45 | assert.Equal(t, "0x0000000000000000000000000000000000000002000bb80000000000000000000000000000000000000001000bb8c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", hexutil.Encode(p)) 46 | 47 | // wraps ether output for exact input single hop 48 | p, _ = EncodeRouteToPath(route_0_weth, false) 49 | assert.Equal(t, "0x0000000000000000000000000000000000000001000bb8c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", hexutil.Encode(p)) 50 | 51 | // wraps ether output for exact output single hop 52 | p, _ = EncodeRouteToPath(route_0_weth, true) 53 | assert.Equal(t, "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000bb80000000000000000000000000000000000000001", hexutil.Encode(p)) 54 | 55 | // wraps ether output for exact input multihop 56 | p, _ = EncodeRouteToPath(route_0_1_weth, false) 57 | assert.Equal(t, "0x0000000000000000000000000000000000000001000bb80000000000000000000000000000000000000002000bb8c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", hexutil.Encode(p)) 58 | 59 | // wraps ether output for exact output multihop 60 | p, _ = EncodeRouteToPath(route_0_1_weth, true) 61 | assert.Equal(t, "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000bb80000000000000000000000000000000000000002000bb80000000000000000000000000000000000000001", hexutil.Encode(p)) 62 | } 63 | 64 | func TestQuoteCallParameters(t *testing.T) { 65 | pool_0_1 := makePool(token0, token1) 66 | pool_1_weth := makePool(token1, weth) 67 | 68 | // single trade input 69 | // single-hop exact input 70 | r, _ := entities.NewRoute([]*entities.Pool{pool_0_1}, token0, token1) 71 | trade, _ := entities.FromRoute(r, core.FromRawAmount(token0, big.NewInt(100)), core.ExactInput) 72 | params, err := QuoteCallParameters(trade.Swaps[0].Route, trade.InputAmount(), trade.TradeType, nil) 73 | if err != nil { 74 | t.Fatal(err) 75 | } 76 | assert.Equal(t, "0xf7729d43000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000bb800000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000000", hexutil.Encode(params.Calldata)) 77 | assert.Equal(t, "0x00", utils.ToHex(params.Value)) 78 | 79 | // single-hop exact output 80 | r, _ = entities.NewRoute([]*entities.Pool{pool_0_1}, token0, token1) 81 | trade, _ = entities.FromRoute(r, core.FromRawAmount(token1, big.NewInt(100)), core.ExactOutput) 82 | params, err = QuoteCallParameters(trade.Swaps[0].Route, trade.OutputAmount(), trade.TradeType, nil) 83 | if err != nil { 84 | t.Fatal(err) 85 | } 86 | assert.Equal(t, "0x30d07f21000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000bb800000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000000", hexutil.Encode(params.Calldata)) 87 | assert.Equal(t, "0x00", utils.ToHex(params.Value)) 88 | 89 | // multi-hop exact input 90 | r, _ = entities.NewRoute([]*entities.Pool{pool_0_1, pool_1_weth}, token0, weth) 91 | trade, _ = entities.FromRoute(r, core.FromRawAmount(token0, big.NewInt(100)), core.ExactInput) 92 | route, _ := trade.Route() 93 | params, err = QuoteCallParameters(route, trade.InputAmount(), trade.TradeType, nil) 94 | if err != nil { 95 | t.Fatal(err) 96 | } 97 | assert.Equal(t, "0xcdca17530000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000000000000000000420000000000000000000000000000000000000001000bb80000000000000000000000000000000000000002000bb8c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000000000000000", hexutil.Encode(params.Calldata)) 98 | assert.Equal(t, "0x00", utils.ToHex(params.Value)) 99 | 100 | // multi-hop exact output 101 | r, _ = entities.NewRoute([]*entities.Pool{pool_0_1, pool_1_weth}, token0, weth) 102 | trade, _ = entities.FromRoute(r, core.FromRawAmount(weth, big.NewInt(100)), core.ExactOutput) 103 | route, _ = trade.Route() 104 | params, err = QuoteCallParameters(route, trade.OutputAmount(), trade.TradeType, nil) 105 | if err != nil { 106 | t.Fatal(err) 107 | } 108 | assert.Equal(t, "0x2f80bb1d000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000042c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000bb80000000000000000000000000000000000000002000bb80000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000", hexutil.Encode(params.Calldata)) 109 | assert.Equal(t, "0x00", utils.ToHex(params.Value)) 110 | 111 | // sqrtPriceLimitX96 112 | r, _ = entities.NewRoute([]*entities.Pool{pool_0_1}, token0, token1) 113 | trade, _ = entities.FromRoute(r, core.FromRawAmount(token0, big.NewInt(100)), core.ExactInput) 114 | route, _ = trade.Route() 115 | params, err = QuoteCallParameters(route, trade.InputAmount(), trade.TradeType, &QuoteOptions{SqrtPriceLimitX96: new(big.Int).Exp(big.NewInt(2), big.NewInt(128), nil)}) 116 | if err != nil { 117 | t.Fatal(err) 118 | } 119 | assert.Equal(t, "0xf7729d43000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000bb800000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000100000000000000000000000000000000", hexutil.Encode(params.Calldata)) 120 | assert.Equal(t, "0x00", utils.ToHex(params.Value)) 121 | } 122 | -------------------------------------------------------------------------------- /periphery/contracts/interfaces/IV3Migrator.sol/IV3Migrator.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "IV3Migrator", 4 | "sourceName": "contracts/interfaces/IV3Migrator.sol", 5 | "abi": [ 6 | { 7 | "inputs": [ 8 | { 9 | "internalType": "address", 10 | "name": "token0", 11 | "type": "address" 12 | }, 13 | { 14 | "internalType": "address", 15 | "name": "token1", 16 | "type": "address" 17 | }, 18 | { 19 | "internalType": "uint24", 20 | "name": "fee", 21 | "type": "uint24" 22 | }, 23 | { 24 | "internalType": "uint160", 25 | "name": "sqrtPriceX96", 26 | "type": "uint160" 27 | } 28 | ], 29 | "name": "createAndInitializePoolIfNecessary", 30 | "outputs": [ 31 | { 32 | "internalType": "address", 33 | "name": "pool", 34 | "type": "address" 35 | } 36 | ], 37 | "stateMutability": "payable", 38 | "type": "function" 39 | }, 40 | { 41 | "inputs": [ 42 | { 43 | "components": [ 44 | { 45 | "internalType": "address", 46 | "name": "pair", 47 | "type": "address" 48 | }, 49 | { 50 | "internalType": "uint256", 51 | "name": "liquidityToMigrate", 52 | "type": "uint256" 53 | }, 54 | { 55 | "internalType": "uint8", 56 | "name": "percentageToMigrate", 57 | "type": "uint8" 58 | }, 59 | { 60 | "internalType": "address", 61 | "name": "token0", 62 | "type": "address" 63 | }, 64 | { 65 | "internalType": "address", 66 | "name": "token1", 67 | "type": "address" 68 | }, 69 | { 70 | "internalType": "uint24", 71 | "name": "fee", 72 | "type": "uint24" 73 | }, 74 | { 75 | "internalType": "int24", 76 | "name": "tickLower", 77 | "type": "int24" 78 | }, 79 | { 80 | "internalType": "int24", 81 | "name": "tickUpper", 82 | "type": "int24" 83 | }, 84 | { 85 | "internalType": "uint256", 86 | "name": "amount0Min", 87 | "type": "uint256" 88 | }, 89 | { 90 | "internalType": "uint256", 91 | "name": "amount1Min", 92 | "type": "uint256" 93 | }, 94 | { 95 | "internalType": "address", 96 | "name": "recipient", 97 | "type": "address" 98 | }, 99 | { 100 | "internalType": "uint256", 101 | "name": "deadline", 102 | "type": "uint256" 103 | }, 104 | { 105 | "internalType": "bool", 106 | "name": "refundAsETH", 107 | "type": "bool" 108 | } 109 | ], 110 | "internalType": "struct IV3Migrator.MigrateParams", 111 | "name": "params", 112 | "type": "tuple" 113 | } 114 | ], 115 | "name": "migrate", 116 | "outputs": [], 117 | "stateMutability": "nonpayable", 118 | "type": "function" 119 | }, 120 | { 121 | "inputs": [ 122 | { 123 | "internalType": "bytes[]", 124 | "name": "data", 125 | "type": "bytes[]" 126 | } 127 | ], 128 | "name": "multicall", 129 | "outputs": [ 130 | { 131 | "internalType": "bytes[]", 132 | "name": "results", 133 | "type": "bytes[]" 134 | } 135 | ], 136 | "stateMutability": "payable", 137 | "type": "function" 138 | }, 139 | { 140 | "inputs": [ 141 | { 142 | "internalType": "address", 143 | "name": "token", 144 | "type": "address" 145 | }, 146 | { 147 | "internalType": "uint256", 148 | "name": "value", 149 | "type": "uint256" 150 | }, 151 | { 152 | "internalType": "uint256", 153 | "name": "deadline", 154 | "type": "uint256" 155 | }, 156 | { 157 | "internalType": "uint8", 158 | "name": "v", 159 | "type": "uint8" 160 | }, 161 | { 162 | "internalType": "bytes32", 163 | "name": "r", 164 | "type": "bytes32" 165 | }, 166 | { 167 | "internalType": "bytes32", 168 | "name": "s", 169 | "type": "bytes32" 170 | } 171 | ], 172 | "name": "selfPermit", 173 | "outputs": [], 174 | "stateMutability": "payable", 175 | "type": "function" 176 | }, 177 | { 178 | "inputs": [ 179 | { 180 | "internalType": "address", 181 | "name": "token", 182 | "type": "address" 183 | }, 184 | { 185 | "internalType": "uint256", 186 | "name": "nonce", 187 | "type": "uint256" 188 | }, 189 | { 190 | "internalType": "uint256", 191 | "name": "expiry", 192 | "type": "uint256" 193 | }, 194 | { 195 | "internalType": "uint8", 196 | "name": "v", 197 | "type": "uint8" 198 | }, 199 | { 200 | "internalType": "bytes32", 201 | "name": "r", 202 | "type": "bytes32" 203 | }, 204 | { 205 | "internalType": "bytes32", 206 | "name": "s", 207 | "type": "bytes32" 208 | } 209 | ], 210 | "name": "selfPermitAllowed", 211 | "outputs": [], 212 | "stateMutability": "payable", 213 | "type": "function" 214 | }, 215 | { 216 | "inputs": [ 217 | { 218 | "internalType": "address", 219 | "name": "token", 220 | "type": "address" 221 | }, 222 | { 223 | "internalType": "uint256", 224 | "name": "nonce", 225 | "type": "uint256" 226 | }, 227 | { 228 | "internalType": "uint256", 229 | "name": "expiry", 230 | "type": "uint256" 231 | }, 232 | { 233 | "internalType": "uint8", 234 | "name": "v", 235 | "type": "uint8" 236 | }, 237 | { 238 | "internalType": "bytes32", 239 | "name": "r", 240 | "type": "bytes32" 241 | }, 242 | { 243 | "internalType": "bytes32", 244 | "name": "s", 245 | "type": "bytes32" 246 | } 247 | ], 248 | "name": "selfPermitAllowedIfNecessary", 249 | "outputs": [], 250 | "stateMutability": "payable", 251 | "type": "function" 252 | }, 253 | { 254 | "inputs": [ 255 | { 256 | "internalType": "address", 257 | "name": "token", 258 | "type": "address" 259 | }, 260 | { 261 | "internalType": "uint256", 262 | "name": "value", 263 | "type": "uint256" 264 | }, 265 | { 266 | "internalType": "uint256", 267 | "name": "deadline", 268 | "type": "uint256" 269 | }, 270 | { 271 | "internalType": "uint8", 272 | "name": "v", 273 | "type": "uint8" 274 | }, 275 | { 276 | "internalType": "bytes32", 277 | "name": "r", 278 | "type": "bytes32" 279 | }, 280 | { 281 | "internalType": "bytes32", 282 | "name": "s", 283 | "type": "bytes32" 284 | } 285 | ], 286 | "name": "selfPermitIfNecessary", 287 | "outputs": [], 288 | "stateMutability": "payable", 289 | "type": "function" 290 | } 291 | ], 292 | "bytecode": "0x", 293 | "deployedBytecode": "0x", 294 | "linkReferences": {}, 295 | "deployedLinkReferences": {} 296 | } 297 | -------------------------------------------------------------------------------- /periphery/contracts/lens/UniswapInterfaceMulticall.sol/UniswapInterfaceMulticall.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "UniswapInterfaceMulticall", 4 | "sourceName": "contracts/lens/UniswapInterfaceMulticall.sol", 5 | "abi": [ 6 | { 7 | "inputs": [], 8 | "name": "getCurrentBlockTimestamp", 9 | "outputs": [ 10 | { 11 | "internalType": "uint256", 12 | "name": "timestamp", 13 | "type": "uint256" 14 | } 15 | ], 16 | "stateMutability": "view", 17 | "type": "function" 18 | }, 19 | { 20 | "inputs": [ 21 | { 22 | "internalType": "address", 23 | "name": "addr", 24 | "type": "address" 25 | } 26 | ], 27 | "name": "getEthBalance", 28 | "outputs": [ 29 | { 30 | "internalType": "uint256", 31 | "name": "balance", 32 | "type": "uint256" 33 | } 34 | ], 35 | "stateMutability": "view", 36 | "type": "function" 37 | }, 38 | { 39 | "inputs": [ 40 | { 41 | "components": [ 42 | { 43 | "internalType": "address", 44 | "name": "target", 45 | "type": "address" 46 | }, 47 | { 48 | "internalType": "uint256", 49 | "name": "gasLimit", 50 | "type": "uint256" 51 | }, 52 | { 53 | "internalType": "bytes", 54 | "name": "callData", 55 | "type": "bytes" 56 | } 57 | ], 58 | "internalType": "struct UniswapInterfaceMulticall.Call[]", 59 | "name": "calls", 60 | "type": "tuple[]" 61 | } 62 | ], 63 | "name": "multicall", 64 | "outputs": [ 65 | { 66 | "internalType": "uint256", 67 | "name": "blockNumber", 68 | "type": "uint256" 69 | }, 70 | { 71 | "components": [ 72 | { 73 | "internalType": "bool", 74 | "name": "success", 75 | "type": "bool" 76 | }, 77 | { 78 | "internalType": "uint256", 79 | "name": "gasUsed", 80 | "type": "uint256" 81 | }, 82 | { 83 | "internalType": "bytes", 84 | "name": "returnData", 85 | "type": "bytes" 86 | } 87 | ], 88 | "internalType": "struct UniswapInterfaceMulticall.Result[]", 89 | "name": "returnData", 90 | "type": "tuple[]" 91 | } 92 | ], 93 | "stateMutability": "nonpayable", 94 | "type": "function" 95 | } 96 | ], 97 | "bytecode": "0x608060405234801561001057600080fd5b50610567806100206000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c80630f28c97d146100465780631749e1e3146100645780634d2301cc14610085575b600080fd5b61004e610098565b60405161005b919061041f565b60405180910390f35b6100776100723660046102a7565b61009c565b60405161005b929190610428565b61004e610093366004610286565b610220565b4290565b8051439060609067ffffffffffffffff811180156100b957600080fd5b506040519080825280602002602001820160405280156100f357816020015b6100e061023a565b8152602001906001900390816100d85790505b50905060005b835181101561021a57600080600086848151811061011357fe5b60200260200101516000015187858151811061012b57fe5b60200260200101516020015188868151811061014357fe5b60200260200101516040015192509250925060005a90506000808573ffffffffffffffffffffffffffffffffffffffff1685856040516101839190610403565b60006040518083038160008787f1925050503d80600081146101c1576040519150601f19603f3d011682016040523d82523d6000602084013e6101c6565b606091505b509150915060005a8403905060405180606001604052808415158152602001828152602001838152508989815181106101fb57fe5b60200260200101819052505050505050505080806001019150506100f9565b50915091565b73ffffffffffffffffffffffffffffffffffffffff163190565b604051806060016040528060001515815260200160008152602001606081525090565b803573ffffffffffffffffffffffffffffffffffffffff8116811461028157600080fd5b919050565b600060208284031215610297578081fd5b6102a08261025d565b9392505050565b600060208083850312156102b9578182fd5b823567ffffffffffffffff808211156102d0578384fd5b818501915085601f8301126102e3578384fd5b8135818111156102ef57fe5b6102fc8485830201610506565b81815284810190848601875b848110156103f457813587017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0606081838f03011215610346578a8bfd5b60408051606081018181108b8211171561035c57fe5b8252610369848d0161025d565b8152818401358c82015260608401358a811115610384578d8efd5b8085019450508e603f850112610398578c8dfd5b8b8401358a8111156103a657fe5b6103b68d85601f84011601610506565b93508084528f838287010111156103cb578d8efd5b808386018e86013783018c018d9052908101919091528552509287019290870190600101610308565b50909998505050505050505050565b6000825161041581846020870161052a565b9190910192915050565b90815260200190565b600060408083018584526020828186015281865180845260609350838701915083838202880101838901875b838110156104f6578983037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa001855281518051151584528681015187850152880151888401889052805188850181905260806104b582828801858c0161052a565b96880196601f919091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01694909401909301925090850190600101610454565b50909a9950505050505050505050565b60405181810167ffffffffffffffff8111828210171561052257fe5b604052919050565b60005b8381101561054557818101518382015260200161052d565b83811115610554576000848401525b5050505056fea164736f6c6343000706000a", 98 | "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100415760003560e01c80630f28c97d146100465780631749e1e3146100645780634d2301cc14610085575b600080fd5b61004e610098565b60405161005b919061041f565b60405180910390f35b6100776100723660046102a7565b61009c565b60405161005b929190610428565b61004e610093366004610286565b610220565b4290565b8051439060609067ffffffffffffffff811180156100b957600080fd5b506040519080825280602002602001820160405280156100f357816020015b6100e061023a565b8152602001906001900390816100d85790505b50905060005b835181101561021a57600080600086848151811061011357fe5b60200260200101516000015187858151811061012b57fe5b60200260200101516020015188868151811061014357fe5b60200260200101516040015192509250925060005a90506000808573ffffffffffffffffffffffffffffffffffffffff1685856040516101839190610403565b60006040518083038160008787f1925050503d80600081146101c1576040519150601f19603f3d011682016040523d82523d6000602084013e6101c6565b606091505b509150915060005a8403905060405180606001604052808415158152602001828152602001838152508989815181106101fb57fe5b60200260200101819052505050505050505080806001019150506100f9565b50915091565b73ffffffffffffffffffffffffffffffffffffffff163190565b604051806060016040528060001515815260200160008152602001606081525090565b803573ffffffffffffffffffffffffffffffffffffffff8116811461028157600080fd5b919050565b600060208284031215610297578081fd5b6102a08261025d565b9392505050565b600060208083850312156102b9578182fd5b823567ffffffffffffffff808211156102d0578384fd5b818501915085601f8301126102e3578384fd5b8135818111156102ef57fe5b6102fc8485830201610506565b81815284810190848601875b848110156103f457813587017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0606081838f03011215610346578a8bfd5b60408051606081018181108b8211171561035c57fe5b8252610369848d0161025d565b8152818401358c82015260608401358a811115610384578d8efd5b8085019450508e603f850112610398578c8dfd5b8b8401358a8111156103a657fe5b6103b68d85601f84011601610506565b93508084528f838287010111156103cb578d8efd5b808386018e86013783018c018d9052908101919091528552509287019290870190600101610308565b50909998505050505050505050565b6000825161041581846020870161052a565b9190910192915050565b90815260200190565b600060408083018584526020828186015281865180845260609350838701915083838202880101838901875b838110156104f6578983037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa001855281518051151584528681015187850152880151888401889052805188850181905260806104b582828801858c0161052a565b96880196601f919091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01694909401909301925090850190600101610454565b50909a9950505050505050505050565b60405181810167ffffffffffffffff8111828210171561052257fe5b604052919050565b60005b8381101561054557818101518382015260200161052d565b83811115610554576000848401525b5050505056fea164736f6c6343000706000a", 99 | "linkReferences": {}, 100 | "deployedLinkReferences": {} 101 | } 102 | -------------------------------------------------------------------------------- /utils/max_liquidity_for_amounts_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "math/big" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/daoleno/uniswap-sdk-core/entities" 9 | ) 10 | 11 | func TestMaxLiquidityForAmounts(t *testing.T) { 12 | type args struct { 13 | sqrtRatioCurrentX96 *big.Int 14 | sqrtRatioAX96 *big.Int 15 | sqrtRatioBX96 *big.Int 16 | amount0 *big.Int 17 | amount1 *big.Int 18 | useFullPrecision bool 19 | } 20 | lgamounts0, _ := new(big.Int).SetString("1214437677402050006470401421068302637228917309992228326090730924516431320489727", 10) 21 | lgamounts1, _ := new(big.Int).SetString("1214437677402050006470401421098959354205873606971497132040612572422243086574654", 10) 22 | lgamounts2, _ := new(big.Int).SetString("1214437677402050006470401421082903520362793114274352355276488318240158678126184", 10) 23 | tests := []struct { 24 | name string 25 | args args 26 | want *big.Int 27 | }{ 28 | { 29 | name: "imprecise - price inside - 100 token0, 200 token1", 30 | args: args{ 31 | EncodeSqrtRatioX96(big.NewInt(1), big.NewInt(1)), 32 | EncodeSqrtRatioX96(big.NewInt(100), big.NewInt(110)), 33 | EncodeSqrtRatioX96(big.NewInt(110), big.NewInt(100)), 34 | big.NewInt(100), 35 | big.NewInt(200), 36 | false, 37 | }, 38 | want: big.NewInt(2148), 39 | }, 40 | { 41 | name: "imprecise - price inside - 100 token0, max token1", 42 | args: args{ 43 | EncodeSqrtRatioX96(big.NewInt(1), big.NewInt(1)), 44 | EncodeSqrtRatioX96(big.NewInt(100), big.NewInt(110)), 45 | EncodeSqrtRatioX96(big.NewInt(110), big.NewInt(100)), 46 | big.NewInt(100), 47 | entities.MaxUint256, 48 | false, 49 | }, 50 | want: big.NewInt(2148), 51 | }, 52 | { 53 | name: "imprecise - price inside - max token0, 200 token1", 54 | args: args{ 55 | EncodeSqrtRatioX96(big.NewInt(1), big.NewInt(1)), 56 | EncodeSqrtRatioX96(big.NewInt(100), big.NewInt(110)), 57 | EncodeSqrtRatioX96(big.NewInt(110), big.NewInt(100)), 58 | entities.MaxUint256, 59 | big.NewInt(200), 60 | false, 61 | }, 62 | want: big.NewInt(4297), 63 | }, 64 | { 65 | name: "imprecise - price below - 100 token0, 200 token1", 66 | args: args{ 67 | EncodeSqrtRatioX96(big.NewInt(99), big.NewInt(110)), 68 | EncodeSqrtRatioX96(big.NewInt(100), big.NewInt(110)), 69 | EncodeSqrtRatioX96(big.NewInt(110), big.NewInt(100)), 70 | big.NewInt(100), 71 | big.NewInt(200), 72 | false, 73 | }, 74 | want: big.NewInt(1048), 75 | }, 76 | { 77 | name: "imprecise - price below - 100 token0, max token1", 78 | args: args{ 79 | EncodeSqrtRatioX96(big.NewInt(99), big.NewInt(110)), 80 | EncodeSqrtRatioX96(big.NewInt(100), big.NewInt(110)), 81 | EncodeSqrtRatioX96(big.NewInt(110), big.NewInt(100)), 82 | big.NewInt(100), 83 | entities.MaxUint256, 84 | false, 85 | }, 86 | want: big.NewInt(1048), 87 | }, 88 | { 89 | name: "imprecise - price below - max token0, 200 token1", 90 | args: args{ 91 | EncodeSqrtRatioX96(big.NewInt(99), big.NewInt(110)), 92 | EncodeSqrtRatioX96(big.NewInt(100), big.NewInt(110)), 93 | EncodeSqrtRatioX96(big.NewInt(110), big.NewInt(100)), 94 | entities.MaxUint256, 95 | big.NewInt(200), 96 | false, 97 | }, 98 | want: lgamounts0, 99 | }, 100 | { 101 | name: "imprecise - price above - 100 token0, 200 token1", 102 | args: args{ 103 | EncodeSqrtRatioX96(big.NewInt(111), big.NewInt(100)), 104 | EncodeSqrtRatioX96(big.NewInt(100), big.NewInt(110)), 105 | EncodeSqrtRatioX96(big.NewInt(110), big.NewInt(100)), 106 | big.NewInt(100), 107 | big.NewInt(200), 108 | false, 109 | }, 110 | want: big.NewInt(2097), 111 | }, 112 | { 113 | name: "imprecise - price above - 100 token0, max token1", 114 | args: args{ 115 | EncodeSqrtRatioX96(big.NewInt(111), big.NewInt(100)), 116 | EncodeSqrtRatioX96(big.NewInt(100), big.NewInt(110)), 117 | EncodeSqrtRatioX96(big.NewInt(110), big.NewInt(100)), 118 | big.NewInt(100), 119 | entities.MaxUint256, 120 | false, 121 | }, 122 | want: lgamounts1, 123 | }, 124 | { 125 | name: "imprecise - price above - max token0, 200 token1", 126 | args: args{ 127 | EncodeSqrtRatioX96(big.NewInt(111), big.NewInt(100)), 128 | EncodeSqrtRatioX96(big.NewInt(100), big.NewInt(110)), 129 | EncodeSqrtRatioX96(big.NewInt(110), big.NewInt(100)), 130 | entities.MaxUint256, 131 | big.NewInt(200), 132 | false, 133 | }, 134 | want: big.NewInt(2097), 135 | }, 136 | { 137 | name: "precise - price inside - 100 token0, 200 token1", 138 | args: args{ 139 | EncodeSqrtRatioX96(big.NewInt(1), big.NewInt(1)), 140 | EncodeSqrtRatioX96(big.NewInt(100), big.NewInt(110)), 141 | EncodeSqrtRatioX96(big.NewInt(110), big.NewInt(100)), 142 | big.NewInt(100), 143 | big.NewInt(200), 144 | true, 145 | }, 146 | want: big.NewInt(2148), 147 | }, 148 | { 149 | name: "precise - price inside - 100 token0, max token1", 150 | args: args{ 151 | EncodeSqrtRatioX96(big.NewInt(1), big.NewInt(1)), 152 | EncodeSqrtRatioX96(big.NewInt(100), big.NewInt(110)), 153 | EncodeSqrtRatioX96(big.NewInt(110), big.NewInt(100)), 154 | big.NewInt(100), 155 | entities.MaxUint256, 156 | true, 157 | }, 158 | want: big.NewInt(2148), 159 | }, 160 | { 161 | name: "precise - price inside - max token0, 200 token1", 162 | args: args{ 163 | EncodeSqrtRatioX96(big.NewInt(1), big.NewInt(1)), 164 | EncodeSqrtRatioX96(big.NewInt(100), big.NewInt(110)), 165 | EncodeSqrtRatioX96(big.NewInt(110), big.NewInt(100)), 166 | entities.MaxUint256, 167 | big.NewInt(200), 168 | true, 169 | }, 170 | want: big.NewInt(4297), 171 | }, 172 | { 173 | name: "precise - price below - 100 token0, 200 token1", 174 | args: args{ 175 | EncodeSqrtRatioX96(big.NewInt(99), big.NewInt(110)), 176 | EncodeSqrtRatioX96(big.NewInt(100), big.NewInt(110)), 177 | EncodeSqrtRatioX96(big.NewInt(110), big.NewInt(100)), 178 | big.NewInt(100), 179 | big.NewInt(200), 180 | true, 181 | }, 182 | want: big.NewInt(1048), 183 | }, 184 | { 185 | name: "precise - price below - 100 token0, max token1", 186 | args: args{ 187 | EncodeSqrtRatioX96(big.NewInt(99), big.NewInt(110)), 188 | EncodeSqrtRatioX96(big.NewInt(100), big.NewInt(110)), 189 | EncodeSqrtRatioX96(big.NewInt(110), big.NewInt(100)), 190 | big.NewInt(100), 191 | entities.MaxUint256, 192 | true, 193 | }, 194 | want: big.NewInt(1048), 195 | }, 196 | { 197 | name: "precise - price below - max token0, 200 token1", 198 | args: args{ 199 | EncodeSqrtRatioX96(big.NewInt(99), big.NewInt(110)), 200 | EncodeSqrtRatioX96(big.NewInt(100), big.NewInt(110)), 201 | EncodeSqrtRatioX96(big.NewInt(110), big.NewInt(100)), 202 | entities.MaxUint256, 203 | big.NewInt(200), 204 | true, 205 | }, 206 | want: lgamounts2, 207 | }, 208 | { 209 | name: "precise - price above - 100 token0, 200 token1", 210 | args: args{ 211 | EncodeSqrtRatioX96(big.NewInt(111), big.NewInt(100)), 212 | EncodeSqrtRatioX96(big.NewInt(100), big.NewInt(110)), 213 | EncodeSqrtRatioX96(big.NewInt(110), big.NewInt(100)), 214 | big.NewInt(100), 215 | big.NewInt(200), 216 | true, 217 | }, 218 | want: big.NewInt(2097), 219 | }, 220 | { 221 | name: "precise - price above - 100 token0, max token1", 222 | args: args{ 223 | EncodeSqrtRatioX96(big.NewInt(111), big.NewInt(100)), 224 | EncodeSqrtRatioX96(big.NewInt(100), big.NewInt(110)), 225 | EncodeSqrtRatioX96(big.NewInt(110), big.NewInt(100)), 226 | big.NewInt(100), 227 | entities.MaxUint256, 228 | true, 229 | }, 230 | want: lgamounts1, 231 | }, 232 | { 233 | name: "precise - price above - max token0, 200 token1", 234 | args: args{ 235 | EncodeSqrtRatioX96(big.NewInt(111), big.NewInt(100)), 236 | EncodeSqrtRatioX96(big.NewInt(100), big.NewInt(110)), 237 | EncodeSqrtRatioX96(big.NewInt(110), big.NewInt(100)), 238 | entities.MaxUint256, 239 | big.NewInt(200), 240 | true, 241 | }, 242 | want: big.NewInt(2097), 243 | }, 244 | } 245 | for _, tt := range tests { 246 | t.Run(tt.name, func(t *testing.T) { 247 | if got := MaxLiquidityForAmounts(tt.args.sqrtRatioCurrentX96, tt.args.sqrtRatioAX96, tt.args.sqrtRatioBX96, tt.args.amount0, tt.args.amount1, tt.args.useFullPrecision); !reflect.DeepEqual(got, tt.want) { 248 | t.Errorf("maxLiquidityForAmounts() = %v, want %v", got, tt.want) 249 | } 250 | }) 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/VictoriaMetrics/fastcache v1.6.0 h1:C/3Oi3EiBCqufydp1neRZkqcwmEiuRT9c3fqvvgKm5o= 2 | github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k= 3 | github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU= 4 | github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= 5 | github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk= 6 | github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= 7 | github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= 8 | github.com/daoleno/uniswap-sdk-core v0.1.5 h1:VlU6NXnJBJ75D3GmX01CGIEMoiizXlu9v+jSEj26lhM= 9 | github.com/daoleno/uniswap-sdk-core v0.1.5/go.mod h1:OV1Kvws5JShxPz3qFpjpkuZB4gdebRpqm/AcYMZ7TZQ= 10 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 11 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 12 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 13 | github.com/deckarep/golang-set v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsPppp4= 14 | github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo= 15 | github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= 16 | github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= 17 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= 18 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= 19 | github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= 20 | github.com/ethereum/go-ethereum v1.10.20 h1:75IW830ClSS40yrQC1ZCMZCt5I+zU16oqId2SiQwdQ4= 21 | github.com/ethereum/go-ethereum v1.10.20/go.mod h1:LWUN82TCHGpxB3En5HVmLLzPD7YSrEUFmFfN1nKkVN0= 22 | github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c= 23 | github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI= 24 | github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= 25 | github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= 26 | github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= 27 | github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= 28 | github.com/golang-jwt/jwt/v4 v4.3.0 h1:kHL1vqdqWNfATmA0FNMdmZNMyZI1U6O31X4rlIPoBog= 29 | github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= 30 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 31 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 32 | github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= 33 | github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 34 | github.com/hashicorp/go-bexpr v0.1.10 h1:9kuI5PFotCboP3dkDYFr/wi0gg0QVbSNz5oFRpxn4uE= 35 | github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs= 36 | github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= 37 | github.com/holiman/uint256 v1.2.0 h1:gpSYcPLWGv4sG43I2mVLiDZCNDh/EpGjSk8tmtxitHM= 38 | github.com/huin/goupnp v1.0.3 h1:N8No57ls+MnjlB+JPiCVSOyy/ot7MJTqlo7rn+NYSqQ= 39 | github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= 40 | github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= 41 | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= 42 | github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= 43 | github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= 44 | github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjUEN1uBnDo34A= 45 | github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= 46 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 47 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 48 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 49 | github.com/prometheus/tsdb v0.7.1 h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA= 50 | github.com/rjeczalik/notify v0.9.2 h1:MiTWrPj55mNDHEiIX5YUSKefw/+lCQVoAFmD6oQm5w8= 51 | github.com/rjeczalik/notify v0.9.2/go.mod h1:aErll2f0sUX9PXZnVNyeiObbmTlk5jnMoCa4QEjJeqM= 52 | github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= 53 | github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= 54 | github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= 55 | github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= 56 | github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= 57 | github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= 58 | github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4 h1:Gb2Tyox57NRNuZ2d3rmvB3pcmbu7O1RS3m8WRx7ilrg= 59 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 60 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 61 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 62 | github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= 63 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 64 | github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a h1:1ur3QoCqvE5fl+nylMaIr9PVV1w343YRDtsy+Rwu7XI= 65 | github.com/tklauser/go-sysconf v0.3.10 h1:IJ1AZGZRWbY8T5Vfk04D9WOA5WSejdflXxP03OUqALw= 66 | github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk= 67 | github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ= 68 | github.com/tklauser/numcpus v0.5.0 h1:ooe7gN0fg6myJ0EKoTAf5hebTZrH52px3New/D9iJ+A= 69 | github.com/tklauser/numcpus v0.5.0/go.mod h1:OGzpTxpcIMNGYQdit2BYL1pvk/dSOaJWjKoflh+RQjo= 70 | github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef h1:wHSqTBrZW24CsNJDfeh9Ex6Pm0Rcpc7qrgKBiL44vF4= 71 | github.com/urfave/cli/v2 v2.10.2 h1:x3p8awjp/2arX+Nl/G2040AZpOCHS/eMJJ1/a+mye4Y= 72 | github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= 73 | github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= 74 | github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= 75 | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY= 76 | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 77 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= 78 | golang.org/x/sys v0.0.0-20180926160741-c2ed4eda69e7/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 79 | golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 80 | golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 81 | golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 82 | golang.org/x/sys v0.0.0-20220702020025-31831981b65f h1:xdsejrW/0Wf2diT5CPp3XmKUNbr7Xvw8kYilQ+6qjRY= 83 | golang.org/x/sys v0.0.0-20220702020025-31831981b65f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 84 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= 85 | golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE= 86 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 87 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 88 | gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= 89 | gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= 90 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 91 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 92 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 93 | --------------------------------------------------------------------------------