Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Swap js integration #320

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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", "cargo", "cargo-lock"]
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix local pre-commit

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had the same issue. Maybe if you upgrade the pre-commit to 4.0.1 or later, it works.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here, this issue popped up for me out of the blue yesterday. updating pre commit resolved it

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes you should run this hook when Cargo.toml or Cargo.lock get updated

types_or: ["rust"]
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", "cargo", "cargo-lock"]
types_or: ["rust"]
files: .
# Hooks for contracts-svm
- id: cargo-fmt-contracts-svm
Expand Down
15 changes: 11 additions & 4 deletions Tiltfile
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down Expand Up @@ -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",
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mint more tokens to everyone and revert bid number to the higher value

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any reason you are setting lower bid number here? if the bid isn't high enough, you may not be paying for the rent of the fee receiver if it doesn't already have the funds for rent exemption. i think it's fine here bc we airdrop, but worth checking and figuring out the possible scenarios

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"],
)
11 changes: 3 additions & 8 deletions auction-server/api-types/src/opportunity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,9 @@ pub struct OpportunityBidResult {
}

#[derive(Serialize, Deserialize, ToSchema, Clone, PartialEq, Debug, Display)]
#[serde(rename_all = "lowercase")]
#[serde(rename_all = "snake_case")]
pub enum ProgramSvm {
#[serde(rename = "swap_kamino")]
#[strum(serialize = "swap_kamino")]
SwapKamino,
#[serde(rename = "limo")]
#[strum(serialize = "limo")]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

strum and serde are doing diff stuff. Are you sure it's safe to remove the strum?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think strum here will affect the Display which is used in the tracing.
So after this change it might be with a capital letter in the tracing?

Limo,
}

Expand Down Expand Up @@ -298,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.
Expand All @@ -316,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.
Expand All @@ -341,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,
Expand Down
2 changes: 1 addition & 1 deletion auction-server/src/auction/entities/auction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ pub struct Auction<T: ChainTrait> {
pub bids: Vec<Bid<T>>,
}

#[derive(PartialEq)]
#[derive(PartialEq, Debug)]
pub enum SubmitType {
ByServer,
ByOther,
Expand Down
113 changes: 79 additions & 34 deletions auction-server/src/auction/service/verification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ use {
transaction::VersionedTransaction,
},
std::{
str::FromStr,
sync::Arc,
time::Duration,
},
Expand Down Expand Up @@ -546,22 +547,6 @@ impl Service<Svm> {
(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,
Expand Down Expand Up @@ -616,23 +601,71 @@ impl Service<Svm> {
},
),
};
let mint_fee = match swap_data.fee_token {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think some of this server stuff may be unnecessary once you pull main? pushed some updates recently

FeeToken::Input => mint_input,
FeeToken::Output => mint_output,
};

let permission_account = get_quote_permission_key(&tokens, &user_wallet);
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(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should use this address for creating the permission key

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think the whole point of making the router fee receiver ta generic as opposed to constraining it to be the ATA was so that routers could provide arbitrary token accounts

also the wallet_program_router_account is no longer in the config

&[
&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,
// TODO*: to fix once deadline param added to swap instruction--just set this way to make sure compiles
deadline: OffsetDateTime::now_utc(),
// deadline: OffsetDateTime::from_unix_timestamp(swap_data.deadline).map_err(
// |e| {
// RestError::BadParameters(format!(
// "Invalid deadline: {:?} {:?}",
// swap_data.deadline, e
// ))
// },
// )?,
router: self.config.chain_config.wallet_program_router_account,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we use the router_fee_receiver_ta to create the permission key, we don't need to do these checks. actually the permission key will be invalid if the receiver_ta account does not match with what we expect.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a good point, we should probably include the router in the permission_key construction in get_quote_permission_key. that's an easy fix, we can mark it as a TODO (or you can add it here after merging main)

deadline: OffsetDateTime::from_unix_timestamp(swap_data.deadline).map_err(
|e| {
RestError::BadParameters(format!(
"Invalid deadline: {:?} {:?}",
swap_data.deadline, e
))
},
)?,
submit_type: SubmitType::ByOther,
})
}
Expand All @@ -642,12 +675,10 @@ impl Service<Svm> {
}
}

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()]
Expand All @@ -660,9 +691,17 @@ impl Service<Svm> {
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) {
Expand Down Expand Up @@ -714,7 +753,13 @@ impl Service<Svm> {
)
}
SubmitType::ByServer => {
self.all_signatures_exists(&message_bytes, accounts, &signatures, &[])
self.relayer_signer_exists(accounts, &signatures)?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the relayer exist at this point?

self.all_signatures_exists(
&message_bytes,
accounts,
&signatures,
&[self.config.chain_config.express_relay.relayer.pubkey()],
)
}
}
}
Expand Down
8 changes: 1 addition & 7 deletions auction-server/src/opportunity/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Arc<StoreNew>>,
Json(params): Json<QuoteCreate>,
) -> Result<Json<Quote>, RestError> {
let program = get_program(&auth)?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's better to make it permissioned at the beginnig

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?;

Expand Down
2 changes: 1 addition & 1 deletion auction-server/src/opportunity/entities/opportunity_svm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
16 changes: 8 additions & 8 deletions auction-server/src/opportunity/service/get_quote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,9 @@ impl Service<ChainTypeSvm> {
let router = quote_create.router;
let chain_config = self.get_config(&quote_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(&quote_create.tokens, &quote_create.user_wallet_address);
Expand All @@ -124,13 +126,13 @@ impl Service<ChainTypeSvm> {
),
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 {
Expand Down Expand Up @@ -252,9 +254,7 @@ impl Service<ChainTypeSvm> {
.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 {
Expand Down
6 changes: 6 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pnpm-workspace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions sdk/js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"@coral-xyz/anchor": "catalog:",
"@kamino-finance/limo-sdk": "catalog:",
"@solana/web3.js": "catalog:",
"@solana/spl-token": "catalog:",
"bs58": "^6.0.0",
"decimal.js": "^10.4.3",
"isomorphic-ws": "^5.0.0",
Expand Down
2 changes: 2 additions & 0 deletions sdk/js/src/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@ export const SVM_CONSTANTS: Record<string, SvmConstantsConfig> = {
expressRelayProgram: new PublicKey(
"PytERJFhAKuNNuaiXkApLfWzwNwSNDACpigT3LwQfou"
),
walletRouter: new PublicKey("3hv8L8UeBbyM3M25dF3h2C5p8yA4FptD7FFZu4Z1jCMn"),
},
solana: {
expressRelayProgram: new PublicKey(
"PytERJFhAKuNNuaiXkApLfWzwNwSNDACpigT3LwQfou"
),
walletRouter: new PublicKey("3hv8L8UeBbyM3M25dF3h2C5p8yA4FptD7FFZu4Z1jCMn"),
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add todo to change later

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

merge main and get rid of

},
};
Loading
Loading