From 27278c412a6763f3d57a83df3abac5ef08d6aa50 Mon Sep 17 00:00:00 2001 From: Dani Mehrjerdi Date: Fri, 1 Nov 2024 12:13:25 +0100 Subject: [PATCH] Add remove opportunties to sdk (#200) --- auction-server/src/opportunity/api.rs | 1 + sdk/js/package-lock.json | 4 +- sdk/js/package.json | 2 +- sdk/js/src/examples/simpleSearcherLimo.ts | 15 +- sdk/js/src/index.ts | 47 +++++- sdk/js/src/serverTypes.d.ts | 143 ++++++++++++------ sdk/js/src/types.ts | 9 +- sdk/python/express_relay/client.py | 20 ++- sdk/python/express_relay/models/__init__.py | 2 + sdk/python/express_relay/models/base.py | 2 + sdk/python/express_relay/models/svm.py | 33 ++++ .../searcher/examples/simple_searcher_svm.py | 9 +- sdk/python/pyproject.toml | 2 +- 13 files changed, 235 insertions(+), 54 deletions(-) diff --git a/auction-server/src/opportunity/api.rs b/auction-server/src/opportunity/api.rs index f9f9b2b5..90f0c1be 100644 --- a/auction-server/src/opportunity/api.rs +++ b/auction-server/src/opportunity/api.rs @@ -115,6 +115,7 @@ pub struct OpportunityDeleteV1Svm { } #[derive(Serialize, Deserialize, ToSchema, Clone, PartialEq, Debug)] +#[serde(tag = "version")] pub enum OpportunityDeleteSvm { #[serde(rename = "v1")] #[schema(title = "v1")] diff --git a/sdk/js/package-lock.json b/sdk/js/package-lock.json index 2456ac7c..45a73022 100644 --- a/sdk/js/package-lock.json +++ b/sdk/js/package-lock.json @@ -1,12 +1,12 @@ { "name": "@pythnetwork/express-relay-js", - "version": "0.12.3", + "version": "0.13.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@pythnetwork/express-relay-js", - "version": "0.12.3", + "version": "0.13.0", "license": "Apache-2.0", "dependencies": { "@coral-xyz/anchor": "^0.30.1", diff --git a/sdk/js/package.json b/sdk/js/package.json index e94a526a..86cce6d5 100644 --- a/sdk/js/package.json +++ b/sdk/js/package.json @@ -1,6 +1,6 @@ { "name": "@pythnetwork/express-relay-js", - "version": "0.12.3", + "version": "0.13.0", "description": "Utilities for interacting with the express relay protocol", "homepage": "https://github.com/pyth-network/per/tree/main/sdk/js", "author": "Douro Labs", diff --git a/sdk/js/src/examples/simpleSearcherLimo.ts b/sdk/js/src/examples/simpleSearcherLimo.ts index 5949291d..5ce85c69 100644 --- a/sdk/js/src/examples/simpleSearcherLimo.ts +++ b/sdk/js/src/examples/simpleSearcherLimo.ts @@ -6,7 +6,12 @@ import { Opportunity, OpportunitySvm, } from "../index"; -import { BidStatusUpdate, ChainId, SvmChainUpdate } from "../types"; +import { + BidStatusUpdate, + ChainId, + OpportunityDelete, + SvmChainUpdate, +} from "../types"; import { SVM_CONSTANTS } from "../const"; import * as anchor from "@coral-xyz/anchor"; @@ -43,7 +48,8 @@ class SimpleSearcherLimo { undefined, this.opportunityHandler.bind(this), this.bidStatusHandler.bind(this), - this.svmChainUpdateHandler.bind(this) + this.svmChainUpdateHandler.bind(this), + this.removeOpportunitiesHandler.bind(this) ); this.connectionSvm = new Connection(endpointSvm, "confirmed"); } @@ -186,6 +192,11 @@ class SimpleSearcherLimo { this.recentBlockhash[update.chain_id] = update.blockhash; } + // NOTE: Developers are responsible for implementing custom removal logic specific to their use case. + async removeOpportunitiesHandler(opportunityDelete: OpportunityDelete) { + console.log(`Opportunities ${opportunityDelete} don't exist anymore`); + } + async start() { try { await this.client.subscribeChains([argv.chainId]); diff --git a/sdk/js/src/index.ts b/sdk/js/src/index.ts index 0ec7e794..4abd2dc3 100644 --- a/sdk/js/src/index.ts +++ b/sdk/js/src/index.ts @@ -18,6 +18,7 @@ import { OpportunityCreate, TokenAmount, SvmChainUpdate, + OpportunityDelete, } from "./types"; import { Connection, @@ -97,6 +98,10 @@ export class Client { update: SvmChainUpdate ) => Promise; + private websocketRemoveOpportunitiesCallback?: ( + opportunityDelete: OpportunityDelete + ) => Promise; + private getAuthorization() { return this.clientOptions.apiKey ? { @@ -110,7 +115,10 @@ export class Client { wsOptions?: WsOptions, opportunityCallback?: (opportunity: Opportunity) => Promise, bidStatusCallback?: (statusUpdate: BidStatusUpdate) => Promise, - svmChainUpdateCallback?: (update: SvmChainUpdate) => Promise + svmChainUpdateCallback?: (update: SvmChainUpdate) => Promise, + removeOpportunitiesCallback?: ( + opportunityDelete: OpportunityDelete + ) => Promise ) { this.clientOptions = clientOptions; this.clientOptions.headers = { @@ -121,6 +129,7 @@ export class Client { this.websocketOpportunityCallback = opportunityCallback; this.websocketBidStatusCallback = bidStatusCallback; this.websocketSvmChainUpdateCallback = svmChainUpdateCallback; + this.websocketRemoveOpportunitiesCallback = removeOpportunitiesCallback; } private connectWebsocket() { @@ -158,6 +167,17 @@ export class Client { if (typeof this.websocketSvmChainUpdateCallback === "function") { await this.websocketSvmChainUpdateCallback(message.update); } + } else if ("type" in message && message.type === "remove_opportunities") { + if (typeof this.websocketRemoveOpportunitiesCallback === "function") { + await this.websocketRemoveOpportunitiesCallback({ + chainId: message.opportunity_delete.chain_id, + program: message.opportunity_delete.program, + permissionAccount: new PublicKey( + message.opportunity_delete.permission_account + ), + router: new PublicKey(message.opportunity_delete.router), + }); + } } else if ("id" in message && message.id) { // Response to a request sent earlier via the websocket with the same id const callback = this.callbackRouter[message.id]; @@ -333,6 +353,31 @@ export class Client { } } + /** + * Remove an opportunity from the server and update the searchers + * @param opportunity Opportunity to be removed + */ + async removeOpportunity(opportunity: OpportunityDelete) { + if (opportunity.program !== "limo") { + throw new ClientError("Only limo opportunities can be removed"); + } + + const client = createClient(this.clientOptions); + const body = { + chain_id: opportunity.chainId, + version: "v1" as const, + program: opportunity.program, + permission_account: opportunity.permissionAccount.toBase58(), + router: opportunity.router.toBase58(), + }; + const response = await client.DELETE("/v1/opportunities", { + body, + }); + if (response.error) { + throw new ClientError(response.error.error); + } + } + /** * Submits a raw bid for a permission key * @param bid diff --git a/sdk/js/src/serverTypes.d.ts b/sdk/js/src/serverTypes.d.ts index 2d05640b..0398549f 100644 --- a/sdk/js/src/serverTypes.d.ts +++ b/sdk/js/src/serverTypes.d.ts @@ -35,17 +35,19 @@ export interface paths { * and will be available for bidding. */ post: operations["post_opportunity"]; + /** Delete all opportunities for specified data. */ + delete: operations["delete_opportunities"]; }; "/v1/opportunities/quote": { /** - * Submit a quote request + * Submit a quote request. * @description The server will estimate the quote price, which will be used to create an opportunity. * After a certain time, searcher bids are collected, the winning signed bid will be returned along with the estimated price. */ post: operations["post_quote"]; }; "/v1/opportunities/{opportunity_id}/bids": { - /** Bid on opportunity */ + /** Bid on opportunity. */ post: operations["opportunity_bid"]; }; "/v1/profiles/access_tokens": { @@ -234,22 +236,22 @@ export interface components { */ amount: string; /** - * @description The latest unix timestamp in seconds until which the bid is valid + * @description The latest unix timestamp in seconds until which the bid is valid. * @example 1000000000000000000 */ deadline: string; /** - * @description Executor address + * @description The executor address. * @example 0x5FbDB2315678afecb367f032d93F642f64180aa2 */ executor: string; /** - * @description The nonce of the bid permit signature + * @description The nonce of the bid permit signature. * @example 123 */ nonce: string; /** - * @description The opportunity permission key + * @description The opportunity permission key. * @example 0xdeadbeefcafe */ permission_key: string; @@ -265,7 +267,7 @@ export interface components { /** @example OK */ status: string; }; - /** @description The input type for creating a new opportunity */ + /** @description The input type for creating a new opportunity. */ OpportunityCreate: | components["schemas"]["OpportunityCreateEvm"] | components["schemas"]["OpportunityCreateSvm"]; @@ -273,11 +275,11 @@ export interface components { /** @enum {string} */ version: "v1"; }; - /** @description Program specific parameters for the opportunity */ + /** @description Program specific parameters for the opportunity. */ OpportunityCreateProgramParamsV1Svm: | { /** - * @description The Limo order to be executed, encoded in base64 + * @description The Limo order to be executed, encoded in base64. * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 */ order: string; @@ -309,10 +311,10 @@ export interface components { version: "v1"; }; /** - * @description Opportunity parameters needed for on-chain execution + * @description Opportunity parameters needed for on-chain execution. * If a searcher signs the opportunity and have approved enough tokens to opportunity adapter, * by calling this target contract with the given target calldata and structures, they will - * send the tokens specified in the sell_tokens field and receive the tokens specified in the buy_tokens field. + * send the tokens specified in the `sell_tokens` field and receive the tokens specified in the `buy_tokens` field. */ OpportunityCreateV1Evm: { buy_tokens: components["schemas"]["TokenAmountEvm"][]; @@ -345,12 +347,12 @@ export interface components { }; /** * @description Opportunity parameters needed for on-chain execution. - * Parameters may differ for each program + * Parameters may differ for each program. */ OpportunityCreateV1Svm: ( | { /** - * @description The Limo order to be executed, encoded in base64 + * @description The Limo order to be executed, encoded in base64. * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 */ order: string; @@ -385,34 +387,59 @@ export interface components { */ chain_id: string; /** - * @description The permission account to be permitted by the ER contract for the opportunity execution of the protocol + * @description The permission account to be permitted by the ER contract for the opportunity execution of the protocol. * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 */ permission_account: string; /** - * @description The router account to be used for the opportunity execution of the protocol + * @description The router account to be used for the opportunity execution of the protocol. * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 */ router: string; sell_tokens: components["schemas"]["TokenAmountSvm"][]; /** * Format: int64 - * @description The slot where the program params were fetched from using the RPC + * @description The slot where the program params were fetched from using the RPC. * @example 293106477 */ slot: number; }; + /** @description The input type for deleting opportunities. */ + OpportunityDelete: components["schemas"]["OpportunityDeleteSvm"]; + OpportunityDeleteSvm: components["schemas"]["OpportunityDeleteV1Svm"] & { + /** @enum {string} */ + version: "v1"; + }; + /** @description Opportunity parameters needed for deleting live opportunities. */ + OpportunityDeleteV1Svm: { + /** + * @description The chain id for the opportunity. + * @example solana + */ + chain_id: string; + /** + * @description The permission account for the opportunity. + * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 + */ + permission_account: string; + program: components["schemas"]["ProgramSvm"]; + /** + * @description The router account for the opportunity. + * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 + */ + router: string; + }; OpportunityEvm: (components["schemas"]["OpportunityParamsV1Evm"] & { /** @enum {string} */ version: "v1"; }) & { /** - * @description Creation time of the opportunity (in microseconds since the Unix epoch) + * @description Creation time of the opportunity (in microseconds since the Unix epoch). * @example 1700000000000000 */ creation_time: number; /** - * @description The opportunity unique id + * @description The opportunity unique id. * @example obo3ee3e-58cc-4372-a567-0e02b2c3d479 */ opportunity_id: string; @@ -430,17 +457,17 @@ export interface components { OpportunityParamsV1Evm: components["schemas"]["OpportunityCreateV1Evm"]; /** * @description Opportunity parameters needed for on-chain execution. - * Parameters may differ for each program + * Parameters may differ for each program. */ OpportunityParamsV1Svm: ( | { /** - * @description The Limo order to be executed, encoded in base64 + * @description The Limo order to be executed, encoded in base64. * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 */ order: string; /** - * @description Address of the order account + * @description Address of the order account. * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 */ order_address: string; @@ -451,25 +478,25 @@ export interface components { buy_token: components["schemas"]["TokenAmountSvm"]; /** * Format: double - * @description The maximum slippage percentage that the user is willing to accept + * @description The maximum slippage percentage that the user is willing to accept. * @example 0.5 */ maximum_slippage_percentage: number; /** - * @description The permission account to be permitted by the ER contract for the opportunity execution of the protocol + * @description The permission account to be permitted by the ER contract for the opportunity execution of the protocol. * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 */ permission_account: string; /** @enum {string} */ program: "phantom"; /** - * @description The router account to be used for the opportunity execution of the protocol + * @description The router account to be used for the opportunity execution of the protocol. * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 */ router_account: string; sell_token: components["schemas"]["TokenAmountSvm"]; /** - * @description The user wallet address which requested the quote from the wallet + * @description The user wallet address which requested the quote from the wallet. * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 */ user_wallet_address: string; @@ -483,27 +510,29 @@ export interface components { version: "v1"; }) & { /** - * @description Creation time of the opportunity (in microseconds since the Unix epoch) + * @description Creation time of the opportunity (in microseconds since the Unix epoch). * @example 1700000000000000 */ creation_time: number; /** - * @description The opportunity unique id + * @description The opportunity unique id. * @example obo3ee3e-58cc-4372-a567-0e02b2c3d479 */ opportunity_id: string; /** * Format: int64 - * @description The slot where the program params were fetched from using the RPC + * @description The slot where the program params were fetched from using the RPC. * @example 293106477 */ slot: number; }; + /** @enum {string} */ + ProgramSvm: "phantom" | "limo"; Quote: components["schemas"]["QuoteSvm"]; QuoteCreate: components["schemas"]["QuoteCreateSvm"]; /** - * @description Parameters needed to create a new opportunity from the Phantom wallet - * Auction server will extract the output token price for the auction + * @description Parameters needed to create a new opportunity from the Phantom wallet. + * Auction server will extract the output token price for the auction. */ QuoteCreatePhantomV1Svm: { /** @@ -553,26 +582,26 @@ export interface components { }; QuoteV1Svm: { /** - * @description The chain id for the quote + * @description The chain id for the quote. * @example solana */ chain_id: string; /** * Format: int64 - * @description The expiration time of the quote (in seconds since the Unix epoch) + * @description The expiration time of the quote (in seconds since the Unix epoch). * @example 1700000000000000 */ expiration_time: number; input_token: components["schemas"]["TokenAmountSvm"]; /** * Format: double - * @description The maximum slippage percentage that the user is willing to accept + * @description The maximum slippage percentage that the user is willing to accept. * @example 0.5 */ maximum_slippage_percentage: number; output_token: components["schemas"]["TokenAmountSvm"]; /** - * @description The signed transaction for the quote to be executed on chain which is valid until the expiration time + * @description The signed transaction for the quote to be executed on chain which is valid until the expiration time. * @example SGVsbG8sIFdvcmxkIQ== */ transaction: string; @@ -589,13 +618,13 @@ export interface components { status: "error"; }; /** - * @description This enum is used to send the result for a specific client request with the same id - * id is only None when the client message is invalid + * @description This enum is used to send the result for a specific client request with the same id. + * Id is only None when the client message is invalid. */ ServerResultResponse: components["schemas"]["ServerResultMessage"] & { id?: string | null; }; - /** @description This enum is used to send an update to the client for any subscriptions made */ + /** @description This enum is used to send an update to the client for any subscriptions made. */ ServerUpdateResponse: | { opportunity: components["schemas"]["Opportunity"]; @@ -611,6 +640,11 @@ export interface components { /** @enum {string} */ type: "svm_chain_update"; update: components["schemas"]["SvmChainUpdate"]; + } + | { + opportunity_delete: components["schemas"]["OpportunityDelete"]; + /** @enum {string} */ + type: "remove_opportunities"; }; SimulatedBid: | components["schemas"]["SimulatedBidEvm"] @@ -717,12 +751,12 @@ export interface components { }; TokenAmountEvm: { /** - * @description Token amount + * @description The token amount. * @example 1000 */ amount: string; /** - * @description Token contract address + * @description The token contract address. * @example 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 */ token: string; @@ -730,12 +764,12 @@ export interface components { TokenAmountSvm: { /** * Format: int64 - * @description Token amount in lamports + * @description The token amount in lamports. * @example 1000 */ amount: number; /** - * @description Token contract address + * @description The token contract address. * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 */ token: string; @@ -874,7 +908,7 @@ export interface operations { query?: { /** @example op_sepolia */ chain_id?: string | null; - /** @description Get opportunities in live or historical mode */ + /** @description Get opportunities in live or historical mode. */ mode?: components["schemas"]["OpportunityMode"]; /** * @description The permission key to filter the opportunities by. Used only in historical mode. @@ -931,8 +965,29 @@ export interface operations { }; }; }; + /** Delete all opportunities for specified data. */ + delete_opportunities: { + requestBody: { + content: { + "application/json": components["schemas"]["OpportunityDelete"]; + }; + }; + responses: { + /** @description Opportunities deleted successfully */ + 204: { + content: never; + }; + 400: components["responses"]["ErrorBodyResponse"]; + /** @description Chain id was not found */ + 404: { + content: { + "application/json": components["schemas"]["ErrorBodyResponse"]; + }; + }; + }; + }; /** - * Submit a quote request + * Submit a quote request. * @description The server will estimate the quote price, which will be used to create an opportunity. * After a certain time, searcher bids are collected, the winning signed bid will be returned along with the estimated price. */ @@ -958,7 +1013,7 @@ export interface operations { }; }; }; - /** Bid on opportunity */ + /** Bid on opportunity. */ opportunity_bid: { parameters: { path: { diff --git a/sdk/js/src/types.ts b/sdk/js/src/types.ts index eff0f7d8..27fa1309 100644 --- a/sdk/js/src/types.ts +++ b/sdk/js/src/types.ts @@ -1,6 +1,6 @@ import { Address, Hex } from "viem"; import type { components } from "./serverTypes"; -import { Blockhash, PublicKey, Transaction } from "@solana/web3.js"; +import { PublicKey, Transaction } from "@solana/web3.js"; import { OrderStateAndAddress } from "@kamino-finance/limo-sdk/dist/utils"; /** @@ -243,3 +243,10 @@ export type SvmConstantsConfig = { }; export type SvmChainUpdate = components["schemas"]["SvmChainUpdate"]; + +export type OpportunityDelete = { + chainId: ChainId; + permissionAccount: PublicKey; + program: components["schemas"]["ProgramSvm"]; + router: PublicKey; +}; diff --git a/sdk/python/express_relay/client.py b/sdk/python/express_relay/client.py index 70ea4f86..706f677a 100644 --- a/sdk/python/express_relay/client.py +++ b/sdk/python/express_relay/client.py @@ -44,6 +44,7 @@ OpportunityRoot, ClientMessage, BidResponseRoot, + OpportunityDelete, ) from express_relay.models.base import UnsupportedOpportunityVersionException from express_relay.models.evm import OpportunityEvm @@ -117,6 +118,9 @@ def __init__( svm_chain_update_callback: ( Callable[[SvmChainUpdate], Coroutine[Any, Any, Any]] | None ) = None, + remove_opportunities_callback: ( + Callable[[OpportunityDelete], Coroutine[Any, Any, Any]] | None + ) = None, timeout_response_secs: int = 10, ws_options: dict[str, Any] | None = None, http_options: dict[str, Any] | None = None, @@ -127,6 +131,7 @@ def __init__( opportunity_callback: An async function that serves as the callback on a new opportunity. Should take in one external argument of type Opportunity. bid_status_callback: An async function that serves as the callback on a new bid status update. Should take in one external argument of type BidStatusUpdate. svm_chain_update_callback: An async function that serves as the callback on a new svm chain update. Should take in one external argument of type SvmChainUpdate. + remove_opportunities_callback: An async function that serves as the callback on an opportunities delete. Should take in one external argument of type OpportunityDelete. timeout_response_secs: The number of seconds to wait for a response message from the server. ws_options: Keyword arguments to pass to the websocket connection. http_options: Keyword arguments to pass to the HTTP client. @@ -157,6 +162,7 @@ def __init__( self.opportunity_callback = opportunity_callback self.bid_status_callback = bid_status_callback self.svm_chain_update_callback = svm_chain_update_callback + self.remove_opportunities_callback = remove_opportunities_callback if self.api_key: authorization_header = f"Bearer {self.api_key}" if "headers" not in self.http_options: @@ -176,7 +182,8 @@ async def start_ws(self): if not hasattr(self, "ws_loop"): ws_call = self.ws_handler( - self.opportunity_callback, self.bid_status_callback, self.svm_chain_update_callback + self.opportunity_callback, self.bid_status_callback, + self.svm_chain_update_callback, self.remove_opportunities_callback, ) self.ws_loop = asyncio.create_task(ws_call) @@ -325,6 +332,9 @@ async def ws_handler( svm_chain_update_callback: ( Callable[[SvmChainUpdate], Coroutine[Any, Any, Any]] | None ) = None, + remove_opportunities_callback: ( + Callable[[OpportunityDelete], Coroutine[Any, Any, Any]] | None + ) = None, ): """ Continually handles new ws messages as they are received from the server via websocket. @@ -333,6 +343,7 @@ async def ws_handler( opportunity_callback: An async function that serves as the callback on a new opportunity. Should take in one external argument of type Opportunity. bid_status_callback: An async function that serves as the callback on a new bid status update. Should take in one external argument of type BidStatusUpdate. svm_chain_update_callback: An async function that serves as the callback on a new svm chain update. Should take in one external argument of type SvmChainUpdate. + remove_opportunities_callback: An async function that serves as the callback on an opportunities delete. Should take in one external argument of type OpportunityDelete. """ if not self.ws: raise ExpressRelayClientException("Websocket not connected") @@ -363,6 +374,13 @@ async def ws_handler( ) asyncio.create_task(svm_chain_update_callback(svm_chain_update)) + elif msg_json.get("type") == "remove_opportunities": + if remove_opportunities_callback is not None: + remove_opportunities = OpportunityDelete.model_validate( + msg_json["opportunity_delete"] + ) + asyncio.create_task(remove_opportunities_callback(remove_opportunities)) + elif msg_json.get("id"): future = self.ws_msg_futures.pop(msg_json["id"]) future.set_result(msg_json) diff --git a/sdk/python/express_relay/models/__init__.py b/sdk/python/express_relay/models/__init__.py index 2fc0624a..f060c6a8 100644 --- a/sdk/python/express_relay/models/__init__.py +++ b/sdk/python/express_relay/models/__init__.py @@ -18,6 +18,7 @@ ) from express_relay.models.svm import ( BidSvm, + OpportunityDeleteSvm, OpportunitySvm, SvmTransaction, BidStatusSvm, @@ -114,6 +115,7 @@ class OpportunityParams(BaseModel): Opportunity = Union[OpportunityEvm, OpportunitySvm] OpportunityRoot = RootModel[Opportunity] +OpportunityDelete = OpportunityDeleteSvm class SubscribeMessageParams(BaseModel): diff --git a/sdk/python/express_relay/models/base.py b/sdk/python/express_relay/models/base.py index 0adc9c73..fe21d312 100644 --- a/sdk/python/express_relay/models/base.py +++ b/sdk/python/express_relay/models/base.py @@ -8,6 +8,8 @@ class UnsupportedOpportunityVersionException(Exception): pass +class UnsupportedOpportunityDeleteVersionException(Exception): + pass class BidStatus(Enum): PENDING = "pending" diff --git a/sdk/python/express_relay/models/svm.py b/sdk/python/express_relay/models/svm.py index 18ba10cb..a368a74e 100644 --- a/sdk/python/express_relay/models/svm.py +++ b/sdk/python/express_relay/models/svm.py @@ -1,5 +1,6 @@ import base64 from datetime import datetime +from enum import Enum from typing import Any, Annotated, ClassVar from pydantic import ( @@ -21,6 +22,7 @@ from express_relay.models.base import ( IntString, UUIDString, + UnsupportedOpportunityDeleteVersionException, UnsupportedOpportunityVersionException, BidStatus, ) @@ -322,3 +324,34 @@ class SvmChainUpdate(BaseModel): """ chain_id: str blockhash: SvmHash + + +class ProgramSvm(Enum): + LIMO = "limo" + PHANTOM = "phantom" + + +class OpportunityDeleteSvm(BaseModel): + """ + Attributes: + chain_id: The chain ID for opportunities to be removed. + program: The program which this opportunities to be removed. + permission_account: The permission account for the opportunities to be removed. + router: The router for opportunties to be removed. + """ + chain_id: str + program: ProgramSvm + permission_account: SvmAddress + router: SvmAddress + version: str + + supported_versions: ClassVar[list[str]] = ["v1"] + + @model_validator(mode="before") + @classmethod + def check_version(cls, data): + if data["version"] not in cls.supported_versions: + raise UnsupportedOpportunityDeleteVersionException( + f"Cannot handle opportunity version: {data['version']}. Please upgrade your client." + ) + return data diff --git a/sdk/python/express_relay/searcher/examples/simple_searcher_svm.py b/sdk/python/express_relay/searcher/examples/simple_searcher_svm.py index 26c88a69..8332f9af 100644 --- a/sdk/python/express_relay/searcher/examples/simple_searcher_svm.py +++ b/sdk/python/express_relay/searcher/examples/simple_searcher_svm.py @@ -14,7 +14,9 @@ ExpressRelayClient, ) from express_relay.constants import SVM_CONFIGS -from express_relay.models import BidStatusUpdate, Opportunity +from express_relay.models import ( + BidStatusUpdate, Opportunity, OpportunityDelete +) from express_relay.models.base import BidStatus from express_relay.models.svm import BidSvm, OpportunitySvm, SvmChainUpdate, SvmHash from express_relay.svm.generated.express_relay.accounts.express_relay_metadata import ( @@ -50,6 +52,7 @@ def __init__( self.opportunity_callback, self.bid_status_callback, self.svm_chain_update_callback, + self.remove_opportunities_callback, ) self.private_key = private_key self.bid_amount = bid_amount @@ -182,6 +185,10 @@ async def assess_opportunity(self, opp: OpportunitySvm) -> BidSvm | None: async def svm_chain_update_callback(self, svm_chain_update: SvmChainUpdate): self.recent_blockhash[svm_chain_update.chain_id] = svm_chain_update.blockhash + # NOTE: Developers are responsible for implementing custom removal logic specific to their use case. + async def remove_opportunities_callback(self, opportunity_delete: OpportunityDelete): + print(f"Opportunities {opportunity_delete} don't exist anymore") + async def main(): parser = argparse.ArgumentParser() diff --git a/sdk/python/pyproject.toml b/sdk/python/pyproject.toml index 69444e19..e4599a9a 100644 --- a/sdk/python/pyproject.toml +++ b/sdk/python/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "express-relay" -version = "0.12.3" +version = "0.13.0" description = "Utilities for searchers and protocols to interact with the Express Relay protocol." authors = ["dourolabs"] license = "Apache-2.0"