Documentation
Swap between assets
Quote, lock, and execute a conversion across chains.
Conversions on JuicePay run in two phases: a quote tells you the price, and an execution consumes it. Keeping those phases distinct is what makes pricing auditable.
Request a quote#
curl -s -X POST "$JUICEPAY_URL/v1/quotes" \
-H "Authorization: Bearer $JUICEPAY_KEY" \
-d '{
"from": { "asset": "USDC", "amount": "40000.00" },
"to": { "asset": "NGNX" },
"ttl": 30
}' | jq{
"id": "qt_3rP8vB",
"from": { "asset": "USDC", "amount": "40000.00" },
"to": { "asset": "NGNX", "amount": "61928400.00" },
"rate": "1548.21",
"venues": [
{ "name": "venue_alpha", "weight": "0.62" },
{ "name": "venue_beta", "weight": "0.28" },
{ "name": "venue_gamma", "weight": "0.10" }
],
"network_fee": "0.41",
"platform_fee": "0.00",
"expires_at": "2026-08-19T09:14:32Z"
}The venues array is not decoration. It is the evidence for the rate, and it is what an auditor asks about when a conversion happened at a price nobody can reproduce.
Execute it#
const execution = await juicepay.swaps.execute(
{ quote: quote.id, destination_chain: "solana" },
{ idempotencyKey: `swap:${quote.id}` },
);
console.log(execution.status); // "executing"Execution is asynchronous. Poll the swap, or wait for swap.settled.
Handle expiry#
Quotes are short-lived by design: 15 to 60 seconds depending on the liquidity of the pair. If you present a price to a user and they take thirty seconds to confirm, you need a fresh quote.
async function executeWithRefresh(quote: Quote, chain: string) {
try {
return await juicepay.swaps.execute(
{ quote: quote.id, destination_chain: chain },
{ idempotencyKey: `swap:${quote.id}` },
);
} catch (error) {
if (isJuicePayError(error, "quote_expired")) {
const refreshed = await juicepay.quotes.create({
from: quote.from,
to: { asset: quote.to.asset },
ttl: 30,
});
return showUpdatedPrice(refreshed);
}
throw error;
}
}Show the customer the new price rather than executing silently at a different rate. A re-quote is a new decision.
Limit orders#
When the price is not urgent, place a limit order and let it work.
curl -s -X POST "$JUICEPAY_URL/v1/limit_orders" \
-H "Authorization: Bearer $JUICEPAY_KEY" \
-H "Idempotency-Key: lo-ngn-aug-001" \
-d '{
"from": { "asset": "USDC", "amount": "40000.00" },
"to": { "asset": "NGNX" },
"target_rate": "1600.00",
"expires_in": 604800,
"partial_fills": true
}'Your funds stay in your account until the order fills — they are not escrowed at a venue. partial_fills lets a thin book fill the order in tranches rather than rejecting it.
Reading the result#
Every execution produces ledger entries you can audit: a debit in the source asset, a credit in the destination asset, and separate entries for network and platform fees. Reconcile against the ledger, not against the quote — the quote is an offer, the ledger is the truth.
Pricing caveats#
- Thin pairs quote for shorter windows and move further. Do not assume a 60-second TTL on a regional stablecoin pair.
- Large orders disclose their size impact in the quote as
price_impact; check it before executing anything above your usual size. - Fees are always itemised. If a fee line is absent, it is zero — we do not fold fees into the rate.