diff --git a/pages/express-relay/integrate-as-searcher/evm.mdx b/pages/express-relay/integrate-as-searcher/evm.mdx index 6fa68128..d9620cb5 100644 --- a/pages/express-relay/integrate-as-searcher/evm.mdx +++ b/pages/express-relay/integrate-as-searcher/evm.mdx @@ -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"]); ``` @@ -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) diff --git a/pages/express-relay/integrate-as-searcher/svm.mdx b/pages/express-relay/integrate-as-searcher/svm.mdx index acd8eb48..6a56c893 100644 --- a/pages/express-relay/integrate-as-searcher/svm.mdx +++ b/pages/express-relay/integrate-as-searcher/svm.mdx @@ -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. @@ -19,16 +19,22 @@ 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(); ``` @@ -36,22 +42,30 @@ await client.subscribeChains(["development-solana"]); 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()) ``` @@ -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. @@ -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"] } } ``` @@ -125,32 +139,41 @@ import * as limo from "@kamino-finance/limo-sdk"; * @returns The generated bid object */ async generateBid(opportunity: OpportunitySvm): Promise { - 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; } ``` @@ -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 @@ -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); } ``` @@ -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) ``` @@ -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==" }' ``` @@ -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==" } } diff --git a/pages/express-relay/websocket-api-reference.mdx b/pages/express-relay/websocket-api-reference.mdx index a20c364a..dc04f1bb 100644 --- a/pages/express-relay/websocket-api-reference.mdx +++ b/pages/express-relay/websocket-api-reference.mdx @@ -218,11 +218,12 @@ Refer to the examples below, one for each of the status options in EVM and SVM: - + ```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": { @@ -237,8 +238,8 @@ Refer to the examples below, one for each of the status options in EVM and SVM: ```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": { @@ -246,8 +247,8 @@ Refer to the examples below, one for each of the status options in EVM and SVM: "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" } } } @@ -255,18 +256,17 @@ Refer to the examples below, one for each of the status options in EVM and SVM: ```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. + // lost + // The bid lost the auction. + // This bid status will have a result field containing the signature of the transaction corresponding to the winning bid, + // unless the auction had no winner (because all bids were found to be invalid). { "type": "bid_status_update", "status": { "id": "beedbeed-0e42-400f-a8ef-d78aa5422252", "bid_status": { "type": "lost", - "result": "0x99c2bf411330ae997632f88abe8f86c0d1f4c448f7d5061319d23814a0fb1135" + "result": "Jb2urXPyEh4xiBgzYvwEFe4q1iMxG1DNxWGGQg94AmKgqFTwLAiTiHrYiYxwHUB4DV8u5ahNEVtMMDm3sNSRdTg" } } } @@ -275,7 +275,7 @@ Refer to the examples below, one for each of the status options in EVM and SVM: ```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": { @@ -283,18 +283,37 @@ Refer to the examples below, one for each of the status options in EVM and SVM: "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" } } } ``` + + ```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" + } + } + } + ``` + + ```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": { @@ -302,7 +321,7 @@ Refer to the examples below, one for each of the status options in EVM and SVM: "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", } }