Skip to content
JuicePay

JuicePay documentation

Create account

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#

bash
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
json
{
  "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#

ts
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.

ts
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.

bash
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.