-
Notifications
You must be signed in to change notification settings - Fork 112
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
SOR pages #15
SOR pages #15
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,76 @@ | ||
# Use the Smart Order Router | ||
# Use the Smart Order Router | ||
|
||
The Smart Order Router (SOR) is a library for order routing optimzation across Balancer pools for best price execution. It is a component of the SDK but also exists as a standalone package for integrators who only need swap routing logic. The example code and reference shown in the docs will use the SDK, but it should be straightforward to infer the changes required for the standalone SOR package. | ||
|
||
[![npm version](https://img.shields.io/npm/v/@balancer-labs/sor/latest.svg)](https://www.npmjs.com/package/@balancer-labs/sor/v/latest) | ||
|
||
The below docs assume the SDK is installed and initialized: | ||
|
||
```javascript | ||
import { BalancerSDK } from '@balancer-labs/sdk' | ||
|
||
const balancer = new BalancerSDK({ | ||
network: 1, // Mainnet | ||
rpcUrl: 'https://rpc.ankr.com/eth' // rpc endpoint | ||
}) | ||
|
||
const { sor } = balancer // SOR module | ||
``` | ||
|
||
The general flow for finding a trade route using SOR (Smart Order Router) includes the following steps: | ||
|
||
### Step 1. Pool data fetching | ||
The SOR requires information about the available pools and their current status, including the prices of tokens and the liquidity of the pools. It is essential to use the SOR based on up-to-date information, as outdated information can lead to incorrect slippage predictions and potentially result in failed swaps. | ||
```javascript | ||
await sor.fetchPools() | ||
``` | ||
|
||
### Step 2. Route proposal | ||
The SOR determines the optimal trade route based on the available pool data and the desired trade, taking into account factors such as trade size, gas costs, and slippage. When searching for swaps, developers have to choose between two types of swaps: | ||
|
||
* `SwapTypes.SwapExactIn` where the amount of tokens in (sent to the Pool) is known or | ||
* `SwapTypes.SwapExactOut` where the amount of tokens out (received from the Pool) is known. | ||
|
||
|
||
```typescript | ||
sor.getSwaps( | ||
tokenIn: string, // address of tokenIn | ||
tokenOut: string, // address of tokenOut | ||
swapType: SwapTypes, // SwapExactIn or SwapExactOut - see above | ||
swapAmount: string, // amountIn or amountOut depending on the `swapType`; number as a string with 18 decimals | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not 100% clear if this should be wei or human scale. For these I would always add a comment like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. True, it's always hard because of multiple ways it's used across the SDK. Would be good to make it consistent, maybe ethers v6 with BigInt will be a good excuse for the refactoring? |
||
swapOptions: { | ||
gasPrice: string // current gas price; number as a string with 18 decimals | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similar thing here where gas prices are commonly quoted in gwei but not entirely clear what unit type to enter here. Comment something like |
||
swapGas: string | ||
timestamp: number | ||
maxPools: number // number of pool included in path, above 4 is usually a high gas price | ||
poolTypeFilter: PoolFilter | ||
forceRefresh: boolean | ||
}, | ||
useBpts: boolean // include join / exits in the path. transaction needs to be sent via Relayer contract | ||
): Promise<SwapInfo>; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Might be useful to show the SwapInfo type info There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And then maybe along with that have a dropdown section (see deprecated section on deployment addresses docs pages for example) with a JSON stringified example of a returned SwapInfo object. Might be overkill, but I think personally I'd find that useful to see. Your call There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agree on having responses, it's always good to see what you can expect. |
||
``` | ||
The SOR returns the trade information, including the optimal trade route, the expected slippage and gas cost, and the estimated trade outcome as `swapInfo`. | ||
|
||
### Step 3. Transaction encoding | ||
To execute the trade, the returned `swapInfo` must be encoded into a transaction, which requires the following information: | ||
```javascript | ||
const tx = swaps.buildSwap({ | ||
userAddress: '0xstring', // user address | ||
swapInfo, // result from the previous step | ||
kind: SwapType.SwapExactIn, // or SwapExactOut | ||
deadline, // BigNumber block timestamp | ||
maxSlippage, // [bps], eg: 1 == 0.01%, 100 == 1% | ||
}) | ||
``` | ||
|
||
TODO: describe useBpts case | ||
|
||
### Step 4. Broadcast transaction | ||
```javascript | ||
const signer = balancer.provider.getSigner() | ||
await signer.sendTransaction({ | ||
to: tx.to, | ||
data: tx.data, | ||
value: tx.value | ||
}) | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,19 +2,64 @@ | |
order: 1 | ||
--- | ||
# Smart Order Router | ||
The Smart Order Router (SOR) is a tool designed to optimize order routing across Balancer pools for the best possible price execution. It can be accessed through the SDK, but it is also available as a standalone package for those who only need the swap routing functionality. This example code will utilize the SDK, but it is easy to understand how to make changes for the standalone SOR package. | ||
|
||
The Smart Order Router (SOR) is a library for order routing optimzation across Balancer pools for best price execution. It is a component of the SDK but also exists as a standalone package for integrators who only need swap routing logic. The example code and reference shown in the docs will use the SDK, but it should be straightforward to infer the changes required for the standalone SOR package. | ||
The below docs assume the SDK is installed and initialized. | ||
|
||
[![npm version](https://img.shields.io/npm/v/@balancer-labs/sor/latest.svg)](https://www.npmjs.com/package/@balancer-labs/sor/v/latest) | ||
```javascript | ||
import { BalancerSDK } from '@balancer-labs/sdk' | ||
|
||
::: details Preconfig | ||
The below docs assume the SDK is installed and initialized. See [Intro](../overview/README.md) for instructions | ||
::: | ||
const balancer = new BalancerSDK({ | ||
network: 1, // Mainnet | ||
rpcUrl: 'https://rpc.ankr.com/eth' // rpc endpoint | ||
}) | ||
|
||
## SOR Basics | ||
const { swaps } = balancer // Swaps module is abstracting SOR | ||
``` | ||
|
||
TODO: explain the general flow of a SOR request, including pool data fetching, route proposal, route optimization, return data format, and what's required to convert that to a tx. And details around the decision points a developer needs to make for things like `useBpts`, if the relayer is required, etc. | ||
The general flow for finding a trade route using SOR (Smart Order Router) includes the following steps: | ||
|
||
```typescript | ||
### Step 1. Pool data fetching | ||
The SOR requires information about the available pools and their current status, including the prices of tokens and the liquidity of the pools. It is essential to use the SOR based on up-to-date information, as outdated information can lead to incorrect slippage predictions and potentially result in failed swaps. | ||
```javascript | ||
await swaps.fetchPools() | ||
``` | ||
|
||
``` | ||
### Step 2. Route proposal | ||
The SOR determines the optimal trade route based on the available pool data and the desired trade, taking into account factors such as trade size, gas costs, and slippage. When searching for swaps, developers have to choose between two types of swaps: | ||
|
||
* `findRouteGivenIn`, where the amount of tokens being sent to the pool is known, or | ||
* `findRouteGivenOut`, where the amount of tokens received from the pool is known. | ||
|
||
```javascript | ||
const swapInfo = await swaps.findRouteGivenIn({ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When should I use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's SDK abstracted SOR vs naked SOR. I agree it will be too confusing, so I updated everything to use SDK swaps module with findRouteGivenIn / findRouteGivenOut. In join / exits we use ExactIn / ExactOut - that naming comes from smart contracts. I think we should be consistent and use the same naming here, eg: findRouteExactOut. |
||
tokenIn: '0xstring', // address of tokenIn | ||
tokenOut: '0xstring', // address of tokenOut | ||
amount: parseEther('1'), // BigNumber with a trade amount | ||
gasPrice: parseFixed('1', 9), // BigNumber current gas price | ||
maxPools, // number of pool included in path, above 4 is usually a high gas price | ||
}); | ||
``` | ||
The SOR returns the trade information, including the optimal trade route, the expected slippage and gas cost, and the estimated trade outcome as `swapInfo`. | ||
|
||
### Step 3. Transaction encoding | ||
To execute the trade, the returned `swapInfo` must be encoded into a transaction, which requires the following information: | ||
```javascript | ||
const tx = swaps.buildSwap({ | ||
userAddress: '0xstring', // user address | ||
swapInfo, // result from the previous step | ||
kind: SwapType.SwapExactIn, // or SwapExactOut | ||
deadline, // BigNumber block timestamp | ||
maxSlippage, // [bps], eg: 1 == 0.01%, 100 == 1% | ||
}) | ||
``` | ||
|
||
### Step 4. Broadcast transaction | ||
```javascript | ||
const signer = balancer.provider.getSigner() | ||
await signer.sendTransaction({ | ||
to: tx.to, | ||
data: tx.data, | ||
value: tx.value | ||
}) | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I might add an additional note here that
fetchPools()
does a subgraph query under the hood. And then later in the flow pool balances are updated via an onchain call (configurable via a flag).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added explanation on a query sources, but will need to double check the onchain updates configuration if it's working as expected.