-
Notifications
You must be signed in to change notification settings - Fork 35
Express Relay Docs #339
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
Merged
Merged
Express Relay Docs #339
Changes from 38 commits
Commits
Show all changes
55 commits
Select commit
Hold shift + click to select a range
ba754df
Initial commit
aditya520 bf983a1
added some framework to docs
aditya520 e758759
protocol guide wip
aditya520 52c3346
add intro express relay docs
anihamde 8d46c78
Merge branch '(feat)-add-express-relay' of github.com:pyth-network/do…
anihamde 8cb8c04
up - up - update
aditya520 0e4d456
updated guide
aditya520 f45490f
additional resources
aditya520 0a56469
tiny changes
aditya520 d524fd3
uhh
aditya520 73aca99
(WIP) Express relay docs
aditya520 27fbecc
(WIP) API refernce
aditya520 9cf9fec
(WIP) Express relay docs
aditya520 e1c5734
details around auction, opportunities, and permissioning
anihamde a1ebb8d
Merge branch '(feat)-add-express-relay' of github.com:pyth-network/do…
anihamde ce9082d
(WIP) Express relay docs
aditya520 d16b669
(WIP) Express relay docs
aditya520 ecb7ca6
add some moar content
anihamde a9d32cd
(WIP) Express relay docs
aditya520 f044075
content dump
anihamde b0df6fb
Merge branch '(feat)-add-express-relay' of github.com:pyth-network/do…
anihamde 59b1992
Suggested edits (#360)
jayantk 1780065
(WIP) Express relay docs- Yaser comments
aditya520 0ee5829
Update tables
m30m 6ad0182
Change MEV definition from the old one
m30m 703c905
added searcher guide to auction-server
anihamde 31eb98f
finalize opportunity adapter docs dump
anihamde 59227c3
Use cards for the main CTAs
m30m cf27a2e
Separate websocket api reference
m30m bc60b80
WIP-Searcher-guide
aditya520 9979e89
WIP-searcher-guide-refactor
aditya520 2bda439
wip
aditya520 c35641d
WIP-searcher-guide-refactor
aditya520 6901e48
Reorg searcher docs
m30m 56d8802
WIP-protocol-guide-refactor
aditya520 4f2f9e4
WIP-searcher-guide-refactor
aditya520 d172628
WIP-ER
aditya520 e24d4b6
Update docs
m30m fb63c4e
some minor changes
anihamde 47ea3f0
WIP-searcher-guide-refactor
aditya520 84d8831
WIP-searcher-guide-refactor
aditya520 078f9f0
WIP-searcher-guide-refactor
aditya520 d3263cc
WIP-comments-resolved
aditya520 d816fed
address change
aditya520 d028c8d
WIP-comments-resolved
aditya520 2448bc8
WIP-comments-resolved
aditya520 a842125
few small fixes pt 1
anihamde 04826af
WIP-intro page
aditya520 5d3e43e
few small fixes pt 2
anihamde c9963d9
Merge branch '(feat)-add-express-relay' of github.com:pyth-network/do…
anihamde 1338c14
wip docs
aditya520 c97754c
Express Relay docs v1
aditya520 78cb484
index change
aditya520 59459ce
Changed example reference to example repo
aditya520 dee1c14
Express Relay v1.xx
aditya520 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import { StyledTd } from "./Table"; | ||
|
||
const AddressTable = ({ | ||
entries, | ||
explorer, | ||
}: { | ||
entries: { name: string; value: string }[]; | ||
explorer: string; | ||
}) => { | ||
return ( | ||
<table> | ||
<tbody> | ||
{entries.map(({ name, value }) => { | ||
const component = ( | ||
<code | ||
className={ | ||
"nx-border-black nx-border-opacity-[0.04] nx-bg-opacity-[0.03] nx-bg-black nx-break-words nx-rounded-md nx-border nx-py-0.5 nx-px-[.25em] nx-text-[.9em] dark:nx-border-white/10 dark:nx-bg-white/10 " | ||
} | ||
> | ||
{value} | ||
</code> | ||
); | ||
const addLink = | ||
explorer.includes("$ADDRESS") && value.startsWith("0x"); | ||
return ( | ||
<tr key={name}> | ||
<StyledTd>{name}</StyledTd> | ||
<StyledTd> | ||
{addLink ? ( | ||
<a | ||
href={explorer.replace("$ADDRESS", value)} | ||
className={ | ||
"nx-text-primary-600 nx-underline nx-decoration-from-font [text-underline-position:from-font]" | ||
} | ||
target={"_blank"} | ||
> | ||
{component} | ||
</a> | ||
) : ( | ||
component | ||
)} | ||
</StyledTd> | ||
</tr> | ||
); | ||
})} | ||
</tbody> | ||
</table> | ||
); | ||
}; | ||
|
||
export default AddressTable; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
const ContractIcon = ({ className }: { className: string }) => { | ||
return ( | ||
<svg | ||
xmlns="http://www.w3.org/2000/svg" | ||
fill="none" | ||
viewBox="0 0 24 24" | ||
strokeWidth={1.5} | ||
stroke="currentColor" | ||
className={className} | ||
> | ||
<path | ||
strokeLinecap="round" | ||
strokeLinejoin="round" | ||
d="M10.125 2.25h-4.5c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125v-9M10.125 2.25h.375a9 9 0 0 1 9 9v.375M10.125 2.25A3.375 3.375 0 0 1 13.5 5.625v1.5c0 .621.504 1.125 1.125 1.125h1.5a3.375 3.375 0 0 1 3.375 3.375M9 15l2.25 2.25L15 12" | ||
/> | ||
</svg> | ||
); | ||
}; | ||
|
||
export default ContractIcon; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
const SearchIcon = ({ className }: { className: string }) => { | ||
return ( | ||
<svg | ||
xmlns="http://www.w3.org/2000/svg" | ||
fill="none" | ||
viewBox="0 0 24 24" | ||
strokeWidth={1.5} | ||
stroke="currentColor" | ||
className={className} | ||
> | ||
<path | ||
strokeLinecap="round" | ||
strokeLinejoin="round" | ||
d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z" | ||
/> | ||
</svg> | ||
); | ||
}; | ||
|
||
export default SearchIcon; |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
{ | ||
"documentation-home": { | ||
"title": "← Documentation Home", | ||
"href": "/home" | ||
}, | ||
|
||
"-- Express Relay": { | ||
"title": "Express Relay", | ||
"type": "separator" | ||
}, | ||
"index": "Introduction", | ||
|
||
"-- How-to Guides": { | ||
"title": "How-To Guides", | ||
"type": "separator" | ||
}, | ||
|
||
"integrate-as-protocol": "Integrate as a Protocol", | ||
"integrate-as-searcher": "Integrate as a Searcher", | ||
|
||
"-- Reference Material": { | ||
"title": "Reference Material", | ||
"type": "separator" | ||
}, | ||
|
||
"api-reference": { | ||
"title": "HTTP API Reference ↗", | ||
"href": "https://pyth-express-relay-mainnet.asymmetric.re/docs/" | ||
}, | ||
"websocket-api-reference": "Websocket API Reference", | ||
"contract-addresses": "Contract Addresses", | ||
"errors": "Error Codes", | ||
"examples": { | ||
"title": "Example Application ↗", | ||
"href": "https://github.com/pyth-network/pyth-crosschain/tree/main/express_relay/examples/easy_lend" | ||
}, | ||
"-- Understand Express Relay": { | ||
"title": "Understanding Express Relay", | ||
"type": "separator" | ||
}, | ||
|
||
"how-express-relay-works": "How Express Relay Works" | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# API Reference | ||
|
||
Link to swagger |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
import AddressTable from "../../components/AddressTable"; | ||
|
||
Express Relay is currently deployed on the following networks: | ||
|
||
## Mainnets | ||
|
||
You can access the Auction Server via the following endpoint: https://pyth-express-relay-mainnet.asymmetric.re/ | ||
|
||
### Mode | ||
|
||
Network Details: | ||
|
||
<AddressTable | ||
explorer={"https://explorer.mode.network/address/$ADDRESS"} | ||
entries={[ | ||
{ | ||
name: "Express Relay", | ||
value: "0x5Cc070844E98F4ceC5f2fBE1592fB1ed73aB7b48", | ||
}, | ||
{ | ||
name: "Opportunity Adapter Factory", | ||
value: "0x59F78DE21a0b05d96Ae00c547BA951a3B905602f", | ||
}, | ||
{ | ||
name: "Network Id", | ||
value: "34443", | ||
}, | ||
{ | ||
name: "Chain Id", | ||
value: "mode", | ||
}, | ||
{ | ||
name: "Public RPC", | ||
value: "https://mainnet.mode.network/", | ||
}, | ||
{ | ||
name: "Wrapped ETH", | ||
value: "0x4200000000000000000000000000000000000006", | ||
}, | ||
{ | ||
name: "Permit2 Contract", | ||
value: "0x000000000022D473030F116dDEE9F6B43aC78BA3", | ||
}, | ||
]} | ||
/> | ||
|
||
Assets: | ||
|
||
<AddressTable | ||
explorer={"https://explorer.mode.network/address/$ADDRESS"} | ||
entries={[ | ||
{ name: "WETH", value: "0x4200000000000000000000000000000000000006" }, | ||
{ name: "USDC", value: "0xd988097fb8612cc24eeC14542bC03424c656005f" }, | ||
{ name: "USDT", value: "0xf0F161fDA2712DB8b566946122a5af183995e2eD" }, | ||
{ name: "WBTC", value: "0xcDd475325D6F564d27247D1DddBb0DAc6fA0a5CF" }, | ||
{ name: "ezETH", value: "0x2416092f143378750bb29b79eD961ab195CcEea5" }, | ||
{ name: "STONE", value: "0x80137510979822322193FC997d400D5A6C747bf7" }, | ||
{ name: "wrsETH", value: "0xe7903B1F75C534Dd8159b313d92cDCfbC62cB3Cd" }, | ||
{ name: "weETH.mode", value: "0x04C0599Ae5A44757c0af6F9eC3b93da8976c150A" }, | ||
{ name: "M-BTC", value: "0x59889b7021243dB5B1e065385F918316cD90D46c" }, | ||
{ name: "MODE", value: "0xDfc7C877a950e49D2610114102175A06C2e3167a" }, | ||
]} | ||
/> | ||
|
||
## Testnets | ||
|
||
You can access the Auction Server via the following endpoint: https://per-staging.dourolabs.app/ | ||
|
||
### Optimism Sepolia | ||
|
||
Main info: | ||
|
||
<AddressTable | ||
explorer={"https://optimism-sepolia.blockscout.com/address/$ADDRESS"} | ||
entries={[ | ||
{ | ||
name: "Express Relay", | ||
value: "0x2F968931d1B7326d2875E9500980211dcc535eE5", | ||
}, | ||
{ | ||
name: "Opportunity Adapter Factory", | ||
value: "0xfA119693864b2F185742A409c66f04865c787754", | ||
}, | ||
{ | ||
name: "Network Id", | ||
value: "11155420", | ||
}, | ||
{ | ||
name: "Chain Id", | ||
value: "op_sepolia", | ||
}, | ||
{ | ||
name: "Public RPC", | ||
value: "https://sepolia.optimism.io/", | ||
}, | ||
{ | ||
name: "Wrapped ETH", | ||
value: "0x74A4A85C611679B73F402B36c0F84A7D2CcdFDa3", | ||
}, | ||
{ | ||
name: "Permit2 Contract", | ||
value: "0x000000000022D473030F116dDEE9F6B43aC78BA3", | ||
}, | ||
]} | ||
/> | ||
|
||
Assets: | ||
|
||
<AddressTable | ||
explorer={"https://optimism-sepolia.blockscout.com/address/$ADDRESS"} | ||
entries={[ | ||
{ | ||
name: "BTC", | ||
value: "0x3745007F7C8DD8Bec89b3B35f33f13f58b008533 ", | ||
}, | ||
{ | ||
name: "USDC", | ||
value: "0x1e3d75F24296abBC7bd10D151F51d758bCE379Ef", | ||
}, | ||
{ | ||
name: "DOGE", | ||
value: "0x0160Ba0A02A910e27b4189040aC74febcc476687 ", | ||
}, | ||
{ | ||
name: "SOL", | ||
value: "0x193aD24BeeDfAf27f217395f52fA20C7A36D79B3", | ||
}, | ||
{ | ||
name: "PYTH", | ||
value: "0x20e0b5Ff7aa971dAd8f489112Ae009A70608d2D5", | ||
}, | ||
]} | ||
/> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
# Error Codes | ||
|
||
The following table lists the error codes and their explanations for ExpressRelay and OpportunityAdapter contracts. | ||
They can be used to identify the cause of a failed transaction or bid. | ||
|
||
## ExpressRelay | ||
|
||
| Error | Selector | Explanation | | ||
| -------------------------------------------- | ------------ | ------------------------------------------------------------------------------------------- | | ||
| `Unauthorized()` | `0x82b42900` | This function is called by an unauthorized party. | | ||
| `InvalidMagicValue()` | `0x4ed848c1` | An upgrade was attempted to a contract that does not match the ExpressRelay specification. | | ||
| `InvalidPermission()` | `0x868a64de` | The provided permissionKey is invalid (too short). | | ||
| `InvalidFeeSplit()` | `0x0601f697` | The proposed fee split is invalid (fee is larger than feePrecision, 10\*\*18). | | ||
| `InvalidTargetContract()` | `0x5569851a` | The provided target contract is not allowed. (e.g. can not call the ExpressRelay contract). | | ||
| `DuplicateRelayerSubwallet()` | `0xb40d37c3` | The provided subwallet to add has already been added. | | ||
| `RelayerSubwalletNotFound()` | `0xac4d92b3` | The provided subwallet to delete does not exist in the store. | | ||
| `ExternalCallFailed(MulticallStatus status)` | `0x740d0306` | The external call failed with the following MulticallStatus output. | | ||
|
||
## OpportunityAdapter | ||
|
||
| Error | Selector | Explanation | | ||
| -------------------------------------- | ------------ | ---------------------------------------------------------------------------------------------------------------- | | ||
| `NotCalledByExpressRelay()` | `0xd5668c88` | The OpportunityAdapterFactory contract was not called by the ExpressRelay contract. | | ||
| `NotCalledByFactory()` | `0xb02436cc` | The OpportunityAdapter contract was not called by the OpportunityAdapterFactory contract. | | ||
| `AdapterOwnerMismatch()` | `0x446f3eeb` | The provided executor field does not match the owner of the called OpportunityAdapter contract. | | ||
| `InsufficientTokenReceived()` | `0x4af147aa` | The specified buyTokens were not received after calling the target contract. | | ||
| `InsufficientEthToSettleBid()` | `0x9caaa1d7` | The contract did not receive enough ETH to pay the specified bid. | | ||
| `InsufficientWethForTargetCallValue()` | `0x5e520cd4` | The contract did not receive enough Wrapped ETH to pay the targetCallValue to the targetContract. | | ||
| `TargetCallFailed(bytes returnData)` | `0xa932c97a` | The call to targetContract failed with the specified returnData. | | ||
| `DuplicateToken()` | `0x464e3f6a` | There is a duplicate token in either the sellTokens or buyTokens. | | ||
| `EthOrWethBalanceDecreased()` | `0x1979776d` | The ETH or WETH balance of the contract decreased as a result of the call to targetContract and the bid payment. | | ||
| `TargetContractNotAllowed()` | `0x9c86e59e` | The provided targetContract is not allowed. (e.g. can not call the Permit2 contract). | | ||
| `OnlyOwnerCanCall()` | `0x47a8ea58` | Only the owner of the contract can call this method. | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
# How Express Relay Works | ||
|
||
Express Relay allows protocols to eliminate maximal extractable value (MEV). | ||
Many protocols generate MEV on a regular basis. | ||
For example, borrow-lending protocols provide bonuses to searchers for liquidating undercollateralized loans. | ||
Searchers compete for these bonuses by tipping the chain's miners or validators. | ||
The validators capture most of the value of the liquidation bonus via these tips, so the liquidation bonus is in essence a transfer of wealth from the protocol's users to the validators. | ||
|
||
Express Relay solves the problem of MEV by providing protocol developers with an auction primitive that they can use to prioritize access to permissionless operations. | ||
Developers specify a set of operations in their protocol that must be accessed through Express Relay. | ||
Searchers then participate in an off-chain auction to access these operations. | ||
Their bids in the auction determine the priority of their transactions, i.e., the order in which their transactions will be executed. | ||
The winners transactions are forwarded to the blockchain, which both pays their bid and executes the operation. | ||
The profits of the auction are then split between the integrated protocol and other participants in Express Relay. | ||
|
||
 | ||
|
||
The diagram above shows how Express Relay changes the MEV landscape for a liquidation. | ||
In the status quo (left), Searchers tip miners in order to guarantee that their liquidation transaction lands on-chain. | ||
Their transaction directly interacts with the protocol exposing the liquidation opportunity, and the liquidation bonus flows back to the Searcher. | ||
With Express Relay (right), Searchers submit bids for their transaction to the Express Relay auction. | ||
The auction submits the winning bids to the blockchain, where the transactions are processed by the Express Relay Entrypoint before being forwarded on to the integrated protocol. | ||
The Express Relay Entrypoint collects payment from the Searchers and forwards a share of the revenue back to the integrated protocol. | ||
|
||
FIXME: I think the diagram is wrong (specifically the liquidation bonus going back to express relay) | ||
aditya520 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
## Which protocols can use Express Relay? | ||
aditya520 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
Any protocol with permissionless and valuable operations can use Express Relay. | ||
These operations generate MEV, as the validators control which searchers can access them. | ||
Express Relay enables protocols to auction access instead, thereby ensuring the operation is competitively priced. | ||
Lending, perps, and derivatives protocols with liquidation mechanisms are clear candidates that can benefit from integration with Express Relay. | ||
|
||
Aside from eliminating MEV, protocols that need a stable set of searchers may choose to use Express Relay. | ||
Express Relay provides access to a robust network of searchers who are already active in the Express Relay ecosystem. | ||
|
||
## Participants in Express Relay | ||
|
||
There are four types of participants in the Express Relay protocol: | ||
|
||
- The Relayer runs the off-chain auction and forwards winning transactions onto the blockchain. There is a single Relayer chosen by the Pyth DAO. | ||
- Protocol developers integrate their protocol with Express Relay in order to eliminate MEV. | ||
- Searchers participate in auctions to access liquidations and other on-chain opportunities. | ||
- The Pyth DAO owns and governs the Express Relay system |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"auction": "Auction", | ||
"opportunities": "Opportunities", | ||
"permissioning": "Permissioning", | ||
"relayer": "Relayer" | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
# Auction | ||
|
||
The auction in Express Relay is held off-chain at the auction server. | ||
Liquidation bids arrive at the auction server and compete against other bids, vying for the same [permission key](./permissioning.mdx). | ||
A [relayer](./relayer.mdx) selected by governance serves as the auctioneer and determines the auction in line with the criterion of maximizing the revenue shared back to the protocol that generated this opportunity. That means the auctioneer is expected to forward the subset of bids that maximizes the revenue back to the protocol. | ||
|
||
Thus, the Express Relay auction is analogous to a sealed-bid auction, i.e., participants in the auction will not have the contents of their bid disclosed publicly unless they are forwarded on-chain. | ||
|
||
The forwarded subset of transactions is submitted on-chain and first processed by the [`ExpressRelay`](https://github.com/pyth-network/per/blob/main/contracts/src/express-relay/ExpressRelay.sol) contract before individual searchers' submissions are routed to their corresponding `targetContract`s. | ||
|
||
Generally, the auction server expects bids to execute successfully on-chain. Falback bids are also forwarded in case of execution failures for the predicted winners. | ||
|
||
The `ExpressRelay` contract extracts the payment of the specified bid amount only if the searcher's bid is successfully executed on-chain. | ||
Hence, the Express Relay auction can be seen as a generalization of a [first-price sealed-bid auction](https://en.wikipedia.org/wiki/First-price_sealed-bid_auction), in that multiple bids can win and pay their first price. | ||
|
||
The revenue from the auction is shared amongst relevant stakeholders in the Express Relay system. These stakeholders include: | ||
- the protocol that generated the relevant opportunity | ||
- the relayer, which handled running the off-chain components of the system | ||
|
||
The Express Relay contract enforces the exact revenue splits and is subject to change based on governance decisions. |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.