diff --git a/.eslintrc.json b/.eslintrc.json index 5df26dc9a2..670b3e4a09 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -9,7 +9,8 @@ "plugin:@typescript-eslint/recommended", "plugin:react-hooks/recommended", "prettier/@typescript-eslint", - "plugin:prettier/recommended" + "plugin:prettier/recommended", + "plugin:markdown/recommended" ], "overrides": [], "parserOptions": { diff --git a/docs/sdk/v3/guides/01-background.md b/docs/sdk/v3/guides/01-background.md new file mode 100644 index 0000000000..44f81109c6 --- /dev/null +++ b/docs/sdk/v3/guides/01-background.md @@ -0,0 +1,68 @@ +--- +id: background +title: Background +--- + +Before integrating with Uniswap, it may be helpful for newcomers to review the following background information on some important developer web3 concepts, the structure of our examples, and SDK concepts. + +:::info +Already familiar with web3 development and/or the basics of our SDK and want to get right to the code? Start with our first guide, [Getting a Quote](./02-quoting.md)! +::: + +## Providers + +Communication with the blockchain is typically done through a provider and local models of smart contracts and their [ABIs](./01-background.md#abis). + +To achieve this, our examples use the [ethers.js](https://docs.ethers.io/v5/) library. To instantiate a provider you will need a data source. Our examples offer two options: + +- **JSON RPC URL**: If you are working directly with the Ethereum mainnet or a local fork, products such as [infura](https://infura.io/) offer JSON RPC URLs for a wide variety of chains and testnets. For our examples, we'll only be using the Ethereum mainnet. + +- **Wallet Extension**: If you are connecting to a wallet browser extension, these wallets embed a source directly into the Javascript window object as `window.ethereum`. This object surfaces information about the user's wallets and provides the ability to communicate with the connected chain. Importantly for our examples, it can be used with `ethers.js` to construct a provider. + +## Uniswap's Runnable Examples + +Each guide is accompanied and driven by [runnable examples](https://github.com/Uniswap/examples/tree/main/v3-sdk) using React to provide a basic UI for interacting with the example. Each examples provides relevant options such as running against a local blockchain or connecting to the Ethereum mainnet directly. You also have the option of using a wallet extension which can be connected to either environment. + +Inputs and environment settings are configured in each example's `config.ts` and allows for simple setup and configuration. + +### Developing and Testing + +To test your code, we recommend utilizing a local fork of the Ethereum mainnet. To help facilitate easy testing, each example includes a quickstart for running the local chain with a test wallet. To further test, we also recommend using a wallet extension and connecting to the local chain. Finally, each example can be run against the Ethereum mainnet if desired. Full development instructs can be found in the `README.md` of each code example. + +### Utility Libraries + +Each example is concentrated into a single file within the `libs/` folder of the example, with the entry points noted in each guide and README. + +To allow the guides to focus on the SDK's core functionality, additional basic building blocks can be found in each example's `libs` folder. The exported functionality from these files is intended to be the minimum needed for each example and not a complete library for production usage. These also include storing core constants such as definitions for tokens, ABI's, and blockchain addresses that can distract from the core concepts. Below are summaries of the helping libraries you will encounter. + +#### Provider Utilities + +`provider.ts` wraps the basics of `ethers.js` and connecting to wallet extensions into an abstracted view of a provider, a wallet address, and the ability to send transactions. It also helps abstract the configured environment you wish to run against in your example without making code changes outside of your configuration. + +#### Wallet Utilities + +`wallet.ts` offers the ability to query a wallet (whether connected via an extension or defined in code/config) for its balances and other essential information. + +#### Pool Infroamtion + +`pool.ts` contains the basic querying of pool information when not essential / core to the relevant guide + +#### Display Utilities + +`conversion.ts` provides display and light math wrappers to help show human readable prices when dealing with currency amounts (typically stored as raw numbers and the decimal placement separate for precision reasons) in the form of two functions: `fromReadableAmount` and `toReadableAmount` + +## Notable SDK Structures and Concepts + +When working with the SDK it can be helpful to understand some of the design choices and why they are needed. Below you can find a few important concepts. + +### ABI's + +To allow others to interact with a smart contract, each contract exposes an ABI (Application Binary Interface). As these are defined on the blockchain, we must ensure the correct definitions are provided to our Javascript functions. ABI's are provided from various SDK's and imported in as needed. Some examples will define an ABI directly as needed. + +### CurrencyAmount and JSBI + +Cryptocurrency applications often work with very small fractions of tokens. As a result, high precision is very important. To ensure precision is upheld, the `CurrencyAmount` class helps store exact values as fractions and utilizes [JSBI](https://github.com/GoogleChromeLabs/jsbi) for compatibility across the web. To display these amounts nicely to users, additional work is sometimes required. + +### Currency + +The `Currency` class can represent both native currency (ETH) and an ERC20 `Token`. Currencies vary in their relative value, so the `Token` class allows your application to define the number of decimals needed for each currency along with the currency's address, symbol, and name. diff --git a/docs/sdk/v3/guides/01-quick-start.md b/docs/sdk/v3/guides/01-quick-start.md deleted file mode 100644 index 756b678edd..0000000000 --- a/docs/sdk/v3/guides/01-quick-start.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -id: quick-start -title: Quick Start ---- - -The Uniswap SDK is separate from the Uniswap protocol. It is designed to assist developers when interacting with the protocol in any environment that can execute JavaScript, such as websites or node scripts. With the SDK, you can manipulate data that has been queried from the EVM using libraries that assist with several needs, such as data modeling and protection from rounding errors. - -The following guides will help you use [ethers.js](https://docs.ethers.io/v5/) to return state data from the EVM, and the Uniswap V3 SDK to manipulate it once it has been retrieved. - -# Installation - -To interact with the V3 SDK - we recommend installing though the npm package - -```javascript -npm i --save-dev @uniswap/v3-sdk -npm i --save-dev @uniswap/sdk-core -``` - -# Usage - -```javascript -import { Pool } from '@uniswap/v3-sdk' -``` - -# Next Steps - -To use `Ethers.js` for the first time, or would like a more beginner friendly starting place, check out [**Using Ethers.js**](./02-using-ethers). If you are already familiar with ethers.js and general dev setup, you can move to [**Creating a Pool Instance**](./03-creating-a-pool.md). diff --git a/docs/sdk/v3/guides/02-quoting.md b/docs/sdk/v3/guides/02-quoting.md new file mode 100644 index 0000000000..05c5983750 --- /dev/null +++ b/docs/sdk/v3/guides/02-quoting.md @@ -0,0 +1,115 @@ +--- +id: quoting +title: Getting a Quote +--- + +## Introduction + +This guide will cover how to get the current quotes for any token pair on the Uniswap protocol. It is based on the [Quoting code example](https://github.com/Uniswap/examples/tree/main/v3-sdk/quoting), found in the Uniswap code examples [repository](https://github.com/Uniswap/examples). To run this example, check out the examples's [README](https://github.com/Uniswap/examples/blob/main/v3-sdk/minting-position/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 `quoteExactInputSingle` to get a quote for the pair **USDC - 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 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 + +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. + +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) + +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) + +## Computing the Pool's deployment address + +To interact with the **USDC - WETH** Pool contract, we first need to compute its deployment address. +The SDK provides a utility method for that: + +```typescript reference title="Computing the Pool's address" referenceLinkText="View on Github" customStyling +https://github.com/Uniswap/examples/blob/b5e64e3d6c17cb91bc081f1ed17581bbf22024bc/v3-sdk/quoting/src/libs/quote.ts#L40-L45 +``` + +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 configuration file: + +```typescript reference title="Configuration Parameters" referenceLinkText="View on Github" customStyling +https://github.com/Uniswap/examples/blob/1ef393c2b8f8206a3dc5a42562382c267bcc361b/v3-sdk/quoting/src/config.ts#L34-L39 +``` + +## 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: + +```typescript reference title="Setting up a reference to the Pool contract" referenceLinkText="View on Github" customStyling +https://github.com/Uniswap/examples/blob/b5e64e3d6c17cb91bc081f1ed17581bbf22024bc/v3-sdk/quoting/src/libs/quote.ts#L47-L51 +``` + +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 reference title="Uniswap V3 Pool smart contract ABI" referenceLinkText="View on Github" customStyling +https://github.com/Uniswap/examples/blob/b5e64e3d6c17cb91bc081f1ed17581bbf22024bc/v3-sdk/quoting/src/libs/quote.ts#L5 +``` + +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 avoid out of sync data that may be returned if sequential queries are executed over the span of two blocks: + +```typescript reference title="Getting Pool metadata from the Pool smart contact" referenceLinkText="View on Github" customStyling +https://github.com/Uniswap/examples/blob/b5e64e3d6c17cb91bc081f1ed17581bbf22024bc/v3-sdk/quoting/src/libs/quote.ts#L52-L56 +``` + +The return values of these methods will become inputs to the quote fetching function. + +:::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 + +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: + +```typescript reference title="Setting up a reference to the Quoter contract" referenceLinkText="View on Github" customStyling +https://github.com/Uniswap/examples/blob/b5e64e3d6c17cb91bc081f1ed17581bbf22024bc/v3-sdk/quoting/src/libs/quote.ts#L14-L18 +``` + +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: + +```typescript reference title="Uniswap V3 Quoter smart contract ABI" referenceLinkText="View on Github" customStyling +https://github.com/Uniswap/examples/blob/b5e64e3d6c17cb91bc081f1ed17581bbf22024bc/v3-sdk/quoting/src/libs/quote.ts#L4 +``` + +We can now use our Quoter contact 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. + +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: + +```typescript reference title="Getting Quotes from the Quoter contract" referenceLinkText="View on Github" customStyling +https://github.com/Uniswap/examples/blob/b5e64e3d6c17cb91bc081f1ed17581bbf22024bc/v3-sdk/quoting/src/libs/quote.ts#L21-L30 +``` + +The result of the call is the number of output tokens you'd receive for the quoted swap. + +It should be noted that `quoteExactInputSingle` is only 1 of 4 different methods that the quoter 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 +3. `quoteExactOutputSingle` - given the amount you want to get out, produces a quote for the amount in for a swap over a single pool +4. `quoteExactOutput` - given the amount you want to get out, produces a quote for the amount in for a swap over multiple pools + +## Next Steps + +Now that you're able to make a quote, check out our next guide on [trading](./03-trading.md) using this quote! diff --git a/docs/sdk/v3/guides/02-using-ethers.md b/docs/sdk/v3/guides/02-using-ethers.md deleted file mode 100644 index 9b62a3fd96..0000000000 --- a/docs/sdk/v3/guides/02-using-ethers.md +++ /dev/null @@ -1,196 +0,0 @@ ---- -id: using-ethers -title: Using Ethers.js ---- - -This guide will help you install the V3 SDK and [ethers.js](https://docs.ethers.io/v5/) to query state data from Ethereum. -While this tutorial doesn't use the V3 SDK - it will set us up to use it after we get the on-chain data we need. - -## First Steps - -For our first step, we're going to use `ethers.js` to return immutable variables from a Uniswap V3 pool contract, and assign those to an interface in our script that can be repeatedly referenced without continually reading state data directly from the EVM. - -We'll need to make a new directory called `example`: - -```typescript -mkdir example -cd example -``` - -Then we'll make a new project using Node's `npm`: - -```typescript -npm init -``` - -```typescript -npm i typescript --save -npm i ts-node --save -npm i @uniswap/v3-sdk --save -npm i @uniswap/sdk-core --save -npm i ethers --save -``` - -Depending on your machine configuration, you may also need this: - -```typescript -npm install -D tslib @types/node -``` - -## Importing Ethers and the V3 SDK - -We'll need to import ethers, and set up our environment variables so we can query chain data. -For this example, we're using an infura endpoint. If you don't have access to an infura endpoint, you can setup a free account [here](https://infura.io/). - -```typescript -import { ethers } from 'ethers' - -const provider = new ethers.providers.JsonRpcProvider('') -``` - -The first thing we'll need to do is to tell Ethers where to look for our chain data. -To do this, we'll create a local variable with the contract address of the V3 pool we're trying to query: - -```typescript -const poolAddress = '0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8' -``` - -Now we'll need the interface for the functions of the pool contract that we'll be calling: - -```typescript -const poolImmutablesAbi = [ - 'function factory() external view returns (address)', - 'function token0() external view returns (address)', - 'function token1() external view returns (address)', - 'function fee() external view returns (uint24)', - 'function tickSpacing() external view returns (int24)', - 'function maxLiquidityPerTick() external view returns (uint128)', -] -``` - -## Using ethers.js "Contract" - -Once that is setup, we'll create a new instance of a "Contract" using `ethers.js`. This isn't a smart contract itself, but rather a local model of one that helps us move data around off-chain: - -```typescript -const poolContract = new ethers.Contract(poolAddress, poolImmutablesAbi, provider) -``` - -Now we'll create an interface with all the data we're going to return, each assigned to its appropriate type: - -```typescript -interface Immutables { - factory: string - token0: string - token1: string - fee: number - tickSpacing: number - maxLiquidityPerTick: number -} -``` - -## Returning Chain Data - -Now we're ready to query the EVM using `ethers.js` and assign the returned values to the variables inside of our `Immutables` interface. - -```typescript -async function getPoolImmutables() { - const PoolImmutables: Immutables = { - factory: await poolContract.factory(), - token0: await poolContract.token0(), - token1: await poolContract.token1(), - fee: await poolContract.fee(), - tickSpacing: await poolContract.tickSpacing(), - maxLiquidityPerTick: await poolContract.maxLiquidityPerTick(), - } - return PoolImmutables -} -``` - -Finally, we can call our function, and print out the returned data in our console: - -## Calling Our Function - -```typescript -getPoolImmutables().then((result) => { - console.log(result) -}) -``` - -To call our function, we'll navigate to our project directory within our console, and use the following command: - -``` -npx ts-node example.ts -``` - -If everything worked correctly, you should see something like this: - -```typescript -➜ example npx ts-node example.ts -{ - factory: '0x1F98431c8aD98523631AE4a59f267346ea31F984', - token0: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', - token1: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', - fee: 3000, - tickSpacing: 60, - maxLiquidityPerTick: BigNumber { - _hex: '0x023746e6a58dcb13d4af821b93f062', - _isBigNumber: true - } -} -``` - -## The Final Script - -```typescript -import { ethers } from 'ethers' - -const provider = new ethers.providers.JsonRpcProvider('') - -const poolAddress = '0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8' - -const poolImmutablesAbi = [ - 'function factory() external view returns (address)', - 'function token0() external view returns (address)', - 'function token1() external view returns (address)', - 'function fee() external view returns (uint24)', - 'function tickSpacing() external view returns (int24)', - 'function maxLiquidityPerTick() external view returns (uint128)', -] - -const poolContract = new ethers.Contract(poolAddress, poolImmutablesAbi, provider) - -interface Immutables { - factory: string - token0: string - token1: string - fee: number - tickSpacing: number - maxLiquidityPerTick: number -} - -async function getPoolImmutables() { - const [factory, token0, token1, fee, tickSpacing, maxLiquidityPerTick] = await Promise.all([ - poolContract.factory(), - poolContract.token0(), - poolContract.token1(), - poolContract.fee(), - poolContract.tickSpacing(), - poolContract.maxLiquidityPerTick(), - ]) - - const immutables: Immutables = { - factory, - token0, - token1, - fee, - tickSpacing, - maxLiquidityPerTick, - } - return immutables -} - -getPoolImmutables().then((result) => { - console.log(result) -}) -``` diff --git a/docs/sdk/v3/guides/03-creating-a-pool.md b/docs/sdk/v3/guides/03-creating-a-pool.md deleted file mode 100644 index 1052c5bdfd..0000000000 --- a/docs/sdk/v3/guides/03-creating-a-pool.md +++ /dev/null @@ -1,256 +0,0 @@ ---- -id: creating-a-pool -title: Creating a Pool Instance ---- - -This guide extends the previous [Using Ethers.js](./using-ethers) guide by using the fetched data from the EVM to create a `Pool` instance using the V3 SDK. A "Pool" as we refer to it here does not mean an actual V3 pool, but a model of one created with the SDK. This model will help us interact with the protocol, or manipulate data relevant to the protocol, in a way that does not require continually fetching pool data from the EVM - which can be time intensive and computationally costly. - -## Importing the ABI - -First we will replace the abi that we previously wrote out manually with a library that contains the total V3 pool abi for us to easily interact with. Note the abi is imported from the `v3-core` npm package, rather than the `v3-sdk` npm package, as it is a part of the protocol rather than the SDK. - -Depending on your local configuration, you may need to update your tsconfig.json to allow importing of `json` files with `"resolveJsonModule": true,`. - -```typescript -import { ethers } from 'ethers' -import { Pool } from '@uniswap/v3-sdk' -import { Token } from '@uniswap/sdk-core' -import { abi as IUniswapV3PoolABI } from '@uniswap/v3-core/artifacts/contracts/interfaces/IUniswapV3Pool.sol/IUniswapV3Pool.json' -``` - -Now we'll update the `Contract` object with our imported ABI - and keep the pool address and provider the same as the previous example. - -```typescript -const provider = new ethers.providers.JsonRpcProvider('https://mainnet.infura.io/v3/') -const poolAddress = '0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8' -const poolContract = new ethers.Contract(poolAddress, IUniswapV3PoolABI, provider) -``` - -## Creating The Interfaces - -Create two interfaces with types that are appropriate for the data we need. We won't be using all of this data, but some extra data is fetched for context. - -```typescript -interface Immutables { - factory: string - token0: string - token1: string - fee: number - tickSpacing: number - maxLiquidityPerTick: ethers.BigNumber -} - -interface State { - liquidity: ethers.BigNumber - sqrtPriceX96: ethers.BigNumber - tick: number - observationIndex: number - observationCardinality: number - observationCardinalityNext: number - feeProtocol: number - unlocked: boolean -} -``` - -## Fetching Immutable Data - -Fetch the immutable data from the deployed V3 pool contract and return it to create a model of the pool. - -```typescript -async function getPoolImmutables() { - const [factory, token0, token1, fee, tickSpacing, maxLiquidityPerTick] = await Promise.all([ - poolContract.factory(), - poolContract.token0(), - poolContract.token1(), - poolContract.fee(), - poolContract.tickSpacing(), - poolContract.maxLiquidityPerTick(), - ]) - - const immutables: Immutables = { - factory, - token0, - token1, - fee, - tickSpacing, - maxLiquidityPerTick, - } - return immutables -} -``` - -Fetch the state data in with the same `Promise.all` style. This approach queries state data concurrently, rather than sequentially, to avoid out of sync data that may be returned if sequential queries are executed over the span of two blocks. - -> `sqrtPriceX96` and `sqrtRatioX96`, despite being named differently, are interchangeable values. - -## Fetching State Data - -```typescript -async function getPoolState() { - const [liquidity, slot] = await Promise.all([poolContract.liquidity(), poolContract.slot0()]) - - const PoolState: State = { - liquidity, - sqrtPriceX96: slot[0], - tick: slot[1], - observationIndex: slot[2], - observationCardinality: slot[3], - observationCardinalityNext: slot[4], - feeProtocol: slot[5], - unlocked: slot[6], - } - - return PoolState -} -``` - -## Creating the Pool Instance - -Create a function called `main`, which calls previously written functions, and uses the returned data to construct two `Ethers.js` `Token` instances and a V3 SDK `Pool` instance. - -> The final constructor argument when creating a Pool, `ticks`, is optional. `ticks` takes all tick data, including the liquidity within, which can be used to model the result of a swap. Because this can add up to a lot of data fetched from the EVM, it is optional and may be left out when not needed. In this example, we have left it out. - -```typescript -async function main() { - const [immutables, state] = await Promise.all([getPoolImmutables(), getPoolState()]) - - const TokenA = new Token(3, immutables.token0, 6, 'USDC', 'USD Coin') - - const TokenB = new Token(3, immutables.token1, 18, 'WETH', 'Wrapped Ether') - - const poolExample = new Pool( - TokenA, - TokenB, - immutables.fee, - state.sqrtPriceX96.toString(), - state.liquidity.toString(), - state.tick - ) - console.log(poolExample) -} - -main() -``` - -If everything is working, the script should return something like this: - -```typescript -Pool { - token0: Token { - chainId: 1, - decimals: 6, - symbol: 'USDC', - name: 'USD Coin', - isNative: false, - isToken: true, - address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' - }, - token1: Token { - chainId: 1, - decimals: 18, - symbol: 'WETH', - name: 'Wrapped Ether', - isNative: false, - isToken: true, - address: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2' - }, - fee: 3000, - sqrtRatioX96: JSBI(4) [ 1389262056, -1079304777, -1721588872, 19633, sign: false ], - liquidity: JSBI(3) [ 988036789, -62655684, 1, sign: false ], - tickCurrent: 197709, - tickDataProvider: NoTickDataProvider {} -} -``` - -## The Final Script - -```typescript -import { ethers } from 'ethers' -import { Pool } from '@uniswap/v3-sdk' -import { Token } from '@uniswap/sdk-core' -import { abi as IUniswapV3PoolABI } from '@uniswap/v3-core/artifacts/contracts/interfaces/IUniswapV3Pool.sol/IUniswapV3Pool.json' - -const provider = new ethers.providers.JsonRpcProvider('https://mainnet.infura.io/v3/') - -const poolAddress = '0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8' - -const poolContract = new ethers.Contract(poolAddress, IUniswapV3PoolABI, provider) - -interface Immutables { - factory: string - token0: string - token1: string - fee: number - tickSpacing: number - maxLiquidityPerTick: ethers.BigNumber -} - -interface State { - liquidity: ethers.BigNumber - sqrtPriceX96: ethers.BigNumber - tick: number - observationIndex: number - observationCardinality: number - observationCardinalityNext: number - feeProtocol: number - unlocked: boolean -} - -async function getPoolImmutables() { - const [factory, token0, token1, fee, tickSpacing, maxLiquidityPerTick] = await Promise.all([ - poolContract.factory(), - poolContract.token0(), - poolContract.token1(), - poolContract.fee(), - poolContract.tickSpacing(), - poolContract.maxLiquidityPerTick(), - ]) - - const immutables: Immutables = { - factory, - token0, - token1, - fee, - tickSpacing, - maxLiquidityPerTick, - } - return immutables -} - -async function getPoolState() { - const [liquidity, slot] = await Promise.all([poolContract.liquidity(), poolContract.slot0()]) - - const PoolState: State = { - liquidity, - sqrtPriceX96: slot[0], - tick: slot[1], - observationIndex: slot[2], - observationCardinality: slot[3], - observationCardinalityNext: slot[4], - feeProtocol: slot[5], - unlocked: slot[6], - } - - return PoolState -} - -async function main() { - const [immutables, state] = await Promise.all([getPoolImmutables(), getPoolState()]) - - const TokenA = new Token(3, immutables.token0, 6, 'USDC', 'USD Coin') - - const TokenB = new Token(3, immutables.token1, 18, 'WETH', 'Wrapped Ether') - - const poolExample = new Pool( - TokenA, - TokenB, - immutables.fee, - state.sqrtPriceX96.toString(), - state.liquidity.toString(), - state.tick - ) - console.log(poolExample) -} - -main() -``` diff --git a/docs/sdk/v3/guides/03-trading.md b/docs/sdk/v3/guides/03-trading.md new file mode 100644 index 0000000000..880f1c3315 --- /dev/null +++ b/docs/sdk/v3/guides/03-trading.md @@ -0,0 +1,112 @@ +--- +id: trading +title: Executing a Trade +--- + +## Introduction + +This guide will build off our [quoting guide](./02-quoting.md) and show how to use a quote to construct and execute a trade on the Uniswap V3 protocol. It is based on the [Trading code example](https://github.com/Uniswap/examples/tree/main/v3-sdk/trading), 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/trading/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 trade between two ERC20 tokens: **WETH and USDC**. The tokens, amount of input token, and the fee level can be configured as inputs. + +The guide will **cover**: + +1. Constructing a route from pool information +2. Constructing an unchecked trade +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. + +:::note +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. +::: + +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) + +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) + +## Constructing a route from pool information + +To construct our trade, we will first create an model instance of a `Pool`. 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: + +```typescript reference title="Fetching pool metadata" referenceLinkText="View on Github" customStyling +https://github.com/Uniswap/examples/blob/e8bd4178ccaccd6776407f79a319128d4c31f90d/v3-sdk/trading/src/trading.ts#L152-L161 +``` + +Using this metadata along with our inputs, we will then construct a `Pool`: + +```typescript reference title="Constructing a Pool" referenceLinkText="View on Github" customStyling +https://github.com/Uniswap/examples/blob/e8bd4178ccaccd6776407f79a319128d4c31f90d/v3-sdk/trading/src/trading.ts#L42-L49 +``` + +With this `Pool`, we can now construct a route to use in our trade. We will reuse our previous quoting code to calculate the output amount we expect from our trade: + +```typescript reference title="Constructing a Route" referenceLinkText="View on Github" customStyling +https://github.com/Uniswap/examples/blob/e8bd4178ccaccd6776407f79a319128d4c31f90d/v3-sdk/trading/src/trading.ts#L51-L55 +``` + +## 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: + +```typescript reference title="Getting a quote" referenceLinkText="View on Github" customStyling +https://github.com/Uniswap/examples/blob/5aa1cc2d4d5a7a2c5ce4a6f69f6cba28d925eeb3/v3-sdk/trading/src/libs/trading.ts#L58 +``` + +As shown below, the quote is obtained using the `v3-sdk`'s `SwapQuoter`, in contrast to the [previous quoting guide](./02-quoting.md), where we directly accessed the smart contact: + +```typescript reference title="Getting a quote using the v3-sdk" referenceLinkText="View on Github" customStyling +https://github.com/Uniswap/examples/blob/5aa1cc2d4d5a7a2c5ce4a6f69f6cba28d925eeb3/v3-sdk/trading/src/libs/trading.ts#L132-L145 +``` + +The `SwapQuoter`'s `quoteCallParameters` function, gives us the calldata needed to make the call to the `Quoter`, and we then decode the returned quote: + +```typescript reference title="Getting a quote using the v3-sdk" referenceLinkText="View on Github" customStyling +https://github.com/Uniswap/examples/blob/5aa1cc2d4d5a7a2c5ce4a6f69f6cba28d925eeb3/v3-sdk/trading/src/libs/trading.ts#L147-L152 +``` + + +With the quote and the route, we can now construct an unchecked trade using the route in addition to the output amount from a quote based on our input: + +```typescript reference title="Creating a Trade" referenceLinkText="View on Github" customStyling +https://github.com/Uniswap/examples/blob/e8bd4178ccaccd6776407f79a319128d4c31f90d/v3-sdk/trading/src/trading.ts#L59-L73 +``` + +This example uses an exact input trade, but we can also construct a trade using exact output assuming we adapt our quoting code accordingly. + +## 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: + +```typescript reference title="Approve SwapRouter to spend our tokens" referenceLinkText="View on Github" customStyling +https://github.com/Uniswap/examples/blob/5aa1cc2d4d5a7a2c5ce4a6f69f6cba28d925eeb3/v3-sdk/trading/src/libs/trading.ts#L90-L94 +``` + +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 reference title="Constructing SwapOptions" referenceLinkText="View on Github" customStyling +https://github.com/Uniswap/examples/blob/e8bd4178ccaccd6776407f79a319128d4c31f90d/v3-sdk/trading/src/trading.ts#L86-L90 +``` + +Next, we use the Uniswap `SwapRouter` to get the associated call parameters for our trade and options: + +```typescript reference title="Getting call parameters" referenceLinkText="View on Github" customStyling +https://github.com/Uniswap/examples/blob/e8bd4178ccaccd6776407f79a319128d4c31f90d/v3-sdk/trading/src/trading.ts#L92 +``` + +Finally, we can construct a transaction from the method parameters and send the transaction: + +```typescript reference title="Sending a transaction" referenceLinkText="View on Github" customStyling +https://github.com/Uniswap/examples/blob/e8bd4178ccaccd6776407f79a319128d4c31f90d/v3-sdk/trading/src/trading.ts#L94-L101 +``` + +## 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](./04-routing.md) guide will show you how to use this router and execute optimal swaps. diff --git a/docs/sdk/v3/guides/04-fetching-prices.md b/docs/sdk/v3/guides/04-fetching-prices.md deleted file mode 100644 index 2e641a417e..0000000000 --- a/docs/sdk/v3/guides/04-fetching-prices.md +++ /dev/null @@ -1,151 +0,0 @@ ---- -id: fetching-prices -title: Fetching Spot Prices ---- - -## Fetching Token Prices with the SDK - -This guide will teach you how to fetch the current market price of any token on Uniswap. First, you will learn how to call the getter methods `token0Price` and `token1Price` exposed on `Pool` instances. Then you will peek under the hood and learn how the SDK calculates these quantities from the `sqrtPriceX96` value. Going through these calculations will hopefully provide the necessary context behind fixed-point numbers, square roots, and difficult-to-understand variable names like `sqrtPriceX96`. - -### Calling the functions - -Similar to other examples, you first must set up your pool. If you’re unsure how to collect all the parameters necessary in creating a `Pool` instance see [Creating a Pool Instance](../guides/03-creating-a-pool.md) or look at this typescript [example](https://github.com/Uniswap/uniswap-docs/blob/main/examples/sdk/AddAndRemoveLiquidity.tsx). The `Pool` class contains two getter methods `token0Price` and `token1Price` which will return the prices of each token respectively as a `Price`. - -After constructing the pool, you can save the token prices as constants: - -```typescript -const DAI_USDC_POOL = new Pool( - DAI, - USDC, - immutables.fee, - state.sqrtPriceX96.toString(), - state.liquidity.toString(), - state.tick -) - -const token0Price = DAI_USDC_POOL.token0Price -const token1Price = DAI_USDC_POOL.token1Price -``` - -### Understanding sqrtPrice - -What is `sqrtPriceX96`? - -In Uniswap V3, prices of tokens are stored in the [0th slot](../../../contracts/v3/reference/core/interfaces/pool/IUniswapV3PoolState#slot0) of the pool state. Storing the price values instead of deriving them allows pools to perform higher precision operations. In the actual implementation, prices are stored as square roots, hence the `sqrt` prefix. The price is stored as a square root because of the geometric nature of the core AMM algorithm, x\*y=k. Essentially, the [math](https://uniswap.org/whitepaper-v3.pdf) works out well when working with the square root of the price. - -In addition, you'll notice the `X96` suffix at the end of the variable name. This `X*` naming convention is used throughout the Uniswap V3 codebase to indicate values that are encoded as binary [fixed-point numbers](https://en.wikipedia.org/wiki/Fixed-point_arithmetic). Fixed-point is excellent at representing fractions while maintaining consistent fidelity and high precision in integer-only environments like the EVM, making it a perfect fit for representing prices, which of course are ultimately fractions. The number after `X` indicates the number of _fraction bits_ - 96 in this case - reserved for encoding the value after the decimal point. The number of integer bits can be trivially derived from the size of the variable and the number of fraction bits. In this case, `sqrtPriceX96` is stored as a `uint160`, meaning that there are `160 - 96 = 64` integer bits. - -:::note -`sqrtPriceX96` and `sqrtRatioX96` represent the same value, and are interchangeable. -::: - -Consider the following derivation, which formalizes the definitions above: - -```python -sqrtPriceX96 = sqrt(price) * 2 ** 96 -``` - -Thus, to get a `price` from a `sqrtPriceX96` value, you can execute the following operations: - -```python -sqrtPriceX96 = sqrt(price) * 2 ** 96 -# divide both sides by 2 ** 96 -sqrtPriceX96 / (2 ** 96) = sqrt(price) -# square both sides -(sqrtPriceX96 / (2 ** 96)) ** 2 = price -# expand the squared fraction -(sqrtPriceX96 ** 2) / ((2 ** 96) ** 2) = price -# multiply the exponents in the denominator to get the final expression -sqrtRatioX96 ** 2 / 2 ** 192 = price -``` - -You will see that the formula in the last step is how the SDK calculates the prices with the functions [`token0Price`](#token0price) and [`token1Price`](#token1price). - -### token0Price - -Let's apply the math derived above to the functions `token0Price` and `token1Price`. Note that `sqrtRatioX96` is interchangeable with `sqrtPriceX96`. - -```typescript - /** - * Returns the current mid-price of the pool in terms of token0, i.e. the ratio of token1 over token0 - */ - public get token0Price(): Price { - return ( - this._token0Price ?? - (this._token0Price = new Price( - this.token0, - this.token1, - Q192, - JSBI.multiply(this.sqrtRatioX96, this.sqrtRatioX96) - )) - ) - } -``` - -`token0Price` returns a new `Price` as the ratio of token1 over token0. Note that a `Price` is constructed by: - -```typescript -constructor( - baseToken: Token, - quoteToken: Token, - denominator: BigintIsh, - numerator: BigintIsh) -``` - -Let's break down the denominator and the numerator of the returned price and prove that it matches the math derived above. Recall that the expression achieved above is - -```python -price = sqrtRatioX96 ** 2 / 2 ** 192 -``` - -#### The numerator - -It's worth noting that the numerator is misleadingly listed _below_ the denominator in the constructor for a `Price`. In any case, you will see that the numerator of the fraction is `JSBI.multiply(this.sqrtRatioX96, this.sqrtRatioX96)` which nicely follows the math above: `sqrtPriceX96 ** 2`. - -#### The denominator - -The denominator is `Q192`. To break this number down recall the following constants defined in the SDK: - -```typescript -export const Q96 = JSBI.exponentiate(JSBI.BigInt(2), JSBI.BigInt(96)) -export const Q192 = JSBI.exponentiate(Q96, JSBI.BigInt(2)) -``` - -Thus, the denominator for the `token0Price` also matches the math derived above where `Q192` is `(2 ** 96) * (2 ** 96)` which is the same as `(2 ** 192)`. - -### token1Price - -Recall that `token0Price` is the ratio of token1 over token0 and that `token1Price` is the ratio of token0 over token1. This means that the derivation for `token1Price` follows the same math except the numerator and denominator are flipped, implying the inverse. - -So instead of - -```python -price = sqrtRatioX96 ** 2 / 2 ** 192 -``` - -you have - -```python - price = 2 ** 192 / sqrtRatioX96 ** 2 -``` - -which is simply shown below in the function definition of `token1Price` : - -```typescript - /** - * Returns the current mid-price of the pool in terms of token1, i.e. the ratio of token0 over token1 - */ - public get token1Price(): Price { - return ( - this._token1Price ?? - (this._token1Price = new Price( - this.token1, - this.token0, - JSBI.multiply(this.sqrtRatioX96, this.sqrtRatioX96), - Q192 - )) - ) - } -``` - -You can see that in the function definition the numerator is now `Q192` and the denominator is now `JSBI.multiply(this.sqrtRatioX96, this.sqrtRatioX96)`, matching the expression above. diff --git a/docs/sdk/v3/guides/04-routing.md b/docs/sdk/v3/guides/04-routing.md new file mode 100644 index 0000000000..767852d5e1 --- /dev/null +++ b/docs/sdk/v3/guides/04-routing.md @@ -0,0 +1,74 @@ +--- +id: routing +title: Routing a Swap +--- + +## Introduction + +This guide will cover how to use Uniswap's smart order router to compute optimal routes and execute swaps. Rather than trading between a single pool, smart routing may use multiple hops (as many as needed) to ensure that the end result of the swap is the optimal price. It is based on the [routing code example](https://github.com/Uniswap/examples/tree/main/v3-sdk/routing), 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/routing/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 trade between **WETH and USDC**, but you can configure your example to us any two currencies and amount of input currency. + +The guide will **cover**: + +1. Creating a router instance +2. Creating a route +3. Swapping using a route + +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. + +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) + +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) + +## 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: + +```typescript reference title="Instantiating an AlphaRouter" referenceLinkText="View on Github" customStyling +https://github.com/Uniswap/examples/blob/38ff60aeb3ad8ff839db9e7952a726ca7d6b68fd/v3-sdk/routing/src/libs/routing.ts#L24-L27 +``` + +## Creating a route + +Next, we will create our options conforming to the `SwapOptionsSwapRouter02` interface, defining the wallet to use, slippage tolerance, and deadline for the transaction: + +```typescript reference title="Routing Options" referenceLinkText="View on Github" customStyling +https://github.com/Uniswap/examples/blob/38ff60aeb3ad8ff839db9e7952a726ca7d6b68fd/v3-sdk/routing/src/libs/routing.ts#L29-L34 +``` + +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 reference title="Creating a route" referenceLinkText="View on Github" customStyling +https://github.com/Uniswap/examples/blob/38ff60aeb3ad8ff839db9e7952a726ca7d6b68fd/v3-sdk/routing/src/libs/routing.ts#L36-L47 +``` + +## Swapping using a route + + +First, we need to give approval to the `SwapRouter` smart contract to spend our tokens for us: + +```typescript reference title="Approving SwapRouter to spend our tokens" referenceLinkText="View on Github" customStyling +https://github.com/Uniswap/examples/blob/0071bb5883fba6f4cc39a5f1644ac941e4f24822/v3-sdk/routing/src/libs/routing.ts#L66 +``` + +Once the approval has been granted and using the route, we can now execute the trade using the route's computed calldata, values, and gas values: + + +```typescript reference title="Using a route" referenceLinkText="View on Github" customStyling +https://github.com/Uniswap/examples/blob/38ff60aeb3ad8ff839db9e7952a726ca7d6b68fd/v3-sdk/routing/src/libs/routing.ts#L61-L68 +``` + +After swapping, you should see the currency balances update in the UI shortly after the block is confirmed. + +## Next Steps + +Now that you're familiar with trading, consider checking out our next guides on [pooling liquidity](./liquidity/01-minting-position.md) to Uniswap! diff --git a/docs/sdk/v3/guides/05-creating-a-trade.md b/docs/sdk/v3/guides/05-creating-a-trade.md deleted file mode 100644 index b663829f92..0000000000 --- a/docs/sdk/v3/guides/05-creating-a-trade.md +++ /dev/null @@ -1,241 +0,0 @@ ---- -id: creating-a-trade -title: Creating a Trade ---- - -This guide extends the previous [Creating a Pool Instance](03-creating-a-pool.md) and [Using Ethers.js](./using-ethers) guides by using the `pool` object to quote an estimated amount out for a trade, then creates a trade object that can be used to execute a swap. - -## Creating a Quoter Contract Object - -In order to retrieve a quote, create a [Contract](https://docs.ethers.io/v5/api/contract/contract/) object using ethers.js - -The quoter is a smart contract that retrieves estimated output or input amounts for a given swap type. This example creates an object in our javascript environment that models the [quoter interface](https://github.com/Uniswap/uniswap-v3-periphery/blob/main/contracts/interfaces/IQuoter.sol), which can be called to return a swap quote. - -Create the quoter contract object by importing the [ABI](https://docs.soliditylang.org/en/v0.7.0/abi-spec.html) from the [uniswap-v3-periphery](https://www.npmjs.com/package/@uniswap/v3-periphery) npm package. - -```ts -import { abi as QuoterABI } from '@uniswap/v3-periphery/artifacts/contracts/lens/Quoter.sol/Quoter.json' -``` - -assign your [ethereum endpoint provider](https://ethereum.org/en/developers/docs/nodes-and-clients/nodes-as-a-service/), in this case, Infura. - -```ts -const provider = new ethers.providers.JsonRpcProvider('') -``` - -Provide the [deployment address of the quoter contract](https://github.com/Uniswap/uniswap-v3-periphery/blob/main/deploys.md). - -```ts -const quoterContract = new ethers.Contract(quoterAddress, QuoterABI, provider) -``` - -## Using callStatic To Return A Quote - -To get a quote for a swap, we will call the Quoter contract's `quoteExactInputSingle` function, the interface of which looks like this: - -```solidity - function quoteExactInputSingle( - address tokenIn, - address tokenOut, - uint24 fee, - uint256 amountIn, - uint160 sqrtPriceLimitX96 - ) external returns (uint256 amountOut); -``` - -In an ideal world, these quoter functions would be `view` functions, which would make them very easy to query on-chain with minimal gas costs. Instead, the 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. - -To get around this difficulty, we can use the [callStatic](https://docs.ethers.io/v5/api/contract/contract/#contract-callStatic) method provided by `ethers.js`. `callStatic` 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. - -To simulate a transaction without actually broadcasting it to the EVM, use the `callStatic` to call the `ExactInputSingle` function in the `Quoter` contract, which will tell us how much an of output token we will receive given a certain amount of input token when using a single hop swap. - -Note this function uses the `Immutables` interface defined in the earlier guides. - -```ts -async function main() { - const amountIn = 1500 - - const quotedAmountOut = await quoterContract.callStatic.quoteExactInputSingle( - immutables.token0, - immutables.token1, - immutables.fee, - amountIn.toString(), - 0 - ) -} -``` - -## Construct a Trade - -Create a [Route](https://github.com/Uniswap/uniswap-v3-sdk/blob/7c3aedd0cf9441d03607e258734eada44a73863d/src/entities/route.ts) object and assign it to a variable `swapRoute` - -```ts -const swapRoute = new Route([poolExample], TokenA, TokenB) -``` - -Create an [Unchecked Trade](https://github.com/Uniswap/uniswap-v3-sdk/blob/7c3aedd0cf9441d03607e258734eada44a73863d/src/entities/trade.ts#L346), a type of trade that is useful when we have retrieved a quote prior to the construction of the trade object. - -```ts -const uncheckedTradeExample = await Trade.createUncheckedTrade({ - route: swapRoute, - inputAmount: CurrencyAmount.fromRawAmount(TokenA, amountIn.toString()), - outputAmount: CurrencyAmount.fromRawAmount(TokenB, quotedAmountOut.toString()), - tradeType: TradeType.EXACT_INPUT, -}) -``` - -## Print The Quote And Trade To Your Console - -Print the `Quote` and the `UncheckedTrade` to the console. - -```ts -console.log('The quoted amount out is', quotedAmountOut.toString()) -console.log('The unchecked trade object is', uncheckedTradeExample) -``` - -If everything is working, you should see something similar to this returned in your console. - -```console -The quoted amount out is 661497830963 -The unchecked trade object is Trade { - swaps: [ - { - inputAmount: [CurrencyAmount], - outputAmount: [CurrencyAmount], - route: [Route] - } - ], - tradeType: 0 -} -``` - -## The Full Example - -```ts -import { ethers } from 'ethers' -import { Pool } from '@uniswap/v3-sdk' -import { CurrencyAmount, Token, TradeType } from '@uniswap/sdk-core' -import { abi as IUniswapV3PoolABI } from '@uniswap/v3-core/artifacts/contracts/interfaces/IUniswapV3Pool.sol/IUniswapV3Pool.json' -import { Route } from '@uniswap/v3-sdk' -import { Trade } from '@uniswap/v3-sdk' -import { abi as QuoterABI } from '@uniswap/v3-periphery/artifacts/contracts/lens/Quoter.sol/Quoter.json' - -const provider = new ethers.providers.JsonRpcProvider('') - -// USDC-WETH pool address on mainnet for fee tier 0.05% -const poolAddress = '0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640' - -const poolContract = new ethers.Contract(poolAddress, IUniswapV3PoolABI, provider) - -const quoterAddress = '0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6' - -const quoterContract = new ethers.Contract(quoterAddress, QuoterABI, provider) - -interface Immutables { - factory: string - token0: string - token1: string - fee: number - tickSpacing: number - maxLiquidityPerTick: ethers.BigNumber -} - -interface State { - liquidity: ethers.BigNumber - sqrtPriceX96: ethers.BigNumber - tick: number - observationIndex: number - observationCardinality: number - observationCardinalityNext: number - feeProtocol: number - unlocked: boolean -} - -async function getPoolImmutables() { - const [factory, token0, token1, fee, tickSpacing, maxLiquidityPerTick] = await Promise.all([ - poolContract.factory(), - poolContract.token0(), - poolContract.token1(), - poolContract.fee(), - poolContract.tickSpacing(), - poolContract.maxLiquidityPerTick(), - ]) - - const immutables: Immutables = { - factory, - token0, - token1, - fee, - tickSpacing, - maxLiquidityPerTick, - } - return immutables -} - -async function getPoolState() { - // note that data here can be desynced if the call executes over the span of two or more blocks. - const [liquidity, slot] = await Promise.all([poolContract.liquidity(), poolContract.slot0()]) - - const PoolState: State = { - liquidity, - sqrtPriceX96: slot[0], - tick: slot[1], - observationIndex: slot[2], - observationCardinality: slot[3], - observationCardinalityNext: slot[4], - feeProtocol: slot[5], - unlocked: slot[6], - } - - return PoolState -} - -async function main() { - // query the state and immutable variables of the pool - const [immutables, state] = await Promise.all([getPoolImmutables(), getPoolState()]) - - // create instances of the Token object to represent the two tokens in the given pool - const TokenA = new Token(3, immutables.token0, 6, 'USDC', 'USD Coin') - - const TokenB = new Token(3, immutables.token1, 18, 'WETH', 'Wrapped Ether') - - // create an instance of the pool object for the given pool - const poolExample = new Pool( - TokenA, - TokenB, - immutables.fee, - state.sqrtPriceX96.toString(), //note the description discrepancy - sqrtPriceX96 and sqrtRatioX96 are interchangable values - state.liquidity.toString(), - state.tick - ) - - // assign an input amount for the swap - const amountIn = 1500 - - // call the quoter contract to determine the amount out of a swap, given an amount in - const quotedAmountOut = await quoterContract.callStatic.quoteExactInputSingle( - immutables.token0, - immutables.token1, - immutables.fee, - amountIn.toString(), - 0 - ) - - // create an instance of the route object in order to construct a trade object - const swapRoute = new Route([poolExample], TokenA, TokenB) - - // create an unchecked trade instance - const uncheckedTradeExample = await Trade.createUncheckedTrade({ - route: swapRoute, - inputAmount: CurrencyAmount.fromRawAmount(TokenA, amountIn.toString()), - outputAmount: CurrencyAmount.fromRawAmount(TokenB, quotedAmountOut.toString()), - tradeType: TradeType.EXACT_INPUT, - }) - - // print the quote and the unchecked trade instance in the console - console.log('The quoted amount out is', quotedAmountOut.toString()) - console.log('The unchecked trade object is', uncheckedTradeExample) -} - -main() -``` diff --git a/docs/sdk/v3/guides/06-auto-router.md b/docs/sdk/v3/guides/06-auto-router.md deleted file mode 100644 index 4f31c437b6..0000000000 --- a/docs/sdk/v3/guides/06-auto-router.md +++ /dev/null @@ -1,176 +0,0 @@ ---- -id: auto-router -title: Integrating the Auto Router ---- - -# Integrating the Auto Router - -You can use the Auto Router to fetch optimized trade routes for swapping on Uniswap. To use the Auto Router, you will create an `AlphaRouter` instance and use the method `route` to get quotes, gas information, and calldata for an optimized swap. - -In this quick start you will: - -1. Import necessary packages -2. Initialize the `AlphaRouter` -3. Set up the parameters for the `route` method -4. Call `route` to retrieve calldata -5. Submit a route transaction - -## Importing the Package - -To integrate with the Auto Router, you will use the [smart-order-router](https://www.npmjs.com/package/@uniswap/smart-order-router) package. The smart-order-router package allows you to use the Auto Router through the `AlphaRouter` class. - -Import `AlphaRouter` from the smart-order-router package to get started. - -```typescript -import { AlphaRouter } from '@uniswap/smart-order-router' -``` - -## Initializing the AlphaRouter - -The `AlphaRouter` class contains methods for generating optimized routes. To create an instance of the AlphaRouter, configure the `chainId` and `provider` parameters. - -#### Parameters - -| Name | Requirement | Description | -| -------- | ----------- | ---------------------------------------------------------------------------------------------- | -| chainId | [required] | The id of the chain you want to route swaps on. e.g., The chainId for Ethereum mainnet is `1`. | -| provider | [required] | A JSON RPC endpoint, e.g., Infura. | - -```typescript -const router = new AlphaRouter({ chainId: 1, provider: web3Provider }) -``` - -## Calling `route` - -The `route` method returns all the swap calldata and gas information needed for submitting an optimal swap to the chain. - -Once you instantiate `AlphaRouter` call `route` with the following parameters: - -```typescript - { - amount: CurrencyAmount, - quoteCurrency: Currency, - tradeType: TradeType, - swapConfig?: SwapConfig, - partialRoutingConfig?: Partial = {} - } -``` - -#### Parameters - -| Name | Requirement | Description | -| ------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | -| amount | [required] | The amount specified by the user. For EXACT_INPUT swaps, this is the input token amount. For EXACT_OUTPUT swaps, this is the output token. | -| quoteCurrency | [required] | The currency of the token we are returning a quote for. For EXACT_INPUT swaps, this is the output token. For EXACT_OUTPUT, this is the input token | -| swapType | [required] | Either an exactInput swap or an exactOutput swap. | -| swapConfig | [optional] | Configure to set a recipient, slippageTolerance, deadline, and inputTokenPermit. If provided, calldata for executing the swap will also be returned. | -| routingConfig | [optional] | Optional config for tuning the performance of the routing algorithm. | - -This example gets a route for a WETH-USDC swap. - -```typescript -const WETH = new Token(1, '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', 18, 'WETH', 'Wrapped Ether') - -const USDC = new Token(ChainId.MAINNET, '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', 6, 'USDC', 'USD//C') - -const route = await router.route(wethAmount, USDC, TradeType.EXACT_INPUT, { - recipient: myAddress, - slippageTolerance: new Percent(5, 100), - deadline: Math.floor(Date.now() / 1000 + 1800), -}) -``` - -## Submitting a Transaction - -The object returned from calling `route` is a `SwapRoute` object with the following fields: - -```typescript -export type SwapRoute = { - quote: CurrencyAmount - quoteGasAdjusted: CurrencyAmount - estimatedGasUsed: BigNumber - estimatedGasUsedQuoteToken: CurrencyAmount - estimatedGasUsedUSD: CurrencyAmount - gasPriceWei: BigNumber - trade: Trade - route: RouteWithValidQuote[] - blockNumber: BigNumber - methodParameters?: MethodParameters -} -``` - -Use the quoted gas price and generated call data as inputs for the transaction, as done below: - -```typescript -const V3_SWAP_ROUTER_ADDRESS = "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45"; -const MY_ADDRESS = /*YOUR ADDRESS HERE*/; - -const transaction = { - data: route.methodParameters.calldata, - to: V3_SWAP_ROUTER_ADDRESS, - value: BigNumber.from(route.methodParameters.value), - from: MY_ADDRESS, - gasPrice: BigNumber.from(route.gasPriceWei), -}; - -await web3Provider.sendTransaction(transaction); -``` - -## The Full Example - -This full example compiles all of the steps above and also prints the route quote and gas quotes returned from the `SwapRoute` data. - -```typescript -import { AlphaRouter } from '@uniswap/smart-order-router' -import { Token, CurrencyAmount } from '@uniswap/sdk-core' - -const V3_SWAP_ROUTER_ADDRESS = '0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45'; -const MY_ADDRESS = /*YOUR ADDRESS HERE*/; -const web3Provider = /*YOUR PROVIDER HERE*/ - -const router = new AlphaRouter({ chainId: 1, provider: web3Provider }); - -const WETH = new Token( - 1, - '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', - 18, - 'WETH', - 'Wrapped Ether' -); - -const USDC = new Token( - ChainId.MAINNET, - '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', - 6, - 'USDC', - 'USD//C' -); - -const typedValueParsed = '100000000000000000000' -const wethAmount = CurrencyAmount.fromRawAmount(currency, JSBI.BigInt(typedValueParsed)); - -const route = await router.route( - wethAmount, - USDC, - TradeType.EXACT_INPUT, - { - recipient: myAddress, - slippageTolerance: new Percent(5, 100), - deadline: Math.floor(Date.now()/1000 +1800) - } -); - -console.log(`Quote Exact In: ${route.quote.toFixed(2)}`); -console.log(`Gas Adjusted Quote In: ${route.quoteGasAdjusted.toFixed(2)}`); -console.log(`Gas Used USD: ${route.estimatedGasUsedUSD.toFixed(6)}`); - -const transaction = { - data: route.methodParameters.calldata, - to: V3_SWAP_ROUTER_ADDRESS, - value: BigNumber.from(route.methodParameters.value), - from: MY_ADDRESS, - gasPrice: BigNumber.from(route.gasPriceWei), -}; - -await web3Provider.sendTransaction(transaction); -``` diff --git a/docs/sdk/v3/guides/liquidity/01-minting-position.md b/docs/sdk/v3/guides/liquidity/01-minting-position.md new file mode 100644 index 0000000000..8f5685a2e6 --- /dev/null +++ b/docs/sdk/v3/guides/liquidity/01-minting-position.md @@ -0,0 +1,104 @@ +--- +id: minting +title: Minting a Position +--- + +## Introduction + +This guide will cover how to create (or mint) a liquidity position on the Uniswap V3 protocol. +It is based on the [minting a position code example](https://github.com/Uniswap/examples/tree/main/v3-sdk/minting-position), found in the Uniswap code examples [repository](https://github.com/Uniswap/examples). +To run this example, check out the examples's [README](https://github.com/Uniswap/examples/blob/main/v3-sdk/minting-posotion/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 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**. + +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 + +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. + +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) + +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) + +## Giving approval to transfer our tokens + +The first step is to give approval to the protocol's `NonfungiblePositionManager` to transfer our tokens: + +```typescript reference title="Approving our tokens for transferring" referenceLinkText="View on Github" customStyling +https://github.com/Uniswap/examples/blob/b5e64e3d6c17cb91bc081f1ed17581bbf22024bc/v3-sdk/minting-position/src/libs/positions.ts#L46-L51 +``` + +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 reference title="Setting up an ERC20 contract reference and approving" referenceLinkText="View on Github" customStyling +https://github.com/Uniswap/examples/blob/b5e64e3d6c17cb91bc081f1ed17581bbf22024bc/v3-sdk/minting-position/src/libs/positions.ts#L202-L211 +``` + +## 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 reference title="Computing the Pool's address" referenceLinkText="View on Github" customStyling +https://github.com/Uniswap/examples/blob/b5e64e3d6c17cb91bc081f1ed17581bbf22024bc/v3-sdk/minting-position/src/libs/pool.ts#L24-L29 +``` + +Then, we get the Pool's data by creating a reference to the Pool's smart contract and accessing its methods: + +```typescript reference title="Setting up a Pool contract reference and fetching current state data" referenceLinkText="View on Github" customStyling +https://github.com/Uniswap/examples/blob/b5e64e3d6c17cb91bc081f1ed17581bbf22024bc/v3-sdk/minting-position/src/libs/pool.ts#L31-L45 +``` + +Having collected the required data, we can now create an instance of the `Pool` class: + +```typescript reference title="Fetching pool data and creating an instance of the Pool class" referenceLinkText="View on Github" customStyling +https://github.com/Uniswap/examples/blob/b5e64e3d6c17cb91bc081f1ed17581bbf22024bc/v3-sdk/minting-position/src/libs/positions.ts#L111-L118 +``` + +## 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: + +```typescript reference title="Create a Position representation instance" referenceLinkText="View on Github" customStyling +https://github.com/Uniswap/examples/blob/b5e64e3d6c17cb91bc081f1ed17581bbf22024bc/v3-sdk/minting-position/src/libs/positions.ts#L121-L132 +``` + +We use the `fromAmounts` static function of the `Position` class to create an instance of it, which uses the following parameters: + +- The **tickLower** and **tickUpper** parameters specify the price range at which to provide liquidity. This example calls **nearestUsableTick** to get the current useable tick and adjust the lower parameter to be below it by two **tickSpacing** and the upper to be above it by two tickSpacing. This guarantees that the provided liquidity is "in range", meaning it will be earning fees upon minting this position +- **amount0** and **amount1** define the maximum amount of currency the liquidity position can use. In this example, we supply these from our configuration parameters. + +Given those parameters, `fromAmounts` will attempt to calculate the maximum amount of liquidity we can supply. + +## 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. + +```typescript reference title="Getting the transaction calldata and parameters" referenceLinkText="View on Github" customStyling +https://github.com/Uniswap/examples/blob/b5e64e3d6c17cb91bc081f1ed17581bbf22024bc/v3-sdk/minting-position/src/libs/positions.ts#L78-L88 +``` + +The function returns the calldata as well as the value required to execute the transaction: + +```typescript reference title="Submitting the Position NFT minting transaction" referenceLinkText="View on Github" customStyling +https://github.com/Uniswap/examples/blob/b5e64e3d6c17cb91bc081f1ed17581bbf22024bc/v3-sdk/minting-position/src/libs/positions.ts#L91-L100 +``` + +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. + +## Next Steps + +Once you have minted a position, our next guide ([Adding and Removing Liquidity](./02-modifying-position.md)) will demonstrate how you can add and remove liquidity from that minted position! diff --git a/docs/sdk/v3/guides/liquidity/02-modifying-position.md b/docs/sdk/v3/guides/liquidity/02-modifying-position.md new file mode 100644 index 0000000000..1481af67bf --- /dev/null +++ b/docs/sdk/v3/guides/liquidity/02-modifying-position.md @@ -0,0 +1,111 @@ +--- +id: modifying-position +title: Adding & Removing Liquidity +--- + +## Introduction + +This guide will cover how to modify a liquidity position by adding or removing liquidity on the Uniswap V3 protocol. It is based on the [modifying a position code example](https://github.com/Uniswap/examples/tree/main/v3-sdk/modifying-position), found in the Uniswap code examples [repository](https://github.com/Uniswap/examples). To run this example, check out the examples's [README](https://github.com/Uniswap/examples/blob/main/v3-sdk/modifying-position/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 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 and then modify the provided liquidity 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, the Pool **fee** and the **fraction** by which to **add and remove** from our position. + +The guide will **cover**: + +1. Adding liquidity to our position +2. Removing liquidity from our position + +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. + +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) + +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) + +:::note +This guide assumes you are familiar with our [Minting a Position](./01-minting-position.md) guide. A minted position is required to add or remove liquidity from, so the buttons will be disabled until a position is minted. + +Also note that we do not need to give approval to the `NonfungiblePositionManager` to transfer our tokens as we will have already done that when minting our position. +::: + +## 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 reference title="Creating the Position" referenceLinkText="View on Github" customStyling +https://github.com/Uniswap/examples/blob/b5e64e3d6c17cb91bc081f1ed17581bbf22024bc/v3-sdk/modifying-position/src/libs/liquidity.ts#L46-L61 +``` + +The function receives two arguments, which are the `CurrencyAmount`s 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. + +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): + +```typescript reference title="Constructing the options object" referenceLinkText="View on Github" customStyling +https://github.com/Uniswap/examples/blob/b5e64e3d6c17cb91bc081f1ed17581bbf22024bc/v3-sdk/modifying-position/src/libs/liquidity.ts#L63-L67 +``` + +Compared to minting, we have we have omitted the `recipient` parameter and instead passed in the `tokenId` of the position we previously minted. + +The newly created position along with the options object are then passed to the `NonfungiblePositionManager`'s `addCallParameters`: + +```typescript reference title="Passing the position and options object to addCallParameters" referenceLinkText="View on Github" customStyling +https://github.com/Uniswap/examples/blob/b5e64e3d6c17cb91bc081f1ed17581bbf22024bc/v3-sdk/modifying-position/src/libs/liquidity.ts#L70-L73 +``` + +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: + +```typescript reference title="Building and submitting the transaction" referenceLinkText="View on Github" customStyling +https://github.com/Uniswap/examples/blob/b5e64e3d6c17cb91bc081f1ed17581bbf22024bc/v3-sdk/modifying-position/src/libs/liquidity.ts#L76-L85 +``` + +After pressing the button, note how the balance of USDC and DAI drops and our position's liquidity increases. + +## 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. + +To start, we create a position identical to the one we minted: + +```typescript reference title="Creating an identical position as minting" referenceLinkText="View on Github" customStyling +https://github.com/Uniswap/examples/blob/b5e64e3d6c17cb91bc081f1ed17581bbf22024bc/v3-sdk/modifying-position/src/libs/liquidity.ts#L97-L112 +``` + +We then need to construct an options object of type [`RemoveLiquidityOptions`](https://github.com/Uniswap/v3-sdk/blob/08a7c050cba00377843497030f502c05982b1c43/src/nonfungiblePositionManager.ts#L138): + +```typescript reference title="Constructing the options object" referenceLinkText="View on Github" customStyling +https://github.com/Uniswap/examples/blob/b5e64e3d6c17cb91bc081f1ed17581bbf22024bc/v3-sdk/modifying-position/src/libs/liquidity.ts#L126-L133 +``` + +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. + +We have also provide two additional parameters: + +- `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%). +- [`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! + +```typescript reference title="Constructing the collect options object" referenceLinkText="View on Github" customStyling +https://github.com/Uniswap/examples/blob/b5e64e3d6c17cb91bc081f1ed17581bbf22024bc/v3-sdk/modifying-position/src/libs/liquidity.ts#L114-L124 +``` + +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: + +```typescript reference title="Getting the calldata and value for the transaction" referenceLinkText="View on Github" customStyling +https://github.com/Uniswap/examples/blob/b5e64e3d6c17cb91bc081f1ed17581bbf22024bc/v3-sdk/modifying-position/src/libs/liquidity.ts#L135-L138 +``` + +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: + +```typescript reference title="Building and submitting the transaction" referenceLinkText="View on Github" customStyling +https://github.com/Uniswap/examples/blob/b5e64e3d6c17cb91bc081f1ed17581bbf22024bc/v3-sdk/modifying-position/src/libs/liquidity.ts#L141-L150 +``` + +After pressing the button, note how the balance of USDC and DAI increases and our position's liquidity drops. + +## Next Steps + +Now that you can mint and modify a position, check out how to [collect fees](./03-collecting-fees.md) from the position! diff --git a/docs/sdk/v3/guides/liquidity/03-collecting-fees.md b/docs/sdk/v3/guides/liquidity/03-collecting-fees.md new file mode 100644 index 0000000000..499b7b9853 --- /dev/null +++ b/docs/sdk/v3/guides/liquidity/03-collecting-fees.md @@ -0,0 +1,68 @@ +--- +id: liquidity-fees +title: Collecting Fees +--- + +## Introduction + +This guide will cover how to collect fees from a liquidity position on the Uniswap V3 protocol. It is based on the [collecting fees code example](https://github.com/Uniswap/examples/tree/main/v3-sdk/collecting-fees), found in the Uniswap code examples [repository](https://github.com/Uniswap/examples). To run this example, check out the examples's [README](https://github.com/Uniswap/examples/blob/main/v3-sdk/collecting-fees/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 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. + +The guide will **cover**: + +1. Setting up our fee collection +2. Submitting our fee collection transaction + +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. + +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) + +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). + +:::note +This guide assumes you are familiar with our [Minting a Position](./01-minting-position.md) guide. A minted position is required to add or remove liquidity from, so the buttons will be disabled until a position is minted. + +Also note that we do not need to give approval to the `NonfungiblePositionManager` to transfer our tokens as we will have already done that when minting our position. +::: + +## Setting up our fee collection + +All of the fee collecting logic can be found in the [`collectFees`](https://github.com/Uniswap/examples/blob/be67e7df220b0a270c9d18bbaab529e017213adf/v3-sdk/collecting-fees/src/example/Example.tsx#L24) function. Notice how the **Collect Fees** button is disabled until a position is minted. This happens because there will be no fees to collect unless there is a position whose liquidity has been traded against. + +To start, 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 reference title="Constructing the CollectOptions" referenceLinkText="View on Github" customStyling +https://github.com/Uniswap/examples/blob/b5e64e3d6c17cb91bc081f1ed17581bbf22024bc/v3-sdk/collecting-fees/src/libs/liquidity.ts#L44-L61 +``` + +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. + +## Submitting our fee collection transaction + +We then get the call parameters for collecting our fees from our `NonfungiblePositionManager` using the constructed `CollectOptions`: + +```typescript reference title="Getting the calldata and value for the transaction" referenceLinkText="View on Github" customStyling +https://github.com/Uniswap/examples/blob/b5e64e3d6c17cb91bc081f1ed17581bbf22024bc/v3-sdk/collecting-fees/src/libs/liquidity.ts#L64-L65 +``` + +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 reference title="Building and submitting the transaction" referenceLinkText="View on Github" customStyling +https://github.com/Uniswap/examples/blob/b5e64e3d6c17cb91bc081f1ed17581bbf22024bc/v3-sdk/collecting-fees/src/libs/liquidity.ts#L68-L77 +``` + +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. + +## Next Steps + +The previous guides detail all the atomic steps needed to create and manage positions. However, these approaches may not use all of your desired currency. To ensure you are using your full funds while minimizing gas prices, check out our guide on [Swapping and Adding Liquidity](./04-swap-and-add-liquidity.md) in a single transaction! diff --git a/docs/sdk/v3/guides/liquidity/04-minting-positions.md b/docs/sdk/v3/guides/liquidity/04-minting-positions.md deleted file mode 100644 index 80af42e922..0000000000 --- a/docs/sdk/v3/guides/liquidity/04-minting-positions.md +++ /dev/null @@ -1,119 +0,0 @@ ---- -id: minting -title: Minting a new Position ---- - -## Overview - -In this guide, you will learn how to mint a new liquidity position, add liquidity, and then remove liquidity. You will learn how to invoke each function with the required parameters necessary in returning the calldata. Because any liquidity related action relies on setting up pool and position instances, you will also need to know [How to Create A Pool](../03-creating-a-pool.md) and how to set up a position instance. - -In summary, the following is what you will learn in this guide: - -1. Set up the pool instance. This follows the same structure as the previous guide. Refer to [Creating a Pool Instance](../03-creating-a-pool.md) for more detail. -2. Create a position. -3. Construct the calldata for minting a position. -4. Construct the calldata for adding to a position. -5. Construct the calldata for removing from a position. - -## Setting up the pool - -First, call the constructor to create an instance of a Uniswap v3 pool. For example, the following code creates a DAI-USDC 0.05% pool. - -```typescript -const DAI_USDC_POOL = new Pool( - DAI, - USDC, - immutables.fee, - state.sqrtPriceX96.toString(), - state.liquidity.toString(), - state.tick -) -``` - -The input parameters are the two token addresses, the fee tier (0.05%), the current pool price, the current liquidity, and the current tick. Reference [the previous guide](../03-creating-a-pool.md) to understand how to retrieve these necessary parameters for setting up instances of existing pools. - -## Creating a Position Instance - -A position represents the price range for a specific pool that LPs choose to provide in. After constructing a pool, set up the position instance: - -```typescript -const position = new Position({ - pool: DAI_USDC_POOL, - liquidity: state.liquidity * 0.0002, - tickLower: nearestUsableTick(state.tick, immutables.tickSpacing) - immutables.tickSpacing * 2, - tickUpper: nearestUsableTick(state.tick, immutables.tickSpacing) + immutables.tickSpacing * 2, -}) -``` - -You can retrieve the variable inputs (like `state.liquidity` and `immutables.tickSpacing`) from fetching the state data as shown in the previous guide. - -After you fetch these variables, call the Position constructor and input the parameters: `pool`, `liquidity`, `tickLower`, and `tickUpper`: - -- The `pool` parameter takes in the pool instance from step 1. -- The `liquidity` parameter specifies how much liquidity to add. This examples adds a fraction of the current liquidity in the pool: 0.0002 times the amount of current liquidity. In production, this could be a parameter inputted into the function adjustable by the end-user. -- The `tickLower` and `tickUpper` parameters specify the price range at which to provide liquidity. This example calls `nearestUsableTick` to get the current useable tick and adjust the lower parameter to be below it by 2 _ `tickSpacing` and the upper to be above it by 2 _ `tickSpacing`. This guarantees that the provided liquidity is "in range", meaning it will be earning fees upon minting this position. - -## Minting the Position - -To create the calldata for minting a position, use the function defined in the SDK called `addCallParameters` which takes in a position (of type `Position`) and an option (of type `AddLiquidityOptions`). `AddLiquidityOptions` are either `MintOptions` for minting a new position or `IncreaseOptions` for adding liquidity to an existing position. Below, the example outlines the parameters needed to mint a new position and passes in a valid `MintOptions` struct to the SDK function `addCallParameters`. - -`MintOptions` are constructed from `CommonAddLiquidityOptions & MintSpecificOptions`. To see all potential parameters that can be inputted here is the reference for [`CommonAddLiquidityOptions`](https://github.com/Uniswap/v3-sdk/blob/08a7c050cba00377843497030f502c05982b1c43/src/nonfungiblePositionManager.ts#L47): - -```typescript -export interface CommonAddLiquidityOptions { - /** - * How much the pool price is allowed to move. - */ - slippageTolerance: Percent - - /** - * When the transaction expires, in epoch seconds. - */ - deadline: BigintIsh - - /** - * Whether to spend ether. If true, one of the pool tokens must be WETH, by default false - */ - useNative?: NativeCurrency - - /** - * The optional permit parameters for spending token0 - */ - token0Permit?: PermitOptions - - /** - * The optional permit parameters for spending token1 - */ - token1Permit?: PermitOptions -} -``` - -and here is the reference for [`MintSpecificOptions`](https://github.com/Uniswap/v3-sdk/blob/08a7c050cba00377843497030f502c05982b1c43/src/nonfungiblePositionManager.ts#L25): - -```typescript -export interface MintSpecificOptions { - /** - * The account that should receive the minted NFT. - */ - recipient: string - - /** - * Creates pool if not initialized before mint. - */ - createPool?: boolean -} -``` - -You can omit the parameters that are not required. In order to create the most basic valid `MintOptions` struct just set the `slippageTolerance`, `deadline`, and `recipient` which is constructed below: - -```typescript -const deadline = block.timestamp + 200 - -const { calldata, value } = NonfungiblePositionManager.addCallParameters(position, { - slippageTolerance: new Percent(50, 10_000), - recipient: sender, - deadline: deadline, -}) -``` - -where `slippageTolerance` is set to 0.005%, `recipient` is set to the sender address which is an input to this function, and `deadline` is set to the current `block.timestamp` plus some arbitrary amount, for this example. The parameter `slippageTolerance` refers to the percentage that the price can change for the transaction to still succeed. If a price slips beyond the percentage specified, the transaction will not go through. Set `recipient` to the address that will own the newly minted NFT position. Set `deadline` to the timebound at which this transaction can still be submitted. diff --git a/docs/sdk/v3/guides/liquidity/04-swap-and-add-liquidity.md b/docs/sdk/v3/guides/liquidity/04-swap-and-add-liquidity.md new file mode 100644 index 0000000000..3b11f3d3a8 --- /dev/null +++ b/docs/sdk/v3/guides/liquidity/04-swap-and-add-liquidity.md @@ -0,0 +1,115 @@ +--- +id: swap-and-add +title: Swapping and Adding Liquidity +--- + +## Introduction + +This guide will cover how to execute a swap-and-add operation in a single atomic transaction. It is based on the [swap-and-add example](https://github.com/Uniswap/examples/tree/main/v3-sdk/swap-and-add-liquidity), found in the Uniswap code examples [repository](https://github.com/Uniswap/examples). To run this example, check out the examples's [README](https://github.com/Uniswap/examples/tree/main/v3-sdk/swap-and-add-liquidity) 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! +::: + +When adding liquidity to a Uniswap v3 pool, you must provide two assets in a particular ratio. In many cases, your contract or the user's wallet hold a different ratio of those two assets. In order to deposit 100% of your assets, you must first swap your assets to the optimal ratio and then add liquidity. + +However, the swap may shift the balance of the pool and thus change the optimal ratio. To avoid that, we can execute this swap-and-add liquidity operation in an atomic fashion, using a router. The inputs to our guide are the **two tokens** that we are pooling for, the **amount** of each token we are pooling for, the **amount** of each token to swap-and-add, and the Pool **fee**. + +The guide will **cover**: + +1. Setup a router instance +2. Configuring our ratio calculation +3. Calculating our currency ratio +4. Constructing and executing our swap-and-add transaction + +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. + +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) + +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). + +:::note +This guide assumes you are familiar with our [Minting a Position](./01-minting-position.md) guide. A minted position is required to add or remove liquidity from, so the buttons will be disabled until a position is minted. + +Also note that we do not need to give approval to the `NonfungiblePositionManager` to transfer our tokens as we will have already done that when minting our position. +::: + +## Setup a router instance + +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 reference title="Approve SwapRouter to spend our tokens" referenceLinkText="View on Github" customStyling +https://github.com/Uniswap/examples/blob/ec48bb845402419fa6e613cb26512a76d864afa5/v3-sdk/swap-and-add-liquidity/src/libs/liquidity.ts#L58-L66 +``` + +The 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: + +```typescript reference title="Creating a router instance" referenceLinkText="View on Github" customStyling +https://github.com/Uniswap/examples/blob/b5e64e3d6c17cb91bc081f1ed17581bbf22024bc/v3-sdk/swap-and-add-liquidity/src/libs/liquidity.ts#L57 +``` + +For a more detailed example, check out our [routing guide](../04-routing.md). + +## Configuring our ratio calculation + +Having created the router, we now need to construct the parameters required to make a call to its `routeToRatio` function, which will ensure the ratio of currency used matches the pool's required ratio to add our total liquidity. This will require the following parameters: + +The first two parameters are the currency amounts we use as input to the `routeToRatio` algorithm: + +```typescript reference title="Constructing the two CurrencyAmounts" referenceLinkText="View on Github" customStyling +https://github.com/Uniswap/examples/blob/ec48bb845402419fa6e613cb26512a76d864afa5/v3-sdk/swap-and-add-liquidity/src/libs/liquidity.ts#L78-L92 +``` + +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 reference title="Constructing the position object" referenceLinkText="View on Github" customStyling +https://github.com/Uniswap/examples/blob/b5e64e3d6c17cb91bc081f1ed17581bbf22024bc/v3-sdk/swap-and-add-liquidity/src/libs/liquidity.ts#L75-L78 +``` + +We then need to create an instance of `SwapAndAddConfig` which will set additional configuration parameters for the `routeToRatio` algorithm: + +- `ratioErrorTolerance` determines the margin of error the resulting ratio can have from the optimal ratio. +- `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 reference title="Constructing SwapAndAddConfig" referenceLinkText="View on Github" customStyling +https://github.com/Uniswap/examples/blob/b5e64e3d6c17cb91bc081f1ed17581bbf22024bc/v3-sdk/swap-and-add-liquidity/src/libs/liquidity.ts#L80-L83 +``` + +Finally, we will create an instance of `SwapAndAddOptions` to configure which position we are adding liquidity to and our defined swapping parameters in two different objects: + +- **`swapConfig`** configures the `recipient` of leftover dust from swap, `slippageTolerance` and a `deadline` for the swap. +- **`addLiquidityOptions`** must contain a `tokenId` to add to an existing position + +```typescript reference title="Constructing SwapAndAddOptions" referenceLinkText="View on Github" customStyling +https://github.com/Uniswap/examples/blob/b5e64e3d6c17cb91bc081f1ed17581bbf22024bc/v3-sdk/swap-and-add-liquidity/src/libs/liquidity.ts#L85-L95 +``` + +## Calculating our currency ratio + +Having constructed all the parameters we need to call `routeToRatio`, we can now make the call to the function: + +```typescript reference title="Making the call to routeToRatio" referenceLinkText="View on Github" customStyling +https://github.com/Uniswap/examples/blob/b5e64e3d6c17cb91bc081f1ed17581bbf22024bc/v3-sdk/swap-and-add-liquidity/src/libs/liquidity.ts#L97-L103 +``` + +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 reference title="Checking that a route was found" referenceLinkText="View on Github" customStyling +https://github.com/Uniswap/examples/blob/b5e64e3d6c17cb91bc081f1ed17581bbf22024bc/v3-sdk/swap-and-add-liquidity/src/libs/liquidity.ts#L105-L110 +``` + +In case a route was not found, we return from the function a `Failed` state for the transaction. + +## Constructing and executing our swap-and-add transaction + +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 reference title="Constructing and sending the transaction" referenceLinkText="View on Github" customStyling +https://github.com/Uniswap/examples/blob/b5e64e3d6c17cb91bc081f1ed17581bbf22024bc/v3-sdk/swap-and-add-liquidity/src/libs/liquidity.ts#L112-L120 +``` + +If the transaction was successful, our swap-and-add will be completed! We should see our input token balances decrease and our position balance should be increased accordingly. diff --git a/docs/sdk/v3/guides/liquidity/05-adding-liquidity.md b/docs/sdk/v3/guides/liquidity/05-adding-liquidity.md deleted file mode 100644 index 898133d9c0..0000000000 --- a/docs/sdk/v3/guides/liquidity/05-adding-liquidity.md +++ /dev/null @@ -1,56 +0,0 @@ ---- -id: adding -title: Adding Liquidity ---- - -## Adding Liquidity to the Position - -Constructing the calldata for adding liquidity uses the same function call to `addCallParameters`, which takes in a `Position` and an options field of type `AddLiquidityOptions`. Since the goal of this example is to add liquidity to an already existing position, construct `IncreaseOptions` instead of `MintOptions`. `IncreaseOptions` types are defined by the interfaces [`CommonAddLiquidityOptions`](https://github.com/Uniswap/v3-sdk/blob/08a7c050cba00377843497030f502c05982b1c43/src/nonfungiblePositionManager.ts#L47) and [`IncreaseSpecificOptions`](https://github.com/Uniswap/v3-sdk/blob/08a7c050cba00377843497030f502c05982b1c43/src/nonfungiblePositionManager.ts#L37). - -```typescript -export interface CommonAddLiquidityOptions { - /** - * How much the pool price is allowed to move. - */ - slippageTolerance: Percent - - /** - * When the transaction expires, in epoch seconds. - */ - deadline: BigintIsh - - /** - * Whether to spend ether. If true, one of the pool tokens must be WETH, by default false - */ - useNative?: NativeCurrency - - /** - * The optional permit parameters for spending token0 - */ - token0Permit?: PermitOptions - - /** - * The optional permit parameters for spending token1 - */ - token1Permit?: PermitOptions -} - -export interface IncreaseSpecificOptions { - /** - * Indicates the ID of the position to increase liquidity for. - */ - tokenId: BigintIsh -} -``` - -To construct the `IncreaseOptions` struct, specify `slippageTolerance`, `deadline`, and `tokenId`. The `tokenId` is the unique identifier of the position (or more specifically, the ERC721 that represents a position). In this example, the `tokenId` is naively set to 1, but in production, each `tokenId` is unique and can be fetched on chain. - -Use [the functions](https://docs.openzeppelin.com/contracts/2.x/api/token/erc721#ERC721Enumerable-tokenOfOwnerByIndex-address-uint256-) `tokensByIndex` or `tokenOfOwnerByIndex` to fetch `tokenId`s for ERC721s. - -```typescript -const { calldata, value } = NonfungiblePositionManager.addCallParameters(position, { - slippageTolerance: new Percent(50, 10_000), - deadline: deadline, - tokenId: 1, -}) -``` diff --git a/docs/sdk/v3/guides/liquidity/06-removing-liquidity.md b/docs/sdk/v3/guides/liquidity/06-removing-liquidity.md deleted file mode 100644 index 9a2f0e651a..0000000000 --- a/docs/sdk/v3/guides/liquidity/06-removing-liquidity.md +++ /dev/null @@ -1,105 +0,0 @@ ---- -id: removing -title: Removing Liquidity ---- - -## Removing Liquidity from a Position - -Use the function `removeCallParameters` with the `position` instantiated earlier and a `RemoveLiquidityOptions` interface. The reference for [`RemoveLiquidityOptions`](https://github.com/Uniswap/v3-sdk/blob/08a7c050cba00377843497030f502c05982b1c43/src/nonfungiblePositionManager.ts#L138) is: - -```typescript -/** - * Options for producing the calldata to exit a position. - */ -export interface RemoveLiquidityOptions { - /** - * The ID of the token to exit - */ - tokenId: BigintIsh - - /** - * The percentage of position liquidity to exit. - */ - liquidityPercentage: Percent - - /** - * How much the pool price is allowed to move. - */ - slippageTolerance: Percent - - /** - * When the transaction expires, in epoch seconds. - */ - deadline: BigintIsh - - /** - * Whether the NFT should be burned if the entire position is being exited, by default false. - */ - burnToken?: boolean - - /** - * The optional permit of the token ID being exited, in case the exit transaction is being sent by an account that does not own the NFT - */ - permit?: NFTPermitOptions - - /** - * Parameters to be passed on to collect - */ - collectOptions: Omit -} -``` - -To remove liquidity from a position, set the parameters for `tokenId`, `liquidityPercentage`, `slippageTolerance`, `deadline`, and `collectOptions`. - -The reference for [`CollectOptions`](https://github.com/Uniswap/v3-sdk/blob/08a7c050cba00377843497030f502c05982b1c43/src/nonfungiblePositionManager.ts#L105) is - -```typescript -export interface CollectOptions { - /** - * Indicates the ID of the position to collect for. - */ - tokenId: BigintIsh - - /** - * Expected value of tokensOwed0, including as-of-yet-unaccounted-for fees/liquidity value to be burned - */ - expectedCurrencyOwed0: CurrencyAmount - - /** - * Expected value of tokensOwed1, including as-of-yet-unaccounted-for fees/liquidity value to be burned - */ - expectedCurrencyOwed1: CurrencyAmount - - /** - * The account that should receive the tokens. - */ - recipient: string -} -``` - -The parameter inputs are outlined below for `RemoveLiquidityOptions`: - -- the`tokenId` is again set to 1 for this example, but refers to the unique id of the position nft. -- `liquidityPercentage` represents the amount of liquidity to remove. Adjust this parameter based on how much liquidity you want to remove. Set it to `Percent(1)` to remove all liquidity. -- This example sets `slipppageTolerance` and `deadline` to the same values as the previous examples. -- `collectOptions` defines the tokens to be collected and are passed onto `collect`. This example collects the maximum amount of DAI and USDC. - -When collecting fees in ETH, you must precompute the fees owed to protect against reentrancy attacks. In order to set a safety check, set the minimum fees owed in `expectedCurrencyOwed0` and `expectedCurrencyOwed1`. To calculate this, quote the `collect` function and store the amounts. The interface does similar behavior [here](https://github.com/Uniswap/uniswap-interface/blob/eff512deb8f0ab832eb8d1834f6d1a20219257d0/src/hooks/useV3PositionFees.ts#L32). For this example, it is not necessary to set a minimum amount of fees to collect because there is no concern with reentrancy for tokens (DAI/USDC). - -```typescript -const { calldata, value } = NonfungiblePositionManager.removeCallParameters(position, { - tokenId: 1, - liquidityPercentage: new Percent(1), - slippageTolerance: new Percent(50, 10_000), - deadline: deadline, - collectOptions: { - expectedCurrencyOwed0: CurrencyAmount.fromRawAmount(DAI, 0), - expectedCurrencyOwed1: CurrencyAmount.fromRawAmount(USDC, 0), - recipient: sender, - }, -}) -``` - -## The example code - -You now know how to mint a liquidity position, add liquidity, and remove liquidity. Here's the [full example code](https://github.com/Uniswap/uniswap-docs/blob/main/examples/sdk/AddAndRemoveLiquidity.tsx) for reference. diff --git a/docs/sdk/v3/guides/liquidity/07-swap-and-add.md b/docs/sdk/v3/guides/liquidity/07-swap-and-add.md deleted file mode 100644 index 9413b35d37..0000000000 --- a/docs/sdk/v3/guides/liquidity/07-swap-and-add.md +++ /dev/null @@ -1,167 +0,0 @@ ---- -id: swap-and-add -title: Swap and Add Liquidity Atomically ---- - -# Swap and Add Liquidity Atomically - -When adding liquidity to a Uniswap v3 pool, you must provide two assets in a particular ratio. In many cases, your contract or the user's wallet hold a different ratio of those two assets. In order to deposit 100% of your assets, you must first **swap** your assets to the optimal ratio and then **add liquidity**. However, the swap may shift the balance of the pool and thus change the optimal ratio! - -This guide will teach you how to execute this swap-and-add operation in a single atomic transaction. First, you will use the Auto Router to fetch calldata to swap to the optimal ratio and add liquidity. Then you will submit the transaction to the on-chain router contract `SwapRouter02.sol`. - -## Initializing the Alpha Router - -First, [import](../06-auto-router.md#importing-the-package) and [initialize](../06-auto-router.md#initializing-the-alpharouter) an instance of the Alpha Router. If you are using a different network, be sure to specify the right `chainId` parameter. - -```bash -npm install @uniswap/smart-order-router -``` - -```typescript -import { AlphaRouter } from '@uniswap/smart-order-router' -const router = new AlphaRouter({ chainId: 1, provider: web3Provider }) -``` - -## Fetching calldata from `routeToRatio` - -Now call the `routeToRatio` method on the Auto Router. This function will return all the calldata you need to submit an atomic swap-and-add transaction. - -#### Parameters - -| Name | Requirement | Description | -| ------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `token0Balance` | required | The initial balance of `token0` that you wish to swap-and-add, where `token0` is the `token0` in your target liquidity pool. | -| `token1Balance` | required | The initial balance of `token1` that you wish to swap-and-add, where `token1` is the `token1` in your target liquidity pool. | -| `position` | required | A [Position](../liquidity/04-minting-positions.md) object that contains the details of the position for which to add liquidity. The position liquidity can be set to `1`, since liquidity is still unknown and will be set inside the call to `routeToRatio`. | -| `swapAndAddConfig` | required | A [swapAndAddConfig](https://github.com/Uniswap/smart-order-router/blob/b26ffdc978ab1076c817392ab20ed2df325daf7a/src/routers/router.ts#L123) sets configurations for the `routeToRatio` algorithm. `ratioErrorTolerance` determines the margin of error the resulting ratio can have from the optimal ratio. `maxIterations` determines the maximum times the algorithm will iterate to find a ratio within error tolerance. If `maxIterations` is exceeded, an error is returned. | -| `swapAndAddOptions` | optional | If [swapAndAddOptions](https://github.com/Uniswap/smart-order-router/blob/b26ffdc978ab1076c817392ab20ed2df325daf7a/src/routers/router.ts#L130) is included, `routeToRatio` will return the calldata for executing the atomic swap-and-add. These options contain `swapConfig` and `addLiquidityOptions`. `swapConfig` configures to set a recipient of leftover dust from swap, slippageTolerance, deadline, inputTokenPermit and outputTokenPermit. `addLiquidityOptions` must contain a `tokenId` to add to an existing position, or `recipient` to mint a new one. It also includes a slippage tolerance and deadline for adding liquidity. | -| `routingConfig` | optional | Optional advanced config for tuning the performance of the routing algorithm. View the [AlphaRouterConfig](https://github.com/Uniswap/smart-order-router/blob/b26ffdc978ab1076c817392ab20ed2df325daf7a/src/routers/alpha-router/alpha-router.ts#L222) object for these optional configuration parameters. | - -`token0Balance` [required] - -- The initial balance of token0 that you wish to swap-and-add, where token0 is the token0 in your target liquidity pool. - -`token1Balance` [required] - -- The initial balance of token1 that you wish to swap-and-add, where token1 is the token1 in your target liquidity pool. - -`position` [required] - -- A [position object](../liquidity/04-minting-positions.md) that contains the details of the position for which to add liquidity. The position liquidity can be set to 1, since liquidity is still unknown and will be set inside the call to `routeToRatio`. - -`swapAndAddConfig` [required] - -- A [swapAndAddConfig](https://github.com/Uniswap/smart-order-router/blob/b26ffdc978ab1076c817392ab20ed2df325daf7a/src/routers/router.ts#L123) sets configurations for the routeToRatio algorithm. `ratioErrorTolerance` determines the margin of error the resulting ratio can have from the optimal ratio. `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. - -`swapAndAddOptions` [optional] - -- If [swapAndAddOptions](https://github.com/Uniswap/smart-order-router/blob/b26ffdc978ab1076c817392ab20ed2df325daf7a/src/routers/router.ts#L130) is included, routeToRatio will return the calldata for executing the atomic swap-and-add. These options contain `swapConfig` and `addLiquidityOptions`. `swapConfig` configures to set a recipient of leftover dust from swap, slippageTolerance, deadline, inputTokenPermit and outputTokenPermit. `addLiquidityOptions` must contain a `tokenId` to add to an existing position, or `recipient` to mint a new one. It also includes a slippage tolerance and deadline for adding liquidity. - -`routingConfig` [optional] - -- Optional config for tuning the performance of the routing algorithm. View the [AlphaRouterConfig object](https://github.com/Uniswap/smart-order-router/blob/b26ffdc978ab1076c817392ab20ed2df325daf7a/src/routers/alpha-router/alpha-router.ts#L222) for these optional configuration parameters. - -Here is a complete example that initializes these parameters and then calls `routeToRatio` - -```typescript -const USDC = new Token( - ChainId.MAINNET, - '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', - 6, - 'USDC', - 'USD//C' -); - -const WETH = new Token( - 1, - '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', - 18, - 'WETH', - 'Wrapped Ether' -); - -const token0Balance = CurrencyAmount.fromRawAmount(USDC, '5000000000') -const token1Balance = CurrencyAmount.fromRawAmount(WETH, '0') -const pool = new Pool( - USDC, - WETH, - 3000, - '1283723400872544054280619964098219', - '8390320113764730804' , - '193868' -); - - -const routeToRatioResponse = await router.routeToRatio( - token0Balance, - token1Balance, - position: new Position({ - pool, - tickLower: -60, - tickUpper: 60, - liquidity: 1, - }), - swapAndAddConfig: { - ratioErrorTolerance: new Fraction(1, 100), - maxIterations: 6, - }, - swapAndAddOptions: { - swapConfig: { - recipient: , - slippage: new Percent(5, 100), - deadline: 100 - }, - addLiquidityOptions: { - tokenId: 10, - } - } -); -``` - -## Submitting a Transaction - -The `routeToRatio` function returns a `SwapToRatioResponse` object. If a route was found successfully, this object will have two fields: - -- `status` will be set to `success` -- `result` will contain the `SwapToRatioRoute` object - -The `SwapToRatioRoute` object will have the properties listed out in the type below: - -```typescript -type SwapToRatioRoute = { - quote: CurrencyAmount - quoteGasAdjusted: CurrencyAmount - optimalRatio: Fraction - postSwapTargetPool: Pool - estimatedGasUsed: BigNumber - estimatedGasUsedQuoteToken: CurrencyAmount - estimatedGasUsedUSD: CurrencyAmount - gasPriceWei: BigNumber - trade: Trade - route: RouteWithValidQuote[] - blockNumber: BigNumber - methodParameters?: MethodParameters -} -``` - -Use the quoted gas price defined as `gasPriceWei` in the above `SwapToRatioRoute` object above and generated call data as inputs for the transaction, as done below: - -```typescript -import { SwapToRatioStatus } from "@uniswap/smart-order-router"; - -const V3_SWAP_ROUTER_ADDRESS = "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45"; -const MY_ADDRESS = "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B"; - -if (routeToRatioResponse.status == SwapToRatioStatus.SUCCESS) { - const route = routeToRatioResponse.result - const transaction = { - data: route.methodParameters.calldata, - to: V3_SWAP_ROUTER_ADDRESS, - value: BigNumber.from(route.methodParameters.value), - from: MY_ADDRESS, - gasPrice: BigNumber.from(route.gasPriceWei), - }; -} - -await web3Provider.sendTransaction(transaction); -``` diff --git a/docs/sdk/v3/guides/liquidity/_category_.json b/docs/sdk/v3/guides/liquidity/_category_.json index f9a4a1c91d..1a66cbbe2c 100644 --- a/docs/sdk/v3/guides/liquidity/_category_.json +++ b/docs/sdk/v3/guides/liquidity/_category_.json @@ -1,5 +1,5 @@ { - "label": "Interacting with Pool Liquidity", - "position": 6, + "label": "Pooling Liquidity", + "position": 5, "collapsed": true } diff --git a/docs/sdk/v3/overview.md b/docs/sdk/v3/overview.md index f2b5518a82..6759fc15c6 100644 --- a/docs/sdk/v3/overview.md +++ b/docs/sdk/v3/overview.md @@ -7,14 +7,22 @@ title: Overview > **Welcome to the V3 Uniswap SDK!** -The Uniswap V3 SDK provides abstractions to assist you with interacting with the Uniswap V3 smart contracts in a Typescript/Javascript environment. It makes uses of the [**Core SDK**](../core//overview.md) to gain access to abstractions that are common amongst the Uniswap SDKs. +The Uniswap V3 SDK provides abstractions to assist you with interacting with the Uniswap V3 smart contracts in a Typescript/Javascript environment (e.g. websites, node scripts). It makes uses of the [**Core SDK**](../core/overview.md) to gain access to abstractions that are common amongst the Uniswap SDKs. With the SDK, you can manipulate data that has been queried from the [EVM](https://ethereum.org/en/developers/docs/evm/) using libraries that assist with needs such as data modeling, protection from rounding errors, and compile time enforced typing. -These docs will help you better understand how to use the SDK and integrate it into your application. +To begin, we recommend looking at our [**Guides**](./guides/01-background.md) which include [runnable examples](https://github.com/Uniswap/examples/tree/main/v3-sdk) and walkthroughs of core usages. These guides will help you better understand how to use the SDK and integrate it into your application. -To begin, we recommend looking at the [**Guides**](./guides/01-quick-start) and for deeper reference see the [**Technical Reference**](./reference/overview). +For complete documentation of the SDK's offerings, see the [**Technical Reference**](./reference/overview.md). +## Installation -# Resources +To interact with the V3 SDK we recommend installing though npm: + +```bash +npm i --save @uniswap/v3-sdk +npm i --save @uniswap/sdk-core +``` + +## Developer Links - [**V3 SDK Github Repo**](https://github.com/Uniswap/v3-sdk) - [**Core SDK Github Repo**](https://github.com/Uniswap/sdk-core) diff --git a/docusaurus.config.js b/docusaurus.config.js index d9fefe527b..f74cc78b67 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -230,6 +230,7 @@ module.exports = { }, ], plugins: [ + ['@saucelabs/theme-github-codeblock', {}], [ '@docusaurus/plugin-content-docs', { diff --git a/package.json b/package.json index c6df076260..bf5ed74f5d 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "@emotion/react": "^11.4.0", "@emotion/styled": "^11.3.0", "@mdx-js/react": "^1.6.21", + "@saucelabs/theme-github-codeblock": "https://github.com/Uniswap/docusaurus-theme-github-codeblock.git#f55fe4caed9fce974c9b82bf2164f76cc5509eef", "@types/react": "^17.0.11", "@uniswap/analytics": "1.1.0", "@uniswap/analytics-events": "2.0.0", @@ -58,6 +59,7 @@ "dotenv": "^16.0.3", "eslint": "^7.11.0", "eslint-config-prettier": "^6.11.0", + "eslint-plugin-markdown": "^3.0.0", "eslint-plugin-prettier": "^3.1.3", "eslint-plugin-react": "^7.21.5", "eslint-plugin-react-hooks": "^4.6.0", diff --git a/src/css/custom.css b/src/css/custom.css index e00c0580ec..573f9115d6 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -508,6 +508,13 @@ html[data-theme='dark'] .DocSearch-Hit a { } } +.github-codeblock-reference-link { + margin-top: -16px; + padding-bottom: 13px; + font-size: 0.7em; + text-align: right; +} + /** * Copyright (c) Facebook, Inc. and its affiliates. * diff --git a/yarn.lock b/yarn.lock index 993abc7012..563b2f35f8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2601,6 +2601,10 @@ resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.11.tgz#aeb16f50649a91af79dbe36574b66d0f9e4d9f71" integrity sha512-3NsZsJIA/22P3QUyrEDNA2D133H4j224twJrdipXN38dpnIOzAbUDtOwkcJ5pXmn75w7LSQDjA4tO9dm1XlqlA== +"@saucelabs/theme-github-codeblock@https://github.com/Uniswap/docusaurus-theme-github-codeblock.git#f55fe4caed9fce974c9b82bf2164f76cc5509eef": + version "0.2.0" + resolved "https://github.com/Uniswap/docusaurus-theme-github-codeblock.git#f55fe4caed9fce974c9b82bf2164f76cc5509eef" + "@sideway/address@^4.1.3": version "4.1.3" resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.3.tgz#d93cce5d45c5daec92ad76db492cc2ee3c64ab27" @@ -4541,7 +4545,7 @@ debug@^3.1.1: dependencies: ms "^2.1.1" -debug@^4.0.1, debug@^4.3.1, debug@^4.3.4: +debug@^4.0.0, debug@^4.0.1, debug@^4.3.1, debug@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -5027,6 +5031,13 @@ eslint-config-prettier@^6.11.0: dependencies: get-stdin "^6.0.0" +eslint-plugin-markdown@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-markdown/-/eslint-plugin-markdown-3.0.0.tgz#69a63ab3445076a3c2eb6fce6f5114785b19d318" + integrity sha512-hRs5RUJGbeHDLfS7ELanT0e29Ocyssf/7kBM+p7KluY5AwngGkDf8Oyu4658/NZSGTTq05FZeWbkxXtbVyHPwg== + dependencies: + mdast-util-from-markdown "^0.8.5" + eslint-plugin-prettier@^3.1.3: version "3.4.1" resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.1.tgz#e9ddb200efb6f3d05ffe83b1665a716af4a387e5" @@ -7011,6 +7022,17 @@ mdast-util-definitions@^4.0.0: dependencies: unist-util-visit "^2.0.0" +mdast-util-from-markdown@^0.8.5: + version "0.8.5" + resolved "https://registry.yarnpkg.com/mdast-util-from-markdown/-/mdast-util-from-markdown-0.8.5.tgz#d1ef2ca42bc377ecb0463a987910dae89bd9a28c" + integrity sha512-2hkTXtYYnr+NubD/g6KGBS/0mFmBcifAsI0yIWRiRo0PjVs6SSOSOdtzbp6kSGnShDN6G5aWZpKQ2lWRy27mWQ== + dependencies: + "@types/mdast" "^3.0.0" + mdast-util-to-string "^2.0.0" + micromark "~2.11.0" + parse-entities "^2.0.0" + unist-util-stringify-position "^2.0.0" + mdast-util-to-hast@10.0.1: version "10.0.1" resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-10.0.1.tgz#0cfc82089494c52d46eb0e3edb7a4eb2aea021eb" @@ -7072,6 +7094,14 @@ methods@~1.1.2: resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= +micromark@~2.11.0: + version "2.11.4" + resolved "https://registry.yarnpkg.com/micromark/-/micromark-2.11.4.tgz#d13436138eea826383e822449c9a5c50ee44665a" + integrity sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA== + dependencies: + debug "^4.0.0" + parse-entities "^2.0.0" + micromatch@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259"