diff --git a/auction-server/.clippy.toml b/auction-server/.clippy.toml new file mode 100644 index 00000000..c4940893 --- /dev/null +++ b/auction-server/.clippy.toml @@ -0,0 +1 @@ +ignore-interior-mutability = ["bytes::Bytes", "auction_server::opportunity::entities::opportunity::OpportunityKey", "ethers::types::Bytes"] diff --git a/auction-server/src/api.rs b/auction-server/src/api.rs index 3bbe0187..7ee65eb8 100644 --- a/auction-server/src/api.rs +++ b/auction-server/src/api.rs @@ -350,7 +350,9 @@ pub async fn start_api(run_options: RunOptions, store: Arc) -> Result< opportunity::QuoteV1Svm, opportunity::OpportunityDelete, opportunity::OpportunityDeleteSvm, + opportunity::OpportunityDeleteEvm, opportunity::OpportunityDeleteV1Svm, + opportunity::OpportunityDeleteV1Evm, opportunity::ProgramSvm, ErrorBodyResponse, diff --git a/auction-server/src/auction.rs b/auction-server/src/auction.rs index 0192edf7..677a9a8e 100644 --- a/auction-server/src/auction.rs +++ b/auction-server/src/auction.rs @@ -13,12 +13,15 @@ use { PermissionKeySvm, }, models, - opportunity::service::{ - get_live_opportunities::GetOpportunitiesInput, - ChainType as OpportunityChainType, - ChainTypeEvm as OpportunityChainTypeEvm, - ChainTypeSvm as OpportunityChainTypeSvm, - Service as OpportunityService, + opportunity::{ + self, + service::{ + get_live_opportunities::GetOpportunitiesInput, + ChainType as OpportunityChainType, + ChainTypeEvm as OpportunityChainTypeEvm, + ChainTypeSvm as OpportunityChainTypeSvm, + Service as OpportunityService, + }, }, server::{ EXIT_CHECK_INTERVAL, @@ -1224,7 +1227,10 @@ async fn verify_signatures_svm( let opportunities = store_new .opportunity_service_svm .get_live_opportunities(GetOpportunitiesInput { - key: (bid.chain_id.clone(), permission_key.clone()), + key: opportunity::entities::OpportunityKey( + bid.chain_id.clone(), + permission_key.clone(), + ), }) .await; opportunities.into_iter().any(|opportunity| { @@ -1469,7 +1475,10 @@ pub trait ChainStore: !self .get_opportunity_service(store_new) .get_live_opportunities(GetOpportunitiesInput { - key: (self.get_name().clone(), permission_key.clone()), + key: opportunity::entities::OpportunityKey( + self.get_name().clone(), + permission_key.clone(), + ), }) .await .is_empty() diff --git a/auction-server/src/opportunity/api.rs b/auction-server/src/opportunity/api.rs index 38e8d470..7ad18a0b 100644 --- a/auction-server/src/opportunity/api.rs +++ b/auction-server/src/opportunity/api.rs @@ -114,6 +114,18 @@ pub struct OpportunityDeleteV1Svm { pub program: ProgramSvm, } +/// Opportunity parameters needed for deleting live opportunities. +#[serde_as] +#[derive(Serialize, Deserialize, ToSchema, Clone, PartialEq, Debug)] +pub struct OpportunityDeleteV1Evm { + /// The permission key of the opportunity. + #[schema(example = "0xdeadbeefcafe", value_type = String)] + pub permission_key: Bytes, + /// The chain id for the opportunity. + #[schema(example = "solana", value_type = String)] + pub chain_id: ChainId, +} + #[derive(Serialize, Deserialize, ToSchema, Clone, PartialEq, Debug)] #[serde(tag = "version")] pub enum OpportunityDeleteSvm { @@ -122,12 +134,24 @@ pub enum OpportunityDeleteSvm { V1(OpportunityDeleteV1Svm), } +#[derive(Serialize, Deserialize, ToSchema, Clone, PartialEq, Debug)] +#[serde(tag = "version")] +pub enum OpportunityDeleteEvm { + #[serde(rename = "v1")] + #[schema(title = "v1")] + V1(OpportunityDeleteV1Evm), +} + /// The input type for deleting opportunities. #[derive(Serialize, Deserialize, ToSchema, Clone, PartialEq, Debug)] -#[serde(untagged)] +#[serde(tag = "chain_type")] pub enum OpportunityDelete { + #[serde(rename = "svm")] #[schema(title = "svm")] Svm(OpportunityDeleteSvm), + #[serde(rename = "evm")] + #[schema(title = "evm")] + Evm(OpportunityDeleteEvm), } /// The input type for creating a new opportunity. @@ -608,6 +632,7 @@ impl OpportunityDelete { pub fn get_chain_id(&self) -> &ChainId { match self { OpportunityDelete::Svm(OpportunityDeleteSvm::V1(params)) => ¶ms.chain_id, + OpportunityDelete::Evm(OpportunityDeleteEvm::V1(params)) => ¶ms.chain_id, } } } @@ -786,21 +811,26 @@ pub async fn delete_opportunities( State(store): State>, Json(opportunity_delete): Json, ) -> Result { - let OpportunityDelete::Svm(OpportunityDeleteSvm::V1(params)) = opportunity_delete; - if get_program(&auth)? != params.program { - return Err(RestError::Forbidden); - } + match opportunity_delete { + OpportunityDelete::Evm(_) => Err(RestError::Forbidden), + OpportunityDelete::Svm(params_svm) => { + let OpportunityDeleteSvm::V1(params) = params_svm; + if get_program(&auth)? != params.program { + return Err(RestError::Forbidden); + } - store - .opportunity_service_svm - .remove_opportunities(RemoveOpportunitiesInput { - chain_id: params.chain_id, - permission_account: params.permission_account, - router: params.router, - }) - .await?; + store + .opportunity_service_svm + .remove_opportunities(RemoveOpportunitiesInput { + chain_id: params.chain_id, + permission_account: params.permission_account, + router: params.router, + }) + .await?; - Ok(StatusCode::NO_CONTENT) + Ok(StatusCode::NO_CONTENT) + } + } } pub fn get_routes(store: Arc) -> Router> { diff --git a/auction-server/src/opportunity/entities/opportunity.rs b/auction-server/src/opportunity/entities/opportunity.rs index 686cd270..a3586dbb 100644 --- a/auction-server/src/opportunity/entities/opportunity.rs +++ b/auction-server/src/opportunity/entities/opportunity.rs @@ -21,7 +21,9 @@ use { }; pub type OpportunityId = Uuid; -pub type OpportunityKey = (ChainId, PermissionKey); + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct OpportunityKey(pub ChainId, pub PermissionKey); #[derive(Debug, Clone, PartialEq)] pub struct OpportunityCoreFields { @@ -73,8 +75,9 @@ pub trait Opportunity: fn new_with_current_time(val: Self::OpportunityCreate) -> Self; fn get_models_metadata(&self) -> Self::ModelMetadata; + fn get_opportunity_delete(&self) -> api::OpportunityDelete; fn get_key(&self) -> OpportunityKey { - (self.chain_id.clone(), self.permission_key.clone()) + OpportunityKey(self.chain_id.clone(), self.permission_key.clone()) } } diff --git a/auction-server/src/opportunity/entities/opportunity_evm.rs b/auction-server/src/opportunity/entities/opportunity_evm.rs index 8bdb0e09..02396c8b 100644 --- a/auction-server/src/opportunity/entities/opportunity_evm.rs +++ b/auction-server/src/opportunity/entities/opportunity_evm.rs @@ -64,13 +64,20 @@ impl Opportunity for OpportunityEvm { target_calldata: self.target_calldata.clone(), } } + + fn get_opportunity_delete(&self) -> api::OpportunityDelete { + api::OpportunityDelete::Evm(api::OpportunityDeleteEvm::V1(api::OpportunityDeleteV1Evm { + permission_key: self.core_fields.permission_key.clone(), + chain_id: self.core_fields.chain_id.clone(), + })) + } } impl OpportunityCreate for OpportunityCreateEvm { type ApiOpportunityCreate = api::OpportunityCreateEvm; fn get_key(&self) -> super::OpportunityKey { - ( + super::OpportunityKey( self.core_fields.chain_id.clone(), self.core_fields.permission_key.clone(), ) diff --git a/auction-server/src/opportunity/entities/opportunity_svm.rs b/auction-server/src/opportunity/entities/opportunity_svm.rs index c4b0e004..b06e4f94 100644 --- a/auction-server/src/opportunity/entities/opportunity_svm.rs +++ b/auction-server/src/opportunity/entities/opportunity_svm.rs @@ -105,13 +105,22 @@ impl Opportunity for OpportunitySvm { slot: self.slot, } } + + fn get_opportunity_delete(&self) -> api::OpportunityDelete { + api::OpportunityDelete::Svm(api::OpportunityDeleteSvm::V1(api::OpportunityDeleteV1Svm { + chain_id: self.chain_id.clone(), + permission_account: self.permission_account, + router: self.router, + program: self.program.clone().into(), + })) + } } impl OpportunityCreate for OpportunityCreateSvm { type ApiOpportunityCreate = api::OpportunityCreateSvm; fn get_key(&self) -> super::OpportunityKey { - ( + super::OpportunityKey( self.core_fields.chain_id.clone(), self.core_fields.permission_key.clone(), ) @@ -299,15 +308,6 @@ impl OpportunitySvm { pub fn get_permission_key(router: Pubkey, permission_account: Pubkey) -> PermissionKey { PermissionKey::from([router.to_bytes(), permission_account.to_bytes()].concat()) } - - pub fn get_opportunity_delete(&self) -> api::OpportunityDelete { - api::OpportunityDelete::Svm(api::OpportunityDeleteSvm::V1(api::OpportunityDeleteV1Svm { - chain_id: self.chain_id.clone(), - permission_account: self.permission_account, - router: self.router, - program: self.program.clone().into(), - })) - } } impl From for api::ProgramSvm { diff --git a/auction-server/src/opportunity/mod.rs b/auction-server/src/opportunity/mod.rs index 1d4f9cc2..204803bb 100644 --- a/auction-server/src/opportunity/mod.rs +++ b/auction-server/src/opportunity/mod.rs @@ -1,8 +1,8 @@ mod contracts; -mod entities; mod repository; mod token_spoof; pub mod api; +pub mod entities; pub mod service; pub mod workers; diff --git a/auction-server/src/opportunity/repository/remove_opportunity.rs b/auction-server/src/opportunity/repository/remove_opportunity.rs index 7d7deff9..4b3f6c81 100644 --- a/auction-server/src/opportunity/repository/remove_opportunity.rs +++ b/auction-server/src/opportunity/repository/remove_opportunity.rs @@ -4,7 +4,10 @@ use { InMemoryStore, Repository, }, - crate::opportunity::entities::Opportunity, + crate::opportunity::entities::{ + self, + Opportunity, + }, sqlx::Postgres, time::{ OffsetDateTime, @@ -17,8 +20,9 @@ impl Repository { &self, db: &sqlx::Pool, opportunity: &T::Opportunity, - reason: OpportunityRemovalReason, + reason: entities::OpportunityRemovalReason, ) -> anyhow::Result<()> { + let reason: OpportunityRemovalReason = reason.into(); let now = OffsetDateTime::now_utc(); sqlx::query("UPDATE opportunity SET removal_time = $1, removal_reason = $2 WHERE id = $3 AND removal_time IS NULL") .bind(PrimitiveDateTime::new(now.date(), now.time())) diff --git a/auction-server/src/opportunity/service/get_quote.rs b/auction-server/src/opportunity/service/get_quote.rs index af1a7c47..8bba325e 100644 --- a/auction-server/src/opportunity/service/get_quote.rs +++ b/auction-server/src/opportunity/service/get_quote.rs @@ -218,7 +218,7 @@ impl Service { "Auction finished for the opportunity".to_string(), )); if let Err(e) = repo - .remove_opportunity(&db, &opportunity, removal_reason.into()) + .remove_opportunity(&db, &opportunity, removal_reason) .await { tracing::error!("Failed to remove opportunity: {:?}", e); diff --git a/auction-server/src/opportunity/service/mod.rs b/auction-server/src/opportunity/service/mod.rs index e7c00f20..638b22a3 100644 --- a/auction-server/src/opportunity/service/mod.rs +++ b/auction-server/src/opportunity/service/mod.rs @@ -169,11 +169,17 @@ impl ConfigSvm { } } +#[derive(Debug, PartialEq)] +pub enum ChainTypeEnum { + Evm, + Svm, +} + pub trait ChainType: Send + Sync { type Config: Config; type InMemoryStore: InMemoryStore; - fn get_name() -> String; + fn get_type() -> ChainTypeEnum; } pub struct ChainTypeEvm; @@ -183,8 +189,8 @@ impl ChainType for ChainTypeEvm { type Config = ConfigEvm; type InMemoryStore = InMemoryStoreEvm; - fn get_name() -> String { - "evm".to_string() + fn get_type() -> ChainTypeEnum { + ChainTypeEnum::Evm } } @@ -192,8 +198,8 @@ impl ChainType for ChainTypeSvm { type Config = ConfigSvm; type InMemoryStore = InMemoryStoreSvm; - fn get_name() -> String { - "svm".to_string() + fn get_type() -> ChainTypeEnum { + ChainTypeEnum::Svm } } diff --git a/auction-server/src/opportunity/service/remove_invalid_or_expired_opportunities.rs b/auction-server/src/opportunity/service/remove_invalid_or_expired_opportunities.rs index 49b19c68..e146e6da 100644 --- a/auction-server/src/opportunity/service/remove_invalid_or_expired_opportunities.rs +++ b/auction-server/src/opportunity/service/remove_invalid_or_expired_opportunities.rs @@ -8,8 +8,17 @@ use { Service, }, crate::{ - api::RestError, - opportunity::entities, + api::{ + ws::UpdateEvent, + RestError, + }, + opportunity::{ + entities::{ + self, + Opportunity as _, + }, + service::ChainTypeEnum, + }, state::UnixTimestampMicros, }, std::time::{ @@ -59,16 +68,46 @@ where if let Some(reason) = reason { tracing::info!( - "Removing Opportunity {} for reason {:?}", - opportunity.id, - reason + opportunity = ?opportunity, + reason = ?reason, + "Removing Opportunity", ); - if let Err(e) = self + match self .repo - .remove_opportunity(&self.db, opportunity, reason.into()) + .remove_opportunity(&self.db, opportunity, reason) .await { - tracing::error!("Failed to remove opportunity: {}", e); + Ok(()) => { + // TODO Remove this later + // For now we don't want searchers to update any of their code on EVM chains. + // So we are not broadcasting remove opportunities event for EVM chains. + if T::get_type() == ChainTypeEnum::Evm { + continue; + } + + // If there are no more opportunities with this key, it means all of the + // opportunities have been removed for this key, so we can broadcast remove opportunities event. + if self + .repo + .get_in_memory_opportunities_by_key(&opportunity.get_key()) + .await + .is_empty() + { + if let Err(e) = self.store.ws.broadcast_sender.send( + UpdateEvent::RemoveOpportunities( + opportunity.get_opportunity_delete(), + ), + ) { + tracing::error!( + error = e.to_string(), + "Failed to broadcast remove opportunity" + ); + } + } + } + Err(e) => { + tracing::error!(error = ?e, "Failed to remove opportunity"); + } } } } diff --git a/auction-server/src/opportunity/service/remove_opportunities.rs b/auction-server/src/opportunity/service/remove_opportunities.rs index 2ec38177..2a4328cb 100644 --- a/auction-server/src/opportunity/service/remove_opportunities.rs +++ b/auction-server/src/opportunity/service/remove_opportunities.rs @@ -10,7 +10,10 @@ use { }, kernel::entities::ChainId, opportunity::{ - entities, + entities::{ + self, + Opportunity as _, + }, repository::{ self, }, @@ -39,7 +42,7 @@ impl Service { &self.db, permission_key.clone(), input.chain_id.clone(), - &(input.chain_id.clone(), permission_key), + &entities::OpportunityKey(input.chain_id.clone(), permission_key), repository::OpportunityRemovalReason::Invalid, ) .await diff --git a/auction-server/src/opportunity/workers.rs b/auction-server/src/opportunity/workers.rs index f5e57877..c0a6d66b 100644 --- a/auction-server/src/opportunity/workers.rs +++ b/auction-server/src/opportunity/workers.rs @@ -22,7 +22,7 @@ where Service: Verification, { tracing::info!( - chain_type = T::get_name(), + chain_type = ?T::get_type(), "Starting opportunity verifier..." ); let mut exit_check_interval = tokio::time::interval(EXIT_CHECK_INTERVAL); @@ -39,7 +39,7 @@ where } } tracing::info!( - chain_type = T::get_name(), + chain_type = ?T::get_type(), "Shutting down opportunity verifier..." ); Ok(()) diff --git a/sdk/js/package-lock.json b/sdk/js/package-lock.json index 88f9691c..b714a29d 100644 --- a/sdk/js/package-lock.json +++ b/sdk/js/package-lock.json @@ -1,12 +1,12 @@ { "name": "@pythnetwork/express-relay-js", - "version": "0.13.1", + "version": "0.13.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@pythnetwork/express-relay-js", - "version": "0.13.1", + "version": "0.13.2", "license": "Apache-2.0", "dependencies": { "@coral-xyz/anchor": "^0.30.1", diff --git a/sdk/js/package.json b/sdk/js/package.json index b0ea0e58..3c1fb429 100644 --- a/sdk/js/package.json +++ b/sdk/js/package.json @@ -1,6 +1,6 @@ { "name": "@pythnetwork/express-relay-js", - "version": "0.13.1", + "version": "0.13.2", "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/index.ts b/sdk/js/src/index.ts index 89e3d240..f3549562 100644 --- a/sdk/js/src/index.ts +++ b/sdk/js/src/index.ts @@ -19,6 +19,7 @@ import { TokenAmount, SvmChainUpdate, OpportunityDelete, + ChainType, } from "./types"; import { Connection, @@ -169,14 +170,26 @@ export class Client { } } 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), - }); + const opportunityDelete: OpportunityDelete = + message.opportunity_delete.chain_type === ChainType.EVM + ? { + chainType: ChainType.EVM, + chainId: message.opportunity_delete.chain_id, + permissionKey: checkHex( + message.opportunity_delete.permission_key + ), + } + : { + chainType: ChainType.SVM, + 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), + }; + + await this.websocketRemoveOpportunitiesCallback(opportunityDelete); } } else if ("id" in message && message.id) { // Response to a request sent earlier via the websocket with the same id @@ -366,12 +379,17 @@ export class Client { * @param opportunity Opportunity to be removed */ async removeOpportunity(opportunity: OpportunityDelete) { + if (opportunity.chainType === ChainType.EVM) { + throw new ClientError("Only SVM opportunities can be removed"); + } + if (opportunity.program !== "limo") { throw new ClientError("Only limo opportunities can be removed"); } const client = createClient(this.clientOptions); const body = { + chain_type: opportunity.chainType, chain_id: opportunity.chainId, version: "v1" as const, program: opportunity.program, diff --git a/sdk/js/src/serverTypes.d.ts b/sdk/js/src/serverTypes.d.ts index 3c8eb893..ec847c28 100644 --- a/sdk/js/src/serverTypes.d.ts +++ b/sdk/js/src/serverTypes.d.ts @@ -405,12 +405,37 @@ export interface components { slot: number; }; /** @description The input type for deleting opportunities. */ - OpportunityDelete: components["schemas"]["OpportunityDeleteSvm"]; + OpportunityDelete: + | (components["schemas"]["OpportunityDeleteSvm"] & { + /** @enum {string} */ + chain_type: "svm"; + }) + | (components["schemas"]["OpportunityDeleteEvm"] & { + /** @enum {string} */ + chain_type: "evm"; + }); + OpportunityDeleteEvm: components["schemas"]["OpportunityDeleteV1Evm"] & { + /** @enum {string} */ + version: "v1"; + }; OpportunityDeleteSvm: components["schemas"]["OpportunityDeleteV1Svm"] & { /** @enum {string} */ version: "v1"; }; /** @description Opportunity parameters needed for deleting live opportunities. */ + OpportunityDeleteV1Evm: { + /** + * @description The chain id for the opportunity. + * @example solana + */ + chain_id: string; + /** + * @description The permission key of the opportunity. + * @example 0xdeadbeefcafe + */ + permission_key: string; + }; + /** @description Opportunity parameters needed for deleting live opportunities. */ OpportunityDeleteV1Svm: { /** * @description The chain id for the opportunity. diff --git a/sdk/js/src/types.ts b/sdk/js/src/types.ts index 27fa1309..70ba95ed 100644 --- a/sdk/js/src/types.ts +++ b/sdk/js/src/types.ts @@ -244,9 +244,27 @@ export type SvmConstantsConfig = { export type SvmChainUpdate = components["schemas"]["SvmChainUpdate"]; -export type OpportunityDelete = { +export type OpportunityDeleteSvm = { chainId: ChainId; permissionAccount: PublicKey; program: components["schemas"]["ProgramSvm"]; router: PublicKey; }; + +export type OpportunityDeleteEvm = { + chainId: ChainId; + permissionKey: Hex; +}; + +export enum ChainType { + EVM = "evm", + SVM = "svm", +} + +export type OpportunityDelete = + | (OpportunityDeleteSvm & { + chainType: ChainType.SVM; + }) + | (OpportunityDeleteEvm & { + chainType: ChainType.EVM; + }); diff --git a/sdk/python/express_relay/client.py b/sdk/python/express_relay/client.py index c7f61c81..2824f5ea 100644 --- a/sdk/python/express_relay/client.py +++ b/sdk/python/express_relay/client.py @@ -45,6 +45,7 @@ ClientMessage, BidResponseRoot, OpportunityDelete, + OpportunityDeleteRoot, ) from express_relay.models.base import UnsupportedOpportunityVersionException from express_relay.models.evm import OpportunityEvm @@ -376,10 +377,11 @@ async def ws_handler( elif msg_json.get("type") == "remove_opportunities": if remove_opportunities_callback is not None: - remove_opportunities = OpportunityDelete.model_validate( + remove_opportunities = OpportunityDeleteRoot.model_validate( msg_json["opportunity_delete"] ) - asyncio.create_task(remove_opportunities_callback(remove_opportunities)) + if remove_opportunities: + asyncio.create_task(remove_opportunities_callback(remove_opportunities)) elif msg_json.get("id"): future = self.ws_msg_futures.pop(msg_json["id"]) diff --git a/sdk/python/express_relay/models/__init__.py b/sdk/python/express_relay/models/__init__.py index f060c6a8..2cbd5090 100644 --- a/sdk/python/express_relay/models/__init__.py +++ b/sdk/python/express_relay/models/__init__.py @@ -10,6 +10,7 @@ Bytes32, HexString, Address, + OpportunityDeleteEvm, SignedMessageString, OpportunityEvm, TokenAmount, @@ -115,7 +116,8 @@ class OpportunityParams(BaseModel): Opportunity = Union[OpportunityEvm, OpportunitySvm] OpportunityRoot = RootModel[Opportunity] -OpportunityDelete = OpportunityDeleteSvm +OpportunityDelete = Union[OpportunityDeleteEvm | OpportunityDeleteSvm] +OpportunityDeleteRoot = RootModel[OpportunityDelete] class SubscribeMessageParams(BaseModel): diff --git a/sdk/python/express_relay/models/base.py b/sdk/python/express_relay/models/base.py index fe21d312..09817ed5 100644 --- a/sdk/python/express_relay/models/base.py +++ b/sdk/python/express_relay/models/base.py @@ -11,6 +11,9 @@ class UnsupportedOpportunityVersionException(Exception): class UnsupportedOpportunityDeleteVersionException(Exception): pass +class UnsupportedOpportunityDeleteChainTypeException(Exception): + pass + class BidStatus(Enum): PENDING = "pending" SUBMITTED = "submitted" diff --git a/sdk/python/express_relay/models/evm.py b/sdk/python/express_relay/models/evm.py index 758a63c1..aafaca1e 100644 --- a/sdk/python/express_relay/models/evm.py +++ b/sdk/python/express_relay/models/evm.py @@ -12,6 +12,7 @@ from express_relay.models.base import ( IntString, UUIDString, + UnsupportedOpportunityDeleteVersionException, UnsupportedOpportunityVersionException, BidStatus, ) @@ -197,3 +198,25 @@ class BidResponseEvm(BaseModel): initiation_time: datetime profile_id: str | None = Field(default=None) gas_limit: IntString + + +class OpportunityDeleteEvm(BaseModel): + """ + Attributes: + chain_id: The chain ID for opportunities to be removed. + permission_key: The permission key for the opportunities to be removed. + """ + chain_id: str + permission_key: HexString + + supported_versions: ClassVar[list[str]] = ["v1"] + chain_type: ClassVar[str] = "evm" + + @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/models/svm.py b/sdk/python/express_relay/models/svm.py index a368a74e..34844990 100644 --- a/sdk/python/express_relay/models/svm.py +++ b/sdk/python/express_relay/models/svm.py @@ -346,6 +346,7 @@ class OpportunityDeleteSvm(BaseModel): version: str supported_versions: ClassVar[list[str]] = ["v1"] + chain_type: ClassVar[str] = "svm" @model_validator(mode="before") @classmethod diff --git a/sdk/python/pyproject.toml b/sdk/python/pyproject.toml index e4599a9a..096bda07 100644 --- a/sdk/python/pyproject.toml +++ b/sdk/python/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "express-relay" -version = "0.13.0" +version = "0.13.1" description = "Utilities for searchers and protocols to interact with the Express Relay protocol." authors = ["dourolabs"] license = "Apache-2.0"