Skip to content

Commit

Permalink
Feat/swap last look sdks (#381)
Browse files Browse the repository at this point in the history
  • Loading branch information
danimhr authored Feb 5, 2025
1 parent 44026ba commit acfcaae
Show file tree
Hide file tree
Showing 26 changed files with 1,022 additions and 402 deletions.
3 changes: 2 additions & 1 deletion Cargo.lock

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

14 changes: 9 additions & 5 deletions Tiltfile
Original file line number Diff line number Diff line change
Expand Up @@ -199,28 +199,32 @@ local_resource(

local_resource(
"svm-limonade",
serve_cmd="pnpm run --prefix scripts/limonade limonade --global-config $(solana-keygen pubkey keypairs/limo_global_config.json) --endpoint http://127.0.0.1:9000 --chain-id local-solana --api-key $(poetry -C tilt-scripts run python3 create_limo_profile.py) --rpc-endpoint %s" % rpc_url_solana,
serve_cmd="pnpm run --prefix scripts/limonade limonade --global-config $(solana-keygen pubkey keypairs/limo_global_config.json) --endpoint http://127.0.0.1:9000 --chain-id local-solana --api-key $(poetry -C tilt-scripts run python3 tilt-scripts/utils/create_profile.py --name limo --email [email protected] --role protocol) --rpc-endpoint %s" % rpc_url_solana,
resource_deps=["svm-initialize-programs", "auction-server"],
)

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 local-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="poetry run python3 -m express_relay.searcher.examples.testing_searcher_svm --endpoint-express-relay http://127.0.0.1:9000 --chain-id local-solana --api-key $(poetry -C tilt-scripts run python3 ../../tilt-scripts/utils/create_profile.py --name python_sdk --email [email protected] --role searcher) --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"],
)

js_searcher_command = (
"JS_API_KEY=$(poetry -C tilt-scripts run python3 tilt-scripts/utils/create_profile.py --name js_sdk --email [email protected] --role searcher);"
+ "cd sdk/js;"
+ "pnpm run testing-searcher-svm --endpoint-express-relay http://127.0.0.1:9000 --chain-id local-solana --api-key $JS_API_KEY --private-key-json-file ../../keypairs/searcher_js.json --endpoint-svm http://127.0.0.1:8899 --bid 10000000 --fill-rate 4 --bid-margin 100"
)
local_resource(
"svm-searcher-js",
serve_cmd="pnpm run testing-searcher-svm --endpoint-express-relay http://127.0.0.1:9000 --chain-id local-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",
serve_cmd=js_searcher_command,
resource_deps=["svm-initialize-programs", "auction-server"],
)

rust_searcher_command = (
"source tilt-resources.env;"
+ "export SVM_PRIVATE_KEY_FILE=keypairs/searcher_rust.json;"
+ "cargo run -p testing-searcher"
+ "cargo run -p testing-searcher -- --api-key $(poetry -C tilt-scripts run python3 tilt-scripts/utils/create_profile.py --name rust_sdk --email [email protected] --role searcher)"
)
local_resource(
"rust-searcher",
Expand Down
3 changes: 2 additions & 1 deletion apps/swap/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,14 @@
"test:types": "tsc"
},
"dependencies": {
"@pythnetwork/express-relay-js": "workspace:*",
"@radix-ui/react-slot": "^1.1.1",
"@solana/wallet-adapter-base": "^0.9.23",
"@solana/wallet-adapter-react": "^0.15.35",
"@solana/wallet-adapter-react-ui": "^0.9.35",
"@solana/wallet-adapter-wallets": "0.19.10",
"@solana/web3.js": "1.92.3",
"@pythnetwork/express-relay-js": "workspace:*",
"bs58": "^6.0.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.473.0",
Expand Down
34 changes: 27 additions & 7 deletions apps/swap/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,21 @@ import {
import { PublicKey } from "@solana/web3.js";
import { useCallback, useState } from "react";
import { useExpressRelayClient } from "@/components/ExpressRelayProvider";
import bs58 from "bs58";

const USDC = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
const USDT = new PublicKey("Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB");

export default function Home() {
const { publicKey, sendTransaction } = useWallet();
const { publicKey, signTransaction } = useWallet();
const { connection } = useConnection();
const expressRelayClient = useExpressRelayClient();

const [log, setLog] = useState<string[]>([]);

const handleClick = useCallback(() => {
const inner = async () => {
if (!publicKey || !sendTransaction) return;
if (!publicKey || !signTransaction) return;
setLog(["Getting quote..."]);
// random to avoid same opportunity submitted recently error
const amount = 1000000 + Math.floor(Math.random() * 1000);
Expand All @@ -32,7 +33,10 @@ export default function Home() {
chainId: "development-solana",
inputTokenMint: USDC,
outputTokenMint: USDT,
router: publicKey,
referralFeeInfo: {
router: publicKey,
referralFeeBps: 0,
},
userWallet: publicKey,
specifiedTokenAmount: {
amount,
Expand All @@ -52,16 +56,32 @@ export default function Home() {
2,
),
]);
const txHash = await sendTransaction(quote.transaction, connection);
setLog((log) => [...log, `Transaction sent: ${txHash}`]);
const signedTransaction = await signTransaction(quote.transaction);
const accountPosition = signedTransaction.message
.getAccountKeys()
.staticAccountKeys.findIndex((key) => key.equals(publicKey));
const signature = signedTransaction.signatures[accountPosition];
if (!signature) {
throw new Error("Signature not found");
}
const tx = await expressRelayClient.submitQuote({
chainId: "development-solana",
referenceId: quote.referenceId,
userSignature: bs58.encode(signature),
});
const tx_hash = tx.signatures[0];
if (!tx_hash) {
throw new Error("Transaction hash not found");
}
setLog((log) => [...log, "Submitted quote: " + bs58.encode(tx_hash)]);
};
inner().catch((error) => {
setLog((log) => [...log, error.message]);
console.error(error);
});
}, [expressRelayClient, publicKey, sendTransaction, connection]);
}, [expressRelayClient, publicKey, signTransaction, connection]);

const canSwap = publicKey && sendTransaction;
const canSwap = publicKey && signTransaction;
return (
<main>
<div className="m-auto w-2/4 parent space-y-2">
Expand Down
3 changes: 2 additions & 1 deletion auction-server/src/auction/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ pub async fn process_bid(
///
/// Bids can only be cancelled if they are in the awaiting signature state.
/// Only the user who created the bid can cancel it.
#[utoipa::path(post, path = "/v1/{chain_id}/bids/{bid_id}/cancel", responses(
#[utoipa::path(post, path = "/v1/{chain_id}/bids/{bid_id}/cancel", params(BidCancelParams), responses(
(status = 200, description = "Bid was cancelled successfully"),
(status = 400, response = ErrorBodyResponse),
(status = 404, description = "Chain id was not found", body = ErrorBodyResponse),
Expand Down Expand Up @@ -295,6 +295,7 @@ pub async fn get_bids_by_time_deprecated(
/// Server will verify the quote and checks if the quote is still valid.
/// If the quote is valid, the server will submit the transaction to the blockchain.
#[utoipa::path(post, path = "/v1/{chain_id}/quotes/submit", request_body = SubmitQuote,
params(("chain_id"=String, Path, description = "The chain id to submit the quote for", example = "solana")),
responses(
(status = 200, body = SubmitQuoteResponse),
(status = 400, response = ErrorBodyResponse),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,10 @@ impl<T: ChainTrait> Repository<T> {
pub async fn get_in_memory_auction_bid_by_bid_id(
&self,
bid_id: entities::BidId,
) -> Option<(entities::Bid<T>, entities::Auction<T>)> {
) -> Option<entities::Bid<T>> {
self.get_in_memory_auctions()
.await
.into_iter()
.find_map(|auction| {
auction
.bids
.iter()
.find(|bid| bid.id == bid_id)
.map(|bid| (bid.clone(), auction.clone()))
})
.find_map(|auction| auction.bids.iter().find(|bid| bid.id == bid_id).cloned())
}
}
64 changes: 31 additions & 33 deletions auction-server/src/auction/service/cancel_bid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,66 +5,64 @@ use {
},
crate::{
api::RestError,
auction::entities::{
self,
BidStatus,
},
auction::entities,
kernel::entities::Svm,
models::Profile,
},
};

#[derive(Debug, Clone)]
pub struct CancelBidInput {
pub bid_id: entities::BidId,
pub profile: Profile,
}

impl Service<Svm> {
async fn cancel_auction_bid_for_lock(
async fn cancel_bid_for_lock(
&self,
bid: entities::Bid<Svm>,
auction: entities::Auction<Svm>,
_lock: entities::BidLock,
input: CancelBidInput,
lock: entities::BidLock,
) -> Result<(), RestError> {
if !bid.status.is_awaiting_signature() {
return Err(RestError::BadParameters(
"Bid is not cancellable".to_string(),
));
}

let tx_hash = bid.chain_data.transaction.signatures[0];
self.update_bid_status(UpdateBidStatusInput {
bid,
new_status: entities::BidStatusSvm::Cancelled {
auction: entities::BidStatusAuction {
id: auction.id,
tx_hash,
},
},
})
.await
}

pub async fn cancel_bid(&self, input: CancelBidInput) -> Result<(), RestError> {
let (bid, auction) = self
let _lock = lock.lock().await;
let bid = self
.repo
.get_in_memory_auction_bid_by_bid_id(input.bid_id)
.await
.ok_or(RestError::BadParameters(
"Bid is not cancellable".to_string(),
"Bid is only cancellable in awaiting_signature state".to_string(),
))?;

if bid.profile_id.ok_or(RestError::Forbidden)? != input.profile.id {
return Err(RestError::Forbidden);
}

match bid.status.clone() {
entities::BidStatusSvm::AwaitingSignature { auction } => {
let tx_hash = bid.chain_data.transaction.signatures[0];
self.update_bid_status(UpdateBidStatusInput {
bid,
new_status: entities::BidStatusSvm::Cancelled {
auction: entities::BidStatusAuction {
id: auction.id,
tx_hash,
},
},
})
.await
}
_ => Err(RestError::BadParameters(
"Bid is only cancellable in awaiting_signature state".to_string(),
)),
}
}

pub async fn cancel_bid(&self, input: CancelBidInput) -> Result<(), RestError> {
// Lock the bid to prevent submission
let bid_lock = self
.repo
.get_or_create_in_memory_bid_lock(input.bid_id)
.await;
let result = self
.cancel_auction_bid_for_lock(bid, auction, bid_lock)
.await;
let result = self.cancel_bid_for_lock(input.clone(), bid_lock).await;
self.repo.remove_in_memory_bid_lock(&input.bid_id).await;
result
}
Expand Down
52 changes: 32 additions & 20 deletions auction-server/src/auction/service/submit_quote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,42 @@ pub struct SubmitQuoteInput {
const DEADLINE_BUFFER: Duration = Duration::from_secs(2);

impl Service<Svm> {
async fn get_bid_to_submit(
&self,
auction_id: entities::AuctionId,
) -> Result<(entities::Auction<Svm>, entities::Bid<Svm>), RestError> {
let auction: entities::Auction<Svm> = self
.get_auction_by_id(GetAuctionByIdInput { auction_id })
.await
.ok_or(RestError::BadParameters("Invalid quote".to_string()))?;

let winner_bid = auction
.bids
.iter()
.find(|bid| bid.status.is_awaiting_signature() || bid.status.is_submitted())
.cloned()
.ok_or(RestError::BadParameters("Invalid quote".to_string()))?;

if winner_bid.status.is_submitted() {
Err(RestError::BadParameters(
"Quote is already submitted".to_string(),
))
} else {
Ok((auction, winner_bid))
}
}

async fn submit_auction_bid_for_lock(
&self,
bid: entities::Bid<Svm>,
auction: entities::Auction<Svm>,
_lock: entities::BidLock,
lock: entities::BidLock,
) -> Result<(), RestError> {
let _lock = lock.lock().await;

// Make sure the bid is still awaiting signature
let _ = self.get_bid_to_submit(auction.id).await?;

let tx_hash = bid.chain_data.transaction.signatures[0];
let auction = self
.repo
Expand Down Expand Up @@ -66,25 +96,7 @@ impl Service<Svm> {
&self,
input: SubmitQuoteInput,
) -> Result<VersionedTransaction, RestError> {
let auction: entities::Auction<Svm> = self
.get_auction_by_id(GetAuctionByIdInput {
auction_id: input.auction_id,
})
.await
.ok_or(RestError::BadParameters("Invalid quote".to_string()))?;

let winner_bid = auction
.bids
.iter()
.find(|bid| bid.status.is_awaiting_signature() || bid.status.is_submitted())
.cloned()
.ok_or(RestError::BadParameters("Invalid quote".to_string()))?;

if winner_bid.status.is_submitted() {
return Err(RestError::BadParameters(
"Quote is already submitted".to_string(),
));
}
let (auction, winner_bid) = self.get_bid_to_submit(input.auction_id).await?;

let mut bid = winner_bid.clone();
let swap_instruction = self
Expand Down
23 changes: 0 additions & 23 deletions create_limo_profile.py

This file was deleted.

Loading

0 comments on commit acfcaae

Please sign in to comment.