Skip to content
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

Merged
merged 1 commit into from
Feb 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 76 additions & 1 deletion docs/guides/aggregators/sor-basics.md
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.
Copy link
Member

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).

Copy link
Contributor Author

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.

```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
Copy link
Member

Choose a reason for hiding this comment

The 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 ex: 1.5 WETH

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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
Copy link
Member

Choose a reason for hiding this comment

The 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 ex: For 10gwei use ...

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>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be useful to show the SwapInfo type info

Copy link
Member

Choose a reason for hiding this comment

The 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

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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
})
```
63 changes: 54 additions & 9 deletions docs/sdk/technical-reference/smart-order-router.md
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When should I use findRouteGivenIn() vs getSwaps()? Explaining any differences or decisions I have to make as a dev would be really useful

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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
})
```