-
Notifications
You must be signed in to change notification settings - Fork 22
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
feat: better per svm docs #536
base: main
Are you sure you want to change the base?
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 | ||||
---|---|---|---|---|---|---|
|
@@ -4,42 +4,60 @@ import { Callout, Tabs, Steps } from "nextra/components"; | |||||
|
||||||
SVM Express Relay searchers fulfill opportunities representing limit orders on the [Limo](https://solscan.io/account/LiMoM9rMhrdYrfzUCxQppvxCSG1FcrUK9G8uLq4A1GF) program. | ||||||
|
||||||
Bids are represented via [SVM transactions](https://solana.com/docs/core/transactions) that include instructions for submitting the bid. | ||||||
|
||||||
<Steps> | ||||||
|
||||||
### Subscribe to New Opportunities | ||||||
### Subscribe to SVM chain | ||||||
|
||||||
Express Relay provides searchers with [Typescript](https://github.com/pyth-network/per/tree/4be711525948cf24c0ebd4ebab007dc7f51b7069/sdk/js) and [Python](https://github.com/pyth-network/per/tree/4be711525948cf24c0ebd4ebab007dc7f51b7069/sdk/python) SDKs to interact with Express Relay. | ||||||
Searchers can use [Typescript](https://github.com/pyth-network/per/tree/4be711525948cf24c0ebd4ebab007dc7f51b7069/sdk/js) | ||||||
and [Python](https://github.com/pyth-network/per/tree/4be711525948cf24c0ebd4ebab007dc7f51b7069/sdk/python) SDKs to interact with auction server. | ||||||
Searchers can also directly fetch available opportunities via HTTP or subscribe to them via WebSocket. | ||||||
|
||||||
<Tabs items={['Typescript', 'Python', 'HTTP', 'Websocket']}> | ||||||
<Callout type="warning" emoji="⚠️"> | ||||||
Polling the server via HTTP for new opportunities is not recommended because | ||||||
of the additional delay it may incur. This can lead to late bid submissions | ||||||
which will be rejected. Some submission information such as the most recent | ||||||
blockhash and priority fees are **only** broadcasted via websocket. | ||||||
</Callout> | ||||||
|
||||||
<Tabs items={['Typescript', 'Python', 'HTTP', 'WebSocket']}> | ||||||
|
||||||
<Tabs.Tab> | ||||||
Pyth provides a Typescript SDK, which allows searchers to subscribe to opportunities: | ||||||
|
||||||
```typescript | ||||||
import { Client, Opportunity } from "@pythnetwork/express-relay-js"; | ||||||
|
||||||
latestChainUpdate: Record<string, SvmChainUpdate> = {} | ||||||
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.
Suggested change
|
||||||
|
||||||
const handleOpportunity = async (opportunity: Opportunity) => { | ||||||
console.log("Received opportunity"); | ||||||
// Implement your opportunity handler here | ||||||
}; | ||||||
|
||||||
const svmChainUpdateHandler = async (update: SvmChainUpdate) { | ||||||
// Store chain updates to use when constructing the transaction | ||||||
latestChainUpdate[update.chainId] = update; | ||||||
} | ||||||
|
||||||
const client = new Client( | ||||||
{ baseUrl: "https://pyth-express-relay-mainnet.asymmetric.re" }, | ||||||
undefined, // Default WebSocket options | ||||||
handleOpportunity | ||||||
handleOpportunity, | ||||||
undefined, | ||||||
svmChainUpdateHandler | ||||||
); | ||||||
|
||||||
async function main() { | ||||||
await client.subscribeChains(["solana"]); | ||||||
} | ||||||
|
||||||
main(); | ||||||
|
||||||
``` | ||||||
|
||||||
</Tabs.Tab> | ||||||
<Tabs.Tab> | ||||||
Pyth provides a Python SDK, which allows searchers to subscribe to opportunities: | ||||||
|
||||||
```python copy | ||||||
import asyncio | ||||||
|
@@ -48,15 +66,22 @@ from express_relay.client import ( | |||||
) | ||||||
from express_relay.models import Opportunity | ||||||
|
||||||
latest_chain_update = {} | ||||||
|
||||||
async def opportunity_callback(opportunity: Opportunity): | ||||||
print("Received opportunity") | ||||||
# Implement your opportunity handler here | ||||||
|
||||||
async def svm_chain_update_callback(svm_chain_update: SvmChainUpdate): | ||||||
# Store chain updates to use when constructing the transaction | ||||||
latest_chain_update[svm_chain_update.chain_id] = svm_chain_update | ||||||
|
||||||
client = ExpressRelayClient( | ||||||
"https://pyth-express-relay-mainnet.asymmetric.re", | ||||||
None, | ||||||
opportunity_callback, | ||||||
None, | ||||||
svm_chain_update_callback | ||||||
) | ||||||
|
||||||
async def main(): | ||||||
|
@@ -77,8 +102,6 @@ curl -X 'GET' \ | |||||
'https://pyth-express-relay-mainnet.asymmetric.re/v1/opportunities?chain_id=solana&mode=live' | ||||||
``` | ||||||
|
||||||
Opportunities are short-lived and could be executed in a matter of seconds. So, the above endpoint could return an empty response. | ||||||
|
||||||
</Tabs.Tab> | ||||||
<Tabs.Tab> | ||||||
Searchers can connect to the server via WebSocket to reduce latency and subscribe to various events. The WebSocket endpoint lives at `/v1/ws`(e.g `wss://pyth-express-relay-mainnet.asymmetric.re/v1/ws`). | ||||||
|
@@ -94,20 +117,57 @@ Here is a sample JSON payload to subscribe to opportunities: | |||||
} | ||||||
``` | ||||||
|
||||||
Consult [`Websocket API reference`](./websocket-api-reference.mdx) for a complete list of methods and parameters. | ||||||
Consult [`WebSocket API reference`](./websocket-api-reference.mdx) for a complete list of methods and parameters. | ||||||
|
||||||
</Tabs.Tab> | ||||||
</Tabs> | ||||||
|
||||||
The server responds with opportunities in the following format: | ||||||
|
||||||
```bash copy | ||||||
```json copy | ||||||
{ | ||||||
// The Limo order to be executed, encoded in base64 | ||||||
"order": "UxMUbQAsjrfQUp5stVwMJ6Mucq7VWTvt4ICe69BJ8lVXqwM+0sysV8OqZTdM0W4p...", | ||||||
// Address of the order account | ||||||
"order_address": "DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5", | ||||||
// Identifier of the program that the order exists in | ||||||
"program": "limo", | ||||||
"chain_id": "solana", | ||||||
// Opportunity format version | ||||||
"version": "v1", | ||||||
"opportunity_id": "271f2a7b-1ec5-420f-b5c0-e3e4317f3d7b", | ||||||
// Creation time of the opportunity (in microseconds since the Unix epoch) | ||||||
"creation_time": 1733503592579589, | ||||||
"slot": 305802439 | ||||||
} | ||||||
``` | ||||||
|
||||||
The `order` field includes the [Limo](https://solscan.io/account/LiMoM9rMhrdYrfzUCxQppvxCSG1FcrUK9G8uLq4A1GF) program | ||||||
order data that can be decoded using the SDKs provided or the program anchor idl. It includes all the necessary information | ||||||
to fill the limit order: | ||||||
|
||||||
- Maker address | ||||||
- Input token and amount (what the maker is selling) | ||||||
- Output token and amount (what the maker is buying in exchange for the input token) | ||||||
|
||||||
<Callout type="info"> | ||||||
Limo limit orders can also be filled partially in a linear fashion. For | ||||||
example, if the order is to buy 10 SOL for \$2000, you can provide 5 SOL and | ||||||
get back \$1000. | ||||||
</Callout> | ||||||
|
||||||
The auction server also broadcast chain specific information that are necessary for building the transaction: | ||||||
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.
Suggested change
|
||||||
|
||||||
```json copy | ||||||
{ | ||||||
"order": "UxMUbQAsjrfQUp5stVwMJ6Mucq7VWTvt4ICe69BJ8lVXqwM+0sysV8OqZTdM0W4p...", // The Limo order to be executed, encoded in base64 | ||||||
"order_address": "DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5", // Address of the order account | ||||||
"program": "limo", // Identifier of the program that the order exists in | ||||||
"chain_id": "development-solana", | ||||||
"version": "v1" // Opportunity format version | ||||||
"type": "svm_chain_update", | ||||||
"update": { | ||||||
"chain_id": "development-solana", | ||||||
// Recent blockhash that you can use when constructing the transaction | ||||||
"blockhash": "6YK9yt6T1NhWNLhRGMapFxreYx6HPcW7RRcNSDsXjnLb", | ||||||
// Latest prioritization fee that is necessary to include in the transaction | ||||||
"latest_prioritization_fee": 319592 | ||||||
} | ||||||
} | ||||||
``` | ||||||
|
||||||
|
@@ -189,7 +249,7 @@ from solders.transaction import Transaction | |||||
from express_relay.models.svm import BidSvm | ||||||
from express_relay.svm.limo_client import OrderStateAndAddress | ||||||
|
||||||
DEADLINE = 2**62 | ||||||
DEADLINE = 2 * 10**10 | ||||||
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. why this change? |
||||||
logger = logging.getLogger(__name__) | ||||||
|
||||||
async def assess_opportunity(self, opp: OpportunitySvm) -> BidSvm | None: | ||||||
|
@@ -242,18 +302,25 @@ The bid you construct will look like | |||||
{ | ||||||
// serialized transaction object, in base-64 encoding | ||||||
"transaction": "SGVsbG8sIFdvcmxkIQ==", | ||||||
"chain_id": "solana", | ||||||
"env": "svm" | ||||||
"chain_id": "solana" | ||||||
} | ||||||
``` | ||||||
|
||||||
where the serialized transaction object should contain an Express Relay `SubmitBid` instruction that specifies the amount you are bidding and the permission details. | ||||||
The transaction submitted to the auction server as the bid should meet the following criteria: | ||||||
|
||||||
- It should contain an Express Relay `SubmitBid` instruction that specifies the amount you are bidding, the permission details, and the deadline. | ||||||
This instruction can be created via the SDKs. | ||||||
- The deadline specified in the `SubmitBid` instruction should be at least 5 seconds in the future. | ||||||
- It should contain an instruction to set the [transaction priority fee](https://solana.com/developers/guides/advanced/how-to-use-priority-fees#what-are-priority-fees). The priority fee should be at least as large the amount | ||||||
advertised via websocket. | ||||||
- It should contain valid signatures for all signers except the relayer | ||||||
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.
Suggested change
|
||||||
- It should pass simulation | ||||||
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.
Suggested change
|
||||||
|
||||||
### Submit Bids on Opportunities to Express Relay | ||||||
|
||||||
Searchers can submit their constructed bids to Express Relay via the SDKs, an HTTP POST request, or a WebSocket connection. | ||||||
|
||||||
<Tabs items={['Typescript', 'Python', 'HTTP', 'Websocket']}> | ||||||
<Tabs items={['Typescript', 'Python', 'HTTP', 'WebSocket']}> | ||||||
|
||||||
<Tabs.Tab> | ||||||
|
||||||
|
@@ -276,7 +343,7 @@ const handleOpportunity = async (opportunity: Opportunity) => { | |||||
|
||||||
The code snippet below demonstrates how to submit a bid using the Python SDK: | ||||||
|
||||||
```python {5} copy | ||||||
```python {8} copy | ||||||
import typing | ||||||
|
||||||
async def generate_bid(opp: OpportunitySvm) -> BidSvm: | ||||||
|
@@ -303,7 +370,7 @@ curl -X POST https://pyth-express-relay-mainnet.asymmetric.re/v1/bids \ | |||||
</Tabs.Tab> | ||||||
<Tabs.Tab> | ||||||
|
||||||
Searchers can submit bids via Websocket to avoid additional network round-trips and get notified about changes to the bid status. | ||||||
Searchers can submit bids via WebSocket to avoid additional network round-trips and get notified about changes to the bid status. | ||||||
|
||||||
```bash copy | ||||||
{ | ||||||
|
@@ -322,16 +389,18 @@ A successful response to a bid submission has the following schema: | |||||
|
||||||
```bash copy | ||||||
{ | ||||||
"id": "1", // Websocket request id | ||||||
// WebSocket request id | ||||||
"id": "1", | ||||||
"status": "success", | ||||||
"result": { | ||||||
"id": "beedbeed-b346-4fa1-8fab-2541a9e1872d", // Bid id | ||||||
// Bid id | ||||||
"id": "beedbeed-b346-4fa1-8fab-2541a9e1872d", | ||||||
"status": "OK" | ||||||
} | ||||||
} | ||||||
``` | ||||||
|
||||||
Consult [`Websocket API reference`](./websocket-api-reference.mdx) for more details. | ||||||
Consult [`WebSocket API reference`](../websocket-api-reference.mdx) for more details. | ||||||
|
||||||
</Tabs.Tab> | ||||||
</Tabs> | ||||||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -37,43 +37,76 @@ In case of error, the `status` field will be `error`, and the error message will | |||||
} | ||||||
``` | ||||||
|
||||||
## Subscribing to opportunities | ||||||
## Subscribing to chains | ||||||
|
||||||
To subscribe to opportunities, you can send a request using the `chain_ids` parameter, which specifies the chains as an array. | ||||||
To subscribe to opportunities and chain updates, you can send a request using the `chain_ids` parameter, which specifies the chains as an array. | ||||||
|
||||||
```json | ||||||
{ | ||||||
"id": "1", | ||||||
"method": "subscribe", | ||||||
"params": { | ||||||
"chain_ids": ["op_sepolia"] | ||||||
"chain_ids": ["solana"] | ||||||
} | ||||||
} | ||||||
``` | ||||||
|
||||||
After a successful subscription, you will receive new opportunities for the selected chains via the WebSocket in the following format: | ||||||
To unsubscribe from a list of chains, you can send the following message: | ||||||
|
||||||
```json copy | ||||||
{ | ||||||
"id": "1", | ||||||
"method": "unsubscribe", | ||||||
"params": { | ||||||
"chain_ids": ["solana"] | ||||||
} | ||||||
} | ||||||
``` | ||||||
|
||||||
After a successful subscription, you will receive updates for the selected chains via the WebSocket in the following format: | ||||||
|
||||||
<Tabs items={['EVM', 'SVM']}> | ||||||
|
||||||
<Tabs.Tab> | ||||||
|
||||||
```json | ||||||
{ | ||||||
"type": "new_opportunity", | ||||||
"opportunity": {...} | ||||||
"type": "new_opportunity", | ||||||
"opportunity": {...} | ||||||
} | ||||||
``` | ||||||
|
||||||
The schema for the opportunity is similar to what’s returned in the [HTTP requests](https://pyth-express-relay-mainnet.asymmetric.re/docs#tag/opportunity/operation/get_opportunities) | ||||||
</Tabs.Tab> | ||||||
<Tabs.Tab> | ||||||
|
||||||
To unsubscribe from a list of chains, you can send the following message: | ||||||
```json | ||||||
{ | ||||||
"type": "new_opportunity", | ||||||
"opportunity": {...} | ||||||
} | ||||||
``` | ||||||
|
||||||
```json copy | ||||||
You will receive svm specific updates in the following format: | ||||||
|
||||||
```json | ||||||
{ | ||||||
"id": "1", | ||||||
"method": "unsubscribe", | ||||||
"params": { | ||||||
"chain_ids": ["op_sepolia"] | ||||||
"type": "svm_chain_update", | ||||||
"update": { | ||||||
"chain_id": "development-solana", | ||||||
// Recent blockhash that you can use when constructing the transaction | ||||||
"blockhash": "6YK9yt6T1NhWNLhRGMapFxreYx6HPcW7RRcNSDsXjnLb", | ||||||
// Latest prioritization fee that is necessary to include in the transaction | ||||||
"latest_prioritization_fee": 319592 | ||||||
} | ||||||
} | ||||||
``` | ||||||
|
||||||
</Tabs.Tab> | ||||||
|
||||||
</Tabs> | ||||||
|
||||||
The schema for the opportunity is similar to what’s returned in the [HTTP requests](https://pyth-express-relay-mainnet.asymmetric.re/docs#tag/opportunity/operation/get_opportunities) | ||||||
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.
Suggested change
|
||||||
|
||||||
## Submitting bids | ||||||
|
||||||
In addition to the HTTP methods, you can submit your bids via WebSocket in order to avoid additional network round trips and get notified about changes to your bid status. | ||||||
|
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.