From a77cc2311797edb5bb2e1a98ef3da6f2c889d33a Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Fri, 28 Mar 2025 20:29:06 +0000 Subject: [PATCH 01/15] python done --- auction-server/src/api.rs | 18 ++++----- .../src/auction/service/verification.rs | 14 +++++-- .../opportunity/entities/opportunity_svm.rs | 37 +++++++++++++++++-- sdk/python/express_relay/client.py | 25 ++++++++++++- sdk/python/express_relay/models/svm.py | 1 + 5 files changed, 79 insertions(+), 16 deletions(-) diff --git a/auction-server/src/api.rs b/auction-server/src/api.rs index 38c17bfc4..eb761832c 100644 --- a/auction-server/src/api.rs +++ b/auction-server/src/api.rs @@ -167,21 +167,21 @@ impl std::fmt::Display for InstructionError { write!( f, "Invalid from account in sol transfer instruction. Expected: {:?} found: {:?}", - found, expected + expected, found ) } InstructionError::InvalidToAccountTransferInstruction { expected, found } => { write!( f, "Invalid to account in sol transfer instruction. Expected: {:?} found: {:?}", - found, expected + expected, found ) } InstructionError::InvalidAmountTransferInstruction { expected, found } => { write!( f, "Invalid amount in sol transfer instruction. Expected: {:?} found: {:?}", - found, expected + expected, found ) } InstructionError::InvalidSyncNativeInstructionCount(address) => { @@ -208,42 +208,42 @@ impl std::fmt::Display for InstructionError { write!( f, "Invalid destination account in close account instruction. Expected: {:?} found: {:?}", - found, expected + expected, found ) } InstructionError::InvalidOwnerCloseAccountInstruction { expected, found } => { write!( f, "Invalid owner in close account instruction. Expected: {:?} found: {:?}", - found, expected + expected, found ) } InstructionError::InvalidMintInCreateAtaInstruction { expected, found } => { write!( f, "Invalid mint in create associated token account instruction. Expected: {:?} found: {:?}", - found, expected + expected, found ) } InstructionError::InvalidOwnerInCreateAtaInstruction { expected, found } => { write!( f, "Invalid owner in create associated token account instruction. Expected: {:?} found: {:?}", - found, expected + expected, found ) } InstructionError::InvalidPayerInCreateAtaInstruction { expected, found } => { write!( f, "Invalid payer in create associated token account instruction. Expected: {:?} found: {:?}", - found, expected + expected, found ) } InstructionError::InvalidTokenProgramInCreateAtaInstruction { expected, found } => { write!( f, "Invalid token program in create associated token account instruction. Expected: {:?} found: {:?}", - found, expected + expected, found ) } InstructionError::InvalidSystemProgramInCreateAtaInstruction(program) => { diff --git a/auction-server/src/auction/service/verification.rs b/auction-server/src/auction/service/verification.rs index bc2d0d427..8c8367d9d 100644 --- a/auction-server/src/auction/service/verification.rs +++ b/auction-server/src/auction/service/verification.rs @@ -832,6 +832,7 @@ impl Service { tx: &VersionedTransaction, swap_data: &express_relay_svm::SwapArgs, swap_accounts: &SwapAccounts, + opportunity_swap_data: &OpportunitySvmProgramSwap, ) -> Result<(), RestError> { let transfer_instructions = self.extract_transfer_instructions(tx).await?; if transfer_instructions.len() > 1 { @@ -845,6 +846,11 @@ impl Service { // User have to wrap Sol if swap_accounts.mint_user == spl_token::native_mint::id() { + // Sometimes the user doesn't have enought SOL, but we want the transaction to fail in the Expert Relay program with InsufficientUserFunds + // Therefore we allow the user to wrap less SOL than needed to it doesn't fail in the transfer instruction + let amount_user_to_wrap = + opportunity_swap_data.get_user_amount_to_wrap(swap_data.amount_user); + if transfer_instructions.len() != 1 { return Err(RestError::InvalidInstruction( None, @@ -874,11 +880,11 @@ impl Service { }, )); } - if swap_data.amount_user != transfer_instruction.lamports { + if amount_user_to_wrap != transfer_instruction.lamports { return Err(RestError::InvalidInstruction( Some(transfer_instruction.index), InstructionError::InvalidAmountTransferInstruction { - expected: swap_data.amount_user, + expected: amount_user_to_wrap, found: transfer_instruction.lamports, }, )); @@ -1300,8 +1306,9 @@ impl Service { tx: &VersionedTransaction, swap_data: &express_relay_svm::SwapArgs, swap_accounts: &SwapAccounts, + opportunity_swap_data: &OpportunitySvmProgramSwap, ) -> Result<(), RestError> { - self.check_transfer_instruction(tx, swap_data, swap_accounts) + self.check_transfer_instruction(tx, swap_data, swap_accounts, opportunity_swap_data) .await?; if swap_accounts.mint_user == spl_token::native_mint::id() { // User has to wrap Sol @@ -1410,6 +1417,7 @@ impl Service { &bid_data.transaction, &swap_data, &swap_accounts, + &opportunity_swap_data, ) .await?; diff --git a/auction-server/src/opportunity/entities/opportunity_svm.rs b/auction-server/src/opportunity/entities/opportunity_svm.rs index 07c0ffb5b..4a2013a81 100644 --- a/auction-server/src/opportunity/entities/opportunity_svm.rs +++ b/auction-server/src/opportunity/entities/opportunity_svm.rs @@ -19,9 +19,9 @@ use { }, ::express_relay::FeeToken as ProgramFeeToken, express_relay::state::FEE_SPLIT_PRECISION, - express_relay_api_types::{ - opportunity as api, - opportunity::QuoteTokensWithTokenPrograms, + express_relay_api_types::opportunity::{ + self as api, + QuoteTokensWithTokenPrograms, }, serde::{ Deserialize, @@ -29,7 +29,13 @@ use { }, solana_sdk::{ clock::Slot, + program_pack::Pack, pubkey::Pubkey, + rent::Rent, + }, + spl_token_2022::{ + extension::StateWithExtensions, + state::Account as TokenAccount, }, std::ops::Deref, time::{ @@ -277,6 +283,31 @@ pub fn get_opportunity_swap_data(opp: &OpportunitySvm) -> &OpportunitySvmProgram } } +impl OpportunitySvmProgramSwap { + pub fn get_user_amount_to_wrap(&self, amount_user: u64) -> u64 { + let number_of_paid_atas_by_user = match ( + &self.token_account_initialization_configs.user_ata_mint_user, + &self + .token_account_initialization_configs + .user_ata_mint_searcher, + ) { + ( + TokenAccountInitializationConfig::UserPayer, + TokenAccountInitializationConfig::UserPayer, + ) => 2, + (TokenAccountInitializationConfig::UserPayer, _) => 1, + (_, TokenAccountInitializationConfig::UserPayer) => 1, + _ => 0, + }; + std::cmp::min( + amount_user, + self.user_mint_user_balance.saturating_sub( + number_of_paid_atas_by_user * Rent::default().minimum_balance(TokenAccount::LEN), + ), + ) + } +} + impl From for api::TokenAccountInitializationConfig { fn from(val: TokenAccountInitializationConfig) -> Self { match val { diff --git a/sdk/python/express_relay/client.py b/sdk/python/express_relay/client.py index ec160f620..c293b25c1 100644 --- a/sdk/python/express_relay/client.py +++ b/sdk/python/express_relay/client.py @@ -651,6 +651,24 @@ def get_token_accounts_to_create( ) ) + @staticmethod + def get_user_amount_to_wrap( + amount_user: int, + user_mint_user_balance: int, + token_account_initialization_configs: TokenAccountInitializationConfigs, + ) -> int: + number_of_paid_atas_by_user = sum( + config == "user_payer" + for config in ( + token_account_initialization_configs.user_ata_mint_user, + token_account_initialization_configs.user_ata_mint_searcher, + ) + ) + return min( + amount_user, + max(0, user_mint_user_balance - number_of_paid_atas_by_user * 2039280), + ) + @staticmethod def extract_swap_info(swap_opportunity: SwapOpportunitySvm) -> SwapAccounts: token_program_searcher = swap_opportunity.tokens.token_program_searcher @@ -749,8 +767,13 @@ def get_svm_swap_instructions( ) if accs["user_token"] == WRAPPED_SOL_MINT: + amount_to_wrap_user = ExpressRelayClient.get_user_amount_to_wrap( + amount_user=amount_user, + user_mint_user_balance=swap_opportunity.user_mint_user_balance, + token_account_initialization_configs=swap_opportunity.token_account_initialization_configs, + ) instructions.extend( - wrap_sol(searcher, accs["user"], amount_user, create_ata=False) + wrap_sol(searcher, accs["user"], amount_to_wrap_user, create_ata=False) ) swap_ix = swap( { diff --git a/sdk/python/express_relay/models/svm.py b/sdk/python/express_relay/models/svm.py index d5560c7df..08734f389 100644 --- a/sdk/python/express_relay/models/svm.py +++ b/sdk/python/express_relay/models/svm.py @@ -360,6 +360,7 @@ class SwapOpportunitySvm(BaseOpportunitySvm): platform_fee_bps: int router_account: SvmAddress user_wallet_address: SvmAddress + user_mint_user_balance: int tokens: SwapTokensSearcherSpecified | SwapTokensUserSpecified token_account_initialization_configs: TokenAccountInitializationConfigs memo: str | None = Field(default=None) From 1da9d683bc77cf309f34e6c8c2a7ca3c87510812 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Fri, 28 Mar 2025 20:33:14 +0000 Subject: [PATCH 02/15] uodate test --- Tiltfile | 2 +- .../src/auction/service/verification.rs | 26 +++------ tilt-scripts/svm/test_swap.py | 57 ++++++++++--------- 3 files changed, 39 insertions(+), 46 deletions(-) diff --git a/Tiltfile b/Tiltfile index 3c95b7f5c..4843fb945 100644 --- a/Tiltfile +++ b/Tiltfile @@ -243,7 +243,7 @@ local_resource( local_resource( "svm-test-swap-endpoint", - "poetry -C tilt-scripts run python3 -m tilt-scripts.svm.test_swap --file-private-key-taker keypairs/admin.json --auction-server-url http://localhost:9000 --input-mint {MINT_SELL} --output-mint {MINT_BUY} --rpc-url {RPC_URL}" + "poetry -C tilt-scripts run python3 -m tilt-scripts.svm.test_swap --file-private-key-taker keypairs/searcher.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", "rust-searcher", "svm-searcher-py"], ) diff --git a/auction-server/src/auction/service/verification.rs b/auction-server/src/auction/service/verification.rs index 8c8367d9d..4f91fea2c 100644 --- a/auction-server/src/auction/service/verification.rs +++ b/auction-server/src/auction/service/verification.rs @@ -1833,18 +1833,10 @@ mod tests { rpc_client::RpcClientConfig, }, solana_sdk::{ - compute_budget, - hash::Hash, - instruction::{ + compute_budget, hash::Hash, instruction::{ AccountMeta, Instruction, - }, - packet::PACKET_DATA_SIZE, - pubkey::Pubkey, - signature::Keypair, - signer::Signer, - system_instruction, - transaction::Transaction, + }, native_token::LAMPORTS_PER_SOL, packet::PACKET_DATA_SIZE, pubkey::Pubkey, signature::Keypair, signer::Signer, system_instruction, transaction::Transaction }, spl_associated_token_account::{ get_associated_token_address, @@ -1993,7 +1985,7 @@ mod tests { token_program_searcher: spl_token::id(), fee_token: fee_token.clone(), referral_fee_bps, - user_mint_user_balance: 0, + user_mint_user_balance: LAMPORTS_PER_SOL, token_account_initialization_configs: TokenAccountInitializationConfigs::searcher_payer(), memo: None, @@ -2029,7 +2021,7 @@ mod tests { token_program_searcher: spl_token::id(), fee_token: fee_token.clone(), referral_fee_bps, - user_mint_user_balance: 0, + user_mint_user_balance: LAMPORTS_PER_SOL, token_account_initialization_configs: TokenAccountInitializationConfigs::searcher_payer(), memo: None, @@ -2065,7 +2057,7 @@ mod tests { token_program_searcher: spl_token::id(), fee_token: fee_token.clone(), referral_fee_bps, - user_mint_user_balance: 0, + user_mint_user_balance: LAMPORTS_PER_SOL, token_account_initialization_configs: TokenAccountInitializationConfigs { user_ata_mint_user: TokenAccountInitializationConfig::SearcherPayer, ..TokenAccountInitializationConfigs::searcher_payer() @@ -2103,7 +2095,7 @@ mod tests { token_program_searcher: spl_token::id(), fee_token: fee_token.clone(), referral_fee_bps, - user_mint_user_balance: 0, + user_mint_user_balance: LAMPORTS_PER_SOL, token_account_initialization_configs: TokenAccountInitializationConfigs::searcher_payer(), memo: None, @@ -2148,7 +2140,7 @@ mod tests { token_program_searcher: spl_token::id(), fee_token: fee_token.clone(), referral_fee_bps, - user_mint_user_balance: 0, + user_mint_user_balance: LAMPORTS_PER_SOL, token_account_initialization_configs: TokenAccountInitializationConfigs::searcher_payer(), memo: None, @@ -2185,7 +2177,7 @@ mod tests { token_program_searcher: spl_token::id(), fee_token: fee_token.clone(), referral_fee_bps, - user_mint_user_balance: 0, + user_mint_user_balance: LAMPORTS_PER_SOL, token_account_initialization_configs: TokenAccountInitializationConfigs { user_ata_mint_user: TokenAccountInitializationConfig::UserPayer, user_ata_mint_searcher: TokenAccountInitializationConfig::UserPayer, @@ -2224,7 +2216,7 @@ mod tests { token_program_searcher: spl_token::id(), fee_token, referral_fee_bps, - user_mint_user_balance: 0, + user_mint_user_balance: LAMPORTS_PER_SOL, token_account_initialization_configs: TokenAccountInitializationConfigs::searcher_payer(), memo: Some("memo".to_string()), diff --git a/tilt-scripts/svm/test_swap.py b/tilt-scripts/svm/test_swap.py index f522d14e3..33e1ff6c4 100644 --- a/tilt-scripts/svm/test_swap.py +++ b/tilt-scripts/svm/test_swap.py @@ -78,33 +78,34 @@ async def send_and_submit_quote(server_url, kp_taker, input_token, output_token, logger.info("Output token %s", result.json()["output_token"]) logger.info("Referrer fee %s", result.json()["referrer_fee"]) logger.info("Platform fee %s", result.json()["platform_fee"]) - response = result.json() - logger.info(response) - tx = SoldersTransaction.from_bytes(base64.b64decode(response["transaction"])) - accounts = tx.message.account_keys - tx = Transaction.from_solders(tx) - tx.sign_partial(kp_taker) - position = accounts.index(pk_taker) - reference_id = response["reference_id"] - - payload = { - "reference_id": reference_id, - "user_signature": str(tx.signatures[position]), - } - await asyncio.sleep(0.5) - result = await http_client.post( - server_url + "/v1/{}/quotes/submit".format(chain_id), - json=payload, - ) - if result.status_code != 200: - logger.error("Failed to submit quote to auction server %s", result.text) - return - - response = result.json() - tx = tx = SoldersTransaction.from_bytes( - base64.b64decode(response["transaction"]) - ) - logger.info("Quote submitted to server. Signature: %s", tx.signatures[0]) + logger.info("Transaction %s", result.json()["transaction"]) + # response = result.json() + # logger.info(response) + # tx = SoldersTransaction.from_bytes(base64.b64decode(response["transaction"])) + # accounts = tx.message.account_keys + # tx = Transaction.from_solders(tx) + # tx.sign_partial(kp_taker) + # position = accounts.index(pk_taker) + # reference_id = response["reference_id"] + + # payload = { + # "reference_id": reference_id, + # "user_signature": str(tx.signatures[position]), + # } + # await asyncio.sleep(0.5) + # result = await http_client.post( + # server_url + "/v1/{}/quotes/submit".format(chain_id), + # json=payload, + # ) + # if result.status_code != 200: + # logger.error("Failed to submit quote to auction server %s", result.text) + # return + + # response = result.json() + # tx = tx = SoldersTransaction.from_bytes( + # base64.b64decode(response["transaction"]) + # ) + # logger.info("Quote submitted to server. Signature: %s", tx.signatures[0]) async def main(): @@ -118,7 +119,7 @@ async def main(): server_url = args.auction_server_url kp_taker = read_kp_from_json(args.file_private_key_taker) - for input_token in [input_mint, native_token_address]: + for input_token in [native_token_address]: for output_token in [output_mint, native_token_address]: if input_token != output_token: for side in ["input", "output"]: From abc931d9ceb8a8d2f9145f5742a97bec7ad639de Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Fri, 28 Mar 2025 20:55:28 +0000 Subject: [PATCH 03/15] draft --- sdk/js/src/index.ts | 3 +++ sdk/js/src/svm.ts | 49 ++++++++++++++++++++++++++++++++++++++++----- sdk/js/src/types.ts | 2 ++ sdk/rust/src/lib.rs | 10 +++++++-- sdk/rust/src/svm.rs | 41 ++++++++++++++++++++++++++++--------- 5 files changed, 89 insertions(+), 16 deletions(-) diff --git a/sdk/js/src/index.ts b/sdk/js/src/index.ts index c25d58a13..1555a0d76 100644 --- a/sdk/js/src/index.ts +++ b/sdk/js/src/index.ts @@ -727,6 +727,9 @@ export class Client { opportunity.token_account_initialization_configs.user_ata_mint_user, }, memo: opportunity.memo ?? undefined, + userMintUserBalance: new anchor.BN( + opportunity.user_mint_user_balance, + ), }; } else { console.warn("Unsupported opportunity", opportunity); diff --git a/sdk/js/src/svm.ts b/sdk/js/src/svm.ts index 03969601a..28bdcd6bc 100644 --- a/sdk/js/src/svm.ts +++ b/sdk/js/src/svm.ts @@ -250,6 +250,7 @@ function extractSwapInfo(swapOpportunity: OpportunitySvmSwap): { tokenProgramSearcher: PublicKey; tokenInitializationConfigs: TokenAccountInitializationConfigs; memo?: string; + userMintUserBalance: anchor.BN; } { const tokenProgramSearcher = swapOpportunity.tokens.tokenProgramSearcher; const tokenProgramUser = swapOpportunity.tokens.tokenProgramUser; @@ -263,6 +264,7 @@ function extractSwapInfo(swapOpportunity: OpportunitySvmSwap): { const router = swapOpportunity.routerAccount; const tokenInitializationConfigs = swapOpportunity.tokenInitializationConfigs; const memo = swapOpportunity.memo; + const userMintUserBalance = swapOpportunity.userMintUserBalance; return { searcherToken, tokenProgramSearcher, @@ -274,6 +276,7 @@ function extractSwapInfo(swapOpportunity: OpportunitySvmSwap): { router, tokenInitializationConfigs, memo, + userMintUserBalance, }; } @@ -426,6 +429,29 @@ function getBidAmountIncludingFees( return bidAmount; } +function getUserAmountToWrap( + amountUser: anchor.BN, + userMintUserBalance: anchor.BN, + tokenAccountInitializationConfigs: TokenAccountInitializationConfigs, +): anchor.BN { + const numberOfPaidAtasByUser = + (tokenAccountInitializationConfigs.userAtaMintUser === "user_payer" + ? 1 + : 0) + + (tokenAccountInitializationConfigs.userAtaMintSearcher === "user_payer" + ? 1 + : 0); + + console.log(numberOfPaidAtasByUser); + return anchor.BN.min( + amountUser, + anchor.BN.max( + userMintUserBalance.sub(new anchor.BN(numberOfPaidAtasByUser * 2039280)), + new anchor.BN(0), + ), + ); +} + export async function constructSwapBid( tx: Transaction, searcher: PublicKey, @@ -436,8 +462,13 @@ export async function constructSwapBid( feeReceiverRelayer: PublicKey, relayerSigner: PublicKey, ): Promise { - const { userToken, searcherToken, user, tokenInitializationConfigs } = - extractSwapInfo(swapOpportunity); + const { + userToken, + searcherToken, + user, + tokenInitializationConfigs, + userMintUserBalance, + } = extractSwapInfo(swapOpportunity); if (swapOpportunity.memo) { tx.instructions.push(createMemoInstruction(swapOpportunity.memo)); @@ -467,7 +498,11 @@ export async function constructSwapBid( ...getWrapSolInstructions( searcher, user, - getBidAmountIncludingFees(swapOpportunity, bidAmount), + getUserAmountToWrap( + getBidAmountIncludingFees(swapOpportunity, bidAmount), + userMintUserBalance, + tokenInitializationConfigs, + ), false, ), // this account creation is handled in the ata initialization section ); @@ -476,8 +511,12 @@ export async function constructSwapBid( ...getWrapSolInstructions( searcher, user, - new anchor.BN( - swapOpportunity.tokens.userTokenAmountIncludingFees.toString(), + getUserAmountToWrap( + new anchor.BN( + swapOpportunity.tokens.userTokenAmountIncludingFees.toString(), + ), + userMintUserBalance, + tokenInitializationConfigs, ), ), ); diff --git a/sdk/js/src/types.ts b/sdk/js/src/types.ts index 01bf71cac..e08e6f633 100644 --- a/sdk/js/src/types.ts +++ b/sdk/js/src/types.ts @@ -3,6 +3,7 @@ import type { components } from "./serverTypes"; import { PublicKey, Transaction } from "@solana/web3.js"; import { OrderStateAndAddress } from "@kamino-finance/limo-sdk/dist/utils"; import { VersionedTransaction } from "@solana/web3.js"; +import * as anchor from "@coral-xyz/anchor"; /** * ERC20 token with contract address and amount @@ -158,6 +159,7 @@ export type OpportunitySvmSwap = { permissionAccount: PublicKey; routerAccount: PublicKey; userWalletAddress: PublicKey; + userMintUserBalance: anchor.BN; feeToken: "searcher_token" | "user_token"; referralFeeBps: number; platformFeeBps: number; diff --git a/sdk/rust/src/lib.rs b/sdk/rust/src/lib.rs index a19f53c64..41f6bee7b 100644 --- a/sdk/rust/src/lib.rs +++ b/sdk/rust/src/lib.rs @@ -777,6 +777,7 @@ impl Biddable for api_types::opportunity::OpportunitySvm { } OpportunityParamsV1ProgramSvm::Swap { user_wallet_address, + user_mint_user_balance, tokens, fee_token, router_account, @@ -834,15 +835,20 @@ impl Biddable for api_types::opportunity::OpportunitySvm { fee_receiver_relayer: params.fee_receiver_relayer, referral_fee_bps, chain_id: opportunity_params.chain_id.clone(), - configs: token_account_initialization_configs, + configs: token_account_initialization_configs.clone(), }, )); if user_token == native_mint::id() { + let user_amount_to_wrap = svm::Svm::get_user_amount_to_wrap( + user_amount_including_fees, + user_mint_user_balance, + &token_account_initialization_configs, + ); instructions.extend(svm::Svm::get_wrap_sol_instructions( svm::GetWrapSolInstructionsParams { payer: params.payer, owner: user_wallet_address, - amount: user_amount_including_fees, + amount: user_amount_to_wrap, create_ata: false, }, )?); diff --git a/sdk/rust/src/svm.rs b/sdk/rust/src/svm.rs index 89ac840d5..86af13f1a 100644 --- a/sdk/rust/src/svm.rs +++ b/sdk/rust/src/svm.rs @@ -25,21 +25,19 @@ use { }, solana_rpc_client::nonblocking::rpc_client::RpcClient, solana_sdk::{ - clock::Slot, - hash::Hash, - instruction::Instruction, - pubkey::Pubkey, - signature::Keypair, - system_instruction::transfer, + clock::Slot, hash::Hash, instruction::Instruction, program_pack::Pack, pubkey::Pubkey, rent::Rent, signature::Keypair, system_instruction::transfer }, spl_associated_token_account::{ get_associated_token_address, get_associated_token_address_with_program_id, instruction::create_associated_token_account_idempotent, }, - spl_token::instruction::{ - close_account, - sync_native, + spl_token::{ + instruction::{ + close_account, + sync_native, + }, + state::Account as TokenAccount, }, std::str::FromStr, }; @@ -469,6 +467,31 @@ impl Svm { }) } + pub fn get_user_amount_to_wrap( + amount_user: u64, + user_mint_user_balance: u64, + token_account_initialization_configs: &TokenAccountInitializationConfigs, + ) -> u64 { + let number_of_paid_atas_by_user = match ( + &token_account_initialization_configs.user_ata_mint_user, + &token_account_initialization_configs.user_ata_mint_searcher, + ) { + ( + TokenAccountInitializationConfig::UserPayer, + TokenAccountInitializationConfig::UserPayer, + ) => 2, + (TokenAccountInitializationConfig::UserPayer, _) => 1, + (_, TokenAccountInitializationConfig::UserPayer) => 1, + _ => 0, + }; + std::cmp::min( + amount_user, + user_mint_user_balance.saturating_sub( + number_of_paid_atas_by_user * Rent::default().minimum_balance(TokenAccount::LEN), + ), + ) + } + /// Adjusts the bid amount in the case where the amount that needs to be provided by the searcher is specified and the fees are in the user token. /// In this case, searchers' bids represent how many tokens they would like to receive. /// However, for the searcher to receive `bidAmount`, the user needs to provide `bidAmount * (FEE_SPLIT_PRECISION / (FEE_SPLIT_PRECISION - fees))` From a29fadf8aae11e5663a7bb429f24e13b235615eb Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Fri, 28 Mar 2025 20:58:02 +0000 Subject: [PATCH 04/15] draft --- .../opportunity/entities/opportunity_svm.rs | 24 +++++++------------ 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/auction-server/src/opportunity/entities/opportunity_svm.rs b/auction-server/src/opportunity/entities/opportunity_svm.rs index 4a2013a81..391ceed4b 100644 --- a/auction-server/src/opportunity/entities/opportunity_svm.rs +++ b/auction-server/src/opportunity/entities/opportunity_svm.rs @@ -285,25 +285,17 @@ pub fn get_opportunity_swap_data(opp: &OpportunitySvm) -> &OpportunitySvmProgram impl OpportunitySvmProgramSwap { pub fn get_user_amount_to_wrap(&self, amount_user: u64) -> u64 { - let number_of_paid_atas_by_user = match ( + let number_of_paid_atas_by_user = [ &self.token_account_initialization_configs.user_ata_mint_user, - &self - .token_account_initialization_configs - .user_ata_mint_searcher, - ) { - ( - TokenAccountInitializationConfig::UserPayer, - TokenAccountInitializationConfig::UserPayer, - ) => 2, - (TokenAccountInitializationConfig::UserPayer, _) => 1, - (_, TokenAccountInitializationConfig::UserPayer) => 1, - _ => 0, - }; + &self.token_account_initialization_configs.user_ata_mint_searcher, + ].iter() + .filter(|&&config| matches!(config, TokenAccountInitializationConfig::UserPayer)) + .count(); + std::cmp::min( amount_user, - self.user_mint_user_balance.saturating_sub( - number_of_paid_atas_by_user * Rent::default().minimum_balance(TokenAccount::LEN), - ), + self.user_mint_user_balance + .saturating_sub(number_of_paid_atas_by_user as u64 * Rent::default().minimum_balance(TokenAccount::LEN)) ) } } From a3b2d90745351dff838450c9ac96a18d20a80290 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Mon, 31 Mar 2025 14:03:32 +0100 Subject: [PATCH 05/15] clean --- .../src/auction/service/verification.rs | 15 ++++- .../opportunity/entities/opportunity_svm.rs | 24 ++++---- sdk/rust/src/svm.rs | 28 +++++---- tilt-scripts/svm/test_swap.py | 57 +++++++++---------- 4 files changed, 69 insertions(+), 55 deletions(-) diff --git a/auction-server/src/auction/service/verification.rs b/auction-server/src/auction/service/verification.rs index 4f91fea2c..7868eeb18 100644 --- a/auction-server/src/auction/service/verification.rs +++ b/auction-server/src/auction/service/verification.rs @@ -1417,7 +1417,7 @@ impl Service { &bid_data.transaction, &swap_data, &swap_accounts, - &opportunity_swap_data, + opportunity_swap_data, ) .await?; @@ -1833,10 +1833,19 @@ mod tests { rpc_client::RpcClientConfig, }, solana_sdk::{ - compute_budget, hash::Hash, instruction::{ + compute_budget, + hash::Hash, + instruction::{ AccountMeta, Instruction, - }, native_token::LAMPORTS_PER_SOL, packet::PACKET_DATA_SIZE, pubkey::Pubkey, signature::Keypair, signer::Signer, system_instruction, transaction::Transaction + }, + native_token::LAMPORTS_PER_SOL, + packet::PACKET_DATA_SIZE, + pubkey::Pubkey, + signature::Keypair, + signer::Signer, + system_instruction, + transaction::Transaction, }, spl_associated_token_account::{ get_associated_token_address, diff --git a/auction-server/src/opportunity/entities/opportunity_svm.rs b/auction-server/src/opportunity/entities/opportunity_svm.rs index 391ceed4b..76187d8a9 100644 --- a/auction-server/src/opportunity/entities/opportunity_svm.rs +++ b/auction-server/src/opportunity/entities/opportunity_svm.rs @@ -33,10 +33,7 @@ use { pubkey::Pubkey, rent::Rent, }, - spl_token_2022::{ - extension::StateWithExtensions, - state::Account as TokenAccount, - }, + spl_token_2022::state::Account as TokenAccount, std::ops::Deref, time::{ Duration, @@ -287,15 +284,20 @@ impl OpportunitySvmProgramSwap { pub fn get_user_amount_to_wrap(&self, amount_user: u64) -> u64 { let number_of_paid_atas_by_user = [ &self.token_account_initialization_configs.user_ata_mint_user, - &self.token_account_initialization_configs.user_ata_mint_searcher, - ].iter() - .filter(|&&config| matches!(config, TokenAccountInitializationConfig::UserPayer)) - .count(); - + &self + .token_account_initialization_configs + .user_ata_mint_searcher, + ] + .iter() + .filter(|&&config| matches!(config, TokenAccountInitializationConfig::UserPayer)) + .count(); + std::cmp::min( amount_user, - self.user_mint_user_balance - .saturating_sub(number_of_paid_atas_by_user as u64 * Rent::default().minimum_balance(TokenAccount::LEN)) + self.user_mint_user_balance.saturating_sub( + number_of_paid_atas_by_user as u64 + * Rent::default().minimum_balance(TokenAccount::LEN), + ), ) } } diff --git a/sdk/rust/src/svm.rs b/sdk/rust/src/svm.rs index 86af13f1a..5bda29569 100644 --- a/sdk/rust/src/svm.rs +++ b/sdk/rust/src/svm.rs @@ -25,7 +25,14 @@ use { }, solana_rpc_client::nonblocking::rpc_client::RpcClient, solana_sdk::{ - clock::Slot, hash::Hash, instruction::Instruction, program_pack::Pack, pubkey::Pubkey, rent::Rent, signature::Keypair, system_instruction::transfer + clock::Slot, + hash::Hash, + instruction::Instruction, + program_pack::Pack, + pubkey::Pubkey, + rent::Rent, + signature::Keypair, + system_instruction::transfer, }, spl_associated_token_account::{ get_associated_token_address, @@ -472,22 +479,19 @@ impl Svm { user_mint_user_balance: u64, token_account_initialization_configs: &TokenAccountInitializationConfigs, ) -> u64 { - let number_of_paid_atas_by_user = match ( + let number_of_paid_atas_by_user = [ &token_account_initialization_configs.user_ata_mint_user, &token_account_initialization_configs.user_ata_mint_searcher, - ) { - ( - TokenAccountInitializationConfig::UserPayer, - TokenAccountInitializationConfig::UserPayer, - ) => 2, - (TokenAccountInitializationConfig::UserPayer, _) => 1, - (_, TokenAccountInitializationConfig::UserPayer) => 1, - _ => 0, - }; + ] + .iter() + .filter(|&&config| matches!(config, TokenAccountInitializationConfig::UserPayer)) + .count(); + std::cmp::min( amount_user, user_mint_user_balance.saturating_sub( - number_of_paid_atas_by_user * Rent::default().minimum_balance(TokenAccount::LEN), + number_of_paid_atas_by_user as u64 + * Rent::default().minimum_balance(TokenAccount::LEN), ), ) } diff --git a/tilt-scripts/svm/test_swap.py b/tilt-scripts/svm/test_swap.py index 33e1ff6c4..f522d14e3 100644 --- a/tilt-scripts/svm/test_swap.py +++ b/tilt-scripts/svm/test_swap.py @@ -78,34 +78,33 @@ async def send_and_submit_quote(server_url, kp_taker, input_token, output_token, logger.info("Output token %s", result.json()["output_token"]) logger.info("Referrer fee %s", result.json()["referrer_fee"]) logger.info("Platform fee %s", result.json()["platform_fee"]) - logger.info("Transaction %s", result.json()["transaction"]) - # response = result.json() - # logger.info(response) - # tx = SoldersTransaction.from_bytes(base64.b64decode(response["transaction"])) - # accounts = tx.message.account_keys - # tx = Transaction.from_solders(tx) - # tx.sign_partial(kp_taker) - # position = accounts.index(pk_taker) - # reference_id = response["reference_id"] - - # payload = { - # "reference_id": reference_id, - # "user_signature": str(tx.signatures[position]), - # } - # await asyncio.sleep(0.5) - # result = await http_client.post( - # server_url + "/v1/{}/quotes/submit".format(chain_id), - # json=payload, - # ) - # if result.status_code != 200: - # logger.error("Failed to submit quote to auction server %s", result.text) - # return - - # response = result.json() - # tx = tx = SoldersTransaction.from_bytes( - # base64.b64decode(response["transaction"]) - # ) - # logger.info("Quote submitted to server. Signature: %s", tx.signatures[0]) + response = result.json() + logger.info(response) + tx = SoldersTransaction.from_bytes(base64.b64decode(response["transaction"])) + accounts = tx.message.account_keys + tx = Transaction.from_solders(tx) + tx.sign_partial(kp_taker) + position = accounts.index(pk_taker) + reference_id = response["reference_id"] + + payload = { + "reference_id": reference_id, + "user_signature": str(tx.signatures[position]), + } + await asyncio.sleep(0.5) + result = await http_client.post( + server_url + "/v1/{}/quotes/submit".format(chain_id), + json=payload, + ) + if result.status_code != 200: + logger.error("Failed to submit quote to auction server %s", result.text) + return + + response = result.json() + tx = tx = SoldersTransaction.from_bytes( + base64.b64decode(response["transaction"]) + ) + logger.info("Quote submitted to server. Signature: %s", tx.signatures[0]) async def main(): @@ -119,7 +118,7 @@ async def main(): server_url = args.auction_server_url kp_taker = read_kp_from_json(args.file_private_key_taker) - for input_token in [native_token_address]: + for input_token in [input_mint, native_token_address]: for output_token in [output_mint, native_token_address]: if input_token != output_token: for side in ["input", "output"]: From 8bca3622874f87c277d8101cbc34bd47b973ddda Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Mon, 31 Mar 2025 14:04:04 +0100 Subject: [PATCH 06/15] clean --- Tiltfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tiltfile b/Tiltfile index 4843fb945..3c95b7f5c 100644 --- a/Tiltfile +++ b/Tiltfile @@ -243,7 +243,7 @@ local_resource( local_resource( "svm-test-swap-endpoint", - "poetry -C tilt-scripts run python3 -m tilt-scripts.svm.test_swap --file-private-key-taker keypairs/searcher.json --auction-server-url http://localhost:9000 --input-mint {MINT_SELL} --output-mint {MINT_BUY} --rpc-url {RPC_URL}" + "poetry -C tilt-scripts run python3 -m tilt-scripts.svm.test_swap --file-private-key-taker keypairs/admin.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", "rust-searcher", "svm-searcher-py"], ) From a79ffe543d8f80cb20adabb55793c6bcc9998baa Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Mon, 31 Mar 2025 14:09:24 +0100 Subject: [PATCH 07/15] clean --- auction-server/src/auction/service/verification.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/auction-server/src/auction/service/verification.rs b/auction-server/src/auction/service/verification.rs index 7868eeb18..a1f1ddb59 100644 --- a/auction-server/src/auction/service/verification.rs +++ b/auction-server/src/auction/service/verification.rs @@ -846,8 +846,8 @@ impl Service { // User have to wrap Sol if swap_accounts.mint_user == spl_token::native_mint::id() { - // Sometimes the user doesn't have enought SOL, but we want the transaction to fail in the Expert Relay program with InsufficientUserFunds - // Therefore we allow the user to wrap less SOL than needed to it doesn't fail in the transfer instruction + // Sometimes the user doesn't have enough SOL, but we want the transaction to fail in the Expert Relay program with InsufficientUserFunds + // Therefore we allow the user to wrap less SOL than needed so it doesn't fail in the transfer instruction let amount_user_to_wrap = opportunity_swap_data.get_user_amount_to_wrap(swap_data.amount_user); @@ -880,7 +880,10 @@ impl Service { }, )); } - if amount_user_to_wrap != transfer_instruction.lamports { + // todo: remove swap_data.amount_user != transfer_instruction.lamports once searchers have updated their sdk + if swap_data.amount_user != transfer_instruction.lamports + && amount_user_to_wrap != transfer_instruction.lamports + { return Err(RestError::InvalidInstruction( Some(transfer_instruction.index), InstructionError::InvalidAmountTransferInstruction { From 55afd424faf1671e82ab0987c9f9cc3bf274cd55 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Mon, 31 Mar 2025 14:15:38 +0100 Subject: [PATCH 08/15] clean --- auction-server/src/opportunity/entities/opportunity_svm.rs | 4 ++-- sdk/rust/src/svm.rs | 4 ++-- 2 files 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 76187d8a9..a2517958b 100644 --- a/auction-server/src/opportunity/entities/opportunity_svm.rs +++ b/auction-server/src/opportunity/entities/opportunity_svm.rs @@ -282,7 +282,7 @@ pub fn get_opportunity_swap_data(opp: &OpportunitySvm) -> &OpportunitySvmProgram impl OpportunitySvmProgramSwap { pub fn get_user_amount_to_wrap(&self, amount_user: u64) -> u64 { - let number_of_paid_atas_by_user = [ + let number_of_atas_paid_by_user = [ &self.token_account_initialization_configs.user_ata_mint_user, &self .token_account_initialization_configs @@ -295,7 +295,7 @@ impl OpportunitySvmProgramSwap { std::cmp::min( amount_user, self.user_mint_user_balance.saturating_sub( - number_of_paid_atas_by_user as u64 + number_of_atas_paid_by_user as u64 * Rent::default().minimum_balance(TokenAccount::LEN), ), ) diff --git a/sdk/rust/src/svm.rs b/sdk/rust/src/svm.rs index 5bda29569..22afafa9b 100644 --- a/sdk/rust/src/svm.rs +++ b/sdk/rust/src/svm.rs @@ -479,7 +479,7 @@ impl Svm { user_mint_user_balance: u64, token_account_initialization_configs: &TokenAccountInitializationConfigs, ) -> u64 { - let number_of_paid_atas_by_user = [ + let number_of_atas_paid_by_user = [ &token_account_initialization_configs.user_ata_mint_user, &token_account_initialization_configs.user_ata_mint_searcher, ] @@ -490,7 +490,7 @@ impl Svm { std::cmp::min( amount_user, user_mint_user_balance.saturating_sub( - number_of_paid_atas_by_user as u64 + number_of_atas_paid_by_user as u64 * Rent::default().minimum_balance(TokenAccount::LEN), ), ) From e4e11f905c58a2f46ac75938e92a21f4ea526ce3 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Mon, 31 Mar 2025 14:26:55 +0100 Subject: [PATCH 09/15] clean js --- sdk/js/src/svm.ts | 64 +++++++++++++++++++---------------------------- 1 file changed, 26 insertions(+), 38 deletions(-) diff --git a/sdk/js/src/svm.ts b/sdk/js/src/svm.ts index 28bdcd6bc..34c875ef4 100644 --- a/sdk/js/src/svm.ts +++ b/sdk/js/src/svm.ts @@ -39,6 +39,7 @@ function getExpressRelayProgram(chain: string): PublicKey { } export const FEE_SPLIT_PRECISION = new anchor.BN(10000); +const RENT_TOKEN_ACCOUNT_LAMPORTS = 2039280; export function getConfigRouterPda( chain: string, @@ -434,19 +435,17 @@ function getUserAmountToWrap( userMintUserBalance: anchor.BN, tokenAccountInitializationConfigs: TokenAccountInitializationConfigs, ): anchor.BN { - const numberOfPaidAtasByUser = - (tokenAccountInitializationConfigs.userAtaMintUser === "user_payer" - ? 1 - : 0) + - (tokenAccountInitializationConfigs.userAtaMintSearcher === "user_payer" - ? 1 - : 0); - - console.log(numberOfPaidAtasByUser); + const numberOfAtasPaidByUser = [ + tokenAccountInitializationConfigs.userAtaMintUser, + tokenAccountInitializationConfigs.userAtaMintSearcher, + ].filter((config) => config === "user_payer").length; + return anchor.BN.min( amountUser, anchor.BN.max( - userMintUserBalance.sub(new anchor.BN(numberOfPaidAtasByUser * 2039280)), + userMintUserBalance.sub( + new anchor.BN(numberOfAtasPaidByUser * RENT_TOKEN_ACCOUNT_LAMPORTS), + ), new anchor.BN(0), ), ); @@ -493,35 +492,24 @@ export async function constructSwapBid( } if (userToken.equals(NATIVE_MINT)) { - if (swapOpportunity.tokens.type === "searcher_specified") { - tx.instructions.push( - ...getWrapSolInstructions( - searcher, - user, - getUserAmountToWrap( - getBidAmountIncludingFees(swapOpportunity, bidAmount), - userMintUserBalance, - tokenInitializationConfigs, - ), - false, - ), // this account creation is handled in the ata initialization section - ); - } else { - tx.instructions.push( - ...getWrapSolInstructions( - searcher, - user, - getUserAmountToWrap( - new anchor.BN( - swapOpportunity.tokens.userTokenAmountIncludingFees.toString(), - ), - userMintUserBalance, - tokenInitializationConfigs, - ), - ), - ); - } + const amountUser = + swapOpportunity.tokens.type === "user_specified" + ? new anchor.BN( + swapOpportunity.tokens.userTokenAmountIncludingFees.toString(), + ) + : getBidAmountIncludingFees(swapOpportunity, bidAmount); + + const amountUserToWrap = getUserAmountToWrap( + amountUser, + userMintUserBalance, + tokenInitializationConfigs, + ); + + tx.instructions.push( + ...getWrapSolInstructions(searcher, user, amountUserToWrap, false), // this account creation is handled in the ata initialization section + ); } + const swapInstruction = await constructSwapInstruction( searcher, swapOpportunity, From 0d901f2248ac7460b203811c17631ae2c0ad3235 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Mon, 31 Mar 2025 14:33:59 +0100 Subject: [PATCH 10/15] clean py --- sdk/python/express_relay/client.py | 17 ++++++++++++----- sdk/python/express_relay/svm/token_utils.py | 2 ++ 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/sdk/python/express_relay/client.py b/sdk/python/express_relay/client.py index c293b25c1..31ba17c3a 100644 --- a/sdk/python/express_relay/client.py +++ b/sdk/python/express_relay/client.py @@ -60,6 +60,7 @@ from express_relay.svm.generated.express_relay.types.swap_args import SwapArgs from express_relay.svm.limo_client import LimoClient from express_relay.svm.token_utils import ( + RENT_TOKEN_ACCOUNT_LAMPORTS, create_associated_token_account_idempotent, get_ata, unwrap_sol, @@ -657,16 +658,22 @@ def get_user_amount_to_wrap( user_mint_user_balance: int, token_account_initialization_configs: TokenAccountInitializationConfigs, ) -> int: - number_of_paid_atas_by_user = sum( - config == "user_payer" - for config in ( + number_of_atas_paid_by_user = ( + [ token_account_initialization_configs.user_ata_mint_user, token_account_initialization_configs.user_ata_mint_searcher, - ) + ] + .filter(lambda x: x == "user_payer") + .count() ) + return min( amount_user, - max(0, user_mint_user_balance - number_of_paid_atas_by_user * 2039280), + max( + 0, + user_mint_user_balance + - number_of_atas_paid_by_user * RENT_TOKEN_ACCOUNT_LAMPORTS, + ), ) @staticmethod diff --git a/sdk/python/express_relay/svm/token_utils.py b/sdk/python/express_relay/svm/token_utils.py index 6f151099e..2c8ea9e3d 100644 --- a/sdk/python/express_relay/svm/token_utils.py +++ b/sdk/python/express_relay/svm/token_utils.py @@ -17,6 +17,8 @@ sync_native, ) +RENT_TOKEN_ACCOUNT_LAMPORTS = 2039280 + def get_ata( owner: Pubkey, token_mint_address: Pubkey, token_program_id: Pubkey From 0fbcf161539e26ac288232b8949c38386bd2ea09 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Mon, 31 Mar 2025 14:38:15 +0100 Subject: [PATCH 11/15] changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ff1763d5a..f8cdfcd47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). - For swap opportunities, the searcher sdks will now add a memo instruction to the bid transaction if the quote requester so desires. This allows the quote requester to track which on-chain transactions correspond to quotes they requested. [458](https://github.com/pyth-network/per/pull/458) +### Fixed + +- For swap opportunities, when a user wants to swap SOL but doesn't have enough funds, the sdk will never try to wrap (on behalf of the user) an amount exceeding the SOL balance of the user. + ## [Rust: 0.7.0, Python 0.22.0, Javascript 0.23.0] - 2025-03-25 ### Changed From 02704af06099ceeac5db6e9aae079489d5c27d93 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Mon, 31 Mar 2025 15:14:29 +0100 Subject: [PATCH 12/15] fix python --- sdk/python/express_relay/client.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/sdk/python/express_relay/client.py b/sdk/python/express_relay/client.py index 31ba17c3a..b1ce52c7f 100644 --- a/sdk/python/express_relay/client.py +++ b/sdk/python/express_relay/client.py @@ -658,13 +658,15 @@ def get_user_amount_to_wrap( user_mint_user_balance: int, token_account_initialization_configs: TokenAccountInitializationConfigs, ) -> int: - number_of_atas_paid_by_user = ( + number_of_atas_paid_by_user = len( [ - token_account_initialization_configs.user_ata_mint_user, - token_account_initialization_configs.user_ata_mint_searcher, + x + for x in [ + token_account_initialization_configs.user_ata_mint_user, + token_account_initialization_configs.user_ata_mint_searcher, + ] + if x == "user_payer" ] - .filter(lambda x: x == "user_payer") - .count() ) return min( From 2c91b9a0966568d4298580431c6d79f9aff0fa8e Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Mon, 31 Mar 2025 15:16:24 +0100 Subject: [PATCH 13/15] fix js --- sdk/js/src/index.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sdk/js/src/index.ts b/sdk/js/src/index.ts index 1555a0d76..546253487 100644 --- a/sdk/js/src/index.ts +++ b/sdk/js/src/index.ts @@ -727,9 +727,7 @@ export class Client { opportunity.token_account_initialization_configs.user_ata_mint_user, }, memo: opportunity.memo ?? undefined, - userMintUserBalance: new anchor.BN( - opportunity.user_mint_user_balance, - ), + userMintUserBalance: new anchor.BN(opportunity.user_mint_user_balance), }; } else { console.warn("Unsupported opportunity", opportunity); From cb5e2b653bfadf708227a46648b9fc2cf253eef3 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Mon, 31 Mar 2025 16:04:07 +0100 Subject: [PATCH 14/15] fix typo --- auction-server/src/auction/service/verification.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/auction-server/src/auction/service/verification.rs b/auction-server/src/auction/service/verification.rs index a1f1ddb59..574291d84 100644 --- a/auction-server/src/auction/service/verification.rs +++ b/auction-server/src/auction/service/verification.rs @@ -846,7 +846,7 @@ impl Service { // User have to wrap Sol if swap_accounts.mint_user == spl_token::native_mint::id() { - // Sometimes the user doesn't have enough SOL, but we want the transaction to fail in the Expert Relay program with InsufficientUserFunds + // Sometimes the user doesn't have enough SOL, but we want the transaction to fail in the Express Relay program with InsufficientUserFunds // Therefore we allow the user to wrap less SOL than needed so it doesn't fail in the transfer instruction let amount_user_to_wrap = opportunity_swap_data.get_user_amount_to_wrap(swap_data.amount_user); From 43f4491495f9f116306acdf624075b3f3c659fde Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Date: Mon, 31 Mar 2025 17:25:48 +0100 Subject: [PATCH 15/15] add comment about token 22 --- auction-server/src/opportunity/entities/opportunity_svm.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/auction-server/src/opportunity/entities/opportunity_svm.rs b/auction-server/src/opportunity/entities/opportunity_svm.rs index a2517958b..6647d2f57 100644 --- a/auction-server/src/opportunity/entities/opportunity_svm.rs +++ b/auction-server/src/opportunity/entities/opportunity_svm.rs @@ -296,7 +296,7 @@ impl OpportunitySvmProgramSwap { amount_user, self.user_mint_user_balance.saturating_sub( number_of_atas_paid_by_user as u64 - * Rent::default().minimum_balance(TokenAccount::LEN), + * Rent::default().minimum_balance(TokenAccount::LEN), // todo: token2022 accounts can be bigger than this, this hack might not work for them ), ) }