From 4326882f0f77ba375cc675dab68993e213c606b9 Mon Sep 17 00:00:00 2001 From: Amin Moghaddam Date: Thu, 9 Jan 2025 19:32:16 -0500 Subject: [PATCH 01/25] feat: initial js sdk for swap --- Tiltfile | 15 +- auction-server/api-types/src/opportunity.rs | 8 +- .../src/auction/entities/auction.rs | 2 +- .../src/auction/service/verification.rs | 96 +++-- auction-server/src/opportunity/api.rs | 8 +- .../opportunity/entities/opportunity_svm.rs | 2 +- .../src/opportunity/service/get_quote.rs | 16 +- pnpm-lock.yaml | 6 + pnpm-workspace.yaml | 1 + sdk/js/package.json | 1 + sdk/js/src/const.ts | 2 + sdk/js/src/examples/simpleSearcherLimo.ts | 63 ++- sdk/js/src/expressRelayTypes.d.ts | 385 +++++++++++++++++- sdk/js/src/idl/idlExpressRelay.json | 269 +++++++++++- sdk/js/src/index.ts | 36 +- sdk/js/src/serverTypes.d.ts | 141 ++++--- sdk/js/src/svm.ts | 177 +++++++- sdk/js/src/types.ts | 37 +- tilt-scripts/svm/test_swap.py | 95 +++++ 19 files changed, 1226 insertions(+), 134 deletions(-) create mode 100644 tilt-scripts/svm/test_swap.py diff --git a/Tiltfile b/Tiltfile index 60ffaf4b..d2d2b132 100644 --- a/Tiltfile +++ b/Tiltfile @@ -115,8 +115,8 @@ local_resource( # setup limo global config and vaults for buy and sell tokens RUN_CLI= "ADMIN=../../keypairs/admin.json RPC_ENV=localnet npm exec limo-cli --" SET_GLOBAL_CONFIG = "LIMO_GLOBAL_CONFIG=$(solana-keygen pubkey ../../keypairs/limo_global_config.json)" -MINT_SELL= "$(solana-keygen pubkey ../../keypairs/mint_sell.json)" -MINT_BUY= "$(solana-keygen pubkey ../../keypairs/mint_buy.json)" +MINT_SELL= "$(solana-keygen pubkey %s/keypairs/mint_sell.json)" % config.main_dir +MINT_BUY= "$(solana-keygen pubkey %s/keypairs/mint_buy.json)" % config.main_dir local_resource( "svm-limo-setup", """solana-keygen new -o ../../keypairs/limo_global_config.json -f --no-bip39-passphrase \ @@ -211,14 +211,21 @@ local_resource( local_resource( "svm-searcher-py", - serve_cmd="poetry run python3 -m express_relay.searcher.examples.testing_searcher_svm --endpoint-express-relay http://127.0.0.1:9000 --chain-id development-solana --private-key-json-file ../../keypairs/searcher_js.json --endpoint-svm http://127.0.0.1:8899 --bid 10000000 --fill-rate 4 --bid-margin 100 --with-latency", + serve_cmd="poetry run python3 -m express_relay.searcher.examples.testing_searcher_svm --endpoint-express-relay http://127.0.0.1:9000 --chain-id development-solana --private-key-json-file ../../keypairs/searcher_py.json --endpoint-svm http://127.0.0.1:8899 --bid 10000000 --fill-rate 4 --bid-margin 100 --with-latency", serve_dir="sdk/python", resource_deps=["svm-initialize-programs", "auction-server"], ) local_resource( "svm-searcher-js", - serve_cmd="pnpm run testing-searcher-limo --endpoint-express-relay http://127.0.0.1:9000 --chain-id development-solana --private-key-json-file ../../keypairs/searcher_py.json --endpoint-svm http://127.0.0.1:8899 --bid 10000000 --fill-rate 4 --bid-margin 100 --with-latency", + serve_cmd="pnpm run testing-searcher-limo --endpoint-express-relay http://127.0.0.1:9000 --chain-id development-solana --private-key-json-file ../../keypairs/searcher_js.json --endpoint-svm http://127.0.0.1:8899 --bid 1000 --fill-rate 4 --bid-margin 100", serve_dir="sdk/js", resource_deps=["svm-initialize-programs", "auction-server"], ) + +local_resource( + "svm-test-swap-endpoint", + "poetry -C tilt-scripts run python3 -m tilt-scripts.svm.test_swap --file-private-key-taker keypairs/searcher_py.json --auction-server-url http://localhost:9000 --input-mint {MINT_SELL} --output-mint {MINT_BUY} --rpc-url {RPC_URL}" + .format(RPC_URL=rpc_url_solana, MINT_SELL=MINT_SELL, MINT_BUY=MINT_BUY), + resource_deps=["svm-searcher-js"], +) diff --git a/auction-server/api-types/src/opportunity.rs b/auction-server/api-types/src/opportunity.rs index ac3cfc7c..f2230871 100644 --- a/auction-server/api-types/src/opportunity.rs +++ b/auction-server/api-types/src/opportunity.rs @@ -61,10 +61,9 @@ pub struct OpportunityBidResult { } #[derive(Serialize, Deserialize, ToSchema, Clone, PartialEq, Debug, Display)] +#[serde(rename_all = "snake_case")] pub enum ProgramSvm { - #[strum(serialize = "swap_kamino")] SwapKamino, - #[strum(serialize = "limo")] Limo, } @@ -295,12 +294,11 @@ pub struct OpportunityEvm { /// Program specific parameters for the opportunity. #[serde_as] #[derive(Serialize, Deserialize, ToSchema, Clone, PartialEq, Debug, ToResponse)] -#[serde(tag = "program")] +#[serde(tag = "program", rename_all = "snake_case")] pub enum OpportunityParamsV1ProgramSvm { /// Limo program specific parameters for the opportunity. /// It contains the Limo order to be executed, encoded in base64. /// SDKs will decode this order and create transaction for bidding on the opportunity. - #[serde(rename = "limo")] #[schema(title = "limo")] Limo { /// The Limo order to be executed, encoded in base64. @@ -313,7 +311,6 @@ pub enum OpportunityParamsV1ProgramSvm { order_address: Pubkey, }, /// Swap program specific parameters for the opportunity. - #[serde(rename = "swap")] #[schema(title = "swap")] Swap { /// The user wallet address which requested the quote from the wallet. @@ -338,6 +335,7 @@ pub enum OpportunityParamsV1ProgramSvm { } #[derive(Serialize, Deserialize, ToSchema, Clone, PartialEq, Debug, ToResponse)] +#[serde(tag = "type", rename_all = "snake_case")] pub enum QuoteTokens { InputTokenSpecified { input_token: TokenAmountSvm, diff --git a/auction-server/src/auction/entities/auction.rs b/auction-server/src/auction/entities/auction.rs index ccaa6786..4def74d4 100644 --- a/auction-server/src/auction/entities/auction.rs +++ b/auction-server/src/auction/entities/auction.rs @@ -34,7 +34,7 @@ pub struct Auction { pub bids: Vec>, } -#[derive(PartialEq)] +#[derive(PartialEq, Debug)] pub enum SubmitType { ByServer, ByOther, diff --git a/auction-server/src/auction/service/verification.rs b/auction-server/src/auction/service/verification.rs index fe86ef2a..9d8ede72 100644 --- a/auction-server/src/auction/service/verification.rs +++ b/auction-server/src/auction/service/verification.rs @@ -77,6 +77,7 @@ use { transaction::VersionedTransaction, }, std::{ + str::FromStr, sync::Arc, time::Duration, }, @@ -546,22 +547,6 @@ impl Service { (Err(_), Ok(swap_instruction)) => { let swap_data = Self::extract_swap_data(&swap_instruction)?; - let router = self - .extract_account( - &transaction, - &swap_instruction, - self.config - .chain_config - .express_relay - .router_account_position_swap, - ) - .await?; - if router != self.config.chain_config.wallet_program_router_account { - return Err(RestError::BadParameters( - "Must use approved router for swap instruction".to_string(), - )); - } - let user_wallet = self .extract_account( &transaction, @@ -616,15 +601,66 @@ impl Service { }, ), }; + let mint_fee = match swap_data.fee_token { + FeeToken::Input => mint_input, + FeeToken::Output => mint_output, + }; + + let router_fee_receiver_ta = self + .extract_account( + &transaction, + &swap_instruction, + self.config + .chain_config + .express_relay + .router_account_position_swap, + ) + .await?; + + //TODO* : do not hardcode the token program and refactor into separate function + let fee_token_program = Pubkey::from_str( + "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + ) + .map_err(|e| { + RestError::BadParameters(format!("Invalid token program address: {:?}", e)) + })?; + let associated_token_program = Pubkey::from_str( + "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL", + ) + .map_err(|e| { + RestError::BadParameters(format!( + "Invalid associated token program address: {:?}", + e + )) + })?; + let expected_router_fee_receiver_ta = Pubkey::find_program_address( + &[ + &self + .config + .chain_config + .wallet_program_router_account + .to_bytes(), + &fee_token_program.to_bytes(), + &mint_fee.to_bytes(), + ], + &associated_token_program, + ) + .0; + + if router_fee_receiver_ta != expected_router_fee_receiver_ta { + return Err(RestError::BadParameters( + "Must use approved router token account for swap instruction".to_string(), + )); + } let permission_account = get_quote_permission_key(&tokens, &user_wallet); Ok(BidDataSvm { amount: bid_amount, permission_account, - router, + router: self.config.chain_config.wallet_program_router_account, // TODO*: to fix once deadline param added to swap instruction--just set this way to make sure compiles - deadline: OffsetDateTime::now_utc(), + deadline: OffsetDateTime::now_utc() + Duration::from_secs(20), // deadline: OffsetDateTime::from_unix_timestamp(swap_data.deadline).map_err( // |e| { // RestError::BadParameters(format!( @@ -642,12 +678,10 @@ impl Service { } } - fn all_signatures_exists( + fn relayer_signer_exists( &self, - message_bytes: &[u8], accounts: &[Pubkey], signatures: &[Signature], - missing_signers: &[Pubkey], ) -> Result<(), RestError> { let relayer_pubkey = self.config.chain_config.express_relay.relayer.pubkey(); let relayer_exists = accounts[..signatures.len()] @@ -660,9 +694,17 @@ impl Service { relayer_pubkey ))); } - + Ok(()) + } + fn all_signatures_exists( + &self, + message_bytes: &[u8], + accounts: &[Pubkey], + signatures: &[Signature], + missing_signers: &[Pubkey], + ) -> Result<(), RestError> { for (signature, pubkey) in signatures.iter().zip(accounts.iter()) { - if missing_signers.contains(pubkey) || pubkey.eq(&relayer_pubkey) { + if missing_signers.contains(pubkey) { continue; } if !signature.verify(pubkey.as_ref(), message_bytes) { @@ -714,7 +756,13 @@ impl Service { ) } SubmitType::ByServer => { - self.all_signatures_exists(&message_bytes, accounts, &signatures, &[]) + self.relayer_signer_exists(accounts, &signatures)?; + self.all_signatures_exists( + &message_bytes, + accounts, + &signatures, + &[self.config.chain_config.express_relay.relayer.pubkey()], + ) } } } diff --git a/auction-server/src/opportunity/api.rs b/auction-server/src/opportunity/api.rs index 7afe2247..123dd956 100644 --- a/auction-server/src/opportunity/api.rs +++ b/auction-server/src/opportunity/api.rs @@ -207,20 +207,14 @@ pub async fn get_opportunities( (status = 404, description = "No quote available right now", body = ErrorBodyResponse), ),)] pub async fn post_quote( - auth: Auth, State(store): State>, Json(params): Json, ) -> Result, RestError> { - let program = get_program(&auth)?; - if program != ProgramSvm::SwapKamino { - return Err(RestError::Forbidden); - } - let quote = store .opportunity_service_svm .get_quote(GetQuoteInput { quote_create: params.into(), - program, + program: ProgramSvm::SwapKamino, }) .await?; diff --git a/auction-server/src/opportunity/entities/opportunity_svm.rs b/auction-server/src/opportunity/entities/opportunity_svm.rs index cef6888e..386cd4a8 100644 --- a/auction-server/src/opportunity/entities/opportunity_svm.rs +++ b/auction-server/src/opportunity/entities/opportunity_svm.rs @@ -62,7 +62,7 @@ pub struct OpportunityCreateSvm { pub slot: Slot, } -// Opportunity can be refreshed after 10 seconds +// Opportunity can be refreshed after 30 seconds const MIN_REFRESH_TIME: Duration = Duration::seconds(30); impl Opportunity for OpportunitySvm { diff --git a/auction-server/src/opportunity/service/get_quote.rs b/auction-server/src/opportunity/service/get_quote.rs index 7814a0e3..8e8faaf9 100644 --- a/auction-server/src/opportunity/service/get_quote.rs +++ b/auction-server/src/opportunity/service/get_quote.rs @@ -99,7 +99,9 @@ impl Service { let router = quote_create.router; let chain_config = self.get_config("e_create.chain_id)?; if router != chain_config.wallet_program_router_account { - return Err(RestError::Forbidden); + return Err(RestError::BadParameters( + "Router account mismatch".to_string(), + )); } let permission_account = get_quote_permission_key("e_create.tokens, "e_create.user_wallet_address); @@ -124,13 +126,13 @@ impl Service { ), chain_id: quote_create.chain_id, sell_tokens: vec![entities::TokenAmountSvm { - token: input_mint, - amount: input_amount, - }], - buy_tokens: vec![entities::TokenAmountSvm { token: output_mint, amount: output_amount, }], + buy_tokens: vec![entities::TokenAmountSvm { + token: input_mint, + amount: input_amount, + }], }; let program_opportunity = match program { @@ -252,9 +254,7 @@ impl Service { .add_auction(AddAuctionInput { auction }) .await?; - let mut bid = winner_bid.clone(); - auction_service.add_relayer_signature(&mut bid); - + let bid = winner_bid.clone(); let signature = bid.chain_data.transaction.signatures[0]; auction = auction_service .update_submitted_auction(UpdateSubmittedAuctionInput { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5acf0732..d58a6b88 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,6 +15,9 @@ catalogs: "@kamino-finance/limo-sdk": specifier: 0.7.1 version: 0.7.1 + "@solana/spl-token": + specifier: 0.4.9 + version: 0.4.9 "@solana/web3.js": specifier: 1.95.3 version: 1.95.3 @@ -268,6 +271,9 @@ importers: "@kamino-finance/limo-sdk": specifier: "catalog:" version: 0.7.1(@solana/web3.js@1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(chai@5.1.2)(decimal.js@10.4.3)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.4)(utf-8-validate@5.0.10) + "@solana/spl-token": + specifier: "catalog:" + version: 0.4.9(@solana/web3.js@1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10))(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.5.4)(utf-8-validate@5.0.10) "@solana/web3.js": specifier: "catalog:" version: 1.95.3(bufferutil@4.0.8)(utf-8-validate@5.0.10) diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 335990d5..d8b64a8d 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -13,6 +13,7 @@ catalog: "@coral-xyz/anchor": 0.30.1 "@kamino-finance/limo-sdk": 0.7.1 "@solana/web3.js": 1.95.3 + "@solana/spl-token": 0.4.9 "viem": 2.16.2 "@types/yargs": 17.0.32 "@types/node": 20.14.9 diff --git a/sdk/js/package.json b/sdk/js/package.json index e5581ac8..c7b5699a 100644 --- a/sdk/js/package.json +++ b/sdk/js/package.json @@ -41,6 +41,7 @@ "@coral-xyz/anchor": "catalog:", "@kamino-finance/limo-sdk": "catalog:", "@solana/web3.js": "catalog:", + "@solana/spl-token": "catalog:", "decimal.js": "^10.4.3", "isomorphic-ws": "^5.0.0", "openapi-client-axios": "^7.5.5", diff --git a/sdk/js/src/const.ts b/sdk/js/src/const.ts index 4b5e34af..f398d588 100644 --- a/sdk/js/src/const.ts +++ b/sdk/js/src/const.ts @@ -28,10 +28,12 @@ export const SVM_CONSTANTS: Record = { expressRelayProgram: new PublicKey( "PytERJFhAKuNNuaiXkApLfWzwNwSNDACpigT3LwQfou" ), + walletRouter: new PublicKey("3hv8L8UeBbyM3M25dF3h2C5p8yA4FptD7FFZu4Z1jCMn"), }, solana: { expressRelayProgram: new PublicKey( "PytERJFhAKuNNuaiXkApLfWzwNwSNDACpigT3LwQfou" ), + walletRouter: new PublicKey("3hv8L8UeBbyM3M25dF3h2C5p8yA4FptD7FFZu4Z1jCMn"), }, }; diff --git a/sdk/js/src/examples/simpleSearcherLimo.ts b/sdk/js/src/examples/simpleSearcherLimo.ts index d4620372..3bfbc5c2 100644 --- a/sdk/js/src/examples/simpleSearcherLimo.ts +++ b/sdk/js/src/examples/simpleSearcherLimo.ts @@ -6,6 +6,8 @@ import { ExpressRelaySvmConfig, Opportunity, OpportunitySvm, + OpportunitySvmLimo, + OpportunitySvmSwap, } from "../index"; import { BidStatusUpdate, @@ -31,6 +33,7 @@ import { getPdaAuthority, OrderStateAndAddress, } from "@kamino-finance/limo-sdk/dist/utils"; +import { constructSwapBid } from "../svm"; const DAY_IN_SECONDS = 60 * 60 * 24; @@ -89,12 +92,12 @@ export class SimpleSearcherLimo { } /** - * Generates a bid for a given opportunity. + * Generates a bid for a given limo 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 { + async generateBidLimo(opportunity: OpportunitySvmLimo): Promise { const order = opportunity.order; const limoClient = new limo.LimoClient( this.connectionSvm, @@ -111,7 +114,7 @@ export class SimpleSearcherLimo { ...ixsTakeOrder ); - const bidAmount = await this.getBidAmount(order); + const bidAmount = await this.getBidAmount(opportunity); const config = await this.getExpressRelayConfig(); const bid = await this.client.constructSvmBid( @@ -133,6 +136,54 @@ export class SimpleSearcherLimo { return bid; } + /** + * Generates a bid for a given limo 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 generateBidSwap(opportunity: OpportunitySvmSwap): Promise { + const feeInstruction = ComputeBudgetProgram.setComputeUnitPrice({ + microLamports: + this.latestChainUpdate[this.chainId].latestPrioritizationFee, + }); + const bidAmount = await this.getBidAmount(opportunity); + const txRaw = new anchor.web3.Transaction().add(feeInstruction); + const config = await this.getExpressRelayConfig(); + const bid = await constructSwapBid( + txRaw, + this.searcher.publicKey, + opportunity, + bidAmount, + new anchor.BN(Math.round(Date.now() / 1000 + DAY_IN_SECONDS)), + this.chainId, + config.relayerSigner + ); + bid.slot = opportunity.slot; + + bid.transaction.recentBlockhash = + this.latestChainUpdate[this.chainId].blockhash; + bid.transaction.feePayer = this.searcher.publicKey; + bid.transaction.partialSign(this.searcher); + console.log(bid.transaction); + return bid; + } + + /** + * Generates a bid for a given opportunity. + * The transaction in this bid transfers assets from the searcher's wallet to fulfill the opportunity. + * @param opportunity The SVM opportunity to bid on. + * @returns The generated bid object. + */ + async generateBid(opportunity: OpportunitySvm): Promise { + if (opportunity.program === "limo") { + return this.generateBidLimo(opportunity); + } else { + // swap opportunity + return this.generateBidSwap(opportunity); + } + } + async getExpressRelayConfig(): Promise { if (!this.expressRelayConfig) { this.expressRelayConfig = await this.client.getExpressRelaySvmConfig( @@ -145,11 +196,11 @@ export class SimpleSearcherLimo { /** * Calculates the bid amount for a given order. - * @param order The limit order to be fulfilled - * @returns The bid amount in lamports + * @param opportunity The opportunity to be fulfilled + * @returns The bid amount in the necessary token */ // eslint-disable-next-line @typescript-eslint/no-unused-vars - async getBidAmount(order: OrderStateAndAddress): Promise { + async getBidAmount(opportunity: OpportunitySvm): Promise { // this should be replaced by a more sophisticated logic to determine the bid amount return this.bid; } diff --git a/sdk/js/src/expressRelayTypes.d.ts b/sdk/js/src/expressRelayTypes.d.ts index c85e4954..4122928d 100644 --- a/sdk/js/src/expressRelayTypes.d.ts +++ b/sdk/js/src/expressRelayTypes.d.ts @@ -5,10 +5,10 @@ * IDL can be found at `target/idl/express_relay.json`. */ export type ExpressRelay = { - address: "GwEtasTAxdS9neVE4GPUpcwR7DB7AizntQSPcG36ubZM"; + address: "PytERJFhAKuNNuaiXkApLfWzwNwSNDACpigT3LwQfou"; metadata: { name: "expressRelay"; - version: "0.2.0"; + version: "0.3.1"; spec: "0.1.0"; description: "Pyth Express Relay program for handling permissioning and bid distribution"; repository: "https://github.com/pyth-network/per"; @@ -17,9 +17,9 @@ export type ExpressRelay = { { name: "checkPermission"; docs: [ - "Checks if permissioning exists for a particular (permission, router) pair within the same transaction", - "Permissioning takes the form of a SubmitBid instruction with matching permission and router accounts", - "Returns the fees paid to the router in the matching instructions" + "Checks if permissioning exists for a particular (permission, router) pair within the same transaction.", + "Permissioning takes the form of a SubmitBid instruction with matching permission and router accounts.", + "Returns the fees paid to the router in the matching instructions." ]; discriminator: [154, 199, 232, 242, 96, 72, 197, 236]; accounts: [ @@ -289,7 +289,7 @@ export type ExpressRelay = { { name: "submitBid"; docs: [ - "Submits a bid for a particular (permission, router) pair and distributes bids according to splits" + "Submits a bid for a particular (permission, router) pair and distributes bids according to splits." ]; discriminator: [19, 164, 237, 254, 64, 139, 237, 93]; accounts: [ @@ -376,6 +376,310 @@ export type ExpressRelay = { } ]; }, + { + name: "swap"; + discriminator: [248, 198, 158, 145, 225, 117, 135, 200]; + accounts: [ + { + name: "searcher"; + docs: [ + "Searcher is the party that sends the input token and receives the output token" + ]; + signer: true; + }, + { + name: "trader"; + docs: [ + "Trader is the party that sends the output token and receives the input token" + ]; + signer: true; + }, + { + name: "searcherInputTa"; + writable: true; + }, + { + name: "searcherOutputTa"; + writable: true; + }, + { + name: "traderInputAta"; + writable: true; + pda: { + seeds: [ + { + kind: "account"; + path: "trader"; + }, + { + kind: "account"; + path: "tokenProgramInput"; + }, + { + kind: "account"; + path: "mintInput"; + } + ]; + program: { + kind: "const"; + value: [ + 140, + 151, + 37, + 143, + 78, + 36, + 137, + 241, + 187, + 61, + 16, + 41, + 20, + 142, + 13, + 131, + 11, + 90, + 19, + 153, + 218, + 255, + 16, + 132, + 4, + 142, + 123, + 216, + 219, + 233, + 248, + 89 + ]; + }; + }; + }, + { + name: "traderOutputAta"; + writable: true; + pda: { + seeds: [ + { + kind: "account"; + path: "trader"; + }, + { + kind: "account"; + path: "tokenProgramOutput"; + }, + { + kind: "account"; + path: "mintOutput"; + } + ]; + program: { + kind: "const"; + value: [ + 140, + 151, + 37, + 143, + 78, + 36, + 137, + 241, + 187, + 61, + 16, + 41, + 20, + 142, + 13, + 131, + 11, + 90, + 19, + 153, + 218, + 255, + 16, + 132, + 4, + 142, + 123, + 216, + 219, + 233, + 248, + 89 + ]; + }; + }; + }, + { + name: "routerFeeReceiverTa"; + docs: [ + "Router fee receiver token account: the referrer can provide an arbitrary receiver for the router fee" + ]; + writable: true; + }, + { + name: "relayerFeeReceiverAta"; + writable: true; + pda: { + seeds: [ + { + kind: "account"; + path: "express_relay_metadata.fee_receiver_relayer"; + account: "expressRelayMetadata"; + }, + { + kind: "account"; + path: "tokenProgramFee"; + }, + { + kind: "account"; + path: "mintFee"; + } + ]; + program: { + kind: "const"; + value: [ + 140, + 151, + 37, + 143, + 78, + 36, + 137, + 241, + 187, + 61, + 16, + 41, + 20, + 142, + 13, + 131, + 11, + 90, + 19, + 153, + 218, + 255, + 16, + 132, + 4, + 142, + 123, + 216, + 219, + 233, + 248, + 89 + ]; + }; + }; + }, + { + name: "expressRelayFeeReceiverAta"; + writable: true; + pda: { + seeds: [ + { + kind: "account"; + path: "expressRelayMetadata"; + }, + { + kind: "account"; + path: "tokenProgramFee"; + }, + { + kind: "account"; + path: "mintFee"; + } + ]; + program: { + kind: "const"; + value: [ + 140, + 151, + 37, + 143, + 78, + 36, + 137, + 241, + 187, + 61, + 16, + 41, + 20, + 142, + 13, + 131, + 11, + 90, + 19, + 153, + 218, + 255, + 16, + 132, + 4, + 142, + 123, + 216, + 219, + 233, + 248, + 89 + ]; + }; + }; + }, + { + name: "mintInput"; + }, + { + name: "mintOutput"; + }, + { + name: "mintFee"; + }, + { + name: "tokenProgramInput"; + }, + { + name: "tokenProgramOutput"; + }, + { + name: "tokenProgramFee"; + }, + { + name: "expressRelayMetadata"; + docs: ["Express relay configuration"]; + pda: { + seeds: [ + { + kind: "const"; + value: [109, 101, 116, 97, 100, 97, 116, 97]; + } + ]; + }; + } + ]; + args: [ + { + name: "data"; + type: { + defined: { + name: "swapArgs"; + }; + }; + } + ]; + }, { name: "withdrawFees"; discriminator: [198, 212, 171, 109, 144, 215, 174, 89]; @@ -450,12 +754,27 @@ export type ExpressRelay = { { code: 6006; name: "insufficientSearcherFunds"; - msg: "Insufficient Searcher Funds"; + msg: "Insufficient searcher funds"; }, { code: 6007; name: "insufficientRent"; msg: "Insufficient funds for rent"; + }, + { + code: 6008; + name: "invalidAta"; + msg: "Invalid ATA provided"; + }, + { + code: 6009; + name: "invalidMint"; + msg: "A token account has the wrong mint"; + }, + { + code: 6010; + name: "invalidTokenProgram"; + msg: "A token account belongs to the wrong token program"; } ]; types: [ @@ -499,6 +818,24 @@ export type ExpressRelay = { { name: "splitRelayer"; type: "u64"; + }, + { + name: "swapPlatformFeeBps"; + type: "u64"; + } + ]; + }; + }, + { + name: "feeToken"; + type: { + kind: "enum"; + variants: [ + { + name: "input"; + }, + { + name: "output"; } ]; }; @@ -562,6 +899,40 @@ export type ExpressRelay = { } ]; }; + }, + { + name: "swapArgs"; + docs: [ + "For all swap instructions and contexts, input and output are defined with respect to the searcher", + "So `mint_input` refers to the token that the searcher provides to the trader and", + "`mint_output` refers to the token that the searcher receives from the trader", + "This choice is made to minimize confusion for the searchers, who are more likely to parse the program" + ]; + type: { + kind: "struct"; + fields: [ + { + name: "amountInput"; + type: "u64"; + }, + { + name: "amountOutput"; + type: "u64"; + }, + { + name: "referralFeeBps"; + type: "u64"; + }, + { + name: "feeToken"; + type: { + defined: { + name: "feeToken"; + }; + }; + } + ]; + }; } ]; }; diff --git a/sdk/js/src/idl/idlExpressRelay.json b/sdk/js/src/idl/idlExpressRelay.json index a185fdfc..f4494a29 100644 --- a/sdk/js/src/idl/idlExpressRelay.json +++ b/sdk/js/src/idl/idlExpressRelay.json @@ -1,8 +1,8 @@ { - "address": "GwEtasTAxdS9neVE4GPUpcwR7DB7AizntQSPcG36ubZM", + "address": "PytERJFhAKuNNuaiXkApLfWzwNwSNDACpigT3LwQfou", "metadata": { "name": "express_relay", - "version": "0.2.0", + "version": "0.3.1", "spec": "0.1.0", "description": "Pyth Express Relay program for handling permissioning and bid distribution", "repository": "https://github.com/pyth-network/per" @@ -11,9 +11,9 @@ { "name": "check_permission", "docs": [ - "Checks if permissioning exists for a particular (permission, router) pair within the same transaction", - "Permissioning takes the form of a SubmitBid instruction with matching permission and router accounts", - "Returns the fees paid to the router in the matching instructions" + "Checks if permissioning exists for a particular (permission, router) pair within the same transaction.", + "Permissioning takes the form of a SubmitBid instruction with matching permission and router accounts.", + "Returns the fees paid to the router in the matching instructions." ], "discriminator": [154, 199, 232, 242, 96, 72, 197, 236], "accounts": [ @@ -259,7 +259,7 @@ { "name": "submit_bid", "docs": [ - "Submits a bid for a particular (permission, router) pair and distributes bids according to splits" + "Submits a bid for a particular (permission, router) pair and distributes bids according to splits." ], "discriminator": [19, 164, 237, 254, 64, 139, 237, 93], "accounts": [ @@ -334,6 +334,194 @@ } ] }, + { + "name": "swap", + "discriminator": [248, 198, 158, 145, 225, 117, 135, 200], + "accounts": [ + { + "name": "searcher", + "docs": [ + "Searcher is the party that sends the input token and receives the output token" + ], + "signer": true + }, + { + "name": "trader", + "docs": [ + "Trader is the party that sends the output token and receives the input token" + ], + "signer": true + }, + { + "name": "searcher_input_ta", + "writable": true + }, + { + "name": "searcher_output_ta", + "writable": true + }, + { + "name": "trader_input_ata", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "account", + "path": "trader" + }, + { + "kind": "account", + "path": "token_program_input" + }, + { + "kind": "account", + "path": "mint_input" + } + ], + "program": { + "kind": "const", + "value": [ + 140, 151, 37, 143, 78, 36, 137, 241, 187, 61, 16, 41, 20, 142, + 13, 131, 11, 90, 19, 153, 218, 255, 16, 132, 4, 142, 123, 216, + 219, 233, 248, 89 + ] + } + } + }, + { + "name": "trader_output_ata", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "account", + "path": "trader" + }, + { + "kind": "account", + "path": "token_program_output" + }, + { + "kind": "account", + "path": "mint_output" + } + ], + "program": { + "kind": "const", + "value": [ + 140, 151, 37, 143, 78, 36, 137, 241, 187, 61, 16, 41, 20, 142, + 13, 131, 11, 90, 19, 153, 218, 255, 16, 132, 4, 142, 123, 216, + 219, 233, 248, 89 + ] + } + } + }, + { + "name": "router_fee_receiver_ta", + "docs": [ + "Router fee receiver token account: the referrer can provide an arbitrary receiver for the router fee" + ], + "writable": true + }, + { + "name": "relayer_fee_receiver_ata", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "account", + "path": "express_relay_metadata.fee_receiver_relayer", + "account": "ExpressRelayMetadata" + }, + { + "kind": "account", + "path": "token_program_fee" + }, + { + "kind": "account", + "path": "mint_fee" + } + ], + "program": { + "kind": "const", + "value": [ + 140, 151, 37, 143, 78, 36, 137, 241, 187, 61, 16, 41, 20, 142, + 13, 131, 11, 90, 19, 153, 218, 255, 16, 132, 4, 142, 123, 216, + 219, 233, 248, 89 + ] + } + } + }, + { + "name": "express_relay_fee_receiver_ata", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "account", + "path": "express_relay_metadata" + }, + { + "kind": "account", + "path": "token_program_fee" + }, + { + "kind": "account", + "path": "mint_fee" + } + ], + "program": { + "kind": "const", + "value": [ + 140, 151, 37, 143, 78, 36, 137, 241, 187, 61, 16, 41, 20, 142, + 13, 131, 11, 90, 19, 153, 218, 255, 16, 132, 4, 142, 123, 216, + 219, 233, 248, 89 + ] + } + } + }, + { + "name": "mint_input" + }, + { + "name": "mint_output" + }, + { + "name": "mint_fee" + }, + { + "name": "token_program_input" + }, + { + "name": "token_program_output" + }, + { + "name": "token_program_fee" + }, + { + "name": "express_relay_metadata", + "docs": ["Express relay configuration"], + "pda": { + "seeds": [ + { + "kind": "const", + "value": [109, 101, 116, 97, 100, 97, 116, 97] + } + ] + } + } + ], + "args": [ + { + "name": "data", + "type": { + "defined": { + "name": "SwapArgs" + } + } + } + ] + }, { "name": "withdraw_fees", "discriminator": [198, 212, 171, 109, 144, 215, 174, 89], @@ -408,12 +596,27 @@ { "code": 6006, "name": "InsufficientSearcherFunds", - "msg": "Insufficient Searcher Funds" + "msg": "Insufficient searcher funds" }, { "code": 6007, "name": "InsufficientRent", "msg": "Insufficient funds for rent" + }, + { + "code": 6008, + "name": "InvalidAta", + "msg": "Invalid ATA provided" + }, + { + "code": 6009, + "name": "InvalidMint", + "msg": "A token account has the wrong mint" + }, + { + "code": 6010, + "name": "InvalidTokenProgram", + "msg": "A token account belongs to the wrong token program" } ], "types": [ @@ -457,6 +660,24 @@ { "name": "split_relayer", "type": "u64" + }, + { + "name": "swap_platform_fee_bps", + "type": "u64" + } + ] + } + }, + { + "name": "FeeToken", + "type": { + "kind": "enum", + "variants": [ + { + "name": "Input" + }, + { + "name": "Output" } ] } @@ -520,6 +741,40 @@ } ] } + }, + { + "name": "SwapArgs", + "docs": [ + "For all swap instructions and contexts, input and output are defined with respect to the searcher", + "So `mint_input` refers to the token that the searcher provides to the trader and", + "`mint_output` refers to the token that the searcher receives from the trader", + "This choice is made to minimize confusion for the searchers, who are more likely to parse the program" + ], + "type": { + "kind": "struct", + "fields": [ + { + "name": "amount_input", + "type": "u64" + }, + { + "name": "amount_output", + "type": "u64" + }, + { + "name": "referral_fee_bps", + "type": "u64" + }, + { + "name": "fee_token", + "type": { + "defined": { + "name": "FeeToken" + } + } + } + ] + } } ] } diff --git a/sdk/js/src/index.ts b/sdk/js/src/index.ts index 9950823c..edec760b 100644 --- a/sdk/js/src/index.ts +++ b/sdk/js/src/index.ts @@ -583,7 +583,7 @@ export class Client { buyTokens: opportunity.buy_tokens.map(checkTokenQty), }; } - if ("order" in opportunity) { + if (opportunity.program === "limo") { const order = Order.decode(Buffer.from(opportunity.order, "base64")); return { chainId: opportunity.chain_id, @@ -595,9 +595,39 @@ export class Client { }, program: "limo", }; + } else if (opportunity.program === "swap") { + let tokens; + if (opportunity.tokens.type === "input_token_specified") { + tokens = { + type: "input_specified", + inputToken: { + amount: opportunity.tokens.input_token.amount, + token: new PublicKey(opportunity.tokens.input_token.token), + }, + outputToken: new PublicKey(opportunity.tokens.output_token), + } as const; + } else { + tokens = { + type: "output_specified", + inputToken: new PublicKey(opportunity.tokens.input_token), + outputToken: { + amount: opportunity.tokens.output_token.amount, + token: new PublicKey(opportunity.tokens.output_token.token), + }, + } as const; + } + return { + chainId: opportunity.chain_id, + slot: opportunity.slot, + opportunityId: opportunity.opportunity_id, + program: "swap", + permissionAccount: new PublicKey(opportunity.permission_account), + routerAccount: new PublicKey(opportunity.router_account), + userWalletAddress: new PublicKey(opportunity.user_wallet_address), + tokens, + }; } else { - console.warn("Cannot handle wallet opportunities"); - return undefined; + console.warn("Unsupported opportunity", opportunity); } } diff --git a/sdk/js/src/serverTypes.d.ts b/sdk/js/src/serverTypes.d.ts index 4d137969..2fe7ac63 100644 --- a/sdk/js/src/serverTypes.d.ts +++ b/sdk/js/src/serverTypes.d.ts @@ -406,7 +406,7 @@ export interface components { | { /** * @description The Limo order to be executed, encoded in base64. - * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 + * @example UxMUbQAsjrfQUp5stVwMJ6Mucq7VWTvt4ICe69BJ8lVXqwM+0sysV8OqZTdM0W4p... */ order: string; /** @@ -418,14 +418,8 @@ export interface components { program: "limo"; } | { - /** - * Format: double - * @description The maximum slippage percentage that the user is willing to accept. - * @example 0.5 - */ - maximum_slippage_percentage: number; /** @enum {string} */ - program: "phantom"; + program: "swap"; /** * @description The user wallet address which requested the quote from the wallet. * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 @@ -479,7 +473,7 @@ export interface components { | { /** * @description The Limo order to be executed, encoded in base64. - * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 + * @example UxMUbQAsjrfQUp5stVwMJ6Mucq7VWTvt4ICe69BJ8lVXqwM+0sysV8OqZTdM0W4p... */ order: string; /** @@ -491,14 +485,8 @@ export interface components { program: "limo"; } | { - /** - * Format: double - * @description The maximum slippage percentage that the user is willing to accept. - * @example 0.5 - */ - maximum_slippage_percentage: number; /** @enum {string} */ - program: "phantom"; + program: "swap"; /** * @description The user wallet address which requested the quote from the wallet. * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 @@ -614,7 +602,7 @@ export interface components { | { /** * @description The Limo order to be executed, encoded in base64. - * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 + * @example UxMUbQAsjrfQUp5stVwMJ6Mucq7VWTvt4ICe69BJ8lVXqwM+0sysV8OqZTdM0W4p... */ order: string; /** @@ -626,26 +614,31 @@ export interface components { program: "limo"; } | { - buy_token: components["schemas"]["TokenAmountSvm"]; - /** - * Format: double - * @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. * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 */ permission_account: string; /** @enum {string} */ - program: "phantom"; + program: "swap"; /** * @description The router account to be used for the opportunity execution of the protocol. * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 */ router_account: string; - sell_token: components["schemas"]["TokenAmountSvm"]; + tokens: + | { + input_token: components["schemas"]["TokenAmountSvm"]; + output_token: components["schemas"]["Pubkey"]; + /** @enum {string} */ + type: "input_token_specified"; + } + | { + input_token: components["schemas"]["Pubkey"]; + output_token: components["schemas"]["TokenAmountSvm"]; + /** @enum {string} */ + type: "output_token_specified"; + }; /** * @description The user wallet address which requested the quote from the wallet. * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 @@ -678,59 +671,80 @@ export interface components { slot: number; }; /** @enum {string} */ - ProgramSvm: "phantom" | "limo"; + ProgramSvm: "swap_kamino" | "limo"; Quote: components["schemas"]["QuoteSvm"]; QuoteCreate: components["schemas"]["QuoteCreateSvm"]; + QuoteCreateSvm: components["schemas"]["QuoteCreateV1SvmParams"] & { + /** @enum {string} */ + version: "v1"; + }; /** - * @description Parameters needed to create a new opportunity from the Phantom wallet. + * @description Parameters needed to create a new opportunity from the swap request. * Auction server will extract the output token price for the auction. */ - QuoteCreatePhantomV1Svm: { + QuoteCreateV1SvmParams: { /** * @description The chain id for creating the quote. * @example solana */ chain_id: string; - /** - * Format: int64 - * @description The input token amount that the user wants to swap. - * @example 100 - */ - input_token_amount: number; /** * @description The token mint address of the input token. * @example EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v */ input_token_mint: string; - /** - * Format: double - * @description The maximum slippage percentage that the user is willing to accept. - * @example 0.5 - */ - maximum_slippage_percentage: number; /** * @description The token mint address of the output token. * @example EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v */ output_token_mint: string; + /** + * @description The router account to send referral fees to. + * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 + */ + router: string; + specified_token_amount: + | { + /** + * Format: int64 + * @example 100 + */ + amount: number; + /** @enum {string} */ + side: "input"; + } + | { + /** + * Format: int64 + * @example 50 + */ + amount: number; + /** @enum {string} */ + side: "output"; + }; /** * @description The user wallet address which requested the quote from the wallet. * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 */ user_wallet_address: string; }; - QuoteCreateSvm: components["schemas"]["QuoteCreateV1Svm"] & { - /** @enum {string} */ - version: "v1"; - }; - QuoteCreateV1Svm: components["schemas"]["QuoteCreatePhantomV1Svm"] & { - /** @enum {string} */ - program: "phantom"; - }; QuoteSvm: components["schemas"]["QuoteV1Svm"] & { /** @enum {string} */ version: "v1"; }; + QuoteTokens: + | { + input_token: components["schemas"]["TokenAmountSvm"]; + output_token: components["schemas"]["Pubkey"]; + /** @enum {string} */ + type: "input_token_specified"; + } + | { + input_token: components["schemas"]["Pubkey"]; + output_token: components["schemas"]["TokenAmountSvm"]; + /** @enum {string} */ + type: "output_token_specified"; + }; QuoteV1Svm: { /** * @description The chain id for the quote. @@ -744,12 +758,6 @@ export interface components { */ expiration_time: number; input_token: components["schemas"]["TokenAmountSvm"]; - /** - * Format: double - * @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. @@ -797,6 +805,25 @@ export interface components { /** @enum {string} */ type: "remove_opportunities"; }; + SpecifiedTokenAmount: + | { + /** + * Format: int64 + * @example 100 + */ + amount: number; + /** @enum {string} */ + side: "input"; + } + | { + /** + * Format: int64 + * @example 50 + */ + amount: number; + /** @enum {string} */ + side: "output"; + }; SvmChainUpdate: { /** @example SLxp9LxX1eE9Z5v99Y92DaYEwyukFgMUF6zRerCF12j */ blockhash: string; @@ -824,7 +851,9 @@ export interface components { TokenAmountSvm: { /** * Format: int64 - * @description The token amount in lamports. + * @description The token amount, represented in the smallest unit of the respective token: + * - For Solana, it is measured in lamports. + * - For other tokens, it follows the smallest denomination of that token. * @example 1000 */ amount: number; diff --git a/sdk/js/src/svm.ts b/sdk/js/src/svm.ts index 1eae2528..70dfd359 100644 --- a/sdk/js/src/svm.ts +++ b/sdk/js/src/svm.ts @@ -6,7 +6,13 @@ import { TransactionInstruction, } from "@solana/web3.js"; import * as anchor from "@coral-xyz/anchor"; -import { BidSvm, ExpressRelaySvmConfig } from "./types"; +import { BidSvm, ExpressRelaySvmConfig, OpportunitySvmSwap } from "./types"; +import { + ASSOCIATED_TOKEN_PROGRAM_ID, + createAssociatedTokenAccountInstruction, + getAssociatedTokenAddressSync, + TOKEN_PROGRAM_ID, +} from "@solana/spl-token"; import { AnchorProvider, Program } from "@coral-xyz/anchor"; import { ExpressRelay } from "./expressRelayTypes"; import expressRelayIdl from "./idl/idlExpressRelay.json"; @@ -82,6 +88,175 @@ export async function constructSubmitBidInstruction( return ixSubmitBid; } +export function getAssociatedTokenAddress( + owner: PublicKey, + tokenMintAddress: PublicKey, + tokenProgram: PublicKey +): PublicKey { + return getAssociatedTokenAddressSync( + tokenMintAddress, + owner, + true, + tokenProgram, + ASSOCIATED_TOKEN_PROGRAM_ID + ); +} + +export function createAssociatedTokenAccountIdempotentInstruction( + owner: PublicKey, + mint: PublicKey, + payer: PublicKey = owner, + tokenProgram: PublicKey +): [PublicKey, TransactionInstruction] { + const ataAddress = getAssociatedTokenAddress(owner, mint, tokenProgram); + const createUserTokenAccountIx = createAssociatedTokenAccountInstruction( + payer, + ataAddress, + owner, + mint, + tokenProgram, + ASSOCIATED_TOKEN_PROGRAM_ID + ); + // idempotent ix discriminator is 1 + createUserTokenAccountIx.data = Buffer.from([1]); + return [ataAddress, createUserTokenAccountIx]; +} + +export async function constructSwapBid( + tx: Transaction, + searcher: PublicKey, + swapOpportunity: OpportunitySvmSwap, + bidAmount: anchor.BN, + deadline: anchor.BN, + chainId: string, + relayerSigner: PublicKey +): Promise { + const expressRelay = new Program( + expressRelayIdl as ExpressRelay, + {} as AnchorProvider + ); + const expressRelayMetadata = getExpressRelayMetadataPda(chainId); + const svmConstants = SVM_CONSTANTS[chainId]; + + const tokenProgramInput = TOKEN_PROGRAM_ID; + const tokenProgramOutput = TOKEN_PROGRAM_ID; + const tokenProgramFee = TOKEN_PROGRAM_ID; + const mintInput = + swapOpportunity.tokens.type === "input_specified" + ? swapOpportunity.tokens.inputToken.token + : swapOpportunity.tokens.inputToken; + const mintOutput = + swapOpportunity.tokens.type === "output_specified" + ? swapOpportunity.tokens.outputToken.token + : swapOpportunity.tokens.outputToken; + const trader = swapOpportunity.userWalletAddress; + const mintFee = mintInput; + const router = swapOpportunity.routerAccount; + + const swapArgs = { + amountInput: + swapOpportunity.tokens.type === "input_specified" + ? new anchor.BN(swapOpportunity.tokens.inputToken.amount) + : bidAmount, + amountOutput: + swapOpportunity.tokens.type === "output_specified" + ? new anchor.BN(swapOpportunity.tokens.outputToken.amount) + : bidAmount, + referralFeeBps: new anchor.BN(0), + feeToken: { input: {} }, + }; + const ixSwap = await expressRelay.methods + .swap(swapArgs) + .accountsStrict({ + expressRelayMetadata, + searcher, + trader: swapOpportunity.userWalletAddress, + searcherInputTa: getAssociatedTokenAddress( + searcher, + mintInput, + tokenProgramInput + ), + searcherOutputTa: getAssociatedTokenAddress( + searcher, + mintOutput, + tokenProgramOutput + ), + traderInputAta: getAssociatedTokenAddress( + trader, + mintInput, + tokenProgramInput + ), + tokenProgramInput, + mintInput, + traderOutputAta: getAssociatedTokenAddress( + trader, + mintOutput, + tokenProgramOutput + ), + tokenProgramOutput, + mintOutput, + routerFeeReceiverTa: getAssociatedTokenAddress( + router, + mintFee, + tokenProgramFee + ), + relayerFeeReceiverAta: getAssociatedTokenAddress( + relayerSigner, + mintFee, + tokenProgramFee + ), + tokenProgramFee, + mintFee, + expressRelayFeeReceiverAta: getAssociatedTokenAddress( + expressRelayMetadata, + mintFee, + tokenProgramFee + ), + }) + .instruction(); + console.log(ixSwap); + ixSwap.programId = svmConstants.expressRelayProgram; + tx.instructions.push( + createAssociatedTokenAccountIdempotentInstruction( + router, + mintFee, + searcher, + tokenProgramFee + )[1] + ); + tx.instructions.push( + createAssociatedTokenAccountIdempotentInstruction( + relayerSigner, + mintFee, + searcher, + tokenProgramFee + )[1] + ); + tx.instructions.push( + createAssociatedTokenAccountIdempotentInstruction( + expressRelayMetadata, + mintFee, + searcher, + tokenProgramFee + )[1] + ); + tx.instructions.push( + createAssociatedTokenAccountIdempotentInstruction( + trader, + mintOutput, + searcher, + tokenProgramOutput + )[1] + ); + tx.instructions.push(ixSwap); + + return { + transaction: tx, + chainId: chainId, + env: "svm", + }; +} + export async function constructSvmBid( tx: Transaction, searcher: PublicKey, diff --git a/sdk/js/src/types.ts b/sdk/js/src/types.ts index d3d2520b..4ee0aae1 100644 --- a/sdk/js/src/types.ts +++ b/sdk/js/src/types.ts @@ -99,9 +99,7 @@ export type OpportunityEvm = { opportunityId: string; }; -export type OpportunitySvm = { - order: OrderStateAndAddress; - program: "limo"; +export type OpportunitySvmMetadata = { /** * The chain id where the opportunity will be executed. */ @@ -116,9 +114,39 @@ export type OpportunitySvm = { opportunityId: string; }; +export type OpportunitySvmLimo = { + order: OrderStateAndAddress; + program: "limo"; +} & OpportunitySvmMetadata; + +export type TokenAmountSvm = { + amount: number; + token: PublicKey; +}; +export type OpportunitySvmSwap = { + permissionAccount: PublicKey; + routerAccount: PublicKey; + userWalletAddress: PublicKey; + // TODO: maybe type should be camelCase too? + tokens: + | { + inputToken: PublicKey; + outputToken: TokenAmountSvm; + type: "output_specified"; + } + | { + inputToken: TokenAmountSvm; + outputToken: PublicKey; + type: "input_specified"; + }; + program: "swap"; +} & OpportunitySvmMetadata; + +export type OpportunitySvm = OpportunitySvmLimo | OpportunitySvmSwap; + export type OpportunityCreate = | Omit - | Omit; + | Omit; export type Opportunity = OpportunityEvm | OpportunitySvm; /** @@ -246,6 +274,7 @@ export type BidsResponse = { export type SvmConstantsConfig = { expressRelayProgram: PublicKey; + walletRouter: PublicKey; }; export type SvmChainUpdate = { diff --git a/tilt-scripts/svm/test_swap.py b/tilt-scripts/svm/test_swap.py new file mode 100644 index 00000000..fe0ce4d9 --- /dev/null +++ b/tilt-scripts/svm/test_swap.py @@ -0,0 +1,95 @@ +import argparse +import asyncio +import base64 +import logging +import random +from pathlib import Path + +import httpx +from solana.rpc.async_api import AsyncClient +from solana.rpc.commitment import Confirmed +from solana.transaction import Transaction +from solders.transaction import Transaction as SoldersTransaction + +from ..svm.helpers import configure_logger, read_kp_from_json + +logger = logging.getLogger(__name__) + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser() + parser.add_argument("-v", "--verbose", action="count", default=0) + parser.add_argument( + "--file-private-key-taker", + type=Path, + required=True, + help="JSON file containing the private key (as a byte array) of the taker for swap transaction", + ) + parser.add_argument( + "--auction-server-url", + type=str, + required=True, + help="Auction server endpoint to use for submitting bids", + ) + parser.add_argument( + "--rpc-url", + type=str, + required=False, + default="http://localhost:8899", + help="URL of the Solana RPC endpoint to use for submitting transactions", + ) + parser.add_argument( + "--input-mint", + type=str, + required=True, + help="Input mint", + ) + parser.add_argument( + "--output-mint", + type=str, + required=True, + help="Output mint", + ) + return parser.parse_args() + + +async def main(): + args = parse_args() + + configure_logger(logger, args.verbose) + + kp_taker = read_kp_from_json(args.file_private_key_taker) + pk_taker = kp_taker.pubkey() + logger.info("Taker pubkey: %s", pk_taker) + payload = { + "chain_id": "development-solana", + "input_token_mint": args.input_mint, + "output_token_mint": args.output_mint, + "router": "3hv8L8UeBbyM3M25dF3h2C5p8yA4FptD7FFZu4Z1jCMn", + "specified_token_amount": {"amount": random.randint(1, 1000), "side": "input"}, + "user_wallet_address": str(pk_taker), + "version": "v1", + } + async with httpx.AsyncClient() as http_client: + result = await http_client.post( + args.auction_server_url + "/v1/opportunities/quote", json=payload + ) + if result.status_code != 200: + logger.error("Failed to get quote from auction server") + return + logger.info("Input token %s", result.json()["input_token"]) + logger.info("Output token %s", result.json()["output_token"]) + tx = SoldersTransaction.from_bytes( + base64.b64decode(result.json()["transaction"]) + ) + tx = Transaction.from_solders(tx) + tx.sign_partial(kp_taker) + async with AsyncClient(args.rpc_url) as rpc_client: + await rpc_client.send_raw_transaction(tx.serialize()) + logger.info("Swap transaction sent. Signature: %s", tx.signatures[0]) + await rpc_client.confirm_transaction(tx.signatures[0], commitment=Confirmed) + logger.info("Swap transaction confirmed") + + +if __name__ == "__main__": + asyncio.run(main()) From d7eee0aef2d5ea20100847092a3e674f7b58ea0f Mon Sep 17 00:00:00 2001 From: Amin Moghaddam Date: Thu, 9 Jan 2025 20:03:46 -0500 Subject: [PATCH 02/25] cleanup --- .../src/auction/service/verification.rs | 19 +++---- sdk/js/src/examples/simpleSearcherLimo.ts | 1 - sdk/js/src/expressRelayTypes.d.ts | 53 +++++++++++++++++-- sdk/js/src/idl/idlExpressRelay.json | 53 +++++++++++++++++-- sdk/js/src/svm.ts | 2 +- 5 files changed, 107 insertions(+), 21 deletions(-) diff --git a/auction-server/src/auction/service/verification.rs b/auction-server/src/auction/service/verification.rs index 9d8ede72..61442f27 100644 --- a/auction-server/src/auction/service/verification.rs +++ b/auction-server/src/auction/service/verification.rs @@ -654,21 +654,18 @@ impl Service { } let permission_account = get_quote_permission_key(&tokens, &user_wallet); - Ok(BidDataSvm { amount: bid_amount, permission_account, router: self.config.chain_config.wallet_program_router_account, - // TODO*: to fix once deadline param added to swap instruction--just set this way to make sure compiles - deadline: OffsetDateTime::now_utc() + Duration::from_secs(20), - // deadline: OffsetDateTime::from_unix_timestamp(swap_data.deadline).map_err( - // |e| { - // RestError::BadParameters(format!( - // "Invalid deadline: {:?} {:?}", - // swap_data.deadline, e - // )) - // }, - // )?, + deadline: OffsetDateTime::from_unix_timestamp(swap_data.deadline).map_err( + |e| { + RestError::BadParameters(format!( + "Invalid deadline: {:?} {:?}", + swap_data.deadline, e + )) + }, + )?, submit_type: SubmitType::ByOther, }) } diff --git a/sdk/js/src/examples/simpleSearcherLimo.ts b/sdk/js/src/examples/simpleSearcherLimo.ts index 3bfbc5c2..b77109cb 100644 --- a/sdk/js/src/examples/simpleSearcherLimo.ts +++ b/sdk/js/src/examples/simpleSearcherLimo.ts @@ -165,7 +165,6 @@ export class SimpleSearcherLimo { this.latestChainUpdate[this.chainId].blockhash; bid.transaction.feePayer = this.searcher.publicKey; bid.transaction.partialSign(this.searcher); - console.log(bid.transaction); return bid; } diff --git a/sdk/js/src/expressRelayTypes.d.ts b/sdk/js/src/expressRelayTypes.d.ts index 4122928d..d2849b4f 100644 --- a/sdk/js/src/expressRelayTypes.d.ts +++ b/sdk/js/src/expressRelayTypes.d.ts @@ -129,7 +129,6 @@ export type ExpressRelay = { accounts: [ { name: "admin"; - writable: true; signer: true; relations: ["expressRelayMetadata"]; }, @@ -157,7 +156,6 @@ export type ExpressRelay = { accounts: [ { name: "admin"; - writable: true; signer: true; relations: ["expressRelayMetadata"]; }, @@ -258,7 +256,6 @@ export type ExpressRelay = { accounts: [ { name: "admin"; - writable: true; signer: true; relations: ["expressRelayMetadata"]; }, @@ -286,6 +283,39 @@ export type ExpressRelay = { } ]; }, + { + name: "setSwapPlatformFee"; + discriminator: [2, 135, 75, 15, 8, 105, 142, 47]; + accounts: [ + { + name: "admin"; + signer: true; + relations: ["expressRelayMetadata"]; + }, + { + name: "expressRelayMetadata"; + writable: true; + pda: { + seeds: [ + { + kind: "const"; + value: [109, 101, 116, 97, 100, 97, 116, 97]; + } + ]; + }; + } + ]; + args: [ + { + name: "data"; + type: { + defined: { + name: "setSwapPlatformFeeArgs"; + }; + }; + } + ]; + }, { name: "submitBid"; docs: [ @@ -686,7 +716,6 @@ export type ExpressRelay = { accounts: [ { name: "admin"; - writable: true; signer: true; relations: ["expressRelayMetadata"]; }, @@ -884,6 +913,18 @@ export type ExpressRelay = { ]; }; }, + { + name: "setSwapPlatformFeeArgs"; + type: { + kind: "struct"; + fields: [ + { + name: "swapPlatformFeeBps"; + type: "u64"; + } + ]; + }; + }, { name: "submitBidArgs"; type: { @@ -911,6 +952,10 @@ export type ExpressRelay = { type: { kind: "struct"; fields: [ + { + name: "deadline"; + type: "i64"; + }, { name: "amountInput"; type: "u64"; diff --git a/sdk/js/src/idl/idlExpressRelay.json b/sdk/js/src/idl/idlExpressRelay.json index f4494a29..2b5b6a23 100644 --- a/sdk/js/src/idl/idlExpressRelay.json +++ b/sdk/js/src/idl/idlExpressRelay.json @@ -111,7 +111,6 @@ "accounts": [ { "name": "admin", - "writable": true, "signer": true, "relations": ["express_relay_metadata"] }, @@ -139,7 +138,6 @@ "accounts": [ { "name": "admin", - "writable": true, "signer": true, "relations": ["express_relay_metadata"] }, @@ -228,7 +226,6 @@ "accounts": [ { "name": "admin", - "writable": true, "signer": true, "relations": ["express_relay_metadata"] }, @@ -256,6 +253,39 @@ } ] }, + { + "name": "set_swap_platform_fee", + "discriminator": [2, 135, 75, 15, 8, 105, 142, 47], + "accounts": [ + { + "name": "admin", + "signer": true, + "relations": ["express_relay_metadata"] + }, + { + "name": "express_relay_metadata", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [109, 101, 116, 97, 100, 97, 116, 97] + } + ] + } + } + ], + "args": [ + { + "name": "data", + "type": { + "defined": { + "name": "SetSwapPlatformFeeArgs" + } + } + } + ] + }, { "name": "submit_bid", "docs": [ @@ -528,7 +558,6 @@ "accounts": [ { "name": "admin", - "writable": true, "signer": true, "relations": ["express_relay_metadata"] }, @@ -726,6 +755,18 @@ ] } }, + { + "name": "SetSwapPlatformFeeArgs", + "type": { + "kind": "struct", + "fields": [ + { + "name": "swap_platform_fee_bps", + "type": "u64" + } + ] + } + }, { "name": "SubmitBidArgs", "type": { @@ -753,6 +794,10 @@ "type": { "kind": "struct", "fields": [ + { + "name": "deadline", + "type": "i64" + }, { "name": "amount_input", "type": "u64" diff --git a/sdk/js/src/svm.ts b/sdk/js/src/svm.ts index 70dfd359..e1e5c989 100644 --- a/sdk/js/src/svm.ts +++ b/sdk/js/src/svm.ts @@ -163,6 +163,7 @@ export async function constructSwapBid( ? new anchor.BN(swapOpportunity.tokens.outputToken.amount) : bidAmount, referralFeeBps: new anchor.BN(0), + deadline, feeToken: { input: {} }, }; const ixSwap = await expressRelay.methods @@ -214,7 +215,6 @@ export async function constructSwapBid( ), }) .instruction(); - console.log(ixSwap); ixSwap.programId = svmConstants.expressRelayProgram; tx.instructions.push( createAssociatedTokenAccountIdempotentInstruction( From 221015bdc2af503418dd22240daadc3f72f290ce Mon Sep 17 00:00:00 2001 From: Amin Moghaddam Date: Mon, 13 Jan 2025 16:10:07 -0500 Subject: [PATCH 03/25] test --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d698d042..51ae3a94 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -25,14 +25,14 @@ repos: language: "rust" entry: cargo +nightly-2024-12-03 fmt --manifest-path ./Cargo.toml --all -- --config-path rustfmt.toml pass_filenames: false - types_or: ["rust"] + types_or: ["rust", "cargo", "cargo-lock"] files: . - id: cargo-clippy-workspace name: Cargo clippy for workspace language: "rust" entry: cargo +stable clippy --manifest-path ./Cargo.toml --tests -- -D warnings pass_filenames: false - types_or: ["rust"] + types_or: ["rust", "cargo", "cargo-lock"] files: . # Hooks for contracts-svm - id: cargo-fmt-contracts-svm From a65ae6c04e0bad8cd64b46f068dcca833c69c950 Mon Sep 17 00:00:00 2001 From: Amin Moghaddam Date: Tue, 14 Jan 2025 16:09:53 -0500 Subject: [PATCH 04/25] t --- Tiltfile | 18 +++++------ auction-server/api-types/src/bid.rs | 30 +++++++++++++++++-- auction-server/api-types/src/opportunity.rs | 1 + auction-server/src/auction/api.rs | 19 +++++++++--- auction-server/src/auction/entities/bid.rs | 26 ++++++++++++++-- .../src/auction/service/verification.rs | 28 +++++++++++++---- .../opportunity/service/get_opportunities.rs | 11 +++++++ 7 files changed, 110 insertions(+), 23 deletions(-) diff --git a/Tiltfile b/Tiltfile index d2d2b132..81745ff3 100644 --- a/Tiltfile +++ b/Tiltfile @@ -95,20 +95,20 @@ local_resource( "svm-create-mints", """solana-keygen new -o keypairs/mint_buy.json -f --no-bip39-passphrase \ && solana-keygen new -o keypairs/mint_sell.json -f --no-bip39-passphrase \ - && spl-token create-token -u localhost --fee-payer keypairs/admin.json --mint-authority keypairs/admin.json keypairs/mint_sell.json \ - && spl-token create-token -u localhost --fee-payer keypairs/admin.json --mint-authority keypairs/admin.json keypairs/mint_buy.json \ + && spl-token create-token -u localhost --fee-payer keypairs/admin.json --decimals 6 --mint-authority keypairs/admin.json keypairs/mint_sell.json \ + && spl-token create-token -u localhost --fee-payer keypairs/admin.json --decimals 6 --mint-authority keypairs/admin.json keypairs/mint_buy.json \ && spl-token create-account -u localhost keypairs/mint_buy.json --fee-payer keypairs/admin.json --owner keypairs/searcher_js.json \ && spl-token create-account -u localhost keypairs/mint_sell.json --fee-payer keypairs/admin.json --owner keypairs/searcher_js.json \ && spl-token create-account -u localhost keypairs/mint_buy.json --fee-payer keypairs/admin.json --owner keypairs/searcher_py.json \ && spl-token create-account -u localhost keypairs/mint_sell.json --fee-payer keypairs/admin.json --owner keypairs/searcher_py.json \ && spl-token create-account -u localhost keypairs/mint_buy.json --fee-payer keypairs/admin.json --owner keypairs/admin.json \ && spl-token create-account -u localhost keypairs/mint_sell.json --fee-payer keypairs/admin.json --owner keypairs/admin.json \ - && spl-token mint -u localhost keypairs/mint_buy.json 1000000000 --recipient-owner keypairs/searcher_js.json --mint-authority keypairs/admin.json \ - && spl-token mint -u localhost keypairs/mint_sell.json 1000000000 --recipient-owner keypairs/searcher_js.json --mint-authority keypairs/admin.json \ - && spl-token mint -u localhost keypairs/mint_buy.json 1000000000 --recipient-owner keypairs/searcher_py.json --mint-authority keypairs/admin.json \ - && spl-token mint -u localhost keypairs/mint_sell.json 1000000000 --recipient-owner keypairs/searcher_py.json --mint-authority keypairs/admin.json \ - && spl-token mint -u localhost keypairs/mint_buy.json 1000000000 --recipient-owner keypairs/admin.json --mint-authority keypairs/admin.json \ - && spl-token mint -u localhost keypairs/mint_sell.json 1000000000 --recipient-owner keypairs/admin.json --mint-authority keypairs/admin.json""", + && spl-token mint -u localhost keypairs/mint_buy.json 100000000000 --recipient-owner keypairs/searcher_js.json --mint-authority keypairs/admin.json \ + && spl-token mint -u localhost keypairs/mint_sell.json 100000000000 --recipient-owner keypairs/searcher_js.json --mint-authority keypairs/admin.json \ + && spl-token mint -u localhost keypairs/mint_buy.json 100000000000 --recipient-owner keypairs/searcher_py.json --mint-authority keypairs/admin.json \ + && spl-token mint -u localhost keypairs/mint_sell.json 100000000000 --recipient-owner keypairs/searcher_py.json --mint-authority keypairs/admin.json \ + && spl-token mint -u localhost keypairs/mint_buy.json 100000000000 --recipient-owner keypairs/admin.json --mint-authority keypairs/admin.json \ + && spl-token mint -u localhost keypairs/mint_sell.json 100000000000 --recipient-owner keypairs/admin.json --mint-authority keypairs/admin.json""", resource_deps=["svm-setup-accounts"] ) @@ -218,7 +218,7 @@ local_resource( local_resource( "svm-searcher-js", - serve_cmd="pnpm run testing-searcher-limo --endpoint-express-relay http://127.0.0.1:9000 --chain-id development-solana --private-key-json-file ../../keypairs/searcher_js.json --endpoint-svm http://127.0.0.1:8899 --bid 1000 --fill-rate 4 --bid-margin 100", + serve_cmd="pnpm run testing-searcher-limo --endpoint-express-relay http://127.0.0.1:9000 --chain-id development-solana --private-key-json-file ../../keypairs/searcher_js.json --endpoint-svm http://127.0.0.1:8899 --bid 10000000 --fill-rate 4 --bid-margin 100", serve_dir="sdk/js", resource_deps=["svm-initialize-programs", "auction-server"], ) diff --git a/auction-server/api-types/src/bid.rs b/auction-server/api-types/src/bid.rs index 82163653..da455e8b 100644 --- a/auction-server/api-types/src/bid.rs +++ b/auction-server/api-types/src/bid.rs @@ -35,6 +35,7 @@ use { }, uuid::Uuid, }; +use crate::opportunity::OpportunityId; pub type BidId = Uuid; pub type BidAmountSvm = u64; @@ -251,7 +252,7 @@ pub struct BidCreateEvm { } #[derive(Serialize, Deserialize, ToSchema, Clone, Debug)] -pub struct BidCreateSvm { +pub struct BidCreateOnChainSvm { /// The chain id to bid on. #[schema(example = "solana", value_type = String)] pub chain_id: ChainId, @@ -265,6 +266,30 @@ pub struct BidCreateSvm { pub slot: Option, } +#[derive(Serialize, Deserialize, ToSchema, Clone, Debug)] +#[serde(tag = "type")] +pub struct BidCreateSwapSvm { + /// The chain id to bid on. + #[schema(example = "solana", value_type = String)] + pub chain_id: ChainId, + /// The transaction for bid. + #[schema(example = "SGVsbG8sIFdvcmxkIQ==", value_type = String)] + #[serde(with = "crate::serde::transaction_svm")] + pub transaction: VersionedTransaction, + /// The id of the swap opportunity to bid on. + #[schema(example = "obo3ee3e-58cc-4372-a567-0e02b2c3d479", value_type = Option)] + pub opportunity_id: OpportunityId, +} + + +#[derive(Serialize, Deserialize, ToSchema, Debug, Clone)] +#[serde(untagged)] +pub enum BidCreateSvm { + OnChain(BidCreateOnChainSvm), + Swap(BidCreateSwapSvm), +} + + #[derive(Serialize, Deserialize, ToSchema, Debug, Clone)] #[serde(untagged)] // Remove tags to avoid key-value wrapping pub enum BidCreate { @@ -304,7 +329,8 @@ impl BidCreate { pub fn get_chain_id(&self) -> ChainId { match self { BidCreate::Evm(bid_create_evm) => bid_create_evm.chain_id.clone(), - BidCreate::Svm(bid_create_svm) => bid_create_svm.chain_id.clone(), + BidCreate::Svm(BidCreateSvm::Swap(bid_create_svm)) => bid_create_svm.chain_id.clone(), + BidCreate::Svm(BidCreateSvm::OnChain(bid_create_svm)) => bid_create_svm.chain_id.clone(), } } } diff --git a/auction-server/api-types/src/opportunity.rs b/auction-server/api-types/src/opportunity.rs index c7da199a..6e934a07 100644 --- a/auction-server/api-types/src/opportunity.rs +++ b/auction-server/api-types/src/opportunity.rs @@ -62,6 +62,7 @@ pub struct OpportunityBidResult { #[derive(Serialize, Deserialize, ToSchema, Clone, PartialEq, Debug, Display)] #[serde(rename_all = "snake_case")] +#[strum(serialize_all = "snake_case")] pub enum ProgramSvm { SwapKamino, Limo, diff --git a/auction-server/src/auction/api.rs b/auction-server/src/auction/api.rs index 40044d54..c1a35828 100644 --- a/auction-server/src/auction/api.rs +++ b/auction-server/src/auction/api.rs @@ -422,15 +422,26 @@ impl ApiTrait for Svm { profile: Option, ) -> Result, RestError> { match bid { - BidCreate::Svm(bid_create_svm) => { + BidCreate::Svm(BidCreateSvm::OnChain(bid_create_svm)) => { Ok(entities::BidCreate:: { chain_id: bid_create_svm.chain_id.clone(), profile, initiation_time: OffsetDateTime::now_utc(), - chain_data: entities::BidChainDataCreateSvm { + chain_data: entities::BidChainDataCreateSvm::OnChain(entities::BidChainDataOnChainCreateSvm { transaction: bid_create_svm.transaction.clone(), - slot: bid_create_svm.slot, - }, + slot: bid_create_svm.slot, + }), + }) + }, + BidCreate::Svm(BidCreateSvm::Swap(bid_create_svm)) => { + Ok(entities::BidCreate:: { + chain_id: bid_create_svm.chain_id.clone(), + profile, + initiation_time: OffsetDateTime::now_utc(), + chain_data: entities::BidChainDataCreateSvm::Swap(entities::BidChainDataSwapCreateSvm { + transaction: bid_create_svm.transaction.clone(), + opportunity_id: bid_create_svm.opportunity_id, + }), }) } _ => Err(RestError::BadParameters( diff --git a/auction-server/src/auction/entities/bid.rs b/auction-server/src/auction/entities/bid.rs index f0e88b46..9ecc449c 100644 --- a/auction-server/src/auction/entities/bid.rs +++ b/auction-server/src/auction/entities/bid.rs @@ -262,11 +262,33 @@ pub struct BidCreate { } #[derive(Clone, Debug)] -pub struct BidChainDataCreateSvm { +pub struct BidChainDataOnChainCreateSvm { pub transaction: VersionedTransaction, pub slot: Option, } +#[derive(Clone, Debug)] +pub struct BidChainDataSwapCreateSvm { + pub transaction: VersionedTransaction, + pub opportunity_id : Uuid, +} + + +#[derive(Clone, Debug)] +pub enum BidChainDataCreateSvm{ + OnChain(BidChainDataOnChainCreateSvm), + Swap(BidChainDataSwapCreateSvm), +} + +impl BidChainDataCreateSvm{ + pub fn get_transaction(&self) -> &VersionedTransaction { + match self { + BidChainDataCreateSvm::OnChain(data) => &data.transaction, + BidChainDataCreateSvm::Swap(data) => &data.transaction, + } + } +} + #[derive(Clone, Debug)] pub struct BidChainDataCreateEvm { pub target_contract: Address, @@ -280,7 +302,7 @@ pub type BidAmountEvm = U256; impl PartialEq> for BidCreate { fn eq(&self, other: &Bid) -> bool { - self.chain_data.transaction == other.chain_data.transaction + *self.chain_data.get_transaction() == other.chain_data.transaction && self.chain_id == other.chain_id } } diff --git a/auction-server/src/auction/service/verification.rs b/auction-server/src/auction/service/verification.rs index a2282fd5..61ce17d7 100644 --- a/auction-server/src/auction/service/verification.rs +++ b/auction-server/src/auction/service/verification.rs @@ -84,6 +84,8 @@ use { time::OffsetDateTime, uuid::Uuid, }; +use crate::auction::entities::BidChainDataCreateSvm; +use crate::opportunity::service::get_opportunities::GetOpportunityByIdInput; pub struct VerifyBidInput { pub bid_create: entities::BidCreate, @@ -692,6 +694,15 @@ impl Service { ), }) .await; + + + // if bid.chain_data.opportunity_id.is_some() { + // let opp = self.opportunity_service.get_opportunity_by_id(GetOpportunityByIdInput { opportunity_id: bid.chain_data.opportunity_id.unwrap() }).await; + // tracing::info!("opp {:?}", opp); + // tracing::info!("chain_data {:?}", chain_data); + // } + + let opportunity = opportunities .first() .ok_or_else(|| RestError::BadParameters("Opportunity not found".to_string()))?; @@ -718,7 +729,12 @@ impl Service { const RETRY_LIMIT: usize = 5; const RETRY_DELAY: Duration = Duration::from_millis(100); let mut retry_count = 0; - let bid_slot = bid.chain_data.slot.unwrap_or_default(); + let bid_slot = match &bid.chain_data { + BidChainDataCreateSvm::OnChain(onchain_data) => { + onchain_data.slot + } + BidChainDataCreateSvm::Swap(_) => None + }.unwrap_or_default(); let should_retry = |result_slot: Slot, retry_count: usize, @@ -743,7 +759,7 @@ impl Service { .config .chain_config .simulator - .simulate_transaction(&bid.chain_data.transaction) + .simulate_transaction(&bid.chain_data.get_transaction()) .await; let result = response.map_err(|e| { tracing::error!("Error while simulating bid: {:?}", e); @@ -830,11 +846,11 @@ impl Verification for Service { input: VerifyBidInput, ) -> Result, RestError> { let bid = input.bid_create; - Svm::check_tx_size(&bid.chain_data.transaction)?; - self.check_compute_budget(&bid.chain_data.transaction) + Svm::check_tx_size(&bid.chain_data.get_transaction())?; + self.check_compute_budget(&bid.chain_data.get_transaction()) .await?; let bid_data = self - .extract_bid_data(bid.chain_data.transaction.clone()) + .extract_bid_data(bid.chain_data.get_transaction().clone()) .await?; let bid_payment_instruction_type = match bid_data.submit_type { SubmitType::ByServer => BidPaymentInstructionType::SubmitBid, @@ -850,7 +866,7 @@ impl Verification for Service { permission_account: bid_data.permission_account, router: bid_data.router, bid_payment_instruction_type, - transaction: bid.chain_data.transaction.clone(), + transaction: bid.chain_data.get_transaction().clone(), }; let permission_key = bid_chain_data.get_permission_key(); tracing::Span::current().record("permission_key", bid_data.permission_account.to_string()); diff --git a/auction-server/src/opportunity/service/get_opportunities.rs b/auction-server/src/opportunity/service/get_opportunities.rs index 4f3f050b..97cb360a 100644 --- a/auction-server/src/opportunity/service/get_opportunities.rs +++ b/auction-server/src/opportunity/service/get_opportunities.rs @@ -10,6 +10,7 @@ use { express_relay_api_types::opportunity::{ GetOpportunitiesQueryParams, OpportunityMode, + OpportunityId }, }; @@ -17,7 +18,17 @@ pub struct GetOpportunitiesInput { pub query_params: GetOpportunitiesQueryParams, } +pub struct GetOpportunityByIdInput { + pub opportunity_id: OpportunityId, +} + impl Service { + pub async fn get_opportunity_by_id( + &self, + input: GetOpportunityByIdInput, + )-> Option<::Opportunity> { + self.repo.get_in_memory_opportunity_by_id(input.opportunity_id).await + } pub async fn get_opportunities( &self, input: GetOpportunitiesInput, From 30f5f0e7a833aac34580fe01ddbcfd297957a410 Mon Sep 17 00:00:00 2001 From: Amin Moghaddam Date: Wed, 15 Jan 2025 09:25:10 -0500 Subject: [PATCH 05/25] test --- auction-server/api-types/src/bid.rs | 14 +- auction-server/api-types/src/opportunity.rs | 19 +- auction-server/src/api.rs | 4 + .../src/auction/service/verification.rs | 157 ++- .../opportunity/entities/opportunity_svm.rs | 6 + sdk/js/src/const.ts | 2 - sdk/js/src/examples/simpleSearcherLimo.ts | 5 +- sdk/js/src/expressRelayTypes.d.ts | 1255 ++++++++++------- sdk/js/src/idl/idlExpressRelay.json | 467 +++++- sdk/js/src/index.ts | 56 +- sdk/js/src/serverTypes.d.ts | 628 ++++----- sdk/js/src/svm.ts | 31 +- sdk/js/src/types.ts | 35 +- tilt-scripts/svm/test_swap.py | 1 + 14 files changed, 1674 insertions(+), 1006 deletions(-) diff --git a/auction-server/api-types/src/bid.rs b/auction-server/api-types/src/bid.rs index da455e8b..afd3293b 100644 --- a/auction-server/api-types/src/bid.rs +++ b/auction-server/api-types/src/bid.rs @@ -267,7 +267,11 @@ pub struct BidCreateOnChainSvm { } #[derive(Serialize, Deserialize, ToSchema, Clone, Debug)] -#[serde(tag = "type")] +#[serde(rename_all = "snake_case")] +pub enum BidCreateSwapSvmTag { + Swap +} +#[derive(Serialize, Deserialize, ToSchema, Clone, Debug)] pub struct BidCreateSwapSvm { /// The chain id to bid on. #[schema(example = "solana", value_type = String)] @@ -277,16 +281,20 @@ pub struct BidCreateSwapSvm { #[serde(with = "crate::serde::transaction_svm")] pub transaction: VersionedTransaction, /// The id of the swap opportunity to bid on. - #[schema(example = "obo3ee3e-58cc-4372-a567-0e02b2c3d479", value_type = Option)] + #[schema(example = "obo3ee3e-58cc-4372-a567-0e02b2c3d479", value_type = String)] pub opportunity_id: OpportunityId, + /// The bid type. Should be "swap" + #[schema(example = "swap")] + #[serde(rename = "type")] + pub _type: BidCreateSwapSvmTag, // this is mainly to distinguish next types of bids in the future } #[derive(Serialize, Deserialize, ToSchema, Debug, Clone)] #[serde(untagged)] pub enum BidCreateSvm { - OnChain(BidCreateOnChainSvm), Swap(BidCreateSwapSvm), + OnChain(BidCreateOnChainSvm), } diff --git a/auction-server/api-types/src/opportunity.rs b/auction-server/api-types/src/opportunity.rs index 6e934a07..39542848 100644 --- a/auction-server/api-types/src/opportunity.rs +++ b/auction-server/api-types/src/opportunity.rs @@ -192,12 +192,11 @@ pub struct TokenAmountSvm { /// Program specific parameters for the opportunity. #[serde_as] #[derive(Serialize, Deserialize, ToSchema, Clone, PartialEq, Debug)] -#[serde(tag = "program")] +#[serde(tag = "program", rename_all = "snake_case")] pub enum OpportunityCreateProgramParamsV1Svm { /// Limo program specific parameters for the opportunity. /// It contains the Limo order to be executed, encoded in base64. /// SDKs will decode this order and create transaction for bidding on the opportunity. - #[serde(rename = "limo")] #[schema(title = "limo")] Limo { /// The Limo order to be executed, encoded in base64. @@ -211,7 +210,6 @@ pub enum OpportunityCreateProgramParamsV1Svm { order_address: Pubkey, }, /// Swap program specific parameters for the opportunity. - #[serde(rename = "swap")] #[schema(title = "swap")] Swap { /// The user wallet address which requested the quote from the wallet. @@ -331,12 +329,27 @@ pub enum OpportunityParamsV1ProgramSvm { #[serde_as(as = "DisplayFromStr")] router_account: Pubkey, + /// The referral fee in basis points. + #[schema(example = 10, value_type = u16)] + referral_fee_bps: u16, + + /// Specifies whether the fees are to be paid in input or output token. + #[schema(example = "input_token")] + fee_token: FeeToken, + /// Details about the tokens to be swapped. Either the input token amount or the output token amount must be specified. #[schema(inline)] tokens: QuoteTokens, }, } +#[derive(Serialize, Deserialize, ToSchema, Clone, PartialEq, Debug, ToResponse)] +#[serde(rename_all = "snake_case")] +pub enum FeeToken { + InputToken, + OutputToken, +} + #[derive(Serialize, Deserialize, ToSchema, Clone, PartialEq, Debug, ToResponse)] #[serde(tag = "type", rename_all = "snake_case")] pub enum QuoteTokens { diff --git a/auction-server/src/api.rs b/auction-server/src/api.rs index 7c82eb45..e441936a 100644 --- a/auction-server/src/api.rs +++ b/auction-server/src/api.rs @@ -328,6 +328,9 @@ pub async fn start_api(run_options: RunOptions, store: Arc) -> Result< api_types::ws::APIResponse, api_types::bid::BidCreate, api_types::bid::BidCreateEvm, + api_types::bid::BidCreateOnChainSvm, + api_types::bid::BidCreateSwapSvm, + api_types::bid::BidCreateSwapSvmTag, api_types::bid::BidCreateSvm, api_types::bid::BidStatus, api_types::bid::BidStatusEvm, @@ -372,6 +375,7 @@ pub async fn start_api(run_options: RunOptions, store: Arc) -> Result< api_types::opportunity::OpportunityDeleteV1Svm, api_types::opportunity::OpportunityDeleteV1Evm, api_types::opportunity::ProgramSvm, + api_types::opportunity::FeeToken, ErrorBodyResponse, api_types::ws::ClientRequest, diff --git a/auction-server/src/auction/service/verification.rs b/auction-server/src/auction/service/verification.rs index 61ce17d7..68ce6e8d 100644 --- a/auction-server/src/auction/service/verification.rs +++ b/auction-server/src/auction/service/verification.rs @@ -1,3 +1,4 @@ +use std::str::FromStr; use { super::{ auction_manager::TOTAL_BIDS_PER_AUCTION_EVM, @@ -447,7 +448,7 @@ impl Service { match instructions.len() { 1 => Ok(instructions[0].clone()), - _ => Err(RestError::BadParameters("Bid must include exactly one instruction to Express Relay program that pays the bid".to_string())), + _ => Err(RestError::BadParameters(format!("Bid must include exactly one instruction to Express Relay program that pays the bid but found {} instructions", instructions.len()))), } } @@ -483,42 +484,31 @@ impl Service { async fn extract_bid_data( &self, - transaction: VersionedTransaction, + bid_chain_data_create_svm: &BidChainDataCreateSvm, ) -> Result { - let submit_bid_instruction_result = self.extract_express_relay_bid_instruction( - transaction.clone(), - BidPaymentInstructionType::SubmitBid, - ); - let swap_instruction_result = self.extract_express_relay_bid_instruction( - transaction.clone(), - BidPaymentInstructionType::Swap, - ); - - match ( - submit_bid_instruction_result.clone(), - swap_instruction_result.clone(), - ) { - (Ok(submit_bid_instruction), Err(_)) => { + let svm_config = &self.config + .chain_config + .express_relay; + match bid_chain_data_create_svm { + BidChainDataCreateSvm::OnChain(bid_data) => { + let submit_bid_instruction = self.extract_express_relay_bid_instruction( + bid_data.transaction.clone(), + BidPaymentInstructionType::SubmitBid, + )?; let submit_bid_data = Self::extract_submit_bid_data(&submit_bid_instruction)?; let permission_account = self .extract_account( - &transaction, + &bid_data.transaction, &submit_bid_instruction, - self.config - .chain_config - .express_relay - .permission_account_position_submit_bid, + svm_config.permission_account_position_submit_bid, ) .await?; let router = self .extract_account( - &transaction, + &bid_data.transaction, &submit_bid_instruction, - self.config - .chain_config - .express_relay - .router_account_position_submit_bid, + svm_config.router_account_position_submit_bid, ) .await?; Ok(BidDataSvm { @@ -534,49 +524,34 @@ impl Service { })?, submit_type: SubmitType::ByServer, }) - } - (Err(_), Ok(swap_instruction)) => { + }, + BidChainDataCreateSvm::Swap(bid_data) => { + let swap_instruction = self.extract_express_relay_bid_instruction( + bid_data.transaction.clone(), + BidPaymentInstructionType::Swap, + )?; let swap_data = Self::extract_swap_data(&swap_instruction)?; - let router = self - .extract_account( - &transaction, - &swap_instruction, - self.config - .chain_config - .express_relay - .router_account_position_swap, - ) - .await?; let user_wallet = self .extract_account( - &transaction, + &bid_data.transaction, &swap_instruction, - self.config - .chain_config - .express_relay - .user_wallet_account_position_swap, + svm_config.user_wallet_account_position_swap, ) .await?; let mint_input = self .extract_account( - &transaction, + &bid_data.transaction, &swap_instruction, - self.config - .chain_config - .express_relay - .mint_input_account_position_swap, + svm_config.mint_input_account_position_swap, ) .await?; let mint_output = self .extract_account( - &transaction, + &bid_data.transaction, &swap_instruction, - self.config - .chain_config - .express_relay - .mint_output_account_position_swap, + svm_config.mint_output_account_position_swap, ) .await?; @@ -602,6 +577,58 @@ impl Service { }, ), }; + let mint_fee = match swap_data.fee_token { + FeeToken::Input => mint_input, + FeeToken::Output => mint_output, + }; + + let opp = self.opportunity_service.get_opportunity_by_id(GetOpportunityByIdInput { opportunity_id: bid_data.opportunity_id }).await.ok_or( + RestError::BadParameters("No swap opportunity with the given id found".to_string()) + )?; + + let router_fee_receiver_ta = self + .extract_account( + &bid_data.transaction, + &swap_instruction, + self.config + .chain_config + .express_relay + .router_account_position_swap, + ) + .await?; + + //TODO* : do not hardcode the token program and refactor into separate function + let fee_token_program = Pubkey::from_str( + "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + ) + .map_err(|e| { + RestError::BadParameters(format!("Invalid token program address: {:?}", e)) + })?; + let associated_token_program = Pubkey::from_str( + "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL", + ) + .map_err(|e| { + RestError::BadParameters(format!( + "Invalid associated token program address: {:?}", + e + )) + })?; + + let expected_router_fee_receiver_ta = Pubkey::find_program_address( + &[ + &opp.router.to_bytes(), + &fee_token_program.to_bytes(), + &mint_fee.to_bytes(), + ], + &associated_token_program, + ) + .0; + + if router_fee_receiver_ta != expected_router_fee_receiver_ta { + return Err(RestError::BadParameters( + "Must use approved router token account for swap instruction".to_string(), + )); + } let permission_account = get_quote_permission_key(&tokens, &user_wallet, swap_data.referral_fee_bps); @@ -609,7 +636,7 @@ impl Service { Ok(BidDataSvm { amount: bid_amount, permission_account, - router, + router: opp.router, deadline: OffsetDateTime::from_unix_timestamp(swap_data.deadline).map_err( |e| { RestError::BadParameters(format!( @@ -620,10 +647,8 @@ impl Service { )?, submit_type: SubmitType::ByOther, }) + } - _ => Err(RestError::BadParameters( - "Either submit_bid or swap must be present, but not both".to_string(), - )), } } @@ -695,14 +720,6 @@ impl Service { }) .await; - - // if bid.chain_data.opportunity_id.is_some() { - // let opp = self.opportunity_service.get_opportunity_by_id(GetOpportunityByIdInput { opportunity_id: bid.chain_data.opportunity_id.unwrap() }).await; - // tracing::info!("opp {:?}", opp); - // tracing::info!("chain_data {:?}", chain_data); - // } - - let opportunity = opportunities .first() .ok_or_else(|| RestError::BadParameters("Opportunity not found".to_string()))?; @@ -846,11 +863,11 @@ impl Verification for Service { input: VerifyBidInput, ) -> Result, RestError> { let bid = input.bid_create; - Svm::check_tx_size(&bid.chain_data.get_transaction())?; - self.check_compute_budget(&bid.chain_data.get_transaction()) - .await?; + let transaction = bid.chain_data.get_transaction().clone(); + Svm::check_tx_size(&transaction)?; + self.check_compute_budget(&transaction).await?; let bid_data = self - .extract_bid_data(bid.chain_data.get_transaction().clone()) + .extract_bid_data(&bid.chain_data) .await?; let bid_payment_instruction_type = match bid_data.submit_type { SubmitType::ByServer => BidPaymentInstructionType::SubmitBid, @@ -866,7 +883,7 @@ impl Verification for Service { permission_account: bid_data.permission_account, router: bid_data.router, bid_payment_instruction_type, - transaction: bid.chain_data.get_transaction().clone(), + transaction: transaction.clone(), }; let permission_key = bid_chain_data.get_permission_key(); tracing::Span::current().record("permission_key", bid_data.permission_account.to_string()); diff --git a/auction-server/src/opportunity/entities/opportunity_svm.rs b/auction-server/src/opportunity/entities/opportunity_svm.rs index ea477bf4..ac85fbad 100644 --- a/auction-server/src/opportunity/entities/opportunity_svm.rs +++ b/auction-server/src/opportunity/entities/opportunity_svm.rs @@ -212,10 +212,16 @@ impl From for api::OpportunitySvm { output_token: sell_token.token, } }; + let fee_token = match program.fee_token { + FeeToken::InputToken => api::FeeToken::InputToken, + FeeToken::OutputToken => api::FeeToken::OutputToken, + }; api::OpportunityParamsV1ProgramSvm::Swap { user_wallet_address: program.user_wallet_address, permission_account: val.permission_account, router_account: val.router, + fee_token, + referral_fee_bps: program.referral_fee_bps, // TODO can we make it type safe? tokens, } diff --git a/sdk/js/src/const.ts b/sdk/js/src/const.ts index f398d588..4b5e34af 100644 --- a/sdk/js/src/const.ts +++ b/sdk/js/src/const.ts @@ -28,12 +28,10 @@ export const SVM_CONSTANTS: Record = { expressRelayProgram: new PublicKey( "PytERJFhAKuNNuaiXkApLfWzwNwSNDACpigT3LwQfou" ), - walletRouter: new PublicKey("3hv8L8UeBbyM3M25dF3h2C5p8yA4FptD7FFZu4Z1jCMn"), }, solana: { expressRelayProgram: new PublicKey( "PytERJFhAKuNNuaiXkApLfWzwNwSNDACpigT3LwQfou" ), - walletRouter: new PublicKey("3hv8L8UeBbyM3M25dF3h2C5p8yA4FptD7FFZu4Z1jCMn"), }, }; diff --git a/sdk/js/src/examples/simpleSearcherLimo.ts b/sdk/js/src/examples/simpleSearcherLimo.ts index b77109cb..73179bea 100644 --- a/sdk/js/src/examples/simpleSearcherLimo.ts +++ b/sdk/js/src/examples/simpleSearcherLimo.ts @@ -137,8 +137,8 @@ export class SimpleSearcherLimo { } /** - * Generates a bid for a given limo opportunity. - * The transaction in this bid transfers assets from the searcher's wallet to fulfill the limit order. + * Generates a bid for a given swap opportunity. + * The transaction in this bid transfers assets from the searcher's wallet to the specified wallets to fulfill the opportunity. * @param opportunity The SVM opportunity to bid on. * @returns The generated bid object. */ @@ -159,7 +159,6 @@ export class SimpleSearcherLimo { this.chainId, config.relayerSigner ); - bid.slot = opportunity.slot; bid.transaction.recentBlockhash = this.latestChainUpdate[this.chainId].blockhash; diff --git a/sdk/js/src/expressRelayTypes.d.ts b/sdk/js/src/expressRelayTypes.d.ts index d2849b4f..e3432bc8 100644 --- a/sdk/js/src/expressRelayTypes.d.ts +++ b/sdk/js/src/expressRelayTypes.d.ts @@ -5,41 +5,50 @@ * IDL can be found at `target/idl/express_relay.json`. */ export type ExpressRelay = { - address: "PytERJFhAKuNNuaiXkApLfWzwNwSNDACpigT3LwQfou"; - metadata: { - name: "expressRelay"; - version: "0.3.1"; - spec: "0.1.0"; - description: "Pyth Express Relay program for handling permissioning and bid distribution"; - repository: "https://github.com/pyth-network/per"; - }; - instructions: [ - { - name: "checkPermission"; - docs: [ + "address": "PytERJFhAKuNNuaiXkApLfWzwNwSNDACpigT3LwQfou", + "metadata": { + "name": "expressRelay", + "version": "0.3.1", + "spec": "0.1.0", + "description": "Pyth Express Relay program for handling permissioning and bid distribution", + "repository": "https://github.com/pyth-network/per" + }, + "instructions": [ + { + "name": "checkPermission", + "docs": [ "Checks if permissioning exists for a particular (permission, router) pair within the same transaction.", "Permissioning takes the form of a SubmitBid instruction with matching permission and router accounts.", "Returns the fees paid to the router in the matching instructions." - ]; - discriminator: [154, 199, 232, 242, 96, 72, 197, 236]; - accounts: [ + ], + "discriminator": [ + 154, + 199, + 232, + 242, + 96, + 72, + 197, + 236 + ], + "accounts": [ { - name: "sysvarInstructions"; - address: "Sysvar1nstructions1111111111111111111111111"; + "name": "sysvarInstructions", + "address": "Sysvar1nstructions1111111111111111111111111" }, { - name: "permission"; + "name": "permission" }, { - name: "router"; + "name": "router" }, { - name: "configRouter"; - pda: { - seeds: [ + "name": "configRouter", + "pda": { + "seeds": [ { - kind: "const"; - value: [ + "kind": "const", + "value": [ 99, 111, 110, @@ -53,151 +62,229 @@ export type ExpressRelay = { 116, 101, 114 - ]; + ] }, { - kind: "account"; - path: "router"; + "kind": "account", + "path": "router" } - ]; - }; + ] + } }, { - name: "expressRelayMetadata"; - pda: { - seeds: [ + "name": "expressRelayMetadata", + "pda": { + "seeds": [ { - kind: "const"; - value: [109, 101, 116, 97, 100, 97, 116, 97]; + "kind": "const", + "value": [ + 109, + 101, + 116, + 97, + 100, + 97, + 116, + 97 + ] } - ]; - }; + ] + } } - ]; - args: []; - returns: "u64"; + ], + "args": [], + "returns": "u64" }, { - name: "initialize"; - discriminator: [175, 175, 109, 31, 13, 152, 155, 237]; - accounts: [ + "name": "initialize", + "discriminator": [ + 175, + 175, + 109, + 31, + 13, + 152, + 155, + 237 + ], + "accounts": [ { - name: "payer"; - writable: true; - signer: true; + "name": "payer", + "writable": true, + "signer": true }, { - name: "expressRelayMetadata"; - writable: true; - pda: { - seeds: [ + "name": "expressRelayMetadata", + "writable": true, + "pda": { + "seeds": [ { - kind: "const"; - value: [109, 101, 116, 97, 100, 97, 116, 97]; + "kind": "const", + "value": [ + 109, + 101, + 116, + 97, + 100, + 97, + 116, + 97 + ] } - ]; - }; + ] + } }, { - name: "admin"; + "name": "admin" }, { - name: "relayerSigner"; + "name": "relayerSigner" }, { - name: "feeReceiverRelayer"; + "name": "feeReceiverRelayer" }, { - name: "systemProgram"; - address: "11111111111111111111111111111111"; + "name": "systemProgram", + "address": "11111111111111111111111111111111" } - ]; - args: [ - { - name: "data"; - type: { - defined: { - name: "initializeArgs"; - }; - }; + ], + "args": [ + { + "name": "data", + "type": { + "defined": { + "name": "initializeArgs" + } + } } - ]; + ] }, { - name: "setAdmin"; - discriminator: [251, 163, 0, 52, 91, 194, 187, 92]; - accounts: [ + "name": "setAdmin", + "discriminator": [ + 251, + 163, + 0, + 52, + 91, + 194, + 187, + 92 + ], + "accounts": [ { - name: "admin"; - signer: true; - relations: ["expressRelayMetadata"]; + "name": "admin", + "signer": true, + "relations": [ + "expressRelayMetadata" + ] }, { - name: "expressRelayMetadata"; - writable: true; - pda: { - seeds: [ + "name": "expressRelayMetadata", + "writable": true, + "pda": { + "seeds": [ { - kind: "const"; - value: [109, 101, 116, 97, 100, 97, 116, 97]; + "kind": "const", + "value": [ + 109, + 101, + 116, + 97, + 100, + 97, + 116, + 97 + ] } - ]; - }; + ] + } }, { - name: "adminNew"; + "name": "adminNew" } - ]; - args: []; + ], + "args": [] }, { - name: "setRelayer"; - discriminator: [23, 243, 33, 88, 110, 84, 196, 37]; - accounts: [ + "name": "setRelayer", + "discriminator": [ + 23, + 243, + 33, + 88, + 110, + 84, + 196, + 37 + ], + "accounts": [ { - name: "admin"; - signer: true; - relations: ["expressRelayMetadata"]; + "name": "admin", + "signer": true, + "relations": [ + "expressRelayMetadata" + ] }, { - name: "expressRelayMetadata"; - writable: true; - pda: { - seeds: [ + "name": "expressRelayMetadata", + "writable": true, + "pda": { + "seeds": [ { - kind: "const"; - value: [109, 101, 116, 97, 100, 97, 116, 97]; + "kind": "const", + "value": [ + 109, + 101, + 116, + 97, + 100, + 97, + 116, + 97 + ] } - ]; - }; + ] + } }, { - name: "relayerSigner"; + "name": "relayerSigner" }, { - name: "feeReceiverRelayer"; + "name": "feeReceiverRelayer" } - ]; - args: []; + ], + "args": [] }, { - name: "setRouterSplit"; - discriminator: [16, 150, 106, 13, 27, 191, 104, 8]; - accounts: [ + "name": "setRouterSplit", + "discriminator": [ + 16, + 150, + 106, + 13, + 27, + 191, + 104, + 8 + ], + "accounts": [ { - name: "admin"; - writable: true; - signer: true; - relations: ["expressRelayMetadata"]; + "name": "admin", + "writable": true, + "signer": true, + "relations": [ + "expressRelayMetadata" + ] }, { - name: "configRouter"; - writable: true; - pda: { - seeds: [ + "name": "configRouter", + "writable": true, + "pda": { + "seeds": [ { - kind: "const"; - value: [ + "kind": "const", + "value": [ 99, 111, 110, @@ -211,142 +298,202 @@ export type ExpressRelay = { 116, 101, 114 - ]; + ] }, { - kind: "account"; - path: "router"; + "kind": "account", + "path": "router" } - ]; - }; + ] + } }, { - name: "expressRelayMetadata"; - pda: { - seeds: [ + "name": "expressRelayMetadata", + "pda": { + "seeds": [ { - kind: "const"; - value: [109, 101, 116, 97, 100, 97, 116, 97]; + "kind": "const", + "value": [ + 109, + 101, + 116, + 97, + 100, + 97, + 116, + 97 + ] } - ]; - }; + ] + } }, { - name: "router"; + "name": "router" }, { - name: "systemProgram"; - address: "11111111111111111111111111111111"; + "name": "systemProgram", + "address": "11111111111111111111111111111111" } - ]; - args: [ - { - name: "data"; - type: { - defined: { - name: "setRouterSplitArgs"; - }; - }; + ], + "args": [ + { + "name": "data", + "type": { + "defined": { + "name": "setRouterSplitArgs" + } + } } - ]; + ] }, { - name: "setSplits"; - discriminator: [175, 2, 86, 49, 225, 202, 232, 189]; - accounts: [ + "name": "setSplits", + "discriminator": [ + 175, + 2, + 86, + 49, + 225, + 202, + 232, + 189 + ], + "accounts": [ { - name: "admin"; - signer: true; - relations: ["expressRelayMetadata"]; + "name": "admin", + "signer": true, + "relations": [ + "expressRelayMetadata" + ] }, { - name: "expressRelayMetadata"; - writable: true; - pda: { - seeds: [ + "name": "expressRelayMetadata", + "writable": true, + "pda": { + "seeds": [ { - kind: "const"; - value: [109, 101, 116, 97, 100, 97, 116, 97]; + "kind": "const", + "value": [ + 109, + 101, + 116, + 97, + 100, + 97, + 116, + 97 + ] } - ]; - }; + ] + } } - ]; - args: [ - { - name: "data"; - type: { - defined: { - name: "setSplitsArgs"; - }; - }; + ], + "args": [ + { + "name": "data", + "type": { + "defined": { + "name": "setSplitsArgs" + } + } } - ]; + ] }, { - name: "setSwapPlatformFee"; - discriminator: [2, 135, 75, 15, 8, 105, 142, 47]; - accounts: [ + "name": "setSwapPlatformFee", + "discriminator": [ + 2, + 135, + 75, + 15, + 8, + 105, + 142, + 47 + ], + "accounts": [ { - name: "admin"; - signer: true; - relations: ["expressRelayMetadata"]; + "name": "admin", + "signer": true, + "relations": [ + "expressRelayMetadata" + ] }, { - name: "expressRelayMetadata"; - writable: true; - pda: { - seeds: [ + "name": "expressRelayMetadata", + "writable": true, + "pda": { + "seeds": [ { - kind: "const"; - value: [109, 101, 116, 97, 100, 97, 116, 97]; + "kind": "const", + "value": [ + 109, + 101, + 116, + 97, + 100, + 97, + 116, + 97 + ] } - ]; - }; + ] + } } - ]; - args: [ - { - name: "data"; - type: { - defined: { - name: "setSwapPlatformFeeArgs"; - }; - }; + ], + "args": [ + { + "name": "data", + "type": { + "defined": { + "name": "setSwapPlatformFeeArgs" + } + } } - ]; + ] }, { - name: "submitBid"; - docs: [ + "name": "submitBid", + "docs": [ "Submits a bid for a particular (permission, router) pair and distributes bids according to splits." - ]; - discriminator: [19, 164, 237, 254, 64, 139, 237, 93]; - accounts: [ + ], + "discriminator": [ + 19, + 164, + 237, + 254, + 64, + 139, + 237, + 93 + ], + "accounts": [ { - name: "searcher"; - writable: true; - signer: true; + "name": "searcher", + "writable": true, + "signer": true }, { - name: "relayerSigner"; - signer: true; - relations: ["expressRelayMetadata"]; + "name": "relayerSigner", + "signer": true, + "relations": [ + "expressRelayMetadata" + ] }, { - name: "permission"; + "name": "permission" }, { - name: "router"; - writable: true; + "name": "router", + "writable": true }, { - name: "configRouter"; - pda: { - seeds: [ + "name": "configRouter", + "pda": { + "seeds": [ { - kind: "const"; - value: [ + "kind": "const", + "value": [ 99, 111, 110, @@ -360,99 +507,119 @@ export type ExpressRelay = { 116, 101, 114 - ]; + ] }, { - kind: "account"; - path: "router"; + "kind": "account", + "path": "router" } - ]; - }; + ] + } }, { - name: "expressRelayMetadata"; - writable: true; - pda: { - seeds: [ + "name": "expressRelayMetadata", + "writable": true, + "pda": { + "seeds": [ { - kind: "const"; - value: [109, 101, 116, 97, 100, 97, 116, 97]; + "kind": "const", + "value": [ + 109, + 101, + 116, + 97, + 100, + 97, + 116, + 97 + ] } - ]; - }; + ] + } }, { - name: "feeReceiverRelayer"; - writable: true; - relations: ["expressRelayMetadata"]; + "name": "feeReceiverRelayer", + "writable": true, + "relations": [ + "expressRelayMetadata" + ] }, { - name: "systemProgram"; - address: "11111111111111111111111111111111"; + "name": "systemProgram", + "address": "11111111111111111111111111111111" }, { - name: "sysvarInstructions"; - address: "Sysvar1nstructions1111111111111111111111111"; + "name": "sysvarInstructions", + "address": "Sysvar1nstructions1111111111111111111111111" } - ]; - args: [ - { - name: "data"; - type: { - defined: { - name: "submitBidArgs"; - }; - }; + ], + "args": [ + { + "name": "data", + "type": { + "defined": { + "name": "submitBidArgs" + } + } } - ]; + ] }, { - name: "swap"; - discriminator: [248, 198, 158, 145, 225, 117, 135, 200]; - accounts: [ + "name": "swap", + "discriminator": [ + 248, + 198, + 158, + 145, + 225, + 117, + 135, + 200 + ], + "accounts": [ { - name: "searcher"; - docs: [ + "name": "searcher", + "docs": [ "Searcher is the party that sends the input token and receives the output token" - ]; - signer: true; + ], + "signer": true }, { - name: "trader"; - docs: [ + "name": "trader", + "docs": [ "Trader is the party that sends the output token and receives the input token" - ]; - signer: true; + ], + "signer": true }, { - name: "searcherInputTa"; - writable: true; + "name": "searcherInputTa", + "writable": true }, { - name: "searcherOutputTa"; - writable: true; + "name": "searcherOutputTa", + "writable": true }, { - name: "traderInputAta"; - writable: true; - pda: { - seeds: [ + "name": "traderInputAta", + "writable": true, + "pda": { + "seeds": [ { - kind: "account"; - path: "trader"; + "kind": "account", + "path": "trader" }, { - kind: "account"; - path: "tokenProgramInput"; + "kind": "account", + "path": "tokenProgramInput" }, { - kind: "account"; - path: "mintInput"; + "kind": "account", + "path": "mintInput" } - ]; - program: { - kind: "const"; - value: [ + ], + "program": { + "kind": "const", + "value": [ 140, 151, 37, @@ -485,31 +652,31 @@ export type ExpressRelay = { 233, 248, 89 - ]; - }; - }; + ] + } + } }, { - name: "traderOutputAta"; - writable: true; - pda: { - seeds: [ + "name": "traderOutputAta", + "writable": true, + "pda": { + "seeds": [ { - kind: "account"; - path: "trader"; + "kind": "account", + "path": "trader" }, { - kind: "account"; - path: "tokenProgramOutput"; + "kind": "account", + "path": "tokenProgramOutput" }, { - kind: "account"; - path: "mintOutput"; + "kind": "account", + "path": "mintOutput" } - ]; - program: { - kind: "const"; - value: [ + ], + "program": { + "kind": "const", + "value": [ 140, 151, 37, @@ -542,39 +709,39 @@ export type ExpressRelay = { 233, 248, 89 - ]; - }; - }; + ] + } + } }, { - name: "routerFeeReceiverTa"; - docs: [ + "name": "routerFeeReceiverTa", + "docs": [ "Router fee receiver token account: the referrer can provide an arbitrary receiver for the router fee" - ]; - writable: true; + ], + "writable": true }, { - name: "relayerFeeReceiverAta"; - writable: true; - pda: { - seeds: [ + "name": "relayerFeeReceiverAta", + "writable": true, + "pda": { + "seeds": [ { - kind: "account"; - path: "express_relay_metadata.fee_receiver_relayer"; - account: "expressRelayMetadata"; + "kind": "account", + "path": "express_relay_metadata.fee_receiver_relayer", + "account": "expressRelayMetadata" }, { - kind: "account"; - path: "tokenProgramFee"; + "kind": "account", + "path": "tokenProgramFee" }, { - kind: "account"; - path: "mintFee"; + "kind": "account", + "path": "mintFee" } - ]; - program: { - kind: "const"; - value: [ + ], + "program": { + "kind": "const", + "value": [ 140, 151, 37, @@ -607,31 +774,31 @@ export type ExpressRelay = { 233, 248, 89 - ]; - }; - }; + ] + } + } }, { - name: "expressRelayFeeReceiverAta"; - writable: true; - pda: { - seeds: [ + "name": "expressRelayFeeReceiverAta", + "writable": true, + "pda": { + "seeds": [ { - kind: "account"; - path: "expressRelayMetadata"; + "kind": "account", + "path": "expressRelayMetadata" }, { - kind: "account"; - path: "tokenProgramFee"; + "kind": "account", + "path": "tokenProgramFee" }, { - kind: "account"; - path: "mintFee"; + "kind": "account", + "path": "mintFee" } - ]; - program: { - kind: "const"; - value: [ + ], + "program": { + "kind": "const", + "value": [ 140, 151, 37, @@ -664,320 +831,374 @@ export type ExpressRelay = { 233, 248, 89 - ]; - }; - }; + ] + } + } }, { - name: "mintInput"; + "name": "mintInput" }, { - name: "mintOutput"; + "name": "mintOutput" }, { - name: "mintFee"; + "name": "mintFee" }, { - name: "tokenProgramInput"; + "name": "tokenProgramInput" }, { - name: "tokenProgramOutput"; + "name": "tokenProgramOutput" }, { - name: "tokenProgramFee"; + "name": "tokenProgramFee" }, { - name: "expressRelayMetadata"; - docs: ["Express relay configuration"]; - pda: { - seeds: [ + "name": "expressRelayMetadata", + "docs": [ + "Express relay configuration" + ], + "pda": { + "seeds": [ { - kind: "const"; - value: [109, 101, 116, 97, 100, 97, 116, 97]; + "kind": "const", + "value": [ + 109, + 101, + 116, + 97, + 100, + 97, + 116, + 97 + ] } - ]; - }; + ] + } } - ]; - args: [ - { - name: "data"; - type: { - defined: { - name: "swapArgs"; - }; - }; + ], + "args": [ + { + "name": "data", + "type": { + "defined": { + "name": "swapArgs" + } + } } - ]; + ] }, { - name: "withdrawFees"; - discriminator: [198, 212, 171, 109, 144, 215, 174, 89]; - accounts: [ + "name": "withdrawFees", + "discriminator": [ + 198, + 212, + 171, + 109, + 144, + 215, + 174, + 89 + ], + "accounts": [ { - name: "admin"; - signer: true; - relations: ["expressRelayMetadata"]; + "name": "admin", + "signer": true, + "relations": [ + "expressRelayMetadata" + ] }, { - name: "feeReceiverAdmin"; - writable: true; + "name": "feeReceiverAdmin", + "writable": true }, { - name: "expressRelayMetadata"; - writable: true; - pda: { - seeds: [ + "name": "expressRelayMetadata", + "writable": true, + "pda": { + "seeds": [ { - kind: "const"; - value: [109, 101, 116, 97, 100, 97, 116, 97]; + "kind": "const", + "value": [ + 109, + 101, + 116, + 97, + 100, + 97, + 116, + 97 + ] } - ]; - }; + ] + } } - ]; - args: []; + ], + "args": [] } - ]; - accounts: [ + ], + "accounts": [ { - name: "configRouter"; - discriminator: [135, 66, 240, 166, 94, 198, 187, 36]; + "name": "configRouter", + "discriminator": [ + 135, + 66, + 240, + 166, + 94, + 198, + 187, + 36 + ] }, { - name: "expressRelayMetadata"; - discriminator: [204, 75, 133, 7, 175, 241, 130, 11]; + "name": "expressRelayMetadata", + "discriminator": [ + 204, + 75, + 133, + 7, + 175, + 241, + 130, + 11 + ] } - ]; - errors: [ + ], + "errors": [ { - code: 6000; - name: "feeSplitLargerThanPrecision"; - msg: "Fee split(s) larger than fee precision"; + "code": 6000, + "name": "feeSplitLargerThanPrecision", + "msg": "Fee split(s) larger than fee precision" }, { - code: 6001; - name: "feesHigherThanBid"; - msg: "Fees higher than bid"; + "code": 6001, + "name": "feesHigherThanBid", + "msg": "Fees higher than bid" }, { - code: 6002; - name: "deadlinePassed"; - msg: "Deadline passed"; + "code": 6002, + "name": "deadlinePassed", + "msg": "Deadline passed" }, { - code: 6003; - name: "invalidCpiSubmitBid"; - msg: "Invalid CPI into submit bid instruction"; + "code": 6003, + "name": "invalidCpiSubmitBid", + "msg": "Invalid CPI into submit bid instruction" }, { - code: 6004; - name: "missingPermission"; - msg: "Missing permission"; + "code": 6004, + "name": "missingPermission", + "msg": "Missing permission" }, { - code: 6005; - name: "multiplePermissions"; - msg: "Multiple permissions"; + "code": 6005, + "name": "multiplePermissions", + "msg": "Multiple permissions" }, { - code: 6006; - name: "insufficientSearcherFunds"; - msg: "Insufficient searcher funds"; + "code": 6006, + "name": "insufficientSearcherFunds", + "msg": "Insufficient searcher funds" }, { - code: 6007; - name: "insufficientRent"; - msg: "Insufficient funds for rent"; + "code": 6007, + "name": "insufficientRent", + "msg": "Insufficient funds for rent" }, { - code: 6008; - name: "invalidAta"; - msg: "Invalid ATA provided"; + "code": 6008, + "name": "invalidAta", + "msg": "Invalid ATA provided" }, { - code: 6009; - name: "invalidMint"; - msg: "A token account has the wrong mint"; + "code": 6009, + "name": "invalidMint", + "msg": "A token account has the wrong mint" }, { - code: 6010; - name: "invalidTokenProgram"; - msg: "A token account belongs to the wrong token program"; + "code": 6010, + "name": "invalidTokenProgram", + "msg": "A token account belongs to the wrong token program" + }, + { + "code": 6011, + "name": "invalidReferralFee", + "msg": "Invalid referral fee" } - ]; - types: [ + ], + "types": [ { - name: "configRouter"; - type: { - kind: "struct"; - fields: [ + "name": "configRouter", + "type": { + "kind": "struct", + "fields": [ { - name: "router"; - type: "pubkey"; + "name": "router", + "type": "pubkey" }, { - name: "split"; - type: "u64"; + "name": "split", + "type": "u64" } - ]; - }; + ] + } }, { - name: "expressRelayMetadata"; - type: { - kind: "struct"; - fields: [ + "name": "expressRelayMetadata", + "type": { + "kind": "struct", + "fields": [ { - name: "admin"; - type: "pubkey"; + "name": "admin", + "type": "pubkey" }, { - name: "relayerSigner"; - type: "pubkey"; + "name": "relayerSigner", + "type": "pubkey" }, { - name: "feeReceiverRelayer"; - type: "pubkey"; + "name": "feeReceiverRelayer", + "type": "pubkey" }, { - name: "splitRouterDefault"; - type: "u64"; + "name": "splitRouterDefault", + "type": "u64" }, { - name: "splitRelayer"; - type: "u64"; + "name": "splitRelayer", + "type": "u64" }, { - name: "swapPlatformFeeBps"; - type: "u64"; + "name": "swapPlatformFeeBps", + "type": "u64" } - ]; - }; + ] + } }, { - name: "feeToken"; - type: { - kind: "enum"; - variants: [ + "name": "feeToken", + "type": { + "kind": "enum", + "variants": [ { - name: "input"; + "name": "input" }, { - name: "output"; + "name": "output" } - ]; - }; + ] + } }, { - name: "initializeArgs"; - type: { - kind: "struct"; - fields: [ + "name": "initializeArgs", + "type": { + "kind": "struct", + "fields": [ { - name: "splitRouterDefault"; - type: "u64"; + "name": "splitRouterDefault", + "type": "u64" }, { - name: "splitRelayer"; - type: "u64"; + "name": "splitRelayer", + "type": "u64" } - ]; - }; + ] + } }, { - name: "setRouterSplitArgs"; - type: { - kind: "struct"; - fields: [ + "name": "setRouterSplitArgs", + "type": { + "kind": "struct", + "fields": [ { - name: "splitRouter"; - type: "u64"; + "name": "splitRouter", + "type": "u64" } - ]; - }; + ] + } }, { - name: "setSplitsArgs"; - type: { - kind: "struct"; - fields: [ + "name": "setSplitsArgs", + "type": { + "kind": "struct", + "fields": [ { - name: "splitRouterDefault"; - type: "u64"; + "name": "splitRouterDefault", + "type": "u64" }, { - name: "splitRelayer"; - type: "u64"; + "name": "splitRelayer", + "type": "u64" } - ]; - }; + ] + } }, { - name: "setSwapPlatformFeeArgs"; - type: { - kind: "struct"; - fields: [ + "name": "setSwapPlatformFeeArgs", + "type": { + "kind": "struct", + "fields": [ { - name: "swapPlatformFeeBps"; - type: "u64"; + "name": "swapPlatformFeeBps", + "type": "u64" } - ]; - }; + ] + } }, { - name: "submitBidArgs"; - type: { - kind: "struct"; - fields: [ + "name": "submitBidArgs", + "type": { + "kind": "struct", + "fields": [ { - name: "deadline"; - type: "i64"; + "name": "deadline", + "type": "i64" }, { - name: "bidAmount"; - type: "u64"; + "name": "bidAmount", + "type": "u64" } - ]; - }; + ] + } }, { - name: "swapArgs"; - docs: [ + "name": "swapArgs", + "docs": [ "For all swap instructions and contexts, input and output are defined with respect to the searcher", "So `mint_input` refers to the token that the searcher provides to the trader and", "`mint_output` refers to the token that the searcher receives from the trader", "This choice is made to minimize confusion for the searchers, who are more likely to parse the program" - ]; - type: { - kind: "struct"; - fields: [ + ], + "type": { + "kind": "struct", + "fields": [ { - name: "deadline"; - type: "i64"; + "name": "deadline", + "type": "i64" }, { - name: "amountInput"; - type: "u64"; + "name": "amountInput", + "type": "u64" }, { - name: "amountOutput"; - type: "u64"; + "name": "amountOutput", + "type": "u64" }, { - name: "referralFeeBps"; - type: "u64"; + "name": "referralFeeBps", + "type": "u16" }, { - name: "feeToken"; - type: { - defined: { - name: "feeToken"; - }; - }; - } - ]; - }; + "name": "feeToken", + "type": { + "defined": { + "name": "feeToken" + } + } + } + ] + } } - ]; + ] }; diff --git a/sdk/js/src/idl/idlExpressRelay.json b/sdk/js/src/idl/idlExpressRelay.json index 2b5b6a23..9d0e2ddd 100644 --- a/sdk/js/src/idl/idlExpressRelay.json +++ b/sdk/js/src/idl/idlExpressRelay.json @@ -15,7 +15,16 @@ "Permissioning takes the form of a SubmitBid instruction with matching permission and router accounts.", "Returns the fees paid to the router in the matching instructions." ], - "discriminator": [154, 199, 232, 242, 96, 72, 197, 236], + "discriminator": [ + 154, + 199, + 232, + 242, + 96, + 72, + 197, + 236 + ], "accounts": [ { "name": "sysvar_instructions", @@ -34,7 +43,19 @@ { "kind": "const", "value": [ - 99, 111, 110, 102, 105, 103, 95, 114, 111, 117, 116, 101, 114 + 99, + 111, + 110, + 102, + 105, + 103, + 95, + 114, + 111, + 117, + 116, + 101, + 114 ] }, { @@ -50,7 +71,16 @@ "seeds": [ { "kind": "const", - "value": [109, 101, 116, 97, 100, 97, 116, 97] + "value": [ + 109, + 101, + 116, + 97, + 100, + 97, + 116, + 97 + ] } ] } @@ -61,7 +91,16 @@ }, { "name": "initialize", - "discriminator": [175, 175, 109, 31, 13, 152, 155, 237], + "discriminator": [ + 175, + 175, + 109, + 31, + 13, + 152, + 155, + 237 + ], "accounts": [ { "name": "payer", @@ -75,7 +114,16 @@ "seeds": [ { "kind": "const", - "value": [109, 101, 116, 97, 100, 97, 116, 97] + "value": [ + 109, + 101, + 116, + 97, + 100, + 97, + 116, + 97 + ] } ] } @@ -107,12 +155,23 @@ }, { "name": "set_admin", - "discriminator": [251, 163, 0, 52, 91, 194, 187, 92], + "discriminator": [ + 251, + 163, + 0, + 52, + 91, + 194, + 187, + 92 + ], "accounts": [ { "name": "admin", "signer": true, - "relations": ["express_relay_metadata"] + "relations": [ + "express_relay_metadata" + ] }, { "name": "express_relay_metadata", @@ -121,7 +180,16 @@ "seeds": [ { "kind": "const", - "value": [109, 101, 116, 97, 100, 97, 116, 97] + "value": [ + 109, + 101, + 116, + 97, + 100, + 97, + 116, + 97 + ] } ] } @@ -134,12 +202,23 @@ }, { "name": "set_relayer", - "discriminator": [23, 243, 33, 88, 110, 84, 196, 37], + "discriminator": [ + 23, + 243, + 33, + 88, + 110, + 84, + 196, + 37 + ], "accounts": [ { "name": "admin", "signer": true, - "relations": ["express_relay_metadata"] + "relations": [ + "express_relay_metadata" + ] }, { "name": "express_relay_metadata", @@ -148,7 +227,16 @@ "seeds": [ { "kind": "const", - "value": [109, 101, 116, 97, 100, 97, 116, 97] + "value": [ + 109, + 101, + 116, + 97, + 100, + 97, + 116, + 97 + ] } ] } @@ -164,13 +252,24 @@ }, { "name": "set_router_split", - "discriminator": [16, 150, 106, 13, 27, 191, 104, 8], + "discriminator": [ + 16, + 150, + 106, + 13, + 27, + 191, + 104, + 8 + ], "accounts": [ { "name": "admin", "writable": true, "signer": true, - "relations": ["express_relay_metadata"] + "relations": [ + "express_relay_metadata" + ] }, { "name": "config_router", @@ -180,7 +279,19 @@ { "kind": "const", "value": [ - 99, 111, 110, 102, 105, 103, 95, 114, 111, 117, 116, 101, 114 + 99, + 111, + 110, + 102, + 105, + 103, + 95, + 114, + 111, + 117, + 116, + 101, + 114 ] }, { @@ -196,7 +307,16 @@ "seeds": [ { "kind": "const", - "value": [109, 101, 116, 97, 100, 97, 116, 97] + "value": [ + 109, + 101, + 116, + 97, + 100, + 97, + 116, + 97 + ] } ] } @@ -222,12 +342,23 @@ }, { "name": "set_splits", - "discriminator": [175, 2, 86, 49, 225, 202, 232, 189], + "discriminator": [ + 175, + 2, + 86, + 49, + 225, + 202, + 232, + 189 + ], "accounts": [ { "name": "admin", "signer": true, - "relations": ["express_relay_metadata"] + "relations": [ + "express_relay_metadata" + ] }, { "name": "express_relay_metadata", @@ -236,7 +367,16 @@ "seeds": [ { "kind": "const", - "value": [109, 101, 116, 97, 100, 97, 116, 97] + "value": [ + 109, + 101, + 116, + 97, + 100, + 97, + 116, + 97 + ] } ] } @@ -255,12 +395,23 @@ }, { "name": "set_swap_platform_fee", - "discriminator": [2, 135, 75, 15, 8, 105, 142, 47], + "discriminator": [ + 2, + 135, + 75, + 15, + 8, + 105, + 142, + 47 + ], "accounts": [ { "name": "admin", "signer": true, - "relations": ["express_relay_metadata"] + "relations": [ + "express_relay_metadata" + ] }, { "name": "express_relay_metadata", @@ -269,7 +420,16 @@ "seeds": [ { "kind": "const", - "value": [109, 101, 116, 97, 100, 97, 116, 97] + "value": [ + 109, + 101, + 116, + 97, + 100, + 97, + 116, + 97 + ] } ] } @@ -291,7 +451,16 @@ "docs": [ "Submits a bid for a particular (permission, router) pair and distributes bids according to splits." ], - "discriminator": [19, 164, 237, 254, 64, 139, 237, 93], + "discriminator": [ + 19, + 164, + 237, + 254, + 64, + 139, + 237, + 93 + ], "accounts": [ { "name": "searcher", @@ -301,7 +470,9 @@ { "name": "relayer_signer", "signer": true, - "relations": ["express_relay_metadata"] + "relations": [ + "express_relay_metadata" + ] }, { "name": "permission" @@ -317,7 +488,19 @@ { "kind": "const", "value": [ - 99, 111, 110, 102, 105, 103, 95, 114, 111, 117, 116, 101, 114 + 99, + 111, + 110, + 102, + 105, + 103, + 95, + 114, + 111, + 117, + 116, + 101, + 114 ] }, { @@ -334,7 +517,16 @@ "seeds": [ { "kind": "const", - "value": [109, 101, 116, 97, 100, 97, 116, 97] + "value": [ + 109, + 101, + 116, + 97, + 100, + 97, + 116, + 97 + ] } ] } @@ -342,7 +534,9 @@ { "name": "fee_receiver_relayer", "writable": true, - "relations": ["express_relay_metadata"] + "relations": [ + "express_relay_metadata" + ] }, { "name": "system_program", @@ -366,7 +560,16 @@ }, { "name": "swap", - "discriminator": [248, 198, 158, 145, 225, 117, 135, 200], + "discriminator": [ + 248, + 198, + 158, + 145, + 225, + 117, + 135, + 200 + ], "accounts": [ { "name": "searcher", @@ -411,9 +614,38 @@ "program": { "kind": "const", "value": [ - 140, 151, 37, 143, 78, 36, 137, 241, 187, 61, 16, 41, 20, 142, - 13, 131, 11, 90, 19, 153, 218, 255, 16, 132, 4, 142, 123, 216, - 219, 233, 248, 89 + 140, + 151, + 37, + 143, + 78, + 36, + 137, + 241, + 187, + 61, + 16, + 41, + 20, + 142, + 13, + 131, + 11, + 90, + 19, + 153, + 218, + 255, + 16, + 132, + 4, + 142, + 123, + 216, + 219, + 233, + 248, + 89 ] } } @@ -439,9 +671,38 @@ "program": { "kind": "const", "value": [ - 140, 151, 37, 143, 78, 36, 137, 241, 187, 61, 16, 41, 20, 142, - 13, 131, 11, 90, 19, 153, 218, 255, 16, 132, 4, 142, 123, 216, - 219, 233, 248, 89 + 140, + 151, + 37, + 143, + 78, + 36, + 137, + 241, + 187, + 61, + 16, + 41, + 20, + 142, + 13, + 131, + 11, + 90, + 19, + 153, + 218, + 255, + 16, + 132, + 4, + 142, + 123, + 216, + 219, + 233, + 248, + 89 ] } } @@ -475,9 +736,38 @@ "program": { "kind": "const", "value": [ - 140, 151, 37, 143, 78, 36, 137, 241, 187, 61, 16, 41, 20, 142, - 13, 131, 11, 90, 19, 153, 218, 255, 16, 132, 4, 142, 123, 216, - 219, 233, 248, 89 + 140, + 151, + 37, + 143, + 78, + 36, + 137, + 241, + 187, + 61, + 16, + 41, + 20, + 142, + 13, + 131, + 11, + 90, + 19, + 153, + 218, + 255, + 16, + 132, + 4, + 142, + 123, + 216, + 219, + 233, + 248, + 89 ] } } @@ -503,9 +793,38 @@ "program": { "kind": "const", "value": [ - 140, 151, 37, 143, 78, 36, 137, 241, 187, 61, 16, 41, 20, 142, - 13, 131, 11, 90, 19, 153, 218, 255, 16, 132, 4, 142, 123, 216, - 219, 233, 248, 89 + 140, + 151, + 37, + 143, + 78, + 36, + 137, + 241, + 187, + 61, + 16, + 41, + 20, + 142, + 13, + 131, + 11, + 90, + 19, + 153, + 218, + 255, + 16, + 132, + 4, + 142, + 123, + 216, + 219, + 233, + 248, + 89 ] } } @@ -530,12 +849,23 @@ }, { "name": "express_relay_metadata", - "docs": ["Express relay configuration"], + "docs": [ + "Express relay configuration" + ], "pda": { "seeds": [ { "kind": "const", - "value": [109, 101, 116, 97, 100, 97, 116, 97] + "value": [ + 109, + 101, + 116, + 97, + 100, + 97, + 116, + 97 + ] } ] } @@ -554,12 +884,23 @@ }, { "name": "withdraw_fees", - "discriminator": [198, 212, 171, 109, 144, 215, 174, 89], + "discriminator": [ + 198, + 212, + 171, + 109, + 144, + 215, + 174, + 89 + ], "accounts": [ { "name": "admin", "signer": true, - "relations": ["express_relay_metadata"] + "relations": [ + "express_relay_metadata" + ] }, { "name": "fee_receiver_admin", @@ -572,7 +913,16 @@ "seeds": [ { "kind": "const", - "value": [109, 101, 116, 97, 100, 97, 116, 97] + "value": [ + 109, + 101, + 116, + 97, + 100, + 97, + 116, + 97 + ] } ] } @@ -584,11 +934,29 @@ "accounts": [ { "name": "ConfigRouter", - "discriminator": [135, 66, 240, 166, 94, 198, 187, 36] + "discriminator": [ + 135, + 66, + 240, + 166, + 94, + 198, + 187, + 36 + ] }, { "name": "ExpressRelayMetadata", - "discriminator": [204, 75, 133, 7, 175, 241, 130, 11] + "discriminator": [ + 204, + 75, + 133, + 7, + 175, + 241, + 130, + 11 + ] } ], "errors": [ @@ -646,6 +1014,11 @@ "code": 6010, "name": "InvalidTokenProgram", "msg": "A token account belongs to the wrong token program" + }, + { + "code": 6011, + "name": "InvalidReferralFee", + "msg": "Invalid referral fee" } ], "types": [ @@ -808,7 +1181,7 @@ }, { "name": "referral_fee_bps", - "type": "u64" + "type": "u16" }, { "name": "fee_token", diff --git a/sdk/js/src/index.ts b/sdk/js/src/index.ts index 2b0c7546..52b2d536 100644 --- a/sdk/js/src/index.ts +++ b/sdk/js/src/index.ts @@ -22,7 +22,7 @@ import { OpportunityDelete, ChainType, QuoteRequest, - QuoteResponse, + QuoteResponse, BidSvmOnChain, } from "./types"; import { Connection, @@ -569,26 +569,6 @@ export class Client { } } - private toServerBid(bid: Bid): components["schemas"]["BidCreate"] { - if (bid.env === "evm") { - return { - amount: bid.amount.toString(), - target_calldata: bid.targetCalldata, - chain_id: bid.chainId, - target_contract: bid.targetContract, - permission_key: bid.permissionKey, - }; - } - - return { - chain_id: bid.chainId, - slot: bid.slot, - transaction: bid.transaction - .serialize({ requireAllSignatures: false }) - .toString("base64"), - }; - } - /** * Converts an opportunity from the server to the client format * Returns undefined if the opportunity version is not supported @@ -654,6 +634,8 @@ export class Client { slot: opportunity.slot, opportunityId: opportunity.opportunity_id, program: "swap", + referralFeeBps: opportunity.referral_fee_bps, + feeToken: opportunity.fee_token, permissionAccount: new PublicKey(opportunity.permission_account), routerAccount: new PublicKey(opportunity.router_account), userWalletAddress: new PublicKey(opportunity.user_wallet_address), @@ -664,6 +646,36 @@ export class Client { } } + private toServerBid(bid: Bid): components["schemas"]["BidCreate"] { + if (bid.env === "evm") { + return { + amount: bid.amount.toString(), + target_calldata: bid.targetCalldata, + chain_id: bid.chainId, + target_contract: bid.targetContract, + permission_key: bid.permissionKey, + }; + } + if (bid.type==='swap') { + return { + chain_id: bid.chainId, + opportunity_id: bid.opportunityId, + type:"swap", + transaction: bid.transaction + .serialize({requireAllSignatures: false}) + .toString("base64"), + }; + } else { + return { + chain_id: bid.chainId, + slot: bid.slot, + transaction: bid.transaction + .serialize({requireAllSignatures: false}) + .toString("base64"), + }; + } + } + /** * Converts a quote response from the server to the client format * @param quoteResponse @@ -805,7 +817,7 @@ export class Client { chainId: string, relayerSigner: PublicKey, feeReceiverRelayer: PublicKey - ): Promise { + ): Promise { return svm.constructSvmBid( tx, searcher, diff --git a/sdk/js/src/serverTypes.d.ts b/sdk/js/src/serverTypes.d.ts index 68837eb7..078a0884 100644 --- a/sdk/js/src/serverTypes.d.ts +++ b/sdk/js/src/serverTypes.d.ts @@ -3,6 +3,7 @@ * Do not make direct changes to the file. */ + export interface paths { "/v1/bids": { /** @@ -82,9 +83,7 @@ export interface components { schemas: { APIResponse: components["schemas"]["BidResult"]; Bid: components["schemas"]["BidEvm"] | components["schemas"]["BidSvm"]; - BidCreate: - | components["schemas"]["BidCreateEvm"] - | components["schemas"]["BidCreateSvm"]; + BidCreate: components["schemas"]["BidCreateEvm"] | components["schemas"]["BidCreateSvm"]; BidCreateEvm: { /** * @description Amount of bid in wei. @@ -112,7 +111,7 @@ export interface components { */ target_contract: string; }; - BidCreateSvm: { + BidCreateOnChainSvm: { /** * @description The chain id to bid on. * @example solana @@ -131,6 +130,27 @@ export interface components { */ transaction: string; }; + BidCreateSvm: components["schemas"]["BidCreateSwapSvm"] | components["schemas"]["BidCreateOnChainSvm"]; + BidCreateSwapSvm: { + /** + * @description The chain id to bid on. + * @example solana + */ + chain_id: string; + /** + * @description The id of the swap opportunity to bid on. + * @example obo3ee3e-58cc-4372-a567-0e02b2c3d479 + */ + opportunity_id: string; + /** + * @description The transaction for bid. + * @example SGVsbG8sIFdvcmxkIQ== + */ + transaction: string; + type: components["schemas"]["BidCreateSwapSvmTag"]; + }; + /** @enum {string} */ + BidCreateSwapSvmTag: "swap"; BidEvm: { /** * @description The chain id for bid. @@ -192,82 +212,70 @@ export interface components { */ status: string; }; - BidStatus: - | components["schemas"]["BidStatusEvm"] - | components["schemas"]["BidStatusSvm"]; - BidStatusEvm: - | { - /** @enum {string} */ - type: "pending"; - } - | { - /** - * Format: int32 - * @example 1 - */ - index: number; - /** @example 0x103d4fbd777a36311b5161f2062490f761f25b67406badb2bace62bb170aa4e3 */ - result: string; - /** @enum {string} */ - type: "submitted"; - } - | { - /** - * Format: int32 - * @example 1 - */ - index?: number | null; - /** @example 0x103d4fbd777a36311b5161f2062490f761f25b67406badb2bace62bb170aa4e3 */ - result?: string | null; - /** @enum {string} */ - type: "lost"; - } - | { - /** - * Format: int32 - * @example 1 - */ - index: number; - /** @example 0x103d4fbd777a36311b5161f2062490f761f25b67406badb2bace62bb170aa4e3 */ - result: string; - /** @enum {string} */ - type: "won"; - }; - BidStatusSvm: - | { - /** @enum {string} */ - type: "pending"; - } - | { - /** @example Jb2urXPyEh4xiBgzYvwEFe4q1iMxG1DNxWGGQg94AmKgqFTwLAiTiHrYiYxwHUB4DV8u5ahNEVtMMDm3sNSRdTg */ - result?: string | null; - /** @enum {string} */ - type: "lost"; - } - | { - /** @example Jb2urXPyEh4xiBgzYvwEFe4q1iMxG1DNxWGGQg94AmKgqFTwLAiTiHrYiYxwHUB4DV8u5ahNEVtMMDm3sNSRdTg */ - result: string; - /** @enum {string} */ - type: "submitted"; - } - | { - /** @example Jb2urXPyEh4xiBgzYvwEFe4q1iMxG1DNxWGGQg94AmKgqFTwLAiTiHrYiYxwHUB4DV8u5ahNEVtMMDm3sNSRdTg */ - result: string; - /** @enum {string} */ - type: "won"; - } - | { - /** @example Jb2urXPyEh4xiBgzYvwEFe4q1iMxG1DNxWGGQg94AmKgqFTwLAiTiHrYiYxwHUB4DV8u5ahNEVtMMDm3sNSRdTg */ - result: string; - /** @enum {string} */ - type: "failed"; - } - | { - /** @example Jb2urXPyEh4xiBgzYvwEFe4q1iMxG1DNxWGGQg94AmKgqFTwLAiTiHrYiYxwHUB4DV8u5ahNEVtMMDm3sNSRdTg */ - result: string; - /** @enum {string} */ - type: "expired"; - }; + BidStatus: components["schemas"]["BidStatusEvm"] | components["schemas"]["BidStatusSvm"]; + BidStatusEvm: { + /** @enum {string} */ + type: "pending"; + } | { + /** + * Format: int32 + * @example 1 + */ + index: number; + /** @example 0x103d4fbd777a36311b5161f2062490f761f25b67406badb2bace62bb170aa4e3 */ + result: string; + /** @enum {string} */ + type: "submitted"; + } | ({ + /** + * Format: int32 + * @example 1 + */ + index?: number | null; + /** @example 0x103d4fbd777a36311b5161f2062490f761f25b67406badb2bace62bb170aa4e3 */ + result?: string | null; + /** @enum {string} */ + type: "lost"; + }) | { + /** + * Format: int32 + * @example 1 + */ + index: number; + /** @example 0x103d4fbd777a36311b5161f2062490f761f25b67406badb2bace62bb170aa4e3 */ + result: string; + /** @enum {string} */ + type: "won"; + }; + BidStatusSvm: { + /** @enum {string} */ + type: "pending"; + } | ({ + /** @example Jb2urXPyEh4xiBgzYvwEFe4q1iMxG1DNxWGGQg94AmKgqFTwLAiTiHrYiYxwHUB4DV8u5ahNEVtMMDm3sNSRdTg */ + result?: string | null; + /** @enum {string} */ + type: "lost"; + }) | { + /** @example Jb2urXPyEh4xiBgzYvwEFe4q1iMxG1DNxWGGQg94AmKgqFTwLAiTiHrYiYxwHUB4DV8u5ahNEVtMMDm3sNSRdTg */ + result: string; + /** @enum {string} */ + type: "submitted"; + } | { + /** @example Jb2urXPyEh4xiBgzYvwEFe4q1iMxG1DNxWGGQg94AmKgqFTwLAiTiHrYiYxwHUB4DV8u5ahNEVtMMDm3sNSRdTg */ + result: string; + /** @enum {string} */ + type: "won"; + } | { + /** @example Jb2urXPyEh4xiBgzYvwEFe4q1iMxG1DNxWGGQg94AmKgqFTwLAiTiHrYiYxwHUB4DV8u5ahNEVtMMDm3sNSRdTg */ + result: string; + /** @enum {string} */ + type: "failed"; + } | { + /** @example Jb2urXPyEh4xiBgzYvwEFe4q1iMxG1DNxWGGQg94AmKgqFTwLAiTiHrYiYxwHUB4DV8u5ahNEVtMMDm3sNSRdTg */ + result: string; + /** @enum {string} */ + type: "expired"; + }; BidStatusWithId: { bid_status: components["schemas"]["BidStatus"]; id: string; @@ -316,45 +324,41 @@ export interface components { Bids: { items: components["schemas"]["Bid"][]; }; - ClientMessage: - | { - /** @enum {string} */ - method: "subscribe"; - params: { - chain_ids: string[]; - }; - } - | { - /** @enum {string} */ - method: "unsubscribe"; - params: { - chain_ids: string[]; - }; - } - | { - /** @enum {string} */ - method: "post_bid"; - params: { - bid: components["schemas"]["BidCreate"]; - }; - } - | { - /** @enum {string} */ - method: "post_opportunity_bid"; - params: { - opportunity_bid: components["schemas"]["OpportunityBidEvm"]; - opportunity_id: string; - }; - }; + ClientMessage: { + /** @enum {string} */ + method: "subscribe"; + params: { + chain_ids: string[]; + }; + } | { + /** @enum {string} */ + method: "unsubscribe"; + params: { + chain_ids: string[]; + }; + } | { + /** @enum {string} */ + method: "post_bid"; + params: { + bid: components["schemas"]["BidCreate"]; + }; + } | { + /** @enum {string} */ + method: "post_opportunity_bid"; + params: { + opportunity_bid: components["schemas"]["OpportunityBidEvm"]; + opportunity_id: string; + }; + }; ClientRequest: components["schemas"]["ClientMessage"] & { id: string; }; ErrorBodyResponse: { error: string; }; - Opportunity: - | components["schemas"]["OpportunityEvm"] - | components["schemas"]["OpportunitySvm"]; + /** @enum {string} */ + FeeToken: "input_token" | "output_token"; + Opportunity: components["schemas"]["OpportunityEvm"] | components["schemas"]["OpportunitySvm"]; OpportunityBidEvm: { /** * @description The bid amount in wei. @@ -394,44 +398,40 @@ export interface components { status: string; }; /** @description The input type for creating a new opportunity. */ - OpportunityCreate: - | components["schemas"]["OpportunityCreateEvm"] - | components["schemas"]["OpportunityCreateSvm"]; + OpportunityCreate: components["schemas"]["OpportunityCreateEvm"] | components["schemas"]["OpportunityCreateSvm"]; OpportunityCreateEvm: components["schemas"]["OpportunityCreateV1Evm"] & { /** @enum {string} */ version: "v1"; }; /** @description Program specific parameters for the opportunity. */ - OpportunityCreateProgramParamsV1Svm: - | { - /** - * @description The Limo order to be executed, encoded in base64. - * @example UxMUbQAsjrfQUp5stVwMJ6Mucq7VWTvt4ICe69BJ8lVXqwM+0sysV8OqZTdM0W4p... - */ - order: string; - /** - * @description Address of the order account. - * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 - */ - order_address: string; - /** @enum {string} */ - program: "limo"; - } - | { - /** @enum {string} */ - program: "swap"; - /** - * Format: int32 - * @description The referral fee in basis points. - * @example 10 - */ - referral_fee_bps: number; - /** - * @description The user wallet address which requested the quote from the wallet. - * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 - */ - user_wallet_address: string; - }; + OpportunityCreateProgramParamsV1Svm: { + /** + * @description The Limo order to be executed, encoded in base64. + * @example UxMUbQAsjrfQUp5stVwMJ6Mucq7VWTvt4ICe69BJ8lVXqwM+0sysV8OqZTdM0W4p... + */ + order: string; + /** + * @description Address of the order account. + * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 + */ + order_address: string; + /** @enum {string} */ + program: "limo"; + } | { + /** @enum {string} */ + program: "swap"; + /** + * Format: int32 + * @description The referral fee in basis points. + * @example 10 + */ + referral_fee_bps: number; + /** + * @description The user wallet address which requested the quote from the wallet. + * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 + */ + user_wallet_address: string; + }; OpportunityCreateSvm: components["schemas"]["OpportunityCreateV1Svm"] & { /** @enum {string} */ version: "v1"; @@ -475,37 +475,34 @@ export interface components { * @description Opportunity parameters needed for on-chain execution. * Parameters may differ for each program. */ - OpportunityCreateV1Svm: ( - | { - /** - * @description The Limo order to be executed, encoded in base64. - * @example UxMUbQAsjrfQUp5stVwMJ6Mucq7VWTvt4ICe69BJ8lVXqwM+0sysV8OqZTdM0W4p... - */ - order: string; - /** - * @description Address of the order account. - * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 - */ - order_address: string; - /** @enum {string} */ - program: "limo"; - } - | { - /** @enum {string} */ - program: "swap"; - /** - * Format: int32 - * @description The referral fee in basis points. - * @example 10 - */ - referral_fee_bps: number; - /** - * @description The user wallet address which requested the quote from the wallet. - * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 - */ - user_wallet_address: string; - } - ) & { + OpportunityCreateV1Svm: ({ + /** + * @description The Limo order to be executed, encoded in base64. + * @example UxMUbQAsjrfQUp5stVwMJ6Mucq7VWTvt4ICe69BJ8lVXqwM+0sysV8OqZTdM0W4p... + */ + order: string; + /** + * @description Address of the order account. + * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 + */ + order_address: string; + /** @enum {string} */ + program: "limo"; + } | { + /** @enum {string} */ + program: "swap"; + /** + * Format: int32 + * @description The referral fee in basis points. + * @example 10 + */ + referral_fee_bps: number; + /** + * @description The user wallet address which requested the quote from the wallet. + * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 + */ + user_wallet_address: string; + }) & { buy_tokens: components["schemas"]["TokenAmountSvm"][]; /** * @description The chain id where the opportunity will be executed. @@ -531,15 +528,13 @@ export interface components { slot: number; }; /** @description The input type for deleting opportunities. */ - OpportunityDelete: - | (components["schemas"]["OpportunityDeleteSvm"] & { - /** @enum {string} */ - chain_type: "svm"; - }) - | (components["schemas"]["OpportunityDeleteEvm"] & { - /** @enum {string} */ - chain_type: "evm"; - }); + 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"; @@ -610,54 +605,56 @@ export interface components { * @description Opportunity parameters needed for on-chain execution. * Parameters may differ for each program. */ - OpportunityParamsV1Svm: ( - | { - /** - * @description The Limo order to be executed, encoded in base64. - * @example UxMUbQAsjrfQUp5stVwMJ6Mucq7VWTvt4ICe69BJ8lVXqwM+0sysV8OqZTdM0W4p... - */ - order: string; - /** - * @description Address of the order account. - * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 - */ - order_address: string; - /** @enum {string} */ - program: "limo"; - } - | { - /** - * @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: "swap"; - /** - * @description The router account to be used for the opportunity execution of the protocol. - * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 - */ - router_account: string; - tokens: - | { - input_token: components["schemas"]["TokenAmountSvm"]; - output_token: components["schemas"]["Pubkey"]; - /** @enum {string} */ - type: "input_token_specified"; - } - | { - input_token: components["schemas"]["Pubkey"]; - output_token: components["schemas"]["TokenAmountSvm"]; - /** @enum {string} */ - type: "output_token_specified"; - }; - /** - * @description The user wallet address which requested the quote from the wallet. - * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 - */ - user_wallet_address: string; - } - ) & { + OpportunityParamsV1Svm: ({ + /** + * @description The Limo order to be executed, encoded in base64. + * @example UxMUbQAsjrfQUp5stVwMJ6Mucq7VWTvt4ICe69BJ8lVXqwM+0sysV8OqZTdM0W4p... + */ + order: string; + /** + * @description Address of the order account. + * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 + */ + order_address: string; + /** @enum {string} */ + program: "limo"; + } | ({ + fee_token: components["schemas"]["FeeToken"]; + /** + * @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: "swap"; + /** + * Format: int32 + * @description The referral fee in basis points. + * @example 10 + */ + referral_fee_bps: number; + /** + * @description The router account to be used for the opportunity execution of the protocol. + * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 + */ + router_account: string; + tokens: { + input_token: components["schemas"]["TokenAmountSvm"]; + output_token: components["schemas"]["Pubkey"]; + /** @enum {string} */ + type: "input_token_specified"; + } | { + input_token: components["schemas"]["Pubkey"]; + output_token: components["schemas"]["TokenAmountSvm"]; + /** @enum {string} */ + type: "output_token_specified"; + }; + /** + * @description The user wallet address which requested the quote from the wallet. + * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 + */ + user_wallet_address: string; + })) & { /** @example solana */ chain_id: string; }; @@ -721,25 +718,23 @@ export interface components { * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 */ router: string; - specified_token_amount: - | { - /** - * Format: int64 - * @example 100 - */ - amount: number; - /** @enum {string} */ - side: "input"; - } - | { - /** - * Format: int64 - * @example 50 - */ - amount: number; - /** @enum {string} */ - side: "output"; - }; + specified_token_amount: { + /** + * Format: int64 + * @example 100 + */ + amount: number; + /** @enum {string} */ + side: "input"; + } | { + /** + * Format: int64 + * @example 50 + */ + amount: number; + /** @enum {string} */ + side: "output"; + }; /** * @description The user wallet address which requested the quote from the wallet. * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 @@ -750,19 +745,17 @@ export interface components { /** @enum {string} */ version: "v1"; }; - QuoteTokens: - | { - input_token: components["schemas"]["TokenAmountSvm"]; - output_token: components["schemas"]["Pubkey"]; - /** @enum {string} */ - type: "input_token_specified"; - } - | { - input_token: components["schemas"]["Pubkey"]; - output_token: components["schemas"]["TokenAmountSvm"]; - /** @enum {string} */ - type: "output_token_specified"; - }; + QuoteTokens: { + input_token: components["schemas"]["TokenAmountSvm"]; + output_token: components["schemas"]["Pubkey"]; + /** @enum {string} */ + type: "input_token_specified"; + } | { + input_token: components["schemas"]["Pubkey"]; + output_token: components["schemas"]["TokenAmountSvm"]; + /** @enum {string} */ + type: "output_token_specified"; + }; QuoteV1Svm: { /** * @description The chain id for the quote. @@ -783,65 +776,57 @@ export interface components { */ transaction: string; }; - ServerResultMessage: - | { - result: components["schemas"]["APIResponse"] | null; - /** @enum {string} */ - status: "success"; - } - | { - result: string; - /** @enum {string} */ - status: "error"; - }; + ServerResultMessage: ({ + result: components["schemas"]["APIResponse"] | null; + /** @enum {string} */ + status: "success"; + }) | { + result: string; + /** @enum {string} */ + 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. */ - ServerResultResponse: components["schemas"]["ServerResultMessage"] & { + ServerResultResponse: components["schemas"]["ServerResultMessage"] & ({ id?: string | null; - }; + }); /** @description This enum is used to send an update to the client for any subscriptions made. */ - ServerUpdateResponse: - | { - opportunity: components["schemas"]["Opportunity"]; - /** @enum {string} */ - type: "new_opportunity"; - } - | { - status: components["schemas"]["BidStatusWithId"]; - /** @enum {string} */ - type: "bid_status_update"; - } - | { - /** @enum {string} */ - type: "svm_chain_update"; - update: components["schemas"]["SvmChainUpdate"]; - } - | { - opportunity_delete: components["schemas"]["OpportunityDelete"]; - /** @enum {string} */ - type: "remove_opportunities"; - }; - SpecifiedTokenAmount: - | { - /** - * Format: int64 - * @example 100 - */ - amount: number; - /** @enum {string} */ - side: "input"; - } - | { - /** - * Format: int64 - * @example 50 - */ - amount: number; - /** @enum {string} */ - side: "output"; - }; + ServerUpdateResponse: { + opportunity: components["schemas"]["Opportunity"]; + /** @enum {string} */ + type: "new_opportunity"; + } | { + status: components["schemas"]["BidStatusWithId"]; + /** @enum {string} */ + type: "bid_status_update"; + } | { + /** @enum {string} */ + type: "svm_chain_update"; + update: components["schemas"]["SvmChainUpdate"]; + } | { + opportunity_delete: components["schemas"]["OpportunityDelete"]; + /** @enum {string} */ + type: "remove_opportunities"; + }; + SpecifiedTokenAmount: { + /** + * Format: int64 + * @example 100 + */ + amount: number; + /** @enum {string} */ + side: "input"; + } | { + /** + * Format: int64 + * @example 50 + */ + amount: number; + /** @enum {string} */ + side: "output"; + }; SvmChainUpdate: { /** @example SLxp9LxX1eE9Z5v99Y92DaYEwyukFgMUF6zRerCF12j */ blockhash: string; @@ -916,9 +901,7 @@ export interface components { }; Opportunity: { content: { - "application/json": - | components["schemas"]["OpportunityEvm"] - | components["schemas"]["OpportunitySvm"]; + "application/json": components["schemas"]["OpportunityEvm"] | components["schemas"]["OpportunitySvm"]; }; }; }; @@ -933,6 +916,7 @@ export type $defs = Record; export type external = Record; export interface operations { + /** * Returns at most 20 bids which were submitted after a specific time. * @deprecated diff --git a/sdk/js/src/svm.ts b/sdk/js/src/svm.ts index e1e5c989..914a9189 100644 --- a/sdk/js/src/svm.ts +++ b/sdk/js/src/svm.ts @@ -6,10 +6,10 @@ import { TransactionInstruction, } from "@solana/web3.js"; import * as anchor from "@coral-xyz/anchor"; -import { BidSvm, ExpressRelaySvmConfig, OpportunitySvmSwap } from "./types"; +import {BidSvm, BidSvmOnChain, BidSvmSwap, ExpressRelaySvmConfig, OpportunitySvmSwap} from "./types"; import { ASSOCIATED_TOKEN_PROGRAM_ID, - createAssociatedTokenAccountInstruction, + createAssociatedTokenAccountIdempotentInstruction, getAssociatedTokenAddressSync, TOKEN_PROGRAM_ID, } from "@solana/spl-token"; @@ -96,20 +96,20 @@ export function getAssociatedTokenAddress( return getAssociatedTokenAddressSync( tokenMintAddress, owner, - true, + true, //allow owner to be off-curve tokenProgram, ASSOCIATED_TOKEN_PROGRAM_ID ); } -export function createAssociatedTokenAccountIdempotentInstruction( +export function createAtaIdempotentInstruction( owner: PublicKey, mint: PublicKey, payer: PublicKey = owner, tokenProgram: PublicKey ): [PublicKey, TransactionInstruction] { const ataAddress = getAssociatedTokenAddress(owner, mint, tokenProgram); - const createUserTokenAccountIx = createAssociatedTokenAccountInstruction( + const createUserTokenAccountIx = createAssociatedTokenAccountIdempotentInstruction( payer, ataAddress, owner, @@ -117,8 +117,6 @@ export function createAssociatedTokenAccountIdempotentInstruction( tokenProgram, ASSOCIATED_TOKEN_PROGRAM_ID ); - // idempotent ix discriminator is 1 - createUserTokenAccountIx.data = Buffer.from([1]); return [ataAddress, createUserTokenAccountIx]; } @@ -130,7 +128,7 @@ export async function constructSwapBid( deadline: anchor.BN, chainId: string, relayerSigner: PublicKey -): Promise { +): Promise { const expressRelay = new Program( expressRelayIdl as ExpressRelay, {} as AnchorProvider @@ -162,9 +160,9 @@ export async function constructSwapBid( swapOpportunity.tokens.type === "output_specified" ? new anchor.BN(swapOpportunity.tokens.outputToken.amount) : bidAmount, - referralFeeBps: new anchor.BN(0), + referralFeeBps: new anchor.BN(swapOpportunity.referralFeeBps), deadline, - feeToken: { input: {} }, + feeToken: swapOpportunity.feeToken === 'input_token' ? {input: {}} : {output: {}}, }; const ixSwap = await expressRelay.methods .swap(swapArgs) @@ -217,7 +215,7 @@ export async function constructSwapBid( .instruction(); ixSwap.programId = svmConstants.expressRelayProgram; tx.instructions.push( - createAssociatedTokenAccountIdempotentInstruction( + createAtaIdempotentInstruction( router, mintFee, searcher, @@ -225,7 +223,7 @@ export async function constructSwapBid( )[1] ); tx.instructions.push( - createAssociatedTokenAccountIdempotentInstruction( + createAtaIdempotentInstruction( relayerSigner, mintFee, searcher, @@ -233,7 +231,7 @@ export async function constructSwapBid( )[1] ); tx.instructions.push( - createAssociatedTokenAccountIdempotentInstruction( + createAtaIdempotentInstruction( expressRelayMetadata, mintFee, searcher, @@ -241,7 +239,7 @@ export async function constructSwapBid( )[1] ); tx.instructions.push( - createAssociatedTokenAccountIdempotentInstruction( + createAtaIdempotentInstruction( trader, mintOutput, searcher, @@ -252,6 +250,8 @@ export async function constructSwapBid( return { transaction: tx, + opportunityId: swapOpportunity.opportunityId, + type:"swap", chainId: chainId, env: "svm", }; @@ -267,7 +267,7 @@ export async function constructSvmBid( chainId: string, relayerSigner: PublicKey, feeReceiverRelayer: PublicKey -): Promise { +): Promise { const ixSubmitBid = await constructSubmitBidInstruction( searcher, router, @@ -284,6 +284,7 @@ export async function constructSvmBid( return { transaction: tx, chainId: chainId, + type:"onchain", env: "svm", }; } diff --git a/sdk/js/src/types.ts b/sdk/js/src/types.ts index 77698866..efc12652 100644 --- a/sdk/js/src/types.ts +++ b/sdk/js/src/types.ts @@ -131,6 +131,8 @@ export type OpportunitySvmSwap = { permissionAccount: PublicKey; routerAccount: PublicKey; userWalletAddress: PublicKey; + feeToken: "input_token" | "output_token"; + referralFeeBps: number; // TODO: maybe type should be camelCase too? tokens: | { @@ -234,7 +236,7 @@ export type ExpressRelaySvmConfig = { /** * Represents a raw SVM bid on acquiring a permission key */ -export type BidSvm = { +export type BidSvmOnChain = { /** * @description Transaction object. * @example SGVsbG8sIFdvcmxkIQ @@ -251,11 +253,41 @@ export type BidSvm = { * @example 293106477 */ slot?: number | null; + type:"onchain"; /** * @description The execution environment for the bid. */ env: "svm"; }; + +/** + * Represents a raw SVM bid to fulfill a swap opportunity + */ +export type BidSvmSwap = { + /** + * @description Transaction object. + * @example SGVsbG8sIFdvcmxkIQ + */ + transaction: Transaction; + /** + * @description The chain id to bid on. + * @example solana + */ + chainId: ChainId; + /** + * @description The id of the swap opportunity to bid on. + * @example obo3ee3e-58cc-4372-a567-0e02b2c3d479 + */ + opportunityId: string; + type:"swap"; + /** + * @description The execution environment for the bid. + */ + env: "svm"; +}; + +export type BidSvm = BidSvmOnChain | BidSvmSwap; + export type BidStatusUpdate = { id: BidId; } & components["schemas"]["BidStatus"]; @@ -278,7 +310,6 @@ export type BidsResponse = { export type SvmConstantsConfig = { expressRelayProgram: PublicKey; - walletRouter: PublicKey; }; export type SvmChainUpdate = { diff --git a/tilt-scripts/svm/test_swap.py b/tilt-scripts/svm/test_swap.py index fe0ce4d9..0ba9b074 100644 --- a/tilt-scripts/svm/test_swap.py +++ b/tilt-scripts/svm/test_swap.py @@ -66,6 +66,7 @@ async def main(): "input_token_mint": args.input_mint, "output_token_mint": args.output_mint, "router": "3hv8L8UeBbyM3M25dF3h2C5p8yA4FptD7FFZu4Z1jCMn", + "referral_fee_bps": 10, "specified_token_amount": {"amount": random.randint(1, 1000), "side": "input"}, "user_wallet_address": str(pk_taker), "version": "v1", From 8a587d8ff77ae64b97bb3c4261f3a59a9367e404 Mon Sep 17 00:00:00 2001 From: Amin Moghaddam Date: Wed, 15 Jan 2025 09:32:11 -0500 Subject: [PATCH 06/25] fix pre-commit 1 --- auction-server/api-types/src/bid.rs | 14 +- auction-server/src/auction/entities/bid.rs | 8 +- .../src/auction/service/verification.rs | 60 +- .../opportunity/service/get_opportunities.rs | 8 +- sdk/js/src/expressRelayTypes.d.ts | 1256 +++++++---------- sdk/js/src/idl/idlExpressRelay.json | 460 +----- sdk/js/src/index.ts | 16 +- sdk/js/src/serverTypes.d.ts | 614 ++++---- sdk/js/src/svm.ts | 43 +- sdk/js/src/types.ts | 4 +- 10 files changed, 980 insertions(+), 1503 deletions(-) diff --git a/auction-server/api-types/src/bid.rs b/auction-server/api-types/src/bid.rs index afd3293b..c71ade4c 100644 --- a/auction-server/api-types/src/bid.rs +++ b/auction-server/api-types/src/bid.rs @@ -1,5 +1,6 @@ use { crate::{ + opportunity::OpportunityId, profile::ProfileId, AccessLevel, ChainId, @@ -35,7 +36,6 @@ use { }, uuid::Uuid, }; -use crate::opportunity::OpportunityId; pub type BidId = Uuid; pub type BidAmountSvm = u64; @@ -269,24 +269,24 @@ pub struct BidCreateOnChainSvm { #[derive(Serialize, Deserialize, ToSchema, Clone, Debug)] #[serde(rename_all = "snake_case")] pub enum BidCreateSwapSvmTag { - Swap + Swap, } #[derive(Serialize, Deserialize, ToSchema, Clone, Debug)] pub struct BidCreateSwapSvm { /// The chain id to bid on. #[schema(example = "solana", value_type = String)] - pub chain_id: ChainId, + pub chain_id: ChainId, /// The transaction for bid. #[schema(example = "SGVsbG8sIFdvcmxkIQ==", value_type = String)] #[serde(with = "crate::serde::transaction_svm")] - pub transaction: VersionedTransaction, + pub transaction: VersionedTransaction, /// The id of the swap opportunity to bid on. #[schema(example = "obo3ee3e-58cc-4372-a567-0e02b2c3d479", value_type = String)] pub opportunity_id: OpportunityId, /// The bid type. Should be "swap" #[schema(example = "swap")] #[serde(rename = "type")] - pub _type: BidCreateSwapSvmTag, // this is mainly to distinguish next types of bids in the future + pub _type: BidCreateSwapSvmTag, // this is mainly to distinguish next types of bids in the future } @@ -338,7 +338,9 @@ impl BidCreate { match self { BidCreate::Evm(bid_create_evm) => bid_create_evm.chain_id.clone(), BidCreate::Svm(BidCreateSvm::Swap(bid_create_svm)) => bid_create_svm.chain_id.clone(), - BidCreate::Svm(BidCreateSvm::OnChain(bid_create_svm)) => bid_create_svm.chain_id.clone(), + BidCreate::Svm(BidCreateSvm::OnChain(bid_create_svm)) => { + bid_create_svm.chain_id.clone() + } } } } diff --git a/auction-server/src/auction/entities/bid.rs b/auction-server/src/auction/entities/bid.rs index 9ecc449c..ffb01fe7 100644 --- a/auction-server/src/auction/entities/bid.rs +++ b/auction-server/src/auction/entities/bid.rs @@ -269,18 +269,18 @@ pub struct BidChainDataOnChainCreateSvm { #[derive(Clone, Debug)] pub struct BidChainDataSwapCreateSvm { - pub transaction: VersionedTransaction, - pub opportunity_id : Uuid, + pub transaction: VersionedTransaction, + pub opportunity_id: Uuid, } #[derive(Clone, Debug)] -pub enum BidChainDataCreateSvm{ +pub enum BidChainDataCreateSvm { OnChain(BidChainDataOnChainCreateSvm), Swap(BidChainDataSwapCreateSvm), } -impl BidChainDataCreateSvm{ +impl BidChainDataCreateSvm { pub fn get_transaction(&self) -> &VersionedTransaction { match self { BidChainDataCreateSvm::OnChain(data) => &data.transaction, diff --git a/auction-server/src/auction/service/verification.rs b/auction-server/src/auction/service/verification.rs index 68ce6e8d..f8fc8951 100644 --- a/auction-server/src/auction/service/verification.rs +++ b/auction-server/src/auction/service/verification.rs @@ -1,4 +1,3 @@ -use std::str::FromStr; use { super::{ auction_manager::TOTAL_BIDS_PER_AUCTION_EVM, @@ -11,6 +10,7 @@ use { entities::{ self, BidChainData, + BidChainDataCreateSvm, BidPaymentInstructionType, SubmitType, }, @@ -38,6 +38,7 @@ use { }, service::{ get_live_opportunities::GetLiveOpportunitiesInput, + get_opportunities::GetOpportunityByIdInput, get_quote::get_quote_permission_key, }, }, @@ -79,14 +80,13 @@ use { transaction::VersionedTransaction, }, std::{ + str::FromStr, sync::Arc, time::Duration, }, time::OffsetDateTime, uuid::Uuid, }; -use crate::auction::entities::BidChainDataCreateSvm; -use crate::opportunity::service::get_opportunities::GetOpportunityByIdInput; pub struct VerifyBidInput { pub bid_create: entities::BidCreate, @@ -486,9 +486,7 @@ impl Service { &self, bid_chain_data_create_svm: &BidChainDataCreateSvm, ) -> Result { - let svm_config = &self.config - .chain_config - .express_relay; + let svm_config = &self.config.chain_config.express_relay; match bid_chain_data_create_svm { BidChainDataCreateSvm::OnChain(bid_data) => { let submit_bid_instruction = self.extract_express_relay_bid_instruction( @@ -524,7 +522,7 @@ impl Service { })?, submit_type: SubmitType::ByServer, }) - }, + } BidChainDataCreateSvm::Swap(bid_data) => { let swap_instruction = self.extract_express_relay_bid_instruction( bid_data.transaction.clone(), @@ -582,9 +580,15 @@ impl Service { FeeToken::Output => mint_output, }; - let opp = self.opportunity_service.get_opportunity_by_id(GetOpportunityByIdInput { opportunity_id: bid_data.opportunity_id }).await.ok_or( - RestError::BadParameters("No swap opportunity with the given id found".to_string()) - )?; + let opp = self + .opportunity_service + .get_opportunity_by_id(GetOpportunityByIdInput { + opportunity_id: bid_data.opportunity_id, + }) + .await + .ok_or(RestError::BadParameters( + "No swap opportunity with the given id found".to_string(), + ))?; let router_fee_receiver_ta = self .extract_account( @@ -601,18 +605,18 @@ impl Service { let fee_token_program = Pubkey::from_str( "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", ) - .map_err(|e| { - RestError::BadParameters(format!("Invalid token program address: {:?}", e)) - })?; + .map_err(|e| { + RestError::BadParameters(format!("Invalid token program address: {:?}", e)) + })?; let associated_token_program = Pubkey::from_str( "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL", ) - .map_err(|e| { - RestError::BadParameters(format!( - "Invalid associated token program address: {:?}", - e - )) - })?; + .map_err(|e| { + RestError::BadParameters(format!( + "Invalid associated token program address: {:?}", + e + )) + })?; let expected_router_fee_receiver_ta = Pubkey::find_program_address( &[ @@ -622,7 +626,7 @@ impl Service { ], &associated_token_program, ) - .0; + .0; if router_fee_receiver_ta != expected_router_fee_receiver_ta { return Err(RestError::BadParameters( @@ -647,7 +651,6 @@ impl Service { )?, submit_type: SubmitType::ByOther, }) - } } } @@ -747,11 +750,10 @@ impl Service { const RETRY_DELAY: Duration = Duration::from_millis(100); let mut retry_count = 0; let bid_slot = match &bid.chain_data { - BidChainDataCreateSvm::OnChain(onchain_data) => { - onchain_data.slot - } - BidChainDataCreateSvm::Swap(_) => None - }.unwrap_or_default(); + BidChainDataCreateSvm::OnChain(onchain_data) => onchain_data.slot, + BidChainDataCreateSvm::Swap(_) => None, + } + .unwrap_or_default(); let should_retry = |result_slot: Slot, retry_count: usize, @@ -776,7 +778,7 @@ impl Service { .config .chain_config .simulator - .simulate_transaction(&bid.chain_data.get_transaction()) + .simulate_transaction(bid.chain_data.get_transaction()) .await; let result = response.map_err(|e| { tracing::error!("Error while simulating bid: {:?}", e); @@ -866,9 +868,7 @@ impl Verification for Service { let transaction = bid.chain_data.get_transaction().clone(); Svm::check_tx_size(&transaction)?; self.check_compute_budget(&transaction).await?; - let bid_data = self - .extract_bid_data(&bid.chain_data) - .await?; + let bid_data = self.extract_bid_data(&bid.chain_data).await?; let bid_payment_instruction_type = match bid_data.submit_type { SubmitType::ByServer => BidPaymentInstructionType::SubmitBid, // TODO*: we should verify all components of the swap here (token amounts, fee token side, referral fee) diff --git a/auction-server/src/opportunity/service/get_opportunities.rs b/auction-server/src/opportunity/service/get_opportunities.rs index 97cb360a..5772bdd2 100644 --- a/auction-server/src/opportunity/service/get_opportunities.rs +++ b/auction-server/src/opportunity/service/get_opportunities.rs @@ -9,8 +9,8 @@ use { }, express_relay_api_types::opportunity::{ GetOpportunitiesQueryParams, + OpportunityId, OpportunityMode, - OpportunityId }, }; @@ -26,8 +26,10 @@ impl Service { pub async fn get_opportunity_by_id( &self, input: GetOpportunityByIdInput, - )-> Option<::Opportunity> { - self.repo.get_in_memory_opportunity_by_id(input.opportunity_id).await + ) -> Option<::Opportunity> { + self.repo + .get_in_memory_opportunity_by_id(input.opportunity_id) + .await } pub async fn get_opportunities( &self, diff --git a/sdk/js/src/expressRelayTypes.d.ts b/sdk/js/src/expressRelayTypes.d.ts index e3432bc8..54bca05d 100644 --- a/sdk/js/src/expressRelayTypes.d.ts +++ b/sdk/js/src/expressRelayTypes.d.ts @@ -5,50 +5,41 @@ * IDL can be found at `target/idl/express_relay.json`. */ export type ExpressRelay = { - "address": "PytERJFhAKuNNuaiXkApLfWzwNwSNDACpigT3LwQfou", - "metadata": { - "name": "expressRelay", - "version": "0.3.1", - "spec": "0.1.0", - "description": "Pyth Express Relay program for handling permissioning and bid distribution", - "repository": "https://github.com/pyth-network/per" - }, - "instructions": [ - { - "name": "checkPermission", - "docs": [ + address: "PytERJFhAKuNNuaiXkApLfWzwNwSNDACpigT3LwQfou"; + metadata: { + name: "expressRelay"; + version: "0.3.1"; + spec: "0.1.0"; + description: "Pyth Express Relay program for handling permissioning and bid distribution"; + repository: "https://github.com/pyth-network/per"; + }; + instructions: [ + { + name: "checkPermission"; + docs: [ "Checks if permissioning exists for a particular (permission, router) pair within the same transaction.", "Permissioning takes the form of a SubmitBid instruction with matching permission and router accounts.", "Returns the fees paid to the router in the matching instructions." - ], - "discriminator": [ - 154, - 199, - 232, - 242, - 96, - 72, - 197, - 236 - ], - "accounts": [ + ]; + discriminator: [154, 199, 232, 242, 96, 72, 197, 236]; + accounts: [ { - "name": "sysvarInstructions", - "address": "Sysvar1nstructions1111111111111111111111111" + name: "sysvarInstructions"; + address: "Sysvar1nstructions1111111111111111111111111"; }, { - "name": "permission" + name: "permission"; }, { - "name": "router" + name: "router"; }, { - "name": "configRouter", - "pda": { - "seeds": [ + name: "configRouter"; + pda: { + seeds: [ { - "kind": "const", - "value": [ + kind: "const"; + value: [ 99, 111, 110, @@ -62,229 +53,151 @@ export type ExpressRelay = { 116, 101, 114 - ] + ]; }, { - "kind": "account", - "path": "router" + kind: "account"; + path: "router"; } - ] - } + ]; + }; }, { - "name": "expressRelayMetadata", - "pda": { - "seeds": [ + name: "expressRelayMetadata"; + pda: { + seeds: [ { - "kind": "const", - "value": [ - 109, - 101, - 116, - 97, - 100, - 97, - 116, - 97 - ] + kind: "const"; + value: [109, 101, 116, 97, 100, 97, 116, 97]; } - ] - } + ]; + }; } - ], - "args": [], - "returns": "u64" + ]; + args: []; + returns: "u64"; }, { - "name": "initialize", - "discriminator": [ - 175, - 175, - 109, - 31, - 13, - 152, - 155, - 237 - ], - "accounts": [ + name: "initialize"; + discriminator: [175, 175, 109, 31, 13, 152, 155, 237]; + accounts: [ { - "name": "payer", - "writable": true, - "signer": true + name: "payer"; + writable: true; + signer: true; }, { - "name": "expressRelayMetadata", - "writable": true, - "pda": { - "seeds": [ + name: "expressRelayMetadata"; + writable: true; + pda: { + seeds: [ { - "kind": "const", - "value": [ - 109, - 101, - 116, - 97, - 100, - 97, - 116, - 97 - ] + kind: "const"; + value: [109, 101, 116, 97, 100, 97, 116, 97]; } - ] - } + ]; + }; }, { - "name": "admin" + name: "admin"; }, { - "name": "relayerSigner" + name: "relayerSigner"; }, { - "name": "feeReceiverRelayer" + name: "feeReceiverRelayer"; }, { - "name": "systemProgram", - "address": "11111111111111111111111111111111" + name: "systemProgram"; + address: "11111111111111111111111111111111"; } - ], - "args": [ - { - "name": "data", - "type": { - "defined": { - "name": "initializeArgs" - } - } + ]; + args: [ + { + name: "data"; + type: { + defined: { + name: "initializeArgs"; + }; + }; } - ] + ]; }, { - "name": "setAdmin", - "discriminator": [ - 251, - 163, - 0, - 52, - 91, - 194, - 187, - 92 - ], - "accounts": [ + name: "setAdmin"; + discriminator: [251, 163, 0, 52, 91, 194, 187, 92]; + accounts: [ { - "name": "admin", - "signer": true, - "relations": [ - "expressRelayMetadata" - ] + name: "admin"; + signer: true; + relations: ["expressRelayMetadata"]; }, { - "name": "expressRelayMetadata", - "writable": true, - "pda": { - "seeds": [ + name: "expressRelayMetadata"; + writable: true; + pda: { + seeds: [ { - "kind": "const", - "value": [ - 109, - 101, - 116, - 97, - 100, - 97, - 116, - 97 - ] + kind: "const"; + value: [109, 101, 116, 97, 100, 97, 116, 97]; } - ] - } + ]; + }; }, { - "name": "adminNew" + name: "adminNew"; } - ], - "args": [] + ]; + args: []; }, { - "name": "setRelayer", - "discriminator": [ - 23, - 243, - 33, - 88, - 110, - 84, - 196, - 37 - ], - "accounts": [ + name: "setRelayer"; + discriminator: [23, 243, 33, 88, 110, 84, 196, 37]; + accounts: [ { - "name": "admin", - "signer": true, - "relations": [ - "expressRelayMetadata" - ] + name: "admin"; + signer: true; + relations: ["expressRelayMetadata"]; }, { - "name": "expressRelayMetadata", - "writable": true, - "pda": { - "seeds": [ + name: "expressRelayMetadata"; + writable: true; + pda: { + seeds: [ { - "kind": "const", - "value": [ - 109, - 101, - 116, - 97, - 100, - 97, - 116, - 97 - ] + kind: "const"; + value: [109, 101, 116, 97, 100, 97, 116, 97]; } - ] - } + ]; + }; }, { - "name": "relayerSigner" + name: "relayerSigner"; }, { - "name": "feeReceiverRelayer" + name: "feeReceiverRelayer"; } - ], - "args": [] + ]; + args: []; }, { - "name": "setRouterSplit", - "discriminator": [ - 16, - 150, - 106, - 13, - 27, - 191, - 104, - 8 - ], - "accounts": [ + name: "setRouterSplit"; + discriminator: [16, 150, 106, 13, 27, 191, 104, 8]; + accounts: [ { - "name": "admin", - "writable": true, - "signer": true, - "relations": [ - "expressRelayMetadata" - ] + name: "admin"; + writable: true; + signer: true; + relations: ["expressRelayMetadata"]; }, { - "name": "configRouter", - "writable": true, - "pda": { - "seeds": [ + name: "configRouter"; + writable: true; + pda: { + seeds: [ { - "kind": "const", - "value": [ + kind: "const"; + value: [ 99, 111, 110, @@ -298,202 +211,142 @@ export type ExpressRelay = { 116, 101, 114 - ] + ]; }, { - "kind": "account", - "path": "router" + kind: "account"; + path: "router"; } - ] - } + ]; + }; }, { - "name": "expressRelayMetadata", - "pda": { - "seeds": [ + name: "expressRelayMetadata"; + pda: { + seeds: [ { - "kind": "const", - "value": [ - 109, - 101, - 116, - 97, - 100, - 97, - 116, - 97 - ] + kind: "const"; + value: [109, 101, 116, 97, 100, 97, 116, 97]; } - ] - } + ]; + }; }, { - "name": "router" + name: "router"; }, { - "name": "systemProgram", - "address": "11111111111111111111111111111111" + name: "systemProgram"; + address: "11111111111111111111111111111111"; } - ], - "args": [ - { - "name": "data", - "type": { - "defined": { - "name": "setRouterSplitArgs" - } - } + ]; + args: [ + { + name: "data"; + type: { + defined: { + name: "setRouterSplitArgs"; + }; + }; } - ] + ]; }, { - "name": "setSplits", - "discriminator": [ - 175, - 2, - 86, - 49, - 225, - 202, - 232, - 189 - ], - "accounts": [ + name: "setSplits"; + discriminator: [175, 2, 86, 49, 225, 202, 232, 189]; + accounts: [ { - "name": "admin", - "signer": true, - "relations": [ - "expressRelayMetadata" - ] + name: "admin"; + signer: true; + relations: ["expressRelayMetadata"]; }, { - "name": "expressRelayMetadata", - "writable": true, - "pda": { - "seeds": [ + name: "expressRelayMetadata"; + writable: true; + pda: { + seeds: [ { - "kind": "const", - "value": [ - 109, - 101, - 116, - 97, - 100, - 97, - 116, - 97 - ] + kind: "const"; + value: [109, 101, 116, 97, 100, 97, 116, 97]; } - ] - } + ]; + }; } - ], - "args": [ - { - "name": "data", - "type": { - "defined": { - "name": "setSplitsArgs" - } - } + ]; + args: [ + { + name: "data"; + type: { + defined: { + name: "setSplitsArgs"; + }; + }; } - ] + ]; }, { - "name": "setSwapPlatformFee", - "discriminator": [ - 2, - 135, - 75, - 15, - 8, - 105, - 142, - 47 - ], - "accounts": [ + name: "setSwapPlatformFee"; + discriminator: [2, 135, 75, 15, 8, 105, 142, 47]; + accounts: [ { - "name": "admin", - "signer": true, - "relations": [ - "expressRelayMetadata" - ] + name: "admin"; + signer: true; + relations: ["expressRelayMetadata"]; }, { - "name": "expressRelayMetadata", - "writable": true, - "pda": { - "seeds": [ + name: "expressRelayMetadata"; + writable: true; + pda: { + seeds: [ { - "kind": "const", - "value": [ - 109, - 101, - 116, - 97, - 100, - 97, - 116, - 97 - ] + kind: "const"; + value: [109, 101, 116, 97, 100, 97, 116, 97]; } - ] - } + ]; + }; } - ], - "args": [ - { - "name": "data", - "type": { - "defined": { - "name": "setSwapPlatformFeeArgs" - } - } + ]; + args: [ + { + name: "data"; + type: { + defined: { + name: "setSwapPlatformFeeArgs"; + }; + }; } - ] + ]; }, { - "name": "submitBid", - "docs": [ + name: "submitBid"; + docs: [ "Submits a bid for a particular (permission, router) pair and distributes bids according to splits." - ], - "discriminator": [ - 19, - 164, - 237, - 254, - 64, - 139, - 237, - 93 - ], - "accounts": [ + ]; + discriminator: [19, 164, 237, 254, 64, 139, 237, 93]; + accounts: [ { - "name": "searcher", - "writable": true, - "signer": true + name: "searcher"; + writable: true; + signer: true; }, { - "name": "relayerSigner", - "signer": true, - "relations": [ - "expressRelayMetadata" - ] + name: "relayerSigner"; + signer: true; + relations: ["expressRelayMetadata"]; }, { - "name": "permission" + name: "permission"; }, { - "name": "router", - "writable": true + name: "router"; + writable: true; }, { - "name": "configRouter", - "pda": { - "seeds": [ + name: "configRouter"; + pda: { + seeds: [ { - "kind": "const", - "value": [ + kind: "const"; + value: [ 99, 111, 110, @@ -507,119 +360,99 @@ export type ExpressRelay = { 116, 101, 114 - ] + ]; }, { - "kind": "account", - "path": "router" + kind: "account"; + path: "router"; } - ] - } + ]; + }; }, { - "name": "expressRelayMetadata", - "writable": true, - "pda": { - "seeds": [ + name: "expressRelayMetadata"; + writable: true; + pda: { + seeds: [ { - "kind": "const", - "value": [ - 109, - 101, - 116, - 97, - 100, - 97, - 116, - 97 - ] + kind: "const"; + value: [109, 101, 116, 97, 100, 97, 116, 97]; } - ] - } + ]; + }; }, { - "name": "feeReceiverRelayer", - "writable": true, - "relations": [ - "expressRelayMetadata" - ] + name: "feeReceiverRelayer"; + writable: true; + relations: ["expressRelayMetadata"]; }, { - "name": "systemProgram", - "address": "11111111111111111111111111111111" + name: "systemProgram"; + address: "11111111111111111111111111111111"; }, { - "name": "sysvarInstructions", - "address": "Sysvar1nstructions1111111111111111111111111" + name: "sysvarInstructions"; + address: "Sysvar1nstructions1111111111111111111111111"; } - ], - "args": [ - { - "name": "data", - "type": { - "defined": { - "name": "submitBidArgs" - } - } + ]; + args: [ + { + name: "data"; + type: { + defined: { + name: "submitBidArgs"; + }; + }; } - ] + ]; }, { - "name": "swap", - "discriminator": [ - 248, - 198, - 158, - 145, - 225, - 117, - 135, - 200 - ], - "accounts": [ + name: "swap"; + discriminator: [248, 198, 158, 145, 225, 117, 135, 200]; + accounts: [ { - "name": "searcher", - "docs": [ + name: "searcher"; + docs: [ "Searcher is the party that sends the input token and receives the output token" - ], - "signer": true + ]; + signer: true; }, { - "name": "trader", - "docs": [ + name: "trader"; + docs: [ "Trader is the party that sends the output token and receives the input token" - ], - "signer": true + ]; + signer: true; }, { - "name": "searcherInputTa", - "writable": true + name: "searcherInputTa"; + writable: true; }, { - "name": "searcherOutputTa", - "writable": true + name: "searcherOutputTa"; + writable: true; }, { - "name": "traderInputAta", - "writable": true, - "pda": { - "seeds": [ + name: "traderInputAta"; + writable: true; + pda: { + seeds: [ { - "kind": "account", - "path": "trader" + kind: "account"; + path: "trader"; }, { - "kind": "account", - "path": "tokenProgramInput" + kind: "account"; + path: "tokenProgramInput"; }, { - "kind": "account", - "path": "mintInput" + kind: "account"; + path: "mintInput"; } - ], - "program": { - "kind": "const", - "value": [ + ]; + program: { + kind: "const"; + value: [ 140, 151, 37, @@ -652,31 +485,31 @@ export type ExpressRelay = { 233, 248, 89 - ] - } - } + ]; + }; + }; }, { - "name": "traderOutputAta", - "writable": true, - "pda": { - "seeds": [ + name: "traderOutputAta"; + writable: true; + pda: { + seeds: [ { - "kind": "account", - "path": "trader" + kind: "account"; + path: "trader"; }, { - "kind": "account", - "path": "tokenProgramOutput" + kind: "account"; + path: "tokenProgramOutput"; }, { - "kind": "account", - "path": "mintOutput" + kind: "account"; + path: "mintOutput"; } - ], - "program": { - "kind": "const", - "value": [ + ]; + program: { + kind: "const"; + value: [ 140, 151, 37, @@ -709,39 +542,39 @@ export type ExpressRelay = { 233, 248, 89 - ] - } - } + ]; + }; + }; }, { - "name": "routerFeeReceiverTa", - "docs": [ + name: "routerFeeReceiverTa"; + docs: [ "Router fee receiver token account: the referrer can provide an arbitrary receiver for the router fee" - ], - "writable": true + ]; + writable: true; }, { - "name": "relayerFeeReceiverAta", - "writable": true, - "pda": { - "seeds": [ + name: "relayerFeeReceiverAta"; + writable: true; + pda: { + seeds: [ { - "kind": "account", - "path": "express_relay_metadata.fee_receiver_relayer", - "account": "expressRelayMetadata" + kind: "account"; + path: "express_relay_metadata.fee_receiver_relayer"; + account: "expressRelayMetadata"; }, { - "kind": "account", - "path": "tokenProgramFee" + kind: "account"; + path: "tokenProgramFee"; }, { - "kind": "account", - "path": "mintFee" + kind: "account"; + path: "mintFee"; } - ], - "program": { - "kind": "const", - "value": [ + ]; + program: { + kind: "const"; + value: [ 140, 151, 37, @@ -774,31 +607,31 @@ export type ExpressRelay = { 233, 248, 89 - ] - } - } + ]; + }; + }; }, { - "name": "expressRelayFeeReceiverAta", - "writable": true, - "pda": { - "seeds": [ + name: "expressRelayFeeReceiverAta"; + writable: true; + pda: { + seeds: [ { - "kind": "account", - "path": "expressRelayMetadata" + kind: "account"; + path: "expressRelayMetadata"; }, { - "kind": "account", - "path": "tokenProgramFee" + kind: "account"; + path: "tokenProgramFee"; }, { - "kind": "account", - "path": "mintFee" + kind: "account"; + path: "mintFee"; } - ], - "program": { - "kind": "const", - "value": [ + ]; + program: { + kind: "const"; + value: [ 140, 151, 37, @@ -831,374 +664,325 @@ export type ExpressRelay = { 233, 248, 89 - ] - } - } + ]; + }; + }; }, { - "name": "mintInput" + name: "mintInput"; }, { - "name": "mintOutput" + name: "mintOutput"; }, { - "name": "mintFee" + name: "mintFee"; }, { - "name": "tokenProgramInput" + name: "tokenProgramInput"; }, { - "name": "tokenProgramOutput" + name: "tokenProgramOutput"; }, { - "name": "tokenProgramFee" + name: "tokenProgramFee"; }, { - "name": "expressRelayMetadata", - "docs": [ - "Express relay configuration" - ], - "pda": { - "seeds": [ + name: "expressRelayMetadata"; + docs: ["Express relay configuration"]; + pda: { + seeds: [ { - "kind": "const", - "value": [ - 109, - 101, - 116, - 97, - 100, - 97, - 116, - 97 - ] + kind: "const"; + value: [109, 101, 116, 97, 100, 97, 116, 97]; } - ] - } + ]; + }; } - ], - "args": [ - { - "name": "data", - "type": { - "defined": { - "name": "swapArgs" - } - } + ]; + args: [ + { + name: "data"; + type: { + defined: { + name: "swapArgs"; + }; + }; } - ] + ]; }, { - "name": "withdrawFees", - "discriminator": [ - 198, - 212, - 171, - 109, - 144, - 215, - 174, - 89 - ], - "accounts": [ + name: "withdrawFees"; + discriminator: [198, 212, 171, 109, 144, 215, 174, 89]; + accounts: [ { - "name": "admin", - "signer": true, - "relations": [ - "expressRelayMetadata" - ] + name: "admin"; + signer: true; + relations: ["expressRelayMetadata"]; }, { - "name": "feeReceiverAdmin", - "writable": true + name: "feeReceiverAdmin"; + writable: true; }, { - "name": "expressRelayMetadata", - "writable": true, - "pda": { - "seeds": [ + name: "expressRelayMetadata"; + writable: true; + pda: { + seeds: [ { - "kind": "const", - "value": [ - 109, - 101, - 116, - 97, - 100, - 97, - 116, - 97 - ] + kind: "const"; + value: [109, 101, 116, 97, 100, 97, 116, 97]; } - ] - } + ]; + }; } - ], - "args": [] + ]; + args: []; } - ], - "accounts": [ + ]; + accounts: [ { - "name": "configRouter", - "discriminator": [ - 135, - 66, - 240, - 166, - 94, - 198, - 187, - 36 - ] + name: "configRouter"; + discriminator: [135, 66, 240, 166, 94, 198, 187, 36]; }, { - "name": "expressRelayMetadata", - "discriminator": [ - 204, - 75, - 133, - 7, - 175, - 241, - 130, - 11 - ] + name: "expressRelayMetadata"; + discriminator: [204, 75, 133, 7, 175, 241, 130, 11]; } - ], - "errors": [ + ]; + errors: [ { - "code": 6000, - "name": "feeSplitLargerThanPrecision", - "msg": "Fee split(s) larger than fee precision" + code: 6000; + name: "feeSplitLargerThanPrecision"; + msg: "Fee split(s) larger than fee precision"; }, { - "code": 6001, - "name": "feesHigherThanBid", - "msg": "Fees higher than bid" + code: 6001; + name: "feesHigherThanBid"; + msg: "Fees higher than bid"; }, { - "code": 6002, - "name": "deadlinePassed", - "msg": "Deadline passed" + code: 6002; + name: "deadlinePassed"; + msg: "Deadline passed"; }, { - "code": 6003, - "name": "invalidCpiSubmitBid", - "msg": "Invalid CPI into submit bid instruction" + code: 6003; + name: "invalidCpiSubmitBid"; + msg: "Invalid CPI into submit bid instruction"; }, { - "code": 6004, - "name": "missingPermission", - "msg": "Missing permission" + code: 6004; + name: "missingPermission"; + msg: "Missing permission"; }, { - "code": 6005, - "name": "multiplePermissions", - "msg": "Multiple permissions" + code: 6005; + name: "multiplePermissions"; + msg: "Multiple permissions"; }, { - "code": 6006, - "name": "insufficientSearcherFunds", - "msg": "Insufficient searcher funds" + code: 6006; + name: "insufficientSearcherFunds"; + msg: "Insufficient searcher funds"; }, { - "code": 6007, - "name": "insufficientRent", - "msg": "Insufficient funds for rent" + code: 6007; + name: "insufficientRent"; + msg: "Insufficient funds for rent"; }, { - "code": 6008, - "name": "invalidAta", - "msg": "Invalid ATA provided" + code: 6008; + name: "invalidAta"; + msg: "Invalid ATA provided"; }, { - "code": 6009, - "name": "invalidMint", - "msg": "A token account has the wrong mint" + code: 6009; + name: "invalidMint"; + msg: "A token account has the wrong mint"; }, { - "code": 6010, - "name": "invalidTokenProgram", - "msg": "A token account belongs to the wrong token program" + code: 6010; + name: "invalidTokenProgram"; + msg: "A token account belongs to the wrong token program"; }, { - "code": 6011, - "name": "invalidReferralFee", - "msg": "Invalid referral fee" + code: 6011; + name: "invalidReferralFee"; + msg: "Invalid referral fee"; } - ], - "types": [ + ]; + types: [ { - "name": "configRouter", - "type": { - "kind": "struct", - "fields": [ + name: "configRouter"; + type: { + kind: "struct"; + fields: [ { - "name": "router", - "type": "pubkey" + name: "router"; + type: "pubkey"; }, { - "name": "split", - "type": "u64" + name: "split"; + type: "u64"; } - ] - } + ]; + }; }, { - "name": "expressRelayMetadata", - "type": { - "kind": "struct", - "fields": [ + name: "expressRelayMetadata"; + type: { + kind: "struct"; + fields: [ { - "name": "admin", - "type": "pubkey" + name: "admin"; + type: "pubkey"; }, { - "name": "relayerSigner", - "type": "pubkey" + name: "relayerSigner"; + type: "pubkey"; }, { - "name": "feeReceiverRelayer", - "type": "pubkey" + name: "feeReceiverRelayer"; + type: "pubkey"; }, { - "name": "splitRouterDefault", - "type": "u64" + name: "splitRouterDefault"; + type: "u64"; }, { - "name": "splitRelayer", - "type": "u64" + name: "splitRelayer"; + type: "u64"; }, { - "name": "swapPlatformFeeBps", - "type": "u64" + name: "swapPlatformFeeBps"; + type: "u64"; } - ] - } + ]; + }; }, { - "name": "feeToken", - "type": { - "kind": "enum", - "variants": [ + name: "feeToken"; + type: { + kind: "enum"; + variants: [ { - "name": "input" + name: "input"; }, { - "name": "output" + name: "output"; } - ] - } + ]; + }; }, { - "name": "initializeArgs", - "type": { - "kind": "struct", - "fields": [ + name: "initializeArgs"; + type: { + kind: "struct"; + fields: [ { - "name": "splitRouterDefault", - "type": "u64" + name: "splitRouterDefault"; + type: "u64"; }, { - "name": "splitRelayer", - "type": "u64" + name: "splitRelayer"; + type: "u64"; } - ] - } + ]; + }; }, { - "name": "setRouterSplitArgs", - "type": { - "kind": "struct", - "fields": [ + name: "setRouterSplitArgs"; + type: { + kind: "struct"; + fields: [ { - "name": "splitRouter", - "type": "u64" + name: "splitRouter"; + type: "u64"; } - ] - } + ]; + }; }, { - "name": "setSplitsArgs", - "type": { - "kind": "struct", - "fields": [ + name: "setSplitsArgs"; + type: { + kind: "struct"; + fields: [ { - "name": "splitRouterDefault", - "type": "u64" + name: "splitRouterDefault"; + type: "u64"; }, { - "name": "splitRelayer", - "type": "u64" + name: "splitRelayer"; + type: "u64"; } - ] - } + ]; + }; }, { - "name": "setSwapPlatformFeeArgs", - "type": { - "kind": "struct", - "fields": [ + name: "setSwapPlatformFeeArgs"; + type: { + kind: "struct"; + fields: [ { - "name": "swapPlatformFeeBps", - "type": "u64" + name: "swapPlatformFeeBps"; + type: "u64"; } - ] - } + ]; + }; }, { - "name": "submitBidArgs", - "type": { - "kind": "struct", - "fields": [ + name: "submitBidArgs"; + type: { + kind: "struct"; + fields: [ { - "name": "deadline", - "type": "i64" + name: "deadline"; + type: "i64"; }, { - "name": "bidAmount", - "type": "u64" + name: "bidAmount"; + type: "u64"; } - ] - } + ]; + }; }, { - "name": "swapArgs", - "docs": [ + name: "swapArgs"; + docs: [ "For all swap instructions and contexts, input and output are defined with respect to the searcher", "So `mint_input` refers to the token that the searcher provides to the trader and", "`mint_output` refers to the token that the searcher receives from the trader", "This choice is made to minimize confusion for the searchers, who are more likely to parse the program" - ], - "type": { - "kind": "struct", - "fields": [ + ]; + type: { + kind: "struct"; + fields: [ { - "name": "deadline", - "type": "i64" + name: "deadline"; + type: "i64"; }, { - "name": "amountInput", - "type": "u64" + name: "amountInput"; + type: "u64"; }, { - "name": "amountOutput", - "type": "u64" + name: "amountOutput"; + type: "u64"; }, { - "name": "referralFeeBps", - "type": "u16" + name: "referralFeeBps"; + type: "u16"; }, { - "name": "feeToken", - "type": { - "defined": { - "name": "feeToken" - } - } - } - ] - } + name: "feeToken"; + type: { + defined: { + name: "feeToken"; + }; + }; + } + ]; + }; } - ] + ]; }; diff --git a/sdk/js/src/idl/idlExpressRelay.json b/sdk/js/src/idl/idlExpressRelay.json index 9d0e2ddd..bea53d2f 100644 --- a/sdk/js/src/idl/idlExpressRelay.json +++ b/sdk/js/src/idl/idlExpressRelay.json @@ -15,16 +15,7 @@ "Permissioning takes the form of a SubmitBid instruction with matching permission and router accounts.", "Returns the fees paid to the router in the matching instructions." ], - "discriminator": [ - 154, - 199, - 232, - 242, - 96, - 72, - 197, - 236 - ], + "discriminator": [154, 199, 232, 242, 96, 72, 197, 236], "accounts": [ { "name": "sysvar_instructions", @@ -43,19 +34,7 @@ { "kind": "const", "value": [ - 99, - 111, - 110, - 102, - 105, - 103, - 95, - 114, - 111, - 117, - 116, - 101, - 114 + 99, 111, 110, 102, 105, 103, 95, 114, 111, 117, 116, 101, 114 ] }, { @@ -71,16 +50,7 @@ "seeds": [ { "kind": "const", - "value": [ - 109, - 101, - 116, - 97, - 100, - 97, - 116, - 97 - ] + "value": [109, 101, 116, 97, 100, 97, 116, 97] } ] } @@ -91,16 +61,7 @@ }, { "name": "initialize", - "discriminator": [ - 175, - 175, - 109, - 31, - 13, - 152, - 155, - 237 - ], + "discriminator": [175, 175, 109, 31, 13, 152, 155, 237], "accounts": [ { "name": "payer", @@ -114,16 +75,7 @@ "seeds": [ { "kind": "const", - "value": [ - 109, - 101, - 116, - 97, - 100, - 97, - 116, - 97 - ] + "value": [109, 101, 116, 97, 100, 97, 116, 97] } ] } @@ -155,23 +107,12 @@ }, { "name": "set_admin", - "discriminator": [ - 251, - 163, - 0, - 52, - 91, - 194, - 187, - 92 - ], + "discriminator": [251, 163, 0, 52, 91, 194, 187, 92], "accounts": [ { "name": "admin", "signer": true, - "relations": [ - "express_relay_metadata" - ] + "relations": ["express_relay_metadata"] }, { "name": "express_relay_metadata", @@ -180,16 +121,7 @@ "seeds": [ { "kind": "const", - "value": [ - 109, - 101, - 116, - 97, - 100, - 97, - 116, - 97 - ] + "value": [109, 101, 116, 97, 100, 97, 116, 97] } ] } @@ -202,23 +134,12 @@ }, { "name": "set_relayer", - "discriminator": [ - 23, - 243, - 33, - 88, - 110, - 84, - 196, - 37 - ], + "discriminator": [23, 243, 33, 88, 110, 84, 196, 37], "accounts": [ { "name": "admin", "signer": true, - "relations": [ - "express_relay_metadata" - ] + "relations": ["express_relay_metadata"] }, { "name": "express_relay_metadata", @@ -227,16 +148,7 @@ "seeds": [ { "kind": "const", - "value": [ - 109, - 101, - 116, - 97, - 100, - 97, - 116, - 97 - ] + "value": [109, 101, 116, 97, 100, 97, 116, 97] } ] } @@ -252,24 +164,13 @@ }, { "name": "set_router_split", - "discriminator": [ - 16, - 150, - 106, - 13, - 27, - 191, - 104, - 8 - ], + "discriminator": [16, 150, 106, 13, 27, 191, 104, 8], "accounts": [ { "name": "admin", "writable": true, "signer": true, - "relations": [ - "express_relay_metadata" - ] + "relations": ["express_relay_metadata"] }, { "name": "config_router", @@ -279,19 +180,7 @@ { "kind": "const", "value": [ - 99, - 111, - 110, - 102, - 105, - 103, - 95, - 114, - 111, - 117, - 116, - 101, - 114 + 99, 111, 110, 102, 105, 103, 95, 114, 111, 117, 116, 101, 114 ] }, { @@ -307,16 +196,7 @@ "seeds": [ { "kind": "const", - "value": [ - 109, - 101, - 116, - 97, - 100, - 97, - 116, - 97 - ] + "value": [109, 101, 116, 97, 100, 97, 116, 97] } ] } @@ -342,23 +222,12 @@ }, { "name": "set_splits", - "discriminator": [ - 175, - 2, - 86, - 49, - 225, - 202, - 232, - 189 - ], + "discriminator": [175, 2, 86, 49, 225, 202, 232, 189], "accounts": [ { "name": "admin", "signer": true, - "relations": [ - "express_relay_metadata" - ] + "relations": ["express_relay_metadata"] }, { "name": "express_relay_metadata", @@ -367,16 +236,7 @@ "seeds": [ { "kind": "const", - "value": [ - 109, - 101, - 116, - 97, - 100, - 97, - 116, - 97 - ] + "value": [109, 101, 116, 97, 100, 97, 116, 97] } ] } @@ -395,23 +255,12 @@ }, { "name": "set_swap_platform_fee", - "discriminator": [ - 2, - 135, - 75, - 15, - 8, - 105, - 142, - 47 - ], + "discriminator": [2, 135, 75, 15, 8, 105, 142, 47], "accounts": [ { "name": "admin", "signer": true, - "relations": [ - "express_relay_metadata" - ] + "relations": ["express_relay_metadata"] }, { "name": "express_relay_metadata", @@ -420,16 +269,7 @@ "seeds": [ { "kind": "const", - "value": [ - 109, - 101, - 116, - 97, - 100, - 97, - 116, - 97 - ] + "value": [109, 101, 116, 97, 100, 97, 116, 97] } ] } @@ -451,16 +291,7 @@ "docs": [ "Submits a bid for a particular (permission, router) pair and distributes bids according to splits." ], - "discriminator": [ - 19, - 164, - 237, - 254, - 64, - 139, - 237, - 93 - ], + "discriminator": [19, 164, 237, 254, 64, 139, 237, 93], "accounts": [ { "name": "searcher", @@ -470,9 +301,7 @@ { "name": "relayer_signer", "signer": true, - "relations": [ - "express_relay_metadata" - ] + "relations": ["express_relay_metadata"] }, { "name": "permission" @@ -488,19 +317,7 @@ { "kind": "const", "value": [ - 99, - 111, - 110, - 102, - 105, - 103, - 95, - 114, - 111, - 117, - 116, - 101, - 114 + 99, 111, 110, 102, 105, 103, 95, 114, 111, 117, 116, 101, 114 ] }, { @@ -517,16 +334,7 @@ "seeds": [ { "kind": "const", - "value": [ - 109, - 101, - 116, - 97, - 100, - 97, - 116, - 97 - ] + "value": [109, 101, 116, 97, 100, 97, 116, 97] } ] } @@ -534,9 +342,7 @@ { "name": "fee_receiver_relayer", "writable": true, - "relations": [ - "express_relay_metadata" - ] + "relations": ["express_relay_metadata"] }, { "name": "system_program", @@ -560,16 +366,7 @@ }, { "name": "swap", - "discriminator": [ - 248, - 198, - 158, - 145, - 225, - 117, - 135, - 200 - ], + "discriminator": [248, 198, 158, 145, 225, 117, 135, 200], "accounts": [ { "name": "searcher", @@ -614,38 +411,9 @@ "program": { "kind": "const", "value": [ - 140, - 151, - 37, - 143, - 78, - 36, - 137, - 241, - 187, - 61, - 16, - 41, - 20, - 142, - 13, - 131, - 11, - 90, - 19, - 153, - 218, - 255, - 16, - 132, - 4, - 142, - 123, - 216, - 219, - 233, - 248, - 89 + 140, 151, 37, 143, 78, 36, 137, 241, 187, 61, 16, 41, 20, 142, + 13, 131, 11, 90, 19, 153, 218, 255, 16, 132, 4, 142, 123, 216, + 219, 233, 248, 89 ] } } @@ -671,38 +439,9 @@ "program": { "kind": "const", "value": [ - 140, - 151, - 37, - 143, - 78, - 36, - 137, - 241, - 187, - 61, - 16, - 41, - 20, - 142, - 13, - 131, - 11, - 90, - 19, - 153, - 218, - 255, - 16, - 132, - 4, - 142, - 123, - 216, - 219, - 233, - 248, - 89 + 140, 151, 37, 143, 78, 36, 137, 241, 187, 61, 16, 41, 20, 142, + 13, 131, 11, 90, 19, 153, 218, 255, 16, 132, 4, 142, 123, 216, + 219, 233, 248, 89 ] } } @@ -736,38 +475,9 @@ "program": { "kind": "const", "value": [ - 140, - 151, - 37, - 143, - 78, - 36, - 137, - 241, - 187, - 61, - 16, - 41, - 20, - 142, - 13, - 131, - 11, - 90, - 19, - 153, - 218, - 255, - 16, - 132, - 4, - 142, - 123, - 216, - 219, - 233, - 248, - 89 + 140, 151, 37, 143, 78, 36, 137, 241, 187, 61, 16, 41, 20, 142, + 13, 131, 11, 90, 19, 153, 218, 255, 16, 132, 4, 142, 123, 216, + 219, 233, 248, 89 ] } } @@ -793,38 +503,9 @@ "program": { "kind": "const", "value": [ - 140, - 151, - 37, - 143, - 78, - 36, - 137, - 241, - 187, - 61, - 16, - 41, - 20, - 142, - 13, - 131, - 11, - 90, - 19, - 153, - 218, - 255, - 16, - 132, - 4, - 142, - 123, - 216, - 219, - 233, - 248, - 89 + 140, 151, 37, 143, 78, 36, 137, 241, 187, 61, 16, 41, 20, 142, + 13, 131, 11, 90, 19, 153, 218, 255, 16, 132, 4, 142, 123, 216, + 219, 233, 248, 89 ] } } @@ -849,23 +530,12 @@ }, { "name": "express_relay_metadata", - "docs": [ - "Express relay configuration" - ], + "docs": ["Express relay configuration"], "pda": { "seeds": [ { "kind": "const", - "value": [ - 109, - 101, - 116, - 97, - 100, - 97, - 116, - 97 - ] + "value": [109, 101, 116, 97, 100, 97, 116, 97] } ] } @@ -884,23 +554,12 @@ }, { "name": "withdraw_fees", - "discriminator": [ - 198, - 212, - 171, - 109, - 144, - 215, - 174, - 89 - ], + "discriminator": [198, 212, 171, 109, 144, 215, 174, 89], "accounts": [ { "name": "admin", "signer": true, - "relations": [ - "express_relay_metadata" - ] + "relations": ["express_relay_metadata"] }, { "name": "fee_receiver_admin", @@ -913,16 +572,7 @@ "seeds": [ { "kind": "const", - "value": [ - 109, - 101, - 116, - 97, - 100, - 97, - 116, - 97 - ] + "value": [109, 101, 116, 97, 100, 97, 116, 97] } ] } @@ -934,29 +584,11 @@ "accounts": [ { "name": "ConfigRouter", - "discriminator": [ - 135, - 66, - 240, - 166, - 94, - 198, - 187, - 36 - ] + "discriminator": [135, 66, 240, 166, 94, 198, 187, 36] }, { "name": "ExpressRelayMetadata", - "discriminator": [ - 204, - 75, - 133, - 7, - 175, - 241, - 130, - 11 - ] + "discriminator": [204, 75, 133, 7, 175, 241, 130, 11] } ], "errors": [ diff --git a/sdk/js/src/index.ts b/sdk/js/src/index.ts index 52b2d536..aad37b84 100644 --- a/sdk/js/src/index.ts +++ b/sdk/js/src/index.ts @@ -11,7 +11,6 @@ import { BidParams, BidsResponse, BidStatusUpdate, - BidSvm, ExpressRelaySvmConfig, Opportunity, OpportunityBid, @@ -22,7 +21,8 @@ import { OpportunityDelete, ChainType, QuoteRequest, - QuoteResponse, BidSvmOnChain, + QuoteResponse, + BidSvmOnChain, } from "./types"; import { Connection, @@ -656,22 +656,22 @@ export class Client { permission_key: bid.permissionKey, }; } - if (bid.type==='swap') { + if (bid.type === "swap") { return { chain_id: bid.chainId, opportunity_id: bid.opportunityId, - type:"swap", + type: "swap", transaction: bid.transaction - .serialize({requireAllSignatures: false}) - .toString("base64"), + .serialize({ requireAllSignatures: false }) + .toString("base64"), }; } else { return { chain_id: bid.chainId, slot: bid.slot, transaction: bid.transaction - .serialize({requireAllSignatures: false}) - .toString("base64"), + .serialize({ requireAllSignatures: false }) + .toString("base64"), }; } } diff --git a/sdk/js/src/serverTypes.d.ts b/sdk/js/src/serverTypes.d.ts index 078a0884..ec7c6c05 100644 --- a/sdk/js/src/serverTypes.d.ts +++ b/sdk/js/src/serverTypes.d.ts @@ -3,7 +3,6 @@ * Do not make direct changes to the file. */ - export interface paths { "/v1/bids": { /** @@ -83,7 +82,9 @@ export interface components { schemas: { APIResponse: components["schemas"]["BidResult"]; Bid: components["schemas"]["BidEvm"] | components["schemas"]["BidSvm"]; - BidCreate: components["schemas"]["BidCreateEvm"] | components["schemas"]["BidCreateSvm"]; + BidCreate: + | components["schemas"]["BidCreateEvm"] + | components["schemas"]["BidCreateSvm"]; BidCreateEvm: { /** * @description Amount of bid in wei. @@ -130,7 +131,9 @@ export interface components { */ transaction: string; }; - BidCreateSvm: components["schemas"]["BidCreateSwapSvm"] | components["schemas"]["BidCreateOnChainSvm"]; + BidCreateSvm: + | components["schemas"]["BidCreateSwapSvm"] + | components["schemas"]["BidCreateOnChainSvm"]; BidCreateSwapSvm: { /** * @description The chain id to bid on. @@ -212,70 +215,82 @@ export interface components { */ status: string; }; - BidStatus: components["schemas"]["BidStatusEvm"] | components["schemas"]["BidStatusSvm"]; - BidStatusEvm: { - /** @enum {string} */ - type: "pending"; - } | { - /** - * Format: int32 - * @example 1 - */ - index: number; - /** @example 0x103d4fbd777a36311b5161f2062490f761f25b67406badb2bace62bb170aa4e3 */ - result: string; - /** @enum {string} */ - type: "submitted"; - } | ({ - /** - * Format: int32 - * @example 1 - */ - index?: number | null; - /** @example 0x103d4fbd777a36311b5161f2062490f761f25b67406badb2bace62bb170aa4e3 */ - result?: string | null; - /** @enum {string} */ - type: "lost"; - }) | { - /** - * Format: int32 - * @example 1 - */ - index: number; - /** @example 0x103d4fbd777a36311b5161f2062490f761f25b67406badb2bace62bb170aa4e3 */ - result: string; - /** @enum {string} */ - type: "won"; - }; - BidStatusSvm: { - /** @enum {string} */ - type: "pending"; - } | ({ - /** @example Jb2urXPyEh4xiBgzYvwEFe4q1iMxG1DNxWGGQg94AmKgqFTwLAiTiHrYiYxwHUB4DV8u5ahNEVtMMDm3sNSRdTg */ - result?: string | null; - /** @enum {string} */ - type: "lost"; - }) | { - /** @example Jb2urXPyEh4xiBgzYvwEFe4q1iMxG1DNxWGGQg94AmKgqFTwLAiTiHrYiYxwHUB4DV8u5ahNEVtMMDm3sNSRdTg */ - result: string; - /** @enum {string} */ - type: "submitted"; - } | { - /** @example Jb2urXPyEh4xiBgzYvwEFe4q1iMxG1DNxWGGQg94AmKgqFTwLAiTiHrYiYxwHUB4DV8u5ahNEVtMMDm3sNSRdTg */ - result: string; - /** @enum {string} */ - type: "won"; - } | { - /** @example Jb2urXPyEh4xiBgzYvwEFe4q1iMxG1DNxWGGQg94AmKgqFTwLAiTiHrYiYxwHUB4DV8u5ahNEVtMMDm3sNSRdTg */ - result: string; - /** @enum {string} */ - type: "failed"; - } | { - /** @example Jb2urXPyEh4xiBgzYvwEFe4q1iMxG1DNxWGGQg94AmKgqFTwLAiTiHrYiYxwHUB4DV8u5ahNEVtMMDm3sNSRdTg */ - result: string; - /** @enum {string} */ - type: "expired"; - }; + BidStatus: + | components["schemas"]["BidStatusEvm"] + | components["schemas"]["BidStatusSvm"]; + BidStatusEvm: + | { + /** @enum {string} */ + type: "pending"; + } + | { + /** + * Format: int32 + * @example 1 + */ + index: number; + /** @example 0x103d4fbd777a36311b5161f2062490f761f25b67406badb2bace62bb170aa4e3 */ + result: string; + /** @enum {string} */ + type: "submitted"; + } + | { + /** + * Format: int32 + * @example 1 + */ + index?: number | null; + /** @example 0x103d4fbd777a36311b5161f2062490f761f25b67406badb2bace62bb170aa4e3 */ + result?: string | null; + /** @enum {string} */ + type: "lost"; + } + | { + /** + * Format: int32 + * @example 1 + */ + index: number; + /** @example 0x103d4fbd777a36311b5161f2062490f761f25b67406badb2bace62bb170aa4e3 */ + result: string; + /** @enum {string} */ + type: "won"; + }; + BidStatusSvm: + | { + /** @enum {string} */ + type: "pending"; + } + | { + /** @example Jb2urXPyEh4xiBgzYvwEFe4q1iMxG1DNxWGGQg94AmKgqFTwLAiTiHrYiYxwHUB4DV8u5ahNEVtMMDm3sNSRdTg */ + result?: string | null; + /** @enum {string} */ + type: "lost"; + } + | { + /** @example Jb2urXPyEh4xiBgzYvwEFe4q1iMxG1DNxWGGQg94AmKgqFTwLAiTiHrYiYxwHUB4DV8u5ahNEVtMMDm3sNSRdTg */ + result: string; + /** @enum {string} */ + type: "submitted"; + } + | { + /** @example Jb2urXPyEh4xiBgzYvwEFe4q1iMxG1DNxWGGQg94AmKgqFTwLAiTiHrYiYxwHUB4DV8u5ahNEVtMMDm3sNSRdTg */ + result: string; + /** @enum {string} */ + type: "won"; + } + | { + /** @example Jb2urXPyEh4xiBgzYvwEFe4q1iMxG1DNxWGGQg94AmKgqFTwLAiTiHrYiYxwHUB4DV8u5ahNEVtMMDm3sNSRdTg */ + result: string; + /** @enum {string} */ + type: "failed"; + } + | { + /** @example Jb2urXPyEh4xiBgzYvwEFe4q1iMxG1DNxWGGQg94AmKgqFTwLAiTiHrYiYxwHUB4DV8u5ahNEVtMMDm3sNSRdTg */ + result: string; + /** @enum {string} */ + type: "expired"; + }; BidStatusWithId: { bid_status: components["schemas"]["BidStatus"]; id: string; @@ -324,32 +339,36 @@ export interface components { Bids: { items: components["schemas"]["Bid"][]; }; - ClientMessage: { - /** @enum {string} */ - method: "subscribe"; - params: { - chain_ids: string[]; - }; - } | { - /** @enum {string} */ - method: "unsubscribe"; - params: { - chain_ids: string[]; - }; - } | { - /** @enum {string} */ - method: "post_bid"; - params: { - bid: components["schemas"]["BidCreate"]; - }; - } | { - /** @enum {string} */ - method: "post_opportunity_bid"; - params: { - opportunity_bid: components["schemas"]["OpportunityBidEvm"]; - opportunity_id: string; - }; - }; + ClientMessage: + | { + /** @enum {string} */ + method: "subscribe"; + params: { + chain_ids: string[]; + }; + } + | { + /** @enum {string} */ + method: "unsubscribe"; + params: { + chain_ids: string[]; + }; + } + | { + /** @enum {string} */ + method: "post_bid"; + params: { + bid: components["schemas"]["BidCreate"]; + }; + } + | { + /** @enum {string} */ + method: "post_opportunity_bid"; + params: { + opportunity_bid: components["schemas"]["OpportunityBidEvm"]; + opportunity_id: string; + }; + }; ClientRequest: components["schemas"]["ClientMessage"] & { id: string; }; @@ -358,7 +377,9 @@ export interface components { }; /** @enum {string} */ FeeToken: "input_token" | "output_token"; - Opportunity: components["schemas"]["OpportunityEvm"] | components["schemas"]["OpportunitySvm"]; + Opportunity: + | components["schemas"]["OpportunityEvm"] + | components["schemas"]["OpportunitySvm"]; OpportunityBidEvm: { /** * @description The bid amount in wei. @@ -398,40 +419,44 @@ export interface components { status: string; }; /** @description The input type for creating a new opportunity. */ - OpportunityCreate: components["schemas"]["OpportunityCreateEvm"] | components["schemas"]["OpportunityCreateSvm"]; + OpportunityCreate: + | components["schemas"]["OpportunityCreateEvm"] + | components["schemas"]["OpportunityCreateSvm"]; OpportunityCreateEvm: components["schemas"]["OpportunityCreateV1Evm"] & { /** @enum {string} */ version: "v1"; }; /** @description Program specific parameters for the opportunity. */ - OpportunityCreateProgramParamsV1Svm: { - /** - * @description The Limo order to be executed, encoded in base64. - * @example UxMUbQAsjrfQUp5stVwMJ6Mucq7VWTvt4ICe69BJ8lVXqwM+0sysV8OqZTdM0W4p... - */ - order: string; - /** - * @description Address of the order account. - * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 - */ - order_address: string; - /** @enum {string} */ - program: "limo"; - } | { - /** @enum {string} */ - program: "swap"; - /** - * Format: int32 - * @description The referral fee in basis points. - * @example 10 - */ - referral_fee_bps: number; - /** - * @description The user wallet address which requested the quote from the wallet. - * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 - */ - user_wallet_address: string; - }; + OpportunityCreateProgramParamsV1Svm: + | { + /** + * @description The Limo order to be executed, encoded in base64. + * @example UxMUbQAsjrfQUp5stVwMJ6Mucq7VWTvt4ICe69BJ8lVXqwM+0sysV8OqZTdM0W4p... + */ + order: string; + /** + * @description Address of the order account. + * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 + */ + order_address: string; + /** @enum {string} */ + program: "limo"; + } + | { + /** @enum {string} */ + program: "swap"; + /** + * Format: int32 + * @description The referral fee in basis points. + * @example 10 + */ + referral_fee_bps: number; + /** + * @description The user wallet address which requested the quote from the wallet. + * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 + */ + user_wallet_address: string; + }; OpportunityCreateSvm: components["schemas"]["OpportunityCreateV1Svm"] & { /** @enum {string} */ version: "v1"; @@ -475,34 +500,37 @@ export interface components { * @description Opportunity parameters needed for on-chain execution. * Parameters may differ for each program. */ - OpportunityCreateV1Svm: ({ - /** - * @description The Limo order to be executed, encoded in base64. - * @example UxMUbQAsjrfQUp5stVwMJ6Mucq7VWTvt4ICe69BJ8lVXqwM+0sysV8OqZTdM0W4p... - */ - order: string; - /** - * @description Address of the order account. - * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 - */ - order_address: string; - /** @enum {string} */ - program: "limo"; - } | { - /** @enum {string} */ - program: "swap"; - /** - * Format: int32 - * @description The referral fee in basis points. - * @example 10 - */ - referral_fee_bps: number; - /** - * @description The user wallet address which requested the quote from the wallet. - * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 - */ - user_wallet_address: string; - }) & { + OpportunityCreateV1Svm: ( + | { + /** + * @description The Limo order to be executed, encoded in base64. + * @example UxMUbQAsjrfQUp5stVwMJ6Mucq7VWTvt4ICe69BJ8lVXqwM+0sysV8OqZTdM0W4p... + */ + order: string; + /** + * @description Address of the order account. + * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 + */ + order_address: string; + /** @enum {string} */ + program: "limo"; + } + | { + /** @enum {string} */ + program: "swap"; + /** + * Format: int32 + * @description The referral fee in basis points. + * @example 10 + */ + referral_fee_bps: number; + /** + * @description The user wallet address which requested the quote from the wallet. + * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 + */ + user_wallet_address: string; + } + ) & { buy_tokens: components["schemas"]["TokenAmountSvm"][]; /** * @description The chain id where the opportunity will be executed. @@ -528,13 +556,15 @@ export interface components { slot: number; }; /** @description The input type for deleting opportunities. */ - OpportunityDelete: (components["schemas"]["OpportunityDeleteSvm"] & { - /** @enum {string} */ - chain_type: "svm"; - }) | (components["schemas"]["OpportunityDeleteEvm"] & { - /** @enum {string} */ - chain_type: "evm"; - }); + 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"; @@ -605,56 +635,61 @@ export interface components { * @description Opportunity parameters needed for on-chain execution. * Parameters may differ for each program. */ - OpportunityParamsV1Svm: ({ - /** - * @description The Limo order to be executed, encoded in base64. - * @example UxMUbQAsjrfQUp5stVwMJ6Mucq7VWTvt4ICe69BJ8lVXqwM+0sysV8OqZTdM0W4p... - */ - order: string; - /** - * @description Address of the order account. - * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 - */ - order_address: string; - /** @enum {string} */ - program: "limo"; - } | ({ - fee_token: components["schemas"]["FeeToken"]; - /** - * @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: "swap"; - /** - * Format: int32 - * @description The referral fee in basis points. - * @example 10 - */ - referral_fee_bps: number; - /** - * @description The router account to be used for the opportunity execution of the protocol. - * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 - */ - router_account: string; - tokens: { - input_token: components["schemas"]["TokenAmountSvm"]; - output_token: components["schemas"]["Pubkey"]; - /** @enum {string} */ - type: "input_token_specified"; - } | { - input_token: components["schemas"]["Pubkey"]; - output_token: components["schemas"]["TokenAmountSvm"]; - /** @enum {string} */ - type: "output_token_specified"; - }; - /** - * @description The user wallet address which requested the quote from the wallet. - * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 - */ - user_wallet_address: string; - })) & { + OpportunityParamsV1Svm: ( + | { + /** + * @description The Limo order to be executed, encoded in base64. + * @example UxMUbQAsjrfQUp5stVwMJ6Mucq7VWTvt4ICe69BJ8lVXqwM+0sysV8OqZTdM0W4p... + */ + order: string; + /** + * @description Address of the order account. + * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 + */ + order_address: string; + /** @enum {string} */ + program: "limo"; + } + | { + fee_token: components["schemas"]["FeeToken"]; + /** + * @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: "swap"; + /** + * Format: int32 + * @description The referral fee in basis points. + * @example 10 + */ + referral_fee_bps: number; + /** + * @description The router account to be used for the opportunity execution of the protocol. + * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 + */ + router_account: string; + tokens: + | { + input_token: components["schemas"]["TokenAmountSvm"]; + output_token: components["schemas"]["Pubkey"]; + /** @enum {string} */ + type: "input_token_specified"; + } + | { + input_token: components["schemas"]["Pubkey"]; + output_token: components["schemas"]["TokenAmountSvm"]; + /** @enum {string} */ + type: "output_token_specified"; + }; + /** + * @description The user wallet address which requested the quote from the wallet. + * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 + */ + user_wallet_address: string; + } + ) & { /** @example solana */ chain_id: string; }; @@ -718,23 +753,25 @@ export interface components { * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 */ router: string; - specified_token_amount: { - /** - * Format: int64 - * @example 100 - */ - amount: number; - /** @enum {string} */ - side: "input"; - } | { - /** - * Format: int64 - * @example 50 - */ - amount: number; - /** @enum {string} */ - side: "output"; - }; + specified_token_amount: + | { + /** + * Format: int64 + * @example 100 + */ + amount: number; + /** @enum {string} */ + side: "input"; + } + | { + /** + * Format: int64 + * @example 50 + */ + amount: number; + /** @enum {string} */ + side: "output"; + }; /** * @description The user wallet address which requested the quote from the wallet. * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 @@ -745,17 +782,19 @@ export interface components { /** @enum {string} */ version: "v1"; }; - QuoteTokens: { - input_token: components["schemas"]["TokenAmountSvm"]; - output_token: components["schemas"]["Pubkey"]; - /** @enum {string} */ - type: "input_token_specified"; - } | { - input_token: components["schemas"]["Pubkey"]; - output_token: components["schemas"]["TokenAmountSvm"]; - /** @enum {string} */ - type: "output_token_specified"; - }; + QuoteTokens: + | { + input_token: components["schemas"]["TokenAmountSvm"]; + output_token: components["schemas"]["Pubkey"]; + /** @enum {string} */ + type: "input_token_specified"; + } + | { + input_token: components["schemas"]["Pubkey"]; + output_token: components["schemas"]["TokenAmountSvm"]; + /** @enum {string} */ + type: "output_token_specified"; + }; QuoteV1Svm: { /** * @description The chain id for the quote. @@ -776,57 +815,65 @@ export interface components { */ transaction: string; }; - ServerResultMessage: ({ - result: components["schemas"]["APIResponse"] | null; - /** @enum {string} */ - status: "success"; - }) | { - result: string; - /** @enum {string} */ - status: "error"; - }; + ServerResultMessage: + | { + result: components["schemas"]["APIResponse"] | null; + /** @enum {string} */ + status: "success"; + } + | { + result: string; + /** @enum {string} */ + 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. */ - ServerResultResponse: components["schemas"]["ServerResultMessage"] & ({ + ServerResultResponse: components["schemas"]["ServerResultMessage"] & { id?: string | null; - }); - /** @description This enum is used to send an update to the client for any subscriptions made. */ - ServerUpdateResponse: { - opportunity: components["schemas"]["Opportunity"]; - /** @enum {string} */ - type: "new_opportunity"; - } | { - status: components["schemas"]["BidStatusWithId"]; - /** @enum {string} */ - type: "bid_status_update"; - } | { - /** @enum {string} */ - type: "svm_chain_update"; - update: components["schemas"]["SvmChainUpdate"]; - } | { - opportunity_delete: components["schemas"]["OpportunityDelete"]; - /** @enum {string} */ - type: "remove_opportunities"; - }; - SpecifiedTokenAmount: { - /** - * Format: int64 - * @example 100 - */ - amount: number; - /** @enum {string} */ - side: "input"; - } | { - /** - * Format: int64 - * @example 50 - */ - amount: number; - /** @enum {string} */ - side: "output"; }; + /** @description This enum is used to send an update to the client for any subscriptions made. */ + ServerUpdateResponse: + | { + opportunity: components["schemas"]["Opportunity"]; + /** @enum {string} */ + type: "new_opportunity"; + } + | { + status: components["schemas"]["BidStatusWithId"]; + /** @enum {string} */ + type: "bid_status_update"; + } + | { + /** @enum {string} */ + type: "svm_chain_update"; + update: components["schemas"]["SvmChainUpdate"]; + } + | { + opportunity_delete: components["schemas"]["OpportunityDelete"]; + /** @enum {string} */ + type: "remove_opportunities"; + }; + SpecifiedTokenAmount: + | { + /** + * Format: int64 + * @example 100 + */ + amount: number; + /** @enum {string} */ + side: "input"; + } + | { + /** + * Format: int64 + * @example 50 + */ + amount: number; + /** @enum {string} */ + side: "output"; + }; SvmChainUpdate: { /** @example SLxp9LxX1eE9Z5v99Y92DaYEwyukFgMUF6zRerCF12j */ blockhash: string; @@ -901,7 +948,9 @@ export interface components { }; Opportunity: { content: { - "application/json": components["schemas"]["OpportunityEvm"] | components["schemas"]["OpportunitySvm"]; + "application/json": + | components["schemas"]["OpportunityEvm"] + | components["schemas"]["OpportunitySvm"]; }; }; }; @@ -916,7 +965,6 @@ export type $defs = Record; export type external = Record; export interface operations { - /** * Returns at most 20 bids which were submitted after a specific time. * @deprecated diff --git a/sdk/js/src/svm.ts b/sdk/js/src/svm.ts index 914a9189..519dbdd4 100644 --- a/sdk/js/src/svm.ts +++ b/sdk/js/src/svm.ts @@ -6,7 +6,12 @@ import { TransactionInstruction, } from "@solana/web3.js"; import * as anchor from "@coral-xyz/anchor"; -import {BidSvm, BidSvmOnChain, BidSvmSwap, ExpressRelaySvmConfig, OpportunitySvmSwap} from "./types"; +import { + BidSvmOnChain, + BidSvmSwap, + ExpressRelaySvmConfig, + OpportunitySvmSwap, +} from "./types"; import { ASSOCIATED_TOKEN_PROGRAM_ID, createAssociatedTokenAccountIdempotentInstruction, @@ -96,7 +101,7 @@ export function getAssociatedTokenAddress( return getAssociatedTokenAddressSync( tokenMintAddress, owner, - true, //allow owner to be off-curve + true, //allow owner to be off-curve tokenProgram, ASSOCIATED_TOKEN_PROGRAM_ID ); @@ -109,14 +114,15 @@ export function createAtaIdempotentInstruction( tokenProgram: PublicKey ): [PublicKey, TransactionInstruction] { const ataAddress = getAssociatedTokenAddress(owner, mint, tokenProgram); - const createUserTokenAccountIx = createAssociatedTokenAccountIdempotentInstruction( - payer, - ataAddress, - owner, - mint, - tokenProgram, - ASSOCIATED_TOKEN_PROGRAM_ID - ); + const createUserTokenAccountIx = + createAssociatedTokenAccountIdempotentInstruction( + payer, + ataAddress, + owner, + mint, + tokenProgram, + ASSOCIATED_TOKEN_PROGRAM_ID + ); return [ataAddress, createUserTokenAccountIx]; } @@ -162,7 +168,10 @@ export async function constructSwapBid( : bidAmount, referralFeeBps: new anchor.BN(swapOpportunity.referralFeeBps), deadline, - feeToken: swapOpportunity.feeToken === 'input_token' ? {input: {}} : {output: {}}, + feeToken: + swapOpportunity.feeToken === "input_token" + ? { input: {} } + : { output: {} }, }; const ixSwap = await expressRelay.methods .swap(swapArgs) @@ -215,7 +224,7 @@ export async function constructSwapBid( .instruction(); ixSwap.programId = svmConstants.expressRelayProgram; tx.instructions.push( - createAtaIdempotentInstruction( + createAtaIdempotentInstruction( router, mintFee, searcher, @@ -223,7 +232,7 @@ export async function constructSwapBid( )[1] ); tx.instructions.push( - createAtaIdempotentInstruction( + createAtaIdempotentInstruction( relayerSigner, mintFee, searcher, @@ -231,7 +240,7 @@ export async function constructSwapBid( )[1] ); tx.instructions.push( - createAtaIdempotentInstruction( + createAtaIdempotentInstruction( expressRelayMetadata, mintFee, searcher, @@ -239,7 +248,7 @@ export async function constructSwapBid( )[1] ); tx.instructions.push( - createAtaIdempotentInstruction( + createAtaIdempotentInstruction( trader, mintOutput, searcher, @@ -251,7 +260,7 @@ export async function constructSwapBid( return { transaction: tx, opportunityId: swapOpportunity.opportunityId, - type:"swap", + type: "swap", chainId: chainId, env: "svm", }; @@ -284,7 +293,7 @@ export async function constructSvmBid( return { transaction: tx, chainId: chainId, - type:"onchain", + type: "onchain", env: "svm", }; } diff --git a/sdk/js/src/types.ts b/sdk/js/src/types.ts index efc12652..52ba81f4 100644 --- a/sdk/js/src/types.ts +++ b/sdk/js/src/types.ts @@ -253,7 +253,7 @@ export type BidSvmOnChain = { * @example 293106477 */ slot?: number | null; - type:"onchain"; + type: "onchain"; /** * @description The execution environment for the bid. */ @@ -279,7 +279,7 @@ export type BidSvmSwap = { * @example obo3ee3e-58cc-4372-a567-0e02b2c3d479 */ opportunityId: string; - type:"swap"; + type: "swap"; /** * @description The execution environment for the bid. */ From 3058548ccbc187511fda131d5453b3063255946d Mon Sep 17 00:00:00 2001 From: Amin Moghaddam Date: Wed, 15 Jan 2025 12:09:39 -0500 Subject: [PATCH 07/25] fix --- .../src/auction/service/verification.rs | 22 ++++++++++--------- .../src/opportunity/service/get_quote.rs | 2 +- sdk/js/src/svm.ts | 3 ++- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/auction-server/src/auction/service/verification.rs b/auction-server/src/auction/service/verification.rs index f8fc8951..d3150a7b 100644 --- a/auction-server/src/auction/service/verification.rs +++ b/auction-server/src/auction/service/verification.rs @@ -420,7 +420,7 @@ impl Service { .map_err(|e| RestError::BadParameters(format!("Invalid swap instruction data: {}", e))) } - pub fn extract_express_relay_bid_instruction( + pub fn extract_express_relay_instruction( &self, transaction: VersionedTransaction, instruction_type: BidPaymentInstructionType, @@ -437,19 +437,21 @@ impl Service { .iter() .filter(|instruction| { let program_id = instruction.program_id(transaction.message.static_account_keys()); - if *program_id != self.config.chain_config.express_relay.program_id { - return false; - } - - instruction.data.starts_with(&discriminator) + *program_id == self.config.chain_config.express_relay.program_id }) .cloned() .collect::>(); - match instructions.len() { + let instruction = match instructions.len() { 1 => Ok(instructions[0].clone()), - _ => Err(RestError::BadParameters(format!("Bid must include exactly one instruction to Express Relay program that pays the bid but found {} instructions", instructions.len()))), + _ => Err(RestError::BadParameters(format!("Bid must include exactly one instruction to Express Relay program but found {} instructions", instructions.len()))), + }?; + if !instruction.data.starts_with(&discriminator) { + return Err(RestError::BadParameters( + "Wrong instruction type for Express Relay Program".to_string(), + )); } + Ok(instruction) } async fn check_deadline( @@ -489,7 +491,7 @@ impl Service { let svm_config = &self.config.chain_config.express_relay; match bid_chain_data_create_svm { BidChainDataCreateSvm::OnChain(bid_data) => { - let submit_bid_instruction = self.extract_express_relay_bid_instruction( + let submit_bid_instruction = self.extract_express_relay_instruction( bid_data.transaction.clone(), BidPaymentInstructionType::SubmitBid, )?; @@ -524,7 +526,7 @@ impl Service { }) } BidChainDataCreateSvm::Swap(bid_data) => { - let swap_instruction = self.extract_express_relay_bid_instruction( + let swap_instruction = self.extract_express_relay_instruction( bid_data.transaction.clone(), BidPaymentInstructionType::Swap, )?; diff --git a/auction-server/src/opportunity/service/get_quote.rs b/auction-server/src/opportunity/service/get_quote.rs index 6e00daf3..d2593ea6 100644 --- a/auction-server/src/opportunity/service/get_quote.rs +++ b/auction-server/src/opportunity/service/get_quote.rs @@ -256,7 +256,7 @@ impl Service { let winner_bid = bids.first().expect("failed to get first bid"); let swap_instruction = auction_service - .extract_express_relay_bid_instruction( + .extract_express_relay_instruction( winner_bid.chain_data.transaction.clone(), BidPaymentInstructionType::Swap, ) diff --git a/sdk/js/src/svm.ts b/sdk/js/src/svm.ts index 519dbdd4..c2ed18ee 100644 --- a/sdk/js/src/svm.ts +++ b/sdk/js/src/svm.ts @@ -154,7 +154,8 @@ export async function constructSwapBid( ? swapOpportunity.tokens.outputToken.token : swapOpportunity.tokens.outputToken; const trader = swapOpportunity.userWalletAddress; - const mintFee = mintInput; + const mintFee = + swapOpportunity.feeToken === "input_token" ? mintInput : mintOutput; const router = swapOpportunity.routerAccount; const swapArgs = { From fcfabc1142c632053547ecc833fb699a638adaa2 Mon Sep 17 00:00:00 2001 From: Amin Moghaddam Date: Wed, 15 Jan 2025 13:03:10 -0500 Subject: [PATCH 08/25] better naming --- sdk/js/package.json | 4 ++-- .../{simpleSearcherLimo.ts => simpleSearcherSvm.ts} | 4 ++-- sdk/js/src/examples/testingSearcherPing.ts | 8 ++------ .../{testingSearcherLimo.ts => testingSearcherSvm.ts} | 8 ++------ 4 files changed, 8 insertions(+), 16 deletions(-) rename sdk/js/src/examples/{simpleSearcherLimo.ts => simpleSearcherSvm.ts} (99%) rename sdk/js/src/examples/{testingSearcherLimo.ts => testingSearcherSvm.ts} (94%) diff --git a/sdk/js/package.json b/sdk/js/package.json index d081240a..dd026817 100644 --- a/sdk/js/package.json +++ b/sdk/js/package.json @@ -16,8 +16,8 @@ "build": "tsc", "test": "jest src/ --passWithNoTests", "simple-searcher-evm": "pnpm build && node lib/examples/simpleSearcherEvm.js", - "simple-searcher-limo": "pnpm build && node lib/examples/simpleSearcherLimo.js", - "testing-searcher-limo": "pnpm build && node lib/examples/testingSearcherLimo.js", + "simple-searcher-limo": "pnpm build && node lib/examples/simpleSearcherSvm.js", + "testing-searcher-limo": "pnpm build && node lib/examples/testingSearcherSvm.js", "testing-searcher-ping": "pnpm build && node lib/examples/testingSearcherPing.js", "generate-api-types": "openapi-typescript http://127.0.0.1:9000/docs/openapi.json --output src/serverTypes.d.ts", "generate-anchor-types": "anchor idl type src/idl/idlExpressRelay.json --out src/expressRelayTypes.d.ts && anchor idl type src/examples/idl/idlDummy.json --out src/examples/dummyTypes.d.ts", diff --git a/sdk/js/src/examples/simpleSearcherLimo.ts b/sdk/js/src/examples/simpleSearcherSvm.ts similarity index 99% rename from sdk/js/src/examples/simpleSearcherLimo.ts rename to sdk/js/src/examples/simpleSearcherSvm.ts index 73179bea..55f58ee2 100644 --- a/sdk/js/src/examples/simpleSearcherLimo.ts +++ b/sdk/js/src/examples/simpleSearcherSvm.ts @@ -37,7 +37,7 @@ import { constructSwapBid } from "../svm"; const DAY_IN_SECONDS = 60 * 60 * 24; -export class SimpleSearcherLimo { +export class SimpleSearcherSvm { protected client: Client; protected readonly connectionSvm: Connection; protected mintDecimals: Record = {}; @@ -373,7 +373,7 @@ export function getKeypair( async function run() { const argv = makeParser().parseSync(); const searcherKeyPair = getKeypair(argv.privateKey, argv.privateKeyJsonFile); - const simpleSearcher = new SimpleSearcherLimo( + const simpleSearcher = new SimpleSearcherSvm( argv.endpointExpressRelay, argv.chainId, searcherKeyPair, diff --git a/sdk/js/src/examples/testingSearcherPing.ts b/sdk/js/src/examples/testingSearcherPing.ts index daa102ef..b13af5e2 100644 --- a/sdk/js/src/examples/testingSearcherPing.ts +++ b/sdk/js/src/examples/testingSearcherPing.ts @@ -1,12 +1,8 @@ import * as anchor from "@coral-xyz/anchor"; import { ComputeBudgetProgram, Keypair } from "@solana/web3.js"; -import { - getKeypair, - makeParser, - SimpleSearcherLimo, -} from "./simpleSearcherLimo"; +import { getKeypair, makeParser, SimpleSearcherSvm } from "./simpleSearcherSvm"; -class SearcherPinger extends SimpleSearcherLimo { +class SearcherPinger extends SimpleSearcherSvm { constructor( endpointExpressRelay: string, chainId: string, diff --git a/sdk/js/src/examples/testingSearcherLimo.ts b/sdk/js/src/examples/testingSearcherSvm.ts similarity index 94% rename from sdk/js/src/examples/testingSearcherLimo.ts rename to sdk/js/src/examples/testingSearcherSvm.ts index 21e92b53..bd594f15 100644 --- a/sdk/js/src/examples/testingSearcherLimo.ts +++ b/sdk/js/src/examples/testingSearcherSvm.ts @@ -3,13 +3,9 @@ import { Opportunity } from "../index"; import * as anchor from "@coral-xyz/anchor"; import { Keypair } from "@solana/web3.js"; import { OrderStateAndAddress } from "@kamino-finance/limo-sdk/dist/utils"; -import { - getKeypair, - makeParser, - SimpleSearcherLimo, -} from "./simpleSearcherLimo"; +import { getKeypair, makeParser, SimpleSearcherSvm } from "./simpleSearcherSvm"; -class SearcherLimo extends SimpleSearcherLimo { +class SearcherLimo extends SimpleSearcherSvm { private readonly fillRate: anchor.BN; constructor( From a11f4fa54afcb461719ceae215a3097f7b797ce3 Mon Sep 17 00:00:00 2001 From: Amin Moghaddam Date: Wed, 15 Jan 2025 13:27:41 -0500 Subject: [PATCH 09/25] rename envs and more structure --- auction-server/build.rs | 6 +- auction-server/src/auction/service/mod.rs | 24 ++++-- .../src/auction/service/verification.rs | 23 ++++-- auction-server/src/server.rs | 80 ++++++++++--------- 4 files changed, 79 insertions(+), 54 deletions(-) diff --git a/auction-server/build.rs b/auction-server/build.rs index 9519d8fc..b40677ef 100644 --- a/auction-server/build.rs +++ b/auction-server/build.rs @@ -41,7 +41,7 @@ const SUBMIT_BID_PERMISSION_ACCOUNT_SVM: &str = "permission"; const SUBMIT_BID_ROUTER_ACCOUNT_SVM: &str = "router"; const SWAP_INSTRUCTION_SVM: &str = "swap"; -const SWAP_ROUTER_ACCOUNT_SVM: &str = "router_fee_receiver_ta"; +const SWAP_ROUTER_TOKEN_ACCOUNT_SVM: &str = "router_fee_receiver_ta"; const SWAP_USER_WALLET_ACCOUNT_SVM: &str = "trader"; const SWAP_MINT_INPUT_ACCOUNT_SVM: &str = "mint_input"; const SWAP_MINT_OUTPUT_ACCOUNT_SVM: &str = "mint_output"; @@ -89,11 +89,11 @@ fn verify_and_extract_idl_data() { ) ); println!( - "cargo::rustc-env=SWAP_ROUTER_ACCOUNT_POSITION={}", + "cargo::rustc-env=SWAP_ROUTER_TOKEN_ACCOUNT_POSITION={}", extract_account_position( express_relay_idl.clone(), SWAP_INSTRUCTION_SVM, - SWAP_ROUTER_ACCOUNT_SVM, + SWAP_ROUTER_TOKEN_ACCOUNT_SVM, ) ); println!( diff --git a/auction-server/src/auction/service/mod.rs b/auction-server/src/auction/service/mod.rs index 95990c50..a7b2f605 100644 --- a/auction-server/src/auction/service/mod.rs +++ b/auction-server/src/auction/service/mod.rs @@ -84,15 +84,23 @@ pub mod update_submitted_auction; pub mod verification; pub mod workers; +pub struct SwapInstructionAccountPositions { + pub router_token_account: usize, + pub user_wallet_account: usize, + pub mint_input_account: usize, + pub mint_output_account: usize, +} + +pub struct SubmitBidInstructionAccountPositions { + pub permission_account: usize, + pub router_account: usize, +} + pub struct ExpressRelaySvm { - pub program_id: Pubkey, - pub relayer: Keypair, - pub permission_account_position_submit_bid: usize, - pub router_account_position_submit_bid: usize, - pub router_account_position_swap: usize, - pub user_wallet_account_position_swap: usize, - pub mint_input_account_position_swap: usize, - pub mint_output_account_position_swap: usize, + pub program_id: Pubkey, + pub relayer: Keypair, + pub submit_bid_instruction_account_positions: SubmitBidInstructionAccountPositions, + pub swap_instruction_account_positions: SwapInstructionAccountPositions, } pub struct ConfigSvm { diff --git a/auction-server/src/auction/service/verification.rs b/auction-server/src/auction/service/verification.rs index d3150a7b..a4ac73ae 100644 --- a/auction-server/src/auction/service/verification.rs +++ b/auction-server/src/auction/service/verification.rs @@ -501,14 +501,18 @@ impl Service { .extract_account( &bid_data.transaction, &submit_bid_instruction, - svm_config.permission_account_position_submit_bid, + svm_config + .submit_bid_instruction_account_positions + .permission_account, ) .await?; let router = self .extract_account( &bid_data.transaction, &submit_bid_instruction, - svm_config.router_account_position_submit_bid, + svm_config + .submit_bid_instruction_account_positions + .router_account, ) .await?; Ok(BidDataSvm { @@ -536,7 +540,9 @@ impl Service { .extract_account( &bid_data.transaction, &swap_instruction, - svm_config.user_wallet_account_position_swap, + svm_config + .swap_instruction_account_positions + .user_wallet_account, ) .await?; @@ -544,14 +550,18 @@ impl Service { .extract_account( &bid_data.transaction, &swap_instruction, - svm_config.mint_input_account_position_swap, + svm_config + .swap_instruction_account_positions + .mint_input_account, ) .await?; let mint_output = self .extract_account( &bid_data.transaction, &swap_instruction, - svm_config.mint_output_account_position_swap, + svm_config + .swap_instruction_account_positions + .mint_output_account, ) .await?; @@ -599,7 +609,8 @@ impl Service { self.config .chain_config .express_relay - .router_account_position_swap, + .swap_instruction_account_positions + .router_token_account, ) .await?; diff --git a/auction-server/src/server.rs b/auction-server/src/server.rs index fe7d0653..b5515e55 100644 --- a/auction-server/src/server.rs +++ b/auction-server/src/server.rs @@ -7,6 +7,8 @@ use { auction::service::{ self as auction_service, simulator::Simulator, + SubmitBidInstructionAccountPositions, + SwapInstructionAccountPositions, }, config::{ ChainId, @@ -198,6 +200,35 @@ pub async fn run_migrations(migrate_options: MigrateOptions) -> Result<()> { Ok(()) } +macro_rules! read_svm_position_env { + ($name:expr) => {{ + // Access the environment variable at compile-time + let value = env!($name); // We expect $name to be a string literal + + // Parse the value to usize + value.parse::().expect(&format!( + "Failed to parse the environment variable {:?} as usize", + $name + )) + }}; +} + +fn get_swap_instruction_account_positions() -> SwapInstructionAccountPositions { + SwapInstructionAccountPositions { + router_token_account: read_svm_position_env!("SWAP_ROUTER_TOKEN_ACCOUNT_POSITION"), + user_wallet_account: read_svm_position_env!("SWAP_USER_WALLET_ACCOUNT_POSITION"), + mint_input_account: read_svm_position_env!("SWAP_MINT_INPUT_ACCOUNT_POSITION"), + mint_output_account: read_svm_position_env!("SWAP_MINT_OUTPUT_ACCOUNT_POSITION"), + } +} + +fn get_submit_bid_instruction_account_positions() -> SubmitBidInstructionAccountPositions { + SubmitBidInstructionAccountPositions { + permission_account: read_svm_position_env!("SUBMIT_BID_PERMISSION_ACCOUNT_POSITION"), + router_account: read_svm_position_env!("SUBMIT_BID_ROUTER_ACCOUNT_POSITION"), + } +} + pub async fn start_server(run_options: RunOptions) -> Result<()> { tokio::spawn(async move { tracing::info!("Registered shutdown signal handler..."); @@ -320,45 +351,20 @@ pub async fn start_server(run_options: RunOptions) -> Result<()> { ), ), express_relay: auction_service::ExpressRelaySvm { - program_id: chain_store + program_id: chain_store .config .express_relay_program_id, - relayer: Keypair::from_base58_string( - &run_options - .private_key_svm - .clone() - .expect("No svm private key provided for chain"), - ), - permission_account_position_submit_bid: env!( - "SUBMIT_BID_PERMISSION_ACCOUNT_POSITION" - ) - .parse::() - .expect("Failed to parse permission account position"), - router_account_position_submit_bid: env!( - "SUBMIT_BID_ROUTER_ACCOUNT_POSITION" - ) - .parse::() - .expect("Failed to parse (submit bid) router account position"), - router_account_position_swap: env!( - "SWAP_ROUTER_ACCOUNT_POSITION" - ) - .parse::() - .expect("Failed to parse (swap) router account position"), - user_wallet_account_position_swap: env!( - "SWAP_USER_WALLET_ACCOUNT_POSITION" - ) - .parse::() - .expect("Failed to parse user wallet account position"), - mint_input_account_position_swap: env!( - "SWAP_MINT_INPUT_ACCOUNT_POSITION" - ) - .parse::() - .expect("Failed to parse user wallet account position"), - mint_output_account_position_swap: env!( - "SWAP_MINT_OUTPUT_ACCOUNT_POSITION" - ) - .parse::() - .expect("Failed to parse user wallet account position"), + relayer: + Keypair::from_base58_string( + &run_options + .private_key_svm + .clone() + .expect("No svm private key provided for chain"), + ), + submit_bid_instruction_account_positions: + get_submit_bid_instruction_account_positions(), + swap_instruction_account_positions: + get_swap_instruction_account_positions(), }, ws_address: chain_store.config.ws_addr.clone(), tx_broadcaster_client: TracedSenderSvm::new_client( From 9725a2b9248bf284242531fcaa3e8f893b08d34f Mon Sep 17 00:00:00 2001 From: Amin Moghaddam Date: Thu, 16 Jan 2025 07:23:05 -0500 Subject: [PATCH 10/25] Better error messages --- .../src/auction/service/verification.rs | 36 ++++++++++++++++++- .../opportunity/entities/opportunity_svm.rs | 10 ++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/auction-server/src/auction/service/verification.rs b/auction-server/src/auction/service/verification.rs index a4ac73ae..6688c366 100644 --- a/auction-server/src/auction/service/verification.rs +++ b/auction-server/src/auction/service/verification.rs @@ -33,6 +33,7 @@ use { opportunity::{ self as opportunity, entities::{ + OpportunitySvmProgram::SwapKamino, QuoteTokens, TokenAmountSvm, }, @@ -602,6 +603,39 @@ impl Service { "No swap opportunity with the given id found".to_string(), ))?; + if let SwapKamino(opp_swap_data) = opp.program { + if swap_data.referral_fee_bps != opp_swap_data.referral_fee_bps { + return Err(RestError::BadParameters( + format!( + "Referral fee bps in swap opportunity {} does not match the referral fee bps in the swap instruction {}", + opp_swap_data.referral_fee_bps, swap_data.referral_fee_bps + ), + )); + } + if user_wallet != opp_swap_data.user_wallet_address { + return Err(RestError::BadParameters( + format!( + "User wallet address in swap opportunity {} does not match the user wallet address in the swap instruction {}", + opp_swap_data.user_wallet_address, user_wallet + ), + )); + } + if opp_swap_data.fee_token != swap_data.fee_token { + return Err(RestError::BadParameters( + format!( + "Fee token in swap opportunity {:?} does not match the fee token in the swap instruction {:?}", + opp_swap_data.fee_token, swap_data.fee_token + ), + )); + } + } else { + return Err(RestError::BadParameters(format!( + "Opportunity with id {} is not a swap opportunity", + bid_data.opportunity_id + ))); + } + + let router_fee_receiver_ta = self .extract_account( &bid_data.transaction, @@ -643,7 +677,7 @@ impl Service { if router_fee_receiver_ta != expected_router_fee_receiver_ta { return Err(RestError::BadParameters( - "Must use approved router token account for swap instruction".to_string(), + format!("Associated token account for router does not match. Expected: {:?} found: {:?}", expected_router_fee_receiver_ta, router_fee_receiver_ta), )); } diff --git a/auction-server/src/opportunity/entities/opportunity_svm.rs b/auction-server/src/opportunity/entities/opportunity_svm.rs index ac85fbad..e1214ec1 100644 --- a/auction-server/src/opportunity/entities/opportunity_svm.rs +++ b/auction-server/src/opportunity/entities/opportunity_svm.rs @@ -14,6 +14,7 @@ use { kernel::entities::PermissionKey, opportunity::repository, }, + ::express_relay::FeeToken as ProgramFeeToken, express_relay_api_types::opportunity as api, serde::{ Deserialize, @@ -42,6 +43,15 @@ pub enum FeeToken { OutputToken, } +impl PartialEq for FeeToken { + fn eq(&self, other: &ProgramFeeToken) -> bool { + match self { + FeeToken::InputToken => matches!(other, ProgramFeeToken::Input), + FeeToken::OutputToken => matches!(other, ProgramFeeToken::Output), + } + } +} + #[derive(Debug, Clone, PartialEq)] pub struct OpportunitySvmProgramSwap { pub user_wallet_address: Pubkey, From 9b6a5787eb7d3b69b299e859e1fcec8fe929519e Mon Sep 17 00:00:00 2001 From: Amin Moghaddam Date: Thu, 16 Jan 2025 13:08:21 -0500 Subject: [PATCH 11/25] rename scripts --- Tiltfile | 2 +- sdk/js/package.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Tiltfile b/Tiltfile index 81745ff3..e0c60d8c 100644 --- a/Tiltfile +++ b/Tiltfile @@ -218,7 +218,7 @@ local_resource( local_resource( "svm-searcher-js", - serve_cmd="pnpm run testing-searcher-limo --endpoint-express-relay http://127.0.0.1:9000 --chain-id development-solana --private-key-json-file ../../keypairs/searcher_js.json --endpoint-svm http://127.0.0.1:8899 --bid 10000000 --fill-rate 4 --bid-margin 100", + serve_cmd="pnpm run testing-searcher-svm --endpoint-express-relay http://127.0.0.1:9000 --chain-id development-solana --private-key-json-file ../../keypairs/searcher_js.json --endpoint-svm http://127.0.0.1:8899 --bid 10000000 --fill-rate 4 --bid-margin 100", serve_dir="sdk/js", resource_deps=["svm-initialize-programs", "auction-server"], ) diff --git a/sdk/js/package.json b/sdk/js/package.json index dd026817..cd8d2ca4 100644 --- a/sdk/js/package.json +++ b/sdk/js/package.json @@ -16,8 +16,8 @@ "build": "tsc", "test": "jest src/ --passWithNoTests", "simple-searcher-evm": "pnpm build && node lib/examples/simpleSearcherEvm.js", - "simple-searcher-limo": "pnpm build && node lib/examples/simpleSearcherSvm.js", - "testing-searcher-limo": "pnpm build && node lib/examples/testingSearcherSvm.js", + "simple-searcher-svm": "pnpm build && node lib/examples/simpleSearcherSvm.js", + "testing-searcher-svm": "pnpm build && node lib/examples/testingSearcherSvm.js", "testing-searcher-ping": "pnpm build && node lib/examples/testingSearcherPing.js", "generate-api-types": "openapi-typescript http://127.0.0.1:9000/docs/openapi.json --output src/serverTypes.d.ts", "generate-anchor-types": "anchor idl type src/idl/idlExpressRelay.json --out src/expressRelayTypes.d.ts && anchor idl type src/examples/idl/idlDummy.json --out src/examples/dummyTypes.d.ts", From becaae65fa4a0005035227a496635dc99573bd9f Mon Sep 17 00:00:00 2001 From: Amin Moghaddam Date: Thu, 16 Jan 2025 13:22:08 -0500 Subject: [PATCH 12/25] Use mint token programs received from the server --- sdk/js/src/index.ts | 12 ++++++++ sdk/js/src/serverTypes.d.ts | 28 ++++++++++++++++++ sdk/js/src/svm.ts | 58 ++++++++++++++++++------------------- sdk/js/src/types.ts | 8 +++-- 4 files changed, 75 insertions(+), 31 deletions(-) diff --git a/sdk/js/src/index.ts b/sdk/js/src/index.ts index aad37b84..9191a1da 100644 --- a/sdk/js/src/index.ts +++ b/sdk/js/src/index.ts @@ -618,6 +618,12 @@ export class Client { token: new PublicKey(opportunity.tokens.input_token.token), }, outputToken: new PublicKey(opportunity.tokens.output_token), + inputTokenProgram: new PublicKey( + opportunity.tokens.input_token_program + ), + outputTokenProgram: new PublicKey( + opportunity.tokens.output_token_program + ), } as const; } else { tokens = { @@ -627,6 +633,12 @@ export class Client { amount: BigInt(opportunity.tokens.output_token.amount), token: new PublicKey(opportunity.tokens.output_token.token), }, + inputTokenProgram: new PublicKey( + opportunity.tokens.input_token_program + ), + outputTokenProgram: new PublicKey( + opportunity.tokens.output_token_program + ), } as const; } return { diff --git a/sdk/js/src/serverTypes.d.ts b/sdk/js/src/serverTypes.d.ts index ec7c6c05..97418e05 100644 --- a/sdk/js/src/serverTypes.d.ts +++ b/sdk/js/src/serverTypes.d.ts @@ -443,6 +443,16 @@ export interface components { program: "limo"; } | { + /** + * @description The token program of the input mint. + * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 + */ + input_token_program: string; + /** + * @description The token program of the output mint. + * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 + */ + output_token_program: string; /** @enum {string} */ program: "swap"; /** @@ -516,6 +526,16 @@ export interface components { program: "limo"; } | { + /** + * @description The token program of the input mint. + * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 + */ + input_token_program: string; + /** + * @description The token program of the output mint. + * @example DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5 + */ + output_token_program: string; /** @enum {string} */ program: "swap"; /** @@ -673,13 +693,17 @@ export interface components { tokens: | { input_token: components["schemas"]["TokenAmountSvm"]; + input_token_program: components["schemas"]["Pubkey"]; output_token: components["schemas"]["Pubkey"]; + output_token_program: components["schemas"]["Pubkey"]; /** @enum {string} */ type: "input_token_specified"; } | { input_token: components["schemas"]["Pubkey"]; + input_token_program: components["schemas"]["Pubkey"]; output_token: components["schemas"]["TokenAmountSvm"]; + output_token_program: components["schemas"]["Pubkey"]; /** @enum {string} */ type: "output_token_specified"; }; @@ -785,13 +809,17 @@ export interface components { QuoteTokens: | { input_token: components["schemas"]["TokenAmountSvm"]; + input_token_program: components["schemas"]["Pubkey"]; output_token: components["schemas"]["Pubkey"]; + output_token_program: components["schemas"]["Pubkey"]; /** @enum {string} */ type: "input_token_specified"; } | { input_token: components["schemas"]["Pubkey"]; + input_token_program: components["schemas"]["Pubkey"]; output_token: components["schemas"]["TokenAmountSvm"]; + output_token_program: components["schemas"]["Pubkey"]; /** @enum {string} */ type: "output_token_specified"; }; diff --git a/sdk/js/src/svm.ts b/sdk/js/src/svm.ts index c2ed18ee..b86d7374 100644 --- a/sdk/js/src/svm.ts +++ b/sdk/js/src/svm.ts @@ -16,7 +16,6 @@ import { ASSOCIATED_TOKEN_PROGRAM_ID, createAssociatedTokenAccountIdempotentInstruction, getAssociatedTokenAddressSync, - TOKEN_PROGRAM_ID, } from "@solana/spl-token"; import { AnchorProvider, Program } from "@coral-xyz/anchor"; import { ExpressRelay } from "./expressRelayTypes"; @@ -142,20 +141,21 @@ export async function constructSwapBid( const expressRelayMetadata = getExpressRelayMetadataPda(chainId); const svmConstants = SVM_CONSTANTS[chainId]; - const tokenProgramInput = TOKEN_PROGRAM_ID; - const tokenProgramOutput = TOKEN_PROGRAM_ID; - const tokenProgramFee = TOKEN_PROGRAM_ID; - const mintInput = + const inputTokenProgram = swapOpportunity.tokens.inputTokenProgram; + const outputTokenProgram = swapOpportunity.tokens.outputTokenProgram; + const inputToken = swapOpportunity.tokens.type === "input_specified" ? swapOpportunity.tokens.inputToken.token : swapOpportunity.tokens.inputToken; - const mintOutput = + const outputToken = swapOpportunity.tokens.type === "output_specified" ? swapOpportunity.tokens.outputToken.token : swapOpportunity.tokens.outputToken; const trader = swapOpportunity.userWalletAddress; - const mintFee = - swapOpportunity.feeToken === "input_token" ? mintInput : mintOutput; + const [mintFee, feeTokenProgram] = + swapOpportunity.feeToken === "input_token" + ? [inputToken, inputTokenProgram] + : [outputToken, outputTokenProgram]; const router = swapOpportunity.routerAccount; const swapArgs = { @@ -182,44 +182,44 @@ export async function constructSwapBid( trader: swapOpportunity.userWalletAddress, searcherInputTa: getAssociatedTokenAddress( searcher, - mintInput, - tokenProgramInput + inputToken, + inputTokenProgram ), searcherOutputTa: getAssociatedTokenAddress( searcher, - mintOutput, - tokenProgramOutput + outputToken, + outputTokenProgram ), traderInputAta: getAssociatedTokenAddress( trader, - mintInput, - tokenProgramInput + inputToken, + inputTokenProgram ), - tokenProgramInput, - mintInput, + tokenProgramInput: inputTokenProgram, + mintInput: inputToken, traderOutputAta: getAssociatedTokenAddress( trader, - mintOutput, - tokenProgramOutput + outputToken, + outputTokenProgram ), - tokenProgramOutput, - mintOutput, + tokenProgramOutput: outputTokenProgram, + mintOutput: outputToken, routerFeeReceiverTa: getAssociatedTokenAddress( router, mintFee, - tokenProgramFee + feeTokenProgram ), relayerFeeReceiverAta: getAssociatedTokenAddress( relayerSigner, mintFee, - tokenProgramFee + feeTokenProgram ), - tokenProgramFee, + tokenProgramFee: feeTokenProgram, mintFee, expressRelayFeeReceiverAta: getAssociatedTokenAddress( expressRelayMetadata, mintFee, - tokenProgramFee + feeTokenProgram ), }) .instruction(); @@ -229,7 +229,7 @@ export async function constructSwapBid( router, mintFee, searcher, - tokenProgramFee + feeTokenProgram )[1] ); tx.instructions.push( @@ -237,7 +237,7 @@ export async function constructSwapBid( relayerSigner, mintFee, searcher, - tokenProgramFee + feeTokenProgram )[1] ); tx.instructions.push( @@ -245,15 +245,15 @@ export async function constructSwapBid( expressRelayMetadata, mintFee, searcher, - tokenProgramFee + feeTokenProgram )[1] ); tx.instructions.push( createAtaIdempotentInstruction( trader, - mintOutput, + outputToken, searcher, - tokenProgramOutput + outputTokenProgram )[1] ); tx.instructions.push(ixSwap); diff --git a/sdk/js/src/types.ts b/sdk/js/src/types.ts index 52ba81f4..4b90f78f 100644 --- a/sdk/js/src/types.ts +++ b/sdk/js/src/types.ts @@ -134,7 +134,7 @@ export type OpportunitySvmSwap = { feeToken: "input_token" | "output_token"; referralFeeBps: number; // TODO: maybe type should be camelCase too? - tokens: + tokens: ( | { inputToken: PublicKey; outputToken: TokenAmountSvm; @@ -144,7 +144,11 @@ export type OpportunitySvmSwap = { inputToken: TokenAmountSvm; outputToken: PublicKey; type: "input_specified"; - }; + } + ) & { + inputTokenProgram: PublicKey; + outputTokenProgram: PublicKey; + }; program: "swap"; } & OpportunitySvmMetadata; From a3eab4272b197e5720cfacb77dde0635689ec78d Mon Sep 17 00:00:00 2001 From: Amin Moghaddam Date: Thu, 16 Jan 2025 13:44:28 -0500 Subject: [PATCH 13/25] Check token programs --- .../src/auction/service/verification.rs | 103 +++++++++--------- .../opportunity/service/get_opportunities.rs | 2 +- 2 files changed, 50 insertions(+), 55 deletions(-) diff --git a/auction-server/src/auction/service/verification.rs b/auction-server/src/auction/service/verification.rs index 6688c366..ddc07cfc 100644 --- a/auction-server/src/auction/service/verification.rs +++ b/auction-server/src/auction/service/verification.rs @@ -588,14 +588,10 @@ impl Service { }, ), }; - let mint_fee = match swap_data.fee_token { - FeeToken::Input => mint_input, - FeeToken::Output => mint_output, - }; let opp = self .opportunity_service - .get_opportunity_by_id(GetOpportunityByIdInput { + .get_live_opportunity_by_id(GetOpportunityByIdInput { opportunity_id: bid_data.opportunity_id, }) .await @@ -603,58 +599,46 @@ impl Service { "No swap opportunity with the given id found".to_string(), ))?; - if let SwapKamino(opp_swap_data) = opp.program { - if swap_data.referral_fee_bps != opp_swap_data.referral_fee_bps { - return Err(RestError::BadParameters( - format!( - "Referral fee bps in swap opportunity {} does not match the referral fee bps in the swap instruction {}", - opp_swap_data.referral_fee_bps, swap_data.referral_fee_bps - ), - )); - } - if user_wallet != opp_swap_data.user_wallet_address { - return Err(RestError::BadParameters( - format!( - "User wallet address in swap opportunity {} does not match the user wallet address in the swap instruction {}", - opp_swap_data.user_wallet_address, user_wallet - ), - )); - } - if opp_swap_data.fee_token != swap_data.fee_token { - return Err(RestError::BadParameters( - format!( - "Fee token in swap opportunity {:?} does not match the fee token in the swap instruction {:?}", - opp_swap_data.fee_token, swap_data.fee_token - ), - )); + let opp_swap_data = match opp.program { + SwapKamino(opp_swap_data) => opp_swap_data, + _ => { + return Err(RestError::BadParameters(format!( + "Opportunity with id {} is not a swap opportunity", + bid_data.opportunity_id + ))); } - } else { - return Err(RestError::BadParameters(format!( - "Opportunity with id {} is not a swap opportunity", - bid_data.opportunity_id - ))); - } + }; - let router_fee_receiver_ta = self - .extract_account( - &bid_data.transaction, - &swap_instruction, - self.config - .chain_config - .express_relay - .swap_instruction_account_positions - .router_token_account, - ) - .await?; + if swap_data.referral_fee_bps != opp_swap_data.referral_fee_bps { + return Err(RestError::BadParameters( + format!( + "Referral fee bps in swap opportunity {} does not match the referral fee bps in the swap instruction {}", + opp_swap_data.referral_fee_bps, swap_data.referral_fee_bps + ), + )); + } + if user_wallet != opp_swap_data.user_wallet_address { + return Err(RestError::BadParameters( + format!( + "User wallet address in swap opportunity {} does not match the user wallet address in the swap instruction {}", + opp_swap_data.user_wallet_address, user_wallet + ), + )); + } + let (fee_token, fee_token_program) = match swap_data.fee_token { + FeeToken::Input => (mint_input, opp_swap_data.input_token_program), + FeeToken::Output => (mint_output, opp_swap_data.output_token_program), + }; + if opp_swap_data.fee_token != swap_data.fee_token { + return Err(RestError::BadParameters( + format!( + "Fee token in swap opportunity {:?} does not match the fee token in the swap instruction {:?}", + opp_swap_data.fee_token, swap_data.fee_token + ), + )); + } - //TODO* : do not hardcode the token program and refactor into separate function - let fee_token_program = Pubkey::from_str( - "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", - ) - .map_err(|e| { - RestError::BadParameters(format!("Invalid token program address: {:?}", e)) - })?; let associated_token_program = Pubkey::from_str( "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL", ) @@ -669,11 +653,22 @@ impl Service { &[ &opp.router.to_bytes(), &fee_token_program.to_bytes(), - &mint_fee.to_bytes(), + &fee_token.to_bytes(), ], &associated_token_program, ) .0; + let router_fee_receiver_ta = self + .extract_account( + &bid_data.transaction, + &swap_instruction, + self.config + .chain_config + .express_relay + .swap_instruction_account_positions + .router_token_account, + ) + .await?; if router_fee_receiver_ta != expected_router_fee_receiver_ta { return Err(RestError::BadParameters( diff --git a/auction-server/src/opportunity/service/get_opportunities.rs b/auction-server/src/opportunity/service/get_opportunities.rs index 5772bdd2..2bbcf704 100644 --- a/auction-server/src/opportunity/service/get_opportunities.rs +++ b/auction-server/src/opportunity/service/get_opportunities.rs @@ -23,7 +23,7 @@ pub struct GetOpportunityByIdInput { } impl Service { - pub async fn get_opportunity_by_id( + pub async fn get_live_opportunity_by_id( &self, input: GetOpportunityByIdInput, ) -> Option<::Opportunity> { From 8cf0e1791171f6dc8188c55219bcbbafcdf0fadb Mon Sep 17 00:00:00 2001 From: Amin Moghaddam Date: Thu, 16 Jan 2025 13:52:55 -0500 Subject: [PATCH 14/25] Drop the Kamino, just Swap, it is cleaner --- auction-server/api-types/src/opportunity.rs | 4 ++-- .../src/auction/service/verification.rs | 4 ++-- auction-server/src/opportunity/api.rs | 3 +-- .../opportunity/entities/opportunity_svm.rs | 19 ++++++++++--------- .../src/opportunity/repository/models.rs | 2 +- .../src/opportunity/service/get_quote.rs | 4 ++-- sdk/js/src/serverTypes.d.ts | 2 +- 7 files changed, 19 insertions(+), 19 deletions(-) diff --git a/auction-server/api-types/src/opportunity.rs b/auction-server/api-types/src/opportunity.rs index edf29d93..942d14f3 100644 --- a/auction-server/api-types/src/opportunity.rs +++ b/auction-server/api-types/src/opportunity.rs @@ -64,7 +64,7 @@ pub struct OpportunityBidResult { #[serde(rename_all = "snake_case")] #[strum(serialize_all = "snake_case")] pub enum ProgramSvm { - SwapKamino, + Swap, Limo, } @@ -608,7 +608,7 @@ impl OpportunityCreateSvm { OpportunityCreateSvm::V1(params) => match ¶ms.program_params { OpportunityCreateProgramParamsV1Svm::Limo { .. } => ProgramSvm::Limo, // TODO*: this arm doesn't really matter, bc this function will never be called in get_quote, but we should figure out how to handle this - OpportunityCreateProgramParamsV1Svm::Swap { .. } => ProgramSvm::SwapKamino, + OpportunityCreateProgramParamsV1Svm::Swap { .. } => ProgramSvm::Swap, }, } } diff --git a/auction-server/src/auction/service/verification.rs b/auction-server/src/auction/service/verification.rs index ddc07cfc..8d93bad7 100644 --- a/auction-server/src/auction/service/verification.rs +++ b/auction-server/src/auction/service/verification.rs @@ -33,7 +33,7 @@ use { opportunity::{ self as opportunity, entities::{ - OpportunitySvmProgram::SwapKamino, + OpportunitySvmProgram::Swap, QuoteTokens, TokenAmountSvm, }, @@ -600,7 +600,7 @@ impl Service { ))?; let opp_swap_data = match opp.program { - SwapKamino(opp_swap_data) => opp_swap_data, + Swap(opp_swap_data) => opp_swap_data, _ => { return Err(RestError::BadParameters(format!( "Opportunity with id {} is not a swap opportunity", diff --git a/auction-server/src/opportunity/api.rs b/auction-server/src/opportunity/api.rs index 123dd956..f4940fb3 100644 --- a/auction-server/src/opportunity/api.rs +++ b/auction-server/src/opportunity/api.rs @@ -58,7 +58,6 @@ fn get_program(auth: &Auth) -> Result { match profile.name.as_str() { "limo" => Ok(ProgramSvm::Limo), - "kamino market" => Ok(ProgramSvm::SwapKamino), _ => Err(RestError::Forbidden), } } @@ -214,7 +213,7 @@ pub async fn post_quote( .opportunity_service_svm .get_quote(GetQuoteInput { quote_create: params.into(), - program: ProgramSvm::SwapKamino, + program: ProgramSvm::Swap, }) .await?; diff --git a/auction-server/src/opportunity/entities/opportunity_svm.rs b/auction-server/src/opportunity/entities/opportunity_svm.rs index abd9891a..dad923dd 100644 --- a/auction-server/src/opportunity/entities/opportunity_svm.rs +++ b/auction-server/src/opportunity/entities/opportunity_svm.rs @@ -38,6 +38,7 @@ pub struct OpportunitySvmProgramLimo { } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] pub enum FeeToken { InputToken, OutputToken, @@ -65,7 +66,7 @@ pub struct OpportunitySvmProgramSwap { #[derive(Debug, Clone, PartialEq)] pub enum OpportunitySvmProgram { Limo(OpportunitySvmProgramLimo), - SwapKamino(OpportunitySvmProgramSwap), + Swap(OpportunitySvmProgramSwap), } #[derive(Debug, Clone, PartialEq)] @@ -118,8 +119,8 @@ impl Opportunity for OpportunitySvm { }, ) } - OpportunitySvmProgram::SwapKamino(program) => { - repository::OpportunityMetadataSvmProgram::SwapKamino( + OpportunitySvmProgram::Swap(program) => { + repository::OpportunityMetadataSvmProgram::Swap( repository::OpportunityMetadataSvmProgramSwap { user_wallet_address: program.user_wallet_address, fee_token: program.fee_token, @@ -198,7 +199,7 @@ impl From for api::OpportunitySvm { order: program.order, order_address: program.order_address, }, - OpportunitySvmProgram::SwapKamino(program) => { + OpportunitySvmProgram::Swap(program) => { let buy_token = val .buy_tokens .first() @@ -287,8 +288,8 @@ impl TryFrom> for Op order_address: program.order_address, }) } - repository::OpportunityMetadataSvmProgram::SwapKamino(program) => { - OpportunitySvmProgram::SwapKamino(OpportunitySvmProgramSwap { + repository::OpportunityMetadataSvmProgram::Swap(program) => { + OpportunitySvmProgram::Swap(OpportunitySvmProgramSwap { user_wallet_address: program.user_wallet_address, fee_token: program.fee_token, referral_fee_bps: program.referral_fee_bps, @@ -332,7 +333,7 @@ impl From for OpportunityCreateSvm { referral_fee_bps, input_token_program, output_token_program, - } => OpportunitySvmProgram::SwapKamino(OpportunitySvmProgramSwap { + } => OpportunitySvmProgram::Swap(OpportunitySvmProgramSwap { user_wallet_address, // TODO*: see comment above about this arm fee_token: FeeToken::InputToken, @@ -382,7 +383,7 @@ impl From for OpportunityCreateSvm { impl OpportunitySvm { pub fn get_missing_signers(&self) -> Vec { match self.program.clone() { - OpportunitySvmProgram::SwapKamino(data) => vec![data.user_wallet_address], + OpportunitySvmProgram::Swap(data) => vec![data.user_wallet_address], OpportunitySvmProgram::Limo(_) => vec![], } } @@ -405,7 +406,7 @@ impl From for api::ProgramSvm { fn from(val: OpportunitySvmProgram) -> Self { match val { OpportunitySvmProgram::Limo(_) => api::ProgramSvm::Limo, - OpportunitySvmProgram::SwapKamino(_) => api::ProgramSvm::SwapKamino, + OpportunitySvmProgram::Swap(_) => api::ProgramSvm::Swap, } } } diff --git a/auction-server/src/opportunity/repository/models.rs b/auction-server/src/opportunity/repository/models.rs index e8e969b6..d2a01e2a 100644 --- a/auction-server/src/opportunity/repository/models.rs +++ b/auction-server/src/opportunity/repository/models.rs @@ -75,7 +75,7 @@ pub struct OpportunityMetadataSvmProgramSwap { #[serde(tag = "program", rename_all = "lowercase")] pub enum OpportunityMetadataSvmProgram { Limo(OpportunityMetadataSvmProgramLimo), - SwapKamino(OpportunityMetadataSvmProgramSwap), + Swap(OpportunityMetadataSvmProgramSwap), } #[serde_as] diff --git a/auction-server/src/opportunity/service/get_quote.rs b/auction-server/src/opportunity/service/get_quote.rs index 7e96d267..6570cd69 100644 --- a/auction-server/src/opportunity/service/get_quote.rs +++ b/auction-server/src/opportunity/service/get_quote.rs @@ -160,8 +160,8 @@ impl Service { })?; let program_opportunity = match program { - ProgramSvm::SwapKamino => { - entities::OpportunitySvmProgram::SwapKamino(entities::OpportunitySvmProgramSwap { + ProgramSvm::Swap => { + entities::OpportunitySvmProgram::Swap(entities::OpportunitySvmProgramSwap { user_wallet_address: quote_create.user_wallet_address, // TODO*: we should determine this more intelligently fee_token: entities::FeeToken::InputToken, diff --git a/sdk/js/src/serverTypes.d.ts b/sdk/js/src/serverTypes.d.ts index 97418e05..2428d591 100644 --- a/sdk/js/src/serverTypes.d.ts +++ b/sdk/js/src/serverTypes.d.ts @@ -739,7 +739,7 @@ export interface components { slot: number; }; /** @enum {string} */ - ProgramSvm: "swap_kamino" | "limo"; + ProgramSvm: "swap" | "limo"; Quote: components["schemas"]["QuoteSvm"]; QuoteCreate: components["schemas"]["QuoteCreateSvm"]; QuoteCreateSvm: components["schemas"]["QuoteCreateV1SvmParams"] & { From 6369a17b22953574404721ea59c26b4b00f4b6de Mon Sep 17 00:00:00 2001 From: Amin Moghaddam Date: Thu, 16 Jan 2025 16:21:41 -0500 Subject: [PATCH 15/25] Include quote tokens in more opportunity structs --- auction-server/api-types/src/opportunity.rs | 1 + .../src/auction/service/verification.rs | 98 ++++++++++++++----- .../opportunity/entities/opportunity_svm.rs | 10 +- .../src/opportunity/entities/quote.rs | 38 ++++++- .../src/opportunity/repository/models.rs | 6 +- .../src/opportunity/service/get_quote.rs | 69 ++++++++++--- tilt-scripts/svm/test_swap.py | 2 +- 7 files changed, 178 insertions(+), 46 deletions(-) diff --git a/auction-server/api-types/src/opportunity.rs b/auction-server/api-types/src/opportunity.rs index 942d14f3..6b8e7745 100644 --- a/auction-server/api-types/src/opportunity.rs +++ b/auction-server/api-types/src/opportunity.rs @@ -227,6 +227,7 @@ pub enum OpportunityCreateProgramParamsV1Svm { #[schema(example = "DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5", value_type = String)] #[serde_as(as = "DisplayFromStr")] output_token_program: Pubkey, + quote_tokens: QuoteTokens, }, } diff --git a/auction-server/src/auction/service/verification.rs b/auction-server/src/auction/service/verification.rs index 8d93bad7..2414a2ce 100644 --- a/auction-server/src/auction/service/verification.rs +++ b/auction-server/src/auction/service/verification.rs @@ -35,12 +35,11 @@ use { entities::{ OpportunitySvmProgram::Swap, QuoteTokens, - TokenAmountSvm, }, service::{ get_live_opportunities::GetLiveOpportunitiesInput, get_opportunities::GetOpportunityByIdInput, - get_quote::get_quote_permission_key, + get_quote::get_quote_permission_account, }, }, }, @@ -566,28 +565,6 @@ impl Service { ) .await?; - let (bid_amount, tokens) = match swap_data.fee_token { - FeeToken::Input => ( - swap_data.amount_input, - QuoteTokens::InputTokenSpecified { - input_token: TokenAmountSvm { - token: mint_input, - amount: swap_data.amount_input, - }, - output_token: mint_output, - }, - ), - FeeToken::Output => ( - swap_data.amount_output, - QuoteTokens::OutputTokenSpecified { - input_token: mint_input, - output_token: TokenAmountSvm { - token: mint_output, - amount: swap_data.amount_output, - }, - }, - ), - }; let opp = self .opportunity_service @@ -608,6 +585,72 @@ impl Service { ))); } }; + let ( + bid_amount, + expected_input_token, + expected_input_amount, + expected_output_token, + expected_output_amount, + ) = match opp_swap_data.quote_tokens.clone() { + QuoteTokens::InputTokenSpecified { + input_token, + output_token, + .. + } => ( + swap_data.amount_output, + input_token.token, + Some(input_token.amount), + output_token, + None, + ), + QuoteTokens::OutputTokenSpecified { + input_token, + output_token, + .. + } => ( + swap_data.amount_input, + input_token, + None, + output_token.token, + Some(output_token.amount), + ), + }; + if let Some(expected_input_amount) = expected_input_amount { + if expected_input_amount != swap_data.amount_input { + return Err(RestError::BadParameters( + format!( + "Input amount in swap opportunity {} does not match the input amount in the swap instruction {}", + expected_input_amount, swap_data.amount_input + ), + )); + } + } + if let Some(expected_output_amount) = expected_output_amount { + if expected_output_amount != swap_data.amount_output { + return Err(RestError::BadParameters( + format!( + "Output amount in swap opportunity {} does not match the output amount in the swap instruction {}", + expected_output_amount, swap_data.amount_output + ), + )); + } + } + if expected_input_token != mint_input { + return Err(RestError::BadParameters( + format!( + "Input token in swap opportunity {} does not match the input token in the swap instruction {}", + expected_input_token, mint_input + ), + )); + } + if expected_output_token != mint_output { + return Err(RestError::BadParameters( + format!( + "Output token in swap opportunity {} does not match the output token in the swap instruction {}", + expected_output_token, mint_output + ), + )); + } if swap_data.referral_fee_bps != opp_swap_data.referral_fee_bps { @@ -676,8 +719,11 @@ impl Service { )); } - let permission_account = - get_quote_permission_key(&tokens, &user_wallet, swap_data.referral_fee_bps); + let permission_account = get_quote_permission_account( + &opp_swap_data.quote_tokens, + &user_wallet, + swap_data.referral_fee_bps, + ); Ok(BidDataSvm { amount: bid_amount, diff --git a/auction-server/src/opportunity/entities/opportunity_svm.rs b/auction-server/src/opportunity/entities/opportunity_svm.rs index dad923dd..631a6d65 100644 --- a/auction-server/src/opportunity/entities/opportunity_svm.rs +++ b/auction-server/src/opportunity/entities/opportunity_svm.rs @@ -12,7 +12,10 @@ use { crate::{ auction::entities::BidPaymentInstructionType, kernel::entities::PermissionKey, - opportunity::repository, + opportunity::{ + entities::QuoteTokens, + repository, + }, }, ::express_relay::FeeToken as ProgramFeeToken, express_relay_api_types::opportunity as api, @@ -57,6 +60,7 @@ impl PartialEq for FeeToken { pub struct OpportunitySvmProgramSwap { pub user_wallet_address: Pubkey, pub fee_token: FeeToken, + pub quote_tokens: QuoteTokens, pub referral_fee_bps: u16, // TODO*: these really should not live here. they should live in the opportunity core fields, but we don't want to introduce a breaking change. in any case, the need for the token programs is another sign that quotes should be separated from the traditional opportunity struct. pub input_token_program: Pubkey, @@ -124,6 +128,7 @@ impl Opportunity for OpportunitySvm { repository::OpportunityMetadataSvmProgramSwap { user_wallet_address: program.user_wallet_address, fee_token: program.fee_token, + quote_tokens: program.quote_tokens, referral_fee_bps: program.referral_fee_bps, input_token_program: program.input_token_program, output_token_program: program.output_token_program, @@ -291,6 +296,7 @@ impl TryFrom> for Op repository::OpportunityMetadataSvmProgram::Swap(program) => { OpportunitySvmProgram::Swap(OpportunitySvmProgramSwap { user_wallet_address: program.user_wallet_address, + quote_tokens: program.quote_tokens, fee_token: program.fee_token, referral_fee_bps: program.referral_fee_bps, input_token_program: program.input_token_program, @@ -333,6 +339,7 @@ impl From for OpportunityCreateSvm { referral_fee_bps, input_token_program, output_token_program, + quote_tokens, } => OpportunitySvmProgram::Swap(OpportunitySvmProgramSwap { user_wallet_address, // TODO*: see comment above about this arm @@ -340,6 +347,7 @@ impl From for OpportunityCreateSvm { referral_fee_bps, input_token_program, output_token_program, + quote_tokens: quote_tokens.into(), }), }; diff --git a/auction-server/src/opportunity/entities/quote.rs b/auction-server/src/opportunity/entities/quote.rs index b3286e3d..9594ad8c 100644 --- a/auction-server/src/opportunity/entities/quote.rs +++ b/auction-server/src/opportunity/entities/quote.rs @@ -2,6 +2,14 @@ use { super::token_amount_svm::TokenAmountSvm, crate::kernel::entities::ChainId, express_relay_api_types::opportunity as api, + serde::{ + Deserialize, + Serialize, + }, + serde_with::{ + serde_as, + DisplayFromStr, + }, solana_sdk::{ pubkey::Pubkey, transaction::VersionedTransaction, @@ -27,18 +35,46 @@ pub struct QuoteCreate { pub chain_id: ChainId, } -#[derive(Debug, Clone, PartialEq)] + +#[serde_as] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(tag = "type", rename_all = "snake_case")] pub enum QuoteTokens { InputTokenSpecified { input_token: TokenAmountSvm, + #[serde_as(as = "DisplayFromStr")] output_token: Pubkey, }, OutputTokenSpecified { + #[serde_as(as = "DisplayFromStr")] input_token: Pubkey, output_token: TokenAmountSvm, }, } +impl From for QuoteTokens { + fn from(quote_tokens: api::QuoteTokens) -> Self { + match quote_tokens { + api::QuoteTokens::InputTokenSpecified { + input_token, + output_token, + .. + } => QuoteTokens::InputTokenSpecified { + input_token: input_token.into(), + output_token, + }, + api::QuoteTokens::OutputTokenSpecified { + input_token, + output_token, + .. + } => QuoteTokens::OutputTokenSpecified { + input_token, + output_token: output_token.into(), + }, + } + } +} + impl From for QuoteCreate { fn from(quote_create: api::QuoteCreate) -> Self { let api::QuoteCreate::Svm(api::QuoteCreateSvm::V1(params)) = quote_create; diff --git a/auction-server/src/opportunity/repository/models.rs b/auction-server/src/opportunity/repository/models.rs index d2a01e2a..deecb2af 100644 --- a/auction-server/src/opportunity/repository/models.rs +++ b/auction-server/src/opportunity/repository/models.rs @@ -1,7 +1,10 @@ use { crate::{ models::ChainType, - opportunity::entities::FeeToken, + opportunity::entities::{ + FeeToken, + QuoteTokens, + }, }, ethers::types::{ Address, @@ -64,6 +67,7 @@ pub struct OpportunityMetadataSvmProgramSwap { #[serde_as(as = "DisplayFromStr")] pub user_wallet_address: Pubkey, pub fee_token: FeeToken, + pub quote_tokens: QuoteTokens, pub referral_fee_bps: u16, #[serde_as(as = "DisplayFromStr")] pub input_token_program: Pubkey, diff --git a/auction-server/src/opportunity/service/get_quote.rs b/auction-server/src/opportunity/service/get_quote.rs index 6570cd69..5131a71c 100644 --- a/auction-server/src/opportunity/service/get_quote.rs +++ b/auction-server/src/opportunity/service/get_quote.rs @@ -1,3 +1,4 @@ +use crate::opportunity::entities::TokenAmountSvm; use { super::{ get_token_program::GetTokenProgramInput, @@ -28,6 +29,11 @@ use { service::add_opportunity::AddOpportunityInput, }, }, + ::express_relay::{ + self as express_relay_svm, + state::FEE_SPLIT_PRECISION, + FeeToken, + }, axum_prometheus::metrics, // TODO: is it okay to import api types into the service layer? express_relay_api_types::opportunity::ProgramSvm, @@ -49,7 +55,7 @@ pub struct GetQuoteInput { pub program: ProgramSvm, } -pub fn get_quote_permission_key( +pub fn get_quote_permission_account( tokens: &entities::QuoteTokens, user_wallet_address: &Pubkey, referral_fee_bps: u16, @@ -102,7 +108,7 @@ impl Service { program: &ProgramSvm, ) -> Result { let router = quote_create.router; - let permission_account = get_quote_permission_key( + let permission_account = get_quote_permission_account( "e_create.tokens, "e_create.user_wallet_address, quote_create.referral_fee_bps, @@ -110,16 +116,17 @@ impl Service { // TODO*: we should fix the Opportunity struct (or create a new format) to more clearly distinguish Swap opps from traditional opps // currently, we are using the same struct and just setting the unspecified token amount to 0 - let (input_mint, input_amount, output_mint, output_amount) = match quote_create.tokens { - entities::QuoteTokens::InputTokenSpecified { - input_token, - output_token, - } => (input_token.token, input_token.amount, output_token, 0), - entities::QuoteTokens::OutputTokenSpecified { - input_token, - output_token, - } => (input_token, 0, output_token.token, output_token.amount), - }; + let (input_mint, input_amount, output_mint, output_amount) = + match quote_create.tokens.clone() { + entities::QuoteTokens::InputTokenSpecified { + input_token, + output_token, + } => (input_token.token, input_token.amount, output_token, 0), + entities::QuoteTokens::OutputTokenSpecified { + input_token, + output_token, + } => (input_token, 0, output_token.token, output_token.amount), + }; let core_fields = entities::OpportunityCoreFieldsCreate { permission_key: entities::OpportunitySvm::get_permission_key( @@ -166,6 +173,7 @@ impl Service { // TODO*: we should determine this more intelligently fee_token: entities::FeeToken::InputToken, referral_fee_bps: quote_create.referral_fee_bps, + quote_tokens: quote_create.tokens, input_token_program, output_token_program, }) @@ -205,6 +213,13 @@ impl Service { #[tracing::instrument(skip_all)] pub async fn get_quote(&self, input: GetQuoteInput) -> Result { + if FEE_SPLIT_PRECISION < input.quote_create.referral_fee_bps.into() { + return Err(RestError::BadParameters(format!( + "Referral fee bps higher than {}", + FEE_SPLIT_PRECISION + ))); + } + let config = self.get_config(&input.quote_create.chain_id)?; let auction_service = config.get_auction_service().await; @@ -347,13 +362,35 @@ impl Service { tracing::error!(winner_bid = ?winner_bid, opportunity = ?opportunity, "Failed to update winner bid status"); return Err(RestError::TemporarilyUnavailable); } + // TODO*: fetch this on-chain to incorporate the actual fees based on swap_platform_fee_bps and relayer_split + let metadata = express_relay_svm::state::ExpressRelayMetadata::default(); + let (input_amount, output_amount) = match swap_data.fee_token { + FeeToken::Input => (swap_data.amount_input, swap_data.amount_output), + FeeToken::Output => ( + swap_data.amount_input, + metadata + .compute_swap_fees(swap_data.referral_fee_bps, swap_data.amount_input) + .map_err(|e| { + tracing::error!("Failed to compute swap fees: {:?}", e); + RestError::TemporarilyUnavailable + })? + .remaining_amount, + ), + }; Ok(entities::Quote { - transaction: bid.chain_data.transaction.clone(), + transaction: bid.chain_data.transaction.clone(), expiration_time: deadline, - input_token, - output_token, // TODO*: incorporate fees (when fees are in the output token) - chain_id: input.quote_create.chain_id, + + input_token: TokenAmountSvm { + token: input_token.token, + amount: input_amount, + }, + output_token: TokenAmountSvm { + token: output_token.token, + amount: output_amount, + }, + chain_id: input.quote_create.chain_id, }) } } diff --git a/tilt-scripts/svm/test_swap.py b/tilt-scripts/svm/test_swap.py index 0ba9b074..a36a4e22 100644 --- a/tilt-scripts/svm/test_swap.py +++ b/tilt-scripts/svm/test_swap.py @@ -67,7 +67,7 @@ async def main(): "output_token_mint": args.output_mint, "router": "3hv8L8UeBbyM3M25dF3h2C5p8yA4FptD7FFZu4Z1jCMn", "referral_fee_bps": 10, - "specified_token_amount": {"amount": random.randint(1, 1000), "side": "input"}, + "specified_token_amount": {"amount": random.randint(1, 1000), "side": "output"}, "user_wallet_address": str(pk_taker), "version": "v1", } From 388dbdc3bc6575ccd9ce6146ee6ecd9d7927b532 Mon Sep 17 00:00:00 2001 From: Amin Moghaddam Date: Thu, 16 Jan 2025 16:51:03 -0500 Subject: [PATCH 16/25] Refactor js sdk --- sdk/js/src/index.ts | 34 ++++++++++- sdk/js/src/svm.ts | 146 ++++++++++++++++++++++++++++---------------- 2 files changed, 128 insertions(+), 52 deletions(-) diff --git a/sdk/js/src/index.ts b/sdk/js/src/index.ts index 9191a1da..945d48a6 100644 --- a/sdk/js/src/index.ts +++ b/sdk/js/src/index.ts @@ -23,6 +23,8 @@ import { QuoteRequest, QuoteResponse, BidSvmOnChain, + BidSvmSwap, + OpportunitySvmSwap, } from "./types"; import { Connection, @@ -807,7 +809,7 @@ export class Client { } /** - * Constructs an SVM bid, by adding a SubmitBid instruction to a transaction + * Constructs an SVM On-chain bid, by adding a SubmitBid instruction to a transaction * @param tx The transaction to add a SubmitBid instruction to. This transaction should already check for the appropriate permissions. * @param searcher The address of the searcher that is submitting the bid * @param router The identifying address of the router that the permission key is for @@ -842,4 +844,34 @@ export class Client { feeReceiverRelayer ); } + + /** + * Constructs a Swap Bid, by adding swap instruction + idempotent token account creation instructions to a transaction + * @param tx The transaction to add the instructions to + * @param searcher The address of the searcher filling the swap order + * @param swapOpportunity The swap opportunity to bid on + * @param bidAmount The amount of the bid in either input or output tokens depending on the swap opportunity + * @param deadline The deadline for the bid in seconds since Unix epoch + * @param chainId The chain ID as a string, e.g. "solana" + * @param relayerSigner The address of the relayer that is handling the bid + */ + async constructSwapBid( + tx: Transaction, + searcher: PublicKey, + swapOpportunity: OpportunitySvmSwap, + bidAmount: anchor.BN, + deadline: anchor.BN, + chainId: string, + relayerSigner: PublicKey + ): Promise { + return svm.constructSwapBid( + tx, + searcher, + swapOpportunity, + bidAmount, + deadline, + chainId, + relayerSigner + ); + } } diff --git a/sdk/js/src/svm.ts b/sdk/js/src/svm.ts index b86d7374..9c910e3c 100644 --- a/sdk/js/src/svm.ts +++ b/sdk/js/src/svm.ts @@ -125,15 +125,14 @@ export function createAtaIdempotentInstruction( return [ataAddress, createUserTokenAccountIx]; } -export async function constructSwapBid( - tx: Transaction, +export async function constructSwapInstruction( searcher: PublicKey, swapOpportunity: OpportunitySvmSwap, bidAmount: anchor.BN, deadline: anchor.BN, chainId: string, relayerSigner: PublicKey -): Promise { +): Promise { const expressRelay = new Program( expressRelayIdl as ExpressRelay, {} as AnchorProvider @@ -141,22 +140,16 @@ export async function constructSwapBid( const expressRelayMetadata = getExpressRelayMetadataPda(chainId); const svmConstants = SVM_CONSTANTS[chainId]; - const inputTokenProgram = swapOpportunity.tokens.inputTokenProgram; - const outputTokenProgram = swapOpportunity.tokens.outputTokenProgram; - const inputToken = - swapOpportunity.tokens.type === "input_specified" - ? swapOpportunity.tokens.inputToken.token - : swapOpportunity.tokens.inputToken; - const outputToken = - swapOpportunity.tokens.type === "output_specified" - ? swapOpportunity.tokens.outputToken.token - : swapOpportunity.tokens.outputToken; - const trader = swapOpportunity.userWalletAddress; - const [mintFee, feeTokenProgram] = - swapOpportunity.feeToken === "input_token" - ? [inputToken, inputTokenProgram] - : [outputToken, outputTokenProgram]; - const router = swapOpportunity.routerAccount; + const { + inputToken, + inputTokenProgram, + outputTokenProgram, + outputToken, + trader, + mintFee, + feeTokenProgram, + router, + } = extractSwapInfo(swapOpportunity); const swapArgs = { amountInput: @@ -224,39 +217,90 @@ export async function constructSwapBid( }) .instruction(); ixSwap.programId = svmConstants.expressRelayProgram; - tx.instructions.push( - createAtaIdempotentInstruction( - router, - mintFee, - searcher, - feeTokenProgram - )[1] - ); - tx.instructions.push( - createAtaIdempotentInstruction( - relayerSigner, - mintFee, - searcher, - feeTokenProgram - )[1] - ); - tx.instructions.push( - createAtaIdempotentInstruction( - expressRelayMetadata, - mintFee, - searcher, - feeTokenProgram - )[1] - ); - tx.instructions.push( - createAtaIdempotentInstruction( - trader, - outputToken, - searcher, - outputTokenProgram - )[1] + return ixSwap; +} + +function extractSwapInfo(swapOpportunity: OpportunitySvmSwap): { + outputTokenProgram: PublicKey; + outputToken: PublicKey; + trader: PublicKey; + mintFee: PublicKey; + feeTokenProgram: PublicKey; + router: PublicKey; + inputToken: PublicKey; + inputTokenProgram: PublicKey; +} { + const inputTokenProgram = swapOpportunity.tokens.inputTokenProgram; + const outputTokenProgram = swapOpportunity.tokens.outputTokenProgram; + const inputToken = + swapOpportunity.tokens.type === "input_specified" + ? swapOpportunity.tokens.inputToken.token + : swapOpportunity.tokens.inputToken; + const outputToken = + swapOpportunity.tokens.type === "output_specified" + ? swapOpportunity.tokens.outputToken.token + : swapOpportunity.tokens.outputToken; + const trader = swapOpportunity.userWalletAddress; + const [mintFee, feeTokenProgram] = + swapOpportunity.feeToken === "input_token" + ? [inputToken, inputTokenProgram] + : [outputToken, outputTokenProgram]; + const router = swapOpportunity.routerAccount; + return { + inputToken, + inputTokenProgram, + outputTokenProgram, + outputToken, + trader, + mintFee, + feeTokenProgram, + router, + }; +} + +export async function constructSwapBid( + tx: Transaction, + searcher: PublicKey, + swapOpportunity: OpportunitySvmSwap, + bidAmount: anchor.BN, + deadline: anchor.BN, + chainId: string, + relayerSigner: PublicKey +): Promise { + const expressRelayMetadata = getExpressRelayMetadataPda(chainId); + const { + outputTokenProgram, + outputToken, + trader, + mintFee, + feeTokenProgram, + router, + } = extractSwapInfo(swapOpportunity); + const tokenAccountsToCreate = [ + { owner: router, mint: mintFee, program: feeTokenProgram }, + { owner: relayerSigner, mint: mintFee, program: feeTokenProgram }, + { owner: expressRelayMetadata, mint: mintFee, program: feeTokenProgram }, + { owner: trader, mint: outputToken, program: outputTokenProgram }, + ]; + for (const account of tokenAccountsToCreate) { + tx.instructions.push( + createAtaIdempotentInstruction( + account.owner, + account.mint, + searcher, + account.program + )[1] + ); + } + const swapInstruction = await constructSwapInstruction( + searcher, + swapOpportunity, + bidAmount, + deadline, + chainId, + relayerSigner ); - tx.instructions.push(ixSwap); + tx.instructions.push(swapInstruction); return { transaction: tx, From e025041f03a869caf0df80c28d5a5d2396ab773d Mon Sep 17 00:00:00 2001 From: Amin Moghaddam Date: Thu, 16 Jan 2025 16:54:03 -0500 Subject: [PATCH 17/25] Uuid -> OpportunityId --- auction-server/src/auction/entities/bid.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/auction-server/src/auction/entities/bid.rs b/auction-server/src/auction/entities/bid.rs index ffb01fe7..5e6c760c 100644 --- a/auction-server/src/auction/entities/bid.rs +++ b/auction-server/src/auction/entities/bid.rs @@ -16,6 +16,7 @@ use { self, ProfileId, }, + opportunity::entities::OpportunityId, }, ethers::types::{ Address, @@ -270,7 +271,7 @@ pub struct BidChainDataOnChainCreateSvm { #[derive(Clone, Debug)] pub struct BidChainDataSwapCreateSvm { pub transaction: VersionedTransaction, - pub opportunity_id: Uuid, + pub opportunity_id: OpportunityId, } From fdada5ad8fa07e1c0b6958efa576b4ec48573a5f Mon Sep 17 00:00:00 2001 From: Amin Moghaddam Date: Fri, 17 Jan 2025 08:08:03 -0500 Subject: [PATCH 18/25] fix1 --- auction-server/api-types/src/opportunity.rs | 4 +++- auction-server/src/auction/service/verification.rs | 9 ++++----- .../src/opportunity/service/get_opportunities.rs | 4 ++-- auction-server/src/opportunity/service/get_quote.rs | 5 +++-- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/auction-server/api-types/src/opportunity.rs b/auction-server/api-types/src/opportunity.rs index 6b8e7745..c4bf1228 100644 --- a/auction-server/api-types/src/opportunity.rs +++ b/auction-server/api-types/src/opportunity.rs @@ -361,14 +361,16 @@ pub enum FeeToken { } #[derive(Serialize, Deserialize, ToSchema, Clone, PartialEq, Debug, ToResponse)] -#[serde(tag = "type", rename_all = "snake_case")] +#[serde(tag = "side_specified")] pub enum QuoteTokens { + #[serde(rename = "input")] InputTokenSpecified { input_token: TokenAmountSvm, output_token: Pubkey, input_token_program: Pubkey, output_token_program: Pubkey, }, + #[serde(rename = "output")] OutputTokenSpecified { input_token: Pubkey, output_token: TokenAmountSvm, diff --git a/auction-server/src/auction/service/verification.rs b/auction-server/src/auction/service/verification.rs index 2414a2ce..1b5b49c5 100644 --- a/auction-server/src/auction/service/verification.rs +++ b/auction-server/src/auction/service/verification.rs @@ -38,8 +38,8 @@ use { }, service::{ get_live_opportunities::GetLiveOpportunitiesInput, - get_opportunities::GetOpportunityByIdInput, - get_quote::get_quote_permission_account, + get_opportunities::GetLiveOpportunityByIdInput, + get_quote::get_quote_virtual_permission_account, }, }, }, @@ -565,10 +565,9 @@ impl Service { ) .await?; - let opp = self .opportunity_service - .get_live_opportunity_by_id(GetOpportunityByIdInput { + .get_live_opportunity_by_id(GetLiveOpportunityByIdInput { opportunity_id: bid_data.opportunity_id, }) .await @@ -719,7 +718,7 @@ impl Service { )); } - let permission_account = get_quote_permission_account( + let permission_account = get_quote_virtual_permission_account( &opp_swap_data.quote_tokens, &user_wallet, swap_data.referral_fee_bps, diff --git a/auction-server/src/opportunity/service/get_opportunities.rs b/auction-server/src/opportunity/service/get_opportunities.rs index 2bbcf704..e51e37e7 100644 --- a/auction-server/src/opportunity/service/get_opportunities.rs +++ b/auction-server/src/opportunity/service/get_opportunities.rs @@ -18,14 +18,14 @@ pub struct GetOpportunitiesInput { pub query_params: GetOpportunitiesQueryParams, } -pub struct GetOpportunityByIdInput { +pub struct GetLiveOpportunityByIdInput { pub opportunity_id: OpportunityId, } impl Service { pub async fn get_live_opportunity_by_id( &self, - input: GetOpportunityByIdInput, + input: GetLiveOpportunityByIdInput, ) -> Option<::Opportunity> { self.repo .get_in_memory_opportunity_by_id(input.opportunity_id) diff --git a/auction-server/src/opportunity/service/get_quote.rs b/auction-server/src/opportunity/service/get_quote.rs index 5131a71c..7acd6671 100644 --- a/auction-server/src/opportunity/service/get_quote.rs +++ b/auction-server/src/opportunity/service/get_quote.rs @@ -55,7 +55,7 @@ pub struct GetQuoteInput { pub program: ProgramSvm, } -pub fn get_quote_permission_account( +pub fn get_quote_virtual_permission_account( tokens: &entities::QuoteTokens, user_wallet_address: &Pubkey, referral_fee_bps: u16, @@ -108,7 +108,7 @@ impl Service { program: &ProgramSvm, ) -> Result { let router = quote_create.router; - let permission_account = get_quote_permission_account( + let permission_account = get_quote_virtual_permission_account( "e_create.tokens, "e_create.user_wallet_address, quote_create.referral_fee_bps, @@ -213,6 +213,7 @@ impl Service { #[tracing::instrument(skip_all)] pub async fn get_quote(&self, input: GetQuoteInput) -> Result { + // TODO use compute_swap_fees to make sure instead when the metadata is fetched from on-chain if FEE_SPLIT_PRECISION < input.quote_create.referral_fee_bps.into() { return Err(RestError::BadParameters(format!( "Referral fee bps higher than {}", From e2658c03c77b106ddd765b1b8fca13c5e2547967 Mon Sep 17 00:00:00 2001 From: Amin Moghaddam Date: Fri, 17 Jan 2025 08:28:04 -0500 Subject: [PATCH 19/25] Reorder errors and use const pubkey --- .../src/auction/service/verification.rs | 72 +++++++++---------- 1 file changed, 33 insertions(+), 39 deletions(-) diff --git a/auction-server/src/auction/service/verification.rs b/auction-server/src/auction/service/verification.rs index 1b5b49c5..f9f31f77 100644 --- a/auction-server/src/auction/service/verification.rs +++ b/auction-server/src/auction/service/verification.rs @@ -80,7 +80,6 @@ use { transaction::VersionedTransaction, }, std::{ - str::FromStr, sync::Arc, time::Duration, }, @@ -614,6 +613,30 @@ impl Service { Some(output_token.amount), ), }; + if user_wallet != opp_swap_data.user_wallet_address { + return Err(RestError::BadParameters( + format!( + "User wallet address in swap opportunity {} does not match the user wallet address in the swap instruction {}", + opp_swap_data.user_wallet_address, user_wallet + ), + )); + } + if expected_input_token != mint_input { + return Err(RestError::BadParameters( + format!( + "Input token in swap opportunity {} does not match the input token in the swap instruction {}", + expected_input_token, mint_input + ), + )); + } + if expected_output_token != mint_output { + return Err(RestError::BadParameters( + format!( + "Output token in swap opportunity {} does not match the output token in the swap instruction {}", + expected_output_token, mint_output + ), + )); + } if let Some(expected_input_amount) = expected_input_amount { if expected_input_amount != swap_data.amount_input { return Err(RestError::BadParameters( @@ -634,24 +657,15 @@ impl Service { )); } } - if expected_input_token != mint_input { - return Err(RestError::BadParameters( - format!( - "Input token in swap opportunity {} does not match the input token in the swap instruction {}", - expected_input_token, mint_input - ), - )); - } - if expected_output_token != mint_output { + if opp_swap_data.fee_token != swap_data.fee_token { return Err(RestError::BadParameters( format!( - "Output token in swap opportunity {} does not match the output token in the swap instruction {}", - expected_output_token, mint_output + "Fee token in swap opportunity {:?} does not match the fee token in the swap instruction {:?}", + opp_swap_data.fee_token, swap_data.fee_token ), )); } - if swap_data.referral_fee_bps != opp_swap_data.referral_fee_bps { return Err(RestError::BadParameters( format!( @@ -660,36 +674,16 @@ impl Service { ), )); } - if user_wallet != opp_swap_data.user_wallet_address { - return Err(RestError::BadParameters( - format!( - "User wallet address in swap opportunity {} does not match the user wallet address in the swap instruction {}", - opp_swap_data.user_wallet_address, user_wallet - ), - )); - } let (fee_token, fee_token_program) = match swap_data.fee_token { FeeToken::Input => (mint_input, opp_swap_data.input_token_program), FeeToken::Output => (mint_output, opp_swap_data.output_token_program), }; - if opp_swap_data.fee_token != swap_data.fee_token { - return Err(RestError::BadParameters( - format!( - "Fee token in swap opportunity {:?} does not match the fee token in the swap instruction {:?}", - opp_swap_data.fee_token, swap_data.fee_token - ), - )); - } - let associated_token_program = Pubkey::from_str( - "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL", - ) - .map_err(|e| { - RestError::BadParameters(format!( - "Invalid associated token program address: {:?}", - e - )) - })?; + // ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL + const ASSOCIATED_TOKEN_PROGRAM: Pubkey = Pubkey::new_from_array([ + 140, 151, 37, 143, 78, 36, 137, 241, 187, 61, 16, 41, 20, 142, 13, 131, 11, 90, + 19, 153, 218, 255, 16, 132, 4, 142, 123, 216, 219, 233, 248, 89, + ]); let expected_router_fee_receiver_ta = Pubkey::find_program_address( &[ @@ -697,7 +691,7 @@ impl Service { &fee_token_program.to_bytes(), &fee_token.to_bytes(), ], - &associated_token_program, + &ASSOCIATED_TOKEN_PROGRAM, ) .0; let router_fee_receiver_ta = self From 2d759ad7427db9dee464bf22f6813f614aaf94e7 Mon Sep 17 00:00:00 2001 From: Amin Moghaddam Date: Fri, 17 Jan 2025 09:07:20 -0500 Subject: [PATCH 20/25] Remove quote tokens field --- auction-server/api-types/src/opportunity.rs | 1 - .../src/auction/service/verification.rs | 27 +++++++++++++++++-- .../opportunity/entities/opportunity_svm.rs | 10 +------ .../src/opportunity/entities/quote.rs | 8 ------ .../src/opportunity/repository/models.rs | 6 +---- .../src/opportunity/service/get_quote.rs | 1 - 6 files changed, 27 insertions(+), 26 deletions(-) diff --git a/auction-server/api-types/src/opportunity.rs b/auction-server/api-types/src/opportunity.rs index c4bf1228..ff2e4876 100644 --- a/auction-server/api-types/src/opportunity.rs +++ b/auction-server/api-types/src/opportunity.rs @@ -227,7 +227,6 @@ pub enum OpportunityCreateProgramParamsV1Svm { #[schema(example = "DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5", value_type = String)] #[serde_as(as = "DisplayFromStr")] output_token_program: Pubkey, - quote_tokens: QuoteTokens, }, } diff --git a/auction-server/src/auction/service/verification.rs b/auction-server/src/auction/service/verification.rs index f9f31f77..2518aba7 100644 --- a/auction-server/src/auction/service/verification.rs +++ b/auction-server/src/auction/service/verification.rs @@ -583,13 +583,36 @@ impl Service { ))); } }; + let opp_sell_token = opp + .core_fields + .sell_tokens + .first() + .expect("Swap opportunity sell tokens must not be empty"); + let opp_buy_token = opp + .core_fields + .buy_tokens + .first() + .expect("Swap opportunity buy tokens must not be empty"); + let quote_tokens = match (opp_sell_token.amount, opp_buy_token.amount) { + (0, _) => QuoteTokens::OutputTokenSpecified { + input_token: opp_sell_token.token, + output_token: opp_buy_token.clone(), + }, + (_, 0) => QuoteTokens::InputTokenSpecified { + input_token: opp_sell_token.clone(), + output_token: opp_buy_token.token, + }, + _ => { + panic!("Non zero amount for both sell and buy tokens in swap opportunity"); + } + }; let ( bid_amount, expected_input_token, expected_input_amount, expected_output_token, expected_output_amount, - ) = match opp_swap_data.quote_tokens.clone() { + ) = match quote_tokens.clone() { QuoteTokens::InputTokenSpecified { input_token, output_token, @@ -713,7 +736,7 @@ impl Service { } let permission_account = get_quote_virtual_permission_account( - &opp_swap_data.quote_tokens, + "e_tokens, &user_wallet, swap_data.referral_fee_bps, ); diff --git a/auction-server/src/opportunity/entities/opportunity_svm.rs b/auction-server/src/opportunity/entities/opportunity_svm.rs index 631a6d65..dad923dd 100644 --- a/auction-server/src/opportunity/entities/opportunity_svm.rs +++ b/auction-server/src/opportunity/entities/opportunity_svm.rs @@ -12,10 +12,7 @@ use { crate::{ auction::entities::BidPaymentInstructionType, kernel::entities::PermissionKey, - opportunity::{ - entities::QuoteTokens, - repository, - }, + opportunity::repository, }, ::express_relay::FeeToken as ProgramFeeToken, express_relay_api_types::opportunity as api, @@ -60,7 +57,6 @@ impl PartialEq for FeeToken { pub struct OpportunitySvmProgramSwap { pub user_wallet_address: Pubkey, pub fee_token: FeeToken, - pub quote_tokens: QuoteTokens, pub referral_fee_bps: u16, // TODO*: these really should not live here. they should live in the opportunity core fields, but we don't want to introduce a breaking change. in any case, the need for the token programs is another sign that quotes should be separated from the traditional opportunity struct. pub input_token_program: Pubkey, @@ -128,7 +124,6 @@ impl Opportunity for OpportunitySvm { repository::OpportunityMetadataSvmProgramSwap { user_wallet_address: program.user_wallet_address, fee_token: program.fee_token, - quote_tokens: program.quote_tokens, referral_fee_bps: program.referral_fee_bps, input_token_program: program.input_token_program, output_token_program: program.output_token_program, @@ -296,7 +291,6 @@ impl TryFrom> for Op repository::OpportunityMetadataSvmProgram::Swap(program) => { OpportunitySvmProgram::Swap(OpportunitySvmProgramSwap { user_wallet_address: program.user_wallet_address, - quote_tokens: program.quote_tokens, fee_token: program.fee_token, referral_fee_bps: program.referral_fee_bps, input_token_program: program.input_token_program, @@ -339,7 +333,6 @@ impl From for OpportunityCreateSvm { referral_fee_bps, input_token_program, output_token_program, - quote_tokens, } => OpportunitySvmProgram::Swap(OpportunitySvmProgramSwap { user_wallet_address, // TODO*: see comment above about this arm @@ -347,7 +340,6 @@ impl From for OpportunityCreateSvm { referral_fee_bps, input_token_program, output_token_program, - quote_tokens: quote_tokens.into(), }), }; diff --git a/auction-server/src/opportunity/entities/quote.rs b/auction-server/src/opportunity/entities/quote.rs index 9594ad8c..173595d8 100644 --- a/auction-server/src/opportunity/entities/quote.rs +++ b/auction-server/src/opportunity/entities/quote.rs @@ -6,10 +6,6 @@ use { Deserialize, Serialize, }, - serde_with::{ - serde_as, - DisplayFromStr, - }, solana_sdk::{ pubkey::Pubkey, transaction::VersionedTransaction, @@ -36,17 +32,13 @@ pub struct QuoteCreate { } -#[serde_as] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(tag = "type", rename_all = "snake_case")] pub enum QuoteTokens { InputTokenSpecified { input_token: TokenAmountSvm, - #[serde_as(as = "DisplayFromStr")] output_token: Pubkey, }, OutputTokenSpecified { - #[serde_as(as = "DisplayFromStr")] input_token: Pubkey, output_token: TokenAmountSvm, }, diff --git a/auction-server/src/opportunity/repository/models.rs b/auction-server/src/opportunity/repository/models.rs index deecb2af..d2a01e2a 100644 --- a/auction-server/src/opportunity/repository/models.rs +++ b/auction-server/src/opportunity/repository/models.rs @@ -1,10 +1,7 @@ use { crate::{ models::ChainType, - opportunity::entities::{ - FeeToken, - QuoteTokens, - }, + opportunity::entities::FeeToken, }, ethers::types::{ Address, @@ -67,7 +64,6 @@ pub struct OpportunityMetadataSvmProgramSwap { #[serde_as(as = "DisplayFromStr")] pub user_wallet_address: Pubkey, pub fee_token: FeeToken, - pub quote_tokens: QuoteTokens, pub referral_fee_bps: u16, #[serde_as(as = "DisplayFromStr")] pub input_token_program: Pubkey, diff --git a/auction-server/src/opportunity/service/get_quote.rs b/auction-server/src/opportunity/service/get_quote.rs index 7acd6671..df400eb1 100644 --- a/auction-server/src/opportunity/service/get_quote.rs +++ b/auction-server/src/opportunity/service/get_quote.rs @@ -173,7 +173,6 @@ impl Service { // TODO*: we should determine this more intelligently fee_token: entities::FeeToken::InputToken, referral_fee_bps: quote_create.referral_fee_bps, - quote_tokens: quote_create.tokens, input_token_program, output_token_program, }) From f6ef525a31d1d2a60688117fdea51d31c1729af3 Mon Sep 17 00:00:00 2001 From: Amin Moghaddam Date: Fri, 17 Jan 2025 09:22:25 -0500 Subject: [PATCH 21/25] more fix --- .../src/auction/service/verification.rs | 12 +++---- sdk/js/src/index.ts | 2 +- sdk/js/src/serverTypes.d.ts | 8 ++--- sdk/js/src/types.ts | 32 +++++++++---------- 4 files changed, 27 insertions(+), 27 deletions(-) diff --git a/auction-server/src/auction/service/verification.rs b/auction-server/src/auction/service/verification.rs index 2518aba7..bbcb3682 100644 --- a/auction-server/src/auction/service/verification.rs +++ b/auction-server/src/auction/service/verification.rs @@ -594,13 +594,13 @@ impl Service { .first() .expect("Swap opportunity buy tokens must not be empty"); let quote_tokens = match (opp_sell_token.amount, opp_buy_token.amount) { - (0, _) => QuoteTokens::OutputTokenSpecified { - input_token: opp_sell_token.token, - output_token: opp_buy_token.clone(), + (0, _) => QuoteTokens::InputTokenSpecified { + output_token: opp_sell_token.token, + input_token: opp_buy_token.clone(), }, - (_, 0) => QuoteTokens::InputTokenSpecified { - input_token: opp_sell_token.clone(), - output_token: opp_buy_token.token, + (_, 0) => QuoteTokens::OutputTokenSpecified { + output_token: opp_sell_token.clone(), + input_token: opp_buy_token.token, }, _ => { panic!("Non zero amount for both sell and buy tokens in swap opportunity"); diff --git a/sdk/js/src/index.ts b/sdk/js/src/index.ts index 945d48a6..9dc882db 100644 --- a/sdk/js/src/index.ts +++ b/sdk/js/src/index.ts @@ -612,7 +612,7 @@ export class Client { }; } else if (opportunity.program === "swap") { let tokens; - if (opportunity.tokens.type === "input_token_specified") { + if (opportunity.tokens.side_specified === "input") { tokens = { type: "input_specified", inputToken: { diff --git a/sdk/js/src/serverTypes.d.ts b/sdk/js/src/serverTypes.d.ts index 2428d591..eab012c0 100644 --- a/sdk/js/src/serverTypes.d.ts +++ b/sdk/js/src/serverTypes.d.ts @@ -697,7 +697,7 @@ export interface components { output_token: components["schemas"]["Pubkey"]; output_token_program: components["schemas"]["Pubkey"]; /** @enum {string} */ - type: "input_token_specified"; + side_specified: "input"; } | { input_token: components["schemas"]["Pubkey"]; @@ -705,7 +705,7 @@ export interface components { output_token: components["schemas"]["TokenAmountSvm"]; output_token_program: components["schemas"]["Pubkey"]; /** @enum {string} */ - type: "output_token_specified"; + side_specified: "output"; }; /** * @description The user wallet address which requested the quote from the wallet. @@ -813,7 +813,7 @@ export interface components { output_token: components["schemas"]["Pubkey"]; output_token_program: components["schemas"]["Pubkey"]; /** @enum {string} */ - type: "input_token_specified"; + side_specified: "input"; } | { input_token: components["schemas"]["Pubkey"]; @@ -821,7 +821,7 @@ export interface components { output_token: components["schemas"]["TokenAmountSvm"]; output_token_program: components["schemas"]["Pubkey"]; /** @enum {string} */ - type: "output_token_specified"; + side_specified: "output"; }; QuoteV1Svm: { /** diff --git a/sdk/js/src/types.ts b/sdk/js/src/types.ts index 4b90f78f..85eae482 100644 --- a/sdk/js/src/types.ts +++ b/sdk/js/src/types.ts @@ -127,28 +127,28 @@ export type OpportunitySvmLimo = { program: "limo"; } & OpportunitySvmMetadata; +export type SvmSwapTokens = ( + | { + inputToken: PublicKey; + outputToken: TokenAmountSvm; + type: "output_specified"; + } + | { + inputToken: TokenAmountSvm; + outputToken: PublicKey; + type: "input_specified"; + } +) & { + inputTokenProgram: PublicKey; + outputTokenProgram: PublicKey; +}; export type OpportunitySvmSwap = { permissionAccount: PublicKey; routerAccount: PublicKey; userWalletAddress: PublicKey; feeToken: "input_token" | "output_token"; referralFeeBps: number; - // TODO: maybe type should be camelCase too? - tokens: ( - | { - inputToken: PublicKey; - outputToken: TokenAmountSvm; - type: "output_specified"; - } - | { - inputToken: TokenAmountSvm; - outputToken: PublicKey; - type: "input_specified"; - } - ) & { - inputTokenProgram: PublicKey; - outputTokenProgram: PublicKey; - }; + tokens: SvmSwapTokens; program: "swap"; } & OpportunitySvmMetadata; From 18e001321a803f7372a01c46a7b02aefe1495d8a Mon Sep 17 00:00:00 2001 From: Amin Moghaddam Date: Fri, 17 Jan 2025 10:54:47 -0500 Subject: [PATCH 22/25] Refactor and check token programs --- auction-server/build.rs | 18 + auction-server/src/auction/service/mod.rs | 2 + .../src/auction/service/verification.rs | 417 ++++++++++-------- auction-server/src/server.rs | 2 + 4 files changed, 266 insertions(+), 173 deletions(-) diff --git a/auction-server/build.rs b/auction-server/build.rs index b40677ef..e3302ae4 100644 --- a/auction-server/build.rs +++ b/auction-server/build.rs @@ -45,6 +45,8 @@ const SWAP_ROUTER_TOKEN_ACCOUNT_SVM: &str = "router_fee_receiver_ta"; const SWAP_USER_WALLET_ACCOUNT_SVM: &str = "trader"; const SWAP_MINT_INPUT_ACCOUNT_SVM: &str = "mint_input"; const SWAP_MINT_OUTPUT_ACCOUNT_SVM: &str = "mint_output"; +const SWAP_TOKEN_PROGRAM_INPUT_SVM: &str = "token_program_input"; +const SWAP_TOKEN_PROGRAM_OUTPUT_SVM: &str = "token_program_output"; const IDL_LOCATION: &str = "../contracts/svm/target/idl/express_relay.json"; fn extract_account_position(idl: Idl, instruction_name: &str, account_name: &str) -> usize { @@ -120,6 +122,22 @@ fn verify_and_extract_idl_data() { SWAP_MINT_OUTPUT_ACCOUNT_SVM, ) ); + println!( + "cargo::rustc-env=SWAP_TOKEN_PROGRAM_INPUT_POSITION={}", + extract_account_position( + express_relay_idl.clone(), + SWAP_INSTRUCTION_SVM, + SWAP_TOKEN_PROGRAM_INPUT_SVM, + ) + ); + println!( + "cargo::rustc-env=SWAP_TOKEN_PROGRAM_OUTPUT_POSITION={}", + extract_account_position( + express_relay_idl.clone(), + SWAP_INSTRUCTION_SVM, + SWAP_TOKEN_PROGRAM_OUTPUT_SVM, + ) + ); } fn build_svm_contracts() { diff --git a/auction-server/src/auction/service/mod.rs b/auction-server/src/auction/service/mod.rs index a7b2f605..9d2320f1 100644 --- a/auction-server/src/auction/service/mod.rs +++ b/auction-server/src/auction/service/mod.rs @@ -89,6 +89,8 @@ pub struct SwapInstructionAccountPositions { pub user_wallet_account: usize, pub mint_input_account: usize, pub mint_output_account: usize, + pub token_program_input: usize, + pub token_program_output: usize, } pub struct SubmitBidInstructionAccountPositions { diff --git a/auction-server/src/auction/service/verification.rs b/auction-server/src/auction/service/verification.rs index bbcb3682..77eba875 100644 --- a/auction-server/src/auction/service/verification.rs +++ b/auction-server/src/auction/service/verification.rs @@ -11,6 +11,7 @@ use { self, BidChainData, BidChainDataCreateSvm, + BidChainDataSwapCreateSvm, BidPaymentInstructionType, SubmitType, }, @@ -33,6 +34,7 @@ use { opportunity::{ self as opportunity, entities::{ + OpportunitySvm, OpportunitySvmProgram::Swap, QuoteTokens, }, @@ -105,6 +107,15 @@ pub trait Verification { ) -> Result, RestError>; } +struct SwapAccounts { + user_wallet: Pubkey, + mint_input: Pubkey, + mint_output: Pubkey, + router_token_account: Pubkey, + token_program_input: Pubkey, + token_program_output: Pubkey, +} + impl Service { pub fn get_simulation_call( &self, @@ -483,6 +494,188 @@ impl Service { } } + async fn check_svm_swap_bid_fields( + &self, + bid_data: &BidChainDataSwapCreateSvm, + opp: &OpportunitySvm, + ) -> Result<(), RestError> { + let swap_instruction = self.extract_express_relay_instruction( + bid_data.transaction.clone(), + BidPaymentInstructionType::Swap, + )?; + let swap_data = Self::extract_swap_data(&swap_instruction)?; + let SwapAccounts { + user_wallet, + mint_input, + mint_output, + token_program_input, + token_program_output, + .. + } = self + .extract_swap_accounts(&bid_data.transaction, &swap_instruction) + .await?; + let quote_tokens = Self::get_swap_quote_tokens(opp); + let opp_swap_data = match &opp.program { + Swap(opp_swap_data) => opp_swap_data, + _ => { + return Err(RestError::BadParameters(format!( + "Opportunity with id {} is not a swap opportunity", + bid_data.opportunity_id + ))); + } + }; + let ( + expected_input_token, + expected_input_amount, + expected_output_token, + expected_output_amount, + ) = match quote_tokens.clone() { + QuoteTokens::InputTokenSpecified { + input_token, + output_token, + .. + } => ( + input_token.token, + Some(input_token.amount), + output_token, + None, + ), + QuoteTokens::OutputTokenSpecified { + input_token, + output_token, + .. + } => ( + input_token, + None, + output_token.token, + Some(output_token.amount), + ), + }; + if user_wallet != opp_swap_data.user_wallet_address { + return Err(RestError::BadParameters( + format!( + "Invalid wallet address {} in swap instruction. Value does not match the wallet address in swap opportunity {}", + user_wallet, opp_swap_data.user_wallet_address + ), + )); + } + if expected_input_token != mint_input { + return Err(RestError::BadParameters( + format!( + "Invalid input token {} in swap instruction. Value does not match the input token in swap opportunity {}", + mint_input, expected_input_token + ), + )); + } + if expected_output_token != mint_output { + return Err(RestError::BadParameters( + format!( + "Invalid output token {} in swap instruction. Value does not match the output token in swap opportunity {}", + mint_output, expected_output_token + ), + )); + } + + if token_program_input != opp_swap_data.input_token_program { + return Err(RestError::BadParameters( + format!( + "Invalid input token program {} in swap instruction. Value does not match the input token program in swap opportunity {}", + token_program_input, opp_swap_data.input_token_program + ), + )); + } + + if token_program_output != opp_swap_data.output_token_program { + return Err(RestError::BadParameters( + format!( + "Invalid output token program {} in swap instruction. Value does not match the output token program in swap opportunity {}", + token_program_output, opp_swap_data.output_token_program + ), + )); + } + + + if let Some(expected_input_amount) = expected_input_amount { + if expected_input_amount != swap_data.amount_input { + return Err(RestError::BadParameters( + format!( + "Invalid input amount {} in swap instruction. Value does not match the input amount in swap opportunity {}", + swap_data.amount_input, expected_input_amount + ), + )); + } + } + if let Some(expected_output_amount) = expected_output_amount { + if expected_output_amount != swap_data.amount_output { + return Err(RestError::BadParameters( + format!( + "Invalid output amount {} in swap instruction. Value does not match the output amount in swap opportunity {}", + swap_data.amount_output, expected_output_amount + ), + )); + } + } + if opp_swap_data.fee_token != swap_data.fee_token { + return Err(RestError::BadParameters( + format!( + "Invalid fee token {:?} in swap instruction. Value does not match the fee token in swap opportunity {:?}", + swap_data.fee_token, opp_swap_data.fee_token + ), + )); + } + + if swap_data.referral_fee_bps != opp_swap_data.referral_fee_bps { + return Err(RestError::BadParameters( + format!( + "Invalid referral fee bps {} in swap instruction. Value does not match the referral fee bps in swap opportunity {}", + swap_data.referral_fee_bps, opp_swap_data.referral_fee_bps + ), + )); + } + Ok(()) + } + + async fn extract_swap_accounts( + &self, + tx: &VersionedTransaction, + swap_instruction: &CompiledInstruction, + ) -> Result { + let positions = &self + .config + .chain_config + .express_relay + .swap_instruction_account_positions; + + + let user_wallet = self + .extract_account(tx, swap_instruction, positions.user_wallet_account) + .await?; + let mint_input = self + .extract_account(tx, swap_instruction, positions.mint_input_account) + .await?; + let mint_output = self + .extract_account(tx, swap_instruction, positions.mint_output_account) + .await?; + let router_token_account = self + .extract_account(tx, swap_instruction, positions.router_token_account) + .await?; + let token_program_input = self + .extract_account(tx, swap_instruction, positions.token_program_input) + .await?; + let token_program_output = self + .extract_account(tx, swap_instruction, positions.token_program_output) + .await?; + + Ok(SwapAccounts { + user_wallet, + mint_input, + mint_output, + router_token_account, + token_program_input, + token_program_output, + }) + } + async fn extract_bid_data( &self, bid_chain_data_create_svm: &BidChainDataCreateSvm, @@ -529,41 +722,6 @@ impl Service { }) } BidChainDataCreateSvm::Swap(bid_data) => { - let swap_instruction = self.extract_express_relay_instruction( - bid_data.transaction.clone(), - BidPaymentInstructionType::Swap, - )?; - let swap_data = Self::extract_swap_data(&swap_instruction)?; - - let user_wallet = self - .extract_account( - &bid_data.transaction, - &swap_instruction, - svm_config - .swap_instruction_account_positions - .user_wallet_account, - ) - .await?; - - let mint_input = self - .extract_account( - &bid_data.transaction, - &swap_instruction, - svm_config - .swap_instruction_account_positions - .mint_input_account, - ) - .await?; - let mint_output = self - .extract_account( - &bid_data.transaction, - &swap_instruction, - svm_config - .swap_instruction_account_positions - .mint_output_account, - ) - .await?; - let opp = self .opportunity_service .get_live_opportunity_by_id(GetLiveOpportunityByIdInput { @@ -573,133 +731,31 @@ impl Service { .ok_or(RestError::BadParameters( "No swap opportunity with the given id found".to_string(), ))?; + self.check_svm_swap_bid_fields(bid_data, &opp).await?; - let opp_swap_data = match opp.program { - Swap(opp_swap_data) => opp_swap_data, - _ => { - return Err(RestError::BadParameters(format!( - "Opportunity with id {} is not a swap opportunity", - bid_data.opportunity_id - ))); - } - }; - let opp_sell_token = opp - .core_fields - .sell_tokens - .first() - .expect("Swap opportunity sell tokens must not be empty"); - let opp_buy_token = opp - .core_fields - .buy_tokens - .first() - .expect("Swap opportunity buy tokens must not be empty"); - let quote_tokens = match (opp_sell_token.amount, opp_buy_token.amount) { - (0, _) => QuoteTokens::InputTokenSpecified { - output_token: opp_sell_token.token, - input_token: opp_buy_token.clone(), - }, - (_, 0) => QuoteTokens::OutputTokenSpecified { - output_token: opp_sell_token.clone(), - input_token: opp_buy_token.token, - }, - _ => { - panic!("Non zero amount for both sell and buy tokens in swap opportunity"); - } - }; - let ( - bid_amount, - expected_input_token, - expected_input_amount, - expected_output_token, - expected_output_amount, - ) = match quote_tokens.clone() { - QuoteTokens::InputTokenSpecified { - input_token, - output_token, - .. - } => ( - swap_data.amount_output, - input_token.token, - Some(input_token.amount), - output_token, - None, - ), - QuoteTokens::OutputTokenSpecified { - input_token, - output_token, - .. - } => ( - swap_data.amount_input, - input_token, - None, - output_token.token, - Some(output_token.amount), - ), + let swap_instruction = self.extract_express_relay_instruction( + bid_data.transaction.clone(), + BidPaymentInstructionType::Swap, + )?; + let swap_data = Self::extract_swap_data(&swap_instruction)?; + let SwapAccounts { + user_wallet, + mint_input, + mint_output, + router_token_account, + token_program_input, + token_program_output, + } = self + .extract_swap_accounts(&bid_data.transaction, &swap_instruction) + .await?; + let quote_tokens = Self::get_swap_quote_tokens(&opp); + let bid_amount = match quote_tokens.clone() { + QuoteTokens::InputTokenSpecified { .. } => swap_data.amount_output, + QuoteTokens::OutputTokenSpecified { .. } => swap_data.amount_input, }; - if user_wallet != opp_swap_data.user_wallet_address { - return Err(RestError::BadParameters( - format!( - "User wallet address in swap opportunity {} does not match the user wallet address in the swap instruction {}", - opp_swap_data.user_wallet_address, user_wallet - ), - )); - } - if expected_input_token != mint_input { - return Err(RestError::BadParameters( - format!( - "Input token in swap opportunity {} does not match the input token in the swap instruction {}", - expected_input_token, mint_input - ), - )); - } - if expected_output_token != mint_output { - return Err(RestError::BadParameters( - format!( - "Output token in swap opportunity {} does not match the output token in the swap instruction {}", - expected_output_token, mint_output - ), - )); - } - if let Some(expected_input_amount) = expected_input_amount { - if expected_input_amount != swap_data.amount_input { - return Err(RestError::BadParameters( - format!( - "Input amount in swap opportunity {} does not match the input amount in the swap instruction {}", - expected_input_amount, swap_data.amount_input - ), - )); - } - } - if let Some(expected_output_amount) = expected_output_amount { - if expected_output_amount != swap_data.amount_output { - return Err(RestError::BadParameters( - format!( - "Output amount in swap opportunity {} does not match the output amount in the swap instruction {}", - expected_output_amount, swap_data.amount_output - ), - )); - } - } - if opp_swap_data.fee_token != swap_data.fee_token { - return Err(RestError::BadParameters( - format!( - "Fee token in swap opportunity {:?} does not match the fee token in the swap instruction {:?}", - opp_swap_data.fee_token, swap_data.fee_token - ), - )); - } - - if swap_data.referral_fee_bps != opp_swap_data.referral_fee_bps { - return Err(RestError::BadParameters( - format!( - "Referral fee bps in swap opportunity {} does not match the referral fee bps in the swap instruction {}", - opp_swap_data.referral_fee_bps, swap_data.referral_fee_bps - ), - )); - } let (fee_token, fee_token_program) = match swap_data.fee_token { - FeeToken::Input => (mint_input, opp_swap_data.input_token_program), - FeeToken::Output => (mint_output, opp_swap_data.output_token_program), + FeeToken::Input => (mint_input, token_program_input), + FeeToken::Output => (mint_output, token_program_output), }; // ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL @@ -708,7 +764,7 @@ impl Service { 19, 153, 218, 255, 16, 132, 4, 142, 123, 216, 219, 233, 248, 89, ]); - let expected_router_fee_receiver_ta = Pubkey::find_program_address( + let expected_router_token_account = Pubkey::find_program_address( &[ &opp.router.to_bytes(), &fee_token_program.to_bytes(), @@ -717,21 +773,10 @@ impl Service { &ASSOCIATED_TOKEN_PROGRAM, ) .0; - let router_fee_receiver_ta = self - .extract_account( - &bid_data.transaction, - &swap_instruction, - self.config - .chain_config - .express_relay - .swap_instruction_account_positions - .router_token_account, - ) - .await?; - if router_fee_receiver_ta != expected_router_fee_receiver_ta { + if router_token_account != expected_router_token_account { return Err(RestError::BadParameters( - format!("Associated token account for router does not match. Expected: {:?} found: {:?}", expected_router_fee_receiver_ta, router_fee_receiver_ta), + format!("Associated token account for router does not match. Expected: {:?} found: {:?}", expected_router_token_account, router_token_account), )); } @@ -759,6 +804,32 @@ impl Service { } } + fn get_swap_quote_tokens(opp: &OpportunitySvm) -> QuoteTokens { + let opp_sell_token = opp + .core_fields + .sell_tokens + .first() + .expect("Swap opportunity sell tokens must not be empty"); + let opp_buy_token = opp + .core_fields + .buy_tokens + .first() + .expect("Swap opportunity buy tokens must not be empty"); + match (opp_sell_token.amount, opp_buy_token.amount) { + (0, _) => QuoteTokens::InputTokenSpecified { + output_token: opp_sell_token.token, + input_token: opp_buy_token.clone(), + }, + (_, 0) => QuoteTokens::OutputTokenSpecified { + output_token: opp_sell_token.clone(), + input_token: opp_buy_token.token, + }, + _ => { + panic!("Non zero amount for both sell and buy tokens in swap opportunity"); + } + } + } + fn relayer_signer_exists( &self, accounts: &[Pubkey], diff --git a/auction-server/src/server.rs b/auction-server/src/server.rs index b5515e55..45f214b9 100644 --- a/auction-server/src/server.rs +++ b/auction-server/src/server.rs @@ -219,6 +219,8 @@ fn get_swap_instruction_account_positions() -> SwapInstructionAccountPositions { user_wallet_account: read_svm_position_env!("SWAP_USER_WALLET_ACCOUNT_POSITION"), mint_input_account: read_svm_position_env!("SWAP_MINT_INPUT_ACCOUNT_POSITION"), mint_output_account: read_svm_position_env!("SWAP_MINT_OUTPUT_ACCOUNT_POSITION"), + token_program_input: read_svm_position_env!("SWAP_TOKEN_PROGRAM_INPUT_POSITION"), + token_program_output: read_svm_position_env!("SWAP_TOKEN_PROGRAM_OUTPUT_POSITION"), } } From fc55a922c65d9fad8be357e5b688deca6602cf5b Mon Sep 17 00:00:00 2001 From: Amin Moghaddam Date: Fri, 17 Jan 2025 11:12:46 -0500 Subject: [PATCH 23/25] Include router ta in permission --- .../src/auction/service/verification.rs | 24 ++--- .../src/opportunity/service/get_quote.rs | 90 +++++++++++++------ 2 files changed, 71 insertions(+), 43 deletions(-) diff --git a/auction-server/src/auction/service/verification.rs b/auction-server/src/auction/service/verification.rs index 77eba875..16c77f2c 100644 --- a/auction-server/src/auction/service/verification.rs +++ b/auction-server/src/auction/service/verification.rs @@ -41,7 +41,10 @@ use { service::{ get_live_opportunities::GetLiveOpportunitiesInput, get_opportunities::GetLiveOpportunityByIdInput, - get_quote::get_quote_virtual_permission_account, + get_quote::{ + get_associated_token_account, + get_quote_virtual_permission_account, + }, }, }, }, @@ -757,22 +760,8 @@ impl Service { FeeToken::Input => (mint_input, token_program_input), FeeToken::Output => (mint_output, token_program_output), }; - - // ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL - const ASSOCIATED_TOKEN_PROGRAM: Pubkey = Pubkey::new_from_array([ - 140, 151, 37, 143, 78, 36, 137, 241, 187, 61, 16, 41, 20, 142, 13, 131, 11, 90, - 19, 153, 218, 255, 16, 132, 4, 142, 123, 216, 219, 233, 248, 89, - ]); - - let expected_router_token_account = Pubkey::find_program_address( - &[ - &opp.router.to_bytes(), - &fee_token_program.to_bytes(), - &fee_token.to_bytes(), - ], - &ASSOCIATED_TOKEN_PROGRAM, - ) - .0; + let expected_router_token_account = + get_associated_token_account(&opp.router, &fee_token_program, &fee_token); if router_token_account != expected_router_token_account { return Err(RestError::BadParameters( @@ -783,6 +772,7 @@ impl Service { let permission_account = get_quote_virtual_permission_account( "e_tokens, &user_wallet, + &router_token_account, swap_data.referral_fee_bps, ); diff --git a/auction-server/src/opportunity/service/get_quote.rs b/auction-server/src/opportunity/service/get_quote.rs index df400eb1..2fce4476 100644 --- a/auction-server/src/opportunity/service/get_quote.rs +++ b/auction-server/src/opportunity/service/get_quote.rs @@ -55,12 +55,37 @@ pub struct GetQuoteInput { pub program: ProgramSvm, } +pub fn get_associated_token_account( + owner: &Pubkey, + token_program: &Pubkey, + token: &Pubkey, +) -> Pubkey { + // ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL + const ASSOCIATED_TOKEN_PROGRAM: Pubkey = Pubkey::new_from_array([ + 140, 151, 37, 143, 78, 36, 137, 241, 187, 61, 16, 41, 20, 142, 13, 131, 11, 90, 19, 153, + 218, 255, 16, 132, 4, 142, 123, 216, 219, 233, 248, 89, + ]); + + Pubkey::find_program_address( + &[ + &owner.to_bytes(), + &token_program.to_bytes(), + &token.to_bytes(), + ], + &ASSOCIATED_TOKEN_PROGRAM, + ) + .0 +} + +/// Get a pubkey based on router_token_account, user_wallet_address, referral_fee_bps, mints, and token amounts +/// This pubkey is never mentioned on-chain and is only used internally +/// to distinguish between different swap bids pub fn get_quote_virtual_permission_account( tokens: &entities::QuoteTokens, user_wallet_address: &Pubkey, + router_token_account: &Pubkey, referral_fee_bps: u16, ) -> Pubkey { - // get pda seeded by user_wallet_address, referral_fee_bps, mints, and token amount let input_token_amount: [u8; 8]; let output_token_amount: [u8; 8]; let referral_fee_bps = referral_fee_bps.to_le_bytes(); @@ -73,6 +98,7 @@ pub fn get_quote_virtual_permission_account( let output_token_mint = output_token.as_ref(); input_token_amount = input_token.amount.to_le_bytes(); [ + router_token_account.as_ref(), user_wallet_address.as_ref(), input_token_mint, input_token_amount.as_ref(), @@ -88,6 +114,7 @@ pub fn get_quote_virtual_permission_account( let output_token_mint = output_token.token.as_ref(); output_token_amount = output_token.amount.to_le_bytes(); [ + router_token_account.as_ref(), user_wallet_address.as_ref(), input_token_mint, output_token_mint, @@ -108,11 +135,8 @@ impl Service { program: &ProgramSvm, ) -> Result { let router = quote_create.router; - let permission_account = get_quote_virtual_permission_account( - "e_create.tokens, - "e_create.user_wallet_address, - quote_create.referral_fee_bps, - ); + // TODO*: we should determine this more intelligently + let fee_token = entities::FeeToken::InputToken; // TODO*: we should fix the Opportunity struct (or create a new format) to more clearly distinguish Swap opps from traditional opps // currently, we are using the same struct and just setting the unspecified token amount to 0 @@ -127,24 +151,6 @@ impl Service { output_token, } => (input_token, 0, output_token.token, output_token.amount), }; - - let core_fields = entities::OpportunityCoreFieldsCreate { - permission_key: entities::OpportunitySvm::get_permission_key( - BidPaymentInstructionType::Swap, - router, - permission_account, - ), - chain_id: quote_create.chain_id.clone(), - sell_tokens: vec![entities::TokenAmountSvm { - token: output_mint, - amount: output_amount, - }], - buy_tokens: vec![entities::TokenAmountSvm { - token: input_mint, - amount: input_amount, - }], - }; - let input_token_program = self .get_token_program(GetTokenProgramInput { chain_id: quote_create.chain_id.clone(), @@ -166,12 +172,44 @@ impl Service { RestError::BadParameters("Output token program not found".to_string()) })?; + + let router_token_account = match fee_token { + entities::FeeToken::InputToken => { + get_associated_token_account(&router, &input_token_program, &input_mint) + } + entities::FeeToken::OutputToken => { + get_associated_token_account(&router, &output_token_program, &output_mint) + } + }; + let permission_account = get_quote_virtual_permission_account( + "e_create.tokens, + "e_create.user_wallet_address, + &router_token_account, + quote_create.referral_fee_bps, + ); + + let core_fields = entities::OpportunityCoreFieldsCreate { + permission_key: entities::OpportunitySvm::get_permission_key( + BidPaymentInstructionType::Swap, + router, + permission_account, + ), + chain_id: quote_create.chain_id.clone(), + sell_tokens: vec![entities::TokenAmountSvm { + token: output_mint, + amount: output_amount, + }], + buy_tokens: vec![entities::TokenAmountSvm { + token: input_mint, + amount: input_amount, + }], + }; + let program_opportunity = match program { ProgramSvm::Swap => { entities::OpportunitySvmProgram::Swap(entities::OpportunitySvmProgramSwap { user_wallet_address: quote_create.user_wallet_address, - // TODO*: we should determine this more intelligently - fee_token: entities::FeeToken::InputToken, + fee_token, referral_fee_bps: quote_create.referral_fee_bps, input_token_program, output_token_program, From df595066654f8e1fc145b3ff8fbff1d8f034a2b8 Mon Sep 17 00:00:00 2001 From: Amin Moghaddam Date: Fri, 17 Jan 2025 11:42:07 -0500 Subject: [PATCH 24/25] Fix input/output to sell/buy token mapping --- .../src/auction/service/verification.rs | 30 +------ .../opportunity/entities/opportunity_svm.rs | 83 ++++++++++++------- .../src/opportunity/service/get_quote.rs | 20 ++--- 3 files changed, 65 insertions(+), 68 deletions(-) diff --git a/auction-server/src/auction/service/verification.rs b/auction-server/src/auction/service/verification.rs index 16c77f2c..480c4ce8 100644 --- a/auction-server/src/auction/service/verification.rs +++ b/auction-server/src/auction/service/verification.rs @@ -34,6 +34,7 @@ use { opportunity::{ self as opportunity, entities::{ + get_swap_quote_tokens, OpportunitySvm, OpportunitySvmProgram::Swap, QuoteTokens, @@ -517,7 +518,7 @@ impl Service { } = self .extract_swap_accounts(&bid_data.transaction, &swap_instruction) .await?; - let quote_tokens = Self::get_swap_quote_tokens(opp); + let quote_tokens = get_swap_quote_tokens(opp); let opp_swap_data = match &opp.program { Swap(opp_swap_data) => opp_swap_data, _ => { @@ -751,7 +752,7 @@ impl Service { } = self .extract_swap_accounts(&bid_data.transaction, &swap_instruction) .await?; - let quote_tokens = Self::get_swap_quote_tokens(&opp); + let quote_tokens = get_swap_quote_tokens(&opp); let bid_amount = match quote_tokens.clone() { QuoteTokens::InputTokenSpecified { .. } => swap_data.amount_output, QuoteTokens::OutputTokenSpecified { .. } => swap_data.amount_input, @@ -794,31 +795,6 @@ impl Service { } } - fn get_swap_quote_tokens(opp: &OpportunitySvm) -> QuoteTokens { - let opp_sell_token = opp - .core_fields - .sell_tokens - .first() - .expect("Swap opportunity sell tokens must not be empty"); - let opp_buy_token = opp - .core_fields - .buy_tokens - .first() - .expect("Swap opportunity buy tokens must not be empty"); - match (opp_sell_token.amount, opp_buy_token.amount) { - (0, _) => QuoteTokens::InputTokenSpecified { - output_token: opp_sell_token.token, - input_token: opp_buy_token.clone(), - }, - (_, 0) => QuoteTokens::OutputTokenSpecified { - output_token: opp_sell_token.clone(), - input_token: opp_buy_token.token, - }, - _ => { - panic!("Non zero amount for both sell and buy tokens in swap opportunity"); - } - } - } fn relayer_signer_exists( &self, diff --git a/auction-server/src/opportunity/entities/opportunity_svm.rs b/auction-server/src/opportunity/entities/opportunity_svm.rs index dad923dd..1ae8d929 100644 --- a/auction-server/src/opportunity/entities/opportunity_svm.rs +++ b/auction-server/src/opportunity/entities/opportunity_svm.rs @@ -12,7 +12,10 @@ use { crate::{ auction::entities::BidPaymentInstructionType, kernel::entities::PermissionKey, - opportunity::repository, + opportunity::{ + entities::QuoteTokens, + repository, + }, }, ::express_relay::FeeToken as ProgramFeeToken, express_relay_api_types::opportunity as api, @@ -191,7 +194,34 @@ impl From for api::Opportunity { api::Opportunity::Svm(val.into()) } } - +pub fn get_swap_quote_tokens(opp: &OpportunitySvm) -> QuoteTokens { + if !matches!(opp.program, OpportunitySvmProgram::Swap(_)) { + panic!("Opportunity must be a swap opportunity to get quote tokens"); + } + let opp_sell_token = opp + .core_fields + .sell_tokens + .first() + .expect("Swap opportunity sell tokens must not be empty"); + let opp_buy_token = opp + .core_fields + .buy_tokens + .first() + .expect("Swap opportunity buy tokens must not be empty"); + match (opp_sell_token.amount, opp_buy_token.amount) { + (0, _) => QuoteTokens::OutputTokenSpecified { + input_token: opp_sell_token.token, + output_token: opp_buy_token.clone(), + }, + (_, 0) => QuoteTokens::InputTokenSpecified { + input_token: opp_sell_token.clone(), + output_token: opp_buy_token.token, + }, + _ => { + panic!("Non zero amount for both sell and buy tokens in swap opportunity"); + } + } +} impl From for api::OpportunitySvm { fn from(val: OpportunitySvm) -> Self { let program = match val.program.clone() { @@ -200,38 +230,29 @@ impl From for api::OpportunitySvm { order_address: program.order_address, }, OpportunitySvmProgram::Swap(program) => { - let buy_token = val - .buy_tokens - .first() - .ok_or(anyhow::anyhow!( - "Failed to get buy token from opportunity svm" - )) - .expect("Failed to get buy token from opportunity svm"); - let sell_token = val - .sell_tokens - .first() - .ok_or(anyhow::anyhow!( - "Failed to get sell token from opportunity svm" - )) - .expect("Failed to get sell token from opportunity svm"); - let tokens = if buy_token.amount == 0 { - api::QuoteTokens::OutputTokenSpecified { - input_token: buy_token.token, - output_token: sell_token.clone().into(), - input_token_program: program.input_token_program, + let quote_tokens = get_swap_quote_tokens(&val); + + let tokens = match quote_tokens { + QuoteTokens::InputTokenSpecified { + input_token, + output_token, + } => api::QuoteTokens::InputTokenSpecified { + input_token: input_token.into(), + output_token, + input_token_program: program.input_token_program, output_token_program: program.output_token_program, - } - } else { - if sell_token.amount != 0 { - tracing::error!(opportunity = ?val, "Both token amounts are specified for swap opportunity"); - } - api::QuoteTokens::InputTokenSpecified { - input_token: buy_token.clone().into(), - output_token: sell_token.token, - input_token_program: program.input_token_program, + }, + QuoteTokens::OutputTokenSpecified { + input_token, + output_token, + } => api::QuoteTokens::OutputTokenSpecified { + input_token, + output_token: output_token.into(), + input_token_program: program.input_token_program, output_token_program: program.output_token_program, - } + }, }; + let fee_token = match program.fee_token { FeeToken::InputToken => api::FeeToken::InputToken, FeeToken::OutputToken => api::FeeToken::OutputToken, diff --git a/auction-server/src/opportunity/service/get_quote.rs b/auction-server/src/opportunity/service/get_quote.rs index 2fce4476..503545c9 100644 --- a/auction-server/src/opportunity/service/get_quote.rs +++ b/auction-server/src/opportunity/service/get_quote.rs @@ -196,13 +196,13 @@ impl Service { ), chain_id: quote_create.chain_id.clone(), sell_tokens: vec![entities::TokenAmountSvm { - token: output_mint, - amount: output_amount, - }], - buy_tokens: vec![entities::TokenAmountSvm { token: input_mint, amount: input_amount, }], + buy_tokens: vec![entities::TokenAmountSvm { + token: output_mint, + amount: output_amount, + }], }; let program_opportunity = match program { @@ -271,15 +271,17 @@ impl Service { opportunity: opportunity_create, }) .await?; - let input_token = opportunity.buy_tokens[0].clone(); - let output_token = opportunity.sell_tokens[0].clone(); + let input_token = opportunity.sell_tokens[0].clone(); + let output_token = opportunity.buy_tokens[0].clone(); if input_token.amount == 0 && output_token.amount == 0 { - tracing::error!(opportunity = ?opportunity, "Both token amounts are zero for swap opportunity"); return Err(RestError::BadParameters( - "Both token amounts are zero for swap opportunity".to_string(), + "Token amount can not be zero".to_string(), )); } + // Wait to make sure searchers had enough time to submit bids + sleep(BID_COLLECTION_TIME).await; + // NOTE: This part will be removed after refactoring the permission key type let slice: [u8; 65] = opportunity .permission_key @@ -287,8 +289,6 @@ impl Service { .try_into() .expect("Failed to convert permission key to slice"); let permission_key_svm = PermissionKeySvm(slice); - // Wait to make sure searchers had enough time to submit bids - sleep(BID_COLLECTION_TIME).await; let bid_collection_time = OffsetDateTime::now_utc(); let mut bids = auction_service From f71b5779e9624938f6f1b3c7c348f6d0e93c5fe7 Mon Sep 17 00:00:00 2001 From: Amin Moghaddam Date: Fri, 17 Jan 2025 11:45:39 -0500 Subject: [PATCH 25/25] nit --- .../src/opportunity/entities/opportunity_svm.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/auction-server/src/opportunity/entities/opportunity_svm.rs b/auction-server/src/opportunity/entities/opportunity_svm.rs index 1ae8d929..e377971e 100644 --- a/auction-server/src/opportunity/entities/opportunity_svm.rs +++ b/auction-server/src/opportunity/entities/opportunity_svm.rs @@ -209,14 +209,14 @@ pub fn get_swap_quote_tokens(opp: &OpportunitySvm) -> QuoteTokens { .first() .expect("Swap opportunity buy tokens must not be empty"); match (opp_sell_token.amount, opp_buy_token.amount) { - (0, _) => QuoteTokens::OutputTokenSpecified { - input_token: opp_sell_token.token, - output_token: opp_buy_token.clone(), - }, (_, 0) => QuoteTokens::InputTokenSpecified { input_token: opp_sell_token.clone(), output_token: opp_buy_token.token, }, + (0, _) => QuoteTokens::OutputTokenSpecified { + input_token: opp_sell_token.token, + output_token: opp_buy_token.clone(), + }, _ => { panic!("Non zero amount for both sell and buy tokens in swap opportunity"); }