Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
6 changes: 3 additions & 3 deletions pages/express-relay/integrate-as-searcher/evm.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ Pyth provides a Typescript SDK, which allows searchers to subscribe to opportuni
```typescript
import { Client, Opportunity } from "@pythnetwork/express-relay-js";

const handleOpporunity = async (opportunity: Opportunity) => {
const handleOpportunity = async (opportunity: Opportunity) => {
// Implement your opportunity handler here
};

const client = new Client(
{ baseUrl: "https://pyth-express-relay-mainnet.asymmetric.re" },
undefined, // Default WebSocket options
handleOpporunity
handleOpportunity
);
await client.subscribeChains(["op_sepolia"]);
```
Expand Down Expand Up @@ -175,7 +175,7 @@ Searchers can submit the constructed bids to Express Relay via the SDKs, an HTTP
The code snippet below demonstrates how to submit a bid using the Typescript SDK:

```typescript {4} copy
const handleOpporunity = async (opportunity: Opportunity) => {
const handleOpportunity = async (opportunity: Opportunity) => {
...
const bid = await client.signBid(opportunity, {amount, nonce, deadline}, privateKey)
await client.submitBid(bid)
Expand Down
129 changes: 80 additions & 49 deletions pages/express-relay/integrate-as-searcher/svm.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Callout, Tabs, Steps } from "nextra/components";

# SVM Searcher Integration

SVM Express Relay searchers fulfill opportunities representing limit orders on the [Limo]() program.
SVM Express Relay searchers fulfill opportunities representing limit orders on the [Limo](https://solscan.io/account/LiMoM9rMhrdYrfzUCxQppvxCSG1FcrUK9G8uLq4A1GF) program.

<Steps>

Expand All @@ -19,39 +19,53 @@ Pyth provides a Typescript SDK, which allows searchers to subscribe to opportuni
```typescript
import { Client, Opportunity } from "@pythnetwork/express-relay-js";

const handleOpporunity = async (opportunity: Opportunity) => {
const handleOpportunity = async (opportunity: Opportunity) => {
console.log("Received opportunity");
// Implement your opportunity handler here
};

const client = new Client(
{ baseUrl: "https://pyth-express-relay-mainnet.asymmetric.re" },
undefined, // Default WebSocket options
handleOpporunity
handleOpportunity
);
await client.subscribeChains(["development-solana"]);

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
from express_relay.client import (
ExpressRelayClient,
)
from express_relay.express_relay_types import Opportunity
from express_relay.models import Opportunity

def opportunity_callback(opportunity: Opportunity):
async def opportunity_callback(opportunity: Opportunity):
print("Received opportunity")
# Implement your opportunity handler here
pass

client = ExpressRelayClient(
"https://per-staging.dourolabs.app",
"https://pyth-express-relay-mainnet.asymmetric.re",
None,
opportunity_callback,
None,
)
await client.subscribe_chains(['development-solana'])

async def main():
await client.subscribe_chains(["solana"])
task = await client.get_ws_loop()
await task

if __name__ == "__main__":
asyncio.run(main())
```

</Tabs.Tab>
Expand All @@ -60,7 +74,7 @@ Searchers can request opportunities through an HTTP **GET** call to the [`/v1/op

```bash copy
curl -X 'GET' \
'https://pyth-express-relay-mainnet.asymmetric.re/v1/opportunities?chain_id=development-solana&mode=live'
'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.
Expand All @@ -75,7 +89,7 @@ Here is a sample JSON payload to subscribe to opportunities:
"id": "1",
"method": "subscribe",
"params": {
"chain_ids": ["development-solana"]
"chain_ids": ["solana"]
}
}
```
Expand Down Expand Up @@ -125,32 +139,41 @@ import * as limo from "@kamino-finance/limo-sdk";
* @returns The generated bid object
*/
async generateBid(opportunity: OpportunitySvm): Promise<BidSvm> {
const order = opportunity.order;
const limoClient = new limo.LimoClient(
this.connectionSvm,
order.state.globalConfig
);

const ixsTakeOrder = await this.generateTakeOrderIxs(limoClient, order);
const txRaw = new anchor.web3.Transaction().add(...ixsTakeOrder);

const bidData = await this.getBidData(limoClient, order);

const bid = await this.client.constructSvmBid(
txRaw,
this.searcher.publicKey,
bidData.router,
order.address,
bidData.bidAmount,
new anchor.BN(Math.round(Date.now() / 1000 + DAY_IN_SECONDS)),
this.chainId,
bidData.relayerSigner,
bidData.relayerFeeReceiver
);

bid.transaction.recentBlockhash = this.recentBlockhash[this.chainId];
bid.transaction.sign(this.searcher);
return bid;
const order = opportunity.order;
const limoClient = new limo.LimoClient(
this.connectionSvm,
order.state.globalConfig
);

const ixsTakeOrder = await this.generateTakeOrderIxs(limoClient, order);
const feeInstruction = ComputeBudgetProgram.setComputeUnitPrice({
microLamports:
this.latestChainUpdate[this.chainId].latestPrioritizationFee,
});
const txRaw = new anchor.web3.Transaction().add(
feeInstruction,
...ixsTakeOrder
);

const bidAmount = await this.getBidAmount(order);

const config = await this.getExpressRelayConfig();
const bid = await this.client.constructSvmBid(
txRaw,
this.searcher.publicKey,
getPdaAuthority(limoClient.getProgramID(), order.state.globalConfig),
order.address,
bidAmount,
new anchor.BN(Math.round(Date.now() / 1000 + DAY_IN_SECONDS)),
this.chainId,
config.relayerSigner,
config.feeReceiverRelayer
);

bid.transaction.recentBlockhash =
this.latestChainUpdate[this.chainId].blockhash;
bid.transaction.sign(this.searcher);
return bid;
}
```

Expand Down Expand Up @@ -179,24 +202,32 @@ async def assess_opportunity(self, opp: OpportunitySvm) -> BidSvm | None:
A bid object if the opportunity is worth taking to be submitted to the Express Relay server, otherwise None.
"""
order: OrderStateAndAddress = {"address": opp.order_address, "state": opp.order}

ixs_take_order = await self.generate_take_order_ixs(order)
bid_data = await self.get_bid_data(order)
bid_amount = await self.get_bid_amount(order)
router = self.limo_client.get_pda_authority(
self.limo_client.get_program_id(), order["state"].global_config
)

submit_bid_ix = self.client.get_svm_submit_bid_instruction(
searcher=self.private_key.pubkey(),
router=bid_data.router,
router=router,
permission_key=order["address"],
bid_amount=bid_data.bid_amount,
bid_amount=bid_amount,
deadline=DEADLINE,
chain_id=self.chain_id,
fee_receiver_relayer=bid_data.relayer_fee_receiver,
relayer_signer=bid_data.relayer_signer,
fee_receiver_relayer=(await self.get_metadata()).fee_receiver_relayer,
relayer_signer=(await self.get_metadata()).relayer_signer,
)
latest_chain_update = self.latest_chain_update[self.chain_id]
fee_instruction = set_compute_unit_price(
latest_chain_update.latest_prioritization_fee
)
transaction = Transaction.new_with_payer(
[submit_bid_ix] + ixs_take_order, self.private_key.pubkey()
[fee_instruction, submit_bid_ix] + ixs_take_order, self.private_key.pubkey()
)
transaction.partial_sign(
[self.private_key], recent_blockhash=self.recent_blockhash[self.chain_id]
[self.private_key], recent_blockhash=latest_chain_update.blockhash
)
bid = BidSvm(transaction=transaction, chain_id=self.chain_id)
return bid
Expand Down Expand Up @@ -233,9 +264,9 @@ const generateBid = async (opportunity: OpportunitySvm, recentBlockhash: Blockha
...
}

const handleOpporunity = async (opportunity: Opportunity) => {
const handleOpportunity = async (opportunity: Opportunity) => {
...
const bid = await generateBid(opportunity as OpportunitySvm, this.recentBlockhash[this.chainId]);
const bid = await this.generateBid(opportunity as OpportunitySvm);
await client.submitBid(bid);
}
```
Expand All @@ -252,7 +283,7 @@ async def generate_bid(opp: OpportunitySvm) -> BidSvm:
...

def opportunity_callback(opportunity: Opportunity):
bid = generate_bid(typing.cast(OpportunitySvm, opportunity))
bid = await self.assess_opportunity(typing.cast(OpportunitySvm, opp))
await client.submit_bid(bid, subscribe_to_updates=True)
```

Expand All @@ -264,7 +295,7 @@ Searchers can submit bids through an HTTP POST call to the [`/v1/bids`](https://
curl -X POST https://pyth-express-relay-mainnet.asymmetric.re/v1/bids \
-H "Content-Type: application/json" \
-d '{
"chain_id": "development-solana",
"chain_id": "solana",
"transaction": "SGVsbG8sIFdvcmxkIQ=="
}'
```
Expand All @@ -280,7 +311,7 @@ Searchers can submit bids via Websocket to avoid additional network round-trips
"method": "post_bid",
"params": {
"bid": {
"chain_id": "development-solana",
"chain_id": "solana",
"transaction": "SGVsbG8sIFdvcmxkIQ=="
}
}
Expand Down
52 changes: 36 additions & 16 deletions pages/express-relay/websocket-api-reference.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -218,11 +218,12 @@ Refer to the examples below, one for each of the status options in EVM and SVM:
</Tabs.Tab>

<Tabs.Tab>
<Tabs items={[`pending`, `submitted`, `lost`, `won`, `expired`]}>
<Tabs items={[`pending`, `submitted`, `lost`, `won`, `failed`, `expired`]}>
<Tabs.Tab>
```json
// pending
// The temporary state, which means the auction for this bid is pending
// The temporary state which means the auction for this bid is pending.
// It will be updated to Lost or Submitted after the auction takes place.
{
"type": "bid_status_update",
"status": {
Expand All @@ -237,17 +238,17 @@ Refer to the examples below, one for each of the status options in EVM and SVM:
<Tabs.Tab>
```json
// submitted
/// The bid is submitted to the chain, with the transaction with the signature.
/// This state is temporary and will be updated to either lost or won after conclusion of the auction.
// The bid won the auction and was submitted to the chain, with the signature of the corresponding transaction provided in the result field.
// This state is temporary and will be updated to either Won or Failed after the transaction is included in a block, or Expired if the transaction expires before it is included.
{
"type": "bid_status_update",
"status": {
"id": "beedbeed-0e42-400f-a8ef-d78aa5422252",
"bid_status": {
// the enum for the bid_status
"type": "submitted",
// the hash of the transaction that the bid's calldata was included in
"result": "0xabc393b634fdf3eb45be8350fd16cd1b4add47b96059beacc1d8c20e51d75ec3"
// the signature of the transaction that contains the bid
"result": "Jb2urXPyEh4xiBgzYvwEFe4q1iMxG1DNxWGGQg94AmKgqFTwLAiTiHrYiYxwHUB4DV8u5ahNEVtMMDm3sNSRdTg"
}
}
}
Expand All @@ -256,17 +257,17 @@ Refer to the examples below, one for each of the status options in EVM and SVM:
<Tabs.Tab>
```json
// lost
/// The bid lost the auction.
/// The result will be None if the auction was concluded off-chain and no auction was submitted to the chain.
/// The result will be not None if another bid were selected for submission to the chain.
/// The signature of the transaction for the submitted bid is the result value.
// The bid lost the auction.
// The result will be None if the auction had no winner (because all bids were found to be invalid).
// The result will be Some if this bid lost to another bid.
// The signature of the transaction for the submitted bid is the result value.
{
"type": "bid_status_update",
"status": {
"id": "beedbeed-0e42-400f-a8ef-d78aa5422252",
"bid_status": {
"type": "lost",
"result": "0x99c2bf411330ae997632f88abe8f86c0d1f4c448f7d5061319d23814a0fb1135"
"result": "Jb2urXPyEh4xiBgzYvwEFe4q1iMxG1DNxWGGQg94AmKgqFTwLAiTiHrYiYxwHUB4DV8u5ahNEVtMMDm3sNSRdTg"
}
}
}
Expand All @@ -275,34 +276,53 @@ Refer to the examples below, one for each of the status options in EVM and SVM:
<Tabs.Tab>
```json
// won
/// The bid won the auction, with the transaction with the signature.
// The bid won the auction and was included in a block successfully.
{
"type": "bid_status_update",
"status": {
"id": "beedbeed-0e42-400f-a8ef-d78aa5422252",
"bid_status": {
// the enum for the bid_status
"type": "won",
// the hash of the transaction that the bid's calldata was included in
"result": "0xabc393b634fdf3eb45be8350fd16cd1b4add47b96059beacc1d8c20e51d75ec3"
// the signature of the transaction that included the bid
"result": "Jb2urXPyEh4xiBgzYvwEFe4q1iMxG1DNxWGGQg94AmKgqFTwLAiTiHrYiYxwHUB4DV8u5ahNEVtMMDm3sNSRdTg"
}
}
}
```
</Tabs.Tab>

<Tabs.Tab>
```json
// failed
// The bid was submitted on-chain, was included in a block, but resulted in a failed transaction.
{
"type": "bid_status_update",
"status": {
"id": "beedbeed-0e42-400f-a8ef-d78aa5422252",
"bid_status": {
// the enum for the bid_status
"type": "failed",
// the signature of the transaction that included the bid
"result": "Jb2urXPyEh4xiBgzYvwEFe4q1iMxG1DNxWGGQg94AmKgqFTwLAiTiHrYiYxwHUB4DV8u5ahNEVtMMDm3sNSRdTg"
}
}
}
```
</Tabs.Tab>

<Tabs.Tab>
```json
// expired
// /// The bid expired without being submitted on chain.
// The bid was submitted on-chain but expired before it was included in a block.
{
"type": "bid_status_update",
"status": {
"id": "beedbeed-0e42-400f-a8ef-d78aa5422252",
"bid_status": {
// the enum for the bid_status
"type": "expired",
// the hash of the transaction that the bid's calldata was included in
// the signature of the transaction that included the bid
"result": "Jb2urXPyEh4xiBgzYvwEFe4q1iMxG1DNxWGGQg94AmKgqFTwLAiTiHrYiYxwHUB4DV8u5ahNEVtMMDm3sNSRdTg",
}
}
Expand Down
Loading