diff --git a/docs/sdk/swap-widget/guides/swap-widget.mdx b/docs/sdk/swap-widget/guides/swap-widget.mdx index 891b8f0e8e..6c04c46b4d 100644 --- a/docs/sdk/swap-widget/guides/swap-widget.mdx +++ b/docs/sdk/swap-widget/guides/swap-widget.mdx @@ -53,7 +53,7 @@ function App() { } ``` -That’s it! You should now see a fully functional swap widget on your site. The widget is self-contained and gracefully handles all interactions with the Uniswap Protocol. It leverages the [Auto Router](/sdk/v3/guides/routing) to compute the best price across all Uniswap v2 and v3 pools. +That’s it! You should now see a fully functional swap widget on your site. The widget is self-contained and gracefully handles all interactions with the Uniswap Protocol. It leverages the [Auto Router](/sdk/v3/guides/swaps/routing) to compute the best price across all Uniswap v2 and v3 pools. See a full implementation of the swap widget in the `/cra` and `/nextjs` branches of the [widgets-demo](https://github.com/Uniswap/widgets-demo) repo. @@ -101,7 +101,7 @@ import { provider } from './your/provider' // We recommend you pass your own JSON-RPC endpoints. const jsonRpcUrlMap = { - 1: ['https://mainnet.infura.io/v3/'], + 1: ['https://mainnet.infura.io/v3/'], 3: ['https://ropsten.infura.io/v3/'] } @@ -116,7 +116,7 @@ function App() { JSON-RPC endpoints are used to read data when no `provider` is connected. We strongly recommend you pass either a Web3 Provider to the `provider` prop, or JSON-RPC endpoint URLs to the `jsonRpcUrlMap` prop. -The widget will use these endpoints to fetch on-chain data and submit transactions for signature. If the user connects a MetaMask wallet, the widget will use the JSON-RPC provided by MetaMask when possible. [(See a list of all chains supported on widget.)](https://github.com/Uniswap/widgets/blob/main/src/constants/chains.ts#L4) +The widget will use these endpoints to fetch on-chain data and submit transactions for signature. If the user connects a MetaMask wallet, the widget will use the JSON-RPC provided by MetaMask when possible. [(See a list of all chains supported on widget.)](https://github.com/Uniswap/widgets/blob/main/src/constants/chains.ts#L4) If you don’t yet have JSON-RPC endpoints, you can easily create them with services like [Chainnodes](https://www.chainnodes.org), [Infura](https://infura.io/product/ethereum) or [Alchemy](https://www.alchemy.com/supernode). diff --git a/docs/sdk/v3/guides/02-local-development.md b/docs/sdk/v3/guides/02-local-development.md index fd1808604c..84958bff24 100644 --- a/docs/sdk/v3/guides/02-local-development.md +++ b/docs/sdk/v3/guides/02-local-development.md @@ -33,8 +33,8 @@ one of the network you want to use. For this guide, the following packages are used: -- [`@uniswap/v3-sdk`](https://www.npmjs.com/package/@uniswap/v3-sdk) -- [`@uniswap/sdk-core`](https://www.npmjs.com/package/@uniswap/sdk-core) +- [`@uniswapfoundation/v3-sdk`](https://www.npmjs.com/package/@uniswapfoundation/v3-sdk) +- [`@uniswapfoundation/sdk-core`](https://www.npmjs.com/package/@uniswapfoundation/sdk-core) - [`ethers@5`](https://www.npmjs.com/package/ethers) Please note that we use ethers version 5, as this is still the most commonly used version of ethers.js. diff --git a/docs/sdk/v3/guides/advanced/03-active-liquidity.md b/docs/sdk/v3/guides/advanced/02-active-liquidity.md similarity index 100% rename from docs/sdk/v3/guides/advanced/03-active-liquidity.md rename to docs/sdk/v3/guides/advanced/02-active-liquidity.md diff --git a/docs/sdk/v3/guides/advanced/02-pool-data.md b/docs/sdk/v3/guides/advanced/02-pool-data.md deleted file mode 100644 index cbcdd0e260..0000000000 --- a/docs/sdk/v3/guides/advanced/02-pool-data.md +++ /dev/null @@ -1,369 +0,0 @@ ---- -id: pool-data -title: Fetching Pool Data ---- - -## Introduction - -This guide will cover how to initialize a Pool with full tick data to allow offchain calculations. It is based on the [Fetching Pool data example](https://github.com/Uniswap/examples/tree/main/v3-sdk/pool-data), found in the Uniswap code examples [repository](https://github.com/Uniswap/examples). To run this example, check out the guide's [README](https://github.com/Uniswap/examples/blob/main/v3-sdk/pool-data/README.md) and follow the setup instructions. - -:::info -If you need a briefer on the SDK and to learn more about how these guides connect to the examples repository, please visit our [background](./01-background.md) page! -::: - -In this example we will use **ethers JS** and **ethers-multicall** to construct a `Pool` object that we can use in the following guides. - -This guide will **cover**: - -1. Computing the Pool's address -2. Referencing the Pool contract and fetching metadata -3. Fetching the positions of all initialized Ticks with multicall -4. Fetching all ticks by their indices with a multicall -5. Constructing the Pool object - -At the end of the guide, we will have created a `Pool` Object that accurately represents the state of a V3 pool at the time we fetched it. - -For this guide, the following Uniswap packages are used: - -- [`@uniswap/v3-sdk`](https://www.npmjs.com/package/@uniswap/v3-sdk) -- [`@uniswap/sdk-core`](https://www.npmjs.com/package/@uniswap/sdk-core) - -We will also use the `ethers-multicall` npm package: - -- [`ethers-multicall`](https://www.npmjs.com/package/ethers-multicall) - -The core code of this guide can be found in [`fetcher.ts`](https://github.com/Uniswap/examples/tree/main/v3-sdk/multicall/src/libs/fetcher.ts) - -## Configuration - -The example accompanying this guide can be configured in the [`config.ts`](https://github.com/Uniswap/examples/tree/main/v3-sdk/multicall/src/config.ts) file. -The default configuration defines the rpc endpoint and the pool that is used for this guide: - -```typescript -export const CurrentConfig: ExampleConfig = { - env: Environment.MAINNET, - rpc: { - local: 'http://localhost:8545', - mainnet: 'https://mainnet.infura.io/v3/0ac57a06f2994538829c14745750d721', - }, - ... - pool: { - token0: USDC_TOKEN, - token1: WETH_TOKEN, - fee: FeeAmount.MEDIUM, - }, -} -``` - -FeeAmount.MEDIUM means that the pool has a swap fee of **0.3%**. -The `USDC_TOKEN` and `WETH_TOKEN` are defined in the [`constants.ts`](https://github.com/Uniswap/examples/tree/main/v3-sdk/multicall/src/libs/constants.ts) file: - -```typescript -export const WETH_TOKEN = new Token( - SupportedChainId.MAINNET, - '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', - 18, - 'WETH', - 'Wrapped Ether' -) - -export const USDC_TOKEN = new Token( - SupportedChainId.MAINNET, - '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', - 6, - 'USDC', - 'USD//C' -) -``` - -## Computing the Pool's deployment address - -In this example, we will construct the **USDC - WETH** Pool with **MEDIUM** fees. The SDK provides a method to compute the address: - -```typescript -import { Pool } from '@uniswap/v3-sdk' -import { CurrentConfig } from '../config.ts' - -const poolAddress = Pool.getAddress( - CurrentConfig.pool.token0, - CurrentConfig.pool.token1, - CurrentConfig.pool.fee - ) -``` - -Uniswap V3 allows 4 different Fee tiers when deploying a pool, so multiple pools can exist for each pair of tokens. - -## Creating a Pool Contract instance and fetching metadata - -Now that we have the address of a **USDC - ETH** Pool, we can construct an instance of an **ethers** `Contract` to interact with it. -To construct the Contract we need to provide the address of the contract, its ABI and a provider connected to an [RPC endpoint](https://www.chainnodes.org/docs). We get access to the contract's ABI through the `@uniswap/v3-core` package, which holds the core smart contracts of the Uniswap V3 protocol: - -```typescript -import { ethers } from 'ethers -import IUniswapV3PoolABI from '@uniswap/v3-core/artifacts/contracts/interfaces/IUniswapV3Pool.sol/IUniswapV3Pool.json' - -const provider = getProvider() -const poolContract = new ethers.Contract( - poolAddress, - IUniswapV3PoolABI.abi, - provider -) -``` - -The `getProvider()` function returns an `ethers.providers.JsonRpcProvider` with either the local or mainnet rpc url that we defined, depending on the Environment that we set in `config.ts`. - -Once we have set up our reference to the contract, we can proceed to access its methods. To construct our offchain representation of the Pool Contract, we need to fetch its liquidity, sqrtPrice, currently active tick and the full Tick data. -We get the **liquidity**, **sqrtPrice** and **tick** directly from the blockchain by calling `liquidity()`and `slot0()` on the Pool contract: - -```typescript -const [liquidity, slot0] = - await Promise.all([ - poolContract.liquidity(), - poolContract.slot0(), - ]) -``` - -The [slot0 function](../../../../contracts/v3/reference/core/interfaces/pool/IUniswapV3PoolState.md#slot0) represents the first (0th) storage slot of the pool and exposes multiple useful values in a single function: - -```solidity - function slot0( - ) external view returns ( - uint160 sqrtPriceX96, - int24 tick, - uint16 observationIndex, - uint16 observationCardinality, - uint16 observationCardinalityNext, - uint8 feeProtocol, - bool unlocked - ) -``` - -For our use case, we only need the `sqrtPriceX96` and the currently active `tick`. - -## Fetching all Ticks - -V3 pools use ticks to [concentrate liquidity](../../../../concepts/protocol/concentrated-liquidity.md) in price ranges and allow for better pricing of trades. -Even though most Pools only have a couple of **initialized ticks**, it is possible that a pools liquidity is defined by thousands of **initialized ticks**. -In that case, it can be very expensive or slow to get all of them with normal RPC calls. - -If you are not familiar with the concept of ticks, check out the [`introduction`](./01-introduction.md). - -To access tick data, we will use the `ticks` function of the V3 Pool contract: - -```solidity - function ticks( - int24 tick - ) external view returns ( - uint128 liquidityGross, - int128 liquidityNet, - uint256 feeGrowthOutside0X128, - uint256 feeGrowthOutside1X128, - int56 tickCumulativeOutside, - uint160 secondsPerLiquidityOutsideX128, - uint32 secondsOutside, - bool initialized - ) -``` - -The `tick` parameter that we provide the function with is the **index** (memory position) of the Tick we are trying to fetch. -To get the indices of all initialized Ticks of the Pool, we can calculate them from the **tickBitmaps**. -To fetch a `tickBitmap` function of the V3 Pool: - -```solidity - function tickBitmap( - int16 wordPosition - ) external view returns (uint256) -``` - -A pool stores lots of bitmaps, each of which contain the status of 256 Ticks. -The parameter `int16 wordPosition` the function accepts is the position of the bitMap we want to fetch. -We can calculate all the position of bitMaps (or words as they are sometimes called) from the `tickSpacing` of the Pool, which is in turn dependant on the Fee tier. - -So to summarise we need 4 steps to fetch all initialized ticks: - -1. Calculate all bitMap positions from the tickSpacing of the Pool. -2. Fetch all bitMaps using their positions. -3. Calculate the memory positions of all Ticks from the bitMaps. -4. Fetch all Ticks by their memory position. - -We will use multicalls for the fetch calls. - -## Multicall - -Multicall contracts **aggregate results** from multiple contract calls and therefore allow sending multiple contract calls in **one RPC request**. -This can improve the **speed** of fetching large amounts of data significantly and ensures that the data fetched is all from the **same block**. - -We will use the Multicall2 contract by MakerDAO. -We use the `ethers-muticall` npm package to easily interact with the Contract. - -## Calculating all bitMap positions - -As mentioned, Uniswap V3 Pools store **bitmaps**, also called *words*, that represent the state of **256 initializable ticks** at a time. -The value at a bit of a word is 1 if the tick at this index is initialized and 0 if it isn't. -We can calculate the positions of initialized ticks from the **words** of the Pool. - -All ticks of Uniswap V3 pools are between the indices `-887272` and `887272`. -We can calculate the minimum and maximum word from these indices and the Pool's tickSpacing: - -```typescript -function tickToWord(tick: number): number { - let compressed = Math.floor(tick / tickSpacing) - if (tick < 0 && tick % tickSpacing !== 0) { - compressed -= 1 - } - return tick >> 8 -} - -const minWord = tickToWord(-887272) -const maxWord = tickToWord(887272) -``` - -Ticks can only be initialized at indices that are **divisible by the tickSpacing**. -One word contains 256 ticks, so we can compress the ticks by right shifting 8 bit. - -## Fetching bitMaps from their position - -Knowing the positions of words in the Pool contract, we can now fetch them from the Pool using multicall and the `tickBitmap` read call. - -First we initialize our multicall providers and Pool Contract: - -```typescript -import { ethers } from 'ethers' -import { Contract, Provider } from 'ethers-multicall' - -const ethersProvider = new ethers.providers.JsonRpcProvider("...rpcUrl") -const multicallProvider = new Provider(ethersProvider) -await multicallProvider.init() - -const poolContract = new Contract(poolAddress, IUniswapV3PoolABI.abi) -``` - -The `multicallProvider` creates the multicall request and sends it via the ethers Provider. - -Next we loop through all possible word positions and add a `tickBitmap` call for each: - -```typescript -let calls: any[] = [] -let wordPosIndices: number[] = [] -for (let i = minWord; i <= maxWord; i++) { - wordPosIndices.push(i) - calls.push(poolContract.tickBitmap(i)) -} -``` - -We also keep track of the word position indices to be able to loop through them in the same order we added the calls to the array. - -We use the `multicallProvider.all()` function to send a multicall and map the results: - -```typescript -const results: bigint[] = (await multicallProvider.all(calls)).map( - (ethersResponse) => { - return BigInt(ethersResponse.toString()) - } - ) -``` - -A great visualization of what the bitMaps look like can be found in the [Uniswap V3 development book](https://uniswapv3book.com/docs/milestone_2/tick-bitmap-index/): - -TickBitmap - -We encourage anyone trying to get a deeper understanding of the Uniswap protocol to read the Uniswap V3 Book. - -## Calculating the memory positions of all Ticks - -Now that we fetched all **bitMaps**, we check which ticks are initialized and calculate the **tick position** from the **word index** and the **tickSpacing** of the pool. - -We check if a tick is **initialized** inside the word by shifting a bit by the index we are looking at and performing a bitwise AND operation: - -```typescript -const bit = 1n -const initialized = (bitmap & (bit << BigInt(i))) !== 0n -``` - -If the tick is **initialized**, we revert the compression from tick to word we made earlier by multiplying the word index with 256, which is the same as left shifting by 8 bit, adding the position we are currently at, and multiplying with the tickSpacing: - -```typescript -const tickIndex = (ind * 256 + i) * tickSpacing -``` - -The whole loop looks like this: - -```typescript -const tickIndices: number[] = [] - - for (let j = 0; j < wordPosIndices.length; j++) { - const ind = wordPosIndices[j] - const bitmap = results[j] - - if (bitmap !== 0n) { - for (let i = 0; i < 256; i++) { - const bit = 1n - const initialized = (bitmap & (bit << BigInt(i))) !== 0n - if (initialized) { - const tickIndex = (ind * 256 + i) * tickSpacing - tickIndices.push(tickIndex) - } - } - } - } -``` - -We now have an array containing the indices of all initialized Ticks. - -## Fetching all Ticks by their indices - -We use the multicallProvider again to execute an aggregated read call for all tick indices. -We create an array of call Promises again and use `.all()` to make our multicall: - -```typescript -const calls: any[] = [] - -for (const index of tickIndices) { - calls.push(poolContract.ticks(index)) -} - -const results = await multicallProvider.all(calls) -``` - -Again, the order of the results array is the same as the elements in **tickIndices**. - -We are able to combine the **tickIndices** and **results** array to create an array of `Tick` objects: - -```typescript -const allTicks: Tick[] = [] - - for (let i = 0; i < tickIndices.length; i++) { - const index = tickIndices[i] - const ethersResponse = results[i] - const tick = new Tick({ - index, - liquidityGross: JSBI.BigInt(ethersResponse.liquidityGross.toString()), - liquidityNet: JSBI.BigInt(ethersResponse.liquidityNet.toString()), - }) - allTicks.push(tick) - } -``` - -We need to parse the response from our RPC provider to JSBI values that the v3-sdk can work with. - -## Constructing the Pool - -We have everything to construct our `Pool` now: - -```typescript -const usdcWethPool = new Pool( - USDC, - WETH, - feeAmount, - slot0.sqrtPriceX96, - liquidity, - slot0.tick, - allTicks -) -``` - -With this fully initialized Pool, we can make accurate offchain calculations. - -## Next Steps - -Now that you are familiar with fetching Pool data, continue your journey with the [next example](./03-active-liquidity.md) on visualizing the Liquidity density of a pool. diff --git a/docs/sdk/v3/guides/advanced/04-price-oracle.md b/docs/sdk/v3/guides/advanced/03-price-oracle.md similarity index 55% rename from docs/sdk/v3/guides/advanced/04-price-oracle.md rename to docs/sdk/v3/guides/advanced/03-price-oracle.md index 9b9bc9e7e4..b2957f7430 100644 --- a/docs/sdk/v3/guides/advanced/04-price-oracle.md +++ b/docs/sdk/v3/guides/advanced/03-price-oracle.md @@ -6,103 +6,96 @@ title: Uniswap as a Price Oracle ## Introduction This guide will cover how to fetch price observations from a V3 pool to get onchain asset prices. -It is based on the [Price Oracle example](https://github.com/Uniswap/examples/tree/main/v3-sdk/price-oracle), found in the Uniswap code examples [repository](https://github.com/Uniswap/example). -To run this example, check out the guide's [README](https://github.com/Uniswap/examples/blob/main/v3-sdk/price-oracle/README.md) and follow the setup instructions. +It is based on the [Price Oracle example](https://github.com/Uniswap/examples/tree/main/v3-sdk/oracle), found in the Uniswap code examples [repository](https://github.com/Uniswap/examples). +To run this example, check out the guide's [README](https://github.com/Uniswap/examples/blob/main/v3-sdk/oracle/README.md) and follow the setup instructions. :::info If you need a briefer on the SDK and to learn more about how these guides connect to the examples repository, please visit our [background](./01-background.md) page! ::: -In this example we will use **ethers JS** to observe the development of a Pool's current tick over several blocks. +In this example we will observe the development of a Pool's current tick over several blocks. We will then calculate the time weighted average price - **TWAP**, and time weighted average liquidity - **TWAL** over the observed time interval. This guide will **cover**: -1. Understanding observations -2. Fetching observations -3. Computing TWAP -4. Computing TWAL -5. Why prefer observe over observations +1. Fetching observations +2. Computing TWAP +3. Computing TWAL Before diving into this guide, consider reading the theory behind using Uniswap V3 as an [Onchain Oracle](../../../../concepts/protocol/oracle.md). +:::info +The SDKs that are used in the guide are now published by the [Uniswap Foundation](https://github.com/uniswapfoundation) instead of Uniswap Labs. +You can find a list of supported SDKs [here](https://www.npmjs.com/org/uniswapfoundation). +Make sure you don't mix SDKs published by Uniswap Labs and the Uniswap Foundation to avoid unpredictable behavior. +::: + For this guide, the following Uniswap packages are used: -- [`@uniswap/v3-sdk`](https://www.npmjs.com/package/@uniswap/v3-sdk) +- [`@uniswapfoundation/v3-sdk`](https://www.npmjs.com/package/@uniswapfoundation/v3-sdk) The core code of this guide can be found in [`oracle.ts`](https://github.com/Uniswap/examples/tree/main/v3-sdk/price-oracle/src/libs/oracle.ts) ## Understanding Observations -First, we need to create a Pool contract to fetch data from the blockchain. Check out the [Pool data guide](./02-pool-data.md) to learn how to compute the address and create an **ethers Contract** to interact with. +First, we need to create a Pool object to interact with the blockchain. +We use `initFromChain` to create a Pool with an RPC connection in the same manner we did in the Trading guides: ```typescript -const poolContract = new ethers.Contract( - poolAddress, - IUniswapV3PoolABI.abi, - provider -) +const provider = new ethers.providers.JsonRpcProvider( + '...rpcUrl' + ) + +const pool = await Pool.initFromChain({ + provider, + tokenA: CurrentConfig.pool.token0, + tokenB: CurrentConfig.pool.token1, + fee: CurrentConfig.pool.fee, + }) ``` -All V3 pools store observations of the current tick and the block timestamp. +All V3 pools store observations of the current tick and the block timestamp. + To minimize pool deployment costs, only one Observation is stored in the contract when the Pool is created. Anyone who is willing to pay the gas costs can [increase](../../../../contracts/v3/reference/core/UniswapV3Pool.md#increaseobservationcardinalitynext) the number of stored observations to up to `65535`. -If the Pool cannot store an additional Observation, it overwrites the oldest one. -We create an interface to map our data to: +If the Pool cannot store an additional Observation, it overwrites the oldest one. -```typescript -interface Observation { - secondsAgo: number - tickCumulative: bigint - secondsPerLiquidityCumulativeX128: bigint -} -``` +## Number of Observations -To fetch the `Observations` from our pool contract, we will use the [`observe`](../../../../contracts/v3/reference/core/UniswapV3Pool.md#observe) function: - -```solidity -function observe( - uint32[] secondsAgos -) external view override noDelegateCall returns ( - int56[] tickCumulatives, - uint160[] secondsPerLiquidityCumulativeX128s -) -``` +Before fetching observations, we want to make sure the Pool stores enough observations to act as a reliable oracle. We first check how many observations are stored in the Pool by calling the `slot0` function. ```typescript -const slot0 = await poolContract.slot0() +const slot0 = await pool.rpcSlot0() const observationCount = slot0.observationCardinality const maxObservationCount = slot0.observationCardinalityNext ``` -The `observationCardinalityNext` is the maximum number of Observations the Pool **can store** at the moment. The `observationCardinality` is the actual number of Observations the Pool **has currently stored**. +The `observationCardinalityNext` is the maximum number of Observations the Pool **can store** at the moment. + Observations are only stored when the `swap()` function is called on the Pool or when a **Position is modified**, so it can take some time to write the Observations after the `observationCardinalityNext` was increased. If the number of Observations on the Pool is not sufficient, we need to call the `increaseObservationCardinalityNext()` function and set it to the value we desire. -This is a write function as the contract needs to store more data on the blockchain. -We will need a **wallet** or **signer** to pay the corresponding gas fee. +This is a write function because the contract needs to store more data on the blockchain. +We will need to pay the corresponding gas fee. In this example, we want to fetch 10 observations. ```typescript import { ethers } from 'ethers' -let provider = new ethers.providers.WebSocketProvider('rpcUrl...') let wallet = new ethers.Wallet('private_key', provider) +const observationCardinalityNext = 10 -const poolContract = new ethers.Contract( - poolAddress, - IUniswapV3PoolABI.abi, - wallet -) - -const txRes = await poolContract.increaseObservationCardinalityNext(10) +const txRes = await pool.rpcIncreaseObservationCardinalityNext({ + signer: wallet, + observationCardinalityNext, + }) ``` The Pool will now fill the open Observation Slots. @@ -116,10 +109,36 @@ Because of this, we can be sure the oldest Observation is **at least** 10 blocks It is very likely that the number of blocks covered is bigger than 10. ::: +We are now sure that at least 10 observations exist, and can safely fetch observations for the last 10 blocks. + ## Fetching Observations -We are now sure that at least 10 observations exist, and can safely fetch observations for the last 10 blocks. -We call the `observe` function with an array of numbers, representing the timestamps of the Observations in seconds ago from now. +To fetch the `Observations` from our pool, we will use the [`observe`](../../../../contracts/v3/reference/core/UniswapV3Pool.md#observe) function: + +```solidity +function observe( + uint32[] secondsAgos +) external view override noDelegateCall returns ( + int56[] tickCumulatives, + uint160[] secondsPerLiquidityCumulativeX128s +) +``` + +The sdk wraps this function in the `rpcObserve` function on our Pool object. + +```typescript + const observeResponse = await pool.rpcObserve({ secondsAgo: timestamps }) +``` + +Let's create an interface to map our data to: + +```typescript +interface Observation { + secondsAgo: number + tickCumulative: bigint + secondsPerLiquidityCumulativeX128: bigint +} +``` In this example, we calculate averages over the last ten blocks so we fetch 2 observations with 9 times the blocktime in between. Fetching an Observation `0s` ago will return the **most recent Observation** interpolated to the current timestamp as observations are written at most once a block. @@ -129,18 +148,19 @@ const timestamps = [ 0, 108 ] -const [tickCumulatives, secondsPerLiquidityCumulatives] = await poolContract.observe(timestamps) +const observeResponse = await pool.rpcObserve(timestamps) const observations: Observation[] = timestamps.map((time, i) => { return { - secondsAgo: time - tickCumulative: BigInt(tickCumulatives[i]) - secondsPerLiquidityCumulativeX128: BigInt(secondsPerLiquidityCumulatives[i]) + secondsAgo: time, + tickCumulative: observeResponse.tickCumulatives[i], + secondsPerLiquidityCumulativeX128: + observeResponse.secondsPerLiquidityCumulativeX128s[i], } }) ``` -We map the response from the RPC provider to match our Observations interface. +We map the response from the RPC provider to match our `Observation` interface. ## Calculating the average Price @@ -154,15 +174,13 @@ We cannot directly use the value of a single Observation for anything meaningful const diffTickCumulative = observations[0].tickCumulative - observations[1].tickCumulative const secondsBetween = 108 -const averageTick = diffTickCumulative / secondsBetween +const averageTick = Number(diffTickCumulative / BigInt(secondsBetween)) ``` -Now that we know the average active Tick over the last 10 blocks, we can calculate the price with the `tickToPrice` function, which returns a [`Price`](../../../core/reference/classes/Price.md) Object. Check out the [Pool data](./02-pool-data.md) guide to understand how to construct a Pool Object and access its properties. We don't need the full Tick Data for this guide. +Now that we know the average active Tick over the last 10 blocks, we can calculate the price with the `tickToPrice` function, which returns a [`Price`](../../../core/reference/classes/Price.md) Object. ```typescript -import { tickToPrice, Pool } from '@uniswap/v3-sdk' - -const pool = new Pool(...) +import { tickToPrice } from '@uniswapfoundation/v3-sdk' const TWAP = tickToPrice(pool.token0, pool.token1, averageTick) ``` @@ -207,81 +225,6 @@ Adding massive amounts of liquidity to a Pool and withdrawing them after a block Use the **TWAP** with care and consider handling outliers. ::: -## Why prefer observe over observations? - -As touched on previously, the `observe` function calculates Observations for the timestamps requested from the nearest observations stored in the Pool. -It is also possible to directly fetch the stored observations by calling the `observations` function with the index of the Observation that we are interested in. - -Let's fetch all observations stored in our Pool. We already made sure the observationCardinality is 10. -The solidity struct `Observation` looks like this: - -```solidity -struct Observation { - // the block timestamp of the observation - uint32 blockTimestamp; - // the tick accumulator, i.e. tick * time elapsed since the pool was first initialized - int56 tickCumulative; - // the seconds per liquidity, i.e. seconds elapsed / max(1, liquidity) since the pool was first initialized - uint160 secondsPerLiquidityCumulativeX128; - // whether or not the observation is initialized - bool initialized; -} -``` - -It is possible to request any Observation up to (excluding) index `65535`, but indices equal to or greater than the `observationCardinality` will return uninitialized Observations. - -The full code to the following code snippets can be found in [`oracle.ts`](https://github.com/uniswap/examples/blob/main/v3-sdk/oracle/src/libs/oracle.ts) - -```typescript -let requests = [] -for (let i = 0; i < 10; i++) { - requets.push(poolContract.observations(i)) -} - -const results = await Promise.all(requests) -``` - -We can only request one Observation at a time, so we create an Array of Promises to get an Array of Observations. - -We already see one difference, to using the `observe` function here. -While `observe` creates an array onchain in the smart contract and returns it, calling `observations` requires us to make multiple RPC calls. - -:::note -Depending on our setup and the Node we are using, either option can be faster, but making multiple RPC calls always has the danger of the blockchain state changing between our calls. -While it is extremely unlikely, it is still possible that our Node updates with a new block and new Observation in between our calls. -Because we access indices of an array, this would give us an unexpected result that we need to handle as an edge case in our implementation. -::: - -One way to handle this behaviour is deploying or [using](https://github.com/mds1/multicall) a Contract with a [multicall](https://solidity-by-example.org/app/multi-call/) functionality to get all observations with one request. -You can also find an example of a JS multicall in the [Pool data guide](./02-pool-data.md). - -We map the RPC result to the Typescript interface that we created: - -```typescript -const utcNow = Math.floor(Date.now() / 1000) -const observations = results.map((result) => { - const secondsAgo = utcNow - Number(result.blockTimeStamp) - return { - secondsAgo, - tickCumulative: BigInt(result.tickCumulative), - secondsPerLiquidityCumulativeX128: BigInt(result.secondsPerLiquidityCumulativeX128) - } -}).sort((a, b) => a.secondsAgo - b.secondsAgo) -``` - -We now have an Array of observations in the same format that we are used to. - -:::note -Because Observations are stored in a **fixed size array** with always the oldest Observation overwritten if a new one is stored, they are **not sorted**. -We need to sort the result by the timestamp. -::: - -The timestamps of the Observations we got are correspondent to blocks where **Swaps or Position changes** happened on the Pool. -Because of this, we would need to calculate Observations for specific intervals manually from the **surrounding Observations**. - -In conclusion, it is much harder to work with `observations` than with `observe`, and we need to consider multiple edge cases. -For this reason, it is recommended to use the `observe` function. - ## Next Steps Now that you are familiar with the Oracle feature of Uniswap, consider checking out the [next guide](./05-range-orders.md) on **Range Orders**. diff --git a/docs/sdk/v3/guides/advanced/05-range-orders.md b/docs/sdk/v3/guides/advanced/04-range-orders.md similarity index 89% rename from docs/sdk/v3/guides/advanced/05-range-orders.md rename to docs/sdk/v3/guides/advanced/04-range-orders.md index a1f7f30376..1d010f83ee 100644 --- a/docs/sdk/v3/guides/advanced/05-range-orders.md +++ b/docs/sdk/v3/guides/advanced/04-range-orders.md @@ -27,10 +27,16 @@ This guide will **cover**: Before working through this guide, consider checking out the Range Orders [concept page](../../../../concepts/protocol/range-orders.md) to understand how Limit orders can be executed with Uniswap V3. +:::info +The SDKs that are used in the guide are now published by the [Uniswap Foundation](https://github.com/uniswapfoundation) instead of Uniswap Labs. +You can find a list of supported SDKs [here](https://www.npmjs.com/org/uniswapfoundation). +Make sure you don't mix SDKs published by Uniswap Labs and the Uniswap Foundation to avoid unpredictable behavior. +::: + For this guide, the following Uniswap packages are used: -- [`@uniswap/v3-sdk`](https://www.npmjs.com/package/@uniswap/v3-sdk) -- [`@uniswap/sdk-core`](https://www.npmjs.com/package/@uniswap/sdk-core) +- [`@uniswapfoundation/v3-sdk`](https://www.npmjs.com/package/@uniswapfoundation/v3-sdk) +- [`@uniswapfoundation/sdk-core`](https://www.npmjs.com/package/@uniswapfoundation/sdk-core) The core code of this guide can be found in [`range-order.ts`](https://github.com/Uniswap/examples/tree/main/v3-sdk/range-order/src/libs/range-order.ts). @@ -81,13 +87,12 @@ To create our Position, we need to first decide the Tick Range that we want to p ### Upper Tick -We [create a Pool](./02-pool-data.md) that represents the V3 Pool we are interacting with and get the `token0Price`. +We create a Pool that represents the V3 Pool we are interacting with and get the `token0Price`. We won't need full tick data in this example. ```typescript -import { Pool } from '@uniswap/v3-sdk' +import { Pool } from '@uniswapfoundation/v3-sdk' -... const pool = new Pool(token0, token1, fee, sqrtPriceX96, liquidity, tickCurrent) const currentPrice = pool.token0Price @@ -96,7 +101,7 @@ const currentPrice = pool.token0Price Next we increase the `Price` by 5%. We create a new Price with a numerator 5% higher than our current Price: ```typescript -import { Price, Fraction } from '@uniswap/sdk-core' +import { Price, Fraction } from '@uniswapfoundation/sdk-core' const targetFraction = Price.asFraction.multiply(new Fraction(100 + 5, 100)) @@ -120,7 +125,7 @@ We use the `priceToClosestTick` function to find the closest tick to our targetP We then use the `nearestUsableTick` function to find the closest initializable Tick for the `tickSpacing` of the `Pool`. ```typescript -import {priceToClosestTick, nearestUsableTick} from '@uniswap/v3-sdk' +import {priceToClosestTick, nearestUsableTick} from '@uniswapfoundation/v3-sdk' let targetTick = nearestUsableTick( priceToClosestTick(targetPrice), @@ -128,7 +133,7 @@ let targetTick = nearestUsableTick( ) ``` -This nearest Tick will most likely not **exactly** match our Price target. +This nearest Tick will most likely not **exactly** match our Price target but should be quite close. Depending on our personal preferences we can either err on the higher or lower side of our target by adding or subtracting the `tickSpacing` if the initializable Tick is lower or higher than the theoretically closest Tick. @@ -164,7 +169,7 @@ If you are not familiar with liquidity Positions, check out the [liquidity posit We create a `Position` object with our ticks and the amount of tokens we want to deposit: ```typescript -import { Position } from '@uniswap/v3-sdk' +import { Position } from '@uniswapfoundation/v3-sdk' const position = Position.fromAmount0({ pool: pool, @@ -176,7 +181,7 @@ const position = Position.fromAmount0({ ``` Before we mint our position, we need to give the `NonfungiblePositionManager` Contract an approval to transfer our tokens. -We can find the Contract address on the official [Uniswap Github](https://github.com/Uniswap/v3-periphery/blob/main/deploys.md). +We can get the Contract address from the `sdk-core`. For local development, the contract address is the same as the network we are forking from. So if we are using a local fork of mainnet like described in the [Local development guide](../02-local-development.md), the contract address would be the same as on mainnet. @@ -202,7 +207,7 @@ Once we have our approval, we create the calldata for the **Mint** call using th ```typescript import {MintOptions, NonfungiblePositionManager} -import { Percent } from '@uniswap/sdk-core' +import { Percent } from '@uniswapfoundation/sdk-core' const mintOptions: MintOptions = { recipient: wallet.address, @@ -329,18 +334,12 @@ We check if the tick has crossed our position, and if so we withdraw the Positio ## Closing the Limit Order -We call the NonfungiblePositionManager Contract with the `tokenId` to get all info of our position as we may have gotten fees from trades on the Pool: +We use the NonfungiblePositionManager together with the `tokenId` to get all info of our position as we may have gotten fees from trades on the Pool: ```typescript -import INON_FUNGIBLE_POSITION_MANAGER from '@uniswap/v3-periphery/artifacts/contracts/NonfungiblePositionManager.sol/NonfungiblePositionManager.json' - -const positionManagerContract = new ethers.Contract( - NONFUNGIBLE_POSITION_MANAGER_CONTRACT_ADDRESS, - INONFUNGIBLE_POSITION_MANAGER.abi, - provider -) +import { NonfungiblePositionManager } from '@uniswapfoundation/v3-sdk' -const positionInfo = await positionManagerContract.positions(tokenId) +const currentPosition = await NonfungiblePositionManager.fetchWithPositionId(getProvider(), tokenId) ``` We use the `NonfungiblePositionManager`, the `pool`, `positionInfo` and `tokenId` to create call parameter for a `decreaseLiquidity` call. @@ -348,21 +347,20 @@ We use the `NonfungiblePositionManager`, the `pool`, `positionInfo` and `tokenId We start with creating `CollectOptions`: ```typescript -import { Percent, CurrencyAmount } from '@uniswap/sdk-core' -import { CollectOptions, RemoveLiquidityOptions } from '@uniswap/v3-sdk' -import JSBI from 'jsbi' +import { Percent, CurrencyAmount } from '@uniswapfoundation/sdk-core' +import { CollectOptions, RemoveLiquidityOptions } from '@uniswapfoundation/v3-sdk' const collectOptions: Omit = { expectedCurrencyOwed0: CurrencyAmount.fromRawAmount( - pool.token0, - JSBI.BigInt(positionInfo.tokensOwed0.toString()) + CurrentConfig.tokens.token0, + 0 ), expectedCurrencyOwed1: CurrencyAmount.fromRawAmount( - pool.token1, - JSBI.BigInt(positionInfo.tokensOwed1.toString()) + CurrentConfig.tokens.token1, + 0 ), - recipient: wallet.address, -} + recipient: address, + } ``` Next we create `RemoveLiquidityOptions`. We remove all our liquidity so we set liquidityPercentage to `1`: @@ -378,24 +376,12 @@ const removeLiquidityOptions: RemoveLiquidityOptions = { } ``` -We create a new `Position` object from the updated `positionInfo` info we fetched: - -```typescript - -const updatedPosition = new Position{ - pool, - liquidity: JSBI.BigInt(currentPositionInfo.liquidity.toString()), - tickLower: currentPositionInfo.tickLower, - tickUpper: currentPositionInfo.tickUpper, -} -``` - We have everything to create our calldata now and are ready to make our Contract call: ```typescript const { calldata, value } = NonfungiblePositionManager.removeCallParameters( - updatedPosition, + currentPosition, removeLiquidityOptions ) const transaction = { diff --git a/docs/sdk/v3/guides/liquidity/01-position-data.md b/docs/sdk/v3/guides/liquidity/01-position-data.md index 0aa4bafd21..d9a4b04c03 100644 --- a/docs/sdk/v3/guides/liquidity/01-position-data.md +++ b/docs/sdk/v3/guides/liquidity/01-position-data.md @@ -5,21 +5,8 @@ title: Liquidity Positions ## Introduction -This guide will introduce us to **liquidity positions** in Uniswap V3 and present the `v3-sdk` classes and Contracts used to interact with the protocol. -The concepts and code snippets showcased here can be found across the **Pooling Liquidity** examples in the Uniswap code examples [repository](https://github.com/Uniswap/examples). - -In this guide, we will take a look at the [Position](../../reference/classes/Position.md) and [NonfungiblePositionManager](../../reference/classes/NonfungiblePositionManager.md) classes, as well as the [NonfungiblePositionManager Contract](../../../../contracts/v3/reference/periphery/NonfungiblePositionManager.md). - +This guide will introduce us to **liquidity positions** in Uniswap V3 and provide some theoretical background for the guides in this section. At the end of the guide, we should be familiar with the most important classes used to interact with liquidity positions. -We should also understand how to fetch positions from the **NonfungiblePositionManager Contract**. - -For this guide, the following Uniswap packages are used: - -- [`@uniswap/v3-sdk`](https://www.npmjs.com/package/@uniswap/v3-sdk) -- [`@uniswap/sdk-core`](https://www.npmjs.com/package/@uniswap/sdk-core) -- [`@uniswap/v3-periphery`](https://www.npmjs.com/package/@uniswap/v3-periphery) - -The code mentioned in this guide can be found across the [minting Position](https://github.com/Uniswap/examples/blob/main/v3-sdk/minting-position/src), [collecting Fees](https://github.com/Uniswap/examples/blob/main/v3-sdk/collecting-fees/src), [modifying positions](https://github.com/Uniswap/examples/blob/d34a53412dbf905802da2249391788a225719bb8/v3-sdk/modifying-position/src) and [swap and add liquidity](https://github.com/Uniswap/examples/blob/main/v3-sdk/swap-and-add-liquidity/src) examples. ## Prerequisites @@ -47,7 +34,7 @@ For example, a Pool with **HIGH** fee (1%) has a tickSpacing of 200, meaning the $$1.0001^{200} = 1.0202$$ or $$2.02$$% -### Liquidity Positions +## Liquidity Positions When someone provides liquidity to a Pool, they create a **Liquidity Position**. This position is defined by the amount of liquidity provided and the start tick and the end tick, or price range, of the Position. @@ -57,149 +44,14 @@ In this case, the liquidity provider will pay only one type of Token into the Po To learn more about how Ticks and Liquidity positions work, consider reading the [whitepaper](https://uniswap.org/whitepaper-v3.pdf) or the other resources mentioned above. -Now that we have a rough understanding of liquidity positions in Uniswap V3, let's look at the correspondent classes the SDK offers us. - -## Position class - -The **sdk** provides a [`Position`](https://github.com/Uniswap/v3-sdk/blob/main/src/entities/position.ts) class used to create local representations of an onchain position. -It is used to create the calldata for onchain calls to mint or modify an onchain position. - -There are four ways to construct a position. - -Directly with the [constructor](https://github.com/Uniswap/v3-sdk/blob/08a7c05/src/entities/position.ts#L40): - -```typescript -import { Pool, Position } from '@uniswap/v3-sdk' -import JSBI from 'jsbi' - -const pool = new Pool(...) -const tickLower: number = -100 -const tickUpper: number = 200 -const liquidity: JSBI = JSBI.BigInt('1000000000000000000') - -const position = new Position({ - pool, - liquidity, - tickLower, - tickUpper -}) -``` - -Using the [`fromAmounts()`](https://github.com/Uniswap/v3-sdk/blob/08a7c05/src/entities/position.ts#L312) function: - -```typescript -import { BigIntish } from '@uniswap/sdk-core' - -const pool = new Pool(...) -const tickLower: number = -100 -const tickUpper: number = 200 -const amount0: BigIntish = '1000000000000000000' -const amount1: BigIntish = JSBI.BigInt('1000000000000000000') -const useFullPrecision: boolean = true - -const position = Position.fromAmounts({ - pool, - tickLower, - tickUpper, - amount0, - amount1, - useFullPrecision -}) -``` - -Or using the [`fromAmount0()`](https://github.com/Uniswap/v3-sdk/blob/08a7c050cba00377843497030f502c05982b1c43/src/entities/position.ts#L354) or [`fromAmount1()`](https://github.com/Uniswap/v3-sdk/blob/08a7c050cba00377843497030f502c05982b1c43/src/entities/position.ts#L378) functions: - -```typescript -import { BigIntish } from '@uniswap/sdk-core' -... - -const pool = new Pool(...) -const tickLower: number = -200 -const tickUpper: number = 100 -const amount0: BigIntish = '1000000000000000000' -const useFullPrecision: boolean = true - -const singleSidePositionToken0 = Position.fromAmount0({ - pool, - tickLower, - tickUpper, - amount0, - useFullPrecision -}) - -const amount1: BigIntish = 100000000 - -const singleSidePositionToken1 = Position.fromAmount1({ - pool, - tickLower, - tickUpper, - amount1, - useFullPrecision -}) -``` - -These last two functions calculate a position at the given tick range given the amount of `token0` or `token1`. The amount of the second token is calculated from the ratio of the tokens inside the tick range and the amount of token one. - -A create transaction would then fail if the wallet doesn't hold enough `token1` or the Contract is not given the necessary **Transfer Approval**. - -All of these functions take an Object with **named values** as a call parameter. The amount and liquidity values are of type `BigIntish` which accepts `number`, `string` and `JSBI`. - -The values of `tickLower` and `tickUpper` must match **initializable ticks** of the Pool. - -## NonfungiblePositionManager - -The `NonfungiblePositionManager` class is mainly used to create calldata for functions on the **NonfungiblePositionManager Contract**. - -We will look at the **sdk** class and write functions on the Contract in this section. - -### Creating a Position - -To create a position on a Pool, the [`mint`](../../../../contracts/v3/reference/periphery/NonfungiblePositionManager.md#mint) function is called on the Contract. -The **sdk** class provides the `addCallParameters` function to create the calldata for the transaction: - -```typescript -import { MintOptions, NonfungiblePositionManager } from '@uniswap/v3-sdk' - -const mintOptions: MintOptions = { - recipient: address, - deadline: Math.floor(Date.now() / 1000) + 60 * 20, - slippageTolerance: new Percent(50, 10_000), -} - -// get calldata for minting a position -const { calldata, value } = NonfungiblePositionManager.addCallParameters( - positionToMint, - mintOptions -) -``` - -This call creates a position if it doesn't exist, but can also be used to increase an existing position. -Take a look at the [Mint Position guide](./02-minting-position.md) and [Modify Position guide](./04-modifying-position.md) to learn more. - -### Decreasing and Increasing a Position - -To decrease or increase the liquidity of a Position, the `decreaseLiquidity` or `increaseLiquidity` functions are called on the Contract. -To increase, `addCallParameters` is used as mentioned above, to decrease we use `removeCallParameters`: - -```typescript -const { calldata, value } = NonfungiblePositionManager.removeCallParameters( - currentPosition, - removeLiquidityOptions -) -``` - -Take a look at the [Modify Positions guide](04-modifying-position.md) to learn how to create the `currentPosition` and `removeLiquidityOptions` parameters. - -### Collecting Fees +The SDK wraps Liquidity Positions in the `Position` class. -To collect fees accrued, the `collect` function is called on the Contract. -The **sdk class** provides the `collectCallParameters` function to create the calldata for that: +## NFTPositionManager -```typescript -const { calldata, value } = - NonfungiblePositionManager.collectCallParameters(collectOptions) -``` +To simplify managing Liquidity Positions, Uniswap deployed the [NonfungiblePositionManager](../../../../contracts/v3/reference/periphery/NonfungiblePositionManager.md) contract. +The SDK includes the `NonfungiblePositionManager` class that wraps this contract and provides utility functions to interact with it. +We will use it to create and modify Positions in the following guides. ## Next steps diff --git a/docs/sdk/v3/guides/liquidity/02-minting-position.md b/docs/sdk/v3/guides/liquidity/02-minting-position.md index e14e7d1ecf..0672d1f8d2 100644 --- a/docs/sdk/v3/guides/liquidity/02-minting-position.md +++ b/docs/sdk/v3/guides/liquidity/02-minting-position.md @@ -13,22 +13,26 @@ To run this example, check out the examples's [README](https://github.com/Uniswa If you need a briefer on the SDK and to learn more about how these guides connect to the examples repository, please visit our [background](../01-background.md) page! ::: -In the Uniswap V3 protocol, liquidity positions are represented using non-fungible tokens. In this guide we will use the `NonfungiblePositionManager` class to help us mint a liquidity position for the **USDC - DAI** pair. The inputs to our guide are the **two tokens** that we are pooling for, the **amount** of each token we are pooling for and the Pool **fee**. +In the Uniswap V3 protocol, liquidity positions are represented using non-fungible tokens. In this guide we will mint a liquidity position for the **USDC - DAI** pair. The inputs to our guide are the **two tokens** that we are pooling for, the **amount** of each token we are pooling for and the Pool **fee**. The guide will **cover**: 1. Giving approval to transfer our tokens -2. Creating an instance of a `Pool` -3. Calculating our `Position` from our input tokens -4. Configuring and executing our minting transaction +2. Calculating our `Position` from our input tokens +3. Configuring and executing our minting transaction At the end of the guide, given the inputs above, we should be able to mint a liquidity position with the press of a button and view the position on the UI of the web application. +:::info +The SDKs that are used in the guide are now published by the [Uniswap Foundation](https://github.com/uniswapfoundation) instead of Uniswap Labs. +You can find a list of supported SDKs [here](https://www.npmjs.com/org/uniswapfoundation). +Make sure you don't mix SDKs published by Uniswap Labs and the Uniswap Foundation to avoid unpredictable behavior. +::: + For this guide, the following Uniswap packages are used: -- [`@uniswap/v3-sdk`](https://www.npmjs.com/package/@uniswap/v3-sdk) -- [`@uniswap/sdk-core`](https://www.npmjs.com/package/@uniswap/sdk-core) -- [`@uniswap/smart-order-router`](https://www.npmjs.com/package/@uniswap/smart-order-router) +- [`@uniswapfoundation/v3-sdk`](https://www.npmjs.com/package/@uniswapfoundation/v3-sdk) +- [`@uniswapfoundation/sdk-core`](https://www.npmjs.com/package/@uniswapfoundation/sdk-core) The core code of this guide can be found in [`mintPosition()`](https://github.com/Uniswap/examples/blob/main/v3-sdk/minting-position/src/libs/positions.ts#L37) @@ -36,132 +40,68 @@ The core code of this guide can be found in [`mintPosition()`](https://github.co We want to use the `NonfungiblePositionManager` contract to create our liqudity position. In situations where a smart contract is transfering tokens on our behalf, we need to give it approval to do so. -This is done by interacting with the Contract of the contract, considering ERC20 Tokens are smart contracts of their own. -Considering this, the first step to create our position is to give approval to the protocol's `NonfungiblePositionManager` to transfer our tokens: +We can use the `approveTokenTransfer()` function from the sdk for that: ```typescript -const token0Approval = await getTokenTransferApproval( - token0Address, - amount0 -) -const token1Approval = await getTokenTransferApproval( - token1Address, - amount1 -) -``` - -The logic to achieve that is wrapped in the `getTokenTransferApprovals` function. In short, since both **USDC** and **DAI** are ERC20 tokens, we setup a reference to their smart contracts and call the `approve` function: - -```typescript -import { ethers, BigNumber } from 'ethers' - -async function getTokenTransferApproval(address: string, amount: BigNumber) { - const provider = new ethers.providers.JsonRpcProvider(rpcUrl) - - const tokenContract = new ethers.Contract( - token.address, - ERC20_ABI, - provider - ) - - return tokenContract.approve( - NONFUNGIBLE_POSITION_MANAGER_CONTRACT_ADDRESS, - amount - ) -} +import { approveTokenTransfer } from '@uniswapfoundation/v3-sdk' +import { NONFUNGIBLE_POSITION_MANAGER_ADDRESSES } from '@uniswapfoundation/sdk-core' + +const positionManagerAddress = NONFUNGIBLE_POSITION_MANAGER_ADDRESSES[ + CurrentConfig.tokens.token0.chainId + ] + +const token0Approval = await approveTokenTransfer({ + contractAddress: positionManagerAddress, + tokenAddress: CurrentConfig.tokens.token0.address, + amount: amount0, + signer: getWallet(), + }) + const token1Approval = await approveTokenTransfer({ + contractAddress: positionManagerAddress, + tokenAddress: CurrentConfig.tokens.token1.address, + amount: amount1, + signer: getWallet(), + }) ``` -We can get the Contract address for the NonfungiblePositionManager from [Github](https://github.com/Uniswap/v3-periphery/blob/main/deploys.md). -For Ethereum mainnet or a local fork of mainnet, we see that the contract address is `0xC36442b4a4522E871399CD717aBDD847Ab11FE88`. -In our example, this is defined in the [`constants.ts`](https://github.com/Uniswap/examples/blob/main/v3-sdk/minting-position/src/libs/constants.ts) file. - -## Creating an instance of a `Pool` - -Having approved the transfer of our tokens, we now need to get data about the pool for which we will provide liquidity, in order to instantiate a Pool class. - -To start, we compute our Pool's address by using a helper function and passing in the unique identifiers of a Pool - the **two tokens** and the Pool **fee**. -The **fee** input parameter represents the swap fee that is distributed to all in range liquidity at the time of the swap. - -```typescript -import { computePoolAddress, FeeAmount } from '@uniswap/v3-sdk' -import { Token } from '@uniswap/sdk-core' - -const token0: Token = ... -const token1: Token = ... -const fee: FeeAmount = ... -const POOL_FACTORY_CONTRACT_ADDRESS: string = ... - -const currentPoolAddress = computePoolAddress({ - factoryAddress: POOL_FACTORY_CONTRACT_ADDRESS, - tokenA: token0, - tokenB: token1, - fee: poolFee, -}) -``` +We can get the Contract address for the NonfungiblePositionManager from the `NONFUNGIBLE_POSITION_MANAGER_ADDRESSES` in the sdk-core. -Again, we can get the factory contract address from [Github](https://github.com/Uniswap/v3-periphery/blob/main/deploys.md). -For Ethereum mainnet, or a local fork of mainnet, it is `0x1F98431c8aD98523631AE4a59f267346ea31F984`. -In our example, it is defined in [`constants.ts`](https://github.com/Uniswap/examples/blob/main/v3-sdk/minting-position/src/libs/constants.ts) +## Calculating our `Position` from our input tokens -Then, we get the Pool's data by creating a reference to the Pool's smart contract and accessing its methods, very similar to what we did in the [Quoting guide](../swaps/01-quoting.md#referencing-the-pool-contract-and-fetching-metadata): +To create our Position, we first need to instantiate a `Pool` object: ```typescript -import IUniswapV3PoolABI from '@uniswap/v3-core/artifacts/contracts/interfaces/IUniswapV3Pool.sol/IUniswapV3Pool.json' - -const poolContract = new ethers.Contract( - currentPoolAddress, - IUniswapV3PoolABI.abi, - provider -) - -const [liquidity, slot0] = - await Promise.all([ - poolContract.liquidity(), - poolContract.slot0(), - ]) -``` + import {Pool} from '@uniswapfoundation/v3-sdk' -Having collected the required data, we can now create an instance of the `Pool` class: + const provider = getProvider() -```typescript -import { Pool } from '@uniswap/v3-sdk' - -const configuredPool = new Pool( - token0, - token1, - poolFee, - slot0.sqrtPriceX96.toString(), - liquidity.toString(), - slot0.tick -) + const pool = await Pool.initFromChain({ + provider, + tokenA: CurrentConfig.tokens.token0, + tokenB: CurrentConfig.tokens.token1, + fee: CurrentConfig.tokens.poolFee, + }) ``` -We need a Pool instance to create our Position as various parameters of liquidity positions depend on the state of the Pool where they are created. -An example is the current price (named *sqrtPriceX96* after the way it is encoded) to know the ratio of the two Tokens we need to send to the Pool. - -Liquidity provided below the current Price will be provided in the first Token of the Pool, while liquidity provided above the current Price is made up by the second Token. - -## Calculating our `Position` from our input tokens - -Having created the instance of the `Pool` class, we can now use that to create an instance of a `Position` class, which represents the price range for a specific pool that LPs choose to provide in: +Next, we can use the pool to create an instance of a `Position` object, which represents a Liquidity Position offchain: ```typescript -import { Position } from '@uniswap/v3-sdk' -import { BigIntish } from '@uniswap/sdk-core' +import { Position } from '@uniswapfoundation/v3-sdk' +import { BigIntish } from '@uniswapfoundation/sdk-core' -// The maximum token amounts we want to provide. BigIntish accepts number, string or JSBI +// The maximum token amounts we want to provide. BigIntish accepts number, string or bigint const amount0: BigIntish = ... const amount1: BigIntish = ... const position = Position.fromAmounts({ - pool: configuredPool, + pool: pool, tickLower: - nearestUsableTick(configuredPool.tickCurrent, configuredPool.tickSpacing) - - configuredPool.tickSpacing * 2, + nearestUsableTick(pool.tickCurrent, pool.tickSpacing) - + pool.tickSpacing * 2, tickUpper: - nearestUsableTick(configuredPool.tick, configuredPool.tickSpacing) + - configuredPool.tickSpacing * 2, + nearestUsableTick(pool.tick, pool.tickSpacing) + + pool.tickSpacing * 2, amount0: amount0, amount1: amount1, useFullPrecision: true, @@ -177,23 +117,26 @@ Given those parameters, `fromAmounts` will attempt to calculate the maximum amou ## Configuring and executing our minting transaction -The Position instance is then passed as input to the `NonfungiblePositionManager`'s `addCallParameters` function. The function also requires an [`AddLiquidityOptions`](https://github.com/Uniswap/v3-sdk/blob/08a7c050cba00377843497030f502c05982b1c43/src/nonfungiblePositionManager.ts#L77) object as its second parameter. This is either of type [`MintOptions`](https://github.com/Uniswap/v3-sdk/blob/08a7c050cba00377843497030f502c05982b1c43/src/nonfungiblePositionManager.ts#L74) for minting a new position or [`IncreaseOptions`](https://github.com/Uniswap/v3-sdk/blob/08a7c050cba00377843497030f502c05982b1c43/src/nonfungiblePositionManager.ts#L75) for adding liquidity to an existing position. For this example, we're using a `MintOptions` to create our position. +We can now mint our Position: ```typescript -import { MintOptions, NonfungiblePositionManager } from '@uniswap/v3-sdk' -import { Percent } from '@uniswap/sdk-core' +import { MintOptions } from '@uniswapfoundation/v3-sdk' +import { Percent } from '@uniswapfoundation/sdk-core' + +const signer = getWallet() const mintOptions: MintOptions = { - recipient: address, + recipient: getWalletAddress(), deadline: Math.floor(Date.now() / 1000) + 60 * 20, slippageTolerance: new Percent(50, 10_000), } // get calldata for minting a position -const { calldata, value } = NonfungiblePositionManager.addCallParameters( - position, - mintOptions -) +const txResponse = await positionToMint.mint({ + signer: getWallet(), + provider, + options: mintOptions, + }) ``` The `MintOptions` interface requires three keys: @@ -202,30 +145,7 @@ The `MintOptions` interface requires three keys: - `deadline` defines the latest point in time at which we want our transaction to be included in the blockchain. - `slippageTolerance` defines the maximum amount of **change of the ratio** of the Tokens we provide. The ratio can change if for example **trades** that change the price of the Pool are included before our transaction. -The `addCallParameters` function returns the calldata as well as the value required to execute the transaction: - -```typescript -const transaction = { - data: calldata, - to: NONFUNGIBLE_POSITION_MANAGER_CONTRACT_ADDRESS, - value: value, - from: address, - maxFeePerGas: MAX_FEE_PER_GAS, - maxPriorityFeePerGas: MAX_PRIORITY_FEE_PER_GAS, -} -``` - -We use our wallet to send the transaction. As it is a write call, we need to sign the transaction with a valid private key. - -```typescript -const wallet = new ethers.Wallet(privateKey, provider) - -const txRes = await wallet.sendTransaction(transaction) -``` - -Write calls do not return the result of the transaction. If we want to read the result we would need to use for example `trace_transaction`. -You can find an example of that in the [Range Order guide](../advanced/05-range-orders.md). -In this example, we don't need the result of the transaction. +The `mint()` function directly signs and executes a transaction that will create our Position. The effect of the transaction is to mint a new Position NFT. We should see a new position with liquidity in our list of positions. diff --git a/docs/sdk/v3/guides/liquidity/03-fetching-positions.md b/docs/sdk/v3/guides/liquidity/03-fetching-positions.md index 744ecb6806..4b1242375e 100644 --- a/docs/sdk/v3/guides/liquidity/03-fetching-positions.md +++ b/docs/sdk/v3/guides/liquidity/03-fetching-positions.md @@ -5,7 +5,7 @@ title: Fetching Positions ## Introduction -This guide will cover how to create (or mint) a liquidity position on the Uniswap V3 protocol. +This guide will cover how to fetch Positions from the Blockchain. Like the [Liquidity Position guide](./01-position-data.md) it doesn't have an accompanying example, nevertheless the concepts and functions used here can be found among the various examples that interact with liquidity positions. :::info @@ -17,125 +17,81 @@ In this guide, we will fetch **all Positions** an address has and fetch the **de The guide will **cover**: -1. Creating an ethersJS contract to interact with the NonfungiblePositionManager. -2. Fetching all positions for an address. -3. Fetching the position info for the positions. +1. Fetching all positions for an address. +2. Fetching the position info for the positions. -At the end of the guide, given the inputs above, we should be able to mint a liquidity position with the press of a button and view the position on the UI of the web application. +:::info +The SDKs that are used in the guide are now published by the [Uniswap Foundation](https://github.com/uniswapfoundation) instead of Uniswap Labs. +You can find a list of supported SDKs [here](https://www.npmjs.com/org/uniswapfoundation). +Make sure you don't mix SDKs published by Uniswap Labs and the Uniswap Foundation to avoid unpredictable behavior. +::: + +For this guide, the following Uniswap packages are used: -For this guide, we do not need to use the Uniswap SDKs, we will only import the contract ABI for the NonfungiblePositionManager Contract from [`@uniswap/v3-periphery`](https://www.npmjs.com/package/@uniswap/v3-periphery). +- [`@uniswapfoundation/v3-sdk`](https://www.npmjs.com/package/@uniswapfoundation/v3-sdk) +- [`@uniswapfoundation/sdk-core`](https://www.npmjs.com/package/@uniswapfoundation/sdk-core) -## Connecting to the NFTPositionManager Contract +## Fetching the number of Positions -We use **ethersJS** to interact with the NonfungiblePositionManager Contract. Let's create an ethers Contract: +We want to fetch all Positions for our address. +We first fetch the number of positions an address owns using the `getPositionCount` function: ```typescript -import { ethers } from 'ethers' -import INONFUNGIBLE_POSITION_MANAGER from '@uniswap/v3-periphery/artifacts/contracts/NonfungiblePositionManager.sol/NonfungiblePositionManager.json' +import ethers from 'ethers' +import { Position } from '@uniswapfoundation/v3-sdk' -const provider = new ethers.providers.JsonRpcProvider(rpcUrl) +const provider = new ethers.providers.JsonRpcProvider('...rpcUrl') -const nfpmContract = new ethers.Contract( - NONFUNGIBLE_POSITION_MANAGER_CONTRACT_ADDRESS, - INONFUNGIBLE_POSITION_MANAGER.abi, - provider -) -``` +// Address we want to fetch the positions for +const address = '...' -We get the Contract ABI from the 'v3-periphery` package and the contract address from [Github](https://github.com/Uniswap/v3-periphery/blob/main/deploys.md) +const positionCount: bigint = await Position.getPositionCount(provider, address) +``` -## Fetching the Position Ids +Depending on the number of Positions this address owns, we may want to fetch them individually or all at once. -We want to fetch all Position Ids for our address. We first fetch the number of positions and then the ids by their indices. +## Fetching individual Positions -We fetch the number of positions using the `balanceOf` read call: +If we want to paginate the positions an address owns we can fetch them one by one using the `getPositionForAddressAndIndex` function: ```typescript +let index = 0 -const numPositions = await nfpmContract.balanceOf(address) +const position: Position = await getPositionForAddressAndIndex( + provider, + address, + index +) ``` -Next we iterate over the number of positions and fetch the ids: +We can use the position count we fetched earlier to create a loop for all positions we want to fetch. -```typescript -const calls = [] +## Fetching all Positions for an address -for (let i = 0; i < numPositions; i++) { - calls.push( - nfpmContract.tokenOfOwnerByIndex(address, i) - ) -} - -const positionIds = await Promise.all(calls) -``` +To fetch all Positions at once, we can use the `getAllPositionsForAddress` function: -## Fetching the Position Info - -Now that we have the ids of the Positions associated with our address, we can fetch the position info using the `positions` function. - -The solidity function returns a lot of values describing the Position: - -```solidity -function positions( - uint256 tokenId - ) external view returns ( - uint96 nonce, - address operator, - address token0, - address token1, - uint24 fee, - int24 tickLower, - int24 tickUpper, - uint128 liquidity, - uint256 feeGrowthInside0LastX128, - uint256 feeGrowthInside1LastX128, - uint128 tokensOwed0, - uint128 tokensOwed1 - ) +```typescript +const allPositions: Position[] = await getAllPositionsForAddress( + provider, + address +) ``` -In this example we only care about values needed to interact with positions, so we create an Interface `PositionInfo`: +This call may be too heavy for addresses that have hundreds of Positions. -```typescript -interface PositionInfo { - tickLower: number - tickUpper: number - liquidity: JSBI - feeGrowthInside0LastX128: JSBI - feeGrowthInside1LastX128: JSBI - tokensOwed0: JSBI - tokensOwed1: JSBI -} -``` +## Fetching a Position with its Id -We fetch the Position data with `positions`: +In some cases we may know the id of a specific Position, for example if we just created it. +We can use the `fetchWithPositionId` function to fetch the Position: ```typescript -const positionCalls = [] - -for (let id of positionIds) { - positionCalls.push( - nfpmContract.positions(id) - ) -} -const callResponses = await Promise.all(positionCalls) +const position: Position = await fetchWithPositionId( + provider, + positionId +) ``` -Finally, we map the RPC response to our interface: - -```typescript -const positionInfos = callResponses.map((position) => { - return { - tickLower: position.tickLower, - tickUpper: position.tickUpper, - liquidity: JSBI.BigInt(position.liquidity), - feeGrowthInside0LastX128: JSBI.BigInt(position.feeGrowthInside0LastX128), - feeGrowthInside1LastX128: JSBI.BigInt(position.feeGrowthInside1LastX128), - tokensOwed0: JSBI.BigInt(position.tokensOwed0), - tokensOwed1: JSBI.BigInt(position.tokensOwed1), - } -}) -``` +## Next Steps -We now have an array containing PositionInfo for all positions that our address holds. +Now that we know how to fetch Positions, let's move to the next guide on [modifying Positions](./04-modifying-position.md). diff --git a/docs/sdk/v3/guides/liquidity/04-modifying-position.md b/docs/sdk/v3/guides/liquidity/04-modifying-position.md index 4e349321cd..2e980cffe2 100644 --- a/docs/sdk/v3/guides/liquidity/04-modifying-position.md +++ b/docs/sdk/v3/guides/liquidity/04-modifying-position.md @@ -20,10 +20,16 @@ The guide will **cover**: At the end of the guide, given the inputs above, we should be able to add or remove liquidity from a minted position with the press of a button and see the change reflected in our position and the balance of our tokens. +:::info +The SDKs that are used in the guide are now published by the [Uniswap Foundation](https://github.com/uniswapfoundation) instead of Uniswap Labs. +You can find a list of supported SDKs [here](https://www.npmjs.com/org/uniswapfoundation). +Make sure you don't mix SDKs published by Uniswap Labs and the Uniswap Foundation to avoid unpredictable behavior. +::: + For this guide, the following Uniswap packages are used: -- [`@uniswap/v3-sdk`](https://www.npmjs.com/package/@uniswap/v3-sdk) -- [`@uniswap/sdk-core`](https://www.npmjs.com/package/@uniswap/sdk-core) +- [`@uniswapfoundation/v3-sdk`](https://www.npmjs.com/package/@uniswapfoundation/v3-sdk) +- [`@uniswapfoundation/sdk-core`](https://www.npmjs.com/package/@uniswapfoundation/sdk-core) The core code of this guide can be found in [`addLiquidity()`](https://github.com/Uniswap/examples/blob/d34a53412dbf905802da2249391788a225719bb8/v3-sdk/modifying-position/src/example/Example.tsx#L33) and [`removeLiquidity()`](https://github.com/Uniswap/examples/blob/733d586070afe2c8cceb35d557a77eac7a19a656/v3-sdk/modifying-position/src/example/Example.tsx#L83) @@ -68,201 +74,207 @@ The `fractionToRemove` variable is the fraction of the Position that we want to ## Adding liquidity to our position -Assuming we have already minted a position, our first step is to construct the modified position using our original position to calculate the amount by which we want to increase our current position: - -```typescript -const fractionToAdd: number = ... - -const amount0Increased: JSBI = fromReadableAmount( - readableAmount0 * fractionToAdd, - token0.decimals -) -const amount1Increase: JSBI = fromReadableAmount( - readableAmount1 * fractionToAdd, - token1.decimals -) - -const positionToIncreaseBy = constructPosition( - amount0Increased, - amount1Increase - ) -) -``` +Assuming we have already minted a position, our first step is to fetch that position, or reuse the `Position` object if it was just created. -The `fromReadableAmount()` function calculates the amount of tokens in their smallest unit, so for example 1 ETH would be `1000000000000000000` Wei as ETH has 18 decimals. +To fetch a position using the id you will need an initialized ethers provider for the RPC you are using. +For the local fork configuration mentioned above you can use `http://localhost:8545`. -A better way to get the amounts might be to fetch them with the positionId directly from the blockchain. -We demonstrated how to do that in the [first guide](./01-position-data.md#fetching-positions) of this series. +Use the following snippet to fetch a position using the position id: ```typescript -import { Pool, Position } from '@uniswap/v3-sdk' -import JSBI from 'jsbi' - -function constructPosition( - amount0: JSBI, - amount1: JSBI -): Position { - // create Pool same as in the previous guide - const pool = new Pool(...) - - // create position using the maximum liquidity from input amounts - return Position.fromAmounts({ - pool, - tickLower: - nearestUsableTick(pool.tickCurrent, pool.tickSpacing) - - pool.tickSpacing * 2, - tickUpper: - nearestUsableTick(pool.tickCurrent, pool.tickSpacing) + - pool.tickSpacing * 2, - amount0, - amount1, - useFullPrecision: true, - }) -} +import { ethers } from 'ethers' +import { Position } from '@uniswapfoundation/v3-sdk' + +// You need to know this, or fetch the position differently +const myPositionId = "0xabcdef" + +const ethersProvider = new ethers.providers.JsonRpcProvider("http://localhost:8545") +const position = await Position.fetchWithPositionId(ethersProvider, myPositionId) ``` -The function receives two arguments, which are the amounts that are used to construct the Position instance. In this example, both of the arguments follow the same logic: we multiply the parameterized `tokenAmount` by the parameterized `fractionToAdd` since the new liquidity position will be added on top of the already minted liquidity position. +For more examples on how to fetch your position, refer to the [Fetching Positions Guide](./03-fetching-positions.md). + +The easiest version of increasing your position is if you know a percentage by which you want to increase it. +If you increase it by 10%, it means both current token balances (amount0, amount1) in the position will be increased by 10% each. + +So if you have a position with 1000 USDC and 1000 USDT, the 10% increase will increase both to 1100. +This also means you need to make approvals to the `NonfungiblePositionManager` before to reflect at least those 100 USDC and USDT changes, as mentioned in the beginning of this guide. The exact amount can be calculated using current amount0 and amount1 of your position and the percentage you are using. -We then need to construct an options object of type [`AddLiquidityOptions`](https://github.com/Uniswap/v3-sdk/blob/08a7c050cba00377843497030f502c05982b1c43/src/nonfungiblePositionManager.ts#L77) similar to how we did in the minting case. In this case, we will use [`IncreaseOptions`](https://github.com/Uniswap/v3-sdk/blob/08a7c050cba00377843497030f502c05982b1c43/src/nonfungiblePositionManager.ts#L75): +The snippet to increase liquidity by 10% can be seen below: ```typescript -import { AddLiquidityOptions } from '@uniswap/v3-sdk' +import { ethers } from 'ethers' +import { Fraction } from '@uniswapfoundation/sdk-core' + +// Use your private key or another way to initialize a Wallet. +// A different way would be to use the Metamask signer in the browser. +const myWallet = new ethers.Wallet("0x_private_key") + +const ethersProvider = new ethers.providers.JsonRpcProvider("http://localhost:8545") +// Either you know this already like mentioned before, or you can access it from the fetched position with +// position.positionId +const positionId = "0xabcdef" const addLiquidityOptions: AddLiquidityOptions = { deadline: Math.floor(Date.now() / 1000) + 60 * 20, slippageTolerance: new Percent(50, 10_000), - tokenId, + tokenId: positionId, } -``` -Compared to minting, we have we have omitted the `recipient` parameter and instead passed in the `tokenId` of the position we previously minted. -As the Position already exists, the recipient doesn't change, instead the NonfungiblePositionManager contract can modify the existing Position by accessing it with its id. +const transactionResponse = await position.increasePositionByPercentageOnChain({ + signer: myWallet, + provider: ethersProvider, + percentage: new Fraction(10, 100), // (10 / 100) for 10%tokenId + options: addLiquidityOptions +}) -The tokenId can be fetched with the tokenOfOwnerByIndex function of the NonfungiblePositionManager Contract as described [here](./01-position-data.md#fetching-positions). +// Wait for 3 confirmations and then access the transaction receipt. +const transactionReceipt = await transactionResponse.wait(3) +``` -The newly created position along with the options object are then passed to the `NonfungiblePositionManager`'s `addCallParameters`: +In certain case you might want to increase your position to a specific value. -```typescript -import { NonfungiblePositionManager } from '@uniswap/v3-sdk' +Let's assume you display position data in a UI to the user and they can freely change the amounts to which they want to change the position amounts. +Whenever the user changes one of the values (amount0 or amount1), you will need to automatically update the other value as positions can only be changed +in percentage increments on both sides as they need to be balanced. -const positionToIncreaseBy = constructPosition(CurrentConfig.tokens.amount0, CurrentConfig.tokens.amount1) +If you display a position with the following values: -const { calldata, value } = NonfungiblePositionManager.addCallParameters( - positionToIncreaseBy, - addLiquidityOptions -) +``` +Pool: USDC <> WETH +amount0: 1000 USDC +amount1: 0.5 WETH ``` -The return values of `addCallParameters` are the calldata and value of the transaction we need to submit to increase our position's liquidity. We can now build and execute the transaction: +And the user wants to change `amount0` to 1500 USDC, you would run a calculation like the following: ```typescript -import { ethers } from 'ethers' +const currentAmount0 = myPosition.amount0 // USDC +const currentAmount1 = myPosition.amount1 // WETH -const transaction = { - data: calldata, - to: NONFUNGIBLE_POSITION_MANAGER_CONTRACT_ADDRESS, - value: value, - from: address, - maxFeePerGas: MAX_FEE_PER_GAS, - maxPriorityFeePerGas: MAX_PRIORITY_FEE_PER_GAS, -} +// User asked for 1500 USDC. Fractions are initialized with the smallest amount +// So we need to multiply 1500 by 10^decimals +const wantedAmount0 = CurrencyAmount.fromFractionalAmount(myPosition.pool.token0, 1500n * (10n ** BigInt(myPosition.pool.token0.decimals)), 1) + +// First make sure that wantedAmount0 is higher than currentAmount0, otherwise you will need to follow +// the next section of this guide to decrease a position. + +const positionMultiplier = wantedAmount0.divide(currentAmount0).asFraction -const wallet = new ethers.Wallet(privateKey, provider) +const resultingAmount1 = currentAmount1.multiply(positionMultiplier) -const txRes = await wallet.sendTransaction(transaction) +// We can now display resultingAmount1 to the user, so he knows both amounts before confirming the position change. + +// 1500 / 1000 = 1.5, so we need to subtract 1 to get 0.5, which is the format the increase position function expects +const percentageIncrease = positionMultiplier.subtract(new Fraction(1, 1)) ``` -We can get the Contract address for the NonfungiblePositionManager from [Github](https://github.com/Uniswap/v3-periphery/blob/main/deploys.md). +Using this `percentageIncrease` Fraction you can follow the snippets from before to increase the position by this percentage. -After pressing the button, note how the balance of USDC and DAI drops and our position's liquidity increases. +Make sure to approve the `NonfungiblePositionManager` by the difference of current amounts and wanted amounts for both tokens. If you made an unlimited +approval for minting the position, you can skip this step. ## Removing liquidity from our position -The `removeLiquidity` function is the mirror action of adding liquidity and will be somewhat similar as a result, requiring a position to already be minted. +Assuming we have already minted a position, our first step is to fetch that position, or reuse the `Position` object if it was just created. + +To fetch a position using the id you will need an initialized ethers provider for the RPC you are using. +For the local fork configuration mentioned above you can use `http://localhost:8545`. -To start, we create a position identical to the one we minted: +Use the following snippet to fetch a position using the position id: ```typescript -const amount0: JSBI = fromReadableAmount( - readableAmount0 * fractionToAdd, - token0.decimals -) -const amount1: JSBI = fromReadableAmount( - readableAmount1 * fractionToAdd, - token1.decimals -) - -const currentPosition = constructPosition( - amount0, - amount1 -) +import { ethers } from 'ethers' +import { Position } from '@uniswapfoundation/v3-sdk' + +// You need to know this, or fetch the position differently +const myPositionId = "0xabcdef" + +const ethersProvider = new ethers.providers.JsonRpcProvider("http://localhost:8545") +const position = await Position.fetchWithPositionId(ethersProvider, myPositionId) ``` -We then need to construct an options object of type [`RemoveLiquidityOptions`](https://github.com/Uniswap/v3-sdk/blob/08a7c050cba00377843497030f502c05982b1c43/src/nonfungiblePositionManager.ts#L138): +For more examples on how to fetch your position, refer to the [Fetching Positions Guide](./03-fetching-positions.md). + +The easiest version of increasing your position is if you know a percentage by which you want to decrease it. +If you decrease it by 10%, it means both current token balances (amount0, amount1) in the position will be decreased by 10% each. + +So if you have a position with 1000 USDC and 1000 USDT, the 10% decrease will decrease both to 900. + +If you want to completely close a position, pass 100% to this function. + +The snippet to decrease liquidity by 10% can be seen below: ```typescript -import { RemoveLiquidityOptions } from '@uniswap/v3-sdk' -import { Percent } from '@uniswap/sdk-core' +import { ethers } from 'ethers' +import { Fraction } from '@uniswapfoundation/sdk-core' + +// Use your private key or another way to initialize a Wallet. +// A different way would be to use the Metamask signer in the browser. +const myWallet = new ethers.Wallet("0x_private_key") + +const ethersProvider = new ethers.providers.JsonRpcProvider("http://localhost:8545") -const removeLiquidityOptions: RemoveLiquidityOptions = { +// Either you know this already like mentioned before, or you can access it from the fetched position with +// position.positionId +const positionId = "0xabcdef" +const decreaseLiquidityOptions: Omit = { deadline: Math.floor(Date.now() / 1000) + 60 * 20, slippageTolerance: new Percent(50, 10_000), tokenId: positionId, - // percentage of liquidity to remove - liquidityPercentage: new Percent(0.5), - collectOptions, } + +const transactionResponse = await position.decreasePositionByPercentageOnChain({ + signer: myWallet, + provider: ethersProvider, + percentage: new Fraction(10, 100), // (10 / 100) for 10%tokenId + options: decreaseLiquidityOptions +}) + +// Wait for 3 confirmations and then access the transaction receipt. +const transactionReceipt = await transactionResponse.wait(3) ``` -Just as with adding liquidity, we have we have omitted the `recipient` parameter and instead passed in the `tokenId` of the position we previously minted. +In certain case you might want to decrease your position to a specific value. -We have also provide two additional parameters: +Let's assume you display position data in a UI to the user and they can freely change the amounts to which they want to change the position amounts. +Whenever the user changes one of the values (amount0 or amount1), you will need to automatically update the other value as positions can only be changed +in percentage increments on both sides as they need to be balanced. -- `liquidityPercentage` determines how much liquidity is removed from our initial position (as a `Percentage`), and transfers the removed liquidity back to our address. We set this percentage from our guide configuration ranging from 0 (0%) to 1 (100%). In this example we would remove 50% of the liquidity. -- [`collectOptions`](https://github.com/Uniswap/v3-sdk/blob/08a7c050cba00377843497030f502c05982b1c43/src/nonfungiblePositionManager.ts#L105) gives us the option to collect the fees, if any, that we have accrued for this position. In this example, we won't collect any fees, so we provide zero values. If you'd like to see how to collect fees without modifying your position, check out our [collecting fees](./03-collecting-fees.md) guide! +If you display a position with the following values: -```typescript -import { CurrencyAmount } from '@uniswap/sdk-core' -import { CollectOptions } from '@uniswap/v3-sdk' - -const collectOptions: Omit = { - expectedCurrencyOwed0: CurrencyAmount.fromRawAmount( - token0, - 0 - ), - expectedCurrencyOwed1: CurrencyAmount.fromRawAmount( - token1, - 0 - ), - recipient: address, -} +``` +Pool: USDC <> WETH +amount0: 1000 USDC +amount1: 0.5 WETH ``` -The position object along with the options object is passed to the `NonfungiblePositionManager`'s `removeCallParameters`, similar to how we did in the adding liquidity case: +And the user wants to change `amount0` to 800 USDC, you would run a calculation like the following: ```typescript -const { calldata, value } = NonfungiblePositionManager.removeCallParameters( - currentPosition, - removeLiquidityOptions -) -``` +const currentAmount0 = myPosition.amount0 // USDC +const currentAmount1 = myPosition.amount1 // WETH -The return values `removeCallParameters` are the calldata and value that are needed to construct the transaction to remove liquidity from our position. We can build the transaction and send it for execution: +// User asked for 800 USDC. Fractions are initialized with the smallest amount +// So we need to multiply 1500 by 10^decimals +const wantedAmount0 = CurrencyAmount.fromFractionalAmount(myPosition.pool.token0, 800n * (10n ** BigInt(myPosition.pool.token0.decimals)), 1) -```typescript -const transaction = { - data: calldata, - to: NONFUNGIBLE_POSITION_MANAGER_CONTRACT_ADDRESS, - value: value, - from: address, - maxFeePerGas: MAX_FEE_PER_GAS, - maxPriorityFeePerGas: MAX_PRIORITY_FEE_PER_GAS, -} +// First make sure that wantedAmount0 is lower than currentAmount0, otherwise you will need to follow +// the previous section of this guide to increase a position. + +const positionMultiplier = wantedAmount0.divide(currentAmount0).asFraction -const txRes = await wallet.sendTransaction(transaction) +const resultingAmount1 = currentAmount1.multiply(positionMultiplier) + +// We can now display resultingAmount1 to the user, so he knows both amounts before confirming the position change. + +// 800 / 1000 = 0.8, so we need to do 1 - 0.8 to get 0.2, which is the format the decrease position function expects +const percentageDecrease = new Fraction(1, 1).subtract(positionMultiplier) ``` -After pressing the button, note how the balance of USDC and DAI increases and our position's liquidity drops. +Using this `percentageDecrease` Fraction you can follow the snippets from before to decrease the position by this percentage. + +For decreasing positions, no approvals are required. ## Next Steps diff --git a/docs/sdk/v3/guides/liquidity/05-collecting-fees.md b/docs/sdk/v3/guides/liquidity/05-collecting-fees.md index b2860165ab..a070af76c2 100644 --- a/docs/sdk/v3/guides/liquidity/05-collecting-fees.md +++ b/docs/sdk/v3/guides/liquidity/05-collecting-fees.md @@ -11,18 +11,23 @@ This guide will cover how to collect fees from a liquidity position on the Unisw If you need a briefer on the SDK and to learn more about how these guides connect to the examples repository, please visit our [background](../01-background.md) page! ::: -In the Uniswap V3 protocol, liquidity positions are represented using non-fungible tokens. In this guide we will use the `NonfungiblePositionManager` class to help us mint a liquidity position for the **USDC - DAI** pair. We will then attempt to collect any fees that the position has accrued from those trading against our provisioned liquidity. The inputs to our guide are the **two tokens** that we are pooling for, the **amount** of each token we are pooling for, the Pool **fee** and the **max amount of accrued fees** we want to collect for each token. +In the Uniswap V3 protocol, liquidity positions are represented using non-fungible tokens. In this guide we will use the `NonfungiblePositionManager` class to help us mint a liquidity position for the **USDC - DAI** pair. We will then attempt to collect any fees that the position has accrued from those trading against our provisioned liquidity. The inputs to our guide are the **two tokens** that we are pooling for, the **amount** of each token we are pooling for, the Pool **fee** and the **percentage of our accrued fees** we want to collect from the pool. The guide will **cover**: -1. Setting up our fee collection -2. Submitting our fee collection transaction +1. Collecting Fees from a position At the end of the guide, given the inputs above, we should be able to collect the accrued fees (if any) of a minted position with the press of a button and see the change reflected in our position and the balance of our tokens. +:::info +The SDKs that are used in the guide are now published by the [Uniswap Foundation](https://github.com/uniswapfoundation) instead of Uniswap Labs. +You can find a list of supported SDKs [here](https://www.npmjs.com/org/uniswapfoundation). +Make sure you don't mix SDKs published by Uniswap Labs and the Uniswap Foundation to avoid unpredictable behavior. +::: + For this guide, the following Uniswap packages are used: -- [`@uniswap/v3-sdk`](https://www.npmjs.com/package/@uniswap/v3-sdk) +- [`@uniswapfoundation/v3-sdk`](https://www.npmjs.com/package/@uniswapfoundation/v3-sdk) - [`@uniswap/sdk-core`](https://www.npmjs.com/package/@uniswap/sdk-core) The core code of this guide can be found in [`collectFees()`](https://github.com/Uniswap/examples/blob/main/v3-sdk/collecting-fees/src/libs/liquidity.ts#L35). @@ -40,82 +45,24 @@ All of the fee collecting logic can be found in the [`collectFees`](https://gith To start, we fetch the position from the NonfungiblePositionManager Contract to get the fees we are owed: ```typescript -import { ethers } from 'ethers' -import JSBI from 'jsbi' -... - -const nfpmContract = new ethers.Contract(NONFUNGIBLE_POSITION_MANAGER_ADDRESS, provider) -const position = nfpmContract.positions(positionId) -``` +import { Position } from '@uniswapfoundation/v3-sdk' -Next, we construct an options object of type [`CollectOptions`](https://github.com/Uniswap/v3-sdk/blob/08a7c050cba00377843497030f502c05982b1c43/src/nonfungiblePositionManager.ts#L105) that holds the data about the fees we want to collect: - -```typescript -import { CurrencyAmount } from '@uniswap/sdk-core' - -const collectOptions: CollectOptions = { - tokenId: positionId, - expectedCurrencyOwed0: CurrencyAmount.fromRawAmount( - CurrentConfig.tokens.token0, - JSBI.BigInt(position.tokensOwed0) - ), - expectedCurrencyOwed1: CurrencyAmount.fromRawAmount( - CurrentConfig.tokens.token1, - JSBI.BigInt(position.tokensOwed1) - ), - recipient: address, -} +const position = await Position.fetchWithPositionId(provider, positionId) ``` Read more about fetching position info [here](./01-position-data.md#fetching-positions). -Similar to the other functions exposed by the `NonfungiblePositionManager`, we pass the `tokenId` and the `recipient` of the fees, which in this case is our function's input position id and our wallet's address. - -The other two `CurrencyAmount` parameters (`expectedCurrencyOwed0` and `expectedCurrencyOwed1`) define the **maximum** amount of currency we expect to get collect through accrued fees of each token in the pool. We set these through our guide's configuration. - -In a real world scenario, we can fetch the amount of fees that are owed to the Position through the `positions()` function of the NonfungiblePositionManager Contract. -We fetch the position info like in this code snippet taken from the [Fetching Positions guide](./03-fetching-positions.md): +Next, we specify the percentage of fees that we want to collect and use the sdk to collect our fees. +We want to collect all fees in this example: ```typescript -const positionInfos = callResponses.map((position) => { - return { - tickLower: position.tickLower, - tickUpper: position.tickUpper, - liquidity: JSBI.BigInt(position.liquidity), - feeGrowthInside0LastX128: JSBI.BigInt(position.feeGrowthInside0LastX128), - feeGrowthInside1LastX128: JSBI.BigInt(position.feeGrowthInside1LastX128), - tokensOwed0: JSBI.BigInt(position.tokensOwed0), - tokensOwed1: JSBI.BigInt(position.tokensOwed1), - } -}) -``` +const percentageToCollect = new Percent(1) -The `tokensOwed0` and `tokensOwed1` values are the fees owed. - -In this example, we have the values hardcoded in the [`config.ts`](https://github.com/Uniswap/examples/blob/main/v3-sdk/collecting-fees/src/config.ts) file. - -## Submitting our fee collection transaction - -Next, we get the call parameters for collecting our fees from our `NonfungiblePositionManager` using the constructed `CollectOptions`: - -```typescript -const { calldata, value } = - NonfungiblePositionManager.collectCallParameters(collectOptions) -``` - -The function above returns the calldata and value required to construct the transaction for collecting accrued fees. Now that we have both the calldata and value we needed for the transaction, we can build and execute the it: - -```typescript -const transaction = { - data: calldata, - to: NONFUNGIBLE_POSITION_MANAGER_CONTRACT_ADDRESS, - value: value, - from: address, - maxFeePerGas: MAX_FEE_PER_GAS, - maxPriorityFeePerGas: MAX_PRIORITY_FEE_PER_GAS, -} - -const txRes = await wallet.sendTransaction(transaction) +const txResponse = await position.collectFeesOnChain({ + signer: wallet, + provider, + percentage: CurrentConfig.tokens.feePercentage, + }) ``` After pressing the button, if someone has traded against our position, we should be able to note how the balance of USDC and DAI increases as we collect fees. diff --git a/docs/sdk/v3/guides/liquidity/06-swap-and-add-liquidity.md b/docs/sdk/v3/guides/liquidity/06-swap-and-add-liquidity.md index be1b7d20d6..d47e25c119 100644 --- a/docs/sdk/v3/guides/liquidity/06-swap-and-add-liquidity.md +++ b/docs/sdk/v3/guides/liquidity/06-swap-and-add-liquidity.md @@ -24,11 +24,17 @@ The guide will **cover**: At the end of the guide, given the inputs above, we should be able swap-and-add liquidity using 100% of the input assets with the press of a button and see the change reflected in our position and the balance of our tokens. +:::info +The SDKs that are used in the guide are now published by the [Uniswap Foundation](https://github.com/uniswapfoundation) instead of Uniswap Labs. +You can find a list of supported SDKs [here](https://www.npmjs.com/org/uniswapfoundation). +Make sure you don't mix SDKs published by Uniswap Labs and the Uniswap Foundation to avoid unpredictable behavior. +::: + For this guide, the following Uniswap packages are used: -- [`@uniswap/v3-sdk`](https://www.npmjs.com/package/@uniswap/v3-sdk) -- [`@uniswap/sdk-core`](https://www.npmjs.com/package/@uniswap/sdk-core) -- [`@uniswap/smart-order-router`](https://www.npmjs.com/package/@uniswap/smart-order-router) +- [`@uniswapfoundation/v3-sdk`](https://www.npmjs.com/package/@uniswapfoundation/v3-sdk) +- [`@uniswapfoundation/sdk-core`](https://www.npmjs.com/package/@uniswapfoundation/sdk-core) +- [`@uniswapfoundation/smart-order-router`](https://www.npmjs.com/package/@uniswapfoundation/smart-order-router) The core code of this guide can be found in [`swapAndAddLiquidity()`](https://github.com/Uniswap/examples/blob/main/v3-sdk/swap-and-add-liquidity/src/libs/liquidity.ts#L48). @@ -43,24 +49,30 @@ Also note that we do not need to give approval to the `NonfungiblePositionManage The first step is to approve the `SwapRouter` smart contract to spend our tokens for us in order for us to add liquidity to our position: ```typescript -const tokenInApproval = await getTokenTransferApproval( - token0, - V3_SWAP_ROUTER_ADDRESS -) - -const tokenOutApproval = await getTokenTransferApproval( - token1, - V3_SWAP_ROUTER_ADDRESS -) +// Give approval to the router contract to transfer tokens + const tokenInApproval = await approveTokenTransfer({ + contractAddress: V3_SWAP_ROUTER_ADDRESS, + tokenAddress: CurrentConfig.tokens.token0.address, + amount: TOKEN_AMOUNT_TO_APPROVE_FOR_TRANSFER, + signer: getWallet(), + }) + + const tokenOutApproval = await approveTokenTransfer({ + contractAddress: V3_SWAP_ROUTER_ADDRESS, + tokenAddress: CurrentConfig.tokens.token1.address, + amount: TOKEN_AMOUNT_TO_APPROVE_FOR_TRANSFER, + signer: getWallet(), + }) ``` We described the `getTokenTransferApproval` function [here](./02-minting-position.md#giving-approval-to-transfer-our-tokens). +We defined the address for the swaprouter in our `constants.ts` file. -Then we can setup our router, the [`AlphaRouter`](https://github.com/Uniswap/smart-order-router/blob/97c1bb7cb64b22ebf3509acda8de60c0445cf250/src/routers/alpha-router/alpha-router.ts#L333), which is part of the [smart-order-router package](https://www.npmjs.com/package/@uniswap/smart-order-router). The router requires a `chainId` and a `provider` to be initialized. Note that routing is not supported for local forks, so we will use a mainnet provider even when swapping on a local fork: +Then we can setup our router, the [`AlphaRouter`](https://github.com/Uniswap/smart-order-router/blob/97c1bb7cb64b22ebf3509acda8de60c0445cf250/src/routers/alpha-router/alpha-router.ts#L333), which is part of the [smart-order-router package](https://www.npmjs.com/package/@uniswapfoundation/smart-order-router). The router requires a `chainId` and a `provider` to be initialized. Note that routing is not supported for local forks, so we will use a mainnet provider even when swapping on a local fork: ```typescript import { ethers } from 'ethers' -import { AlphaRouter } from '@uniswap/smart-order-router' +import { AlphaRouter } from '@uniswapfoundation/smart-order-router' const provider = new ethers.providers.JsonRpcProvider(rpcUrl) @@ -76,7 +88,7 @@ Having created the router, we now need to construct the parameters required to m The first two parameters are the currency amounts we use as input to the `routeToRatio` algorithm: ```typescript -import { CurrencyAmount } from '@uniswap/sdk-core' +import { CurrencyAmount } from '@uniswapfoundation/sdk-core' const token0CurrencyAmount = CurrencyAmount.fromRawAmount( token0, @@ -98,7 +110,7 @@ const token1CurrencyAmount = CurrencyAmount.fromRawAmount( Next, we will create a placeholder position with a liquidity of `1` since liquidity is still unknown and will be set inside the call to `routeToRatio`: ```typescript -import { Pool, Position, nearestUsableTick } from '@uniswap/v3-sdk' +import { Pool, Position, nearestUsableTick } from '@uniswapfoundation/v3-sdk' const placeholderPosition = new Position{ pool, @@ -118,8 +130,8 @@ We then need to create an instance of `SwapAndAddConfig` which will set addition - `maxIterations` determines the maximum times the algorithm will iterate to find a ratio within error tolerance. If max iterations is exceeded, an error is returned. The benefit of running the algorithm more times is that we have more chances to find a route, but more iterations will longer to execute. We've used a default of 6 in our example. ```typescript -import { Fraction } from '@uniswap/sdk-core' -import { SwapAndAddConfig } from '@uniswap/smart-order-router' +import { Fraction } from '@uniswapfoundation/sdk-core' +import { SwapAndAddConfig } from '@uniswapfoundation/smart-order-router' const swapAndAddConfig: SwapAndAddConfig = { ratioErrorTolerance: new Fraction(1, 100), @@ -133,7 +145,7 @@ Finally, we will create an instance of `SwapAndAddOptions` to configure which po - **`addLiquidityOptions`** must contain a `tokenId` to add to an existing position ```typescript -import { SwapAndAddOptions } from '@uniswap/smart-order-router' +import { SwapAndAddOptions } from '@uniswapfoundation/smart-order-router' const swapAndAddOptions: SwapAndAddOptions = { swapOptions: { @@ -153,7 +165,7 @@ const swapAndAddOptions: SwapAndAddOptions = { Having constructed all the parameters we need to call `routeToRatio`, we can now make the call to the function: ```typescript -import { SwapToRatioResponse } from '@uniswap/smart-order-router' +import { SwapToRatioResponse } from '@uniswapfoundation/smart-order-router' const routeToRatioResponse: SwapToRatioResponse = await router.routeToRatio( token0CurrencyAmount, @@ -167,7 +179,7 @@ const routeToRatioResponse: SwapToRatioResponse = await router.routeToRatio( The return type of the function call is [SwapToRatioResponse](https://github.com/Uniswap/smart-order-router/blob/97c1bb7cb64b22ebf3509acda8de60c0445cf250/src/routers/router.ts#L121). If a route was found successfully, this object will have two fields: the status (success) and the `SwapToRatioRoute` object. We check to make sure that both of those conditions hold true before we construct and submit the transaction: ```typescript -import { SwapToRatioStatus } from '@uniswap/smart-order-router' +import { SwapToRatioStatus } from '@uniswapfoundation/smart-order-router' if ( !routeToRatioResponse || @@ -184,7 +196,7 @@ In case a route was not found, we return from the function a `Failed` state for After making sure that a route was successfully found, we can now construct and send the transaction. The response (`SwapToRatioRoute`) will have the properties we need to construct our transaction object: ```typescript -import { SwapToRatioRoute } from '@uniswap/smart-order-router' +import { SwapToRatioRoute } from '@uniswapfoundation/smart-order-router' const route: SwapToRatioRoute = routeToRatioResponse.result const transaction = { diff --git a/docs/sdk/v3/guides/swaps/01-quoting.md b/docs/sdk/v3/guides/swaps/01-quoting.md index f858bacd25..10778b33e7 100644 --- a/docs/sdk/v3/guides/swaps/01-quoting.md +++ b/docs/sdk/v3/guides/swaps/01-quoting.md @@ -11,23 +11,27 @@ This guide will cover how to get the current quotes for any token pair on the Un If you need a briefer on the SDK and to learn more about how these guides connect to the examples repository, please visit our [background](../01-background.md) page! ::: -In this example we will use `quoteExactInputSingle` to get a quote for the pair **USDC - WETH**. +In this example we will use the Quoter class to get a quote for a trade from **USDC to WETH**. The inputs are the **token in**, the **token out**, the **amount in** and the **fee**. -The **fee** input parameters represents the swap fee that distributed to all in range liquidity at the time of the swap. It is one of the identifiers of a Pool, the others being **tokenIn** and **tokenOut**. +The **fee** input parameter represents the swap fee that is deducted from the trade and given to liquidity providers. It is one of the identifiers of a Pool, the others being **tokenIn** and **tokenOut**. The guide will **cover**: -1. Computing the Pool's deployment address -2. Referencing the Pool contract and fetching metadata -3. Referencing the Quoter contract and getting a quote +1. Fetching a Quote for a simple swap on one Pool At the end of the guide, we should be able to fetch a quote for the given input token pair and the input token amount with the press of a button on the web application. +:::info +The SDKs that are used in the guide are now published by the [Uniswap Foundation](https://github.com/uniswapfoundation) instead of Uniswap Labs. +You can find a list of supported SDKs [here](https://www.npmjs.com/org/uniswapfoundation). +Make sure you don't mix SDKs published by Uniswap Labs and the Uniswap Foundation to avoid unpredictable behavior. +::: + For this guide, the following Uniswap packages are used: -- [`@uniswap/v3-sdk`](https://www.npmjs.com/package/@uniswap/v3-sdk) -- [`@uniswap/sdk-core`](https://www.npmjs.com/package/@uniswap/sdk-core) +- [`@uniswapfoundation/v3-sdk`](https://www.npmjs.com/package/@uniswapfoundation/v3-sdk) +- [`@uniswapfoundation/sdk-core`](https://www.npmjs.com/package/@uniswapfoundation/sdk-core) The core code of this guide can be found in [`quote.ts`](https://github.com/Uniswap/examples/blob/main/v3-sdk/quoting/src/libs/quote.ts) @@ -36,7 +40,8 @@ The core code of this guide can be found in [`quote.ts`](https://github.com/Unis We will use the example configuration `CurrentConfig` in most code snippets of this guide. It has the format: ```typescript -import { Token } from '@uniswap/sdk-core' +import { Token } from '@uniswapfoundation/sdk-core' +import { FeeAmount } from '@uniswapfoundation/v3-sdk' interface ExampleConfig { rpc: { @@ -45,9 +50,9 @@ interface ExampleConfig { } tokens: { in: Token - amountIn: number + readableAmountIn: number out: Token - poolFee: number + poolFee: FeeAmount } } @@ -77,140 +82,61 @@ The pool used is defined by a pair of tokens in [`constants.ts`](https://github. You can also change these two tokens and the fee of the pool in the config, just make sure a Pool actually exists for your configuration. Check out the top pools on [Uniswap info](https://info.uniswap.org/#/pools). -## Computing the Pool's deployment address +Check out the full code for the following snippets in [quote.ts](https://github.com/Uniswap/examples/blob/main/v3-sdk/quoting/src/libs/quote.ts) -To interact with the **USDC - WETH** Pool contract, we first need to compute its deployment address. -If you haven't worked directly with smart contracts yet, check out this [guide](https://docs.alchemy.com/docs/smart-contract-basics) from Alchemy. -The SDK provides a utility method for that: +## Using the SwapQuoter class to fetch a quote -```typescript -import { computePoolAddress } from '@uniswap/v3-sdk' - -const currentPoolAddress = computePoolAddress({ - factoryAddress: POOL_FACTORY_CONTRACT_ADDRESS, - tokenA: CurrentConfig.tokens.in, - tokenB: CurrentConfig.tokens.out, - fee: CurrentConfig.tokens.poolFee, -}) -``` +To get quotes for trades, Uniswap has deployed a **Quoter Contract**. We will use this contract to fetch the output amount we can expect for our trade, without actually executing the trade. -Since each *Uniswap V3 Pool* is uniquely identified by 3 characteristics (token in, token out, fee), we use those -in combination with the address of the *PoolFactory* contract to compute the address of the **USDC - ETH** Pool. -These parameters have already been defined in our [constants.ts](https://github.com/Uniswap/examples/blob/main/v3-sdk/quoting/src/libs/constants.ts#L14) file: +The `SwapQuoter` class allows us to interact with the Quoter Contract. +We will use the `quoteExactInputSingle` function to fetch a quote for our swap: ```typescript -const WETH_TOKEN = new Token( - 1, - '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', - 18, - 'WETH', - 'Wrapped Ether' -) - -const USDC_TOKEN = new Token( - 1, - '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', - 6, - 'USDC', - 'USD//C' -) +async function quoteExactInputSingle( + amountIn: CurrencyAmount, + tokenOut: TOutput, + poolFee: FeeAmount, + provider: Provider +): Promise> ``` -These constants are used in the `config.ts` file, as mentioned in the Introduction. - -We can find the Pool Factory Contract address for our chain [here](../../../../contracts/v3/reference/Deployments.md). - -## Referencing the Pool contract and fetching metadata - -Now that we have the deployment address of the **USDC - ETH** Pool, we can construct an instance of an **ethers** `Contract` to interact with it: +The function expects a `CurrencyAmount` object. We use ethers to parse the input amount: ```typescript import { ethers } from 'ethers' +import { CurrencyAmount } from '@uniswapfoundation/sdk-core' -const provider = new ethers.providers.JsonRpcProvider(rpcUrl) -const poolContract = new ethers.Contract( - currentPoolAddress, - IUniswapV3PoolABI.abi, - provider -) -``` - -To construct the *Contract* we need to provide the address of the contract, its ABI and the provider that will carry out the RPC call for us. -We get access to the contract's ABI through the [@uniswap/v3-core](https://www.npmjs.com/package/@uniswap/v3-core) package, which holds the core smart contracts of the Uniswap V3 protocol: - -```typescript -import IUniswapV3PoolABI from '@uniswap/v3-core/artifacts/contracts/interfaces/IUniswapV3Pool.sol/IUniswapV3Pool.json' -``` - -Having constructed our reference to the contract, we can now access its methods through our provider. -We use a batch `Promise` call. This approach queries state data concurrently, rather than sequentially, to minimize the chance of fetching out of sync data that may be returned if sequential queries are executed over the span of two blocks: - -```typescript -const [token0, token1, fee, liquidity, slot0] = await Promise.all([ - poolContract.token0(), - poolContract.token1(), - poolContract.fee(), - poolContract.liquidity(), - poolContract.slot0(), -]) -``` - -The return values of these methods will become inputs to the quote fetching function. -The `token0` and `token1` variables are the addresses of the tokens in the Pool and should not be mistaken for `Token` objects from the sdk. -For the full code, check out [`getPoolConstants()`](https://github.com/Uniswap/examples/blob/main/v3-sdk/quoting/src/libs/quote.ts#L35) in `quote.ts`. - -:::note -In this example, the metadata we fetch is already present in our inputs. This guide fetches this information first in order to show how to fetch any metadata, which will be expanded on in future guides. -::: - -## Referencing the Quoter contract and getting a quote - -To get quotes for trades, Uniswap has deployed a **Quoter Contract**. We will use this contract to fetch the output amount we can expect for our trade, without actually executing the trade. -Check out the full code for the following snippets in [quote.ts](https://github.com/Uniswap/examples/blob/main/v3-sdk/quoting/src/libs/quote.ts) - -Like we did for the Pool contract, we need to construct an instance of an **ethers** `Contract` for our Quoter contract in order to interact with it: +const rawInputAmount = ethers.utils.parseUnits( + CurrentConfig.tokens.amountIn, + CurrentConfig.tokens.in.decimals + ) -```typescript -const quoterContract = new ethers.Contract( - QUOTER_CONTRACT_ADDRESS, - Quoter.abi, - getProvider() +const currencyAmountIn = CurrencyAmount.fromRawAmount( + CurrentConfig.tokens.tokenIn, + rawInputAmount ) ``` -We get access to the contract's ABI through the [@uniswap/v3-periphery](https://www.npmjs.com/package/@uniswap/v3-periphery) package, which holds the periphery smart contracts of the Uniswap V3 protocol: +We can now use the SwapQuoter class to fetch a quote for our swap. We need a provider to connect to the blockchain: ```typescript -import Quoter from '@uniswap/v3-periphery/artifacts/contracts/lens/Quoter.sol/Quoter.json' -``` - -We get the QUOTE_CONTRACT_ADDRESS for our chain from [Github](https://github.com/Uniswap/v3-periphery/blob/main/deploys.md). - -We can now use our Quoter contract to obtain the quote. - -In an ideal world, the quoter functions would be `view` functions, which would make them very easy to query on-chain with minimal gas costs. However, the Uniswap V3 Quoter contracts rely on state-changing calls designed to be reverted to return the desired data. This means calling the quoter will be very expensive and should not be called on-chain. +import { SwapQuoter } from '@uniswapfoundation/v3-sdk' -To get around this difficulty, we can use the `callStatic` method provided by the **ethers.js** `Contract` instances. -This is a useful method that submits a state-changing transaction to an Ethereum node, but asks the node to simulate the state change, rather than to execute it. Our script can then return the result of the simulated state change: +const provider = new ethers.providers.JsonRpcProvider(CurrentConfig.rpc.mainnet) -```typescript -const quotedAmountOut = await quoterContract.callStatic.quoteExactInputSingle( - token0, - token1, - fee, - fromReadableAmount( - CurrentConfig.tokens.amountIn, - CurrentConfig.tokens.in.decimals - ).toString(), - 0 -) +const currencyAmountOut = await SwapQuoter.quoteExactInputSingle({ + amountIn: currencyAmountIn, + tokenOut: CurrentConfig.tokens.out, + poolFee: CurrentConfig.tokens.poolFee, + provider, + }) ``` -The `fromReadableAmount()` function creates the amount of the smallest unit of a token from the full unit amount and the decimals. +The function calls the Quoter contract and parses the response value. It only works on chains where Uniswap has officially deployed the QuoterV2 contract. -The result of the call is the number of output tokens you'd receive for the quoted swap. +The return value is a `CurrencyAmount` object of the expected output amount for our swap. -It should be noted that `quoteExactInputSingle` is only 1 of 4 different methods that the quoter offers: +It should be noted that `quoteExactInputSingle` is only 1 of 4 different methods that the quoter **contract** offers: 1. `quoteExactInputSingle` - given the amount you want to swap, produces a quote for the amount out for a swap of a single pool 2. `quoteExactInput` - given the amount you want to swap, produces a quote for the amount out for a swap over multiple pools @@ -219,6 +145,8 @@ It should be noted that `quoteExactInputSingle` is only 1 of 4 different methods If we want to trade two tokens that do not share a pool with each other, we will need to make swaps over multiple pools. This is where the `quoteExactInput` and `quoteExactOutput` methods come in. + +In the `SwapQuoter` JS class, all 4 of these functions can be accessed with the `callQuoter` function, using a `Route` object. We will dive deeper into routing in the [routing guide](03-routing.md). For the `exactOutput` and `exactOutputSingle` methods, we need to keep in mind that a pool can not give us more than the amount of Tokens it holds. diff --git a/docs/sdk/v3/guides/swaps/02-trading.md b/docs/sdk/v3/guides/swaps/02-trading.md index 868ed986db..1fa6699dce 100644 --- a/docs/sdk/v3/guides/swaps/02-trading.md +++ b/docs/sdk/v3/guides/swaps/02-trading.md @@ -18,7 +18,7 @@ In this example we will trade between two ERC20 tokens: **WETH and USDC**. The t The guide will **cover**: 1. Constructing a route from pool information -2. Constructing an unchecked trade +2. Fetching a Quote for the route 3. Executing a trade At the end of the guide, we should be able to create and execute a trade between any two ERC20 tokens using the example's included UI. @@ -27,10 +27,16 @@ At the end of the guide, we should be able to create and execute a trade between Included in the example application is functionality to wrap/unwrap ETH as needed to fund the example `WETH` to `USDC` swap directly from an `ETH` balance. ::: +:::info +The SDKs that are used in the guide are now published by the [Uniswap Foundation](https://github.com/uniswapfoundation) instead of Uniswap Labs. +You can find a list of supported SDKs [here](https://www.npmjs.com/org/uniswapfoundation). +Make sure you don't mix SDKs published by Uniswap Labs and the Uniswap Foundation to avoid unpredictable behavior. +::: + For this guide, the following Uniswap packages are used: -- [`@uniswap/v3-sdk`](https://www.npmjs.com/package/@uniswap/v3-sdk) -- [`@uniswap/sdk-core`](https://www.npmjs.com/package/@uniswap/sdk-core) +- [`@uniswapfoundation/v3-sdk`](https://www.npmjs.com/package/@uniswapfoundation/v3-sdk) +- [`@uniswapfoundation/sdk-core`](https://www.npmjs.com/package/@uniswapfoundation/sdk-core) The core code of this guide can be found in [`trading.ts`](https://github.com/Uniswap/examples/blob/main/v3-sdk/trading/src/libs/trading.ts) @@ -108,53 +114,30 @@ If you cannot see the Tokens traded in your wallet, you possibly have to [import ## Constructing a route from pool information -To construct our trade, we will first create an model instance of a `Pool`. We create an **ethers** contract like in the [previous guide](./01-quoting.md#referencing-the-pool-contract-and-fetching-metadata). -We will first extract the needed metadata from the relevant pool contract. Metadata includes both constant information about the pool as well as information about its current state stored in its first slot: +To construct our trade, we will first create a model instance of a `Pool`. We create an **ethers** provider like in the [previous guide](./01-quoting.md). +The sdk has a utility function to create a Pool from onchain data: ```typescript -async function getPoolInfo() { - const [token0, token1, fee, liquidity, slot0] = - await Promise.all([ - poolContract.fee(), - poolContract.liquidity(), - poolContract.slot0(), - ]) - - return { - fee, - liquidity, - sqrtPriceX96: slot0[0], - tick: slot0[1], - } -} -``` + import {Pool} from '@uniswapfoundation/v3-sdk' + import ethers from 'ethers' -Before continuing, let's talk about the values we fetched here and what they represent: + const provider = new ethers.providers.JsonRpcProvider(CurrentConfig.rpc.mainnet) -- `fee` is the fee that is taken from every swap that is executed on the pool in 1 per million - if the `fee` value of a pool is 500, ```500/ 1000000``` (or 0.05%) of the trade amount is taken as a fee. This fee goes to the liquidity providers of the Pool. -- `liquidity` is the amount of liquidity the Pool can use for trades at the current price. -- `sqrtPriceX96` is the current Price of the pool, encoded as a ratio between `token0` and `token1`. -- `tick` is the tick at the current price of the pool. + const pool = await Pool.initFromChain( + provider, + CurrentConfig.tokens.in, + CurrentConfig.tokens.out, + CurrentConfig.tokens.poolFee + ) +``` -Check out the [whitepaper](https://uniswap.org/whitepaper-v3.pdf) to learn more on how liquidity and ticks work in Uniswap V3. +Every Pool is uniquely identified by the two tokens it contains and its fee. +The initialized Pool already has all necessary metadata for our example but does not contain any Tick Data. +Fetching Ticks can be expensive for large pools and is not necessary for most use cases. +We will dive deeper into this topic in the [next guide](./03-simulate-offchain.md) You can find the full code in [`pool.ts`](https://github.com/Uniswap/examples/blob/main/v3-sdk/trading/src/libs/pool.ts). -Using this metadata along with our inputs, we will then construct a `Pool`: - -```typescript -const poolInfo = await getPoolInfo() - -const pool = new Pool( - CurrentConfig.tokens.in, - CurrentConfig.tokens.out, - CurrentConfig.tokens.poolFee, - poolInfo.sqrtPriceX96.toString(), - poolInfo.liquidity.toString(), - poolInfo.tick -) -``` - ## Creating a Route With this `Pool`, we can now construct a route to use in our trade. Routes represent a route over one or more pools from one Token to another. Let's imagine we have three pools: @@ -176,7 +159,7 @@ The `Route` object can find this route from an array of given pools and an input To keep it simple for this guide, we only swap over one Pool: ```typescript -import { Route } from '@uniswap/v3-sdk' +import { Route } from '@uniswapfoundation/v3-sdk' const swapRoute = new Route( [pool], @@ -187,7 +170,6 @@ const swapRoute = new Route( Our `Route` understands that `CurrentConfig.tokens.in` should be traded for `CurrentConfig.tokens.out` over the Array of pools `[pool]`. - ## Constructing an unchecked trade Once we have constructed the route object, we now need to obtain a quote for the given `inputAmount` of the example: @@ -196,60 +178,44 @@ Once we have constructed the route object, we now need to obtain a quote for the const amountOut = await getOutputQuote(swapRoute) ``` -As shown below, the quote is obtained using the `v3-sdk`'s `SwapQuoter`, in contrast to the [previous quoting guide](./01-quoting.md), where we directly accessed the smart contact: +As shown below, the quote is obtained using the `v3-sdk`'s `SwapQuoter`, for this guide we use the `callQuoter()` function. +In contrast to the `quoteExactInputSingle()` function we used in the previous guide, this function works for a Route with any number of Uniswap V3 Pools, not just a swap over a single Pool: ```typescript -import { SwapQuoter } from '@uniswap/v3-sdk' -import { CurrencyAmount, TradeType } from '@uniswap/sdk-core' +import { SwapQuoter } from '@uniswapfoundation/v3-sdk' +import { CurrencyAmount, TradeType } from '@uniswapfoundation/sdk-core' -const { calldata } = await SwapQuoter.quoteCallParameters( - swapRoute, - CurrencyAmount.fromRawAmount( - CurrentConfig.tokens.in, - fromReadableAmount( - CurrentConfig.tokens.amountIn, - CurrentConfig.tokens.in.decimals +const rawInputAmount = ethers.utils.parseUnits( + CurrentConfig.tokens.amountIn, + CurrentConfig.tokens.in.decimals ) - ), - TradeType.EXACT_INPUT, - { - useQuoterV2: true, - } -) -``` -The `SwapQuoter`'s `quoteCallParameters` function, gives us the calldata needed to make the call to the `Quoter`, and we then decode the returned quote: +const currencyAmountIn = CurrencyAmount.fromRawAmount( + CurrentConfig.tokens.tokenIn, + rawInputAmount +) -```typescript -const quoteCallReturnData = await provider.call({ - to: QUOTER_CONTRACT_ADDRESS, - data: calldata, +const expectedOutput = await SwapQuoter.callQuoter({ + route: swapRoute, + amount: currencyAmountIn, + tradeType: TradeType.EXACT_INPUT, + provider }) - -return ethers.utils.defaultAbiCoder.decode(['uint256'], quoteCallReturnData) ``` +We construct the input the same way we did in the previous guide. +The return value of the `callQuoter()` function is the expected output, parsed as a `CurrencyAmount` object. + With the quote and the route, we can now construct a trade using the route in addition to the output amount from a quote based on our input. Because we already know the expected output of our Trade, we do not have to check it again. We can use the `uncheckedTrade` function to create our Trade: ```typescript -import { Trade } from 'uniswap/v3-sdk' -import { CurrencyAmount, TradeType } from '@uniswap/sdk-core' -import JSBI from 'jsbi' +import { Trade } from '@uniswapfoundation/v3-sdk' const uncheckedTrade = Trade.createUncheckedTrade({ route: swapRoute, - inputAmount: CurrencyAmount.fromRawAmount( - CurrentConfig.tokens.in, - fromReadableAmount( - CurrentConfig.tokens.amountIn, - CurrentConfig.tokens.in.decimals - ) - ), - outputAmount: CurrencyAmount.fromRawAmount( - CurrentConfig.tokens.out, - JSBI.BigInt(amountOut) - ), + inputAmount: currencyAmountIn, + outputAmount: expectedOutput, tradeType: TradeType.EXACT_INPUT, }) ``` @@ -258,55 +224,52 @@ This example uses an exact input trade, but we can also construct a trade using ## Executing a trade -Once we have created a trade, we can now execute this trade with our provider. First, we must give the `SwapRouter` approval to spend our tokens for us: +Once we have created a trade, we can now execute this trade with our provider. +We will use the `executeTrade()` function of the `SwapRouter` class. +First we specify the deadline and the slippage tolerance we are willing to accept for our trade: ```typescript -const tokenApproval = await getTokenTransferApproval(CurrentConfig.tokens.in) -``` - -You can find the approval function [here](https://github.com/Uniswap/examples/blob/main/v3-sdk/trading/src/libs/trading.ts#L151). -We will use this function or similar implementations in most guides. - -Then, we set our options that define how much time and slippage can occur in our execution as well as the address to use for our wallet: - -```typescript -import { SwapOptions } from '@uniswap/v3-sdk' -import { Percent } from '@uniswap/sdk-core' - -const options: SwapOptions = { - slippageTolerance: new Percent(50, 10_000), // 50 bips, or 0.50% - deadline: Math.floor(Date.now() / 1000) + 60 * 20, // 20 minutes from the current Unix time - recipient: walletAddress, -} +import { SwapOptions } frpm '@uniswapfoundation/v3-sdk' +import { Percent } from '@uniswapfoundation/sdk-core' + +const swapOptions: SwapOptions = { + slippageTolerance: new Percent(50, 10_000), + deadline: Math.floor(Date.now() / 1000) + 60 * 5, // 5 minutes from the current Unix time + recipient: walletAddress, + } ``` The slippage of our trade is the maximum decrease from our calculated output amount that we are willing to accept for this trade. -The deadline is the latest point in time when we want the transaction to go through. +The deadline is the latest point in time when we want the transaction to go through. If we set this value too high, the transaction could be left waiting for days and we would need to pay gas fees to cancel it. +The swapOptions are an optional parameter of the `executeTrade()` function and default to exactly what we specified here if they are not provided. -Next, we use the `SwapRouter` class, a representation of the Uniswap [SwapRouter Contract](https://github.com/Uniswap/v3-periphery/blob/v1.0.0/contracts/SwapRouter.sol), to get the associated call parameters for our trade and options: +As we want to execute a state changing transaction on the blockchain, we need a wallet to sign our transaction: ```typescript -import { SwapRouter } from '@uniswap/v3-sdk' +import { ethers } from 'ethers' -const methodParameters = SwapRouter.swapCallParameters([uncheckedTrade], options) +const wallet = getWallet() +wallet.connect(provider) ``` -Finally, we can construct a transaction from the method parameters and send the transaction: +We are now ready to execute our trade: ```typescript -const tx = { - data: methodParameters.calldata, - to: SWAP_ROUTER_ADDRESS, - value: methodParameters.value, - from: walletAddress, - maxFeePerGas: MAX_FEE_PER_GAS, - maxPriorityFeePerGas: MAX_PRIORITY_FEE_PER_GAS, -} +import { SwapRouter } from '@uniswapfoundation/v3-sdk' -const res = await wallet.sendTransaction(tx) +const txResponse = await SwapRouter.executeTrade({ + trades: [uncheckedTrade], + options: swapOptions, + signer: wallet +}) ``` +The function automatically checks if the necessary token transfer approvals exist and creates them if not. +For this reason, we usually need to wait 2 blocks for the execution to finish. +The return value is an `ethers.TransactionResponse` object. + ## Next Steps -The resulting example allows for trading between any two ERC20 tokens, but this can be suboptimal for the best pricing and fees. To achieve the best possible price, we use the Uniswap auto router to route through pools to get an optimal cost. Our [routing](./03-routing.md) guide will show you how to use this router and execute optimal swaps. +So far, we have used onchain calls to get a quote for our trades. +In the next guide on [offchain simulations](03-simulate-offchain.md), we will use the sdk to fetch Tickdata first and simulate our Trades offchain. diff --git a/docs/sdk/v3/guides/swaps/03-simulate-offchain.md b/docs/sdk/v3/guides/swaps/03-simulate-offchain.md new file mode 100644 index 0000000000..738dee7159 --- /dev/null +++ b/docs/sdk/v3/guides/swaps/03-simulate-offchain.md @@ -0,0 +1,184 @@ +--- +id: simulate +title: Simulating Trades +--- + +## Introduction + +In this guide, we will fetch tickdata for several pools and simulate trades offchain to find the best possible route, before executing our trade onchain. + +:::info +If you need a briefer on the SDK and to learn more about how these guides connect to the examples repository, please visit our [background](../01-background.md) page! + +To get started with local development, also check out the [local development guide](../02-local-development.md). +::: + +In this example we will trade between two ERC20 tokens: **USDC and DAI**. The tokens, amount of input token, and the available Pools can be configured as inputs. + +The guide will **cover**: + +1. Constructing a fully initialized Pool +2. Simulating Swaps +3. Executing a Trade + +At the end of the guide, we should be able to initialize Pools with full tickdata and simulate trades offchain. + +:::info +The SDKs that are used in the guide are now published by the [Uniswap Foundation](https://github.com/uniswapfoundation) instead of Uniswap Labs. +You can find a list of supported SDKs [here](https://www.npmjs.com/org/uniswapfoundation). +Make sure you don't mix SDKs published by Uniswap Labs and the Uniswap Foundation to avoid unpredictable behavior. +::: + +For this guide, the following Uniswap packages are used: + +- [`@uniswapfoundation/v3-sdk`](https://www.npmjs.com/package/@uniswapfoundation/v3-sdk) +- [`@uniswapfoundation/sdk-core`](https://www.npmjs.com/package/@uniswapfoundation/sdk-core) + +The core code of this guide can be found in [`simulations.ts`](https://github.com/Uniswap/examples/blob/main/v3-sdk/offchain-simulation/src/libs/simulations.ts) + +## Example configuration + +We will use the example configuration `CurrentConfig` in most code snippets of this guide. It has the format: + +```typescript +import { Token } from '@uniswapfoundation/sdk-core' +import { FeeAmount, Pool } from '@uniswapfoundation/v3-sdk' + +interface ExampleConfig { + rpc: { + local: string, + mainnet: string + } + tokens: { + in: Token + readableAmountIn: number + out: Token + }, + pools: Pool[] +} + +export const CurrentConfig: ExampleConfig = {...} +``` + +The default config of the example uses a local fork of mainnet. If you haven't already, check out our [local development guide](../02-local-development.md). +To change the rpc endpoint or the Pools used, edit the [`Currentconfig`](https://github.com/Uniswap/examples/blob/main/v3-sdk/quoting/src/config.ts#L21). + +```typescript +export const CurrentConfig: ExampleConfig = { + rpc: { + local: 'http://localhost:8545', + mainnet: '...' + }, + tokens: { + in: USDC_TOKEN, + readableAmountIn: 1000, + out: DAI_TOKEN, + }, + pools: [ + USDC_WETH_Pool, + USDT_WETH_Pool, + USDT_DAI_Pool + ] +} +``` + +Fetching the TickData for a Pool requires a lot of RPC requests. Make sure you are aware of the costs of your RPC provider and strongly consider using a websocket provider for performance. +Free, public rpc endpoints are usually not fast enough to fetch this amount of data in a sensible time. +This functionality of the sdk will usually be used in a backend. + +The pools used in the configuration are imported from `constants.ts`. You can add any Pool you want to the array, just make sure they exist onchain. +Check out the top pools on [Uniswap info](https://info.uniswap.org/#/pools). + +## Initializing the Pools + +We already initialized a Pool object in the previous guide using the `initFromChain` function. +By default, a Pool does not hold the full Tickdata necessary to compute trades, as fetching it is not necessary for most use cases and can be expensive. + +To fetch the full Tickdata for the Pool, we use the `initializeTicks()` function on our Pools: + +```typescript +import { ethers } from 'ethers' + +const provider = new ethers.providers.JsonRpcProvider(CurrenConfig.rpc.local) + +let promises = [] + +let pools = CurrentConfig.pools +for (let pool of pools) { + const request = pool.initializeTicks(provider) + promises.push(request) +} + +await Promise.all(promises) +``` + +Our pools are now fully initialized and we can use them to simulate our trade offchain. + +## Simulating trades + +In this example, we want to make an exact Input trade from **USDC** to **DAI**. +We use the `Trade.bestTradeExactIn` function to find the best route given our Pools: + +```typescript +import { Trade, BestTradeOptions } from '@uniswapfoundation/v3-sdk' +import { CurrencyAmount } from '@uniswapfoundation/sdk-core' + +const currencyAmountIn = CurrencyAmount.fromRawAmount(...) + +const bestTrades = Trade.bestTradeExactIn( + pools, + currencyAmountIn, + CurrentConfig.tokens.out +) + +const bestTrade = bestTrades[0] +``` + +The function allows us to optionally define `BestTradeOptions`. +The maxNumResults is the maximum number of Trades that should be returned, the maxHops is the maximum number of Pools that should be used in a single Route in one of those trades. +We will keep the default configuration in this example: + +```typescript +{ maxNumResults = 3, maxHops = 3 } +``` + +We can get the output amount and the routes that are used by the Trade from the object returned. +The `bestTradeExactIn` function considers splitting the trade between multiple routes, so for larger trades the input amount may be split between several routes. +We can access them with the `swaps` getter if we want to access this information. + +## Executing the trade + +Like in the previous guide, we execute the trade with `executeTrade()` on the `SwapRouter` class: + +```typescript +import { SwapRouter } from '@uniswapfoundation/v3-sdk' + +wallet.connect(provider) +const txResponse = SwapRouter.executeTrade( + trades: [bestTrade[0]], + signer: wallet +) +``` + +### Doing everything at once + +If you are not interested in the quote or don't want to initialize the pools yourself, you can directly execute a swap by using the `executeBestSimulatedSwapOnPools()` function: + +```typescript +wallet.connect(provider) + +const txResponse = SwapRouter.executeBestSimulatedSwapOnPools( + pools, + amount: currencyAmountIn, + currencyIn: CurrentConfig.tokens.in, + currencyOut: CurrentConfig.tokens.out, + tradeType: TradeType.EXACT_INPUT, + signer: wallet +) +``` + +This function is an abstraction of all the steps laid out in this guide and directly executes the best trade found by the `bestTradeExactIn` or `bestTradeExactOut` functions. + +## Next Steps + +Now that you are familiar with simulating Trades and finding optimal routes offchain, we will demonstrate an alternative solution for routing in the [next guide](04-routing.md). diff --git a/docs/sdk/v3/guides/swaps/03-routing.md b/docs/sdk/v3/guides/swaps/04-routing.md similarity index 70% rename from docs/sdk/v3/guides/swaps/03-routing.md rename to docs/sdk/v3/guides/swaps/04-routing.md index 2e35b64590..b130ee3ce3 100644 --- a/docs/sdk/v3/guides/swaps/03-routing.md +++ b/docs/sdk/v3/guides/swaps/04-routing.md @@ -21,18 +21,24 @@ The guide will **cover**: At the end of the guide, we should be able to create a route and and execute a swap between any two currencies tokens using the example's included UI. +:::info +The SDKs that are used in the guide are now published by the [Uniswap Foundation](https://github.com/uniswapfoundation) instead of Uniswap Labs. +You can find a list of supported SDKs [here](https://www.npmjs.com/org/uniswapfoundation). +Make sure you don't mix SDKs published by Uniswap Labs and the Uniswap Foundation to avoid unpredictable behavior. +::: + For this guide, the following Uniswap packages are used: -- [`@uniswap/v3-sdk`](https://www.npmjs.com/package/@uniswap/v3-sdk) -- [`@uniswap/sdk-core`](https://www.npmjs.com/package/@uniswap/sdk-core) -- [`@uniswap/smart-order-router`](https://www.npmjs.com/package/@uniswap/smart-order-router) +- [`@uniswapfoundation/v3-sdk`](https://www.npmjs.com/package/@uniswapfoundation/v3-sdk) +- [`@uniswapfoundation/sdk-core`](https://www.npmjs.com/package/@uniswapfoundation/sdk-core) +- [`@uniswapfoundation/smart-order-router`](https://www.npmjs.com/package/@uniswapfoundation/smart-order-router) The core code of this guide can be found in [`routing.ts`](https://github.com/Uniswap/examples/blob/main/v3-sdk/routing/src/libs/routing.ts) The config, which we will use in some code snippets in this guides has this structure: ```typescript -import { Token } from '@uniswap/sdk-core' +import { Token } from '@uniswapfoundation/sdk-core' interface ExampleConfig { env: Environment @@ -56,10 +62,10 @@ export const CurrentConfig: ExampleConfig = {...} ## Creating a router instance -To compute our route, we will use the `@uniswap/smart-order-router` package, specifically the `AlphaRouter` class which requires a `chainId` and a `provider`. Note that routing is not supported for local forks, so we will use a mainnet provider even when swapping on a local fork: +To compute our route, we will use the `@uniswapfoundation/smart-order-router` package, specifically the `AlphaRouter` class which requires a `chainId` and a `provider`. Note that routing is not supported for local forks, so we will use a mainnet provider even when swapping on a local fork: ```typescript -import { AlphaRouter, ChainId } from '@uniswap/smart-order-router' +import { AlphaRouter, ChainId } from '@uniswapfoundation/smart-order-router' const provider = new ethers.providers.JsonRpcProvider(rpcUrl) @@ -71,12 +77,14 @@ const router = new AlphaRouter({ ## Creating a route -We will use the [SwapRouter02](https://github.com/Uniswap/v3-periphery/blob/v1.0.0/contracts/SwapRouter.sol) for our trade. -The `smart-order-router` package provides us with a SwapOptionsSwapRouter02` interface, defining the wallet to use, slippage tolerance, and deadline for the transaction that we need to interact with the contract: +We will use the [SwapRouter02](https://github.com/Uniswap/swap-router-contracts/blob/main/contracts/SwapRouter02.sol) for our trade. +This is a different SwapRouter contract than the one we used in the previous example. +In contrast to the contract we used previously, this on can execute swaps on both V3 Pools and V2 Pairs. +The `smart-order-router` package provides us with a `SwapOptionsSwapRouter02` interface, defining the wallet to use, slippage tolerance, and deadline for the transaction that we need to interact with the contract: ```typescript -import { SwapOptionsSwapRouter02, SwapType } from '@uniswap/smart-order-router' -import { Percent } from '@uniswap/sdk-core' +import { SwapOptionsSwapRouter02, SwapType } from '@uniswapfoundation/smart-order-router' +import { Percent } from '@uniswapfoundation/sdk-core' const options: SwapOptionsSwapRouter02 = { recipient: CurrentConfig.wallet.address, @@ -86,45 +94,30 @@ const options: SwapOptionsSwapRouter02 = { } ``` -Like explained in the [previous guide](./02-trading.md#executing-a-trade), it is important to set the parameters to sensible values. +Like explained in the [trading guide](./02-trading.md#executing-a-trade), it is important to set the parameters to sensible values. Using these options, we can now create a trade (`TradeType.EXACT_INPUT` or `TradeType.EXACT_OUTPUT`) with the currency and the input amount to use to get a quote. For this example, we'll use an `EXACT_INPUT` trade to get a quote outputted in the quote currency. ```typescript -import { CurrencyAmount, TradeType } from '@uniswap/sdk-core' +import { CurrencyAmount, TradeType } from '@uniswapfoundation/sdk-core' -const rawTokenAmountIn: JSBI = fromReadableAmount( +const rawTokenAmountIn = fromReadableAmount( CurrentConfig.currencies.amountIn, CurrentConfig.currencies.in.decimals ) - -const route = await router.route( - CurrencyAmount.fromRawAmount( + const currencyAmountIn = CurrencyAmount.fromRawAmount( CurrentConfig.currencies.in, rawTokenAmountIn - ), + ) + +const route = await router.route( + currencyAmountIn, CurrentConfig.currencies.out, TradeType.EXACT_INPUT, options ) ``` -The `fromReadableAmount` function calculates the amount of tokens in the Token's smallest unit from the full unit and the Token's decimals: - -```typescript title="src/libs/conversion.ts" -export function fromReadableAmount(amount: number, decimals: number): JSBI { - const extraDigits = Math.pow(10, countDecimals(amount)) - const adjustedAmount = amount * extraDigits - return JSBI.divide( - JSBI.multiply( - JSBI.BigInt(adjustedAmount), - JSBI.exponentiate(JSBI.BigInt(10), JSBI.BigInt(decimals)) - ), - JSBI.BigInt(extraDigits) - ) -} -``` - `route` and `route.methodParameters` are *optional* as the request can fail, for example if **no route exists** between the two Tokens or because of networking issues. We check if the call was succesful: @@ -138,7 +131,7 @@ Depending on our preferences and reason for the issue we could retry the request ## Swapping using a route -First, we need to give approval to the `SwapRouter` smart contract to spend our tokens for us: +First, we need to give approval to the `SwapRouter02` smart contract to spend our tokens for us: ```typescript import { ethers } from 'ethers' @@ -159,7 +152,7 @@ const tokenApproval = await tokenContract.approve( To be able to spend the tokens of a wallet, a smart contract first needs to get an approval from that wallet. ERC20 tokens have an `approve` function that accepts the address of the smart contract that we want to allow spending our tokens and the amount the smart contract should be allowed to spend. -We can get the **V3_SWAP_ROUTER_ADDRESS** for our chain from [Github](https://github.com/Uniswap/v3-periphery/blob/main/deploys.md). +We can get the **V3_SWAP_ROUTER_ADDRESS** for our chain from [Github](https://github.com/Uniswap/v3-periphery/blob/main/deploys.md). Keep in mind that different chains might have **different deployment addresses** for the same contracts. The deployment address for local forks of a network are the same as in the network you forked, so for a **fork of mainnet** it would be the address for **Mainnet**. diff --git a/docusaurus.config.js b/docusaurus.config.js index 723a621971..4d97703211 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -240,15 +240,15 @@ module.exports = { from: '/sdk/v3/guides/quick-start', }, { - to: '/sdk/v3/guides/quoting', + to: '/sdk/v3/guides/swaps/quoting', from: ['/sdk/v3/guides/creating-a-pool', '/sdk/v3/guides/fetching-prices'], }, { - to: '/sdk/v3/guides/trading', + to: '/sdk/v3/guides/swaps/trading', from: '/sdk/v3/guides/creating-a-trade', }, { - to: '/sdk/v3/guides/routing', + to: '/sdk/v3/guides/swaps/routing', from: '/sdk/v3/guides/auto-router', }, { diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 2cbc8ea89c..4c11f05f69 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -79,12 +79,12 @@ export const dAppGuides = [ { title: 'Create a Trade', text: 'Fetch a Quote for a Trade and execute the Trade', - to: '/sdk/v3/guides/trading', + to: '/sdk/v3/guides/swaps/trading', }, { title: 'Route trades', text: 'Use Routing to get optimized prices for your Trades', - to: '/sdk/v3/guides/routing', + to: '/sdk/v3/guides/swaps/routing', }, { title: 'Provide liquidity',