From 993c5f3e7636257939b96cf84dc39ec7a822cca7 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Wed, 4 Dec 2024 19:59:03 +0000 Subject: [PATCH 1/7] update new opportunity stuff --- .../integrate-as-searcher/svm.mdx | 36 +++++++++++++------ 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/pages/express-relay/integrate-as-searcher/svm.mdx b/pages/express-relay/integrate-as-searcher/svm.mdx index acd8eb48..2a335eeb 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://explorer.solana.com/address/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"] } } ``` From 005706de13d7d78ef9b30fe7e8bf48ea2c0c0bd4 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Wed, 4 Dec 2024 20:07:09 +0000 Subject: [PATCH 2/7] pass --- .../integrate-as-searcher/evm.mdx | 6 +- .../integrate-as-searcher/svm.mdx | 75 +++++++++++-------- 2 files changed, 46 insertions(+), 35 deletions(-) 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 2a335eeb..6530dfc4 100644 --- a/pages/express-relay/integrate-as-searcher/svm.mdx +++ b/pages/express-relay/integrate-as-searcher/svm.mdx @@ -127,18 +127,12 @@ See the following examples of how to construct a bid object via the SDKs: Below is an excerpt of example code. See the full example in the [Typescript SDK](https://github.com/pyth-network/per/blob/4be711525948cf24c0ebd4ebab007dc7f51b7069/sdk/js/src/examples/simpleSearcherLimo.ts). ```typescript copy -import { OpportunitySvm } from "../index"; -import { BidSvm } from "../types"; - -import * as anchor from "@coral-xyz/anchor"; -import * as limo from "@kamino-finance/limo-sdk"; - -/** - * Generates a bid for a given opportunity. The transaction in this bid transfers assets from the searcher's wallet to fulfill the limit order. - * @param opportunity The SVM opportunity to bid on - * @returns The generated bid object - */ -async generateBid(opportunity: OpportunitySvm): Promise { + /** + * Generates a bid for a given opportunity. The transaction in this bid transfers assets from the searcher's wallet to fulfill the limit order. + * @param opportunity The SVM opportunity to bid on + * @returns The generated bid object + */ + async generateBid(opportunity: OpportunitySvm): Promise { const order = opportunity.order; const limoClient = new limo.LimoClient( this.connectionSvm, @@ -146,26 +140,35 @@ async generateBid(opportunity: OpportunitySvm): Promise { ); const ixsTakeOrder = await this.generateTakeOrderIxs(limoClient, order); - const txRaw = new anchor.web3.Transaction().add(...ixsTakeOrder); + const feeInstruction = ComputeBudgetProgram.setComputeUnitPrice({ + microLamports: + this.latestChainUpdate[this.chainId].latestPrioritizationFee, + }); + const txRaw = new anchor.web3.Transaction().add( + feeInstruction, + ...ixsTakeOrder + ); - const bidData = await this.getBidData(limoClient, order); + const bidAmount = await this.getBidAmount(order); + const config = await this.getExpressRelayConfig(); const bid = await this.client.constructSvmBid( txRaw, this.searcher.publicKey, - bidData.router, + getPdaAuthority(limoClient.getProgramID(), order.state.globalConfig), order.address, - bidData.bidAmount, + bidAmount, new anchor.BN(Math.round(Date.now() / 1000 + DAY_IN_SECONDS)), this.chainId, - bidData.relayerSigner, - bidData.relayerFeeReceiver + config.relayerSigner, + config.feeReceiverRelayer ); - bid.transaction.recentBlockhash = this.recentBlockhash[this.chainId]; + bid.transaction.recentBlockhash = + this.latestChainUpdate[this.chainId].blockhash; bid.transaction.sign(this.searcher); return bid; -} + } ``` @@ -193,24 +196,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 @@ -247,9 +258,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); } ``` @@ -266,7 +277,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) ``` @@ -278,7 +289,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==" }' ``` @@ -294,7 +305,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==" } } From ec4ba0111b7d918b5657478534472ddd5c4775a4 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Wed, 4 Dec 2024 20:12:11 +0000 Subject: [PATCH 3/7] add limo --- pages/express-relay/integrate-as-searcher/svm.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/express-relay/integrate-as-searcher/svm.mdx b/pages/express-relay/integrate-as-searcher/svm.mdx index 6530dfc4..70de1a93 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](https://explorer.solana.com/address/LiMoM9rMhrdYrfzUCxQppvxCSG1FcrUK9G8uLq4A1GF) program. +SVM Express Relay searchers fulfill opportunities representing limit orders on the [Limo](https://solscan.io/account/LiMoM9rMhrdYrfzUCxQppvxCSG1FcrUK9G8uLq4A1GF) program. From 830756152339427e887ee806c82f996889e530f1 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Wed, 4 Dec 2024 20:31:54 +0000 Subject: [PATCH 4/7] add failed --- .../express-relay/websocket-api-reference.mdx | 50 +++++++++++++------ 1 file changed, 35 insertions(+), 15 deletions(-) diff --git a/pages/express-relay/websocket-api-reference.mdx b/pages/express-relay/websocket-api-reference.mdx index a20c364a..fe092b80 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": { @@ -247,7 +248,7 @@ Refer to the examples below, one for each of the status options in EVM and SVM: // the enum for the bid_status "type": "submitted", // the hash of the transaction that the bid's calldata was included in - "result": "0xabc393b634fdf3eb45be8350fd16cd1b4add47b96059beacc1d8c20e51d75ec3" + "result": "Jb2urXPyEh4xiBgzYvwEFe4q1iMxG1DNxWGGQg94AmKgqFTwLAiTiHrYiYxwHUB4DV8u5ahNEVtMMDm3sNSRdTg" } } } @@ -256,17 +257,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. + // 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" } } } @@ -275,7 +276,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 +284,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 +322,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", } } From 547608d3a8f5f5b6ddf96f1e9d1aebf657cc2148 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Wed, 4 Dec 2024 20:43:06 +0000 Subject: [PATCH 5/7] go --- pages/express-relay/websocket-api-reference.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/express-relay/websocket-api-reference.mdx b/pages/express-relay/websocket-api-reference.mdx index fe092b80..a652607f 100644 --- a/pages/express-relay/websocket-api-reference.mdx +++ b/pages/express-relay/websocket-api-reference.mdx @@ -247,7 +247,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": "submitted", - // the hash of the transaction that the bid's calldata was included in + // the signature of the transaction that contains the bid "result": "Jb2urXPyEh4xiBgzYvwEFe4q1iMxG1DNxWGGQg94AmKgqFTwLAiTiHrYiYxwHUB4DV8u5ahNEVtMMDm3sNSRdTg" } } From a89d24b1517b578c6d472c94247d9c266454ea86 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Wed, 4 Dec 2024 20:49:32 +0000 Subject: [PATCH 6/7] clean --- .../integrate-as-searcher/svm.mdx | 90 ++++++++++--------- 1 file changed, 48 insertions(+), 42 deletions(-) diff --git a/pages/express-relay/integrate-as-searcher/svm.mdx b/pages/express-relay/integrate-as-searcher/svm.mdx index 70de1a93..6a56c893 100644 --- a/pages/express-relay/integrate-as-searcher/svm.mdx +++ b/pages/express-relay/integrate-as-searcher/svm.mdx @@ -127,48 +127,54 @@ See the following examples of how to construct a bid object via the SDKs: Below is an excerpt of example code. See the full example in the [Typescript SDK](https://github.com/pyth-network/per/blob/4be711525948cf24c0ebd4ebab007dc7f51b7069/sdk/js/src/examples/simpleSearcherLimo.ts). ```typescript copy - /** - * Generates a bid for a given opportunity. The transaction in this bid transfers assets from the searcher's wallet to fulfill the limit order. - * @param opportunity The SVM opportunity to bid on - * @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 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; - } +import { OpportunitySvm } from "../index"; +import { BidSvm } from "../types"; + +import * as anchor from "@coral-xyz/anchor"; +import * as limo from "@kamino-finance/limo-sdk"; + +/** + * Generates a bid for a given opportunity. The transaction in this bid transfers assets from the searcher's wallet to fulfill the limit order. + * @param opportunity The SVM opportunity to bid on + * @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 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; +} ``` From 3450a4d2baa1ff1e70957824ef990bd6114f56cc Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Fri, 6 Dec 2024 18:14:57 +0000 Subject: [PATCH 7/7] go --- pages/express-relay/websocket-api-reference.mdx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pages/express-relay/websocket-api-reference.mdx b/pages/express-relay/websocket-api-reference.mdx index a652607f..dc04f1bb 100644 --- a/pages/express-relay/websocket-api-reference.mdx +++ b/pages/express-relay/websocket-api-reference.mdx @@ -256,11 +256,10 @@ Refer to the examples below, one for each of the status options in EVM and SVM: ```json - // lost + // lost // 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. + // 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": {