Quickstart: Request for Quotes
Requests for Quotes
RFQs allow solvers to request a trade and receive ready-to-execute transaction calldata. When a solver signs and submits this transaction, their trade will execute atomically.
Solvers may even bundle this transaction with others to atomically complete a multi-step trade such as a Cowswap intent, but how this may be done is beyond the scope of this guide.
We call the API endpoint that serves said calldata an auth server.
The following example demonstrates how to use Renegade's SDK to:
- Request a quote from Renegade's auth server
- Assemble the quote (as calldata) into a transaction
- Submit it on-chain
To get your own values for EXTERNAL_MATCH_KEY and EXTERNAL_MATCH_SECRET,
please contact chris@renegade.fi.
- Rust
- TypeScript
The full Rust source code can be found here.
// 1. Create an external match client
let api_key = std::env::var("EXTERNAL_MATCH_KEY")?;
let api_secret = std::env::var("EXTERNAL_MATCH_SECRET")?;
let ext_client = ExternalMatchClient::new_base_sepolia_client(&api_key, &api_secret)?;
// 2. Ensure the darkpool has approval to spend USDC before requesting a
// quote, so that the settlement tx can be submitted immediately
let private_key = std::env::var("PRIVATE_KEY")?;
let signer = PrivateKeySigner::from_str(&private_key)?;
let provider = ProviderBuilder::new()
.wallet(EthereumWallet::from(signer))
.connect_http(RPC_URL.parse()?);
let darkpool: Address = ext_client.get_exchange_metadata().await?.settlement_contract_address.parse()?;
let input_mint: Address = USDC.parse()?;
let erc20 = IERC20::new(input_mint, &provider);
let allowance = erc20.allowance(provider.default_signer_address(), darkpool).call().await?;
if allowance < U256::from(10_000_000u128) {
erc20.approve(darkpool, U256::MAX).send().await?.watch().await?;
println!("Approved darkpool to spend USDC");
}
// 3. Build an external order
let order = ExternalOrderBuilderV2::new()
.input_mint(USDC)
.output_mint(WETH)
.input_amount(10_000_000) // 10 USDC
.build()?;
// 4. Request a quote
let Some(quote) = ext_client.request_quote_v2(order).await? else {
println!("No quote available");
return Ok(());
};
println!(
"Quote: receive {} of {}",
quote.quote.receive.amount, quote.quote.receive.mint
);
// 5. Assemble the quote into a settlement transaction
let Some(resp) = ext_client.assemble_quote_v2(quote).await? else {
println!("No bundle returned");
return Ok(());
};
// 6. Submit the settlement transaction on-chain
let tx = resp.settlement_tx().with_gas_limit(1_000_000);
let pending = provider.send_transaction(tx).await?;
let receipt = pending.get_receipt().await?;
if receipt.status() {
println!("Settlement tx confirmed: {:#x}", receipt.transaction_hash);
} else {
println!("Settlement tx reverted: {:#x} (bundle may have expired)", receipt.transaction_hash);
}
The full TypeScript source code can be found here.
import { ExternalMatchClient, OrderSide } from "@renegade-fi/renegade-sdk";
import { erc20Abi, createPublicClient, createWalletClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { baseSepolia } from "viem/chains";
const WETH = "0x31a5552AF53C35097Fdb20FFf294c56dc66FA04c";
const USDC = "0xD9961Bb4Cb27192f8dAd20a662be081f546b0E74";
// --- Env setup ---
const API_KEY = process.env.EXTERNAL_MATCH_KEY;
const API_SECRET = process.env.EXTERNAL_MATCH_SECRET;
const privateKey = process.env.PRIVATE_KEY;
if (!API_KEY) throw new Error("EXTERNAL_MATCH_KEY is not set");
if (!API_SECRET) throw new Error("EXTERNAL_MATCH_SECRET is not set");
if (!privateKey) throw new Error("PRIVATE_KEY is not set");
const account = privateKeyToAccount(privateKey as `0x${string}`);
const owner = account.address;
const publicClient = createPublicClient({ chain: baseSepolia, transport: http() });
const walletClient = createWalletClient({ account, chain: baseSepolia, transport: http() });
// 1. Create external match client
const client = ExternalMatchClient.newBaseSepoliaClient(API_KEY, API_SECRET);
// 2. Build order
const order = {
base_mint: WETH,
quote_mint: USDC,
side: OrderSide.BUY,
quote_amount: BigInt(2_000_000), // 2 USDC
} as const;
// 3. Request quote
console.log("Fetching quote...");
const quote = await client.requestQuote(order);
if (!quote) { console.error("No quote available"); process.exit(1); }
console.log(`Quote: receive ${quote.quote.receive.amount} of ${quote.quote.receive.mint}`);
// 4. Assemble quote into settlement tx
console.log("Assembling quote...");
const bundle = await client.assembleQuote(quote);
if (!bundle) { console.error("No bundle available"); process.exit(1); }
const tx = bundle.match_bundle.settlement_tx;
// 5. Check & set ERC20 allowance
const isSell = bundle.match_bundle.match_result.direction === "Sell";
const tokenAddress = isSell
? bundle.match_bundle.match_result.base_mint as `0x${string}`
: bundle.match_bundle.match_result.quote_mint as `0x${string}`;
const amount = isSell
? bundle.match_bundle.match_result.base_amount
: bundle.match_bundle.match_result.quote_amount;
const spender = tx.to as `0x${string}`;
const allowance = await publicClient.readContract({
address: tokenAddress, abi: erc20Abi, functionName: "allowance", args: [owner, spender],
});
if (allowance < amount) {
const approveTx = await walletClient.writeContract({
address: tokenAddress, abi: erc20Abi, functionName: "approve", args: [spender, amount],
});
await publicClient.waitForTransactionReceipt({ hash: approveTx });
console.log("Approved");
}
// 6. Submit settlement tx
console.log("Submitting bundle...");
const hash = await walletClient.sendTransaction({
to: tx.to as `0x${string}`,
data: tx.data as `0x${string}`,
type: "eip1559",
});
console.log("Successfully submitted transaction", hash);